-
Notifications
You must be signed in to change notification settings - Fork 14
Open
Description
Summary
When two solids touch on a coplanar face, boolean_union keeps the shared interface as two opposite-facing faces instead of removing it.
This creates an internal partition inside the union result:
- topology is non-manifold (junction edges have more than 2 incident faces)
surface_areais overcountedvolumecan still appear correct
Minimal reproduction
[union [cube 10 10 25] [translate 0 0 25 [cube 10 10 35]]]
Two cuboids are stacked and share the entire plane z = 25.
Expected
Result should be identical to a single 10 x 10 x 60 cuboid:
- no internal faces
- manifold boundary
surface_area = 2600
Actual
- result contains 12 faces
- two extra faces lie on the internal interface plane (
z = 25), with opposite normals surface_area = 2800(+200 = 2 x (10 x 10))
Why this is a bug
Keeping both sides of the mating coplanar interface means interior geometry is emitted as boundary geometry.
That is invalid for a closed solid boundary representation:
- internal partitions must not appear in the outer shell
- each boundary edge should be shared by exactly 2 boundary faces
Impact
- non-manifold output from a union operation
- incorrect
surface_area - can break downstream CAD/mesh workflows that require valid closed solids
Larger repro (partial overlap)
[union [cube 50 30 5] [translate 0 0 5 [cube 5 30 40]]]
Expected clean L-shape boundary.
Actual result includes two spurious internal faces at z = 5 in the column footprint (x = 0..5, y = 0..30), producing non-manifold junction edges.
Reference test
Related test on GitHub:
Inline test code (self-contained in this report):
#[test]
fn test_union_coplanar_no_internal_faces() {
let bottom = make_cube(10.0, 10.0, 25.0);
let mut top = make_cube(10.0, 10.0, 35.0);
translate_brep(&mut top, 0.0, 0.0, 25.0);
let result = boolean_op(&bottom, &top, BooleanOp::Union, 32);
// Volume should be correct: 10 * 10 * 60 = 6000
let mesh = result.to_mesh(32);
let volume = compute_mesh_volume(&mesh);
assert!(
(volume - 6000.0).abs() < 10.0,
"Union volume should be 6000, got {:.1}",
volume
);
// The outer shell must have exactly 6 faces (no internal partition)
let brep = result
.as_brep()
.expect("coplanar box union should produce BRep, not mesh fallback");
let solid = &brep.topology.solids[brep.solid_id];
let shell = &brep.topology.shells[solid.outer_shell];
assert_eq!(
shell.faces.len(),
6,
"Expected 6 outer faces for a 10x10x60 cuboid, got {} \
(internal coplanar faces not removed)",
shell.faces.len()
);
}Notes
- Coplanar side-face splitting by itself is cosmetic.
- The critical defect is retention of the internal coplanar interface faces.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels