fix: cuts no longer split walls they don't touch (#670)

A cut built slab by slab (the exact prism boolean) sewed the result from horizontal
slabs and only joins merged the coplanar pieces back. So a floor vent cut through the
Raspberry Pi case split the long wall into strips at the ports' tops and bottoms
(87 faces instead of 79 at 32 mm), and at 24 mm the end opening split the end wall.

- Prism booleans merge coplanar neighbours for every operation (faces only: edges,
  curves and surfaces stay exact), keeping each face's construction source where all
  its pieces share it, Split Face lines and regularization protection. Painted or
  protected operands keep their authored pieces as before.
- The prism history now tells apart a wall's several edges with its caps (a wall
  with openings meets each opening's floor), so cuts beside existing openings keep
  exact history and faces keep their names.
- Tests: the Pi case's faces pinned after every feature at 24, at 24 changed to 32,
  and built at 32 (each outer side one face); random openings and bosses on a
  shelled case never split another side; every corpus design rebuilds a parameter
  edit like a fresh build and reverts exactly.
- Action matrix baseline: demo/pi-case re-blessed (fewer, merged faces; no
  regressions), demo/pi-case-0.8 recorded, and 88 new passes on
  regressions/lid_ribs_flush_with_window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-13 10:14:52 -04:00
1 parent 9ba72e118b
commit 1e4387f3ea
8 files changed
+1277 -376

No files matched your search

+29
View File
@@ -146,6 +146,35 @@ pub(crate) fn heal_body(body: &Body, solid: Solid, size: f64, keep: &[Vec<Vec3>]
heal_build(solid, size, keep, faces, edges, counts)
}
/// A prism boolean's result `body` with the coplanar pieces its slabs leave merged (nothing
/// else healed: its edges are exact as they are), its history carried through, and what else it carries kept: each face's
/// construction source where every piece merged into it had that same one, its Split Face
/// lines and its regularization protection.
pub(crate) fn heal_prism_result(body: &Body, size: f64, keep: &[Vec<Vec3>], counts: &[(usize, usize)]) -> Result<Body> {
let (faces, edges): (Option<Vec<FaceProv>>, Option<Vec<Vec<EdgeOrigin>>>) =
body.history().map(|h| (h.faces.clone(), h.edges.iter().map(|e| e.origins.clone()).collect())).unzip();
let (healed, map) = crate::heal::merge_faces_traced((*body.solid).clone(), size, keep);
let sources = match (body.face_sources(), &map) {
(Some(sources), Some(m)) => Some(
m.faces
.iter()
.map(|under| {
let mut each = under.iter().map(|i| sources.get(*i).cloned().flatten());
let first = each.next().flatten()?;
each.all(|s| s.as_ref() == Some(&first)).then_some(first)
})
.collect::<Vec<_>>(),
),
_ => None,
};
let carried = faces.zip(edges).zip(map).and_then(|((f, e), m)| m.carry(&f, &e));
let (faces, edges) = carried.unzip();
let mut out = record::build(healed, counts, faces, edges.as_ref())?;
out = out.with_face_sources(sources.map(std::sync::Arc::new)).inherit_regularization_protection(body);
out.splits = body.splits.clone();
Ok(out)
}
/// `body` healed as the local modifiers take it (coplanar neighbours as one face, as after a
/// join; faces split on purpose kept apart), with its history over `[body]`.
pub(crate) fn healed(body: &Body) -> Result<Body> {