diff --git a/crates/doc/src/config_shell_tests.rs b/crates/doc/src/config_shell_tests.rs index de07c82b..8990fd3e 100644 --- a/crates/doc/src/config_shell_tests.rs +++ b/crates/doc/src/config_shell_tests.rs @@ -253,6 +253,7 @@ fn saved_former_strip_projection_reports_lost_without_expanding_healthy_material before, depth: 1, mats: vec![], + sheets: vec![], }))); let captured = crate::naming::face_name_at_body(&old_body, Vec3::new(20., 6., 1.)).unwrap(); assert_eq!(crate::naming::parent_piece(&captured), Some(stem.as_str()), "actual strip capture records a piece name"); @@ -376,6 +377,7 @@ fn saved_curved_strip_projection_is_lost_after_exact_wall_coalescence() { before, depth: 1, mats: vec![], + sheets: vec![], }))); let t = std::f64::consts::FRAC_1_SQRT_2; let at = Vec3::new(34. + 6. * t, 24. + 6. * t, 1.); @@ -515,6 +517,7 @@ fn saved_stepped_tab_cut_piece_projection_is_lost_without_expanding_material() { before, depth: 1, mats: vec![], + sheets: vec![], }))); let at = Vec3::new(0., 15., 1.); let former = crate::naming::face_name_at_body(&old_body, at).unwrap(); diff --git a/crates/doc/src/eval.rs b/crates/doc/src/eval.rs index a065ba03..1d9f0000 100644 --- a/crates/doc/src/eval.rs +++ b/crates/doc/src/eval.rs @@ -364,7 +364,8 @@ impl ModelState { fn stamp(&mut self, before: &Arc, f: &Feature, key: u64, mats: Vec) { let feature = f.id; let depth = before.bodies.iter().filter_map(|b| b.names.origin.as_ref().map(|o| o.depth)).max().unwrap_or(0) + 1; - let origin = Arc::new(crate::naming::NamingOrigin { feature: f.clone(), before: before.clone(), depth, mats }); + let sheets = if f.kind.is_sheet() { self.sheets.clone() } else { Vec::new() }; + let origin = Arc::new(crate::naming::NamingOrigin { feature: f.clone(), before: before.clone(), depth, mats, sheets }); // Pieces a cut left are named after their faces, not the kernel's shell order (unless // the feature was saved when they were named in that order). if !f.shell_piece_order { diff --git a/crates/doc/src/naming.rs b/crates/doc/src/naming.rs index cfd38b7a..80e001b2 100644 --- a/crates/doc/src/naming.rs +++ b/crates/doc/src/naming.rs @@ -31,6 +31,8 @@ pub struct NamingOrigin { pub depth: usize, /// Instance transforms of a pattern or mirror (its copies are named after their sources). pub mats: Vec, + /// The sheet metal bodies after a sheet feature (their folds place moved faces). + pub sheets: Vec, } /// The lazily computed names of a body, shared by its clones. @@ -334,6 +336,24 @@ fn geometric_from(b: &ModelBody, o: &NamingOrigin, nf: usize, names_of: fn(&Mode names = got.into_iter().map(|n| n.map(|n| format!("{n}@F{fid}.{}", k + 1))).collect(); } } + // A sheet feature that moved panels (a fold, an unfold): faces it carried along lie on the + // faces they were, taken back through the panel they are on. + if let (Some(after), Some(before), Some(input)) = ( + o.sheets.iter().find(|s| s.body == b.name), + o.before.sheets.iter().find(|s| s.body == b.name), + inputs.iter().find(|(x, _, _)| x.name == b.name), + ) { + let input = std::slice::from_ref(input); + for (name, ps) in names.iter_mut().zip(&pts) { + if name.is_some() { + continue; + } + let back: Option> = ps.iter().map(|p| after.point_before(before, *p)).collect(); + if let Some(back) = back.filter(|b| !b.is_empty()) { + *name = vote(&back, input, tol, None); + } + } + } let empty = Vec::new(); let names: Vec = names .into_iter() diff --git a/crates/doc/src/sheet.rs b/crates/doc/src/sheet.rs index 2a74b70e..ac343153 100644 --- a/crates/doc/src/sheet.rs +++ b/crates/doc/src/sheet.rs @@ -1327,6 +1327,39 @@ impl SheetBody { v } + /// Where a world point of this sheet lay in `before`, the same sheet before a feature + /// changed its folds: the point goes back to flat coordinates through the panel it lies on + /// and is folded again the way `before` folded that material. A panel a fold made was part of + /// the panel it was folded out of. `None` off every panel (on a bend). + pub(crate) fn point_before(&self, before: &SheetBody, p: Vec3) -> Option { + const TOL: f64 = 1e-4; + for (pi, r) in self.panels() { + let Some(inv) = mat_inverse(&self.world(pi)) else { continue }; + let q = apply_point(&inv, p); + if q.z < -TOL || q.z > self.t + TOL { + continue; + } + let q2 = Vec2::new(q.x, q.y); + let pts = dedup(&r.outer.ccw().polyline(1e-3)); + if !point_in(&pts, q2) && !near_outline(&pts, q2, TOL) { + continue; + } + // Panels `before` did not have yet came out of their parent's (bounded walk). + let mut owner = pi; + for _ in 0..self.flanges.len() { + match owner { + Some(i) if i >= before.flanges.len() => owner = self.flanges.get(i).and_then(|f| f.parent), + _ => break, + } + } + if owner.is_some_and(|i| i >= before.flanges.len()) { + return None; + } + return Some(apply_point(&before.world(owner), q)); + } + None + } + /// The panel edge nearest a world point: (panel, edge a, edge b in flat coordinates, on the /// top face?, distance). pub fn edge_at(&self, p: Vec3) -> Option<(Option, Vec2, Vec2, bool, f64)> { @@ -2409,6 +2442,19 @@ fn merge_loops(polys: &[Vec], work: &mut FlatWork) -> Result Ok(loops) } +/// Whether `q` lies within `tol` of the closed polygon `poly`'s outline. +fn near_outline(poly: &[Vec2], q: Vec2, tol: f64) -> bool { + let m = poly.len(); + (0..m).any(|i| match (poly.get(i), poly.get((i + 1) % m)) { + (Some(&a), Some(&b)) => { + let d = b - a; + let t = if d.len2() > 1e-18 { ((q - a).dot(d) / d.len2()).clamp(0.0, 1.0) } else { 0.0 }; + (a + d * t).dist(q) < tol + } + _ => false, + }) +} + fn signed_area(p: &[Vec2]) -> f64 { let m = p.len(); (0..m).filter_map(|i| Some(p.get(i)?.cross(*p.get((i + 1) % m)?))).sum::() / 2.0 diff --git a/crates/engine/src/cmd/sheet_tests.rs b/crates/engine/src/cmd/sheet_tests.rs index e4520911..ed3c8d25 100644 --- a/crates/engine/src/cmd/sheet_tests.rs +++ b/crates/engine/src/cmd/sheet_tests.rs @@ -2478,7 +2478,6 @@ fn hole_face_names(s: &mut Session) -> Vec { } #[test] -#[ignore = "fails until #535 is fixed"] fn a_hole_on_a_folded_panel_keeps_the_name_of_its_sketch_circle() { // A Ø10 hole at x = 20 on a 100 x 60 plate; the fold at x = 40 stands that (smaller) side up. let mut s = Session::default();