Booleans build the same body every time: face order no longer follows heap addresses (#748)

truck's face and edge IDs are their addresses, so walking maps keyed by them followed heap layout, and the ribbed lid's side-wall hole passed or failed by chance. The planar boolean's rebuild and glue now take truck's connected components in the shell's order, and the planar boolean's own maps are ordered. Healing's union-find keeps the lesser root and takes a run's ends in order of use. The face classification after an intersection orders each component's faces by the shell first. The lid now builds the same body on every run, with a test that builds it six times in one process. Its 0.5 mm side-wall holes, and one sensor enclosure hole of the same kind, now fail every time and are marked failing in the baseline.

Fixes #707.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-13 12:31:23 -04:00
1 parent 58af8accfa
commit a3dfbaa0b0
6 files changed
+95 -32

No files matched your search

@@ -40,8 +40,16 @@ impl<P, C, S> FacesClassification<P, C, S> {
let [and, or, unknown] = self.and_or_unknown();
let and_boundary = and.extract_boundaries();
let or_boundary = or.extract_boundaries();
// SolidCraft: `connected_components` walks a map keyed by face pointers, so each
// component's face order (and with it which boundary edge comes first) followed the
// heap addresses: the same Boolean could classify a component differently from run to
// run. Put each component's faces back in the shell's order first.
let position: HashMap<_, usize> = unknown.face_iter().enumerate().map(|(i, face)| (face.id(), i)).collect();
let components = unknown.connected_components();
for comp in components {
let mut faces: Vec<_> = comp.face_iter().cloned().collect();
faces.sort_by_key(|face| position.get(&face.id()).copied().unwrap_or(usize::MAX));
let comp: Shell<P, C, S> = faces.into_iter().collect();
let boundary = comp.extract_boundaries();
let Some(first) = boundary.first().and_then(|w| w.edge_iter().next()).map(|e| e.id()) else { continue };
let set = if and_boundary.iter().flatten().any(|edge| edge.id() == first) {