From 723c7b61c4b90fb1eff49fdfeb6c3f74505fc45c Mon Sep 17 00:00:00 2001
From: "H. Conrad" <78785932+Ian-cmd-ops@users.noreply.github.com>
Date: Thu, 15 Oct 2026 19:18:31 -0700
Subject: [PATCH] Boolean: cut around a part's sealed voids when the whole cut
fails (#1570)
A cut clear of every void cuts the outer shell alone and puts the voids back (robustness seed 15 now builds).
---
crates/kernel/src/body_lump_tests.rs | 23 +++++++++
crates/kernel/src/ops.rs | 75 ++++++++++++++++++++++++++++
tests/robustness/goldens.json | 5 +-
3 files changed, 101 insertions(+), 2 deletions(-)
diff --git a/crates/kernel/src/body_lump_tests.rs b/crates/kernel/src/body_lump_tests.rs
index 19457af3..89aa73ad 100644
--- a/crates/kernel/src/body_lump_tests.rs
+++ b/crates/kernel/src/body_lump_tests.rs
@@ -37,6 +37,29 @@ fn check_nested(body: &Body) {
}
}
+/// Robustness seed 15, shrunk: a sphere cut at a corner of a part with a sealed internal void far
+/// from the sphere. The same cut on the part without the void works; with it, the boolean failed
+/// ("no result", then "the exact fallback … takes planar faces only"). A cut from a box or a
+/// cylinder on the voided part worked.
+#[test]
+fn sphere_cut_far_from_a_void_keeps_the_void() {
+ let block = box_solid(Vec3::ZERO, Vec3::new(40.0, 40.0, 40.0)).unwrap();
+ let cavity = box_solid(Vec3::new(25.0, 25.0, 25.0), Vec3::new(30.0, 30.0, 30.0)).unwrap();
+ let hollow = boolean(&block, &cavity, BoolOp::Cut).unwrap().unwrap();
+ assert_eq!(hollow.solid.boundaries().len(), 2, "the cavity is a second shell");
+ let ball = sphere(Vec3::new(0.0, 0.0, 40.0), 8.0).unwrap();
+ let cut = boolean(&hollow, &ball, BoolOp::Cut).unwrap().unwrap();
+ // The block, less the cavity, less the eighth of the ball inside the corner.
+ let want = 64_000.0 - 125.0 - std::f64::consts::PI * 512.0 / 6.0;
+ let got = measure(&cut).unwrap().volume;
+ assert!((got - want).abs() < want * 1e-3, "volume {got}, want {want}");
+ assert_eq!(cut.solid.boundaries().len(), 2, "the cavity stays a sealed second shell");
+ let mesh = cut.tessellate_with(0.01, true).unwrap();
+ for (p, material) in [(Vec3::new(27.5, 27.5, 27.5), false), (Vec3::new(1.0, 1.0, 39.0), false), (Vec3::new(20.0, 20.0, 20.0), true)] {
+ assert_eq!(mesh.contains(p), material, "material at {p:?}");
+ }
+}
+
#[test]
fn nested_cavities_stay_with_their_material_islands_for_every_shell_order() {
let body = nested_island();
diff --git a/crates/kernel/src/ops.rs b/crates/kernel/src/ops.rs
index a78e73e9..102b6e98 100644
--- a/crates/kernel/src/ops.rs
+++ b/crates/kernel/src/ops.rs
@@ -332,6 +332,15 @@ fn boolean_via(a: &Body, b: &Body, op: BoolOp, whole: &dyn Fn(&Body, &Body, Bool
}
}
}
+ // A piece with sealed voids, cut clear of them: the outer shell alone, the voids put back.
+ if op == BoolOp::Cut && r.is_err() {
+ let (cut, report) = capture_boolean_diagnostics(|| cut_around_voids(a, b, whole));
+ if let Ok(Some(x)) = cut {
+ r = Ok(Some(x));
+ diagnostics = report;
+ diagnostics.push(BooleanFallback::new(BooleanStrategy::Piecewise));
+ }
+ }
if op == BoolOp::Union
&& let Ok(Some(body)) = &r
&& !preserves_planar_surfaces(a, b, body)
@@ -374,6 +383,72 @@ fn boolean_via(a: &Body, b: &Body, op: BoolOp, whole: &dyn Fn(&Body, &Body, Bool
r
}
+/// The most shells [`cut_around_voids`] takes apart.
+const MAX_VOID_SHELLS: usize = 64;
+
+/// `a` cut by `b` around `a`'s voids: when `a` is one piece with sealed voids (inner shells) and
+/// `b` stays clear of every void, the outer shell alone is cut and the voids are put back. The cut
+/// of the whole failed on such bodies where the same cut of the outer shell works: a sphere cut
+/// far from a void (robustness seed 15). `None` when `a` has no voids, `b` comes near one, or
+/// the cut of the outer shell fails too. The result carries no history.
+fn cut_around_voids(a: &Body, b: &Body, whole: &dyn Fn(&Body, &Body, BoolOp) -> Result