diff --git a/crates/kernel/src/face_source_tests.rs b/crates/kernel/src/face_source_tests.rs index 8c4e16c7de..c46f0008bf 100644 --- a/crates/kernel/src/face_source_tests.rs +++ b/crates/kernel/src/face_source_tests.rs @@ -30,6 +30,9 @@ fn box_certificates_are_six_construction_roles_with_dimension_stable_axes() { // A side far shorter than the body (a 0.3 mm label of a metre): its walls' edges lie // closer together than the sweep's body-sized nearness, and each keeps its own role. (Vec3::new(500., -0.3, 1400.), Vec3::new(1500., 0., 1800.)), + // Far thinner than the body's flatness tolerance (0.1 µm in 14 mm): each cap is in its + // own plane only. + (Vec3::ZERO, Vec3::new(10., 10., 1e-4)), ] { let body = crate::box_solid(a, b).unwrap().with_box_certificate(42).unwrap(); let mut roles = std::collections::HashSet::new(); diff --git a/crates/kernel/src/smlib_sweeps.rs b/crates/kernel/src/smlib_sweeps.rs index 6eb0767668..94cbe3406e 100644 --- a/crates/kernel/src/smlib_sweeps.rs +++ b/crates/kernel/src/smlib_sweeps.rs @@ -272,7 +272,12 @@ mod imp { let m = body.mesh(&crate::MeshTolerance::chord(mesh_tol))?; let faces = face_tris(&m); let n = body.face_count(); - let flat_tol = size * 1e-5 + 1e-9; + // A cap's points lie on its plane to the evaluation's precision. Sized to the body, the + // flatness tolerance can exceed the gap between parallel start and end planes (a box + // 0.1 µm thick in a 14 mm body), which would put each cap in both: it stays under a + // quarter of that gap. + let gap = end.filter(|e| e.normal().cross(start.normal()).len() < 1e-9).map_or(f64::INFINITY, |e| start.height(e.origin).abs()); + let flat_tol = (size * 1e-5 + 1e-9).min(0.25 * gap); let near = 3.0 * mesh_tol + flat_tol; let flat = |f: usize, pl: &Plane| { faces.get(f).is_some_and(|ft| !ft.tris.is_empty() && ft.tris.iter().all(|(t, _)| t.iter().all(|q| pl.height(*q).abs() <= flat_tol)))