diff --git a/crates/kernel/src/boolean_history_tests.rs b/crates/kernel/src/boolean_history_tests.rs index 13dd0b39..900fe78c 100644 --- a/crates/kernel/src/boolean_history_tests.rs +++ b/crates/kernel/src/boolean_history_tests.rs @@ -484,3 +484,20 @@ fn bore_union_of_a_bore_in_two_arcs_has_no_history() { let r = crate::coplanar::bore_union(&floor, &ring, &|_| true).expect("the bores are recut"); assert!(r.history().is_none()); } + +/// Bodies clearly apart, joined as one body of several pieces (`union_apart`): every face and +/// edge is its piece's own, kept. +#[test] +fn bodies_apart_joined_keep_their_faces() { + let pieces = [boxed(0.0, 0.0, 0.0, 2.0, 2.0, 2.0), boxed(5.0, 0.0, 0.0, 7.0, 2.0, 2.0), boxed(10.0, 0.0, 0.0, 12.0, 2.0, 2.0)]; + let r = crate::union_apart(&pieces).unwrap().unwrap(); + let operands: Vec<&Body> = pieces.iter().collect(); + let h = verified(&r, &operands, 0.01); + assert_eq!(h.operands, 3); + for (k, piece) in pieces.iter().enumerate() { + for f in 0..piece.face_count() { + assert_eq!(from(h, face(k, f)).len(), 1, "{}", show(h)); + } + } + assert!(faces_of(h).iter().all(|(change, origins)| *change == Change::Kept && origins.len() == 1), "{}", show(h)); +} diff --git a/crates/kernel/src/ops.rs b/crates/kernel/src/ops.rs index 3355ba4d..e5027cd4 100644 --- a/crates/kernel/src/ops.rs +++ b/crates/kernel/src/ops.rs @@ -672,7 +672,22 @@ pub fn union_apart(bodies: &[Body]) -> Result> { } } } - join_pieces(bodies.to_vec()) + // The pieces stay as they are, side by side: each face and edge of the result is its piece's + // own (the history over `bodies`). + let Some(joined) = join_pieces(bodies.to_vec())? else { return Ok(None) }; + let operands: Vec<&Body> = bodies.iter().collect(); + let faces = bodies + .iter() + .enumerate() + .flat_map(|(operand, b)| { + (0..b.face_count()).map(move |face| (std::collections::BTreeSet::from([crate::history::FaceOrigin::Face { operand, face }]), true)) + }) + .collect(); + let mut edges = HashMap::new(); + for (operand, b) in bodies.iter().enumerate() { + edges.extend(record::edge_origins(&b.solid, operand)); + } + Ok(Some(record::traced(joined, &record::counts(&operands), Some(record::regions(faces)), Some(&edges)))) } /// Bodies (apart from each other) as one body of several pieces.