Skip to content

kernel: Difference with shared/coplanar boundary faces produces wrong volume #161

@ecto

Description

@ecto

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingkernelRust kernel cratesrobustnessEdge cases / numerical robustness

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions