Fix rotate_extrude() volume mismatches (issue #87) - #100
Merged
Conversation
Built a live OpenSCAD 2021.01 + Manifold v3.5.2 oracle in this session (tests/tools/README.md's documented recipe) and bisected both corpus files construct-by-construct against it. Found and fixed five real bugs in MeshEvaluator::evalExtrusion's rotate_extrude branch: - A profile entirely on the -X side was rejected outright; only a straddling profile is actually invalid. Fixed by mirroring such a profile onto +X (with winding correction) before calling Revolve(), then rotating the result 180 degrees about Z to restore the original sweep. - A negative angle= produced inside-out (negative-volume) geometry, since Manifold::Revolve() only winds correctly for a positive sweep. Fixed by always sweeping with abs(angle) and mirroring the result across the XZ plane for an originally-negative angle instead. - Segment count used a fixed proxy radius (10) instead of the profile's own X-extent, matching real OpenSCAD's Calc::get_fragments_from_r(). - A partial sweep was tessellated at full-circle density instead of being scaled down by angle/360 like real OpenSCAD does (floored to a minimum of 3, not 1: Manifold::Revolve() silently ignores an explicit circularSegments <= 2 and falls back to its own auto-quality count, which produced zero triangles for a small enough angle). - angle='s NaN/Infinity handling (should mean "full circle") was lost before MeshEvaluator ever saw it, since the generic evalNumber() path collapses non-finite numbers to 0.0 -- indistinguishable from a literal angle=0 (a real, distinct "no geometry" case). Fixed by special-casing "angle" in CsgEvaluator::evalExtrusion. rotate_extrude-tests.scad: 27% -> 13.0% relative volume error (now an exact volume match; the remainder is an extreme $fn=1 edge case's rotational-phase misalignment, not chased further this pass). rotate_extrude-angle.scad: 71% -> 16.7% (every real partial/negative- angle/edge-case construct now matches to floating-point noise in isolation; the remainder is a 2021.01-vs-master oracle-version ambiguity around a deprecated positional argument, left open per v3.9's own documented caution about not guessing which oracle to match). Full details, including two phase-alignment leads investigated and deliberately not pursued (one measurably regressed already-passing cases), in docs/roadmap.md's new v3.12 section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qt1mwHFdm3v3JMMm3VC65j
particlesector
commented
Aug 2, 2026
particlesector
left a comment
Owner
Author
There was a problem hiding this comment.
Solid bug-fix PR: the mirrored-profile, negative-angle, and NaN/Infinity handling all check out against the stated Pappus/oracle math. One correctness issue flagged inline in the segment-count radius calc — the rest looks good.
Generated by Claude Code
…ssion test A review comment on this PR flagged minX/maxX being seeded at 0.0 (instead of +-infinity) and the non-mirrored branch using maxX instead of maxX-minX as two bugs in the segment-count radius calculation. Re-verified against the live OpenSCAD 2021.01 oracle this PR was built against: the 0-seeding is not a bug, it's an exact match for real OpenSCAD's own rotatePolygon() (GeometryEvaluator.cc), which seeds min_x/max_x at 0 before the same fmin/fmax scan. This means the segment- count radius is the profile's distance from the axis to its far edge, not its own true width -- confirmed by comparing segment counts against the live binary for a profile spanning x=[16,26] (real OpenSCAD uses 24 segments, matching the axis-seeded formula; a true-extent formula would give 16). Reverted the seeding back to 0.0, kept the (now-safe) simplification of computing radius as an unconditional maxX-minX without branching on `mirrored` (this was already correct under 0-seeding, since minX==0 whenever the profile isn't mirrored). Added a regression test/fixture pair (rotate_extrude_radius_near.scad/_far.scad: same-width profiles at different axis distances) that would fail under a true-extent implementation, so this doesn't get "fixed" back the same way again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qt1mwHFdm3v3JMMm3VC65j
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #87.
rotate_extrude()volume mismatches vs. real OpenSCAD (27% relative error onrotate_extrude-tests.scad, 71% onrotate_extrude-angle.scad) were found via the project's volumetric corpus-comparison methodology (docs/roadmap.md v3.9). Rather than fix from source-reading alone, this session built a live oracle (OpenSCAD 2021.01 via apt + Manifold v3.5.2 built from source, pertests/tools/README.md's documented recipe) and bisected both corpus files construct-by-construct against it.Found and fixed 5 real bugs in
MeshEvaluator::evalExtrusion'srotate_extrudebranch (plus one inCsgEvaluator.cpp):Revolve(), then rotating the result 180° about Z to restore the original sweep.angle=produced inside-out (negative-volume) geometry —Manifold::Revolve()only winds correctly for a positive sweep. Fixed by always sweeping withabs(angle)and mirroring the result across the XZ plane for an originally-negative angle instead.Calc::get_fragments_from_r().angle/360like real OpenSCAD does (floored to a minimum of 3, not 1 —Manifold::Revolve()silently ignores an explicitcircularSegments <= 2and falls back to its own auto-quality count, which produced zero triangles for a small enough angle).angle='sNaN/Infinityhandling was lost beforeMeshEvaluatorever saw it — the genericevalNumber()path collapses non-finite numbers to0.0, indistinguishable from a literalangle=0(a real, distinct "no geometry" case). Fixed by special-casing"angle"inCsgEvaluator::evalExtrusion.Results:
rotate_extrude-tests.scad: 27% → 13.0% relative volume error (now an exact volume match — the remainder is an extreme$fn=1/3-segment edge case's rotational-phase misalignment, not chased further this pass).rotate_extrude-angle.scad: 71% → 16.7% (every "real" partial-angle/negative-angle/edge-case construct now matches to floating-point noise in isolation; the remainder is a 2021.01-vs-current-corpus oracle-version ambiguity around a deprecated positional argument, left open per the project's own documented caution about not guessing which oracle to match).Full writeup, including two phase-alignment leads investigated and deliberately reverted (one measurably regressed already-passing cases), is in
docs/roadmap.md's new v3.12 section.Test plan
CsgEval:rotate_extrude angle keeps NaN/Infinity distinct from a literal 0intests/test_csg_evaluator.cpp) — doesn't need Manifold.tests/test_headless_build.cpp([v87][bugfix]), with volumes pinned against Pappus's centroid theorem for the mirror/negative-angle cases and an empty-geometry check forangle=0.chiselcad_tests, built against a real Manifold v3.5.2).tests/tools/scad_to_stl/stl_diffon both originally-reported corpus files plus ~15 individually bisected sub-cases.🤖 Generated with Claude Code
https://claude.ai/code/session_01Qt1mwHFdm3v3JMMm3VC65j
Generated by Claude Code