From f87f22874f4bec36485ec2209aec699fdfd63199 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Sat, 31 Oct 2026 08:57:45 -0400 Subject: [PATCH] Modify history tests: shell, offset and thicken are one test each through the public API, with the volumes, refusals and history the SMLib copies checked Co-Authored-By: Claude Opus 5.5 --- crates/kernel/src/modify_history_tests.rs | 115 +++++++--------------- 1 file changed, 34 insertions(+), 81 deletions(-) diff --git a/crates/kernel/src/modify_history_tests.rs b/crates/kernel/src/modify_history_tests.rs index 7c5b503a59..45d2d36f2a 100644 --- a/crates/kernel/src/modify_history_tests.rs +++ b/crates/kernel/src/modify_history_tests.rs @@ -55,6 +55,10 @@ fn face_at(b: &Body, p: Vec3) -> usize { (0..b.face_count()).min_by(|x, y| to_face(&m, *x, p).total_cmp(&to_face(&m, *y, p))).unwrap() } +fn volume(b: &Body) -> f64 { + crate::measure(b).unwrap().volume +} + fn cube() -> Body { box_solid(Vec3::ZERO, Vec3::new(40.0, 30.0, 20.0)).unwrap() } @@ -227,6 +231,8 @@ fn shell_lines_the_walls_with_offsets() { let b = cube(); let top = Vec3::new(20.0, 15.0, 20.0); let r = crate::shell(&b, &[top], 2.0).unwrap(); + // The cavity is 36 x 26 x 18 (the floor 2 thick, the top open). + assert!((volume(&r) - (24000.0 - 36.0 * 26.0 * 18.0)).abs() < 1e-3, "{}", volume(&r)); let h = verified(&r, &b, reach(2.0)); // Five inner walls, each an offset of the face it lines; the top is left as a rim. let top = face_at(&b, top); @@ -236,7 +242,7 @@ fn shell_lines_the_walls_with_offsets() { assert_eq!(h.faces[walls[0]].change, Change::Generated); } assert!(with(h, &[FaceOrigin::Offset { operand: 0, face: top }]).is_empty()); - assert_eq!(changes(h), [0, 6, 0, 0, 5]); + assert_eq!(changes(h), [0, 6, 0, 0, 5], "{h:?}"); } #[test] @@ -306,10 +312,17 @@ fn offset_moves_a_face() { let b = ell(); let top = Vec3::new(5.0, 5.0, 20.0); let r = crate::offset_faces(&b, &[top], 3.0).unwrap(); + // The L's plan is 40 x 15 + 15 x 15 = 825; 23 high. + assert!((volume(&r) - 825.0 * 23.0).abs() < 1e-3, "{}", volume(&r)); let h = verified(&r, &b, Reach { made: 0.0, moved: 3.0 * 1.1 }); assert_eq!(h.faces.len(), b.face_count()); - assert_eq!(with(h, &[face(face_at(&b, top))]).len(), 1); + let moved = with(h, &[face(face_at(&b, top))]); + assert_eq!(moved.len(), 1); + assert_eq!(h.faces[moved[0]].change, Change::Modified); assert!(h.edges.iter().all(|e| matches!(e.origins.as_slice(), [EdgeOrigin::Edge { .. }]))); + // Into the body by its whole height: nothing is left behind the face. + let e = crate::offset_faces(&b, &[top], -20.0).unwrap_err(); + assert!(e.to_string().contains("no room"), "{e}"); } #[test] @@ -403,10 +416,24 @@ fn surfaces_keep_their_faces() { fn thicken_offsets_the_surface() { let s = crate::patch_region(&Plane::XY, &Region2 { outer: square(0.0, 0.0, 10.0, 10.0), holes: vec![] }).unwrap(); let t = crate::thicken(&s, 2.0, false).unwrap(); + assert!((volume(&t) - 200.0).abs() < 1e-6, "{}", volume(&t)); + // Along the sheet's normal (+Z). + let m = t.mesh(&crate::MeshTolerance::chord(TOL)).unwrap(); + let (lo, hi) = m.positions.iter().fold((f64::MAX, f64::MIN), |(lo, hi), p| (lo.min(p.z), hi.max(p.z))); + assert!(lo.abs() < 1e-9 && (hi - 2.0).abs() < 1e-9, "z {lo}..{hi}"); let h = verified(&t, &s, reach(2.0)); - assert_eq!(with(h, &[face(0)]).len(), 1); - assert_eq!(with(h, &[FaceOrigin::Offset { operand: 0, face: 0 }]).len(), 1); - assert_eq!(blends(h).len(), 4); + assert_eq!(with(h, &[face(0)]).len(), 1, "{h:?}"); + assert_eq!(with(h, &[FaceOrigin::Offset { operand: 0, face: 0 }]).len(), 1, "{h:?}"); + assert_eq!(blends(h).len(), 4, "{h:?}"); + let both = crate::thicken(&s, 2.0, true).unwrap(); + let m = both.mesh(&crate::MeshTolerance::chord(TOL)).unwrap(); + let (lo, hi) = m.positions.iter().fold((f64::MAX, f64::MIN), |(lo, hi), p| (lo.min(p.z), hi.max(p.z))); + assert!((lo + 1.0).abs() < 1e-9 && (hi - 1.0).abs() < 1e-9, "z {lo}..{hi}"); + let h = verified(&both, &s, reach(1.0)); + assert_eq!(with(h, &[FaceOrigin::Offset { operand: 0, face: 0 }]).len(), 2, "{h:?}"); + assert_eq!(blends(h).len(), 4, "{h:?}"); + let against = crate::thicken(&s, -2.0, false).unwrap(); + assert!((volume(&against) - 200.0).abs() < 1e-6); } #[test] @@ -494,44 +521,13 @@ fn chamfer_after_box_cuts() { } } -/// Shell, offset faces and thicken through SMLib (`crate::offset_backend`), their histories -/// checked against the geometry as truck's are, and their numbers against the analytic ones. +/// Shell, offset faces and draft straight through `crate::offset_backend`: refusals, and their +/// histories checked against the geometry and their numbers against the analytic ones. #[cfg(feature = "smlib")] mod smlib { use super::*; use crate::offset_backend; - fn volume(b: &Body) -> f64 { - crate::measure(b).unwrap().volume - } - - #[test] - fn shell_lines_the_walls_with_offsets() { - let b = cube(); - let top = Vec3::new(20.0, 15.0, 20.0); - let r = offset_backend::shell(&b, &[top], 2.0).unwrap(); - // The cavity is 36 x 26 x 18 (the floor 2 thick, the top open). - assert!((volume(&r) - (24000.0 - 36.0 * 26.0 * 18.0)).abs() < 1e-3, "{}", volume(&r)); - let h = verified(&r, &b, reach(2.0)); - let top = face_at(&b, top); - for f in (0..6).filter(|f| *f != top) { - let walls = with(h, &[FaceOrigin::Offset { operand: 0, face: f }]); - assert_eq!(walls.len(), 1, "{h:?}"); - assert_eq!(h.faces[walls[0]].change, Change::Generated); - } - assert!(with(h, &[FaceOrigin::Offset { operand: 0, face: top }]).is_empty()); - assert_eq!(changes(h), [0, 6, 0, 0, 5], "{h:?}"); - } - - #[test] - fn shell_of_a_body_that_is_not_convex() { - let b = ell(); - let r = offset_backend::shell(&b, &[Vec3::new(5.0, 5.0, 20.0)], 2.0).unwrap(); - let h = verified(&r, &b, reach(2.0 * 1.5)); - let offsets = h.faces.iter().filter(|f| matches!(f.origins.as_slice(), [FaceOrigin::Offset { .. }])).count(); - assert_eq!(offsets, 7, "{h:?}"); - } - /// tangency-labs/solidcore#380: a closed shell whose wall leaves no room inside the body is /// refused before SMLib's offset sees it, by the body's own inradius (the L's legs are 15 /// wide, the largest ball fits at the inner corner: 15 * sqrt(2) / (1 + sqrt(2)) = 8.79), @@ -573,25 +569,6 @@ mod smlib { assert!(e.contains("the 1.5 mm walls would meet along a line"), "{e}"); } - #[test] - fn offset_moves_a_face() { - let b = ell(); - let top = Vec3::new(5.0, 5.0, 20.0); - let healed = crate::boolean_record::healed(&b).unwrap(); - let chosen = [face_at(&healed, top)]; - let r = offset_backend::offset_faces(&healed, &chosen, 3.0).unwrap(); - // The L's plan is 40 x 15 + 15 x 15 = 825; 23 high. - assert!((volume(&r) - 825.0 * 23.0).abs() < 1e-3, "{}", volume(&r)); - let h = verified(&r, &healed, Reach { made: 0.0, moved: 3.0 * 1.1 }); - assert_eq!(h.faces.len(), healed.face_count()); - let moved = with(h, &[face(chosen[0])]); - assert_eq!(moved.len(), 1); - assert_eq!(h.faces[moved[0]].change, Change::Modified); - // Into the body by its whole height: nothing is left behind the face. - let e = offset_backend::offset_faces(&healed, &chosen, -20.0).unwrap_err(); - assert!(e.to_string().contains("no room"), "{e}"); - } - #[test] fn draft_tilts_a_side_and_keeps_its_name() { let b = cube(); @@ -616,28 +593,4 @@ mod smlib { let e = offset_backend::draft(&b, &[Vec3::new(20.0, 15.0, 20.0)], &Plane::XY, Vec3::Z, 5.0 * deg).unwrap_err(); assert!(e.to_string().contains("not supported yet"), "{e}"); } - - #[test] - fn thicken_offsets_the_surface() { - let s = crate::patch_region(&Plane::XY, &Region2 { outer: square(0.0, 0.0, 10.0, 10.0), holes: vec![] }).unwrap(); - let t = offset_backend::thicken(&s, 2.0, false).unwrap(); - assert!((volume(&t) - 200.0).abs() < 1e-6, "{}", volume(&t)); - // Along the sheet's normal (+Z). - let m = t.mesh(&crate::MeshTolerance::chord(TOL)).unwrap(); - let (lo, hi) = m.positions.iter().fold((f64::MAX, f64::MIN), |(lo, hi), p| (lo.min(p.z), hi.max(p.z))); - assert!(lo.abs() < 1e-9 && (hi - 2.0).abs() < 1e-9, "z {lo}..{hi}"); - let h = verified(&t, &s, reach(2.0)); - assert_eq!(with(h, &[face(0)]).len(), 1, "{h:?}"); - assert_eq!(with(h, &[FaceOrigin::Offset { operand: 0, face: 0 }]).len(), 1, "{h:?}"); - assert_eq!(blends(h).len(), 4, "{h:?}"); - let both = offset_backend::thicken(&s, 2.0, true).unwrap(); - let m = both.mesh(&crate::MeshTolerance::chord(TOL)).unwrap(); - let (lo, hi) = m.positions.iter().fold((f64::MAX, f64::MIN), |(lo, hi), p| (lo.min(p.z), hi.max(p.z))); - assert!((lo + 1.0).abs() < 1e-9 && (hi - 1.0).abs() < 1e-9, "z {lo}..{hi}"); - let h = verified(&both, &s, reach(1.0)); - assert_eq!(with(h, &[FaceOrigin::Offset { operand: 0, face: 0 }]).len(), 2, "{h:?}"); - assert_eq!(blends(h).len(), 4, "{h:?}"); - let against = offset_backend::thicken(&s, -2.0, false).unwrap(); - assert!((volume(&against) - 200.0).abs() < 1e-6); - } }