Open the release PR against the branch it was dispatched from
gh pr create targets the repository's default branch when --base is
omitted, so dispatching Prepare release against v3.5.2-release opened a
PR into master carrying the entire branch difference. Pass
--base "$GITHUB_REF_NAME" so a backport release lands on its own branch.
Also force-push the generated branch, so a re-run after a failed attempt
replaces it rather than being rejected as non-fast-forward.
* Resolve the deployed commit after checkout and use that for the artifact lookup instead of github.sha.
* publish_npm.yml builds from the checked-out commit instead of downloading a master artifact
* derive version into files
* Six files that were stale at 3.5.0 are now 3.5.1.
* Nightly deploy of master to master/
* Trigger on master CI instead of a nightly cron
Per review on #1801: cron would fire more often than master actually
receives commits, and still add up to 24h of latency. workflow_run on
CI completion is better on both counts.
Also pins the artifact to the triggering run via run_id, checks out the
exact commit CI validated, and skips deploying when that CI run failed.
* Replace the atomic reinterpret_cast with an AtomicRef shim
* Scope the C++20 deprecation suppression to the shared_ptr atomics
* Move the shared_ptr atomic helpers into atomic_compat.h
* Apply review: 32-bit MSVC fallback, comment accuracy, include hygiene
* Hoist the MSVC includes back to file scope
* Address review: enumerate CI jobs, reject 32-bit MSVC
* Name the MSVC atomic backend condition once
* Correct atomic_compat's comment on what survives at C++20
* Back off to a thin C++17 cast shim; std::atomic_ref at C++20
* Drop the AtomicRef concurrency tests; they cannot detect breakage
* support weekly benchmarks
* updated for perftest
* rem clipper
* other tests
* parse cmake output + use ember-benchmark-cases.json
* add only required STl files for ember tests
* remove ember tests+fix corrupted stl file
* shell script to py + minor changes
* mac-os
* more info of machine with linux fallback
* storage for weekly benchmarks
* rebase
* binary stl
* clean
* .
* fix ember phase capture and drop removed phases
- merge stderr into stdout in run_ember_case so man_bench diagnostic
output is not silently dropped
- remove Assembly/Triangulation/Simplification/Sorting from
INDEPENDENT_PHASES; their Timer::Print calls were removed in PR #1745
and they always produced 0.0; only Intersect12 and Winding03 phases
are still reported
- update build_ember_summary table to match the 4 remaining phases
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* revert runner
* add frontend + trigger on publish + san extended test
* fix formatting
* TEMP: allow workflow_dispatch to exercise data-publish steps for testing
Not for merge - revert before this PR lands. Only relaxing the if: gate
so a manual run can validate the folded run: > block and sanitizer arg
passing reviewers asked about, without waiting for a scheduled/release run.
* Revert "TEMP: allow workflow_dispatch to exercise data-publish steps for testing"
This reverts commit e00c5f5784eefa27255681dce4105bd45be3daa0.
* Move weekly sanitizer lane to Linux, fix artifact path, derive compiler
- Move build_sanitizer/test_sanitizer in weekly_benchmark.yml from
macos-15 to ubuntu-24.04, matching the existing PR-triggered sanitizer
lane in manifold.yml, since nothing about ASan/UBSan requires macOS
here. Drops the compiler overrides and macOS-only setup steps.
- Download sanitizer build artifacts into build/ instead of the
workspace root, so manifold_test's baked-in RPATH to libsamples can
actually resolve. It was crashing on load with
"Library not loaded: @rpath/libsamples.3.dylib" because the artifact
download flattened the build/ directory away.
- Capture the sanitizer build's own cmake configure log and derive
SANITIZER_SUMMARY_COMPILER from it at runtime instead of hardcoding
the string, reusing system_metadata.py's existing helpers.
- Trim set -euo pipefail to set -u in the data-publish steps, since
GitHub Actions' default bash invocation already applies -e/pipefail.
- Remove the duplicate weeklybenchmark-test job from manifold.yml in
favor of testing weekly_benchmark.yml directly via workflow_dispatch.
* Merge dashboard pages + remove dead JS fallbacks + gtest JSON output + simplify perf summary formatting + clean up .gitignore
* Stop forcing clang-18 for sanitizer builds, use runner default (gcc)
* small changes
* -Wno-maybe-uninitialized
* review changes
* dead code
* fix cmke read
* change flags
* remove redundant flags and schema version
* /dashboard route + remove upload route + change dead and retention changes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix commit
* remove cross_section flag
* minor changes
* minor changes 2
* review changes
* base_median=0 case check rem
* remove schema
* switch median to mean
* switch to macos
* more info of machine with linux fallback
* print per-size perfTest memory usage
* .
* .
* pin benchmark_guard to macos-15, drop test build, clean up JSON fields
* rem redundant code
* revert cmake bug
* memory guard
* review feedback
* updated subprocess() func
* rem mismatch logic
* Insert 2D intersections in point order with exact constructed-vertex identity
* Unify the kd-tree axis sort into one comparator
* Add non-closing-walk counterexamples and scope the retained-graph oracle
* Document the point-order arrangement
* Add near-vertical three-edge concurrence regression
* Add failing regression test for dense near-concurrent fan
* Replace the point-order arrangement with a Smith sweep-line
* Rewrite the Boolean2 design doc for the sweep engine
* Preserve collinear vertices on vertical edges in the sweep
* Use int64_t for signed multiplicity; drop redundant test try/catch
* Re-run CI (WASM worker font-fetch CDN 429 flake)
* Simplify the boolean2 winding and crossing-insertion pipeline
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Re-disable the TinyFeatureNearCorner offset tests pending winding robustness
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: decide segment crossings by sign, not eps-near-endpoint band
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: re-enable TinyFeatureNearCorner offset tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: document the sign-based crossing decision
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: re-disable HostDropAtOffset4096 (non-planar at large offset)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: decide winding at the start vertex, drop the clearance offset
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: use la::cross for the winding perp-dot, inline RayContribution
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: re-enable ShortEdge/InclExcl with eps*length-floor bounds
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add a CrossSection regression test for clustered edge crossings
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: tidy the new tests for review (named constant, headroom, messages)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: update the clustered-crossing test for the current CrossSection API
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: reword the perp-incidence test comment per review
Use point-to-line distance vs point-to-point proximity; drop the
along-line/inverse-projection aside Emmett flagged as not worth mentioning.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: rework incident-edge winding to Interpolate+Shadows
Per review, replace the explicit cross(d,w)/cross(n,w) perturbation in
LeftWindingAtVertex's incident branch with the Interpolate+Shadows side
test the non-incident branch already uses (the incident edge's far vert
and the classified far vert share a y-side of P, so the side test reduces
cleanly). Validated bit-identical to the prior winding across the full
suite + corpus + a 40k scaled random/degenerate differential.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: add a disabled guard for the macro-feature-drop corner touch
Two disjoint polygons point-touching ~2e-9 at a corner produce the same
non-closing-walk assert as the HostDrop case, but here the walk a release
build would drop is the entire ~422k-area piece, not a sub-eps tangle.
Kept disabled as the witness that the closed-walk assert cannot be
relaxed to an unconditional drop.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: rename winding local far -> farEnd (far is reserved on MSVC/MinGW)
The Interpolate+Shadows rework named a local `far`, which MSVC and MinGW
(MXE) treat as a reserved legacy pointer qualifier, breaking those builds
while gcc/clang accepted it. Pure rename, no behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: infer eps from absolute scale and drop the local-origin recenter
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: add disabled test for the centered sub-eps non-closing walk
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: merge winding-split verts to close the walk
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: add disabled test for near-coincident-corner non-closing walk
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Revert "boolean2: merge winding-split verts to close the walk"
This reverts commit cb9c1abd3cbb3b6098c4c9eba120e9d5203a1cff, keeping the
near-coincident-corner regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add visible large-eps/bbox missed-crossing regression (DISABLED)
A crucible search found a near-coincident missed-crossing open walk where the
op-eps is ~17% of the bbox (eps=1.75, bbox ~10.3) - eye-resolvable, vs ~3e-12 of
the bbox in production. Same class as the disabled near-coincident-corner case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: split edges at incidences before crossing detection
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: describe split-first arrangement in Boolean2.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* boolean2: pass out-params by reference
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Enable position-inclusive-eps regression tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* CrossSection API break (#1707): drop FillRule/Compose, const-ref Hull, tolerance API
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* CrossSection API break (#1707): C bindings + test fixes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* CrossSection API break (#1707): Python + WASM bindings
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* CrossSection API break (#1707): doc + example followups
- Drop stale @param fillrule from CrossSection(SimplePolygon) doc
- Replace CrossSection.compose with batch_boolean in all_apis.py
- Add get_tolerance/set_tolerance to all_apis.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* CrossSection API break (#1707): fix WASM JoinType::Bevel
'Bevel' was registered in the embind enum but missing from the
TypeScript type and from joinTypeToInt(), so passing 'Bevel' silently
fell through to 'Square' (0). Add it to both.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* CrossSection API break (#1707): tolerance API fixups
- Rect and Circle constructors now set tolerance_ via InferEps,
consistent with the Polygons constructor. Previously GetTolerance()
returned 0 for these paths.
- Hull(const SimplePolygon&): use the local sorted copy (points) for
InferEps, not the original const-ref (pts).
- Move SetTolerance declaration to the Information doxygen group
alongside GetTolerance.
- Add CBIND.cross_section_tolerance smoke test for
manifold_cross_section_get/set_tolerance.
- Fix stale comment in SimplifyPostFiltersBoolean2Output referencing
the old epsilon=1e-6 default.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* CrossSection API break (#1707): fix WASM compose/voronoi, doc Simplify default
- Remove 'compose' from crossSectionStaticFunctions in garbage-collector.ts
(CrossSection.compose no longer exists; wrapping undefined crashed with
"originalFn is not a function" in WASM TBB:OFF CI lanes)
- Add 'setTolerance' to crossSectionMemberFunctions (returns new CrossSection)
- Update voronoi.mjs to use CrossSection.union() instead of .compose()
- Fix stale @param epsilon / default 1e-6 in manifold-encapsulated-types.d.ts
(parameter is now 'tolerance', default 0 uses the geometry's own tolerance)
- Add @param doc note to CrossSection::Simplify explaining that default 0
resolves to tolerance_ (geometry-scale-derived, not a fixed 1e-6)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add boolean2 and CrossSection regression and corpus tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add TinyFeatureNearCorner annihilation regression tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add regression tests for the eps-only new-to-old merge
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Drop tolerance from the boolean2 tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add DISABLED_ tiny-feature point-contact host-drop seeds
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add contracts to boolean2_test validators
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Share the polygon-corpus reader and inline single-use test helpers
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Tighten offset, Simplify, and rhombi-union assertions
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Use linalg helpers instead of hand-rolled vector math
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Note the validator oracle's >eps precondition
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Assert the empty intersection in the disabled host-drop seeds
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Strengthen cross-section area oracle tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Guard the offset corpus test under MANIFOLD_NO_IOSTREAM
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Consolidate CrossSection test anonymous namespaces and tidy seed comments
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Move the direct-engine NonFinite test out of cross_section_test
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix tiny-feature annihilation in the intersection-vertex merge
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Snap new-to-old intersection verts at eps scale
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Drop tolerance from the arrangement entry points
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Pin nearby-intersection clusters to their old vertex
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Revert "Pin nearby-intersection clusters to their old vertex"
This reverts commit 3e171d165e8492c072a532750bb32c2e5b261e35.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Move boolean2 engine files up to src/; rename to boolean2_* form
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Consolidate the nine boolean2 engine headers into src/boolean2.h
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Merge the boolean2 pipeline and containment sources into their targets
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Update gen_docs, Doxyfile, docs, and format.sh for the flat boolean2 layout
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Restore the module contract comments dropped by the source merges
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix Boolean2.md architecture table and two merge leftovers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Wire CrossSection to the boolean2 backend
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Simplify CrossSection via vertex decimation, not merge-tolerance snap
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address wiring review: Hull/finite guards, inline mutex, signpost, asserts
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Migrate CrossSection wiring to ApplyFillRule; Simplify decimates only
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Defer transform tolerance scaling to GetPaths; fix Simplify comment
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Tidy wiring comments to house style; use la::isfinite for guards
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Cut over CrossSection from Clipper2 to boolean2
Replace the Clipper2-backed CrossSection implementation with the in-tree boolean2 one and drop the Clipper2 dependency, the MANIFOLD_CROSS_SECTION_BACKEND selector, and its CI and packaging plumbing. The public CrossSection API is unchanged: the FillRule argument is kept but only Positive construction is supported. Samples.Sponge4's degenerate-triangle bound is raised, since boolean2's Simplify leaves more near-collinear vertices (more benign zero-area tris; topology unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Delegate single-contour CrossSection ctor to the Polygons one
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Always build CrossSection; drop the MANIFOLD_CROSS_SECTION option
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Flatten manifold::boolean2 into manifold; unify JoinType
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Make SimplifyRing O(n log n) via min-heap with lazy invalidation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Drop stale MANIFOLD_CROSS_SECTION flag from the PR perf guard script
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Scrub Clipper2 and the removed option from the new sanitizer CI script
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Move gtest filter to env var
* extract sanitizer gtest filter into shared script
* set explicit ASAN options for sanitizer test lane
* reproduction on failure
* split sanitizer lane into build/test with core-vs-extended subsets, clang-18 pin, and runtime guardrails
* caches files
* add shared libs on path
* lib path added
* change name "test (core/extended subset)" to "(ASan+UBSan)"
* moved runs to scripts
* rem repro
* readme change
* removed cache as discussed with @elalish in meeting
* testing by removing LD_LIBRARY_PATH
* restore
* only keep find and remove hardcoded paths
* run path use origin cmake args
* removed lib path
* Bump Windows CI to CMake 4.3.3 for the windows-2025-vs2026 runner
windows-2025 migrated to VS 2026 (VS 18 / MSVC 14.51). CMake 4.0.2 has no Visual Studio 18 2026 generator, falls back to NMake Makefiles, and rejects -A x64. That generator was added in CMake 4.2; get-cmake catalogs are version-locked to the CMake they ship, so the action tag moves too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* TEMP: disable get-cmake cloud cache to confirm CI past the flaky restore
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Revert "TEMP: disable get-cmake cloud cache to confirm CI past the flaky restore"
The cache-off run went green, confirming the CMake 4.3.3 bump fixes Windows on the vs2026 runner. Restoring the default cloud cache.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add boolean2 polygon offset and containment decompose
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fold offset and containment decls into boolean2.h; drop tiny headers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Make fill-rule application machine-eps; drop redundant concave V
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add libc++ EXTENSIVE hardened CI lane
A clang/libc++ lane built with _LIBCPP_HARDENING_MODE=EXTENSIVE so the
&v[size] class of UB (#1735) traps in CI instead of silently working at
runtime. tbb/clipper2/gtest are built from source under the same libc++
flags (USE_BUILTIN + FETCHCONTENT_SOURCE_DIR) for ABI parity with the
test binary.
* TEMP: revert #1736 to show the hardened lane catches #1735
Reintroduces the &v[size] UB that #1736 fixed so build_libcxx_hardened
goes red and demonstrates it catches the regression. Reverted once CI
red is confirmed.
* Restore #1736 fix (hardened lane confirmed red on the bug)
Reverts the temporary #1736 revert now that build_libcxx_hardened
demonstrated red on the reintroduced &v[size] UB (both TBB legs failed
at the manifold_test step). Back to green on the fixed tree.
* init
* upload benchmark artifacts and clean worktrees
* all warning only and run count mismatch
* completely non-blocking and mean of mean run times compared
* remove redundant if: always()
* changed to per benchmark mean
* standardize perf JSON schema with run index, stdev, n_runs, and metadata
* .
* add raw files to logs and clean yml
* add EOF
* .
* move shell to scripts
* Add boolean2 core implementation
Add the experimental Boolean2 core arrangement pipeline under src/cross_section/boolean2 and compile it behind the existing CrossSection backend selector.
The core includes polygon conversion, vertex merge, edge-pair broad phase, intersection insertion, near-vertex splitting, fixed-point cleanup, canonical sub-edge generation, winding-rule filtering, containment, and offset helpers. It also adds docs/Boolean2.md describing the design, relationship to #289, validation boundary, and staged backend plan.
This does not switch production CrossSection behavior to Boolean2. The boolean2 backend translation unit remains placeholder-only on this branch; public CrossSection wiring and behavior-level tests are staged for later PRs in #1707.
* Keep boundary-touching holes in decomposition
* Split repeated-vertex output walks
* Remove unused boolean2 edge-area helper
* Require full-ring containment in decomposition
* Format full-ring containment helper
* Remove unused edge-vertex list builder
* Include owning boolean2 headers in sources
* Remove unreachable edge-pair parallel branch
* Prune unreachable boolean2 helper paths
* Remove stale fused helper fallback
* Split epsilon-width boolean output slits
* Use operation epsilon for output slit splitting
* Name boolean2 tolerance constants
* Name remaining boolean2 heuristic constants
* Document boolean2 heuristic margins
* List boolean2 parallel policy header
* Add Boolean2 debug trace phases
* Stabilize boolean2 geometric tolerances
* Add Boolean2 architecture diagram
* Tighten Boolean2 architecture diagram labels
* Use Markdown labels in Boolean2 architecture diagram
* Set Boolean2 diagram wrapping width
* Balance Boolean2 diagram label wrapping
* Replace Boolean2 architecture diagram with table
* Inline Boolean2 CCW predicate use
* Use halfedge terminology in Boolean2 filter
* Tighten Boolean2 terminology
* Restore Boolean2 API terminology
* Use nonzero union for boolean2 set add
* Classify initial nonzero outer face locally
* Use symbolic graph order for boolean2 intersections
* Use positive winding for boolean2 Add
* Run boolean2 core as a single pass
* Remove boolean2 fixed-point iteration code
* Hide boolean2 diagnostics behind helpers
* Tighten Boolean2 core style
* Address automated code review
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Rename boolean2 remap locals to match inputVert2Merged field
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Drop seedRayCastLeastBiased voting in favor of a single ray cast
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Drop dead perturbRank and inline degenerate Shadows tie-break
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Thread propagated tolerance through to structural_remerge
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address Boolean2 core review cleanup
* Fix fused narrow scratch references
* Remove stale Boolean2 XOR docs
* Rename Boolean2 combined narrow timing
* Address Boolean2 review follow-ups
* Remove final near-repeat extraction tolerance
* Propagate all new boolean2 intersections
* Clean up Boolean2 core dead paths
* Address Boolean2 review style pass
* Clean remaining Boolean2 casts
* Clarify Boolean2 review follow-up comments
* Use symbolic Boolean2 winding seeds
* Fix symbolic winding seed underflow
* Account for current eps in endpoint snaps
When a freshly computed intersection is compared to an existing endpoint, the endpoint may already carry propagated tolerance and the new point also carries this operation's intersection error. Use tolerance + eps for the new-to-old snap gate so near-endpoint intersections do not leave a T-junction just outside the propagated tolerance budget.
* Clarify Boolean2 review follow-ups
* Make Boolean2 fill handling Positive-only
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add Manifold::Contains and Manifold::WindingNumber
Closes#1440.
Implements point-in-mesh containment testing via the Boolean winding
number kernel (Kernel02) already used by the CSG operations, giving
correct results for non-simply-connected shapes and overlapping regions
where simple ray-parity would be wrong.
Core: Manifold::Impl::PointWinding in boolean3.cpp
- Builds a minimal Impl for the query points (vertPos + zero normals)
- Pre-filters with the 3D bounding box - points outside return 0 immediately
- Queries the BVH with vec3 points, which triggers DoesOverlap(vec3) - an
XY-projected check - so all faces with XY overlap are returned regardless
of Z, enabling correct +Z winding accumulation above the query point
- Processes all points in a single parallel BVH traversal
Public API (include/manifold/manifold.h, src/manifold.cpp):
- int WindingNumber(vec3 point) const
- std::vector<int> WindingNumber(const std::vector<vec3>& points) const
- bool Contains(vec3 point) const
- std::vector<bool> Contains(const std::vector<vec3>& points) const
Bindings: C (scalar), Python (scalar + batch), WASM (scalar + TypeScript types)
Tests (test/measurement_test.cpp):
- ContainsTallBox: verifies XY-projected collider query (would fail with 3D Box)
- WindingWatertight: no double-counting at shared edges/vertices
- WindingHollowShell: real winding number, not just ray parity
- ContainsBatch: batch results match scalar for all points
- WindingMatchesRayCastParity: cross-checked against existing RayCast
* Fix clang-format violations and nanobind overload_cast in Python bindings
nanobind requires static_cast for overloaded member functions;
nb::overload_cast without return type doesn't resolve ambiguity.
Also wrap long comment lines to satisfy clang-format 20.
* Simplify winding number API
Remove scalar WindingNumber(vec3) and all Contains overloads from C++,
C, Python, and WASM bindings.
Rename `active` to `query2input` in PointWinding.
Plumb the
vector API through WASM instead of the previous single-point calls to
avoid per-point marshalling overhead.
* Fix clang-format-lint errors
* Route Boolean3::Result timing through the shared phase mechanism
Per Emmett's #1742 review: drop the four hand-placed Result Timers
(Assembly/Triangulation/Simplification/Sorting) and report through the
same per-phase timing as LevelSet/FromMesh/Smooth, so all phased ops emit
the one `phase N (file:line) = X ms` format.
The phase() lambda already brackets those spans (the 4 named regions were
just unions of consecutive phase boundaries), so it gains a `line` arg and
records via a new MANIFOLD_RECORD_PHASE_AT - an explicit-line variant, since
a lambda called from 11 sites has only its own body line. Boolean never hits
the static-factory reset that seeds `lastPhase`, so BeginPhaseTiming stamps
the baseline at Result entry.
Left alone: the intersection-stage Timers in the Boolean3 ctor (a P/Q
sub-op breakdown, not sequential phases) and the verts/tris summary.
perfPhases now drives its booleans through an ExecutionContext so the phase
lines print. Progress/cancel semantics and the release build are unchanged
(timing only records with a ctx attached, under MANIFOLD_DEBUG/_TIMING).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Give Boolean3::Result phase timing a per-op local baseline + uid
Addresses the #1745 review: the per-phase timing keyed off the shared
`ctx->lastPhase`, so when a CSG tree evaluates booleans concurrently the
parallel `Boolean3::Result` calls clobbered each other's baseline (garbage
deltas) and their phase lines interleaved with no way to tell them apart.
Move the baseline to a per-`Result` stack local (`LocalPhaseTiming`) and tag
every printed line with a per-boolean uid (`[bN]`) - so concurrent booleans
keep correct deltas and the interleaved output is splittable. The static
factories keep the ctx baseline (single-threaded, unaffected). Debug-only
(verbose >= 2, MANIFOLD_DEBUG/_TIMING); release is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-phase timing via ADVANCE_PHASE_OR_RETURN; extend perfPhases
Bake per-phase wall-clock timing into the phase-boundary macro instead of
hand-placing Timers per op. ADVANCE_PHASE_OR_RETURN now records the elapsed
time since the previous boundary (baseline stamped in ResetForStaticFactory)
via a RecordPhase helper, printing "phase N (file:line) = X ms" under
verbose >= 2. lastPhase lives on ExecutionContext::Impl; everything is behind
MANIFOLD_DEBUG || MANIFOLD_TIMING, so release builds are unchanged.
One change covers every phased op: LevelSet (5), FromMesh ingest (7), and
Smooth (14 = ingest + tangent phases) all time automatically, with no
per-op instrumentation and no risk of drifting out of sync with the cancel/
Progress boundaries. Timing records only when a ctx is attached, so it rides
the same ExecutionContext path as Progress/cancel.
extras/perfPhases: drive LevelSet through an ExecutionContext and add
FromMesh and Smooth sections (BenchFromMesh / BenchSmooth) so the new
coverage is exercised.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add regression tests for originalID races on shared CSG nodes (#1740)
Two ThreadSanitizer-catchable tests, both barrier-synced so all threads
first-touch one shared node at once:
- ConcurrentSharedNodeOriginalIDStable: a transformed leaf realized lazily
by GetImpl on the const-accessor path.
- ConcurrentSharedOpNodeOriginalIDStable: an unevaluated op node evaluated
by per-thread Manifold copies (sharing pNode_), hitting ToLeafNode's cache_.
These commit only the tests; both fail without the following fix - under the
TSAN lane they halt on the race, and the functional EXPECT catches the
originalID = -1 collapse.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix originalID races on shared CSG nodes (#1740)
Lazily-evaluated CSG nodes are mutated through const accessors:
CsgLeafNode::GetImpl rewrites pImpl_/transform_ to realize a pending
transform, and CsgOpNode::ToLeafNode publishes cache_. A Manifold and its
copies share the underlying node (pNode_) but each gets its own pNodeMutex_,
and GetCsgLeafNode drops that lock before GetImpl/cache_ access - so two
threads touching one shared node (e.g. OriginalID()/NumTri()/GetMeshGL(), or
evaluating a shared op node) corrupt it concurrently. The torn write leaves
triRef.meshIDs absent from meshIDtransform; IncrementMeshIDs collapses them
and GetMeshGL emits originalID = -1. Distinct from #1688 (Compose's
meshIDCounter_ double-read).
The boolean path is unaffected: it copies leaf operands (node->Transform)
before GetImpl, so it never mutates a shared node.
Fix: a per-node mutex guards CsgLeafNode's lazy realize and the const
accessors that read pImpl_/transform_ (GetImpl, Transform, GetBoundingBox,
NumVert, copy ctor). CsgOpNode reads/publishes cache_ under impl_'s existing
guard, including the cancellation path, so concurrent evaluators of a shared
op node converge on one result. Compose's raw field reads are documented as
requiring thread-local (unshared) leaves.
Under TSAN, 8 threads on a shared node go from racing to clean for both the
leaf and op-node paths. Full suite 436/436 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ctx-aware static factories (FromMeshGL / LevelSet / Smooth) had no
binding, so ops with no source Manifold to attach via WithContext - notably
MeshGL ingest - could not be observed or cancelled from the language
bindings. The ExecutionContext object and WithContext were already bound;
this adds the three factories as methods on the context, mirroring the plain
static factories.
- C: manifold_execution_context_{level_set,level_set_seq,of_meshgl,
of_meshgl64,smooth,smooth64}. The level_set helper is generalized to run
under an ExecutionContext; the SDF callback user-data is renamed sdf_context
to keep it distinct from the ExecutionContext argument.
- Python: ExecutionContext.{from_mesh,level_set,smooth} mirroring the
Manifold static-factory lambdas.
- WASM: ExecutionContext.{fromMesh,smooth,levelSet} (embind helpers in
helpers.cpp, JS wrappers in bindings.js, .d.ts declarations).
Tests: CBIND.execution_context_factories (C), all_apis.py coverage (Python),
and an ExecutionContext-static-factories case (WASM). All build and pass
locally across the three layers.
Toward #971.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds ExecutionContext::LevelSet, an eager ctx-aware factory for
Manifold::LevelSet, so a level-set build can report Progress() and honor
Cancel(). Same shape as ExecutionContext::FromMeshGL (#1723) and Smooth
(#1734); continues the #971 eager-ops work.
- Factor the Manifold::LevelSet body into Impl::CreateLevelSet;
Manifold::LevelSet is now a thin ctx=nullptr wrapper (behavior unchanged).
- 5 progress phases (kPhasesPerLevelSet): voxel SDF sampling, NearSurface,
ComputeVerts, BuildTris, lumped finalize. The NearSurface hash-table resize
loop counts once.
- Cancel: entry gate, resize-loop gate, per-chunk cancel through the four grid
for_each_n loops, and SortGeometry(ctx) in finalize (gated before
SetNormalsAndCoplanar, whose input must not be partial-on-cancel).
Tests: ExecutionContextLevelSet cluster (HappyPath, MatchesFactory,
CancelBeforeSampling, CancelConcurrent), trimmed to LevelSet-unique paths.
Python + wasm bindings are split into a follow-up.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Thread ExecutionContext into Manifold::Smooth via ExecutionContext::Smooth
Adds the static factories `ExecutionContext::Smooth(MeshGL)` and
`Smooth(MeshGL64)`, mirroring the FromMeshGL pattern from #1723. The
ingest phases (`kPhasesPerFromMesh = 7`) plus seven new tangent-creation
phases in `CreateTangents` (`kPhasesPerSmooth = 14` total) check cancel
and publish progress at each boundary.
Implementation reuses the existing scaffolding:
- A templated `MakeSmoothImpl` helper in `impl.h` replaces the
anonymous-namespace `SmoothImpl` in `constructors.cpp` so both the
non-ctx `Manifold::Smooth` and the ctx-aware `ExecutionContext::Smooth`
share it. Entry-time cancel wins; past the entry gate, validation
errors from the inner MeshGL ctor short-circuit before
CreateTangents, keeping phase counters honest.
- `Manifold::Impl::CreateTangents(std::vector<Smoothness>)` gains an
optional `ExecutionContext::Impl* ctx = nullptr` parameter and seven
`ADVANCE_PHASE_OR_RETURN(ctx)` call sites. The two parallel
`for_each_n` loops pass `ctx` to the existing ctx-aware overload in
`parallel.h` for per-chunk cancel granularity, no wrapper needed.
- `LinearizeFlatTangents` and `DistributeTangents` are not ctx-aware
themselves -- the boundary belongs at their call site in
CreateTangents, since both are also called from the unrelated
`CreateTangents(int normalIdx)` path.
Test coverage in `context_test.cpp` mirrors the ten
ExecutionContextFromMeshGL tests plus one Smooth-specific case
(`CancelWithSharpenedEdges`) asserting `NumTri() == 0` on Cancelled --
the faceID-restoration loop in `MakeSmoothImpl` is a natural no-op
after `MakeEmpty`.
* Trim comments per manifold-style-review
- MakeSmoothImpl docstring matches the two-line FromMeshGL ctor
precedent at impl.h:279-280.
- Drop the Phase 4 sub-loop note from kPhasesPerSmooth; the
bump-in-lockstep rule is the load-bearing invariant.
- Shorten the inline validation-error and faceID-restoration comments.
* Collapse FromMeshGL/Smooth ctx test clusters into one TYPED_TEST_SUITE
Addresses @elalish's PR #1734 review comment about generic test generation.
The two factories share 10 near-identical tests; a `TYPED_TEST_SUITE`
parameterized by `FromMeshGLTraits` / `SmoothTraits` removes ~150 lines
of duplication while keeping per-factory phase counts and names.
The Smooth-specific `CancelWithSharpenedEdges` stays as a separate
`TEST(ExecutionContextSmoothExtras, ...)` since no other factory takes a
`sharpenedEdges` argument. A compile-time `kIsStaticFactoryTraits`
contract check guards against future traits with missing members.
* Trim Smooth ctx tests to unique paths
Reverts the TYPED_TEST_SUITE indirection from 6bde95ed; per review, cut
the per-factory test surface instead. The shared counter/reuse/validation
machinery is covered by the full FromMeshGL cluster, so Smooth keeps only
its unique paths: happy path, parity with the plain factory, the
entry-time cancel gate in MakeSmoothImpl, concurrent cancel through the
extra tangent phases, and sharpenedEdges.
`&v[v.size()]` is UB even when only used as a pointer-comparison
sentinel. libc++'s fast hardening mode (`_LIBCPP_HARDENING_MODE_FAST`,
default in nixpkgs and on darwin under some build profiles) traps in
`operator[]` before returning the reference, aborting the test process.
Two sites both added in #1718:
- `Manifold.GetNormalLegacyContract` walks each MeshGL run via
`&triVerts[runIndex[run + 1]]`; the final run's end index is
`triVerts.size()`.
- `RelatedGL` in `test/test_main.cpp` does the same, breaking
`Boolean.Normals` and `BooleanComplex.InterpolatedNormals` too.
Switch to `v.data() + i` for both sentinels - no behavior change in
well-defined cases, no bounds check fired.
Verified: with `CXXFLAGS=-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST`
and `-stdlib=libc++`, the three previously-trapping tests pass and the
full suite (420/420) is clean.
* Thread ExecutionContext into MeshGL ingest via ExecutionContext::FromMeshGL
Adds ExecutionContext::FromMeshGL(MeshGL) / FromMeshGL(MeshGL64) -
eager, ctx-aware analog of Manifold(MeshGL). First installment of the
static-factories-on-ctx direction from #971; MeshGL ingest is
stephomi's headline perf concern in that thread.
Impl(MeshGLP, ctx) threads cancel + donePhases through the 7 heavy
ingest steps (CreateHalfedges through SortGeometry). Entry-time
Cancel beats empty/malformed input; past the gate, validation errors
beat racing Cancel. New Manifold::FromImpl(shared_ptr<Impl>) (vein of
Invalid() / PropagateStatus()) lets factories wrap Impls without
friending.
Follow-ups: bindings (Python/WASM/C), other static factories (Hull,
LevelSet, Smooth) - separate PRs per #971.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Apply clang-format-20 to FromMeshGL test
Catches CI check_format failure on PR #1723 - v18 (local) accepted but
v20 (CI) wants different wrapping on the bad.vertProperties comment
alignment and the EXPECT_LT call. No semantic change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add RETURN_IF_CANCELLED / ADVANCE_PHASE_OR_RETURN macros; apply to MeshGL ctor
Per @pca006132 feedback on #1723, factor the repeated phase-boundary
cancel-check + donePhases.fetch_add block into a shared macro. The
naming makes the early return visible at the call site (avoiding the
"function call hides a return" footgun).
Two macros, both defined in src/execution_impl.h next to IsCancelled and
the kPhasesPer* constants:
- RETURN_IF_CANCELLED(ctx, ...): variadic so it covers both bare `return;`
and value-returning sites (`Vec<int>{}`, `ErrorLeaf(Cancelled)`, etc.).
Available for the ~50 existing `if (IsCancelled(ctx)) return ...;`
sites across the .cpp files; this commit doesn't migrate them - that
follows separately.
- ADVANCE_PHASE_OR_RETURN(ctx): MakeEmpty(Cancelled) + return on cancel,
then fetch_add(1) on the continue path. Specific to Impl methods that
count progress (relies on `this->MakeEmpty`). Applied to the seven
phase boundaries in Manifold::Impl::Impl(MeshGLP, ctx).
The seven `ADVANCE_PHASE_OR_RETURN(ctx)` calls in the ctor body still
must equal kPhasesPerFromMesh; the constant's docstring notes this and
points at the ctor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Apply RETURN_IF_CANCELLED to existing cancel-check sites
Mechanical migration of the 44 one-line `if (IsCancelled(ctx)) return
...;` sites across the library to the macro added in the previous
commit. Covers bare `return;` and value-returning forms (`Vec<int>{}`,
`cancelled()`, `ErrorLeaf(Manifold::Error::Cancelled)`,
`std::make_tuple(...)`, `Intersections{}`).
Files touched: boolean3.cpp, boolean_result.cpp, csg_tree.cpp,
face_op.cpp, minkowski.cpp, parallel.h, sort.cpp.
The six multi-line cancel handlers stay inline (they do more than just
return - MakeEmpty in quickhull / smoothing, stack poisoning in
csg_tree::ToLeafNode, the phase() lambda in boolean_result). Those
either need a slightly different macro (MakeEmpty + return) or have
genuinely call-site-specific logic.
No behaviour change; 429 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Trim macro docstrings and the in-ctor comment
Cut the verbose justifications down to the load-bearing facts: macro
purpose + variadic shape on RETURN_IF_CANCELLED, prerequisites + the
"call count == kPhasesPer*" invariant on ADVANCE_PHASE_OR_RETURN, and
just the invariant in the ctor itself.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Split RETURN_IF_CANCELLED into bare and _VAL forms
Empty `__VA_ARGS__` is ill-formed under ISO C++17 (gcc emits
-Wpedantic, clang -Wgnu-zero-variadic-macro-arguments). Manifold's own
CI does not enable -Wpedantic, but the prior variadic form would
regress downstream consumers that `add_subdirectory(manifold)` with
-Wpedantic -Werror inherited into the subdirectory.
- `RETURN_IF_CANCELLED(ctx)` for void-returning functions.
- `RETURN_IF_CANCELLED_VAL(ctx, val)` for value-returning functions.
13 call sites updated to the _VAL form (boolean3.cpp x5, csg_tree.cpp
x3, minkowski.cpp x4, boolean_result.cpp x1).
* Revert "Split RETURN_IF_CANCELLED into bare and _VAL forms"
This reverts commit 6e415bf5ec07f2c5b40d20a5aaccd6e6014df69c.
* Revert "Apply RETURN_IF_CANCELLED to existing cancel-check sites"
This reverts commit 7b56ead7029f0401369d42c06cdf100fe92535f4.
* Drop unused RETURN_IF_CANCELLED macro
The variadic RETURN_IF_CANCELLED macro added in e27f07d8 and its split
in 6e415bf5 were just reverted alongside the 44-site migration. The
definition is now unreferenced; remove it. ADVANCE_PHASE_OR_RETURN
remains for the MeshGL ctor's phase boundaries.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Record normals on Manifold; auto-substitute on GetMeshGL; roundtrip via MeshGLP
Following Emmett's #1712 direction: make "where are my normals?" a
property of the Manifold rather than something the caller threads
through every call. After CalculateNormals(), GetMeshGL() returns
solid-frame normals without needing the idx repeated; an ofMesh ->
getMesh roundtrip preserves the recording via a new optional
MeshGLP::hasNormals flag.
The standard slot is the first three extra-property channels (MeshGL
channels 3, 4, 5). CalculateNormals / SmoothByNormals / UpdateNormals
gain default normalIdx values (0 / 0 / -1 respectively) so the no-arg
call is the new preferred form; non-zero values are kept for
compatibility but flagged in the docs. Refine clears the recording
(interpolated normals are wrong).
Bindings updated in lock-step: Python (nb::arg defaults, has_normals
field on MeshGL/MeshGL64), WASM (JS wrapper for smoothByNormals,
helpers.cpp roundtrips hasNormals, .d.ts type updates), C (
manifold_meshgl{,64}_has_normals accessors). Single-signature shape
means no overload disambiguation needed in any binding.
Open follow-ups: [[deprecated]] attribute + internal caller migration,
SetProperties detection (flagged with TODO).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Record normals on Manifold; auto-apply transforms via runFlags bit 1
Fixes#1712, fixes#1719.
Make "where are my normals?" a property of the Manifold rather than
something the caller threads through every call. After CalculateNormals,
GetMeshGL returns world-frame normals without an explicit normalIdx, and
a MeshGL <-> Manifold round-trip preserves the recording per-run even
across mixed-input Booleans (e.g., a cube without normals composed with
a sphere that has them).
The #1712 repro from
(sphere - sphere.Translate(10)).CalculateNormals().GetMeshGL()
now returns the expected outward-from-cavity normals. The underlying
cross-mesh seam-vert sign issue (#1719) is fixed in SetNormals.
Mechanism:
- runFlags becomes a bitmask: bit 0 = backside (existing), bit 1 =
hasNormals (new). New MeshGLP::HasNormals(run) accessor mirrors
Backside(run). Backside fixed from `runFlags[run] == 1` to `& 1` so
it survives the addition of a second bit.
- CalculateNormals(0) marks the per-meshID Relation.hasNormals flag,
which Impl::HasNormals() ANDs across to give the impl-wide answer.
GetMeshGL(-1) auto-substitutes slot 0 on output when HasNormals() is
true.
- Normals at the standard slot are stored world-frame on the Impl.
Impl::Transform and csg_tree.cpp::Compose eager-transform
properties_[0..2] per-meshID so the slot stays in sync with vertPos_
and faceNormal_ across any sequence of transforms, including mixed-
input Boolean/Compose outputs where some meshIDs carry normals and
others don't. EagerTransformPropNormals is a shared helper.
- SetNormals multi-normal walk sign-flips cross(next, here) when it
disagrees with faceNormal_[next.face]. This handles Boolean
subtractee triangles (winding unchanged, faceNormal_ negated by the
Boolean post-pass) without losing the existing "flair" curving for
smooth surfaces. This is what fixes#1719.
- CreateProperties negates Q's slot 0..2 per source-meshID for
subtractee cavity inversion.
- GetNormal returns properties_[0..2] directly when the meshID's
Relation.hasNormals is true (world-frame already); applies the per-
meshID inverse-normal-transform when false, preserving the master
contract for hand-built MeshGL inputs that don't set the bit.
- Non-zero normalIdx to CalculateNormals uses the legacy deferred-
transform path: stores per-mesh-frame, recovered to world-frame by
GetMeshGL's per-run transform on export. Docstring notes this path
will be removed in a future release.
API additions:
- MeshGLP::HasNormals(run) C++ accessor plus C and Python bindings.
- MeshGLP::Backside(run) C and Python bindings (previously C++-only).
Tests:
- Properties.CalculateNormals: previously-FIXME'd dot(pos, norm) > 0
asserts enabled (#1719 regression).
- Manifold.CalculateNormalsRoundTripsThroughGetMeshGL: ofMesh+getMesh
preserving the per-run flag, Rotate before and after CalculateNormals,
no-arg invocation, non-zero idx.
- Manifold.GetNormalLegacyContract: per-mesh-frame fallback in
GetNormal for MeshGLs without bit 1.
- Manifold.TransformMixedNormalsPerMeshID,
Manifold.ComposeMixedNormalsPerMeshID,
Manifold.BooleanSubtractMixedQPerMeshIDNegation: per-meshID iteration
in Impl::Transform / Compose / CreateProperties for mixed-input
Boolean outputs.
- Manifold.CalculateNormalsNonZeroIdxSurvivesTransform: legacy non-zero
normalIdx path across post-calc transforms.
- CBIND.run_flag_accessors: exercises the new C accessors.
Known limitations:
- Hand-built MeshGL inputs that share propVerts across runs with
differing hasNormals settings produce one-interpretation-wins
behavior at the shared propVerts (the stored value can only have one
semantic). Standard CalculateNormals / Boolean / Compose outputs
never produce this shape. Documented on MeshGLP::HasNormals(run).
- Warp / WarpBatch / SetProperties keep the recording set; if the user
replaces slot 0..2 or warps non-rigidly, the stored normals are stale
until they call CalculateNormals again. Documented on each method.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address PR #1718 review feedback
Substantive:
- Split CalculateNormalsRoundTripsThroughGetMeshGL into 8 named tests
(NormalsCavity, NormalsRotateBefore/AfterCalc, NormalsAutoSubstitute,
NormalsRoundTrip, NormalsRefinePreserved, NormalsSmoothByNormalsNoArg,
NormalsNonStandardSlotNotRecorded). Pithier names per Emmett's request.
- Fix Backside(run) docstring: framework already orients stored normals
on the standard flow, the bit is informational only.
- SafeNormalize during the eager-transform in
Manifold::Impl::EagerTransformPropNormals so non-orthogonal transforms
and barycentric interpolation don't leave non-unit values that compound
downstream (e.g., at Boolean edges feeding SmoothByNormals).
- Add `backside(run)` and `hasNormals(run)` methods on the WASM Mesh
class, plus runFlags field documentation in the .d.ts.
- Add a deliberately-failing test (NormalsSharedPropVertMixedFlags) that
demonstrates the data-model conflict in the shared-propVert + mixed-
hasNormals case. Hand-built MeshGL inputs with shared propVerts across
runs of differing hasNormals can only store one slot 0..2 value;
Impl::Transform picks one interpretation, corrupting the other. Test
is intentionally red as a discussion-driver for PR #1718 review
thread (whether to fix by splitting propVerts in the Manifold(MeshGL)
ctor, or to document the constraint).
Trivial:
- Rename wasHasNormals -> hadNormals in Impl::InitializeOriginal.
- Drop verbose eager-transform comment block in Impl::Transform; the
helper call site is self-documenting.
- Note AND-not-OR semantics on Impl::HasNormals(); rename hint covered
by the new doc.
- TODOs at the legacy non-zero-normalIdx codepaths (the getTransform
lambda in SetNormals, the legacy fallback in GetMeshGLImpl per-vert
normalization) marking them for removal alongside the deprecated API.
- Deprecation notice on GetMeshGL/GetMeshGL64's explicit normalIdx
parameter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Split shared propVerts across runs with mixed hasNormals
Addresses comment 3254084222 on PR #1718. When a single input propVert
is referenced by triangles from runs with differing hasNormals flags,
the eager-transform contract can't track both interpretations through
one slot 0..2: a Transform would rotate the hasNormals=true run's
normal and corrupt the hasNormals=false run's color (or vice versa).
The MeshGL ctor now duplicates such propVerts so each camp gets its
own slot. The first-seen camp keeps the original index; the opposite
camp gets an appended alt copy holding a snapshot of the original
property data. Subsequent triangles from each camp resolve through
their respective indices. DedupePropVerts already skips cross-meshID
pairs, so the duplicate survives ctor cleanup, and
EagerTransformPropNormals is gated on TriHasNormals per triangle, so
only the hasNormals camp's copy gets rotated.
Behaviour preserved for the existing numProp == 0 cases (the
single-array legacy push pattern is retained when there's no distinct
prop space). For numProp > 0 we always populate both triProp and
triVert now - prior code skipped triVert when prop2vert was empty, but
the split can introduce triProp values >= numVert that need triVert
for valid v0/v1 in CreateHalfedges.
Docs: re-add a paragraph to MeshGLP::HasNormals (and the WASM
hasNormals method) describing the auto-split, replacing the
"undefined for hand-built mixed-flag inputs" caveat that ce0e18d8
removed when it staged the failing test.
The new test NormalsSharedPropVertMixedFlags (added in ce0e18d8 as a
discussion-driver red test) now passes; its docstring is updated to
describe the fix rather than the failure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix clang-format on myHasN assignment
clang-format v20 (CI) wants the && wrapped onto a continuation rather
than aligned after `=`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "Split shared propVerts across runs with mixed hasNormals"
Per discussion on PR #1718 (review comment 3254084222): the shared-
propVert + mixed-hasNormals input shape is genuinely weird (no standard
output produces it) and adding the auto-split to every MeshGL ingest
slows the import path - already a sore spot relative to Boolean - and
adds maintenance surface for a case users shouldn't be hitting.
Reverting cf8d898a (the split implementation + doc changes) and ce6e394a
(the clang-format follow-up that touched the same block). Test wording
and updated doc explaining the constraint follow in a separate commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Document undefined behavior for cross-run shared propVerts
Add a caveat paragraph on `MeshGLP::HasNormals` (C++ header) and the
WASM `hasNormals(run)` method noting:
- hasNormals is per-run, so different runs may set it differently
(leading with this since the original removed-then-not-restored caveat
was misread on review as forbidding mixed-flag runs).
- Behavior is undefined when a single propVert is shared by triangles
from runs that disagree - a Transform rotates the slot on behalf of
the hasNormals=true camp and clobbers any hasNormals=false sharer.
- Standard CalculateNormals / Boolean / Compose outputs never produce
that shape - hand-built MeshGL inputs are the only way to hit it.
Repurpose the `NormalsSharedPropVertMixedFlagsUndefined` test (renamed
from `NormalsSharedPropVertMixedFlags`) to pin the documented
behaviour: assert the hasNormals camp's slot rotates as expected
(`run0Bad == 0`) and the no-normals camp's value is clobbered
(`run1Bad > 0`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Retry transient fetch failures in bundler / import / texture loaders
Wraps the three single-shot fetches in `bindings/wasm/lib/` with a small
shared retry-with-backoff helper. Retries on network errors, HTTP 5xx,
and HTTP 429; 4xx (other than 429) returns immediately so typo'd URLs
still fail fast. Three attempts at 200ms / 600ms, then surface the
error - ~800ms of added latency in the worst case.
Helps both production (ManifoldCAD.org and direct API consumers) and
CI, where jsDelivr rate-limiting has been flaking the bundler tests
(issue #1711).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address review on #1717: consolidate fetchWithRetry, simplify retry semantics
Per tonious's review:
- Move fetchWithRetry into lib/util.ts alongside the other helpers; delete
lib/fetch-with-retry.ts. (FetchError stays in lib/error.ts.)
- fetchWithRetry now throws FetchError on non-2xx directly, so the two
model/texture call sites are clean drop-ins (no manual response.ok check).
Bundler catches FetchError and adapts to the esbuild errors-array shape
inline at the call site.
- Drop the broad try/catch; only retry on TypeError (the "Failed to fetch"
/ DNS / connection-reset family). AbortError and other exceptions
propagate untouched. The unreachable-throw at the end goes away.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address review on #1717: dedup delay, consolidate fetchWithRetry exit
Restructure fetchWithRetry to consolidate the retry delay into a single
trailing call, per tonious's suggestion. The break path always assigns
response first, so a single non-null assertion at the end keeps TS happy
without scattering ! through the FetchError construction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Thread ExecutionContext into Hull and Minkowski
Pick up the remaining non-static eager ops from the #971 eager-ops
backlog (Refine landed in #1704, Face2Tri in #1699). Hull and Minkowski
now observe attached ctx via the same data-attached pattern: snapshot
this->ctx_ at the public entry point, pass through to GetCsgLeafNode
for input-tree eval, then thread to Impl::Hull / Impl::Minkowski for
in-call cancel checks.
Hull:
- Impl::Hull gains an optional ctx parameter. Cancel checks fire at
entry, after QuickHull's buildMesh, and during the SortGeometry
post-pass (already ctx-aware). QuickHull's own loop stays cancel-
blind for now -- granularity is "after buildMesh returns," mirroring
how Refine handles Subdivide.
- Manifold::Hull() (the member version) snapshots this->ctx_ and
threads through. Static Hull factories (vector<vec3>, vector<Manifold>)
are out of scope: they have no input Manifold to source ctx from
under the data-attached model. Separate follow-up.
Minkowski:
- Impl::Minkowski gains an optional ctx parameter.
- Cancel checks fire at function entry, between batches in the
convex-non-convex path, per A-face in the non-convex-non-convex
path, and before the final BatchBoolean.
- Internal BatchBoolean calls are evaluated with ctx via an evalBatch
helper that calls tree.GetCsgLeafNode(ctx), which routes through
Boolean3's existing per-phase cancel machinery. So mid-Boolean
cancel works inside Minkowski, not just between batches.
- Internal Manifold::Hull(simpleHull) calls (the static factory) remain
ctx-blind. Per-hull work isn't interruptible.
Tests:
- ManifoldContextCancelMidHull: pre-cancelled ctx aborts Hull.
- ManifoldContextCancelMidMinkowski: pre-cancelled ctx aborts
MinkowskiSum at the up-front check.
- ManifoldContextCancelConcurrentMinkowski (PAR=1 only): thread fires
cancel during a non-convex Minkowski; result is Cancelled or NoError
depending on timing race, same shape as the existing
ExecutionContextCancelConcurrent.
397/397 tests pass.
LevelSet (also on the backlog) is deferred: it's a static factory with
no input Manifold, same design tension as the static Hull factories.
Picking the static-factory ctx convention is a separate discussion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Hull: post-SortGeometry cancel check + concurrent test
Two small follow-ups from review of the previous commit:
- Impl::Hull's SortGeometry returns silently on cancel, leaving the
Impl in a partial state. Add an IsCancelled check between
SortGeometry and SetNormalsAndCoplanar so the latter doesn't run on
broken geometry and the caller sees a Cancelled result.
- Add ManifoldContextCancelConcurrentHull (PAR=1 only): observer thread
fires cancel during a Sphere(1.0, 256).Hull(), exercising the
post-buildMesh and mid-SortGeometry cancel paths that the existing
pre-cancel test doesn't reach.
397/397 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix macOS CI: processOverlaps in ConcurrentMinkowski test
The tet-difference input (tet - tet.Rotate(0,0,90).Translate(vec3(1)))
produces near-degenerate geometry that, under MANIFOLD_DEBUG, fails the
polygon.cpp triangulation CCW assertion on macOS. The pre-existing
Boolean.NonConvexNonConvexMinkowski{Sum,Difference} tests use the same
input with the same processOverlaps=true workaround; this test had been
missing it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Document Hull/Minkowski in the WithContext / ExecutionContext docs
- common.h: list Hull and MinkowskiSum/Difference alongside the Refine
family in the eager-op list; replace the misleading "per-boolean
granularity" paragraph with one that names the actual check points
in each op; add a Minkowski cancellation @code example.
- manifold.h: same eager-op list update on WithContext, plus a
MinkowskiSum line in the idiom example.
- bindings/wasm/*.d.ts: mirror the eager-op list in both the
Manifold.withContext docstring and the ExecutionContext interface
docstring.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Attach ExecutionContext to Manifold; remove per-call Status(ctx) variant
Replaces the per-call ExecutionContext parameter on Status() with a
data-attachment model: WithContext(ctx) returns a copy of the Manifold
with the context attached, and the no-arg Status() routes through any
attached context. Subsequent operations on the attached Manifold (and
the Manifolds they derive) inherit the attachment, so Translate,
Boolean, Hull and friends observe progress and cancellation without
needing extra parameters.
The attachment travels with the data: copying propagates it, and ops
returning a Manifold inherit from the primary operand (`*this` for
member ops, `manifolds[0]` for the static factories that combine
multiple Manifolds). WithoutContext() detaches; GetContext() returns
the attached context as std::optional<ExecutionContext> sharing state
with the original.
Removes the explicit Status(ExecutionContext&) overload from C++ and
its matching forms in the C / Python / WASM bindings
(manifold_status_with_context, .def("status", ctx), statusWithContext)
in favour of attach-then-Status(). Refine variants are left untouched
in this commit; that's the next one.
* Refine: route attached ExecutionContext through Subdivide post-pass (#1702)
The motivating example for per-call ctx parameters (PR #1702) was
mid-Refine cancellation. With ExecutionContext now attached to the
Manifold, that need can be served without changing public signatures.
Adds an optional ctx parameter to Manifold::Impl::Refine and threads
the cancel check between Subdivide and the post-pass
(SetNormalsAndCoplanar / CalculateVertNormals / SortGeometry), routing
SortGeometry through its existing ctx-aware overload. The public
Refine, RefineToLength and RefineToTolerance variants pass ctx_.get()
both into GetCsgLeafNode (input-tree evaluation) and into Impl::Refine
(post-Subdivide work).
Subdivide itself stays cancel-blind for now; the cancellation point is
at the granularity of "after Subdivide returns", which matches how
other ops in the codebase observe cancellation around their hot kernel.
* Refactor: collapse construct+inherit pattern into Derived() helper
Member ops returning a derived Manifold previously did:
Manifold result(...);
result.InheritContextFrom(*this);
return result;
Replace with a templated private helper `Derived(T&& arg)` that
forwards `arg` to the appropriate Manifold ctor and inherits the
attached ExecutionContext from `*this`. Static factories that combine
multiple Manifolds use the lvalue form, e.g. `manifolds[0].Derived(...)`.
Templated to forward through shared_ptr-of-derived-CsgNode (e.g.
shared_ptr<CsgLeafNode>) which would otherwise need two user-defined
conversions to reach Manifold.
No behavior change. Saves ~40 lines across 17 call sites.
* Pivot ExecutionContext attach to "eager-reads-input, deferred-drops"
Per #1704 review: ctx is consumed only by the next eager op invoked
on the result (Status, Refine family). Deferred ops (Boolean,
transforms, batch ops) ignore any attached ctx; their result has no
ctx. Raw copy/assignment preserves the attachment.
Public surface trimmed to one method per binding (C++ With,
manifold_with_context, with_context, withContext); WithoutContext /
HasContext / GetContext and their mirrors are dropped -- under the
new model the user always knows what ctx they attached, so
introspection has no real use case. Internal Derived() helper and
InheritContextFrom go with the inheritance step; call sites use
plain Manifold(...) construction.
Test flips: deferred ops drop input ctx; Refine result has no ctx;
copy/assignment preservation checked behaviorally via Status()
routing rather than HasContext.
395/395 pass (was 397; net -2 dropped + 1 new test for the
(a+b).With(ctx).Status() idiom).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Cleanups around ExecutionContext attach
- Fix `Manifold(Manifold())` wart in `Mirror`'s degenerate-normal branch
(search-and-replace leftover from collapsing the old `Derived(...)`
helper into plain `Manifold(...)` construction). Should be just
`Manifold()`.
- Remove dead `ExecutionContext(std::shared_ptr<Impl>)` ctor: it was
used by the now-removed `GetContext()`. No live callers.
- Tighten the `With(ctx)` docstring to explicitly close the list of
ctx-observing ops -- `Volume` / `GetMeshGL` / `BoundingBox` etc.
force evaluation but do not currently observe attached ctx. The
previous wording was positive-only and could mislead a casual reader.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Rename C++ `With` -> `WithContext` to match bindings
The bindings use `with_context` (C, Python) / `withContext` (WASM); the
C++ method had been the asymmetric shorter `With` on the rationale that
C++ overload resolution future-proofs the verb. Per Emmett's review on
#1704, the consistency tradeoff is small enough that matching the
bindings is the cleaner outcome.
Mechanical rename across 8 files (header decl, impl, bindings, tests,
docstring prose). No semantic changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Drop redundant Manifold(...) wrap around Minkowski return
`Impl::Minkowski` already returns a `Manifold`; wrapping it just invokes
the move ctor with no effect. Sed leftover from collapsing the old
`Derived(...)` helper into plain `Manifold(...)` construction. Same
class as the earlier Mirror fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Do not rebuild indices unless necessary.
* Use PropertyResolvers to deduplicate copied models.
* Ensure baseColorFactor is a Vec3.
* Testing, testing, testing...
* Fixi build error.
* Add declaration for gltf-validator
* Thread ExecutionContext into Face2Tri for in-flight cancel
Face2Tri runs inside Boolean3::Result's level-6 phase, between two
phase boundaries. Without ctx threaded in, cancel requested while
Face2Tri is mid-sweep waits for the entire per-face triangulation
to finish before the next phase() check observes it -- a visible
stall on dense intersection geometry where Face2Tri dominates wall
time.
Adds ExecutionContext::Impl* ctx as a defaulted last parameter on
Face2Tri, threaded through both for_each calls via the existing
ctx-aware parallel.h overload, plus explicit IsCancelled checks at
entry, inside the tbb::task_group lambda, after group.wait(),
before CreateHalfedges, and inside the sequential fallback loop.
If cancel fires inside Face2Tri, the surrounding phase() boundary
check in boolean_result.cpp observes Cancelled and discards outR,
so partial state never escapes.
No new phase counter; Face2Tri stays accounted as part of level 6.
kPhasesPerBoolean is unchanged.
ExecutionContextCancelDuringFace2Tri exercises the new code path.
It does not strictly distinguish pre/post-fix outcomes (existing
phase-boundary checks already yield Cancelled regardless), but
sanitizer lanes catch UB regressions in the partial-state path.
Part of #971's "remaining cancel blind spots" follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Remove ExecutionContextCancelDuringFace2Tri (review)
The test was nearly a duplicate of ExecutionContextCancelMidBoolean
above it. Per @elalish on #1699, picking one is enough.
The motivating claim in the test comment ("high-segment-count
spheres bias the boolean's wall time toward Face2Tri") was also
wrong: Face2Tri's general triangulation path fires for numEdge > 4,
which sphere booleans rarely produce. Flat faces with many edges
are what drive Face2Tri time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reorganize tests: split ExecutionContext + measurement clusters
Per @elalish's suggestion on #1687 and review on #1697.
Two extractions from manifold_test.cpp:
- test/context_test.cpp: ExecutionContext and ForEachCtx tests, plus
ParallelStableSortStability. Cancel/progress/reuse semantics for the
ExecutionContext API and the ctx-aware for_each / parallel mergeSort
mechanism tests they sit on top of.
- test/measurement_test.cpp: RayCast tests from manifold_test.cpp
plus MinGap and TriangleDistance tests pulled out of
properties_test.cpp. All "measurement-style queries on Manifolds":
ray-mesh intersection, inter-manifold distance, the per-triangle
distance primitive MinGap is built on.
Pure file moves: tests preserved verbatim, no behavior change.
Per the review on #1697:
- ForEachCtx and ParallelStableSort were briefly in their own
parallel_test.cpp; merged into context_test.cpp on Emmett's
suggestion that they're context-related and the file is too small
to stand alone.
- ray_cast_test.cpp was a single-feature file; broadened and renamed
to measurement_test.cpp with the MinGap and TriangleDistance tests
that fit the same conceptual cluster.
Rebased onto upstream/master after #1694 merged. The two new
ExecutionContext tests from #1694
(ProgressReachesOneOnTrivialBooleans, SubBooleanProgress) plus the
Progress() == 1.0 cross-check assertions from #1694 land in
context_test.cpp; the ExecutionContextNoWorkNeeded test's expected
value updates accordingly.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Sub-Boolean progress granularity via per-phase increments
Add `donePhases` and `totalPhases` atomics. `Progress()` is now
`donePhases / totalPhases`, where `totalPhases` is set at
`GetCsgLeafNode` to `(NumLeaves - 1) * kPhasesPerBoolean` for the
Boolean tree path. Decoupling the denominator from `totalBooleans`
means future non-Boolean ops (Hull, LevelSet, Refine, etc.) can
add their own phase budgets to `totalPhases` and increment
`donePhases` independently, without further changes to
`ExecutionContext::Progress`.
Sub-Boolean granularity comes from `Boolean3::Result`: the existing
local `cancelled()` lambda is renamed to `phase()`, which both
advances `donePhases` and checks cancel. Each `if (auto c = phase())`
site replaces the old `if (auto c = cancelled())` -- zero new lines
per phase. `kPhasesPerBoolean` must equal the count of `phase()` sites
in `Boolean3::Result`; a DEBUG_ASSERT on the happy-path return catches
drift.
A local `PhaseBalance` scope guard tops up `donePhases` to
`kPhasesPerBoolean` on any non-cancelled return, so trivial Booleans
(empty inputs, status errors, no intersection) that early-exit before
the first `phase()` site still credit a full Boolean's worth of
phases. On cancel, partial publication is intentional (Progress < 1.0
correctly reflects incomplete work).
`Compose` mirrors its existing `doneBooleans += set.size() - 1` with
a matching `donePhases += (set.size() - 1) * kPhasesPerBoolean`.
`doneBooleans` / `totalBooleans` semantics are unchanged and remain
useful for test introspection; they are no longer used in `Progress()`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Tests for sub-Boolean progress granularity
ExecutionContextProgressReachesOneOnTrivialBooleans: empty + cube
takes Boolean3::Result's IsEmpty fast-path, skipping every phase()
site. Asserts that PhaseBalance still credits kPhasesPerBoolean so
totalPhases == donePhases and Progress() reaches 1.0.
ExecutionContextSubBooleanProgress: a single overlapping-spheres
Boolean (totalBooleans == 1, so doneBooleans only flips at the very
end). An observer thread polls for donePhases > 0 while
doneBooleans == 0; the observation isn't asserted (timing-dependent,
avoids CI flakes) but the post-eval invariants confirm exact phase
accounting (totalPhases == donePhases == kPhasesPerBoolean,
Progress == 1.0).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address review comments
Three changes from https://github.com/elalish/manifold/pull/1694#pullrequestreview-4231453781:
1. PhaseBalance: split struct definition from instantiation
(`struct PhaseBalance { ... }; PhaseBalance balance{ctx_};`),
matching conventional C++ style.
2. PhaseBalance: drop reference-to-local members. The struct now owns
`published` and `fullPath` directly; the `phase()` lambda and
happy-path return access them via `balance.published` /
`balance.fullPath`. No more aliasing between the struct and
surrounding locals.
3. `Progress()` returns 1.0 (not 0.0) when no phases are scheduled.
"No work to do" maps more naturally to "trivially complete" than
to "not started," and matches the user expectation that a
returned `Status() == NoError` means the operation is done. Updated
the public docstring in common.h, the WASM TS docstring, and the
`ExecutionContextNoWorkNeeded` / `CBIND.execution_context_happy_path`
/ WASM `bindings.test.ts` tests that pinned the old value.
Pre-evaluation `Progress()` (called before `Status(ctx)`) now also
reports 1.0; this matches the new "no pending work" semantic and is
documented as such. Users polling `Progress()` from another thread
should already gate on a separate "started" signal -- the previous
0.0 was equally racy under that pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-iter cancel checks in AddNewEdgeVerts, AppendPartialEdges, AppendNewEdges, CreateProperties
Follow-up to #1674. AddNewEdgeVerts (sequential branch),
AppendPartialEdges, and AppendNewEdges iterate sequentially over
concurrent_maps / Vecs; CreateProperties has a per-tri sequential
loop. None used the ctx-aware for_each path, so a cancel
mid-execution wasn't observed until the next parallel op.
Add ctx parameter to AppendPartialEdges and AppendNewEdges, with a
per-iter IsCancelled(ctx) check at the top of each loop body. Same
per-iter check inside the existing sequential branch of
AddNewEdgeVerts and inside the per-tri loop in CreateProperties
(both already take ctx). Boolean3::Result picks up the corresponding
post-call IsCancelled guards so partial output cannot feed
unconditional downstream consumers.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Cancellable primitive: IsCancelled boundary + ctx-aware for_each
Privatize ExecutionContext::Impl::cancel; IsCancelled(ctx) is the
canonical reader, friended back. parallel.h gains for_each / for_each_n
overloads that check IsCancelled(ctx) once per chunk (parallel) or once
at start (sequential). nullptr ctx folds out at the call site, so
non-cancellable callers cost nothing. No callers of the new overloads
yet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Thread ctx through CSG; post-loop IsCancelled guards; tests
Convert cancellable parallel loops in boolean / sort / edge_op to the
ctx-aware overloads, pairing each with a post-loop IsCancelled early
return so partial output can't feed unconditional downstream consumers.
Add invariant docs on Boolean3 / Intersect12_ for the contract the
guards point at. Tests cover within-phase cancel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* parallel.h: periodic IsCancelled check on the seq for_each branch
Replace `std::for_each` on the sequential branch with a manual loop
that calls `IsCancelled(ctx)` every kSeqCancelChunk = 1024 elements,
so cancel latency is bounded under MANIFOLD_PAR=OFF and on the small-
input branch under PAR=ON. nullptr ctx still falls through to plain
`std::for_each` — non-cancellable callers pay nothing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strips iostream- and filesystem-using bits from the public API and
tests, for freestanding/embedded consumers (e.g.,
wasm32-unknown-unknown via wasm-cxx-shim). Default OFF.
When ON, defines MANIFOLD_NO_IOSTREAM and MANIFOLD_NO_FILESYSTEM as
PUBLIC compile defs on manifold, gates iostream-using TESTs at
compile time, and triggers CLIPPER2_NO_IOSTREAM=ON for the bundled
Clipper2 via a carry-patch tracking AngusJohnson/Clipper2#1094
(drops once that lands and the SHA pin moves past it). Incompatible
with MANIFOLD_DEBUG / MANIFOLD_TIMING (FATAL_ERROR).
Discussion: https://github.com/elalish/manifold/discussions/1046
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Compose() read the global atomic Manifold::Impl::meshIDCounter_ twice
to compute the per-component offset — once when shifting triRef.meshID
values and again when shifting meshIDtransform keys. Under cross-thread
CSG, another thread can advance the counter between the reads, so the
two offsets disagree. triRef ends up holding meshIDs not present in
meshIDtransform; IncrementMeshIDs's HashTable lookup then misses,
collapses those meshIDs to 0, and GetMeshGL64 emits runs with the
default Relation's originalID = -1.
Reading the counter once and reusing the snapshot in both shifts keeps
them consistent. Behavior is unchanged single-threaded.
Test: Boolean.BatchBooleanComposeMeshIDStable. A background thread
bumps the counter while a single worker repeatedly runs
BatchBoolean(Add) on three disjoint cubes (which forces the Compose
path). Without the fix the test sees a ~99% orphan-run rate; with the
fix, none.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix mergeRec stability when pivot comes from right half
mergeRec uses the larger half's midpoint as pivot. The left-pivot
branch is stable; the right-pivot branch used lower_bound on the
left, which puts equal-to-pivot from the left into the second
sub-merge -- after the pivot -- breaking the left-before-right
ordering that stability requires. Switch to upper_bound so
equal-to-pivot from the left land in the first sub-merge.
Adds ParallelStableSortStability covering several equal-key
bucket sizes that span partition boundaries (under MANIFOLD_PAR=ON).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Tighten ParallelStableSortStability test per review
- key is size_t instead of double
- input keys go through a multiplicative hash so the input isn't already
sorted by key
- compare element-wise against std::stable_sort as the oracle, dropping
the manual within-bucket check
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Preprocessor definitions should be propagated through
INTERFACE_COMPILE_DEFINITIONS, not INTERFACE_COMPILE_OPTIONS. When
-DMANIFOLD_DEBUG etc. are placed in compile_options they end up in the
installed manifoldTargets.cmake as INTERFACE_COMPILE_OPTIONS. The consequences
for downstream users are:
1. CMake's de-duplication and filtering logic that strips duplicates only
operates on COMPILE_DEFINITIONS; keeping them in COMPILE_OPTIONS means
every consumer pays the cost of extra command-line tokens that may
interact unexpectedly with other flags.
2. Makes it impossible to inspect compile-time feature flags through the
standard INTERFACE_COMPILE_DEFINITIONS target property.
The fix is a one-line change per usage: replace target_compile_options
with target_compile_definitions and drop the -D prefix that the latter
adds automatically.
std::hypot is recommended but not required correctly-rounded (IEEE 754
§9.2), so different libms are free to round differently. The three
calls in src/svd.h sit inside SVD's iterative reductions; in principle
this leaves SpectralNorm open to platform-dependent rounding, even
though the OBJ-hash determinism CI hasn't caught it in practice.
Switching to std::sqrt(a*a + b*b) makes the result bit-exact across
platforms -- *, +, and sqrt are correctly-rounded under existing
-ffp-contract=off. Doesn't need overflow protection at SVD's input
scales.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This removes the check and early return in the CMake configuration if
emscripten is configured, allowing the installation of the CMake
configuration and library headers
Follow-up to #1663. Before this change, cancellation only checked at the
CSG tree loop heads (SimpleBoolean / BatchBoolean / BatchUnion). A single
boolean between two huge meshes couldn't be cancelled — it would run to
completion regardless of ctx->cancel. Addresses howardt's concern on #971.
Boolean3's pipeline is now checked at 8 phase boundaries: 4 in the ctor
(before each Intersect12 and Winding03) and 4 in Result (assembly entry,
Face2Tri, CreateProperties, CalculateBBox — the latter three mirror
existing MANIFOLD_DEBUG timer sections). Adding one cancel check per
boundary bounds mid-boolean cancel latency to at most one phase, at the
cost of 8 relaxed atomic loads per boolean — negligible relative to the
work each phase does.
ExecutionContext::Impl* is passed to the ctor (default nullptr so
Manifold::Split and other non-CSG callers don't need to thread it).
On cancel, Result() returns an empty Impl with Error::Cancelled, same
error path as cancel at the outer loop. We rely on ctx->cancel being
sticky: if ctor detects cancel and bails, the top-of-Result check
catches it without needing a separate cancelled_ flag.
Test: ExecutionContextCancelMidBoolean spawns eval on a worker thread
and cancels after 1ms. Cancelled may come from either the inner
Boolean3 checks or the outer ToLeafNode check after a fast
SimpleBoolean; NoError is valid if eval races past. MANIFOLD_PAR == 1
guarded like CancelConcurrent.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The destructor at src/csg_tree.cpp:560 guarded the destructive
handleChildren(*childImpl) with `impl_.UseCount() == 1`, which checks
the outer (being-destroyed) node's impl — always unique when we reach
the destructor — not whether the child CsgOpNode is still held by other
live trees. Result: when a tree containing a shared, unevaluated
CsgOpNode was destroyed, it emptied that shared node's impl, breaking
any other tree that still referenced it.
Minimal reproducer (segfaults pre-fix):
Manifold shared = a + b;
Manifold tree2 = shared - c;
{
Manifold tree1 = shared + c;
} // tree1 destroyed -> shared's impl emptied
tree2.NumTri(); // crash: Add finalize with empty positive_children
Fix: check both `child.use_count() == 1` (no other tree holds this
CsgOpNode) and `child->impl_.UseCount() == 1` (no other CsgOpNode
shares this impl via Transform).
Related to #1378. Likely root cause of #1672.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add MANIFOLD_TIMING build option for release-build phase timing
MANIFOLD_DEBUG enables the Timer instrumentation in Boolean3 but also
the DEBUG_ASSERTs in hot kernels (Interpolate, Intersect, Kernel11),
inflating measurements. MANIFOLD_TIMING gates just the Timer code.
Also splits the Intersections ctor segment into four sub-phases
(Intersect12 P->Q, Intersect12 Q->P, Winding03 P, Winding03 Q) so the
verbose output maps 1:1 to the four ctor cancel checkpoints in #1669.
MANIFOLD_DEBUG is a strict superset, so existing builds get these
additional sub-phase lines alongside the Intersections total.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add perfPhases benchmark
Reusable harness for MANIFOLD_TIMING=ON (or MANIFOLD_DEBUG=ON). Runs
Add/Subtract/Intersect on sphere (8 scales) and MengerSponge(3,4),
forcing sponge construction before timing the ops. Gated on
MANIFOLD_DEBUG OR MANIFOLD_TIMING (matches minimizeTestcase pattern).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add ExecutionContext language bindings
Mirrors the C++ ExecutionContext API (from #1663) in the C, Python, and
WASM bindings:
- C: opaque ManifoldExecutionContext with manifold_execution_context{,_
cancel,_cancelled,_progress} and manifold_status_with_context; full
size/alloc/destruct/delete plumbing.
- Python: ExecutionContext class with cancel/cancelled/progress; Status
overload via nanobind overload dispatch.
- WASM: ExecutionContext embind class with the same methods; new
Manifold.statusWithContext(ctx) that delegates to a shared
ErrorToString helper (and adds the previously-missing Cancelled case).
Tests: C API round-trip (alloc, cancel, status-with-context); WASM
vitest smoke. Python smoke verified locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Clarify ExecutionContext.delete() docstring
Matches the existing wording used on Manifold.delete() and
CrossSection.delete(). Addresses review confusion on #1668.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Match ExecutionContext.delete() framing to peer APIs
Add "// Memory" section comment and @group Basics tag to match the
shape of Manifold.delete() and CrossSection.delete(). Follow-up to
elalish's comment on #1668 — the docstring wording was updated in
992bca54, but the surrounding framing still made delete() look like
an afterthought.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add real Python cancel assertion to all_apis.py
The pre-existing ExecutionContext block in all_apis.py was a smoke test
only — it called cancel()/status(ctx) on a leaf node (which skips CSG
evaluation entirely) and didn't assert the result. Add an assertion
using a CsgOpNode (Manifold.cube() + Manifold.sphere(1)) so a
regression in the Python status(ctx) overload — like the nanobind
overload-resolution break that tripped CI once during #1668 dev —
would actually fail the test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add TSAN CI workflow (initially per-PR)
Runs the test suite under ThreadSanitizer with a TBB built with
TBB_SANITIZE=thread (cached keyed on TBB tag + compiler + runner) so
TBB's internal fences are swapped for acquire/release ops TSAN can
model. Fails the job if any `WARNING: ThreadSanitizer` lines appear
in the test log.
Per-PR trigger for initial validation. Intent is to move to
nightly + on-demand once the plumbing is proven.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add -Wno-tsan to TBB build flags
GCC 14's -Wtsan fires on TBB's atomic_thread_fence calls even in
TBB_SANITIZE=thread mode. oneTBB master adds this suppression in its
own sanitize.cmake but it landed after v2022.3.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update cold-run timing note in TSAN workflow
Trivial comment update; also triggers a re-run to measure warm-cache
timing now that the first cold run populated the TBB cache for this
branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* TSAN: use halt_on_error=1, drop log-grep hack
Per pca006132's review suggestion. First race fails the job via TSAN's
own exit code — simpler than halt_on_error=0 + grep. Developers
investigating can rerun locally without the flag to see all races.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Improve editor load and keep Monaco features stable.
Load important Monaco parts first, move heavy parts to background loading, and fix local dev service worker behavior so editor features stay reliable.
* update esbuild.wsm
* fix editor syntax color and highliting
* correct minor nits
* correct minor nits (2)
Fix leak in manifold_alloc_* (C API)
The C API is designed around the pattern:
ptr = manifold_alloc_X(); // raw storage
manifold_foo(ptr, ...); // placement-new into ptr
manifold_delete_X(ptr); // dtor + free
but manifold_alloc_* was implemented as `new T`, which both allocates
AND constructs a default T. The subsequent placement-new in every
constructor function (manifold_cube, manifold_extrude, etc.) then
overwrites the memory without calling the old object's destructor.
For types whose default ctor allocates (Manifold's pNode_,
CrossSection's paths_), the original shared_ptr is leaked every
time — linear in the number of allocations.
Visible downstream as persistent LSan reports for
`manifold::Manifold::Manifold`, `manifold::CrossSection::CrossSection`,
and `Clipper2Lib` (via PathImpl's default PathsD).
Fix: return raw storage via `::operator new(sizeof(T))` so
placement-new constructs into uninitialized memory. The existing
in-tree C API test already uses `malloc(manifold_X_size())` +
destruct + free, so the "raw storage" contract is the one the API
was designed around.
Behavior change: alloc + delete without an intervening constructor
call is now UB (delete would call ~T() on uninitialized memory).
This pattern isn't documented, no in-tree test exercises it, and
real callers always populate.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add ExecutionContext for boolean progress + cancellation
Progress towards #971. Introduces ExecutionContext for observing progress
and requesting cancellation of long-running boolean evaluations via a new
Status(ExecutionContext&) overload; no other public API is affected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add cross-Manifold cancel tests
Cover three documented behaviors that weren't pinned down:
- Sticky cancel persists across different Manifolds evaluated with the
same ctx.
- A fresh ExecutionContext is not contaminated by an earlier cancelled
one; new evaluations can still complete.
- A cancelled ctx applied to an already-leaf Manifold returns the
Manifold's real status, since no evaluation happens to short-circuit.
Also drop a stale comment in ExecutionContextCancelPermanent that hinted
at a cancel-clearing API we never added.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Disambiguate Status overload in Python binding
The new Status(ExecutionContext&) overload made &Manifold::Status
ambiguous in nanobind's .def() call. Cast to the no-arg overload
explicitly; the new overload is not yet exposed in bindings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Guard std::thread tests behind MANIFOLD_PAR == 1
Emscripten/WASM builds without pthreads cannot construct std::thread
and abort at runtime. The two tests that spawn a side thread to
observe progress or trigger cancellation mid-evaluation aren't
meaningful on a non-parallel build anyway — evaluation is synchronous.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address #1663 review: iterative NumLeaves, poison stack on cancel, docs example
- NumLeaves: rewrite iteratively. Recursive version could stack-overflow
on deep CsgOpNode trees (e.g. `m += m1 += m2 += ...` chains), which
is the exact case the existing iterative ToLeafNode was written for.
- ToLeafNode cancel: poison every op_node currently on the stack, not
just `this`. A sub-op may have had its impl_ partially mutated during
its finalize (children replaced with an intermediate result); leaving
its cache_ unset would let a later evaluation of a shared sub-op run
against the partially-reduced tree. All frames now share one
ErrorLeaf(Cancelled), which is safe because the empty-cancelled Impl
is transform-invariant for error-status queries.
- ExecutionContext docstring: add a concrete observer-thread example.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix concurrent-use race on static affinity_partitioner
In AddNewEdgeVerts, `static tbb::affinity_partitioner ap` is shared
across every call. BatchBoolean dispatches SimpleBoolean in parallel
via task_group, so multiple workers hit the same static concurrently.
oneTBB's affinity_partitioner_base mutates its internal my_array /
my_size during parallel_for without synchronization, so concurrent
use is a real data race.
Change static -> thread_local: each worker gets its own partitioner,
and per-thread affinity is preserved across successive calls on the
same thread. Remove the `no_sanitize("thread")` attribute that was
silencing the signal.
TSAN warnings on Boolean + Minkowski: 3 -> 0. No test regressions.
Test-suite timing is within run-to-run noise.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Finish error propagation for Minkowski, SplitByPlane, Mirror
Three methods silently dropped errored-input status:
- MinkowskiSum / MinkowskiDifference: no status check before
Impl::Minkowski.
- SplitByPlane: if (IsEmpty()) early-return returned default
Manifold() pair; errored manifolds are empty, so error was lost.
- Mirror: zero-length normal path returned empty Manifold() without
honoring input error.
All use the standard PropagateStatus pattern before the early return.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement Manifold::RayCast(origin, endpoint) returning all hits sorted
by distance as std::vector<RayHit>. Constructs a minimal single-edge
Impl for the ray segment and calls Kernel12 from boolean3 directly,
reusing the full symbolic perturbation machinery for watertight results
at shared edges and vertices.
Partial implementation for #1640
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a per-instance shared_ptr<mutex> to both Manifold and CrossSection,
protecting the mutable pNode_/paths_ fields from concurrent access.
All reads of pNode_ in const methods go through either LoadPNode()
(builder methods) or GetCsgLeafNode() (query methods), both of which
lock the mutex. CrossSection::GetPaths() and Transform() similarly lock
pathsMutex_.
The mutex is wrapped in shared_ptr so it works with defaulted move
constructors. Copy constructor and operator= lock the source's mutex
to read its fields. operator= uses std::scoped_lock on both self and
source mutexes for deadlock avoidance.
Fixes#1635
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Error status is supposed to propagate through operations "like NaN" —
once a Manifold has an error, it should stick. Several code paths
silently dropped it or did expensive work on errored inputs.
Add PropagateStatus() helper and early-return guards to all affected
methods. Refactor existing guards in AsOriginal, Warp, WarpBatch to
use the new helper.
Partial fix for #1474
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add axes indicator to model-viewer
* fix: moved the label on the grid and removed form the console; prevent memory leaks for objects with dispose method; simplified the; simplified ceilNiceNumber function
* fix: capped minor axis per major axis; added transparent gray to the whole grid; grid sizing
Add missing C API bindings for Manifold methods and MeshGL fields
New bindings:
- manifold_get_tolerance, manifold_set_tolerance, manifold_simplify,
manifold_num_prop_vert
- manifold_meshgl{,64}_tolerance, _run_flags_length, _run_flags,
_num_run, _update_normals
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two issues caused CalculateNormals/SmoothOut to produce rippled surfaces
when minSharpAngle was zero or very small:
1. Unclamped acos: math::acos(la::dot(n1, n2)) returns NaN when the dot
product exceeds 1.0 due to floating point error. NaN > minSharpAngle
is always false, so these edges were silently treated as smooth. Fixed
by using the existing AngleBetween() helper which clamps the input.
2. Float-noise false positives: with minSharpAngle=0, the comparison
dihedral > 0 catches edges between nearly-coplanar faces where the
dihedral angle is nonzero only due to floating point noise (~1e-6
degrees). This creates incorrect vertNumSharp counts, corrupting the
normal grouping. Fixed by clamping minSharpAngle to a minimum of
degrees(sqrt(kPrecision)) (~5.7e-5 degrees), safely above float
noise but negligible for any real geometry.
Fixes#1577
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Merge() returns false (mesh already manifold), both
manifold_meshgl_merge and manifold_meshgl64_merge were destructing the
valid copy and returning the input pointer, violating the C API's
ownership convention. Always return the copy instead, since Merge()
leaves the mesh valid regardless of its return value.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Improve script icons in editor
Show edit icon only for the current user script and keep delete behavior safe for user scripts.
* Polish script rename and icon layout
Keep rename inline, show typed text while renaming, and keep icons clean for long script names.
* removed edit icon from dropdown and the dropdown width was full in less then 820px screen width so i solved it
* i have made some changes
* feat: added edge visibility toggle
* fix: formatting undo on editor.css and changed the position of edge toggle button next to documentation button
* fix: simplified edge overlay to wire overlay; removed transparency and changed the depthWrite to polygonOffset of the mesh material; removed imports from lib folder; changed the background color changing logic of toggle edge button
* fix: removed unused EDGE_MODE_KEY
* feat: exposed WarpBatch in WASM and added a benchmark comparison test between warp and warpBatch
* fix: formatted the bindings/wasm/warpBatch.perf.test.ts and heap is initialized in warpBatch function
* fix: flatten warpBatch vertices to avoid vec3 layout assumptions; add warpBatch correctness test and clean up benchmark
* update: removed the benchmark test
* project file for eclipse
* Adding CaDoodle and new Java Binding maintainer links
* Remove project files and ignore
* Adding comment to the ignore change
* fix(editor): stabilize refresh handling and offline behavior
Fix editor state transitions during auto-typing refreshes and reduce noisy logging from worker/bundler paths.
Improve PWA/dev offline reliability by updating service worker registration, cache versioning, app-shell precache, and local font asset usage for the Godel example.
Use same-origin manifest/favicon assets and move model-viewer import out of inline HTML to avoid Vite html-proxy issues.
* added comments
* added draggable pane dividers
* added changes to wrap the monaco editor content to its window and hide horizontal scrollbar
* fix(editor): replace pane clamp magic numbers with constants
* fix(editor): stabilize refresh handling and offline behavior
Fix editor state transitions during auto-typing refreshes and reduce noisy logging from worker/bundler paths.
Improve PWA/dev offline reliability by updating service worker registration, cache versioning, app-shell precache, and local font asset usage for the Godel example.
Use same-origin manifest/favicon assets and move model-viewer import out of inline HTML to avoid Vite html-proxy issues.
* added comments
* fix(editor): remove thi.ng skip and sw url version
* fix upside down cone using cylinder
* minor changes
* minor fixes
* commit import example
* 3mf tests
* formatting
* formattingv2
* importer+testmodify
* formatting
* integrate 3MF into main importer and normalize to glTF conventions
* added missing import logic and fast-xml-parser
* formatting
* review feedback
* formatting
* CI fixes:Added per-converted-mesh source-node tracking and used that for world transforms.
direct manifold source mesh
Sphere test now also uses strict round-trip checks
* CI test final fix
* removed redundant map use and common function
* formatting
* Fix ManifoldCAD PWA paths and manifest metadata
* fix(editor): handle service worker registration failures and stabilize PWA path resolution
* fix(editor): handle service worker registration failures and stabilize PWA path resolution
* style(editor): align editor.js with clang-format
* fix(editor): clear type fetch indicator after resolution
* fix(editor): improve type indicator visibility and harden service worker
- Declare autoTypings with let before AutoTypings.create() to avoid
TDZ issues when callbacks fire during creation
- Enforce a minimum 350ms display window for the type-fetch indicator
so it is visible even when resolution completes very quickly
- Track resolveEventUntil timestamp so a ResolveNewImports event alone
is enough to keep the indicator on even if isResolving clears early
- Use console.log (not console.debug) for indicator ON/OFF transitions
so they appear without enabling Verbose in DevTools
service-worker.js:
- Guard fetch handler to only intercept GET requests so POST/PUT etc.
pass through without error
- Wrap network fetch in try/catch; fall back to cached response on
network failure instead of propagating an uncaught rejection
- Add skipWaiting() on install and clients.claim() on activate for
immediate worker takeover on update
* style(editor): apply clang-format to editor.js and service-worker.js
* refactor(editor): simplify type indicator for review split
* Update to more recent cibuildwheel
Signed-off-by: Jonathan Hogg <me@jonathanhogg.com>
* cibuildwheel 3.x.x updates
- No longer need to skip PyPy wheels as they are off by default
- Pushing minimum Python version to 3.9 (3.8 has been EOL for over a
year)
Signed-off-by: Jonathan Hogg <me@jonathanhogg.com>
---------
Signed-off-by: Jonathan Hogg <me@jonathanhogg.com>
* commit
* review-feedback
* Restore Degrees vs. Radians section to tips.md
Lost during merge conflict resolution in previous merge commit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
I got clever with the site build, and didn't look at the `publish_npm`
pipeline. This fixes it by uploading two copies of the build.
`wasmpublic` contains the editor, examples, and documentation ready for
deployment to github pages. `wasm` contains the entire `bindings/wasm`
tree, ready for publication to npm.
Fixes#1548.
* Separate examples and editor.
* Make each sub-package place nice with NPM.
* It has been [0] days since the last linter error.
* Make CI work with separate examples.
* More tests, more tweaks.
* Move editor back under examples.
* It has been [0] days since the last linter error.
* Update lockfiles.
* reimplement ReadOBJ for robustness
* add OBJ IO for MeshGL64
* update tests to use OBJ IO
* make MeshIO private
* include fstream in test_main
* fix msvc
* off-by-one
* make emscripten DCE easier
* fix leak in cbind test
* fix line length
* convert models to internal representation
* simple offset test failing for some reason
* Initial cross section support.
* CSG display example
* It has been [0] days since the last linter error.
* Tests and creature comforts.
* Format
* Fixed example.
* BaseGLTFNode subclasses refactored for consistency.
* Use gltf-io when writing CrossSectionGLTFNodes.
* Revert GLTFNode.addChild() and listChildren()
* Refactor CrossSection and Manifold export for consistency
* A little more cleanup.
* Use a font to generate characters
* It has been [0] days since the last linter error.
* Add manifold_get_meshgl[64]_w_normals to C-bindings to allow transforming normals with GetMeshGL()/GetMeshGL64().
* Fix formatting
* Add MeshGLOptions, MeshGL64Options, manifold_meshgl_w_options and manifold_meshgl64_w_options
* add Manifold- prefix to struct names to match the rest of the C api
* Fix formatting
* TypeScript Documentation Overhaul, Part III
* GLTFMaterial improved.
* Write all the things
* Fix imports and address comments.
* Added minkowski operations.
* Move 3mf section down
* Reign in clang-format
* Address comments.
* Minor fixes.
* Fix excessive memory usage in batch union operations
The MeshCompare comparator used for heap operations in BatchUnion was
calling GetImpl()->NumVert(), which triggers transform application and
creates transformed mesh copies. This caused all meshes to be loaded
into memory simultaneously during the initial sorting phase.
Changes:
- Add NumVert() method to CsgLeafNode that returns the vertex count
without triggering transform application
- Add GetBoundingBox() method to CsgLeafNode for future optimization
(currently still uses GetImpl() for correctness)
- Update MeshCompare to use the new NumVert() method
- Add memory tracking infrastructure under MANIFOLD_DEBUG for debugging
- Add test case for batch union with many transformed meshes
Fixes#1493
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove debug mesh tracking code
Remove the memory tracking infrastructure that was added for debugging
purposes. The core optimization (NumVert() and GetBoundingBox() methods)
remains intact.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove test that was added for debugging
The BatchUnionManyTransformed test was added for debugging but isn't
needed as part of the final fix. The existing BatchBoolean test already
covers the batch union functionality.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use Arvo's algorithm for GetBoundingBox
Replace the 8-corner transformation with Arvo's more efficient algorithm
that computes the transformed AABB directly from the matrix elements.
This reduces the operation from 8 matrix-vector multiplies to 9 scalar
multiplies.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Simplify SimpleBoolean return statement
The two-statement form with explicit std::move was unnecessary.
In C++17, the temporary from Boolean3::Result() binds directly to the
rvalue reference parameter of ImplToLeaf, making the code equivalent.
The simpler one-liner is clearer and equally efficient.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix TypeScript 5.9 compatibility in gltf-io.ts
TypeScript 5.9 has stricter typing for TypedArray buffer types,
distinguishing between ArrayBuffer and ArrayBufferLike (which includes
SharedArrayBuffer). The gltf-transform library's setArray method
expects Uint32Array<ArrayBuffer>, but manifoldMesh.triVerts is typed
as Uint32Array<ArrayBufferLike>.
Add explicit type assertion to fix the build with TypeScript 5.9+.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Missing Minkowski Bindings
* Simplify Minkowski bindings per review feedback
Use camelCase names directly in C++ bindings instead of underscore-prefixed
names with JS wrapper functions. Also update @gltf-transform dependencies
to fix version mismatch build errors.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix excessive memory usage in batch union operations
The MeshCompare comparator used for heap operations in BatchUnion was
calling GetImpl()->NumVert(), which triggers transform application and
creates transformed mesh copies. This caused all meshes to be loaded
into memory simultaneously during the initial sorting phase.
Changes:
- Add NumVert() method to CsgLeafNode that returns the vertex count
without triggering transform application
- Add GetBoundingBox() method to CsgLeafNode for future optimization
(currently still uses GetImpl() for correctness)
- Update MeshCompare to use the new NumVert() method
- Add memory tracking infrastructure under MANIFOLD_DEBUG for debugging
- Add test case for batch union with many transformed meshes
Fixes#1493
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove debug mesh tracking code
Remove the memory tracking infrastructure that was added for debugging
purposes. The core optimization (NumVert() and GetBoundingBox() methods)
remains intact.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove test that was added for debugging
The BatchUnionManyTransformed test was added for debugging but isn't
needed as part of the final fix. The existing BatchBoolean test already
covers the batch union functionality.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use Arvo's algorithm for GetBoundingBox
Replace the 8-corner transformation with Arvo's more efficient algorithm
that computes the transformed AABB directly from the matrix elements.
This reduces the operation from 8 matrix-vector multiplies to 9 scalar
multiplies.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Simplify SimpleBoolean return statement
The two-statement form with explicit std::move was unnecessary.
In C++17, the temporary from Boolean3::Result() binds directly to the
rvalue reference parameter of ImplToLeaf, making the code equivalent.
The simpler one-liner is clearer and equally efficient.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add Naive Minkowski Sum Implementation
* Fix CI Bellyaching
* Add Python Minkowski Test
* Enable Determinism for the Failing CI Tests
* Fix Python Test
* Fix Python Test Again
* Make Python Test Cheaper so it doesn't segfault
* Add Insetting Option
* Update Python Binding Too
* Switch to a Member Function
* Remove the useThreading option
* Fix the Python Binding
* And the example
* Fix Formatting
* Split into Add/Subtract Functions, Add C Binding
* Make Methods const
* Update the tests; ensure that the inset operation works with convex shapes
* Fix Python Example Again
* Add JS Bindings
* Fix Formatting
* Fix Formatting Properly
* Change Function Names to Sum/Difference
* Fix formatting
* Move Tests to boolean_test
* Fix Formatting
* Fix NonConvexConvex Test
* Fix Formatting
* Add Surface Area
* passing the last test
* forgot these
* Fix Test?
* Fix for Disconnected Manifolds
* Codify IsConvex, Make Python Cheaper
* Refactor to use Impl
* Remove Dangling Line Return
* Add another early exit
* Fix Glaring Bugs...
* Fix Formatting
* Again
* Fix Mesh Export
* Run SimplifyTopology on the output of Minkowski
* Update Tests
* Update Formatting
* Make more work multithreaded
* Fix formatting
* Disable test cheating?
* Revert "Disable test cheating?"
This reverts commit d1f35e2668bea54aec87727b3e8c8bce5ac0ebe4.
* Update to latest code
* Fix Formatting
* Break out the Convex/Convex MDifference
I just realized what was supposed to be a simple test was actually two tests, one with more complexity than the other...
* Fix Formatting
* Fix Formatting
* Relax Minkowski test tolerances for cross-platform consistency
The NonConvexConvexMinkowski and NonConvexNonConvexMinkowski tests had
tight tolerances (1e-5) that fail due to minor numerical variations
across platforms. Widened surface area tolerances to 1e-2 and volume
tolerances to 1e-3 to account for triangulation differences.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix Minkowski test CI failures
- Add processOverlaps=true to ConvexConvexMinkowskiD to handle
duplicate/collinear points in triangulation
- Disable NonConvexNonConvexMinkowski test - produces non-manifold
results on some platforms due to coplanar face handling issues
in the naive Minkowski implementation (see issue #1430)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Re-enable NonConvexNonConvexMinkowski test
Re-enabled the test to properly debug the MinkowskiSum behavior
with coplanar faces from non-convex inputs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix MinkowskiDifference hang with tolerance-based coplanarity check
- Split NonConvexNonConvexMinkowski test into separate Sum and Difference tests
- Use tolerance-based coplanarity check (1e-15) instead of exact equality
to handle floating-point precision issues from scaling operations
- Update test expected values to more precise measurements with 1e-5 tolerance
- Use ManifoldParamGuard for RAII-style parameter management in tests
The coplanarity check now uses dot product comparison instead of exact
floating-point equality (linalg::equal), which was failing for scaled
normals due to precision issues. The dot product of parallel unit vectors
should be 1.0, so we check if |dot - 1.0| < 1e-15 (near machine epsilon).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Address PR review feedback for Minkowski implementation
- Move IsConvex() from edge_op.cpp to properties.cpp for better organization
- Fix data race in IsConvex() by using all_of instead of for_each_n with
shared anyConcave boolean (UB per pca006132's review)
- Remove unnecessary pImpl variable in IsConvex, use this directly
- Simplify convexity check to use > 0 instead of tolerance parameter
- Split NonConvexConvexMinkowski test into Sum and Difference for consistency
with NonConvexNonConvex tests
- Add analytical volume/surface area formulas for ConvexConvex tests
- Truncate excessive precision in test expected values
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Run clang-format
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Move Minkowski implementation from Manifold to Impl
- Add Impl::Minkowski method in impl.cpp with batch processing optimization
- Manifold::Minkowski now delegates to Impl::Minkowski
- This is more consistent with the codebase style and avoids repeated
GetCsgLeafNode().GetImpl() calls
- Includes BATCH_SIZE=1000 optimization for hull operations
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add parallelization to NonConvex-NonConvex Minkowski case
- Flatten nested face pair loops into single index space
- Process pairs in parallel batches using for_each_n
- Use validHull flag array to handle coplanar face filtering
- Collect valid hulls after parallel execution for BatchBoolean
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Run clang-format
* Use periodic batch reduction for NC-NC Minkowski to handle large meshes
Replace face-pair batching with per-A-face processing and periodic
reduction every 200 faces. This approach:
- Processes A faces sequentially, creating B-face hulls in parallel
- Merges accumulated results every REDUCE_THRESHOLD faces
- Prevents memory from growing unboundedly with large meshes
Enables complex meshes like spoon (904 tris) to complete without
crashing while maintaining similar performance for smaller meshes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Address review feedback from PR #666
- Pre-allocate composedHulls vector
- Simplify validHulls loop by checking IsEmpty() instead of separate bool vector
- Add surface area check to ConvexConvexMinkowski test
- Remove private Minkowski wrapper function, call Impl directly
- Add performance warning to MinkowskiSum/MinkowskiDifference docs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Increase coplanarity tolerance in NC-NC Minkowski sum
Increase kCoplanarTol from 1e-15 to 1e-9 to more reliably skip
nearly-coplanar face pairs that could cause degenerate hull
computations and potential hangs.
This tolerance is conservative enough to skip genuinely coplanar
faces while still allowing valid near-coplanar face pairs to be
processed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Emmett Lalish <elalish@google.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Split user docs from api docs.
* Build fixes.
* Organizing dev + user docs
* Remove selected @internal tags. They'll be excluded from user documentation anyhow.
* Added redirects jsdocs.
* Updated readme and deploy
* Fewer redirects.
* tighten tests
* refactor normal calculations
* fixed smooth slerp
* fixed face normals
* cleanup
* addressing comments
* tighten sine surface test
* more renaming
* fixed sine surface test
* cleanup
* new symbolic perturbation
* added perturbation test, not passing yet
* matched triangulations in test
* added second test
* try to fix tests
* small refactor
* fix compile
* remove manifold_curvature_size (dead)
* meshgl_num_* returning size_t (consistency)
meshgl64_num_* functions have been updated to return size_t like the
other index and length arguments in the bindings
* Fix mesh_gl_num* functions in source as well.
@Loosetooth caught this in #1448
`ArrayBuffer().bytes()` is not in wide use, and fails on Chromium
browsers.
While fixing this, it made sense to adjust the import/export
interfaces to use high level ArrayBuffer objects. Implementations
should be allowed to create their own data views.
* Import GLTF for display, bypassing manifold.
* Now with makeManifold()
* Documentation and textures.
* Refactored import and export into non-oo modules.
* Break down gltfNodeToManifold
* Small changes:
* Renamed `NonManifoldGLTFNode` to `VisualizationGLTFNode`
* Removed extra methods on `VisualizationGLTFNode`
* Scaled glTF imports from 1:1m to 1:1mm.
* Made most of the importer logic internal
* Tested the importer with manifoldCAD.
* Formatting...
* Updated package.lock.
* Better material management.
* Enable mesh tolerance
* Add tolerance parameter to importManifold.
* Merge meshes when not manifold.
* More progress. Still needs tests.
* Missed a few.
* Importer and exporter interfaces working. Using importer in worker tests.
* Export ImportOptions.
* It has been [0] days since the last linter error.
* import-manifold example added.
* First pass at tests and relative URLs.
* Update import-manifold example.
* Remove some wip code.
* Updated bundler exports, now with a test.
* Documentation updates.
* import.meta.main -> import.meta.url
* add exports for encapsulated and global types in package.json
* add .d.ts extensions to new exports for consistency
* explicitly export manifold.js and manifold.d.ts
* explicitly export manifoldCAD.js
* Stop using deprecated macos-13 images in CI
I made the smallest bump possible. Could also jump to macos-15-intel
at the appropriate places, but I figured you may be trying to keep
this running for (barely-)older OS versions.
* Use macos-15
* Remove GlobalDefaults object.
On the way, this involved:
* Added `lib/level-of-detail.ts`
* Refactored `lib/manifoldCAD.ts` and `types/manifoldCAD.d.ts` to use re-exports.
* Improved documentation.
* Extracted GLTFNode into a module.
* Added @internal to hide functions that aren't really public facing.
* Updated examples to match.
* Fix tests.
* Add specific error messages
* When glMatrix is mentioned in a ReferenceError
* When GLTF nodes are created at the top level,
yet no geometry is exported to manifoldCAD
* Treat bundled code as typescript by default.
* Better wording.
* Add TypeScript syntax test.
* It has been [0] days since the last linter error.
* Script import tests and infrastructure
* Almost working.
* Cleaned up esbuild plugin
* Exports in package.json
* Better errors through source mapping
* Consistent wasm initialization
* Bundler options and context types
* Massive refactor! WIP so PR comments not fully addressed (yet).
* Evaluator is gone. Most of it is in `garbage-collector` and `worker`.
* Models now have to `export default` rather than set `result`.
* Examples have been updated accordingly, and moved to `test/examples`.
* It has been [0] days since the last linter error.
* Bundle worker for easier inclusion in other projects.
Slightly more verbose worker.
* esbuild worker fix
* Format...
* More tests.
* CLI updated to match.
* Small tweaks.
* Now, with less gl-matrix
* Added involute gear example
* Enable CDN tests.
* Free of side effects.
* Involute fix.
* Tacks that may be brass in colour.
* Tracking GLTF nodes for fun and profit.
* Documentation update.
* bundle -> bundler in package.json
* Transform static imports to dynamic
...when called from a web worker. This allows use of `import x from 'npm'`
By default, packages will be loaded from https://esm.run/.
* Tests and broader syntax support.
* Extract evaluation and export from worker
* Refactor evaluator to be object oriented.
This involved extracting some of the context stuff back into the
`worker.ts`; the evaluator doesn't really need to know how to set up the
exporter.
* Exporter does not need `module`.
...As there's now an easier way to add methods to the evaluation context.
* Now working with nodejs.
* exportModel now returns a Blob.
* /examples/lib moved to /lib
* Add npm to wasm build.
* Evaluate documentation and tweaks
* Documentation and copyright updates.
* Update vite config.
on many linux distributions /usr/bin/python would refer to python 2
which no longer exists. python 3.x is usually /usr/bin/python3.
The finder handles this for us.
Fixes#1327
* Add JoinType.Bevel to Python binding
* Implement JoinType.Bevel
* Add JoinType.Bevel to other bindings
* Fix typo and test and formatting
* Fix formatting
* use union-find for winding number
* address comments
* fix comparison signs
* cleanup
* remove cfind, not needed when lock-free
* move DisjointSets to its own header with copyright info
* fix naming and casts
* Add test CurvedWallSimplify
* Change code formatting to appease check_format
* Fix two remaining format issues
* Move CurvedWallSimplify to boolean_test and call it DISABLED_SimplifyCracks
* started adding halfedge prop support
* fixed bugs
* attempt to push props through triangulation
* fixed a few bugs
* fixed MANIFOLD_ASSERT
* fixed bug
* updated edge_op
* fixed sort
* fixed compactProps
* more updates
* a few more
* fixed a bug
* fixed old bug
* fixed bug
* fixed edge swap
* small WASM update
* updated boolean
* updated smoothing
* removed triProperties
* reduce test memory
* format
* moved numProp out of meshRelation
* moved properties out of meshRelation
* harmonize includes
* remove unused
* update test to check simplify with properties
* fix CI
* more CI fixes
* undo no-unused req
* renamed internal faceID to coplanarID
* fixed tests
* updated quad short path
* Remove per-error hacks in favor of a "disable Werror" option
For users who are just looking to build and aren't interested in failing the
build due to warnings, provide a MANIFOLD_STRICT option to control it.
By default it will remain on, since developers will generally want to fix
errors found, but for user projects or those working with older compilers
setting -DMANIFOLD_STRICT=OFF will allow them to avoid build failure when
warnings are spurious.
Also should eliminate most of the motivation to suppress specific warnings
per-file, although that may still be desirable in some specific instances if
the newest compilers are the ones generating verifiably incorrect/unavoidable
errors.
* formatting
* Make a quick try at turning Dump into a public Read/Write API not dependent on MANIFOLD_EXPORT being set.
* Try to add Read/Write for MeshGLP, in addition to Manifold.
* OBJ indexes faces starting at 1, not 0
* Go with fixed output precision to make sure we clearly always have the expected number of printed digits
* Adjust comments - we are writing OBJ files, just putting more info in comments.
* Adjust names to be less general
These are not (and are not intended to be) general purpose/format I/O methods.
While we're at it, make clear that the format itself is not intended to be
guaranteed stable across releases.
* Formatting, see if we can also skip Windows CR line ending chars.
* more formatting
* Move ReadOBJ/WriteOBJ implementation to .cpp file
* formatting
* Remove MeshGL* ReadOBJ/WriteOBJ support, guard Manifold versions with MANIFOLD_IO option.
* Whoops, shared_ptr needs <memory>
* Add header guard, remove const char methods, use const std::string param.
* formatting
* MANIFOLD_IO -> MANIFOLD_DEBUG, reduce to just stream API
* formatting
* See if std::filesystem helps Windows find files correctly.
* Various cleanups, disable (I think) the Offset tests having trouble in WASM
* guard iostream include
* Remove unneeded headers
* Changed the delete memory operation to a destructure in the Merge functions as Manifold doesn't own this memory.
* Fix code format error.
---------
Co-authored-by: Scott Richmond <strich@users.noreply.github.com>
* fix opposite face removal
* added testcase
* fix test genus
* handle halfedgeTangent and faceNormal
* address comment
* fix CreateFaces
* set triPriority
* use named constant
* capture for msvc
* format
* make edge collapse order deterministic
* reorder halfedges
* update simplify result
* deterministic vertex normal calculation
* fix compilation error
* reduce code duplication
* test for determinism in ember test
* fix nondeterminism in topology fixup
* format
* fix compilation error
* fix nondeterminism in EdgePos vector
* make batch boolean deterministic
* test determinism with some samples
* don't stress test when wasm is used
* disable CondensedMatter64 for wasm?
* Add tests for performance like in the EMBER paper.
See the README for details.
* Trimming outliers didn't work. Trim plot range instead.
* cleanup
---------
Co-authored-by: pca006132 <john.lck40@gmail.com>
* refactor
* separate propVert from createFaces
* working, but very slow
* no longer slow
* added test
* loosen test
* merging
* update nix actions
* fix
---------
Co-authored-by: pca006132 <john.lck40@gmail.com>
* avoid fill after resize if possible
* speed up simplification
* fix FlagEdge
* avoids calling SplitPinchedVerts again
* triProperties should be initialized
* add comments
* fix
* check for non-finite input
* added test
* update test
* fixed unreferenced verts
* no repro locally anymore...
* fix facing triangle removal, simplify unreferenced vert removal
* remove unreferenced verts less often
* dump debug information
* simple self-intersecting case
* impl.h: define ImportMeshGL64 when MANIFOLD_EXPORT is defined
* try to fix compilation
* address comments
* better checks
* updated test
* some minor updates
* better debug output
* fix includes
* disable test
* smaller example
* allow setting epsilon
* revert an accidental change
* fix models
* address comments
* update dependencies and run wasm-tbb build
* update stack size...
* update
* format
* fix compilation error
* format
* fix
* idk what happened
* change emscripten version back
* don't test for wasm-tbb
* readme changes
Introduced a new function to the C bindings: `manifold_num_prop`. This function retrieves the number of properties associated with a Manifold object. Added corresponding tests to validate its integration.
* Adds C bindings for Triangulate()
* Triangulate() returns a std::vector<ivec3>, which I tried to respect.
Since ivec3 is 3 int's, I have
manifold_triangulation_tri_verts(void *mem, ...)
copy assuming mem has space for 3 * num_tri int's
* NOTE: manifoldc.cpp now includes "manifold/polygon.h"
* NOTE: I couldn't figure out manifold_destruct_triangulation(...)
Also, I don't understand the use case(s) of the destruct functions
(literally don't understand; I am sure they exist)
* Usage code:
```
ManifoldTriangulation *triangulation = manifold_triangulate(manifold_alloc_triangulation(), polygons, epsilon);
size_t num_tri = manifold_triangulation_num_tri(triangulation);
int *tri_verts = (int *) manifold_triangulation_tri_verts(malloc(num_tri * 3 * sizeof(int)), triangulation);
manifold_delete_triangulation(triangulation);
```
* fixing formatting
* fix destruct
* add triangulation CBIND test
---------
Co-authored-by: pca006132 <john.lck40@gmail.com>
* bindings: add MeshGL64 C bindings
* bindings: fix leak and address sanitizer issue
* bindings: add Mesh64 to python
* bindings: fix c binding 64 bit index type
* bindings: fix python 64 bit index type
* bindings: fix python again
* bindings: formatting
* renaming precision to uncertainty, and hiding
* add tolerance
* rename uncertainty to epsilon
* use epsilon instead of tolerance
* guard simplify topology for SetEpsilon
* format
* remove conflict markers
* fix builds
* use epsilon for barycentric
* prefer using epsilon
* update python bindings
* address comments
* format
* remove SetTolerance
Tolerance value is set before the mesh is created, when SimplfiyTopology
cannot be called.
* read/write MeshGL tolerance field
Looks like we lost the O3 flag always being on. This was good for some
fun trying to figure out why performance had dropped off so dramatically
- turned out it was simply because prior builds had NEVER been without
O3, so all builds - Debug or Release - were optimized.
On the other hand, there are arguments for and against O3 with debug
builds. The optimizations complicate/obscure debugging, but it's also
an order of magnitude slower running. Rather than be completely either
or, this PR defines a MANIFOLD_OPTIMIZED variable that a user can
specify to ON explicitly. Build types Release and RelWithDebInfo will
always have O3, and for other cases it'll be up to whether the user has
explicitly requested optimized building with MANIFOLD_OPTIMIZED.
* Define a test for simple offset booleans
This is intended to check for performance regressions - the BRL-CAD
logic performing this operation using v2.4.5 ran considerably faster.
Note - removing in-loop assertions for triangle count > 0 with the
intermediate forms greatly increases the speed, but they are placed
there deliberately to simulate what BRL-CAD does to validate the output
of the booleans on an incremental basis. In the event of a boolean
failure or invalid output being produced for any reason, we want to be
able to capture the *exact* inputs responsible - which means we need to
check after each boolean operation to validate the results and catch
any bad intermediate states at the time they are produced.
* formatting
* More formatting
* Per suggestion from elalish, simplify edge pair setup
* Switch to a Status() check - appears to have the same slowing effect.
* Move the checks out of the loops
* Use initializer lists for better compactness
* Restore in-loop checks with explanation
* Simplify with += syntax.
* Revert "Simplify with += syntax."
Getting CI failure, see if this had an impact somehow...
This reverts commit 55ca9a9dea3f3fddad14ea7858603e35a4a2f629.
* Revert "Use initializer lists for better compactness"
Still not passing?? Revert another commit.
This reverts commit 89065dd7833d55933c8367f4734091ae43b4517e.
* Reapply "Simplify with += syntax."
OK, starting from a passing state, see if this works.
This reverts commit ba6027e003e4d6d64deda35eed8c540bff4a0f80.
* Break the initialization changes down - start with faces array.
* Initialize pts array
* Ah ha. There it is. Indexing problem with second half of pnts setup.
* formatting
* Test
* Revert "Test"
This reverts commit c137a4825824c43a56ba51ea32b073072c9af20e.
* See if the failure triggers if we only check once at the end.
* Revert "See if the failure triggers if we only check once at the end."
This reverts commit 35f0eb0c96685defbfb44910f75bf5974b5c754e.
* python: fix type casters and warp batch
* python: allow docstring override
* update nanobind version
* python: add docstring_override.txt
* python: fix example
* version limit for nanobind
* vec changes
* collider optimization
* fix tracy
* slight optimization for polygon
* cleanup
* cleanup
* CMAKE_CXX_FLAGS should also be a string
* slightly optimize polygon
* should be lowest instead of min
* address comments
* update MANIFOLD_PAR guard
* try to fix black CI
* Check at compile time that MANIFOLD_PAR is set to either P (parallel) or S (series) mode.
* Switch from 'P' and 'S' to 1 and -1
* fix formatting
* Put the MANIFOLD_PAR poison pill in an internal header.
We want this check for Manifold compilation as a cross check on our CMake
logic, but this way client codes will work by default without being forced to
set it explicitly.
* format cmake files
* update format.sh
* fix linker flags for emscripten
* update readme
* use gersemi
* actual format
* should be public instead of interface
* make test work without download
* simplification
* format
* cleanup files
* hide some public APIs
* merged CMakeLists.txt for headers
* fix build
* include cleanup
* include cleanup again
* address #944
* python cmake install
* iostream is still needed
* fix nix
* Try to restore C bindings as a separate library build.
* Doesn't look like we need this define here now.
* meshIO isn't a separate library
* Right - manifoldc.h is in a different location
* Initial file moves, stage logic for src/CMakeLists.txt rework
* Start working on getting reorganized files to build, as well as doing some rework on the CMake logic
* remove unused libs, propagate dependency includes
* Ignore common temporary files and build directories
* Just build the C bindings, since this is already a C++ compile - reduces complexity.
* Fixes for python binding building
* Require C++17, don't use extensions
* Remove C binding CMake file
* Remove extra libraries from link line
* Try to set up the manifoldConfig file to just find the required dependencies, without downloading
* Go looking for glm and Clipper2 installed with Manifold first (untested)
* Use a bundled Clipper2 copy instead of downloading
This preserves the ability to disable cross_section (and thus the use of
clipper) but replaces the Download-and-Build fetch with a bundled source
copy to avoid the network connection. If we go this route, we're also
building Clipper2 directly into Manifold so client libraries don't have
to worry about linking it in.
* Remove option and directories no longer present
* Remove option no longer present.
* Put back the default .gitignore
* Add a few more patterns
* Revert "Use a bundled Clipper2 copy instead of downloading"
Feedback is we don't want to bundle clipper, so revert inclusion
This reverts commit 68e8e3ccceb35c87fc84626e28a95468781be8e3.
* Just build polygon as part of the main library
* Correct some library linkages
* Tweak exported target file install location - this seems to be what's expected on Linux, but will it work for other platforms?
* clang format fixes
* Just include FetchContent up front
* Fix include for minimize_testcase
* formatting
* Add sstream header
* Need to disable more code when cross_section is off
* Don't try to build the bindings unless MANIFOLD_CROSS_SECTION is on.
* formatting
* manifoldc folded into libmanifold
* Fix location of test-cmake.sh script
* Try using previous install dir for Apple/MSVC manifoldConfig.cmake file
Per https://cmake.org/cmake/help/latest/command/find_package.html it
looks like the search is different on different platforms. Need to look
at the available patterns more closely, but for now try using what's
working for the tests in master branch.
* Fix conditional test, add namespace
* Fix header install
* Remove unused bindings filter from lcov line, Remove unused MANIFOLD_CBIND option.
* Don't do tests if downloads are off, since they require googletest.
* elalish spotted that this isn't a public header - fix.
* Try out CMake intenting based on vim + file footer with settings.
* Rename sh directory to scripts per suggestion from pca006132
* Per pca006132, remove unnecessary find_package call for oneDPL.
* Simplify MANIFOLD_PAR to an OFF/ON setting
Per suggestion from pca006132, changed CMake logic to be ON/OFF, and
simplified the C++ defines to test for defined/undefined vs. checking
for a specific value.
* conv.h is also a private header
* Hmm - looks like flake naming was coming from MANIFOLD_PAR - try translating the settings.
* Whoops - need to translate the active value not the cached value.
* OFF is still defined, so make sure we catch that case too
* Explain multiple calls to get_filename_component
* Move parallel.h to src
* Move iters.h to src
* Place optional components in subdirectories.
* Correct path in gen_docs.py
* Fix the file location for python bindings.
* Revert "Move iters.h to src"
parallel.h was apparently specifically public - undo hiding of iter.h
This reverts commit ce3e45a9a6af42393fbb4b220e9bb2c8ebabd901.
* Revert "Move parallel.h to src"
parallel.h was apparently specifically public
This reverts commit 08fbcdf8e95d6afd1eb3617eed9c90caa6744b0d.
* Footers, indenting adjustments for other CMake files
* added RefineToPrecision, but needs work
* fixed 1-d triangulations
* added test of precision
* fixed approximation
* added bindings
* cleanup
* forgot to include
* propagate errors in boolean operands
* propagate error for transform and compose
* use 64-bit integers for MeshGL64
* use size_t for public APIs
* address comments
* fix signed compare errors
* fix
* avoid createFaces calls, need to separate relation from coplanarity
* simplified SetProperties
* fixed merge test
* simplified calculateCurvature
* fixed WASM + cleanup
* more cleanup
* MXE warning flags
* actually use mxe
* try again
* don't warn format when on mxe
* fix mxe
* disable c binding as well
* we can't run test here
* find threads first
* try harder
The test.cpp file and the CMakeLists.txt/Makefile were created with
the '>>' append in shell instead of '>'. Which means these files were
growing on multiple invocations if *-consumer was not deleted
between calls (which means that the test would fail the second time
around).
While at it, add the manifold/parallel.h include also to the
pkg-config test.
Make all includes 'system-header' style with angle brackets, as this
is what users of the library would use.
* added configuration summary
* use custom math type
* use std function if possible
* build success
* fix js binding
* format
* use better primitives
* use old precision for polygon tests
* fix polygon CCW check
* temp-fix tests
* add simpler failing test case
* refactored CutKeyhole
* fixed tests
* more keyholing fixes
* truncate long debug output
* shrink SDF blobs test
* fixed WASM and disabled zebra
* fix hull
* cleanup a bit
* fix wasm
* CI cleanuo
* reenable zebra
* upped precision and fixed tests
* fix format
* disable zebra test on windows
---------
Co-authored-by: Emmett Lalish <elalish@google.com>
* Removing redundant code (structs and function calls)
* Removed unecessary comments
* Removed ConvexHull, CCW and getConvexHullAsMesh
* Moved Impl::Hull to quickhull.cpp
* Modified some comments
* Removing VertexDatSource structure and Replacing it with Vec
* Integrated HalfEdge datastructure of Manifold (in progress)
* Changed QuickHull Class to accomodate VecView
* Formatting changes and modified erase
* Made the suggested changes
* Impl Hull function working
* Formatting
* Adding comments and making code more readable
* Made changes, added manifold namespace, removed multiple lines to add halfEdge
* Added the fix and corrected face indices to now match the expected indices (i.e edge index/3)
* Formatting
* Added comment as TODO and replaced std::cout with DEBUG_ASSERT
* move things around
* remove custom HalfEdge data structure
* Added MANIFOLD_EXCEPTIONS
* Revert "Added MANIFOLD_EXCEPTIONS"
This reverts commit b46e7cec59ae95254723a5b87e32d3c008c8a23f.
* fix include
* format
* Made the suggested changes
* Fixed : processing disabled faces bug
* Removed unecessary comments
* Trying atomic for j
* Suggested fix
* Atomic Add should work
---------
Co-authored-by: pca006132 <john.lck40@gmail.com>
* Add a cmake option for CrossSection
* Add MANIFOLD_CROSS_SECTION to manifoldConfig.cmake
* Document MANIFOLD_CROSS_SECTION in README
* Fix default MANIFOLD_JSBIND value
* Enable tests that run without CrossSection
* Remove cross_section.h from tests that don't use it
* Add ifdef to tests using CrossSection
* CI: Build without CrossSection
* CI: Separate Python bindings test
* Removed QuickHull Dependency
* Replaced Vector3 with glm::vec3 and VertexDataSource (and formatting)
* Revert nanobind
* Removing Templates (since using glm::vec3 so only need float)
* Formatting
* Converted glm::vec3 to glm::vec3d and float to double
* Fixed Formatting and Removed third_party
* Made requested Changes (m_,comments,removing unnecessary code)
* Resolving Conflict
* Fixed std::move warning (Shouldn't affect performance, ideally)
* Reverted third_party directory in bindings
* Reverting std::move
* Corrected std::move and added glm::dot to calculate distance squared
* Reverted m_epsilon due to epsilon and m_epsilon
* Fixed some Names
* Formatting
* Removed all unecessary m_
* Fixed manifold.yaml
* Added headers and pragma once
* Modified License
* Formatting
* Activated the two failing tests and added the fix for the algorithm
* Modified gitmodules to remove third_party
* Removed nanobind
* Testing Framework for Hull
* Fixed Formatting
* Made Changes Based on the Review
* Fixed Formatting
* Fixed Formatting Using clang-16
* Reduced NumSegs for Sphere to fit within memory
* Reducing NumSegs Further
* Reducing SphereSegs Further
* Fixed Formatting
* Check for Valid Convex Mesh
* Added Check for Valid Convex Hull, and two failing cases with reduced points
* Added DISABLED in tests and isMeshConvex check in hull_tests.cpp
* Use system nanobind if installed
* Add a pyproject.toml build-time dependency on nanobind
* Add nanobind to Nix buildInputs
* Replace nanobind submodule with FetchContent
* Remove nanobind directories from tool.scikit-build sdist.exclude
* Use Nix python3.withPackages for nanobind instead of buildInputs
* start of test
* started FindSurface
* beautiful smoothing
* implemented ITP root finding
* improved ITP
* added precision to SDF API
* test comparison to linear interpolation
* fixed quad 4-manifold edge
* fixed bug
* optimized ITP
* fix NaN tangents
* transform iterator
* strided range
* fix pointer/reference to strided range
* fix iter
* avoid thrust
* removed copy_if with stencil
* get rid of transform_reduce
* done!
* use parallel unique
* address comments
* trying to get OneDPL working on Mac
* running fast on mac again
* try fix mac oneDPL build
---------
Co-authored-by: Emmett Lalish <elalish@google.com>
* fix comparison between integers with different signedness and implicit narrowing
* fix compilation error and formatting
* allow build with exception disabled
* CI for building without exception
* format
* another fix
* msvc stuff
* fix python bindings
* update nix
* formatting
* old llvm somehow likes this better
* fix bug
* rename ASSERT into DEBUG_ASSERT
* remove unused file
* use ASSERT and remove obsolete limits
* polygon: change to size_t
* format
* fix sign compare
* install optional_assert.h
* address comments
* fix
* fix
* fix
* fix format
* CI: use GCC 14 in Linux build
Ubuntu 24.04 packages GCC 13. GitHub add GCC 14 to their runner.
* Ignore lcov mismatch errors
Processing ./test/CMakeFiles/manifold_test.dir/smooth_test.cpp.gcno
geninfo: ERROR: mismatched end line for _ZN23Smooth_Tetrahedron_Test8TestBodyEv at /home/runner/work/manifold/manifold/test/smooth_test.cpp:26: 26 -> 39
(use "geninfo --ignore-errors mismatch ..." to bypass this error)
* Update glm to 1.0.1
* Define GLM_ENABLE_EXPERIMENTAL for glm/gtx/quaternion.hpp
With GLM 1:
In file included from manifold/manifold/src/manifold/src/smoothing.cpp:15:
manifold/manifold/build/_deps/glm-src/glm/gtx/quaternion.hpp:24:3: error: "GLM: GLM_GTX_quaternion is an experimental extension
and may change in the future. Use #define GLM_ENABLE_EXPERIMENTAL before including it, if you really want to use it."
* Fix for #786 by setting numProps in GatherFaces method. Added test for Decompose with properties, and ExpectProperties function in test_main in order to check properties in a mesh match those set by SetProperties.
* Correct formatting of ExpectProperties
* Correct more formatting
* Add x_context variants of set_properties and warp functions in cbindings, along with tests
* Revert "Correct more formatting"
This reverts commit 6e409cf0a9e2c000bddd70ba41667d3209410d4a.
* Revert "Correct formatting of ExpectProperties"
This reverts commit 4d5ee88d9c52e380c6722f347bcd032a8a0d2492.
* Revert "Fix for #786 by setting numProps in GatherFaces method. Added test for Decompose with properties, and ExpectProperties function in test_main in order to check properties in a mesh match those set by SetProperties."
This reverts commit 03848fac198851ad3617de35348513ebfdf00791.
* Remove _context variants of functions and make context argument the default for all callbacks. Add bounds checks to warp test and volume and surface area checks to level set test
* Correct formatting
* Use M_PI not M_PIf as Windows/Mac don't have it
* Use glm not C math
* Mac build sensitive to glm::pow<float,float> vs glm::pow<float>
* Use std::pow for pow
* Fix for #786 by setting numProps in GatherFaces method. Added test for Decompose with properties, and ExpectProperties function in test_main in order to check properties in a mesh match those set by SetProperties.
* Correct formatting of ExpectProperties
* Correct more formatting
* Removed ExpectProperties and instead use existing test methods
* Call RelatedGL for each Decomposed Manifold
* switched to ivec4, tests still pass
* added partition quad support
* added quad support for property interp
* forming quad tangents
* convex barycentric
* fix interpolation
* added linear quad interp
* added quad smoothing
* fixed vec out of range
* all tests pass
* fixed quad tangents
* tightened quad test
* cleanup
* avoid dome duplicate work
* added docs
* update test
* updated a bunch of do loops to ForVert
* more cleanup
* Hull failure case
* test cleanup
* test helper cleanup
* fixed edge_op
---------
Co-authored-by: John Collins <jmicahc@gmail.com>
* Add tests for intersection
* Add POC MinGap implementation and tests
* Add tests where closest point falls on the triangle's face or on one of the edges
* Create TriangleDistance declaration
* Delete intersect tests
* Add stub MinGap definition
* Add triangle distance impl
* Add comments
* Fix tests
* Fix build
* Use collider
* Collider exploration
* Collider impl, cleanup
* Use par transform
* Revert settings.json, add comments
* Add comments
* Move tri_dist to utils
* Delete unnecessary include
* Create lambda for bounding box expansion
* Move duplicate logic to lambda
* Cleanup
* Use floats instead of doubles, use glm::clamp instead of own implementation
* User ternaries
* Remove DistanceTriangleTriangleSquared out params
* Move MinGap impl to Impl
* Use std::array instead of C-style
* Modify MinGap test cases for clarity, add overlapping tri_dist test
* Add c, wasm, python bindings
* Fix formatting
* Clean up comments
* Test whether faceboxes need to be sorted
* Add min_gap example to all_apis.py
* Fix formatting
* Clean up python binding
* Fix edge to edge test case
* Test BVH culling
* Remove sorting of other manifold's boxes
* Use collider_ member, remove sorting
* Move tests to manifold_test.cpp
* Remove whitespace
* Rename some tests for clarity
* Remove redundant test, modify test for edge to edge test for clarity
* Fix comments
* Parallelize narrow phase
* Reduce sphere res in test case to not cause out of memory error
* Add complex test case
* Remove unnecessary cast
* Refactor functor is lambda
* test windows dll build
* export all symbols on windows
* skip manifoldc_test on emscripten
* try fix windows build
* fix
* fix2
* fix manifoldc on emscripten
* fix
* fix manifoldc
* fixes
* fix windows ci
* fix python flags
* document windows shenanigans
* I hate windows
* tell users we are downloading thrust from source
Using glm/gtx/compatibility.hpp without GLM_ENABLE_EXPERIMENTAL has
become an error:
error: #error "GLM: GLM_GTX_compatibility is an experimental extension
and may change in the future. Use #define GLM_ENABLE_EXPERIMENTAL
before including it, if you really want to use it."
* Set up test for subtraction of a very large cuboid
Test case for https://github.com/elalish/manifold/issues/712 - these glb
inputs were generated from manifolds that succeeded in producing a
Boolean output, but (on one platform but not another) the Status() call
on the output boolean manifold crashed.
(A test for calling Status() on the output captured on the crashing
platform (in case there was something specifically bad about that
particular output on that platform) was tried, but the result was simply
a successful Status() call even with that specific input - hence, that
additional test is not included in this commit.)
* fixed 0 * inf
* switch to ASSERT
* tighten test
---------
Co-authored-by: Clifford Yapp <238416+starseeker@users.noreply.github.com>
* Extrude and Resolve should be static on Manifold.
* Fixed unit test for extrude/revolve issue.
* More test issues fixed.
* Clean up formatting.
* Placed extrude and revolve on both CrossSection and Manifold.
* Formatting.
* [python] fix warp callback
I recently broke this making it a no-op due to
the vec reference becoming a by-value copy in python
callback. This fix provides a simple adapter like
we had before allowing python to return modified
values from the callback.
* update torus_knot.py demo to expose both warp modes
* add torus_knot.py to pytest
* use pytest in github CI
* add pytest to flake.nix
* [python] numpy updates and cleanup
Also add CrossSection::Bounds()
* cleanup - const and reference args in py bind
* fix unnamed args
* all_apis.py more coverage
* fixup - more param renaming, docstring fixes
* fixup - run black
* fixup - move const to left side
* fixup extrude.py get_volume
* fix python examples for api update
* Apparent infinite loop case for boolean operation
This comes from the FAA Generic Twin example model included with BRL-CAD
- the glb files are written out from the input data structures that
triggered the infinite loop behavior.
* Fix formatting
* disabled GenericTwinBooleanTest7863
---------
Co-authored-by: pca006132 <john.lck40@gmail.com>
Co-authored-by: Emmett Lalish <elalish@gmail.com>
* Prepare a test input with failing boolean
Encountered this case when testing Manifold with the BRL-CAD example
file - specifically, the r.glass8 region. C++ exception "Vec out of
range" is thrown. Both inputs are the outputs of previous Manifold
boolean evaluations.
* disabled Havocglass8Bool test
---------
Co-authored-by: pca006132 <john.lck40@gmail.com>
* added notes about twistDegree and nDivisions
* update version
* removed perf comparison figure
it is pretty outdated considering we don't have the GPU backend anymore
and performance is different now.
* earclip compiles
* some tests passing
* 46 tests passing
* refactor
* cleanup
* new cost
* cleanup
* fixed TouchingHole
* more fixes
* only 5 tests failing
* fixed short sides
* fixed Sponge2
* better cost
* improved keyholing
* Moved Rect to public
* added top/bottom check
* fixed bBox
* Polygon tests pass
* fixed Sponge3a
* another fix, another test
* keyhole refactor
* Added ClipDegenerates
* Refactor FindStarts
* fixed bugs
* fixed Sponge4
* refactor clipped
* another bug, another test
* another bug, another test
* Sponge4 passing
* all tests passing
* removed old triangulator
* fix isfinite
* debug checks
* remove 2-manifold check
* added comments
* fixed missing point bug
* another test, another fix
* two tests added and fixed
* fix build
* fixed CoplanarProp
* fixed seg fault
* reenable 2-manifold check
* refactor for polygon area
* factored out loop
* tests passing
* another test, another fix
* another test, another fix
* fixed dedupe segfault
* ignore duplicated verts
* re-enabled turn180
* re-enabled duplicate tests
* another test, another fix
* refactor keyholing
* more precise area computation
* disabled a test
* added test
* another test, another test
* fixed bridge bound
* fewer short edges
* replaced intersection sorting with InsideEdge
* another test, another fix
* allow a few degenerates in Sponge4
* Add Status() to the Python Bindings
This will help consumers of the Python bindings diagnose why their input meshes are not translating into manifolds properly...
* Make clang-format happier
* Use its suggestions verbatim
* python stuff
* do not choose musllinux_1_2
* skip musl build
musllinux_1_1 is too old
* try musllinux_1_2
* pep-0632
* fix
* update
* fix CI
* skip emulated tests on mac
* use build frontend
* use scikit-build-core
* re-enable action
* minor changes
speed up compilation a bit
* use nanobind
* don't use SABIModule for older python versions
* fix build error
* fix find and replace error
* Revert "re-enable action"
This reverts commit b3ede2a707743def16ded29bfb8e2b9a1c37f596.
* use better policies
* removed redundant copy
* added condensed_matter test
* disable normal check for CondensedMatter16
* disable overlap checking as well
* use single vector SparseIndices
* faster binary search
* tracy
* disable cuda and omp backends
* formatting
* find data races
* remove some cuda stuff
* rename indices to vertices
* fix
* better SparseIndices API
* minor fixes
* parallel boolean
* optimize csg_tree
* relax Samples.GyroidModule
* small fixes
* vec_dh refactor
* fix compiler errors
* disable some gcc warnings
* only apply new warning flags for GCC
* parallel some more
* use std::mutex
* fix compilation error
* check if max concurrency > 1
* use task_group
* disable face reordering
* preserve face order
* comments
* fix cuda build
* fix meshid not found situation
* use explicit stack and scratch buffer
this avoids potential stack overflow and reduces allocation calls
* faster collider
* please cuda
* missing commit
* remove collider optimization
* dedup face_op.cpp
* dedup boolean_result.cpp
* remove ambiguous comment
* include array
* use system gtest if possible
* optional tracy support
* fix meshid not found situation
* try fix CI
* fix ubuntu build
* disable windows tbb build first
* update tbb config
* format
* fix cmake BUILD_SHARED_LIBS
we should make the library type implicit, so users can decide whether or
not to build shared libraries.
* revert public.h changes
* remove version requirement for gtest
* update tracy instructions
no need to use sudo,
and we need some debug flags for good stack capture
* fix js build
option(MANIFOLD_TIMING"Enable Boolean3 phase timing without debug overhead"OFF)
option(MANIFOLD_STRICT"Treat compile warnings as fatal build errors"OFF)
option(
MANIFOLD_NO_IOSTREAM
"Strip iostream- and filesystem-using bits from the public API and tests; useful for freestanding/embedded builds. When ON, defines both MANIFOLD_NO_IOSTREAM and MANIFOLD_NO_FILESYSTEM."
OFF
)
option(
MANIFOLD_DOWNLOADS
"Allow Manifold build to download missing dependencies"
ON
)
option(MANIFOLD_PAR"Enable Parallel backend"OFF)
option(
MANIFOLD_OPTIMIZED
"Force optimized build, even with debugging enabled"
OFF
)
option(MANIFOLD_TEST"Enable testing suite"ON)
option(MANIFOLD_PYBIND"Build python bindings"OFF)
option(MANIFOLD_CBIND"Build C (FFI) bindings"ON)
# MANIFOLD_JSBIND is only available when building with Emscripten
cmake_dependent_option(
MANIFOLD_JSBIND
"Build js binding"
ON
"EMSCRIPTEN"
OFF
)
# These three options can force the build to avoid using the system version of
# the dependency
# This will either use the provided source directory via
# FETCHCONTENT_SOURCE_DIR_XXX, or fetch the source from GitHub.
# Note that the dependency will be built as static dependency to avoid dynamic
# library conflict.
# When the system package is unavailable, the option will be automatically set
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Manifold also supports arbitrary vertex properties and enables mapping of materials for rendering use-cases. Our primary goal is reliability: guaranteed manifold output without caveats or edge cases. Our secondary goal is performance: efficient algorithms that make extensive use of parallelization, or pipelining when only a single thread is available.
## Users
[OpenSCAD](https://openscad.org/), [IFCjs](https://ifcjs.github.io/info/), [Grid.Space](https://grid.space/), and [OCADml](https://github.com/OCADml/OManifold) have all integrated our Manifold geometry kernel! Why? Because its reliability is guaranteed and it's 1,000 times faster than other libraries. See our [usage](https://github.com/elalish/manifold/discussions/340) and [performance](https://github.com/elalish/manifold/discussions/383) discussions for all the latest and to add your own projects & analyses.
Here is an incomplete list of our users, whose integrations may be anywhere from in-progress to released. Please feel free to send a PR to update this list with your own project - it's quite difficult for us to keep track.
For example, here is a log-log plot of Manifold's performance vs. earlier OpenSCAD geometry backends:
Manifold has bindings to many other languages, some maintained in this repository, and others elsewhere. It can also be built in C++ via [vcpkg](https://github.com/microsoft/vcpkg.git). There is also a native Rust port, [manifold-rust](https://github.com/larsbrubaker/manifold-rust), which passes Manifold's full test suite with identical results and has no C++ dependency — from a user's perspective it works like a binding.
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app. Our WASM is not GPU-accelerated, but it's still quite fast and a good way to test out our Manifold library.
| Language | Packager | Name | Maintenance |
| --- | --- | --- | --- |
| C | N/A | N/A | internal |
| C++ | vcpkg | [manifold](https://github.com/microsoft/vcpkg/tree/master/ports/manifold) | external |
If you like OpenSCAD / JSCAD, you might also like [ManifoldCAD][ManifoldCAD.org] - our own solid modelling web app where you script in JS/TS. This uses our npm package, [manifold-3d](https://www.npmjs.com/package/manifold-3d), built via WASM. It's not quite as fast as our raw C++, but it's hard to beat for interoperability.
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
If you prefer Python to JS/TS, make your own copy of [the notebook][Python Colab Example]. It demonstrates interop between our [`manifold3d`](https://pypi.org/project/manifold3d/) PyPI library and the popular [`trimesh`](https://pypi.org/project/trimesh/) library, including showing the interactive model right in the notebook and saving 3D model output.
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm), Python, and C to make this library more portable and easy to use.

We have four core dependencies, making use of submodules to ensure compatibility:
-`graphlite`: connected components algorithm
-`Clipper2`: provides our 2D subsystem
-`GLM`: a compact vector library
-`Thrust`: Nvidia's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms)
## Manifold Library
## What's here
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as a level set function for evaluating signed-distance functions (SDF) that improves significantly over Marching Cubes. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. We provide a [`Merge`](https://manifoldcad.org/docs/html/structmanifold_1_1_mesh_g_l_p.html) function to fix slightly non-manifold meshes, but in general you may need one of the automated repair tools that exist mostly for 3D printing.
The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
To aid in speed, this library makes extensive use of parallelization, generally through Nvidia's Thrust library. You can switch between the CUDA, OMP, TBB, and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled for CUDA, the code will still run without a GPU, falling back to the serial version of the algorithms. The WASM build is serial-only for now, but still fast.
Manifold has full support for arbitrary vertex properties, and also has IDs that make it easy to keep track of materials and what surfaces belong to what input objects or faces. See our [web example](https://manifoldcad.org/model-viewer.html) for a simple demonstration of combining objects with unique textures.
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible.
Also included are a novel and powerful suite of refining functions for smooth mesh interpolation. They handle smoothing both triangles and quads, as well as keeping polygonal faces flat. You can easily create sharp or small-radius edges where desired, or even drive the curvature by normal vectors.
To aid in speed, this library makes extensive use of parallelization through TBB, if enabled. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled with parallel backend, the code will still fall back to the serial version of the algorithms if the problem size is small. The WASM build is serial-only for now, but still fast.
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bear a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible.
### Dependencies
Manifold no longer has **any** required dependencies! However, we do have several optional dependencies, of which the first two are strongly encouraged:
Please avoid saving to STL files! They are lossy and inefficient - when saving a manifold mesh to STL there is no guarantee that the re-imported mesh will still be manifold, as the topology is lost. Please consider using [3MF](https://3mf.io/) instead, as this format was designed from the beginning for manifold meshes representing solid objects.
If you use vertex properties for things like interpolated normals or texture UV coordinates, [glTF](https://www.khronos.org/Gltf) is recommended, specifically using the [`EXT_mesh_manifold`](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_manifold/README.md) extension. This allows for the lossless and efficient transmission of manifoldness even with property boundaries. Try our [make-manifold](https://manifoldcad.org/make-manifold) page to add this extension to your existing glTF/GLB models.
Manifold provides high precision OBJ file IO, but it is limited in functionality and is primarily to aid in testing. If you are using our npm module, we have a much more capable [gltf-io.ts](https://github.com/elalish/manifold/blob/master/bindings/wasm/examples/gltf-io.ts) you can use instead. For other languages we strongly recommend using existing packages that focus on 3D file I/O, e.g. [trimesh](https://trimesh.org/) for Python, particularly when using vertex properties or materials.
Example for integrating with [Assimp](https://github.com/assimp/assimp) is in `extras/meshIO.cpp`, which is used by files such as `extras/convert_file.cpp`.
## Building
@@ -58,19 +109,79 @@ cd manifold
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. && make
test/manifold_test
make test
```
CMake flags (usage e.g. `-DMANIFOLD_USE_CUDA=ON`):
-`MANIFOLD_USE_CUDA=[<OFF>, ON]`: Provides Nvidia GPU parallelization, requires CUDA Toolkit and its nvcc compiler.
-`MANIFOLD_PAR=[<NONE>, OMP, TBB]`: Provides multi-thread parallelization, requires `libomp-dev` or `libtbb-dev`.
-`MANIFOLD_EXPORT=[<OFF>, ON]`: Enables GLB export of 3D models from the tests, requires `libassimp-dev`.
-`MANIFOLD_DEBUG=[<OFF>, ON]`: Enables internal assertions and exceptions.
-`BUILD_TEST_CGAL=[<OFF>, ON]`: Builds a CGAL-based performance [comparison](https://github.com/elalish/manifold/tree/master/extras), requires `libcgal-dev`.
CMake flags (usage e.g. `-DMANIFOLD_DEBUG=ON`):
-`MANIFOLD_JSBIND=[OFF, <ON>]`: Build js binding (when using the emscripten toolchain).
-`MANIFOLD_CBIND=[<OFF>, ON]`: Build C FFI binding.
-`MANIFOLD_DEBUG=[<OFF>, ON]`: Enables exceptions, timing, verbosity, OBJ test dumps. Has almost no effect on its own, but enables further runtime parameters to dump various outputs.
-`MANIFOLD_ASSERT=[<OFF>, ON]`: Enables internal assertions. This incurs around 20% runtime overhead. Requires MANIFOLD_DEBUG to work.
-`MANIFOLD_TEST=[OFF, <ON>]`: Build unit tests, requires `GTest`.
-`TRACY_ENABLE=[<OFF>, ON]`: Enable integration with tracy profiler.
See profiling section below.
-`ASSIMP_ENABLE=[<OFF>, ON]`: Enable integration with assimp, which is needed for some of the utilities in `extras`.
-`MANIFOLD_STRICT=[<OFF>, ON]`: Treat compile warnings as fatal build errors.
-`MANIFOLD_NO_IOSTREAM=[<OFF>, ON]`: Strip iostream- and filesystem-using
bits from the public API and tests; useful for freestanding/embedded
builds (e.g., `wasm32-unknown-unknown`). Defines both
`MANIFOLD_NO_IOSTREAM` and `MANIFOLD_NO_FILESYSTEM` as PUBLIC compile
definitions. The test suite still builds + runs — iostream-using
TESTs in `manifold_test`/`polygon_test`/`manifoldc_test` are gated
out under the macro. Incompatible with `MANIFOLD_DEBUG` /
`MANIFOLD_TIMING` (which use `std::cout` for diagnostic output).
Dependency version override:
-`MANIFOLD_USE_BUILTIN_TBB=[<OFF>, ON]`: Use builtin version of tbb.
-`MANIFOLD_USE_BUILTIN_NANOBIND=[<OFF>, ON]`: Use builtin version of nanobind.
> Note: These two options can force the build to avoid using the system
> version of the dependency. This will either use the provided source directory
> via `FETCHCONTENT_SOURCE_DIR_*` (see below), or fetch the source from GitHub.
> Note that the dependency will be built as static dependency to avoid dynamic
> library conflict. When the system package is unavailable, the option will be
> automatically set to true (except for tbb).
> WARNING: These packages are statically linked to the library, which may be
> unexpected for other consumers of the library. In particular, for tbb, this
> create two versions of tbb when another library also bring their own tbb,
> which may cause performance issues or crash the system.
> It is not recommended to install manifold compiled with builtin tbb, and this
> option requires explicit opt-in now.
Offline building (with missing dependencies/dependency version override):
Need to set `FETCHCONTENT_SOURCE_DIR_*` if the dependency `*` is missing.
-`FETCHCONTENT_SOURCE_DIR_TBB`: path to tbb source (if `MANIFOLD_PAR` is enabled).
-`FETCHCONTENT_SOURCE_DIR_NANOBIND`: path to nanobind source (if `MANIFOLD_PYBIND` is enabled).
-`FETCHCONTENT_SOURCE_DIR_GOOGLETEST`: path to googletest source (if `MANIFOLD_TEST` is enabled).
> Note: When `FETCHCONTENT_SOURCE_DIR_*` is set, CMake will use the provided
> source directly without downloading regardless of the value of
> `MANIFOLD_DOWNLOADS`.
The build instructions used by our CI are in [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml), which is a good source to check if something goes wrong and for instructions specific to other platforms, like Windows.
#### Sanitizer CI Reproduction
To reproduce the sanitizer CI test locally from repo root:
For more detailed documentation, please refer to the C++ API.
### Windows Shenanigans
Windows users should build with `-DBUILD_SHARED_LIBS=OFF`, as enabling shared
libraries in general makes things very complicated.
The DLL file for manifoldc (C FFI bindings) when built with msvc is in `${CMAKE_BINARY_DIR}/bin/${BUILD_TYPE}/manifoldc.dll`.
For example, for the following command, the path relative to the project root directory is `build/bin/Release/manifoldc.dll`.
```sh
cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -A x64 -B build
```
## Contributing
Contributions are welcome! A lower barrier contribution is to simply make a PR that adds a test, especially if it repros an issue you've found. Simply name it prepended with DISABLED_, so that it passes the CI. That will be a very strong signal to me to fix your issue. However, if you know how to fix it yourself, then including the fix in your PR would be much appreciated!
### Formatting
There is a formatting script `format.sh` that automatically formats everything.
It requires clang-format, black formatter for python and [gersemi](https://github.com/BlankSpruce/gersemi) for formatting cmake files.
Note that our script can run with clang-format older than 18, but the GitHub
action check may fail due to slight differences between different versions of
clang-format. In that case, either update your clang-format version or apply the
patch from the GitHub action log.
### Profiling
There is now basic support for the [Tracy profiler](https://github.com/wolfpld/tracy) for our tests.
To enable tracing, compile with `-DTRACY_ENABLE=on` cmake option, and run the test with Tracy server running.
To enable memory profiling in addition to tracing, compile with `-DTRACY_MEMORY_USAGE=ON` in addition to `-DTRACY_ENABLE=ON`.
### Fuzzing
To build with fuzzing support, you should set the following with CMake:
- Enable fuzzing by setting `-DMANIFOLD_FUZZ=ON`
- Disable python bindings by setting `-DMANIFOLD_PYBIND=OFF`
- Use `clang` for compiling by setting `-DCMAKE_CXX_COMPILER=clang++`
- You may need to disable parallelization by setting `-DMANIFOLD_PAR=OFF`, and set `ASAN_OPTIONS=detect_container_overflow=0` when building the binary on MacOS.
## About the author
This library was started by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee and this is my 20% project, not an official Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
This library was started by [Emmett Lalish](https://elalish.blogspot.com/), currently a senior rendering engineer at Wētā FX. This was my 20% project when I was a Google employee, though my day job was maintaining [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
1. Go through Github issues to verify bugs have been fixed and closed.
1. Verify [ManifoldCAD.org](https://manifoldcad.org) - check a few examples, run them, download a GLB and a 3MF.
1. Verify our three.js [example](https://manifoldcad.org/three) is functional.
1. Verify our model-viewer [example](https://manifoldcad.org/model-viewer) is functional - select Union and Intersection.
1. Verify [make-manifold](https://manifoldcad.org/make-manifold) is functional. Try dropping [DragonAttenuation.glb](https://github.com/KhronosGroup/glTF-Sample-Assets/blob/main/Models/DragonAttenuation/glTF-Binary/DragonAttenuation.glb) in and verify you can select "View Manifold GLB" and that the dragon is still present while the backdrop is removed. Download the GLB.
1. Run the Prepare release workflow from the Actions tab with the new version number. It sets the version in CMakeLists.txt, pyproject.toml, flake.nix, package.json and test-cmake.sh, refreshes the package-lock files, and opens a PR.
1. Verify tests pass on that PR, manually trigger build_wheels CI, merge.
1. On Github, draft a new release, make a new tag with the version number, add release notes, and publish.
1. Check the Actions and verify that both PyPI and npm publishing actions ran successfully.
1. Verify the npm [package](https://www.npmjs.com/package/manifold-3d?activeTab=code) looks good - unpacked size should be close to 1MB.
1. Verify PyPI [package](https://pypi.org/project/manifold3d/#files) looks good - a bunch of built distributions ranging from ~600kB to ~1.1MB.
1. If there's a problem with release deployment, the release workflows can be triggered separately, manually for any branch, under the Actions tab. See [Deploying manually](#deploying-manually) for the site deploy in particular.
## Where the site is published
`manifoldcad.org` is served from the `gh-pages` branch, which holds several
builds side by side:
| Path | Updated | Contents |
| --- | --- | --- |
| `/` (root) | each published release | the current release, served at manifoldcad.org |
| `alpha/` | every master commit that passes CI | unreleased build, for testing |
| `vX.Y.Z/` | once, when that release is published | a frozen snapshot of that version |
Each is a complete copy of the site: the ManifoldCAD editor, the examples,
the benchmark dashboard, and both the C++ and TypeScript docs. A release
publishes twice, once to its own `vX.Y.Z/` directory and once to the root,
so manifoldcad.org always serves the current release directly.
Subdirectory deploys erase their target before writing, so each holds exactly
one build. The root deploy cannot, because clearing it would take the
subdirectories with it, so there it overwrites in place instead.
## Deploying manually
The Deploy documentation workflow can be run by hand from the Actions tab for
a quick fix or a docs-only update, without cutting a release. It takes two
inputs:
- **ref** - the branch or tag to build from, e.g. `v3.4.0`
- **destination_dir** - the directory on `gh-pages` to publish into, e.g. `v3.4.0`
The target directory is erased and rebuilt, so the result is exactly what the
chosen ref produces. Nothing outside that directory is affected.
Keep `ref` and `destination_dir` describing the same version. Publishing
master into `v3.4.0/` would leave that directory claiming to be a release it
is not.
Typical uses:
- **Fix a published version.** Branch from its tag, commit the fix, then
deploy that branch into the version's own directory. Useful for correcting
documentation without a patch release.
- **Backfill an old release.** Deploy its tag into a matching directory so the
version has a browsable copy of the site.
- **Preview a branch.** Deploy it somewhere disposable rather than over a
directory that matters.
Builds from before the versioned layout existed assume they are served from
the site root and will load the wrong assets from a subdirectory. Deploying
such a tag needs the relative-path fix applied on top first.
A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. 'Manifold' implies that there are no gaps or tears, and that all faces are oriented outwards.
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. It is available as a WASM module that runs in any modern browser.
Our primary goal is reliability: guaranteed manifold output without caveats or edge cases. Our secondary goal is performance: efficient algorithms that make extensive use of parallelization, or pipelining when only a single thread is available.
## [ManifoldCAD.org](https://manifoldcad.org)
If you like OpenSCAD / OpenJSCAD, you might also like ManifoldCAD - our own solid modelling web app based on this package.

If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app where you script in JS/TS and save a GLB or 3MF file. It contains several examples showing how to use our API to make interesting shapes. You may notice that some of these examples bear a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, this library is dramatically faster and the code is more flexible.
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
<!-- #endregion IncludeInUserGuide -->
<!-- #region NoStlPlease -->
The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
Since Manifold is a WASM module, it does not automatically garbage-collect like regular JavaScript. You must manually `delete()` each manifold object constructed by your scripts, see [discussion](https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287).
Please avoid saving to STL files! They are lossy and inefficient - when saving a manifold mesh to STL there is no guarantee that the re-imported mesh will still be manifold, as the topology is lost. Please consider using [3MF](https://3mf.io/) instead, as this format was designed from the beginning for manifold meshes representing solid objects.
## Examples
If you use vertex properties for things like interpolated normals or texture UV coordinates, [glTF](https://www.khronos.org/Gltf) is recommended, specifically using the [`EXT_mesh_manifold`](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_manifold/README.md) extension. This allows for the lossless and efficient transmission of manifoldness even with property boundaries. Try our [make-manifold](https://manifoldcad.org/make-manifold) page to add this extension to your existing glTF/GLB models.
Please see our usage [examples](https://github.com/elalish/manifold/tree/master/bindings/wasm/examples) to see how to interface this library with `three.js`, `<model-viewer>`, and `glTF`. Of particular note are the included libraries for lossless roundtrip of manifold meshes through glTF files, via a new extension: [EXT_manifold](https://github.com/KhronosGroup/glTF/pull/2286).
<!-- #endregion NoStlPlease -->
## About the author
This library was started by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee and this is my 20% project, not an official Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
This library was started by [Emmett Lalish](https://elalish.blogspot.com/), currently a senior rendering engineer at Wētā FX. This was my 20% project when I was a Google employee, though my day job was maintaining [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
To debug the WASM build directly in Chrome dev tools, simply build in debug mode:
```
emcmake cmake -DCMAKE_BUILD_TYPE=Debug .. && emmake make
```
and install the [DWARF](goo.gle/wasm-debugging-extension) Chrome extension.
When testing [ManifoldCAD.org](https://manifoldcad.org/) (either locally or the
deployed version) note that it uses a service worker for faster loading. This
means you need to open the page twice to see updates (the first time loads the
old version and caches the new one, the second time loads the new version from
cache). To see changes on each reload, open Chrome dev tools, go to the
Application tab and check "update on reload".
## Documentation
This guide and the ManifoldCAD User Guide are built using [TypeDoc](https://typedoc.org).
### Building Documentation
Documentation can be built using NPM:
```bash
cd bindings/wasm
npm install
npm run build
npm run docs
```
After this, the User and Developer docs will be in `docs/jsuser` and `docs/jsapi` respectively.
### Writing for the User Guide
The ManifoldCAD User Guide is generated from the declaration files present in `types/`. These mostly re-export declarations, allowing the User Guide to inherit from the Developer Guide.
TypeDoc reflections (comments) flagged as `@internal` will _not_ be visible in the User Guide, but will be present in the Developer Guide. Reflections tagged as `@hidden` will not be visible in either guide.
### WASM Bindings
The manifold library itself is documented in three files:
* `manifold-global-types.d.ts`: Defines lower level types and interfaces like {@link manifold.Vec2 | Vec2}, {@link manifold.Mat3 | Mat3}, &c.
* `manifold-encapsulated-types.d.ts`: Declares objects and types that are implemented in C++, and compiled to WASM. Definitions of classes like {@link manifold.Manifold | Manifold} and {@link manifold.CrossSection | CrossSection} can be found here.
* `manifold-root.d.ts`: Defines the {@link manifold.ManifoldToplevel | ManifoldToplevel} interface, and re-exports the above two files.
These three files are consolidated into `manifold.d.ts` using [API Extractor](https://api-extractor.com/). This is automatically run before each build as an npm lifecycle hook. It can be run manually as `npm run prebuild`.
### Terminology
These are general guidelines, not hard rules. The aim of this section is to encourage consistent documentation, not to institute brand standards.
* When referring to the library, manifold should generally be lowercase. Manifold can be capitalized when grammatically correct, for example at the start of a sentence. If unclear, `manifold` can be marked up as code to distinguish it from the manifold property.
* The Manifold class should generally be capitalized. Where appropriate, it can be linked directly to the {@link manifold.Manifold | Manifold} class documentation.
* ManifoldCAD should be capitalized. [ManifoldCAD.org](https://manifoldcad.org) should be a link.
There are three ways to use manifold in your own projects:
1. Using the ManifoldCAD worker. The worker adds overhead, but it will sandbox scripts. It also makes it possible to include packages at runtime.
1. Using ManifoldCAD libraries directly. ManifoldCAD implements importers, exporters, and garbage collection amongst other utilities. These modules are reasonably light and can be used outside of manifoldCAD.
1. Using the [Manifold WASM module directly](./bindings.md) without any of the above.
## Using the ManifoldCAD worker
The worker is a code evaluation sandbox intended to run as a [web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). It will bundle a script, include any dependencies, and even fetch those dependencies from a CDN if required. At run time, it will evaluate the script as a [dynamically created function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function).
> Geometry where two manifolds are meant to share a face exactly is called **marginal geometry** and should generally be avoided. Manifold uses symbolic perturbation, which means multiple topologically valid results may be within rounding error of each other — so even small imprecisions can produce unexpected output.
Two common sources of trouble:
- **Floating point drift** — JavaScript uses IEEE 754 binary floating point, so `0.1 + 0.2` evaluates to `0.30000000000000004`. Manifold does not compensate for this in its transform pipeline. A bounding box maximum of `0.30000000000000004` instead of `0.3` is enough to prevent a union from closing properly.
- **Different triangulations** — even with floating-point identical coordinates, if two coplanar faces are triangulated differently, that can also lead to gaps and slivers.
Where marginal geometry is unavoidable, the recommended workaround for floating point drift is to **work in larger, integer-friendly units**:
1. Scale coordinates up by a round factor (e.g., 1000) so your values are whole numbers
2. Use `Math.round()` after any arithmetic to eliminate accumulated drift
3. Perform all Manifold operations
4. Scale the result back down if needed
```js
const scale = 1000;
// Instead of cube([0.1, 0.1, 0.1]) translated by [0.2, 0.2, 0.2]:
See [discussion #1135](https://github.com/elalish/manifold/discussions/1135) for more context.
## Degrees vs. Radians
Manifold's rotation API takes **degrees**, not radians. This is a deliberate design choice: Manifold can eliminate floating point error entirely for multiples of 90°, and uses more efficient code paths when it detects such rotations.
```js
// Pass degrees directly
box.rotate([90, 0, 0]);
box.rotate([0, 45, 0]);
```
If your own code works in radians (e.g. when using `Math.atan2`), convert to degrees before passing to Manifold:
```js
const toDeg = (rad) => rad * (180 / Math.PI);
const angle = Math.atan2(y, x); // radians
box.rotate([0, 0, toDeg(angle)]);
```
For common angles, always prefer the exact degree value (`90`, `45`, `30`, etc.) over a computed approximation to get the precision guarantee. See [issue #1262](https://github.com/elalish/manifold/issues/1262) for a real-world example where a rotation of `-89.999999999999` instead of `-90` caused mesh cracks.
Just go to [ManifoldCAD.org](https://manifoldcad.org), and check out the examples!
### On the Command Line
ManifoldCAD has a [command line interface](https://github.com/elalish/manifold/blob/master/bindings/wasm/bin/manifold-cad). It can be run directly as `./bin/manifold-cad`. It can also be run via `npx`. If Manifold is not already present, use `npx manifold-3d manifold-cad` or `npx manifold-3d`. If Manifold is already installed, `npx manifold-cad` will suffice.
The output file can be in either `.glb` or `.3mf` format, determined by extension.
## Coordinate Systems
ManifoldCAD uses a right-handed coordinate system where '+Z' is up. Length units are assumed to be millimetres. This is slightly at odds with glTF, which also uses a right handed coordinate system, but specifies '+Y' as up, and length units are in metres.
ManifoldCAD will scale and rotate automatically when importing or exporting glTF models. This means that an arrow pointing in the '+Z' direction in will point in the '+Y' direction after export. However, that arrow will still point up, and will still be the same size.
This is the home of our editor, [ManifoldCAD.org](https://manifoldcad.org/), as well as our other public examples of integrations with `three.js`, `<model-viewer>`, and `glTF`. Included are `manifold-gltf.js` and `gltf-io.js`, which are intended to be fairly general-purpose libraries for interfacing between manifolds and glTF. We should probably make them into their own npm package at some point.
## Local development
First, follow the directions in the root README to get your C++ build environment set up and working for WASM. From this directory (`bindings/wasm/examples/`) you can test the JS bindings by running:
```
npm install
npm test
```
To develop the manifoldCAD.org editor as well as our other example pages, run
```
npm run dev
```
which will serve the pages, watch for changes, and automatically rebuild and refresh. This build step doesn't do TS type-checking, so to verify everything is correct (beyond VSCode's TS linting), run
```
npm run build
```
See `package.json` for other useful scripts.
Note that the `emcmake` command automatically copies your WASM build into `built/`, (here, not just under the `buildWASM` directory) which is then packaged by Vite into `dist/assets/`.
To debug the WASM build directly in Chrome dev tools, simply build in debug mode:
```
emcmake cmake -DCMAKE_BUILD_TYPE=Debug .. && emmake make
```
and install the [DWARF](goo.gle/wasm-debugging-extension) Chrome extension.
When testing [ManifoldCAD.org](https://manifoldcad.org/) (either locally or the
deployed version) note that it uses a service worker for faster loading. This
means you need to open the page twice to see updates (the first time loads the
old version and caches the new one, the second time loads the new version from
cache). To see changes on each reload, open Chrome dev tools, go to the
Application tab and check "update on reload".
### Note for firefox users
To use the manifoldCAD.org editor (`npm run dev`), you'll likely have to set
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
First, follow the directions in the root README to get your C++ build environment set up and working for WASM.
To develop the manifoldCAD.org editor as well as our other example pages, run
```
npm run dev
```
which will serve the pages, watch for changes, and automatically rebuild and refresh. This build step doesn't do TS type-checking, so to verify everything is correct (beyond VSCode's TS linting), run
```
npm run build
```
See `package.json` for other useful scripts.
## Bundle Analyzer
To generate a bundle analysis report, run:
On Windows PowerShell:
$env:ANALYZE=1; npm run build
On Linux/macOS:
ANALYZE=1 npm run build
This will include the analyzer in the build and output a report.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.