Skip to content

Fix union()/difference()/intersection() dropping all geometry when one operand is invalid - #71

Merged
revarbat merged 1 commit into
mainfrom
fix-invalid-csg-operand-poisons-union
Aug 2, 2026
Merged

Fix union()/difference()/intersection() dropping all geometry when one operand is invalid#71
revarbat merged 1 commit into
mainfrom
fix-invalid-csg-operand-poisons-union

Conversation

@revarbat

@revarbat revarbat commented Aug 2, 2026

Copy link
Copy Markdown
Member

Summary

  • generateCsg fed every foreground body directly into Manifold's +/-/^ boolean operators. Manifold propagates a non-NoError Status() (e.g. NonFiniteVertex, typically from a degenerate accumulated transform somewhere upstream) onto the whole combined result instead of treating that one operand as an empty no-op contributor — so a single already-invalid part silently zeroed out every valid sibling in the same union().
  • Found via a real user project (snappy-reprap): an entire ~65-part extruder assembly, positioned deep inside several generations of indexed children()-forwarding, vanished from the render with no warning at all — while the exact same assembly rendered fine called directly/shallowly. Real OpenSCAD's CGAL backend doesn't hit this on the same input.
  • Fix: filter out any operand whose own Status() isn't NoError before handing the group to Manifold's boolean ops, matching this evaluator's existing "invalid/empty operand doesn't poison the whole op" precedent for genuinely-empty statements.

Test plan

  • New regression test (Union.OneInvalidOperandDoesNotDiscardValidSiblings) reproduces the exact mechanism with a deliberately NaN-vertex polyhedron() unioned alongside a valid cube() — confirmed it fails on the pre-fix code (volume 0 instead of 8) and passes after the fix
  • Full 747-test differential suite passes both OSCAD_BYTECODE_VM=0 and =1
  • Re-ran real user project snappy-reprap/full_assembly.scad: the extruder assembly (a real, non-degenerate 22,895-vertex merged body) now appears; body count 507→508, warnings unchanged at 2 (pre-existing/unrelated)

🤖 Generated with Claude Code

…e operand is invalid

generateCsg's boolean merge fed every foreground body straight into
Manifold's +/-/^ operators. Manifold propagates a non-NoError
Status() (e.g. NonFiniteVertex, typically from a degenerate
accumulated transform somewhere upstream) onto the WHOLE combined
result instead of treating that one operand as an empty no-op
contributor -- so a single already-invalid part silently zeroed out
every valid sibling in the same union().

Found via a real user project (snappy-reprap): an entire ~65-part
extruder assembly, positioned deep inside several generations of
indexed children()-forwarding (the z_tower_assembly chain), vanished
from the render with no warning at all, while the exact same assembly
rendered fine called directly/shallowly. Real OpenSCAD's CGAL backend
doesn't hit this on the same input.

Fix: filter out any operand whose own Status() isn't NoError before
handing the group to Manifold's boolean ops, matching this
evaluator's existing "invalid/empty operand doesn't poison the whole
op" precedent for genuinely-empty statements.

Bump to 0.13.10.
@revarbat
revarbat merged commit 1d8f674 into main Aug 2, 2026
3 checks passed
@revarbat
revarbat deleted the fix-invalid-csg-operand-poisons-union branch August 2, 2026 03:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant