Skip to content

Make ! the root of the tree, not a tag on the bodies - #87

Merged
revarbat merged 1 commit into
mainfrom
fix-root-modifier
Aug 10, 2026
Merged

Make ! the root of the tree, not a tag on the bodies#87
revarbat merged 1 commit into
mainfrom
fix-root-modifier

Conversation

@revarbat

Copy link
Copy Markdown
Member

Reported against linear_extrude(height=10) !circle(10); — it should leave the 2D circle, and we produced the cylinder.

! does not merely mark what it points at. It makes that subtree the whole model: the siblings go, and so does every operation wrapped around it.

That was implemented by stamping BodyRole::ShowOnly onto the marked bodies and filtering at the top level, which cannot express the second half — by the time a body carries the tag, every ancestor has already been applied to it.

Measured against the reference implementation (2021.01):

source reference was now
translate([50,0,0]) !cube(5); cube at x∈[0,5] x∈[50,55] x∈[0,5]
linear_extrude(height=10) !circle(10); 2D circle 10-tall cylinder 2D circle
module m(){ !cube(5); } translate([50,0,0]) m(); x∈[0,5] x∈[50,55] x∈[0,5]
!cube(5); #sphere(9); cube only cube and sphere cube only
two ! warns, takes the first took both warns, takes the first

The tree is now re-rooted at the first show_only node between resolve and generate, so its ancestors are never generated at all. The last two rows fall out of that rather than being handled separately: a highlighted sibling is outside the new root, and the "first wins" rule needs somewhere to choose, which the body filter never had.

Testing

Modifiers.TopLevelShowOnlyFiltersOutEverythingElse asserted the old behaviour — that a # sibling survives — so it is rewritten as ...LeavesNothingButItsOwnSubtree, against what the reference actually renders. Three tests added: ancestors discarded, the extrude case from the report, and first-wins-with-a-warning.

811 tests pass. Negative-controlled three ways: not re-rooting at all, taking the last ! instead of the first, and dropping the warning — each fails its own tests.

The 2D case needed care in the test: what survives has a section and no body, and asserting on body->BoundingBox() segfaulted before I looked at how 2D is represented.

🤖 Generated with Claude Code

`!` does not merely mark what it points at -- it makes that subtree the
whole model. Everything else goes: the siblings, and every operation
wrapped around it.

This was implemented by stamping a role onto the marked bodies and
filtering at the top level, which cannot express the second half: by the
time a body carries the tag, every ancestor has already been applied to
it. So `translate([50,0,0]) !cube(5);` put the cube at x=50 where the
reference leaves it at the origin, and
`linear_extrude(height=10) !circle(10);` gave a cylinder where the
reference leaves the 2D circle -- the extrude is an ancestor like any
other.

The tree is now re-rooted at the first show_only node between resolve
and generate, so its ancestors are never generated at all. Two further
divergences fall out with it: a highlighted sibling used to survive the
filter and no longer does, and more than one `!` now warns and takes the
first, both matching the reference (checked against 2021.01).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@revarbat
revarbat merged commit ed36daf into main Aug 10, 2026
3 checks passed
@revarbat
revarbat deleted the fix-root-modifier branch August 10, 2026 20:59
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