Sketch on every side wall of one sketch extruded up and down (#957)
* Name a side drawn on several sketch curves by all of them An extrude named each side wall after the one sketch curve its profile segment lies on. A wall along two coincident lines (squares drawn edge to edge) or along collinear lines merged into one profile segment had no single curve, so it was named from geometry and could not carry a sketch attachment. One sketch extruded up and down and joined (the logo) refused a sketch on every one of its side walls. Such a side is now named by every curve it lies on, in sketch order: the first is its name, the others aliases. The set is fixed by the sketch, so the name is exact. The refusal message no longer blames merged faces. Adds the reported logo design and a synthetic up-and-down join to the action-matrix designs, and a regression test that sketches and extrudes on every flat face, fresh and after save and reopen. Fixes #954 Co-Authored-By: Claude Opus 5.5 <[email protected]> * Action matrix: record the up-and-down join designs Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
9f76cca8b4
commit
ea23401710
9 files changed
+305
-13
No files matched your search
@@ -457,6 +457,46 @@ impl<'a> ExactSketchCurves<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSketchCurves<'_> {
|
||||
/// The sketch curves a generated segment is drawn on: [`find_along`](Self::find_along)'s one
|
||||
/// curve, else every drawn curve that contains it or runs along and overlaps it (projections
|
||||
/// only where no drawn curve does), in the sketch's order. A segment two coincident curves
|
||||
/// were drawn over (four squares drawn edge to edge), or one a profile merged from collinear
|
||||
/// curves (the outline of several adjacent profiles extruded together), is drawn on all of
|
||||
/// them: the set is exact and fixed by the sketch, so its face is named by it (the first its
|
||||
/// name, the others aliases) rather than not at all. Empty when it lies on none.
|
||||
pub(crate) fn find_all_along(&self, generated: &Seg2) -> Vec<String> {
|
||||
if let Some(id) = self.find_along(generated) {
|
||||
return vec![id];
|
||||
}
|
||||
let runs_along = |original: &Seg2| {
|
||||
if exact_segment_contains(original, generated) {
|
||||
return true;
|
||||
}
|
||||
let (Seg2::Line { a, b }, Seg2::Line { a: c, b: d }) = (*original, *generated) else { return false };
|
||||
let direction = b - a;
|
||||
let scale = direction.len().max((d - c).len()).max(1.);
|
||||
if direction.len() <= 1e-12
|
||||
|| direction.cross(c - a).abs() > 1e-10 * scale * scale
|
||||
|| direction.cross(d - a).abs() > 1e-10 * scale * scale
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let (tc, td) = ((c - a).dot(direction) / direction.len2(), (d - a).dot(direction) / direction.len2());
|
||||
tc.min(td).max(0.) < tc.max(td).min(1.) - 1e-9
|
||||
};
|
||||
let pick = |linked: bool| -> Vec<String> {
|
||||
self.segments
|
||||
.iter()
|
||||
.filter(|(_, l, originals)| *l == linked && originals.iter().any(runs_along))
|
||||
.map(|(id, _, _)| (*id).to_owned())
|
||||
.collect()
|
||||
};
|
||||
let drawn = pick(false);
|
||||
if drawn.is_empty() { pick(true) } else { drawn }
|
||||
}
|
||||
}
|
||||
|
||||
fn exact_segment_contains(original: &Seg2, generated: &Seg2) -> bool {
|
||||
match (original, generated) {
|
||||
(Seg2::Line { a, b }, Seg2::Line { a: c, b: d }) => {
|
||||
|
||||
@@ -207,23 +207,30 @@ impl<'a> ProfileRoles<'a> {
|
||||
let segments: Vec<&solidcraft_geom::Seg2> =
|
||||
self.region.map(|r| std::iter::once(&r.outer).chain(&r.holes).flat_map(|l| &l.segs).collect()).unwrap_or_default();
|
||||
let curves = self.sketch.map(crate::config::ExactSketchCurves::new);
|
||||
let curve = |segment: usize| -> Option<String> { curves.as_ref()?.find_along(segments.get(segment)?).map(|id| format!("side:{id}")) };
|
||||
// Every curve the segment is drawn on (one, or several coincident or collinear ones).
|
||||
let curve = |segment: usize| -> Vec<String> {
|
||||
let (Some(curves), Some(segment)) = (curves.as_ref(), segments.get(segment)) else { return Vec::new() };
|
||||
curves.find_all_along(segment).into_iter().map(|id| format!("side:{id}")).collect()
|
||||
};
|
||||
let roles = history
|
||||
.faces
|
||||
.iter()
|
||||
.map(|face| {
|
||||
let mut roles: Vec<String> = Vec::new();
|
||||
for origin in &face.origins {
|
||||
let role = match *origin {
|
||||
let found = match *origin {
|
||||
FaceOrigin::Profile { segment } => curve(segment),
|
||||
FaceOrigin::Cap { end } => self.caps.get(usize::from(end)).cloned().flatten(),
|
||||
_ => None,
|
||||
FaceOrigin::Cap { end } => self.caps.get(usize::from(end)).cloned().flatten().into_iter().collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
match role {
|
||||
Some(role) if !roles.contains(&role) => roles.push(role),
|
||||
Some(_) => {}
|
||||
// One unknown origin leaves the face unknown: never a partial guess.
|
||||
None => return Vec::new(),
|
||||
// One unknown origin leaves the face unknown: never a partial guess.
|
||||
if found.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
for role in found {
|
||||
if !roles.contains(&role) {
|
||||
roles.push(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
roles
|
||||
|
||||
Reference in new issue
Block a user