Summary
Difference between two cuboids that share one or more boundary faces returns a topologically-valid solid with a wrong volume. The valid_solid and bbox checks pass, mass_props doesn't. Two distinct mecheval tasks surface this and both report deterministic wrong volumes across every model and every run.
Reproduction A — single shared face
mecheval/tasks/a2-cube-with-pocket-01.json: 40mm cube with a 20×20×10 pocket cut from the top. The pocket's top face at z=40 is coplanar with the cube's top face.
{
"version": "0.1",
"nodes": {
"1": {"id": 1, "name": "cube", "op": {"type": "Cube", "size": {"x": 40, "y": 40, "z": 40}}},
"2": {"id": 2, "name": "cube_c", "op": {"type": "Translate", "child": 1, "offset": {"x": -20, "y": -20, "z": 0}}},
"3": {"id": 3, "name": "pocket", "op": {"type": "Cube", "size": {"x": 20, "y": 20, "z": 10}}},
"4": {"id": 4, "name": "pkt_p", "op": {"type": "Translate", "child": 3, "offset": {"x": -10, "y": -10, "z": 30}}},
"5": {"id": 5, "name": "result", "op": {"type": "Difference", "left": 2, "right": 4}}
},
"materials": {}, "part_materials": {},
"roots": [{"root": 5, "material": "default"}]
}
- Expected volume:
40³ − 20·20·10 = 60000 mm³
- Actual volume: 57333.33 mm³ (deviation ≈ 4.44%, deterministic — 22 of 22 non-parse runs report this exact value)
Reproduction B — multiple shared faces
mecheval/tasks/a2-stepped-block-01.json: 50×30×20 block minus a 25×30×10 corner cut. Cut shares four faces with the block (top, +X, +Y, −Y).
{
"version": "0.1",
"nodes": {
"1": {"id": 1, "name": "block", "op": {"type": "Cube", "size": {"x": 50, "y": 30, "z": 20}}},
"2": {"id": 2, "name": "blk_c", "op": {"type": "Translate", "child": 1, "offset": {"x": -25, "y": -15, "z": 0}}},
"3": {"id": 3, "name": "cut", "op": {"type": "Cube", "size": {"x": 25, "y": 30, "z": 10}}},
"4": {"id": 4, "name": "cut_p", "op": {"type": "Translate", "child": 3, "offset": {"x": 0, "y": -15, "z": 10}}},
"5": {"id": 5, "name": "result", "op": {"type": "Difference", "left": 2, "right": 4}}
},
"materials": {}, "part_materials": {},
"roots": [{"root": 5, "material": "default"}]
}
- Expected volume:
50·30·20 − 25·30·10 = 22500 mm³
- Actual volume: 17500 mm³ (deviation ≈ 22%, deterministic — 21 of 21 non-parse runs report this exact value)
In both cases valid_solid and bbox pass — the kernel produces a manifold solid with the correct extent. Only the integrated mesh volume is wrong.
Probable root cause
split.rs / trim.rs likely has a face-classification path that marks coplanar coincident faces (the pocket/cut sharing a plane with the outer block) as OnSame and decides whether to keep them by checking only one operand. When the difference's "right" operand should contribute an inverted face (the inner pocket bottom replacing the lost outer top), the inversion is missing — leading to an interior face that's either dropped or oriented wrong, but still chains into a closed loop that fools manifold validation.
Divergence-theorem volume math suggests the kernel is leaving the pocket's four side walls out of the surface integral — that exactly accounts for the 2667 mm³ deficit in repro A (the prism sum (1/3) ∫ x·n dA over the four 20×10 side walls). The stepped-block case's 5000 mm³ deficit is similarly consistent with several missing/wrong-oriented interior faces.
Acceptance
- Volume of cube-with-pocket returns
60000 ± 0.1%
- Volume of stepped-block returns
22500 ± 0.1%
- Add a regression test in
crates/vcad-kernel-booleans/tests/ covering Cube - Cube where the cut shares 1, 2, 3, and 4 faces with the outer cube.
Affected mecheval tasks
Refs
Summary
Differencebetween two cuboids that share one or more boundary faces returns a topologically-valid solid with a wrong volume. The valid_solid and bbox checks pass, mass_props doesn't. Two distinct mecheval tasks surface this and both report deterministic wrong volumes across every model and every run.Reproduction A — single shared face
mecheval/tasks/a2-cube-with-pocket-01.json: 40mm cube with a 20×20×10 pocket cut from the top. The pocket's top face at z=40 is coplanar with the cube's top face.{ "version": "0.1", "nodes": { "1": {"id": 1, "name": "cube", "op": {"type": "Cube", "size": {"x": 40, "y": 40, "z": 40}}}, "2": {"id": 2, "name": "cube_c", "op": {"type": "Translate", "child": 1, "offset": {"x": -20, "y": -20, "z": 0}}}, "3": {"id": 3, "name": "pocket", "op": {"type": "Cube", "size": {"x": 20, "y": 20, "z": 10}}}, "4": {"id": 4, "name": "pkt_p", "op": {"type": "Translate", "child": 3, "offset": {"x": -10, "y": -10, "z": 30}}}, "5": {"id": 5, "name": "result", "op": {"type": "Difference", "left": 2, "right": 4}} }, "materials": {}, "part_materials": {}, "roots": [{"root": 5, "material": "default"}] }40³ − 20·20·10 = 60000 mm³Reproduction B — multiple shared faces
mecheval/tasks/a2-stepped-block-01.json: 50×30×20 block minus a 25×30×10 corner cut. Cut shares four faces with the block (top, +X, +Y, −Y).{ "version": "0.1", "nodes": { "1": {"id": 1, "name": "block", "op": {"type": "Cube", "size": {"x": 50, "y": 30, "z": 20}}}, "2": {"id": 2, "name": "blk_c", "op": {"type": "Translate", "child": 1, "offset": {"x": -25, "y": -15, "z": 0}}}, "3": {"id": 3, "name": "cut", "op": {"type": "Cube", "size": {"x": 25, "y": 30, "z": 10}}}, "4": {"id": 4, "name": "cut_p", "op": {"type": "Translate", "child": 3, "offset": {"x": 0, "y": -15, "z": 10}}}, "5": {"id": 5, "name": "result", "op": {"type": "Difference", "left": 2, "right": 4}} }, "materials": {}, "part_materials": {}, "roots": [{"root": 5, "material": "default"}] }50·30·20 − 25·30·10 = 22500 mm³In both cases
valid_solidandbboxpass — the kernel produces a manifold solid with the correct extent. Only the integrated mesh volume is wrong.Probable root cause
split.rs/trim.rslikely has a face-classification path that marks coplanar coincident faces (the pocket/cut sharing a plane with the outer block) asOnSameand decides whether to keep them by checking only one operand. When the difference's "right" operand should contribute an inverted face (the inner pocket bottom replacing the lost outer top), the inversion is missing — leading to an interior face that's either dropped or oriented wrong, but still chains into a closed loop that fools manifold validation.Divergence-theorem volume math suggests the kernel is leaving the pocket's four side walls out of the surface integral — that exactly accounts for the 2667 mm³ deficit in repro A (the prism sum
(1/3) ∫ x·n dAover the four 20×10 side walls). The stepped-block case's 5000 mm³ deficit is similarly consistent with several missing/wrong-oriented interior faces.Acceptance
60000 ± 0.1%22500 ± 0.1%crates/vcad-kernel-booleans/tests/coveringCube - Cubewhere the cut shares 1, 2, 3, and 4 faces with the outer cube.Affected mecheval tasks
a2-cube-with-pocket-01— 0/30 pass-ratea2-stepped-block-01— 0/30 pass-rateRefs