Skip to content

Fix rotate_extrude() volume mismatches (issue #87) - #100

Merged
particlesector merged 2 commits into
mainfrom
claude/issue-list-review-a7pxgn
Aug 2, 2026
Merged

Fix rotate_extrude() volume mismatches (issue #87)#100
particlesector merged 2 commits into
mainfrom
claude/issue-list-review-a7pxgn

Conversation

@particlesector

Copy link
Copy Markdown
Owner

Summary

Closes #87.

rotate_extrude() volume mismatches vs. real OpenSCAD (27% relative error on rotate_extrude-tests.scad, 71% on rotate_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, per tests/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's rotate_extrude branch (plus one in CsgEvaluator.cpp):

  • A profile entirely on the -X side was rejected outright. Only a profile that straddles the rotation axis is actually invalid in real OpenSCAD. Fixed by mirroring such a profile onto +X (with winding correction) before calling Revolve(), then rotating the result 180° about Z to restore the original sweep.
  • A negative angle= produced inside-out (negative-volume) geometryManifold::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 was lost before MeshEvaluator ever saw it — 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.

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

  • Added an IR-level regression test (CsgEval:rotate_extrude angle keeps NaN/Infinity distinct from a literal 0 in tests/test_csg_evaluator.cpp) — doesn't need Manifold.
  • Added 3 real-Manifold regression tests in 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 for angle=0.
  • Full existing suite still passes: 3542 assertions in 629 test cases (chiselcad_tests, built against a real Manifold v3.5.2).
  • Verified against a live OpenSCAD 2021.01 oracle using tests/tools/scad_to_stl/stl_diff on 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

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 particlesector left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/csg/MeshEvaluator.cpp Outdated
…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
@particlesector
particlesector merged commit 1241a8f into main Aug 2, 2026
6 checks passed
@particlesector
particlesector deleted the claude/issue-list-review-a7pxgn branch August 2, 2026 17:42
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.

[High] rotate_extrude() volume mismatches, especially angle= (partial revolution)

2 participants