Compare commits

...
276 Commits
Author SHA1 Message Date
Emmett Lalish 5e7a576cea v2.5.1 (#847) 2024-06-14 13:48:05 -07:00
Emmett Lalish 80a5f2e0c5 fixed property smoothing (#845) 2024-06-14 11:41:26 -07:00
Emmett Lalish 67c4647464 Several smoothing fixes (#844)
* fixed several bugs

* fixed average rotation
2024-06-13 15:26:57 -07:00
Emmett Lalish edd149e753 fixed missing default params in JS bindings (#843) 2024-06-13 09:48:06 -07:00
Emmett Lalish b5c175162a fixed folded normals smoothing (#842) 2024-06-13 09:32:11 -07:00
Clifford Yapp 1df22bee48 Clear compiler warning from BSD. (#841)
* Clear compiler warning from BSD.

* Fix formatting per Github CI
2024-06-13 07:53:23 -07:00
Emmett Lalish d5c0418309 removed animation from roundedFrame (#839) 2024-06-12 14:28:35 -07:00
Emmett Lalish 7a5d5421d4 Fix PyPI again (#838)
* test all OSes

* try older windows

* updated release docs
2024-06-12 13:44:16 -07:00
Emmett Lalish 69437096d0 update cibuildwheels (#837) 2024-06-12 10:33:29 -07:00
Emmett Lalish 3931b3296d v2.5 (#836)
update to v2.5.0
2024-06-12 09:22:41 -07:00
Emmett Lalish 62d0f25ce0 Flair normals (#835)
* added comments

* flaired normals working

* updated tests
2024-06-11 11:38:49 -07:00
Emmett Lalish 1bd16f4c02 added convex polygon triangulation fast path (#829)
* added convex polygon triangulation fast path

* fixed transform_reduce

* fix sliver convexity test
2024-06-10 14:01:35 -07:00
Emmett Lalish 3afe846570 Fixed backward zebra polygon (#834)
fixed two more bugs
2024-06-08 17:31:33 -07:00
Emmett Lalish 12b9494fdd Fix the zebra polygon (#832)
* add Zebra polygon stress test

* fixed Zebra polygon - 4 bugs

* fix MSVC build

* try MSVC fix
2024-06-08 13:00:26 -07:00
Emmett Lalish 0577aabe7f Optimize triangulator with collider (#830)
* implemented - weird linker error

* fix linker

* fixed empty collider

* improved perf and cleanup

* add comments

* more docs
2024-06-05 10:28:59 -07:00
Emmett Lalish bdae9dd11a Improve smooth (#828)
* add max curvature tests

* back to radial basis functions

* fix longWay slerp

* cusps eliminated

* cleanup
2024-06-04 09:04:47 -07:00
Chris Mayo 5f4059ab1f Create a release file containing submodules (#825) 2024-05-31 13:30:51 -07:00
Emmett Lalish 9cb3969f70 Clean up test files (#826)
* match names to files

* GLB naming

* tighten matter tests

* partial revert

* stop skipping my job

* stop cancelling my jobs

* revert CI changes
2024-05-30 16:18:00 -07:00
Hiroshi OgawaandEmmett Lalish 4fb3520287 refactor(wasm/examples): support export models without web worker (#822)
* refactor(wasm/examples): support `evaluateCADToModel` without web worker

* test: copy test

* chore: update vitest

* chore: revert some

* chore: revert more

* refactor: simplify worker-wrapper.ts

* chore: revert format

* test: fix cleanup

* refactor: single module

* chore: format

* chore: revert .vscode/settings.json

* chore: format

* test: tweak

* chore: format

* chore: rename

* refactor: minor

* chore: remove wrong lock file

* added resetToCircularDefaults

* cleanup

* added headers

* removed vitest/web-worker

---------

Co-authored-by: Emmett Lalish <elalish@google.com>
2024-05-28 13:50:25 -07:00
Marius KintelandEmmett Lalish 376d317f14 Add polygon test showing bad tessellation result + fix (#824)
* Add polygon test showing bad tessellation result

* fixed ear cost

---------

Co-authored-by: Emmett Lalish <elalish@google.com>
2024-05-28 11:21:14 -07:00
Emmett Lalish bf18c9f759 Improve smoothing of flat faces (#821)
* Improve SmoothByNormals

* improving SmoothByNormals

* parallelize

* simplify SmoothByNormals

* added DistributeTangents

* fixed NaN bug

* use normals for sharp smoothing

* remove LinearizeTangents

* distrubute tangents for SmoothOut

* Revert "remove LinearizeTangents"

This reverts commit 7276609dc245c26b9a720b5bda1f018d64639954.

* fixed SmoothOut and added tests

* expanded test

* update wasm test
2024-05-28 11:06:27 -07:00
Emmett Lalish cdd1475fe1 added project test (#818)
* added failing test

* update test
2024-05-28 08:46:58 -07:00
Chris Mayo 55a0221f9b CI: use GCC 14 in Linux build (#816)
* 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)
2024-05-27 19:02:47 -07:00
pca006132 d78b1e2042 update clipper2 (#817)
* update clipper2

* test

* test2

* test3
2024-05-25 00:41:31 +08:00
Chris Mayo 562ff02232 Update glm to 1.0.1 (#819)
* 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."
2024-05-23 11:56:14 -07:00
Peter Harman 8e2b1dd485 Add manifold_meshgl_merge to c bindings (#820)
Add manifold_meshgl_merge to call MeshGL::Merge() on a duplicate mesh via the c bindings
2024-05-24 00:18:47 +08:00
Ian McInerney 5ef09daf45 Set import library for Clipper2 target when using system version (#815)
On Windows, the import library is also needed to link against the
Clipper2 DLL, and that must be specified using a different target
property.
2024-05-21 09:42:28 -07:00
Albert Siddhartha Slawinski 6d932a2c7f Fix Manifold.prototype.{split,splitByPlane,rotate} JavaScript bindings functions (#811)
* Fix the JS function to use the correct WASM function names

* Add a simple test

* Update tests and examples based on PR review
2024-05-15 10:14:43 -07:00
Daso Jung fd81cd6172 Split manifold tests (#800)
* Split Boolean test

* Fix name

* formatting

* move cubes test

* formatting

* format

* move tests

* update complex_boolean

* Split manifold test

* Update CMakeLists.txt

* fix typo

* move mingap to properties

* fix conflict

* Update CMakeLists.txt

* organize tests

* fix format

* Update properties.cpp
2024-05-13 09:37:50 -07:00
Emmett Lalish 6b4a4ed841 Update examples from JS -> TS (#807)
* put types back

* updated MV example to TS

* fixed TS errors

* added docs

* switch make-manifold to TS

* fix TS errors

* added docs

* update MV

* fix TS build

* forgot declaration
2024-05-10 16:43:57 -07:00
Emmett Lalish 887f631b8c Make runIndex easier to use (#806)
* handled unsorted groups

* forgot types
2024-05-10 12:52:19 -07:00
Emmett Lalish 443863d09e Runs example (#805)
* switched three example to TS

* use merge

* cleanup

* material groups

* added groups and ids

* working

* added docs
2024-05-09 22:46:16 -07:00
Peter Harman 5301542254 C bindings: pass context to set_properties and warp operation callbacks (#795)
* 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
2024-05-03 09:26:56 -07:00
Emmett Lalish a122bde778 Quaternion smoothing (#801)
* revert smoothness change

* updated to quaternions - not right yet

* fixed triangles, mostly

* fixed numerical stability problem

* fixed more nans

* fixed several bugs

* refactor bezier2D

* improved circularity

* added test

* updated tests

* fixed mirror test

* updated manual smooth model

* updated JS test

* cleanup
2024-04-30 21:41:32 -07:00
Peter Harman c9810fd517 Ensure properties maintained through Decompose operation (#794)
* 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
2024-04-26 08:09:02 -07:00
Emmett Lalish 4c57a56355 split subdivision out of smoothing (#797) 2024-04-25 22:25:08 -07:00
Emmett Lalish cdcbd329e6 Smoothing fixes (#796)
* fixed nan normals

* re-identify flat faces after refine

* fix color interpolation

* fix build
2024-04-25 16:35:35 -07:00
Emmett Lalish c738f1eea1 Smooth quads (#793)
* 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
2024-04-25 08:23:48 -07:00
Daso Jung a8b8bf71b1 Split boolean tests (#792)
* Split Boolean test

* Fix name

* formatting

* move cubes test

* formatting

* format

* move tests

* update complex_boolean
2024-04-25 08:10:12 -07:00
Emmett LalishandJohn Collins c60df1b704 Use forVert (#789)
* 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>
2024-04-16 15:23:29 -07:00
Emmett Lalish 8d67ba3a3a Revert "Use ForVert" (#788)
Revert "Use ForVert (#778)"

This reverts commit 32eefe9157.
2024-04-12 21:31:08 -07:00
Marcell Leleszi 3258152e2c Add MinGap function (#765)
* 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
2024-04-02 09:32:06 -07:00
pca006132 78729599a5 fix glm install (#782)
* fix glm install

* try again?

* proper C binding install

* fix glm install
2024-03-30 13:27:09 +08:00
Emmett Lalish 32eefe9157 Use ForVert (#778)
* updated a bunch of do loops to ForVert

* more cleanup
2024-03-26 13:15:26 -07:00
Emmett Lalish 3e05284250 Smooth By Normals (#776)
* SmoothByNormals compiles

* added ForVert

* tests pass

* added bindings

* stupid formatting...

* fix typo

* small fix

* aligned weights
2024-03-26 09:30:13 -07:00
Emmett Lalish c9ec16969d CalculateNormals and Smooth (#771)
* started implementation

* update to Vec

* refactor

* snap normals to faces

* added flat face sharpening

* added Smooth member

* Smooth working

* refactor SetNormals

* calculateNormals runs

* CalculateNormals works

* update test

* add bindings

* fix python bindings

* fix format

* Smooth -> SmoothOut

* a few fixes

* weird format bug

* addressing feedback

* update docs
2024-03-19 10:29:02 -07:00
John Collins 9b1d255931 Java bindings pointer in README (#769) 2024-03-11 12:48:19 -07:00
Emmett Lalish 22d94d5688 v2.4.5 (#762)
remove skip condition
2024-03-04 19:58:15 -08:00
Emmett Lalish e073141f14 v2.4.4 (#761) 2024-03-04 17:57:15 -08:00
Emmett Lalish f54e535c6a v2.4.3 (#760)
fix artifacts
2024-03-04 17:11:17 -08:00
Emmett Lalish c6801c82ea v2.4.2 (#759) 2024-03-04 15:04:57 -08:00
Emmett Lalish a67aa13133 v2.4.1 (#758)
update actions
2024-03-04 14:28:52 -08:00
Emmett Lalish d43709715d v2.4 (#757)
* v2.4.0

* fix cmake version
2024-03-04 13:24:41 -08:00
Emmett Lalish 2ddbd0a1a0 add docs about vertex properties, etc. (#755)
add docs
2024-03-02 17:32:07 -08:00
Emmett Lalish dc4afa950b Refine To Length (#741)
* sketched out implementation

* started PartitionQuad

* added edge directions

* compiles

* first test passing

* minor progress

* simplify

* refactor

* fixed interior verts

* getting manifold results

* improved triangulation

* finished algorithm

* fixed fans

* a few fixes

* fixed 3-2-2

* handle reversed triangles

* use Vec

* fixed another bug

* refine working

* replaced old subdivide

* fix allocation error

* fixed sphere

* fix cache

* fixed undefined behavior

* fix rounding error

* updated test

* cleanup

* add lock

* added properties support

* tightened test

* fixed edge colors

* fixed corners

* only duplicate props when necessary

* fix boolean props

* fixed manifoldness error

* fixed some narrow triangles

* fix compile

* added bindings

* fix formatting

* addressing feedback

* revert edge divisions change
2024-03-02 16:15:44 -08:00
pca006132 c03a5ac3ad prefer find_package for tbb (#747) 2024-02-19 13:03:37 +08:00
pca006132 cae5a01af1 README: change API documentation URL (#746) 2024-02-18 20:06:26 +08:00
pca006132 d59b9b93bd Fix sparseindices (#742)
* accept size_t for Vec

* Prepare a test case for "SparseIndices too large" core dump

* fix python docstring deps

* fix compilation errors

* limit sdf hashtable size

* another fix to hashtable

* log2(0) is undefined

* fix formatting

* Revert "fix compilation errors"

This reverts commit adfb86c18fb8a3f44f138f8b66d583b3c1c81193.

* fix size_t stuff

* remove processOverlap setting in test

* fix hashtable

* vertex number limit

* address comments
2024-02-16 08:45:43 -08:00
pca006132 f3c5ada9f0 test windows dll build (#743)
* 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
2024-02-15 15:28:41 -08:00
Kyle Finn 4395ab622c [python] add MeshGL::Merge() to python API (#739)
* [python] add MeshGL::Merge() to python API

* [python] mesh.merge in all_apis.py
2024-02-13 09:08:50 -08:00
Emmett Lalish 994956d7c3 switch share to hash (#736) 2024-02-08 17:19:47 -08:00
Emmett Lalish 79f735c59a update manifold, fix startup bug (#735) 2024-02-08 15:31:34 -08:00
Emmett Lalish dd67e992bd Editor buttons (#733)
* added undo, redo, format

* share button works

* cleanup

* reset scroll

* addressing feedback
2024-02-06 08:21:57 -08:00
briansturgill 5bdb8bc233 Added test for hull problem fixed in #730. (#732) 2024-02-05 10:47:22 -08:00
briansturgill c9ae400647 Fix to 2D HullImpl problem. (#730)
* Fixed 2D Hull problem

* Ran document formatting.

* Using public.h CCW, refactored as requested.

* Decided that precision value should 0.
2024-02-05 08:13:35 -08:00
Chris Mayo 9c1517e54e Install headers into manifold subdirectory (#729) 2024-02-05 08:12:57 -08:00
pca006132 3b8282e1d5 fix cube performance (#717)
* fix cube performance

* fix test
2024-01-29 13:46:55 +08:00
pca006132 1635864cbc optimize triangulator for msvc (#722)
optimize for msvc
2024-01-29 12:31:35 +08:00
Chris Mayo 65b2ef8198 Fix manifold3d module being built again in the install phase (#725)
With add_custom_target() "the target has no output file and is always
considered out of date".
2024-01-29 01:54:48 +08:00
Chris Mayo a9055a8abf python/gen_docs.py tidy-up (#724)
* python/gen_docs: Use raw strings in method_key()

Resolve Python 3.12 invalid escape sequence warnings.

* python/gen_docs: Remove unused f-string and md5 import
2024-01-29 01:53:38 +08:00
Chris Mayo 0b7ee87489 GLM 1.0 compatibility (#721)
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."
2024-01-28 08:04:12 +08:00
pca006132 9690305b8d optimize triangulator performance (#718) 2024-01-27 18:30:33 +08:00
pca006132 aa88959899 Update manifoldDeps.cmake (#716) 2024-01-23 15:29:51 +13:00
Emmett LalishandClifford Yapp dbb0ca76b4 Fixed float overflow (#714)
* 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>
2024-01-20 15:16:50 +13:00
Chris Mayo 9018b2ac45 Install Python module into site-packages (#704)
Python_SITEARCH is not writeable by build_nix (tbb) job.
2024-01-17 09:22:27 +13:00
briansturgill f407e43e5b Added BatchBoolean to Python API, both CS & M. (#707) 2024-01-16 15:13:33 +08:00
pca006132 d7cbcb8c2e fix sdist submodule (#709) 2024-01-16 15:13:12 +08:00
pca006132 e27c9c100f cmake updates (#696)
* cmake packaging

* compatibility

* update

* update

* tbb

* fix rpath stuff

* update tbb

* fix nix

* fix bugs

* fix emscripten

* PIC

* CI stuff

* only publish if on master

* assimp dependency for meshIO

* add cmake consumer test

* remove THRUST_HOST_SYSTEM flag

* fix CI permission issue

* cmake minimum required

* install without admin privilege on mac

* fix install directory

* update RELEASE_CHECKLIST.md
2024-01-14 23:31:28 +13:00
briansturgill 8ad5f8e427 Extrude and Resolve should be static on Manifold. (#692)
* 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.
2024-01-14 23:06:11 +13:00
briansturgill afc64f248a CrossSection::Compose was accidently left out of Python API. (#700) 2024-01-14 05:41:36 +08:00
pca006132 c0fb3766e4 update nanobind to v1.8.0 (#699) 2024-01-14 02:52:51 +08:00
Emmett Lalish 887a5021af bump package versions to 2.3.1 (#689)
bump version
2024-01-08 23:26:16 +13:00
Emmett Lalish e83b9bd158 v2.3.1 (#688)
* add missing pytest

* fix npm publish
2024-01-08 22:28:44 +13:00
Emmett Lalish 2d0c705839 v2.3 (#685)
bumped version
2024-01-08 21:06:58 +13:00
Emmett Lalish b36ec2ce0e fix model-viewer example (#686)
updated glTF extension name in GLBs
2024-01-07 22:59:19 +13:00
Emmett Lalish b8d143dd72 fix default partial revolve segments (#684)
* fixed revolve

* correct default segments
2024-01-07 21:27:02 +13:00
Emmett Lalish be022a58e4 fixed revolve (#682) 2024-01-06 23:30:03 +13:00
Emmett Lalish 30e0af942a remove simplify from warp (#677)
remove simplify
2023-12-29 10:24:07 +13:00
Kyle Finn 8c77a6aa05 [python] fix warp callback (#674)
* [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
2023-12-28 11:54:54 +13:00
Kyle Finn 54fa822408 Python auto doc (#665)
* python autogen docstrings

* use function signature hashes in docstring key

* remove sys.path debug code

* docstrings use param names

* autogen docstrings during cmake

* document gen_docs.py

* more readme, remap mesh_gl references

* readme about verify python docstrings, autogen depend on sources
2023-12-27 09:04:01 +13:00
Kyle Finn 7d564cfbc1 Python more numpy (#656)
* [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
2023-12-19 08:34:40 +08:00
Kyle Finn bc0bded294 add WarpBatch - array process warp with single callback (#651)
Add WarpBatch - array process warp with single callback
2023-12-17 22:15:28 +13:00
pca006132 d52decc4f1 example url (#643)
* example url

* update

* format

* address comments

* fix formatting
2023-12-16 18:25:45 +08:00
pca006132 8060376e12 add formatting script (#660)
* update formatting

* update formatter
2023-12-16 18:15:53 +08:00
Kyle Finn eba866d266 [python] manifold3d.triangulate and CrossSection.to_polygons_numpy (#650) 2023-12-14 21:51:48 +13:00
Emmett Lalish cc4966f062 added Colab Python example (#648)
added colab example
2023-12-14 07:33:50 +13:00
Emmett Lalish 3216a24be8 added Slice() (#644)
* added slice

* added test, addressed feedback

* added JS bindings

* remove code oddity

* added python bindings

* added C bindings
2023-12-13 13:48:50 +13:00
Kyle Finn 5e566c8c36 [python] fix JoinType.Round and simplify(epsilon) (#647) 2023-12-12 23:04:01 +13:00
Kyle Finn 80edfee978 [python] add revolveDegrees to revolve() API (#646) 2023-12-12 23:02:19 +13:00
pca006132 0e0d1c4f69 change extrude behavior (#642) 2023-12-12 13:17:07 +08:00
Emmett Lalish e15f4cf375 Added Project() (#638)
* added Project

* updated Clipper2 to 1.3.0

* fix simplify to remove more cruft

* update docs
2023-12-10 19:33:27 +08:00
pca006132 c9322ca63a Misc (#640)
* update tracy

* fix warnings

* remove dead code and warnings

* add comment
2023-12-09 15:40:34 +08:00
Emmett Lalish 45826d6c47 Added morph animation to ManifoldCAD.org (#637)
* added morph animation

* update test
2023-12-06 22:45:25 +13:00
Emmett Lalish dd781cdbdf Fix precision (#635)
* fixed empty collider

* disable test

* added svd

* use glm::mat3

* cleanup

* fix casing

* fixed typo
2023-12-06 10:28:08 +13:00
Emmett Lalish 638badaf9b added fuzzer cases (#633)
* added fuzzer cases

* not infinite looping anymore

* fixed loop check

* fixed bound calc
2023-12-04 15:14:33 +13:00
Emmett Lalish 0149e482bc EXT_manifold -> EXT_mesh_manifold (#630) 2023-11-27 16:18:05 -08:00
pca006132 f683e8c9c0 added triangulator fuzzer (#620)
* added triangulator fuzzer

* added easy to use minimizer script

* update readme

* added rounding
2023-11-25 21:51:07 +08:00
Chun Kit LAM 1f8e8037f1 real fix for 3MF export (#627)
* real fix

* formatting

* updated jscadui/3mf-export
2023-11-23 02:28:11 +08:00
Chun Kit LAM 10a15c1dd4 add thumbnail file to 3mf output (#626)
add _rel to 3mf output
2023-11-22 08:14:37 +08:00
Emmett Lalish 6e1629ddce updated tet puzzle example (#625)
* updated tet puzzle example

* updated description
2023-11-19 23:01:33 -08:00
Emmett Lalish e273f6b09d Add animation to manifoldCAD (#624)
* added global config

* translation animation works

* single times accessor

* animations working

* animation controls working

* add icons
2023-11-19 10:49:29 -08:00
Emmett Lalish 67c468211d maybe fix release actions (#623) 2023-11-18 21:50:02 -08:00
Emmett Lalish 01c38e42ca V2.2.2 (#622)
* bumped version

* added npm publishing automation
2023-11-17 10:20:16 -08:00
Chun Kit LAM 1991232eb7 fix performance regression for CreateProperties (#621)
* fix performance regression for CreateProperties

* added SparseIndices size check

* faster CreateProperties

* fix

* rollback and fix

* applied suggestion
2023-11-17 07:58:33 -08:00
Chun Kit LAM 4551288f95 Use black formatter for python format checking (#618)
* added black formatter to ci

* format gyroid_module.py
2023-11-16 20:44:07 +08:00
Emmett Lalishandpca006132 2e29784449 Update python bindings (#615)
* updated python bindings

* added scallop example

* fix python binding and example

* apply black formatting

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-11-15 22:51:51 -08:00
Johnathon Selstad 2a2cb6f88e [FEAT] Add LevelSet Function to Python Bindings (#616)
* Commit LevelSet Python Binding

* Address PR Comments

* Standardize Indentation
2023-11-15 22:24:45 -08:00
Rob McDonald 1a91d8db2a RFC to make SimplifyTopology() steps optional (#603)
Add option to skip cleaning up triangles in SimplifyTopology()
2023-11-14 15:23:51 -08:00
Emmett Lalish 82b98d9353 Improve gltf-io and manifold-gltf (#612)
* fixed build copy for npm

* update deps

* fixed glTF validator errors

* fixed bug and Vite

* clamped color

* converted manifold-gltf to TS

* converted gltf-io to TS

* fixed TS build

* avoid usage warnings

* update MV

* clean up types

* update gltf-io API

* fixed bugs

* fixed disposal

* fixed example

* a few fixes and cleanup
2023-11-14 15:23:25 -08:00
Emmett Lalish 7273c9bf0f Fix odd number halfedges (#610)
* fixed odd number of halfedges

* both tests fixed

* reenable tests

* wrong isfinite
2023-11-12 16:09:42 -08:00
4ff3f7cf68 Apparent infinite loop case for boolean operation (#608)
* 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>
2023-11-12 15:23:37 -08:00
Clifford Yappandpca006132 fafe4792da Prepare a test input with failing boolean (#609)
* 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>
2023-11-12 14:30:26 -08:00
Emmett Lalish b439603eb9 Fix glTF validator errors (#596)
* fixed build copy for npm

* update deps

* fixed glTF validator errors

* fixed bug and Vite
2023-11-07 16:10:52 -08:00
Chun Kit LAM b6eea01021 fix cmake build error with thrust installed (#595) 2023-11-07 22:49:57 +08:00
Johnathon Selstad b9f7228064 [FIX] Packaged Split() output to Tuple (#593)
* Fix the Split Function

* Fix Formatting

* Also update split_by_plane

* Add Cube Splitting Example
2023-11-07 12:18:27 +08:00
Chun Kit LAM 0ef5f70434 fix wasm cmake problems (#591)
* fix cmake problems

* update emsdk version
2023-11-07 09:38:06 +08:00
Chun Kit LAM bb6acf1f6e V2.2.1 (#589)
* 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.
2023-11-02 22:00:27 -07:00
Emmett Lalish 5930b5c09d Fix NaN halfedgeTangents (#588)
fixed CreateTangents
2023-11-02 12:35:44 -07:00
Emmett Lalish a842de97c6 Fix properties (#587)
* fix testing

* added test

* add comments

* tests pass

* cleanup
2023-11-02 12:07:58 -07:00
Chun Kit LAM 2942229b49 push to pypi (#583)
* push to pypi

* update action name
2023-10-31 11:13:02 +08:00
Chun Kit LAM 0856539bfc fix ubuntu build (#582) 2023-10-30 17:56:09 +08:00
Emmett Lalish 621835683f Earclip (#564)
* 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
2023-10-25 08:16:03 -07:00
Chun Kit LAM bec8164120 fix osx 10.13 error (#577)
* fix osx 10.13 error

* errors regarding std::optional

* format
2023-10-15 09:12:51 -07:00
Sebastien Loriot 062399c1c7 Fix perf test (#575)
do translate the sphere
2023-10-11 02:08:46 +08:00
Chun Kit LAM d51ee709c2 optimized connected component computation (#569)
* use union find for graph connected components

* update readme

* remove graphlite dep
2023-10-05 23:42:57 +08:00
Chun Kit LAM 206871ac11 dependency update (#559)
* dependency update

* revert BUILD_SHARED_LIBS changes

* test

* .

* don't try weird stuff with windows

* update

* update CI
2023-09-15 00:42:12 +08:00
Johnathon Selstad 2055b9c412 [FEAT] Add Status() to the Python Bindings (#558)
* 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
2023-09-12 08:24:30 -07:00
Chun Kit LAM 5e103931f1 added deterministic option (#550)
* added deterministic option

* fix formatting

* address some comments

* use kahan summation

* revert DoesOverlap changes

* remove old code

* formatting
2023-09-05 14:12:34 +08:00
Chun Kit LAM 77051ead35 misc (#548)
* use stl unique

* use stl sort
2023-09-02 15:03:22 +08:00
Eric Vin 83b78f8356 Python Bindings: Change tri_verts type from float to int (#546)
Change tri_verts type from float to int
2023-08-31 01:03:10 +08:00
Chun Kit LAM 6c6193c25b fix ndarray creation (#543) (#544) 2023-08-31 00:09:07 +08:00
Chun Kit LAM e79d343bec fix openscad build (#539)
* fix openscad build

* update clipper2

* conditional compile with pstl

* try NOMINMAX for MSVC

* detect win32

* use struct instead of lambda

* fix non-tbb build

* try to add mxe to CI

* disable pybind for mxe
2023-08-29 03:05:22 +08:00
Chun Kit LAM 1d6a89ac87 fix undefined behavior for SparseIndices (#540) 2023-08-28 13:47:21 +08:00
Chun Kit LAM 8651bf8bd5 nanobind (#537)
* 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.
2023-08-28 13:41:34 +08:00
Chun Kit LAM 1514c15184 fix decimator (#534) 2023-08-24 13:00:09 +08:00
Chun Kit LAM cc83e1b7aa misc (#533)
* use better policies

* removed redundant copy

* added condensed_matter test

* disable normal check for CondensedMatter16

* disable overlap checking as well
2023-08-24 02:09:51 +08:00
pca006132 111350f1ef fix memory leak for vec (#532)
* fix memory leak for vec

* removing reminiscence of CUDA

* formatting
2023-08-16 23:28:26 +08:00
pca006132 b4f583fcae refactor (#531)
* added bound check for vec_dh

* fix cmakefile

* refactored VecDH

* renamed VecDH into Vec

* better packaging

* formatting

* include <algorithm>

* fix windows

* documentation and copyright fixes

* use shorter names and implicit conversions

* formatting

* minimum circular segments = 3
2023-08-16 12:08:10 +08:00
Emmett Lalish 8011fb6072 Triangulator: shift on merge (#528)
* allow shift on merge

* fixed more tests with proper uncertainty

* fixed another test
2023-08-13 22:00:15 -07:00
pca006132 9290d420b0 performance tuning and disabling deprecated backends (#525)
* 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
2023-08-12 14:22:28 +08:00
Emmett Lalish fca66380f5 3mf node export (#517)
* added 3mf hierarchy support, not tested yet

* 3mf nodes work with updated exporter

* updated 3mfexport version
2023-08-07 22:35:50 -07:00
pca006132 8497e44915 parallel some more (#522)
* 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
2023-08-08 01:26:09 +08:00
pca006132 a7eac92087 third_party dependencies (#523)
* 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
2023-08-08 01:12:59 +08:00
pca006132 15399474fa more triangulator check (#516)
* add minimize_testcase helper

* added testcases from #502

* minor cleanup

* Update polygon.cpp

* Update face_op.cpp
2023-08-06 14:57:07 +08:00
pca006132 2d513446f7 fixed for_each_host and for_each_n_host (#519) 2023-08-04 16:50:38 +08:00
Emmett Lalish abff7e8ed3 triangulator fix (#509)
* added tests

* revert param changes

* fixed Precision3 and Hole

* revert test change
2023-07-30 23:42:30 -07:00
Emmett Lalish 7c454dc7a4 tighten CCW (#508) 2023-07-29 22:37:52 -07:00
Emmett Lalishandpca006132 ffdd97d4d3 adding python set_properties (#504)
* adding test of properties

* python set_properties

* fixed sponge example

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-07-29 22:10:28 -07:00
Emmett Lalishandpca006132 7facd0ecc0 Make save dropdown menu (#505)
* fix include

* made new dropdown

* mobile ui

* cleanup

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-07-28 12:23:57 -07:00
pca006132 59760b3369 added basic 3mf export (#500)
* added basic 3mf export

* updated worker test

* fixes

* formatting

* fix
2023-07-27 08:34:51 -07:00
Stéphane GINIER 5f9b484b6c Preventing crash with self intersection (#501)
* Prevent infinite recursion

* Add test for self intersection
2023-07-26 21:43:55 -07:00
pca006132 18edefc6c4 some more optimizations (#503)
* simple speedup from stl algorithms

* fix build

* std::list allocator optimization

* added documentation

* fix weird formatting issues

* try fixing MacOS build

it seems that Apple Clang 14 does not support pstl

* fix again

* fix macos build

* fix apple

* clean up using macro

* fix apple?

* small fix...

* fix apple (again...)
2023-07-25 02:33:15 +08:00
Emmett Lalishandpca006132 93e06a2de8 Update python mesh (#492)
* updated python mesh

* updated python meshgl

* clean up array related stuff

* fix hsplit

* fix according to comments

* make to_mesh accept MeshGL

* fixes

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-07-24 12:08:00 +08:00
Geoff deRosenroll 047159e2c4 Add hull bindings (c, wasm/js, and python) (#497)
* Add C bindings to hull methods

* Add hull python bindings

* WIP adding JS bindings to hull methods

* Add flexible static hull for manifold

* Add hull_points to manifold class

* Remove compose binding

* Cleanup translate/rotate signature overloads
2023-07-20 09:25:27 -07:00
pca006132 a8626f907c please gcc (#496)
* please gcc

* added documentation for mallocManaged
2023-07-19 10:58:28 +08:00
Geoff deRosenroll 2f22b92352 3d Convex Hull via Akuukka QuickHull (#489)
Add convex hull methods to Manifold using newly vendored https://github.com/akuukka/quickhull
2023-07-17 17:09:59 -07:00
Stéphane GINIER a3d7991563 Optimize sort_by_key (SortFaces/SortVerts) (#494)
* Optimize sort_by_key (SortFaces/SortVerts)

Remap boxes separately

* Fix build validation
2023-07-17 10:36:26 +08:00
Stéphane GINIER 2aef6e4728 Optimise SortEntry memory footprint (#493) 2023-07-15 22:21:26 +08:00
pca006132 c167a48d36 optimization in #483 (#491)
* remove extra prefetch

this is in the hot path and causes performance issues

* implemented optimization proposed by @stephomi in #483

* formatting

* fix cuda build

* combine the loop
2023-07-15 19:01:32 +08:00
Peter Harman da3f323113 (C bindings) Pass additional data to level set SDF function (#427)
Allows a developer to pass some context to the level set function in the C FFI bindings, such as a pointer to an object in the host language, so that parallel calls can be threadsafe.
2023-07-13 12:50:40 -07:00
Emmett Lalish 8b7bf4231b updated python doc strings (#488)
* updated doc strings

* pymanifold -> manifold3d
2023-07-13 11:34:31 -07:00
Stéphane GINIER ac2cfd857f Replace map with unordered_map (#484)
Replace map usage with vector
2023-07-13 07:15:08 -07:00
pca006132 285bb0754b fix nixos js build (#473)
* fix nix js build

* fix js CrossSection::rotate binding

* add wasm hull operation
2023-07-12 12:03:19 +08:00
Geoff deRosenroll ba5fead6d5 Add 2d hull operation (#486)
This adds a 2d convex hull operation to CrossSection (ported from OCADml, which I ported based on BOSL2).
2023-07-10 23:43:47 -07:00
Emmett Lalish 96c57c0591 Fix Merge manifoldness issues (#481)
* test passes

* fixed merge properly

* fixed relation

* fixed pinched verts

* added WASM debug instructions
2023-07-07 09:40:10 -07:00
Emmett Lalish 4b65ff3afe Fix operations with different numbers of properties (#480)
* added tests and fixed a bug

* more testing, fixed compose
2023-07-06 13:12:33 -07:00
Geoff deRosenroll 709021d9d2 Remove RectClip API + Rect::AsCrossSection fix (#478)
Following discussion on #455, this removes RectClip from our API, as it does not provide the same guarantees that Clipper2's regular boolean clipping operations give us (that make sure that CrossSections can be extruded successfully).

Also, this fixes AsCrossSection which was returning empty CrossSections due to incorrect winding followed by a Positive fill rule union operation. Now it is working as advertised, while skipping over the needless clipping by implementing it as a CrossSection constructor (building a PathsD directly).
2023-07-05 10:41:03 -07:00
John Collins 6cec951aed Partial Revolve (#455)
* handle verts on revolve axis

* Tests working

* Const declares, test cycle start vert

* clang-format

* clang-format test code

* fix header declaration

* WASM bindings

* format bindings.js

* clang format

* Address comments
2023-07-04 15:52:48 -07:00
Emmett Lalish dd3445ddb1 fixed WASM segfault (#474) 2023-06-30 08:55:02 -07:00
Axel Angel 37db0d0182 python bindings: mirror and Mesh with 2 args (#459)
* python bindings: Add mirror

* python bindings: fix Mesh constructor optional arguments
2023-06-27 07:32:44 -07:00
Emmett Lalish 1b9243df2a Update HTML examples for WASM API changes (#470)
updated for WASM API changes
2023-06-25 20:40:16 -07:00
Emmett Lalish 8b4c79c485 added Fill Rule test (#464) 2023-06-22 16:04:19 -07:00
Geoff deRosenroll 7a197af83d Update Clipper2 to latest commit (#463)
* Update to latest clipper2 commit

* Update RectClip binding - rename and dropped param
2023-06-22 15:02:05 -07:00
Geoff deRosenroll a65fedb2f9 Clean up CrossSection constructor in pymanifold (#461)
Remove vertices intermediary and {vertices} assignment
2023-06-21 14:33:13 -07:00
Geoff deRosenroll 6ae9dee640 Workaround assimp exporting empty 3mfs (#454)
Workaround assimp producing meshless 3mf files by nesting the node holding the mesh into a child of the root node if the extension is .3mf
2023-06-15 10:45:01 -07:00
Geoff deRosenroll 79daba1200 Fix oversized malloc error (gcc inlining related) (#453)
Fixes #450

This breaks the allocation size calculation in ManagedVec::shrink_to_fit onto a separate line in order to work around spooky inlining (I think) by GCC that trips up an excessive allocation compilation error in malloc.
2023-06-15 10:42:49 -07:00
Geoff deRosenroll 2f8e3174f7 Update C bindings to curvature and set properites additions (#451)
Add C bindings to `Manifold::ReserveIDs`, `Manifold::SetProperties`, and `Manifold::CalculateCurvature`.
2023-06-14 23:08:53 -07:00
Emmett Lalish cce35aa5a0 Fix RemoveIfFolded() in the decimator (#447)
* enabled test

* remove test

* test infinite loops

* test infinite loops

* reduced test

* update volume

* fixed RemoveIfFolded

* rename test

* allow degenerates
2023-06-10 16:44:25 -07:00
Emmett Lalish e727b115c0 Change curvature API (#448)
* new curvature API compiles

* tests pass

* update JS example and fix bindings
2023-06-09 22:36:15 -07:00
Johnny Bigert f6431112a8 Update Python bindings (#445)
* Added more Python bindings (CrossSection) and some clarifying Python examples on how to create polygons etc
* Returned extrude from Python example and renamed accordingly.
* Removed PolygonWrapper class from Python bindings
2023-06-08 10:52:10 -07:00
Geoff deRosenroll 2643c291ed Use quality globals instead of arc tolerance for offset (#444) 2023-06-01 11:52:55 -07:00
Emmett Lalish e024edf103 ManifoldCAD docs update (#446)
* fix back compat

* updated docs

* Revert "fix back compat"

This reverts commit 5f102e567577ab8fcd7f2215801f5dcea9d13e8e.
2023-06-01 10:31:05 -07:00
Geoff deRosenroll cbfeed1672 Update JS bindings with CrossSection + reorganize (#440)
- add CrossSection class
- move static method (constructors) under their respective classes
-add Manifold.{split, splitByPlane}
- enable usage of instanceof on Manifold (and now CrossSection) objects simplify registry indirection in worker.ts, enabling method de-structuring (opening static function names out of class module name-spaces)

Build changes:
- break C++ helpers out from bindings.cpp to helpers.cpp
- ensure that ts files are copied during builds even when nothing is recompiled

Note that in addition to the new bindings, the most of the existing toplevel functions have been moved under their respective classes as static methods (breaking change!). I haven't added any new examples yet, but the existing ones are passing and do make use of CrossSection now (some transparently, and some directly), but I think enough of this is together that review is worthwhile.
2023-06-01 00:31:57 -07:00
Emmett Lalish 9dac47a76a Fix editor warp crash (#442)
* reset errored worker

* catch non-finite warp errors
2023-05-26 22:37:10 -07:00
Emmett Lalish eb41b9d36a Add CI check for formatting more languages (#441)
* add format files

* some fixes
2023-05-26 08:19:02 -07:00
Emmett Lalish 72935213e8 Editor scene (#438)
* SetProperties working

* cleanup

* added Node

* switched to Vite

* scene export working

* vertex color output works

* added unlit

* fixed rename bug

* switch worker to TS

* don't fail on empty manifolds

* reusing meshes

* cleanup, updated gyroid example

* change signature of setPropFunc

* updated examples

* add dist for githack testing

* Revert "add dist for githack testing"

This reverts commit 8769d7ab08dfaab9fe7824726f7a7229bf553bb3.

* remove duplicate files

* updated docs

* fixed some bugs

* multi-material working

* switched to vitest

* tests pass

* fixed examples

* fix CI

* update deployment

* tweak logging

* cleanup

* fix copy

* updated READMEs

* extend timeout
2023-05-25 09:40:02 -07:00
Geoff deRosenroll e1ebc66425 (C) Only link MeshIO when MANIFOLD_EXPORT is on (#436)
Only link MeshIO in C bindings when `MANIFOLD_EXPORT` is defined. MeshIO related code has been better consolidated to limit the number of `#ifdef` blocks required.
2023-05-21 00:14:36 -07:00
Emmett Lalish ef52b0cce2 Add SetProperties() (#433)
* SetProperties working

* cleanup
2023-05-16 21:22:49 -07:00
Geoff deRosenroll 812ca5c30f Add manifold_import_meshgl (#434)
Adds missing C binding to MeshIO::ImportMesh
2023-05-16 16:48:40 -07:00
Emmett Lalish f064b2a5fd make-manifold (#428)
* make-manifold working

* prettier

* fixed stack exceeded

* download only manifolds

* keep any meshes that are manifold

* fixed extension accessor writer

* fix read-write roundtrip
2023-05-15 07:50:28 -07:00
pca006132 18abd3ffcf fix compose index error (#430)
* fix compose index error

https://github.com/elalish/manifold/issues/425#issuecomment-1540356917

* simplify test case
2023-05-13 00:36:07 +08:00
Loosetooth 1615b72e83 add unionError test case (#429) 2023-05-11 08:06:09 -07:00
Emmett Lalish 58fb7e3300 EXT_manifold for glTF (#420)
* extension export works

* reading GLBs

* trying to merge documents

* add await

* removed extra materials

* added builds

* several fixes

* fix versions

* move to built

* fixed test.js

* updated README

* disable nix JS test for now

* fix codecov timeout

* fix test
2023-05-04 20:49:39 -07:00
David Braun 1959a30560 Fix bug in three.html example. (#419)
Fix bug in three.html.

The bug didn't change the output but made it harder for newcomers to learn the JavaScript API.
2023-04-21 15:24:11 -07:00
Emmett Lalish 4bdb761d5e fixed warp (#418)
* fixed warp

* fix pi

* ease up test tolerance

* improve cbind test

* fix build

* better bind testing
2023-04-21 17:02:16 +08:00
Emmett Lalish ba042ee8db fix warp simplification (#416)
* fixed warp simplification

* fix test
2023-04-20 11:52:47 -07:00
Emmett Lalish 97317888bc added global coplanarity check (#408)
* added global coplanarity check

* fix early-out

* added test, fixed precision

* one more precision
2023-04-12 08:03:47 -07:00
Emmett Lalish a9bf836574 Added file conversion and debug utility (#407)
* added file conversion utility

* added verbose output

* fix build
2023-04-11 11:38:35 -07:00
Emmett Lalish 29dc753513 Fix triangulator assertion (#406)
fixed debug check
2023-04-11 10:10:17 -07:00
Emmett Lalish 3a517a490e Update polygon tests (#405)
* simplify polygon tests

* fix polygon Dump output

* updated docs

* removed IsManifold
2023-04-11 09:10:34 -07:00
Emmett Lalish aeba26760b Add Merge function (#394)
* start Merge

* started Merge function

* added collider

* refactor now builds

* connect vectors

* finished Merge, updated Collider

* added tests

* added sorting, updated graph_lite

* revert submodule change

* fixed indexing

* fixed prop dedupe bug

* fixed test

* update meshIO linking

* fix MSVC

* use assimp on Mac

* add precision handling

* improved tests

* revert test tweak

* update collider

* clean up multiset

* updated docs
2023-04-09 17:16:40 -07:00
pca006132 85d74b4ccf Flip triangles in csg_tree (#404)
* flip triangles in csg_tree

* added test
2023-04-08 01:11:56 +08:00
Jiří Hon a5f92e2522 fix typo in typescript types (#401) 2023-04-04 13:28:43 -07:00
Jiří Hon fbe4cc6a86 Add JS bindings for triangulation (#393)
* js binding for triangulation

* don't use CrossSection class as it alters the order of the vertices

* remove unused code,
rename original Triangulate to TriangulateIdx,
move TriangulateJS from bindings.cpp to polygon.cpp,
rename TrinagulateJS to Triangulate for consistent C++ and JS API,
add optional precision parameter
2023-04-04 10:19:35 -07:00
Emmett Lalishandpca006132 f25d9601cb update thrust to 2.1 (#389)
* update thrust to 2.1

* fix typo

* does it work?

* fix shell script

* use checkout recursive option

* enable emscripten pthread support

* use GLM CMake

* remove -pthread

* fix emscripten build

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-03-30 20:44:50 -07:00
Geoff deRosenroll 07513bd951 Update Clipper2 commit + avoid building Clipper2Z (#392)
- Clipper2 submodule commit updated
- skip building Clipper2Z with new build option
- fix breaking API change in wrapper
2023-03-25 23:05:18 -07:00
pca006132 9a795162f4 [WIP] Python update (#378)
* nicer docstring

* some missing APIs

* split methods

* cross section python API
2023-03-23 12:54:05 +08:00
Geoff deRosenroll cdce9e7618 Improve handling of invalid shape construction. (#385)
Add `InvalidConstruction` to `Manifold::Error` and return empty manifolds with it set when manifold construction is attempted with invalid parameters

- zero / negative dimensions for Cube
- zero / negative radius for Sphere
- zero / negative height for Extrude
- empty CrossSection for Extrude/Revolve

Also, Circle and Square have been updated to return empty CrossSections for similar arguments.
2023-03-22 21:39:26 -07:00
Emmett Lalish 628edc8e26 fix URL (#386) 2023-03-22 08:04:02 -07:00
Emmett Lalish d209ead1c2 update readme (#384)
* updated readme

* forgot to save

* added performance plot
2023-03-21 22:31:40 -07:00
pca006132 112f9ba501 parallelize some csg operations (#381)
* parallelize some csg operations

* use lower_bound on sorted vector
2023-03-20 08:31:11 -07:00
Geoff deRosenroll 2a63971983 Add missing C bindings + revamp composition with opaque vectors (#382)
- Add bindings to `CrossSection::{Compose,Decompose,NumVert,NumContour,Warp}` 
- Add bindings to `Boolean` and `BatchBoolean` for both `Manifold` and `CrossSection`
- Add `ManifoldManifoldVec` and `ManifoldCrossSectionVec` opaque types for `std::vector` pointers to help clean up bindings involving vectors (particularly when they are returned with dynamic size, *e.g.* `manifold_decompose`
- Remove two-step Components workaround previously introduced to allow return of C array from `manifold_decompose`
2023-03-19 22:19:19 -07:00
Geoff deRosenroll 4962ebf879 Add CrossSection decomposition (#376)
Decompose breaks a CrossSection into independent complex polygons. 

Compose implemented as a batch union (preserving the CrossSection guaranteed lack of intersections).
2023-03-16 22:23:42 -07:00
Emmett Lalish d713986f62 Add missing JS bindings (#375)
* readd transform

* added mirror to wasm bindings

* added trimByPlane wasm bindings

* fixed bugs

* update test

* trying to fix submodule
2023-03-16 13:00:28 -07:00
Geoff deRosenroll 3b72852b0f Add documentation comments to CrossSection and Rect (#374) 2023-03-16 11:45:58 -07:00
Olivier Chafik e17eb038bf Simplify the CsgOpNodes as we build them, rather than in GetChildren (#368)
* Flatten the CsgOpNodes as we build them, rather than in GetChildren

* Don't inline reused nodes in CsgNode::Boolean

* Reinstate difference -> union rewrite

* No need for opportunistic flattening anymore

* Fix typo

* Remove CsgOpNode::Impl::simplified_

* Don't overskip referenced nodes in simplification

* Avoid needless copy of shared pointers

* Disambiguate CsgOpNode::Impl::flatten_ -> forcedToLeafNodes_ and align GetChildren arg naming

* Simplified CsgOpNode::GetChildren

* Simpler (a-(b+c)) -> (a-b-c) transform in CsgOpNode::Boolean to work better w/ CsgOpNode::ToLeafNode

* Simpler build hints for large_scene_test.cpp

* [flatten] Refine rules about tree flattening + added test case

* [manifold] Propagate transforms when flattening op trees

* [flatten] move tests from manifold_test to boolean_test
2023-03-16 10:09:12 -07:00
Jiří Hon 7b0758eddc locateFile property in Module function (#371)
Allows to provide custom url of the manifold.wasm file. This is needed for Vite. Partial fix for #365
2023-03-15 07:56:53 -07:00
Geoff deRosenroll 20178cb209 Add NumVert, NumContour, and Warp to CrossSection (#370) 2023-03-14 23:06:07 -07:00
Geoff deRosenroll 61d87c503c SimplePolygon/Polygons accessors for C bindings (#369) 2023-03-14 22:11:48 -07:00
Geoff deRosenroll 7c2007771e Make tests optional + fix option declarations (#366)
Break out the MeshIO library from the tests subdirectory, and make building the tests optional. In cases where building/re-building the tests is not wanted/needed, this can shorten build times by over 25% on my machine. A major motivator for this is building 3rd party bindings.

Also included here are descriptors for the option declarations, which I
noticed do not actually set the default value as expected if the
description argument is omitted (defaults to OFF, rather than ON for
`_TEST` and `_PYBIND` for example).
2023-03-14 10:47:08 -07:00
Geoff deRosenroll d82cf061f8 Update pybind11 to latest stable commit (#367)
* Update pybind11 to latest stable commit

* Explicitly install python@3.11 in mac CI

* Turn findpython on
2023-03-14 11:11:58 +08:00
Geoff deRosenroll dca9cce529 Lazy transformations for CrossSection (#355)
Add transformation composition with lazy application when needed, similar to the arrangement used in the CSG tree backing Manifold.
2023-03-10 11:25:47 -08:00
pca006132 754a4811a5 Fix cache concurrency issue (#356) (#357)
* added ConcurrentSharedPtr

* fixed csg tree cache concurrency bug (#356)
2023-03-10 08:33:10 +08:00
Geoff deRosenroll 83131c672e Add Mirror transformation method to Manifold (#354)
Add Mirror transformation (over plane through origin described by given normal) method to Manifold and corresponding C binding.
2023-03-07 23:54:27 -08:00
Geoff deRosenroll bd8da6ce1e Add CrossSection and Rect to C bindings (#353)
* + CrossSection and Rect type interfaces and method functions

* `manifold_extrude` and `manifold_revolve` take `ManifoldCrossSection*`

* fix manifoldc extrude test (`ManifoldCrossSection*` argument)
2023-03-07 12:30:28 -08:00
Emmett Lalish 3c736f9030 Added docs, refactored for single OpType (#352)
refactored for single OpType
2023-03-07 09:31:30 -08:00
Geoff deRosenroll bb24367220 Add Geoff deRosenroll to author list (#351) 2023-03-07 08:02:48 -08:00
Geoff deRosenroll a97c666358 2d-operations via Clipper2 (#341)
* WIP Clipper2 wrapper for 2d subsystem

* Working example in clippoly_test

* Add clean_ property to clippoly

* Square center=false by default

* Square defaults to center=false

* Shorten union example

* + simplification and minkowski functions

* + Circle and note on Minkowski

* + Offset (InflatePaths)

* Clippoly -> CrossSection renaming

* Switch from NonZero to Positive fill rule

* -(minkowski, simplifs, clean_) + pre-alloc vecs

Remove minkowski, simplification algorithms other than simplify,
clean (union) arbitrary contours on construction, and pre-allocate
vectors when length is known.

* - index from polygon conversion + missed vec prealloc

* Use Polygon shorthand over std::vector

* Update copyright date

* Add Rotate

* Update copyrights to 2023

* Extrude and Revolve accept CrossSection only

* Fix incorrect use of vector constructor

* Fix remaining incorrect vector construction

* Revert "Update copyrights to 2023"

This reverts commit af8f6c8c95135eb3376e1f150fe720fddea16f33.

* Re-update copyright notice in cross_section files

* Add missing precision_ argument to cleaning Unions

* Lift circular resolution globals into Quality

* Replace uses of Manifold globals with Quality

* + Ellipse, Mirror, Area

* Include mirror in Union example and test area

* + Rewind (reverse winding of all contours)

* + fillrule/IsEmpty - Ellipse/Rewind

* Move Quality into public.h

* Remove bad definition

* Replace manual circles with CrossSection::Circle

* Lists of empty contours are empty

* + Rect class, RectClip, + const params/methods

* Add Transform to CrossSection and Rect
2023-03-03 17:11:15 -08:00
Emmett Lalish 7c8ce3c935 v2.1 (#350)
new npm release
2023-03-02 10:41:19 -08:00
Emmett Lalish 4d434e4f8c deploy extra types files (#349) 2023-03-02 00:04:16 -08:00
Emmett Lalish 9e9a22d66b clean up editor.d.ts (#348) 2023-03-01 23:33:31 -08:00
Rafael 893a4f8ac3 Properly encapsulate ES6 type defs. in module (#346)
* Properly encapsulate ES6 type defs. in module

* Fixed import file name typo

* Copy type definitions to editor folder (not used for now)

* Dummy commit (trigger CI)
2023-03-01 09:47:45 -08:00
Emmett Lalish 78c03f2611 prod deployment again (#347)
minor cleanup
2023-02-28 22:09:28 -08:00
Emmett Lalish 2915f7aa90 prod github pages to deploy (#345) 2023-02-28 12:48:19 -08:00
Emmett Lalish 95daa54591 ugly fix for editor TS (#344) 2023-02-28 12:16:48 -08:00
Emmett Lalish 2c597d1eef make PolyVert private (#343)
* make PolyVert private

* fix python
2023-02-28 10:03:34 -08:00
Rafael e2a8e94906 Fix ES6 .d.ts exports (#342) 2023-02-28 09:16:06 -08:00
Emmett Lalish eae9653745 added docs, tweaked manifest (#339) 2023-02-25 11:00:17 -08:00
Emmett Lalish feb7fb6edd Convert manifoldCAD.org to a PWA (#336)
* added PWA manifest

* added service worker

* check for service worker
2023-02-25 09:49:19 -08:00
Emmett Lalish 32f887d321 SEO for manifoldCAD.org (#335)
* added meta tags

* added og:image
2023-02-23 10:28:28 -08:00
Emmett Lalish 3a962cfa53 Make robust to overlaps by default (#299)
* added polygon tests that only fail on some compilers

* revert accidental narrowing of polygon tests

* default to allowing overlaps

* CI experiments
2023-02-23 10:05:50 -08:00
Emmett Lalish 36ca7acf6c add only() to editor (#334)
* added only() to editor

* added docs
2023-02-23 09:32:12 -08:00
pca006132 0704f62eaa added maze benchmark (#333) 2023-02-24 01:00:13 +08:00
Emmett Lalish 5041c38424 Add gl-matrix to editor (#332)
* switch JS to Mat4

* added glMatrix to editor

* added glMatrix types

* type cleanup

* fixed type names

* updated examples

* fixed types and examples
2023-02-23 08:00:52 -08:00
pca006132 c4bf392f66 hashtable atomic load store fix (#331)
fix hashmap race condition
2023-02-21 22:01:40 -08:00
Emmett Lalish 3f42e5e6d7 Adding show() to editor (#330)
* include all transforms

* added show method for editor debug

* await before cleanup

* tighten JS tests

* move out common test functions

* split manifold and boolean tests

* cleanup

* tighten samples tests

* fixed GetMeshGL

* fixed original IDs for empty runs

* link SDF

* update CUDA files

* revert accidentally-removed test function

* added types and docs
2023-02-20 22:00:04 -08:00
pca006132 376a7c676f wasm: transform accepts mat4 (#329) 2023-02-19 18:21:16 -08:00
Emmett Lalish e6bb87b2bb change enum case (#325)
* change enum case

* fix C enums

* more fixes

* another fix
2023-02-16 13:32:28 -08:00
Emmett Lalish 9477deaa9c Fix deploy (#323)
* use pinned gltf-transform

* fix artifact download
2023-02-16 09:24:18 -08:00
Emmett Lalish 02affd5247 convert to ES6 module (#322)
* es6 tests work

* fixed model-viewer example

* fixed three example

* worker not quite working

* removed three.js dependency

* switched to top-level await

* added normalIdx binding

* fix binding
2023-02-16 08:09:31 -08:00
Emmett Lalish 171fe5466e More relations (#321)
transformed is not original
2023-02-15 08:04:42 -08:00
Rafael 5331b68485 Added missing JS bindings (from discussion in #317) (#320)
* Added missing JS bindings (from discussion in #317)

* Fix formatting

* Add reserveIDs JS bindings

* Use numRun instead of numMatrix

* Make numProp, triVerts and vertProperties required options in Mesh ctor.

* Rename MeshJS originalID to runOriginalID, and transform to runTransform

* Fix formatting with clang-format

* Trigger CI

* Added missing C bindings

* Rename matrix to transform in JS type definitions

* Rename numMatrix to numRun in JS type defs
almost forgot this one again

* JS bindings, transform index -> run
2023-02-14 20:52:23 -08:00
Emmett Lalish 20d7ef96fb update CI to cuda 12 (#319) 2023-02-10 23:22:18 -08:00
Emmett Lalish 3560bbf679 added normals update (#317)
* added normals update

* improved test model

* reverted RelatedGL change

* renormalize
2023-02-10 22:22:25 -08:00
Emmett Lalish 4415cfce46 Transform relations (#315)
* added transforms output

* removed meshids

* read MeshGL runs, IDs, and transforms

* cleanup

* replaced lambdas

* refactor reserveIDs

* refactored reference initialization

* added round-trip test

* added multi-material round trip test

* added faceID round trip test

* back to const inputs

* added missing error types

* shorten CI timeouts

* update docs
2023-02-07 09:50:43 -08:00
Geoff deRosenroll bbb1fabcac Remove Mesh from interface + memcpy depuplication (#313) 2023-01-29 08:34:56 -08:00
Emmett LalishandGeoff deRosenroll f43dded234 Multi-material example (#309)
* removed BufferGeometryUtils

* replaced three.js with gltf-transform

* textured sphere working

* added cubeUV

* added textures

* added relation and sorting to MeshGL

* working textures example!

* added faceID

* removed MeshRelation

* some more removal

* fix output for no properties

* test cleanup

* Add new MeshGL accessors (replacing MeshRelation) (#311)

+ new meshgl accessors + remove mesh_relation_size

* removed meshID from public API

* added input checking for MeshGL

* remove C api

---------

Co-authored-by: Geoff deRosenroll <geoffderosenroll@gmail.com>
2023-01-28 13:02:37 -08:00
Geoff deRosenroll 05f9ceb999 Remove manifold_meshgl_vert_normal from header (impl gone) (#310)
Remove meshgl_vert_normal from header (impl gone)
2023-01-26 08:03:29 -08:00
Emmett LalishandGeoff deRosenroll 422c7f26d9 Property output (#290)
* first pass, boolean math needs refactor to work

* refactored property interpolation

* finished edge_op update

* updated sort

* updated MeshGL

* meshGL -> manifold

* existing tests pass

* prod CI

* fixed floating point error

* construct MeshGL from Mesh

* size fix

* added basic testing

* added property refinement test

* added property boolean test

* bug fix?

* fixed tolerance chacking

* fixed segfault

* fixed more bugs

* fixed mod bug

* basic properties tests passing

* added test

* fixed shared properties

* fixed duplicate merge entries

* compacting property verts

* tightened tests, fixed bug

* added MeshGL export

* fixed more bugs

* fixed coplanar tests

* removed old mesh relation from tests

* fixed property output, but still have extra propVerts

* removed barycentric from MeshRelation

* rename BaryRef to TriRef

* refactored boolean property creation

* moved GetBarycentric back out of the public API

* CreateProperties handles continuous and discontinuous properties

* avoid nan

* widened testing precision

* Update manifoldc to MeshGL breaking changes (#306)

* Update manifoldc to MeshGL breaking changes

* Update to latest MeshRelation/TriRef changes

* updated WASM bindings

* attempt at updating three.js example

* updated JS

* fixed examples

Co-authored-by: Geoff deRosenroll <geoffderosenroll@gmail.com>
2023-01-21 21:41:33 -08:00
pca006132 a701d2be8e weird bug for python binding (#307)
* weird bug for python binding

* formatting
2023-01-06 08:06:44 -08:00
Geoff deRosenroll 5b5f56674a Use sqrtf to return float rather than double in level_set test. (#303)
sqrtf to return float rather than double

This hasn't been a problem on my machine, or in the Manifold CI, but
the double <> float mismatch gave a type error in an ubuntu 22.04
workflow on my omanifold project. Not totally sure why as of yet, but
this gets rid of the implicit conversion at the root of it.
2022-12-29 18:51:53 +08:00
Emmett Lalish 1b7b365c3a Fix mirroring (#298)
* supports scale inversion

* update tangents when scaling

* fixed tests
2022-12-27 11:41:52 -08:00
pca006132 812b056179 fix css issue (#296) 2022-12-22 14:30:47 +08:00
Geoff deRosenroll bd46bfd1cf Fix incorrect value for components length + test (#295) 2022-12-22 13:24:09 +08:00
Geoff deRosenroll a7b457db74 C bindings to facilitate static FFI bindings (#292)
* + c bindings to facilitate static FFI bindings

Squashed commit of the following:

commit a05f9b0e0ab079b95c29b4641ae928f02be219f4
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Dec 10 16:21:47 2022 -0800

    Add flag (default on) for python bindings

    Signed-off-by: Geoff deRosenroll <geoffderosenroll@gmail.com>

commit ce5b601fc7578fa43edbf7363a7b625d1d27a1de
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Dec 9 23:45:29 2022 -0800

    conv function naming consistency

commit 1b50cf077dada8c3bc13d77bfa1c8f0a406c047d
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Tue Dec 6 17:42:18 2022 -0800

    manifold_level_set_seq to C API (force sequential)

commit 77e06daf92e7014ce153a336ed685b9370e965c0
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Tue Dec 6 17:24:10 2022 -0800

    Use an optional executionpolicy param in LevelSet

    This allows bindings to avoid breaking their runtime locks while passing
    in function pointers without introducing another compile flag.

commit 12c62b1e33cdacd07f056859fa2038dfd1f62170
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Dec 5 15:09:41 2022 -0800

    Better section comments

commit 24f1d5107e24f41d3860fe6dea4832a523989eb9
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Dec 2 12:01:50 2022 -0800

    Add missing management functions for Components

commit a7d5aae8550c4ace458e78314eab04a3cf5f2053
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Thu Dec 1 23:45:45 2022 -0800

    Break Decompose up to avoid repetition in C binds

commit a650a1531dc52610db8e6009e82e247d7f256d3e
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sun Nov 27 22:41:38 2022 -0800

    Add manifold_get_properties

commit 97a58396cd8e6c5fad698f1280d02d78e0b51ded
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 26 22:53:42 2022 -0800

    circular segment functions corrected to static

commit c9c1ca7ec599b5f14401c85efde19481d86226f8
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Thu Nov 24 21:19:11 2022 -0800

    polygons from dbl ptr of SimplePolygon

commit 8e5390aa729817d0cc652f92a64e6850e4e16e9b
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Wed Nov 23 12:16:44 2022 -0800

    compose and decompose switched to double ptrs

commit 45c78c3e1aa0eaab8622c77b69221226af06861c
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Tue Nov 22 11:40:32 2022 -0800

    Add manifold copy and empty constructors

commit cfc06fe55cd4a508005385bcdb4037ed89695f80
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 21 15:03:00 2022 -0800

    Add MANIFOLD_SEQ_SDF to avoid runtime lock

commit e0f9d09e5ced6fcb2b83722542e99b20d0799e25
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sun Nov 20 16:56:50 2022 -0800

    Add missing export related contructors.

commit 0cbaa4a6090c402f2a2483c35f5b3b7e82d8cf52
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sun Nov 20 16:03:43 2022 -0800

    Add separate destruct functions

commit fd6c1c250e6b91114b0203b40a941fa73e3473ec
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 17:29:12 2022 -0800

    More consistent array handling

commit ec1eef84153b13bba0b5ae78a42084b634a9d9b1
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 16:52:07 2022 -0800

    Replace array args with pointers

commit 9079cbf7bb788e1b8eb992cbe1e3e51dfc36f1ce
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 15:00:47 2022 -0800

    Revert "Switch back to funptr struct args for simplicity"

    This reverts commit dc78841af654bf81c41e74795bac0f724af59dd0.

commit 62c1078fe9b10c88df86da0a5275f47581dad275
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 14:52:37 2022 -0800

    Switch back to funptr struct args for simplicity

commit d30992db9a4224235246531a5ddf1a034e8e0c1a
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 14:06:12 2022 -0800

    Avoid needing to pass in vec struct for funptrs

commit 2ba7bec1653a655c68bba3d2961fd280bc8e8f32
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 00:32:09 2022 -0800

    Use template spec to avoid fun ptr to cuda

commit 1faf58b3887747af8659eb1be660e2fc40ff17f9
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 21:54:56 2022 -0800

    Make fPIC more conditional.

commit 6215f89447f63753ca0dd2f2fcee88c7aaf5e548
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 21:35:09 2022 -0800

    Avoid passing dynamic lambda to GPU

commit 30fa3dc323df8962e3802a58f346b813a8a759f5
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 18:02:04 2022 -0800

    note and temporary hack RE cuda compilation

commit bb4dfe93cb8e0e7b582450f6d0c0d8d5ba5cef9a
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 17:21:25 2022 -0800

    Set graphlite library to OBJECT

commit e803da50b983c931c69e42fe3fea30aff534034b
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 16:04:28 2022 -0800

    Convert sublibraries to OBJECT to avoid archives

commit d0e9d176e79f18c645e538a5df19263d11630fcd
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 15:28:15 2022 -0800

    Add missing polygon size and deletion funcs

commit 7db72848b9012d9f1a69b28ad878fb65f4524fc3
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Tue Nov 15 22:26:17 2022 -0800

    Remove static flag (control with shared flag)

commit 5ae33f34716998e3d8b3368c619b81f145e34cfe
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Tue Nov 15 22:16:27 2022 -0800

    Add options to avoid python bindings

commit 4181a586288db47acbd55f8e94e94ec4a7fcd252
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 14 22:02:10 2022 -0800

    Rename to manifoldc

commit c4b14d64655ca2fb3ce8ac80af7390b959fed3c7
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 14 21:51:45 2022 -0800

    Cleanup unused typedefs and simplify names

commit a282c90f1f62141ee5c2cd9303ccc8649f2a6bb3
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 14 21:23:07 2022 -0800

    Placeholder (terrible) decompose binding

commit 9409e81abb2c3ba3ffb0983aa0c14e261d032cc2
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 14 20:37:02 2022 -0800

    Turn on export in CLI instead

commit 9dad24b787af0420ef32719c86fb42e11af9a8c1
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 14 20:33:17 2022 -0800

    "Rebased" WIP cbindings

* Turn on -fPIC whenever shared libs are being built

* Add parens to disambiguate sdf policy logic

* Bump public std from 14 to 17

* Add binding flags to workflow builds

* Add export when building cbindings

* Bump ubuntu version (PBR material deprecation)

* Select no-param overload of Decompose

* Add missing const to decompose sig overload

* Re-organize

* variable capitalization consistency

* Drop to std14 to test for regression fix

* Attempted old libassimp compatibility hack

* Rebump to std_17

* Temporarily disabled

TODO: investigate why this is failing on Mac, but not other backends.

* Remove unused tuple include

* Remove uneccesary auto-includes

* Properly disable Boolean_Close

* rename discrete -> numComponents + early return

* Rename discrete -> numComponents

* Rename conv.hh -> conv.h

* Prune auto-includes

* Note on level_set vs level_set_seq

* Set export on for c-bindings

* Adding C binding tests.

* Separate and don't require CBIND build/test

* Remove bad flag

* Take const char ptr for filename

* Remove testc executable

* Add tests from removed testc
2022-12-18 21:59:36 +01:00
pca006132andEmmett Lalish 87059508e7 Fix memory leak (#280)
* generic hashtable implementation

The original hashtable is modified to accept any value type. The key is
still uint64 because atomic is impossible for general key type, and it
seems that we don't really need the key to be generic.
A "better" hash function is added to replace the original identity hash
function, which should hopefully give less collision, seems doing
nothing for now because our test cases don't have much collision.

* fix #245

* fixed uninitialized field

* change hashtable probe step

* put hash function in template parameter

* fixed compiler error

Co-authored-by: Emmett Lalish <elalish@gmail.com>
2022-11-17 10:55:04 +01:00
212 changed files with 103355 additions and 14631 deletions
+59
View File
@@ -0,0 +1,59 @@
name: build_wheels
on:
release:
types: [published]
workflow_dispatch:
jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04, windows-2019, macos-latest]
steps:
- run: |
git config --global submodule.fetchJobs 8
git config --global core.longpaths true
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Build wheels
uses: pypa/cibuildwheel@v2.19
- uses: actions/upload-artifact@v4
with:
name: python-${{ matrix.os }}
path: ./wheelhouse/*.whl
make_sdist:
name: Make SDist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Build SDist
run: pipx run build --sdist
- uses: actions/upload-artifact@v4
with:
name: python-sdist
path: dist/*.tar.gz
pypi-publish:
name: Upload release to PyPI
needs: [build_wheels, make_sdist]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/manifold3d
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
steps:
- uses: actions/download-artifact@v4
with:
pattern: python-*
merge-multiple: true
path: dist
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
+8 -5
View File
@@ -11,11 +11,14 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: DoozyX/clang-format-lint-action@v0.14
- uses: actions/checkout@v4
- uses: DoozyX/clang-format-lint-action@v0.17
with:
source: '.'
exclude: '*/third_party'
extensions: 'h,cpp'
clangFormatVersion: 12
extensions: 'h,cpp,js,ts,html'
clangFormatVersion: 11
- uses: psf/black@stable
with:
options: "--check --verbose"
src: "./bindings/python/examples"
+4 -4
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Doxygen Action
uses: mattnotmitt/doxygen-action@v1
@@ -20,12 +20,12 @@ jobs:
mkdir public
mv ./samples ./public
mv ./docs ./public
mv ./bindings/wasm/examples/* ./public
- name: Download WASM files
uses: dawidd6/action-download-artifact@v2
- name: Download built examples
uses: dawidd6/action-download-artifact@v3
with:
workflow: manifold.yml
workflow_conclusion: completed
pr: ${{github.event.pull_request.number}}
name: wasm
path: ./public
+149 -78
View File
@@ -2,7 +2,7 @@
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
@@ -12,84 +12,120 @@ on:
jobs:
build:
timeout-minutes: 45
strategy:
matrix:
cuda_support: [ON, OFF]
parallel_backend: [NONE, OMP, TBB]
runs-on: ubuntu-20.04
parallel_backend: [NONE, TBB]
gcc: [13, 14]
runs-on: ubuntu-24.04
env:
CC: gcc-${{ matrix.gcc }}
CXX: g++-${{ matrix.gcc }}
if: github.event.pull_request.draft == false
container:
image: docker://nvidia/cuda:11.6.0-devel-ubuntu20.04
steps:
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
apt-get -y update
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
pip install trimesh
- uses: actions/checkout@v3
sudo apt-get update
sudo apt-get install libglm-dev libgtest-dev libassimp-dev git libtbb-dev libthrust-dev pkg-config libpython3-dev lcov
python -m pip install -U trimesh pytest
- uses: actions/checkout@v4
with:
submodules: true
- uses: jwlawson/actions-setup-cmake@v1.12
submodules: recursive
- uses: jwlawson/actions-setup-cmake@v2
- name: Build ${{matrix.backend}}
if: matrix.parallel_backend != 'NONE'
run: |
git apply thrust.diff
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
# note that the test for CUDA backend does not really test CUDA, as we
# don't have CUDA GPU on GitHub Action
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
- name: Test ${{matrix.parallel_backend}}
if: matrix.parallel_backend != 'NONE'
run: |
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
cd build/test
./manifold_test
cd ../../
python3 bindings/python/examples/run_all.py
python3 bindings/python/examples/run_all.py -e
python3 -m pytest
- name: Coverage Report
# only do code coverage for default sequential backend, it seems that TBB
# backend will cause failure
# perhaps issue related to invalid memory access?
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
if: matrix.parallel_backend == 'NONE'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DCODE_COVERAGE=ON .. && make
lcov --capture --gcov-tool gcov-${{ matrix.gcc }} --ignore-errors mismatch --initial --directory . --output-file ./code_coverage_init.info
cd test
./manifold_test
cd ../
lcov --capture --directory . --output-file ./code_coverage_test.info
lcov --capture --gcov-tool gcov-${{ matrix.gcc }} --ignore-errors mismatch --directory . --output-file ./code_coverage_test.info
lcov --add-tracefile ./code_coverage_init.info --add-tracefile ./code_coverage_test.info --output-file ./code_coverage_total.info
lcov --remove ./code_coverage_total.info '/usr/*' '*/third_party/*' '*/test/*' '*/extras/*' '*/bindings/*' --output-file ./code_coverage.info
- uses: codecov/codecov-action@v2
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
cd ../
- uses: codecov/codecov-action@v4
if: matrix.parallel_backend == 'NONE'
with:
files: build/code_coverage.info
fail_ci_if_error: false
name: ${{matrix.parallel_backend}}-${{matrix.cuda_support}}
name: ${{matrix.parallel_backend}}
- name: test cmake consumer
run: |
cd build
sudo cmake --install .
cd ..
./test-cmake.sh
build_wasm:
runs-on: ubuntu-20.04
build_cbind:
timeout-minutes: 30
runs-on: ubuntu-22.04
if: github.event.pull_request.draft == false
steps:
- name: Install dependencies
run: |
sudo apt-get -y update
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs
- uses: actions/checkout@v3
DEBIAN_FRONTEND=noninteractive sudo apt install -y libgtest-dev libglm-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip
- uses: actions/checkout@v4
with:
submodules: true
submodules: recursive
- uses: jwlawson/actions-setup-cmake@v2
- name: Build C bindings with TBB
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_CBIND=ON -DMANIFOLD_PAR=TBB .. && make
- name: Test ${{matrix.parallel_backend}}
run: |
cd build/test
./manifold_test --gtest_filter=CBIND.*
build_wasm:
timeout-minutes: 30
runs-on: ubuntu-22.04
if: github.event.pull_request.draft == false
steps:
- name: Install dependencies
run: |
sudo apt-get -y update
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs libglm-dev
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup WASM
run: |
# setup emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
- uses: jwlawson/actions-setup-cmake@v1.12
./emsdk install 3.1.45
./emsdk activate 3.1.45
- uses: jwlawson/actions-setup-cmake@v2
- name: Build WASM
run: |
source ./emsdk/emsdk_env.sh
git apply thrust.diff
mkdir build
cd build
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
@@ -97,98 +133,133 @@ jobs:
run: |
cd build/test
node ./manifold_test.js
cd ../bindings/wasm
npm install
- name: Test examples
run: |
cd bindings/wasm/examples
npm ci
npm run build
npm test
cp ../manifold.* ./dist/
- name: Upload WASM files
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wasm
path: |
build/bindings/wasm/manifold.js
build/bindings/wasm/manifold.wasm
build/bindings/wasm/manifold.d.ts
retention-days: 7
path: bindings/wasm/examples/dist/
retention-days: 90
overwrite: true
build_windows:
timeout-minutes: 30
strategy:
matrix:
parallel_backend: [NONE]
cuda_support: [OFF]
parallel_backend: [NONE, TBB]
max-parallel: 1
runs-on: windows-2019
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: true
- uses: jwlawson/actions-setup-cmake@v1.12
submodules: recursive
- uses: jwlawson/actions-setup-cmake@v2
- uses: ilammy/msvc-dev-cmd@v1
- name: Install CUDA
if: matrix.backend == 'CUDA'
env:
cuda: "11.3.0"
visual_studio: "Visual Studio 16 2019"
shell: powershell
run: |
Invoke-RestMethod https://raw.githubusercontent.com/ptheywood/cuda-cmake-github-actions/21811d232fc775fa8d44dddf7f8c5ce2cf8d2b81/scripts/actions/install_cuda_windows.ps1 -OutFile install_cuda_windows.ps1
.\install_cuda_windows.ps1
- name: nvcc check
if: matrix.backend == 'CUDA'
shell: powershell
run: |
nvcc -V
ls $env:CUDA_PATH
ls $env:CUDA_PATH\bin
ls $env:CUDA_PATH\include
- name: Build ${{matrix.backend}}
shell: powershell
run: |
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -A x64 -B build
cd build
cmake --build . --target ALL_BUILD --config Release
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
- name: Test ${{matrix.parallel_backend}}
shell: bash
run: |
cd build/bin/Release
./manifold_test.exe
cd ../../
- name: test cmake consumer
run: |
cd build
cmake --install .
cd ..
./test-cmake.sh
build_mxe:
timeout-minutes: 30
strategy:
matrix:
parallel_backend: [NONE, TBB]
max-parallel: 1
runs-on: ubuntu-latest
container: openscad/mxe-x86_64-gui:latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Build
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=OFF -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
- name: Test
run: |
cd build/test
./Release/manifold_test.exe
./manifold_test
build_mac:
timeout-minutes: 30
strategy:
matrix:
parallel_backend: [NONE, TBB]
runs-on: macos-latest
if: github.event.pull_request.draft == false
steps:
- name: Install OpenMP
if: matrix.parallel_backend == 'OMP'
run: brew install libomp
- name: Install common dependencies
run: |
brew install pkg-config googletest assimp
pip install trimesh pytest
- name: Install TBB
if: matrix.parallel_backend == 'TBB'
run: brew install tbb
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: true
- uses: jwlawson/actions-setup-cmake@v1.12
submodules: recursive
- uses: jwlawson/actions-setup-cmake@v2
- name: Build
run: |
git apply thrust.diff
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
- name: Test
run: |
cd build/test
./manifold_test
cd ../../
- name: test cmake consumer
run: |
cd build
sudo cmake --install .
cd ..
./test-cmake.sh
build_nix:
timeout-minutes: 30
strategy:
matrix:
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
variant: [none, tbb, js]
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v15
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: cachix/install-nix-action@v22
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
build_nix_python:
timeout-minutes: 30
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: cachix/install-nix-action@v22
- run: nix build -L '.?submodules=1#manifold3d'
+34
View File
@@ -0,0 +1,34 @@
name: publish_npm
on:
release:
types: [published]
workflow_dispatch:
jobs:
publish_npm:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Download built examples
uses: dawidd6/action-download-artifact@v3
with:
workflow: manifold.yml
workflow_conclusion: completed
name: wasm
path: ./bindings/wasm/
- name: Publish to npm
run: |
cd ./bindings/wasm/
npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
+31
View File
@@ -0,0 +1,31 @@
name: Release File
on:
release:
types: [published]
workflow_dispatch:
jobs:
upload_archive:
name: Upload archive
runs-on: ubuntu-latest
steps:
- run: |
ref=${{ github.ref_name }}
echo "release_name=manifold-${ref#v}" >> $GITHUB_ENV
- uses: actions/checkout@v4
with:
path: ${{ env.release_name }}
submodules: recursive
- name: Build archive
run: >
tar --exclude=".git*" -cz ${{ env.release_name }} -f ${{ env.release_name }}.tar.gz
- name: Log checksum
run: >
sha256sum ${{ env.release_name }}.tar.gz
- name: Add file to release
run: |
cd ${{ env.release_name }} || exit 1
gh release upload ${{ github.ref_name }} ../${{ env.release_name }}.tar.gz
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
+7 -1
View File
@@ -109,8 +109,14 @@ build
buildWASM
docs/html
node_modules/
bindings/wasm/examples/built
bindings/wasm/examples/dist
bindings/wasm/examples/public/manifold*
__pycache__
.vscode/c_cpp_properties.json
*.sublime-project
*.sublime-workspace
.cache
.cache
# PyCharm
.idea
+6 -13
View File
@@ -1,13 +1,6 @@
[submodule "third_party/thrust"]
path = src/third_party/thrust
url = https://github.com/NVIDIA/thrust
[submodule "third_party/glm"]
path = src/third_party/glm
url = https://github.com/g-truc/glm
[submodule "third_party/google_test"]
path = test/third_party/google_test
url = https://github.com/google/googletest.git
[submodule "third_party/pybind11"]
path = bindings/python/third_party/pybind11
url = https://github.com/pybind/pybind11
branch = stable
[submodule "src/third_party/quickhull"]
path = src/third_party/quickhull
url = https://github.com/akuukka/quickhull
[submodule "bindings/python/third_party/nanobind"]
path = bindings/python/third_party/nanobind
url = https://github.com/wjakob/nanobind
+24 -22
View File
@@ -1,26 +1,28 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "manifold test",
"type": "cppdbg",
"MIMode": "lldb",
"request": "launch",
"program": "${workspaceFolder}/build/test/manifold_test",
"args": [
"--gtest_break_on_failure",
"--gtest_filter=Smooth.RefineQuads"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build/test",
"environment": [
{
"name": "manifold test",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/test/manifold_test",
"args": [
"--gtest_break_on_failure",
"--gtest_filter=Manifold.GetMesh"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build/test",
"environment": [
{
"name": "MALLOC_CHECK_",
"value": "2"
}
]
"name": "MALLOC_CHECK_",
"value": "2"
}
]
],
"externalConsole": false
}
]
}
+36 -4
View File
@@ -98,16 +98,32 @@
"concepts": "cpp",
"queue": "cpp",
"stack": "cpp",
"__hash_table": "cpp"
"__hash_table": "cpp",
"__std_stream": "cpp",
"__verbose_abort": "cpp",
"any": "cpp",
"barrier": "cpp",
"charconv": "cpp",
"coroutine": "cpp",
"latch": "cpp",
"semaphore": "cpp",
"span": "cpp",
"__sso_allocator": "cpp",
"source_location": "cpp",
"format": "cpp",
"numbers": "cpp",
"ranges": "cpp",
"stop_token": "cpp"
},
"C_Cpp.clang_format_fallbackStyle": "google",
"editor.formatOnSave": true,
"cmake.configureArgs": [
"-DBUILD_SHARED_LIBS=ON",
"-DMANIFOLD_EXPORT=OFF",
"-DMANIFOLD_CBIND=ON",
"-DMANIFOLD_PYBIND=ON",
"-DMANIFOLD_EXPORT=ON",
"-DMANIFOLD_DEBUG=ON",
"-DMANIFOLD_USE_CUDA=OFF",
"-DMANIFOLD_PAR=NONE",
"-DMANIFOLD_PAR=TBB",
"-DCODE_COVERAGE=OFF",
"-DCMAKE_CXX_FLAGS=''" //'-fsanitize=address,undefined'"
],
@@ -118,4 +134,20 @@
},
"editor.tabSize": 2,
"editor.detectIndentation": false,
"cSpell.words": [
"Bary",
"Coplanarity",
"csaszar",
"Gyroid",
"halfedge",
"halfedges",
"Tris",
"Verts"
],
"javascript.format.semicolons": "insert",
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.formatting.provider": "none",
"clang-format.executable": "clang-format",
}
+2 -1
View File
@@ -6,4 +6,5 @@
# source control.
Emmett Lalish <elalish>
Chun Kit LAM <pca006132>
Google LLC
Geoff deRosenroll <geoffder>
Google LLC
+116 -40
View File
@@ -16,60 +16,136 @@ cmake_minimum_required(VERSION 3.18)
project(manifold LANGUAGES CXX)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
option(MANIFOLD_EXPORT off)
option(MANIFOLD_DEBUG off)
option(MANIFOLD_USE_CUDA off)
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"NONE\"")
set(MANIFOLD_FLAGS "" CACHE STRING "Manifold compiler flags")
option(MANIFOLD_EXPORT "Build mesh export (via assimp) utility library" OFF)
option(MANIFOLD_TEST "Enable testing suite" ON)
# fuzztest is a rather large dependency
option(MANIFOLD_FUZZ "Enable fuzzing tests" OFF)
option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF)
option(MANIFOLD_PYBIND "Build python bindings" OFF)
option(MANIFOLD_CBIND "Build C (FFI) bindings" ON)
option(MANIFOLD_JSBIND "Build js binding" ${EMSCRIPTEN})
option(BUILD_SHARED_LIBS "Build shared library" ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(MANIFOLD_FUZZ)
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(FATAL_ERROR "fuzztest only supports clang" )
endif()
# we should enable debug checks
set(MANIFOLD_DEBUG ON)
# enable fuzztest fuzzing mode
set(FUZZTEST_FUZZING_MODE ON)
# address sanitizer required
set(CMAKE_CXX_FLAGS "$CMAKE_CXX_FLAGS -fsanitize=address")
endif()
option(TRACY_ENABLE "Use tracy profiling" OFF)
option(TRACY_MEMORY_USAGE "Track memory allocation with tracy (expensive)" OFF)
option(BUILD_TEST_CGAL "Build CGAL performance comparisons" OFF)
if(TRACY_ENABLE)
option(CMAKE_BUILD_TYPE "Build type" RelWithDebInfo)
if(TRACY_MEMORY_USAGE)
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} -DTRACY_MEMORY_USAGE)
endif()
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
endif()
else()
option(CMAKE_BUILD_TYPE "Build type" Release)
endif()
if(EMSCRIPTEN)
message("Building for Emscripten")
set(MANIFOLD_FLAGS -fexceptions)
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
set(MANIFOLD_FLAGS -fexceptions -D_LIBCUDACXX_HAS_THREAD_API_EXTERNAL -D_LIBCUDACXX_HAS_THREAD_API_CUDA)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1 -fexceptions -sDISABLE_EXCEPTION_CATCHING=0")
set(MANIFOLD_PYBIND OFF)
set(BUILD_SHARED_LIBS OFF)
endif()
option(PYBIND11_FINDPYTHON on)
include(${CMAKE_CURRENT_SOURCE_DIR}/manifoldDeps.cmake)
option(BUILD_TEST_CGAL off)
option(BUILD_SHARED_LIBS off)
set(GLM_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/glm)
set(PYBIND11_DIR ${PROJECT_SOURCE_DIR}/bindings/python/third_party/pybind11)
set(THRUST_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/thrust)
if(MANIFOLD_USE_CUDA)
enable_language(CUDA)
find_package(CUDA REQUIRED)
# we cannot set THRUST_INC_DIR when building with CUDA, otherwise the
# compiler will not use the system CUDA headers which causes incompatibility
# clear THRUST_INC_DIR, we use the one from nvcc
set(THRUST_INC_DIR "")
set(MANIFOLD_NVCC_RELEASE_FLAGS -O3 -lineinfo)
set(MANIFOLD_NVCC_DEBUG_FLAGS -G)
set(MANIFOLD_NVCC_FLAGS -Xcudafe --diag_suppress=esa_on_defaulted_function_ignored --extended-lambda --expt-relaxed-constexpr
"$<$<CONFIG:RELEASE>:${MANIFOLD_NVCC_RELEASE_FLAGS}>"
"$<$<CONFIG:DEBUG>:${MANIFOLD_NVCC_DEBUG_FLAGS}>")
if(MANIFOLD_PYBIND)
if(Python_VERSION VERSION_LESS 3.12)
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
else()
find_package(Python COMPONENTS Interpreter Development.SABIModule REQUIRED)
endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
if(NOT MSVC)
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused)
add_compile_options(
"$<$<COMPILE_LANGUAGE:CXX>:${WARNING_FLAGS}>"
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=${WARNING_FLAGS}>")
if(CMAKE_EXPORT_COMPILE_COMMANDS AND NOT EMSCRIPTEN)
# for nixos
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
endif()
if (MSVC)
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} /DNOMINMAX /bigobj)
else()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused -Wno-array-bounds
-Wno-stringop-overflow -Wno-alloc-size-larger-than)
else()
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused)
endif()
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} ${WARNING_FLAGS})
endif()
if(CODE_COVERAGE AND NOT MSVC)
set(COVERAGE_FLAGS -coverage -fno-inline-small-functions -fkeep-inline-functions -fkeep-static-functions)
add_compile_options(
"$<$<COMPILE_LANGUAGE:CXX>:${COVERAGE_FLAGS}>"
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=-coverage>")
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} ${COVERAGE_FLAGS})
add_link_options("-coverage")
endif()
# installation rpath
set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
if("${isSystemDir}" STREQUAL "-1")
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
endif("${isSystemDir}" STREQUAL "-1")
add_subdirectory(src)
add_subdirectory(samples)
add_subdirectory(test)
add_subdirectory(extras)
add_subdirectory(bindings)
add_subdirectory(bindings)
if(MANIFOLD_EXPORT)
add_subdirectory(meshIO)
endif()
if(MANIFOLD_TEST)
add_subdirectory(samples)
add_subdirectory(test)
add_subdirectory(extras)
endif()
# installation related
set(MANIFOLD_VERSION_MAJOR 2)
set(MANIFOLD_VERSION_MINOR 5)
set(MANIFOLD_VERSION_PATCH 1)
set(MANIFOLD_VERSION "${MANIFOLD_VERSION_MAJOR}.${MANIFOLD_VERSION_MINOR}.${MANIFOLD_VERSION_PATCH}")
set_property(TARGET manifold PROPERTY VERSION "${MANIFOLD_VERSION}")
set_property(TARGET manifold PROPERTY SOVERSION 2)
if(NOT EMSCRIPTEN)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_BINARY_DIR}/cmake/manifoldConfigVersion.cmake
VERSION ${MANIFOLD_VERSION}
COMPATIBILITY SameMajorVersion
)
install(EXPORT manifoldTargets DESTINATION ${CMAKE_INSTALL_DATADIR}/manifold)
configure_file(manifoldConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/manifoldConfig.cmake @ONLY)
install(FILES
${CMAKE_CURRENT_SOURCE_DIR}/manifoldDeps.cmake
${CMAKE_CURRENT_BINARY_DIR}/cmake/manifoldConfigVersion.cmake
${CMAKE_CURRENT_BINARY_DIR}/manifoldConfig.cmake
DESTINATION ${CMAKE_INSTALL_DATADIR}/manifold
)
endif()
+100 -16
View File
@@ -1,55 +1,97 @@
[![codecov](https://codecov.io/github/elalish/manifold/branch/master/graph/badge.svg?token=IIA8G5HVS7)](https://codecov.io/github/elalish/manifold)
[![PyPI version](https://badge.fury.io/py/manifold3d.svg)](https://badge.fury.io/py/manifold3d)
[![npm version](https://badge.fury.io/js/manifold-3d.svg)](https://badge.fury.io/js/manifold-3d)
[![twitter](https://img.shields.io/twitter/follow/manifoldcad?style=social&logo=twitter)](https://twitter.com/intent/follow?screen_name=manifoldcad)
## [ManifoldCAD.org](https://manifoldcad.org)
## Users
If you like OpenSCAD / OpenJSCAD, you might also like ManifoldCAD - our own solid modelling web app. The operations are not GPU-accelerated, but it's still pretty fast and a good way to test out our Manifold library.
[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.
## Manifold Frontend Sandboxes
### [ManifoldCAD.org](https://manifoldcad.org)
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - 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.
*Note for Firefox users: If you find the editor is stuck on **Loading...**, setting
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
may solve the problem.*
### [Python Colab Example](https://colab.research.google.com/drive/1VxrFYHPSHZgUbl9TeWzCeovlpXrPQ5J5?usp=sharing)
If you prefer Python to JS/TS, make your own copy of the example notebook above. 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.
![A metallic Menger sponge](https://elalish.github.io/manifold/samples/models/mengerSponge3.webp "A metallic Menger sponge")
# Manifold
[**High-level Documentation**](https://elalish.blogspot.com/search/label/Manifold) | [**API Documentation**](https://elalish.github.io/manifold/docs/html/modules.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)
[**API Documentation**](https://elalish.github.io/manifold/docs/html/topics.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)
[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 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) and Python to make this library more portable and easy to use. We have only three dependencies (all built-in header-only libraries): graphlite provides a connected components algorithm, GLM is a compact vector library, and Thrust is Nvidia's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms). We make use of submodules to ensure compatibility.
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 ([manifold3d](https://pypi.org/project/manifold3d/)), and C to make this library more portable and easy to use.
System Dependencies (note that we will automatically download the dependency if there is no such package on the system):
- [`GLM`](https://github.com/g-truc/glm/): A compact header-only vector library.
- [`Thrust`](https://github.com/NVIDIA/thrust): NVIDIA's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms)
- [`tbb`](https://github.com/oneapi-src/oneTBB/): Intel's thread building blocks library. (only when `MANIFOLD_PAR=TBB` is enabled)
- [`gtest`](https://github.com/google/googletest/): Google test library (only when test is enabled, i.e. `MANIFOLD_TEST=ON`)
Other dependencies:
- [`Clipper2`](https://github.com/AngusJohnson/Clipper2): provides our 2D subsystem
- [`quickhull`](https://github.com/akuukka/quickhull): 3D convex hull algorithm.
## 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 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.
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 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.
To aid in speed, this library makes extensive use of parallelization, generally through Nvidia's Thrust library. You can switch between the 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 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.
> Note: OMP and CUDA backends are now removed
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.
## Building
Only CMake, a C++ compiler, and Python are required to be installed and set up to build this library (it has been tested with GCC, LLVM, MSVC). However, a variety of optional dependencies can bring in more functionality, see below.
Only CMake, a C++ compiler, and Python are required to be installed and set up to build this library (it has been tested with GCC, LLVM, MSVC). However, a variety of optional dependencies can bring in more functionality, see below.
Build and test (Ubuntu or similar):
```
git clone --recurse-submodules https://github.com/elalish/manifold.git
cd manifold
git apply thrust.diff
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. && make
test/manifold_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`.
CMake flags (usage e.g. `-DMANIFOLD_DEBUG=ON`):
- `MANIFOLD_JSBIND=[OFF, <ON>]`: Build js binding when using emscripten.
- `MANIFOLD_CBIND=[<OFF>, ON]`: Build C FFI binding.
- `MANIFOLD_PYBIND=[OFF, <ON>]`: Build python binding.
- `MANIFOLD_PAR=[<NONE>, TBB]`: Provides multi-thread parallelization, requires `libtbb-dev` if `TBB` backend is selected.
- `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.
- `MANIFOLD_TEST=[OFF, <ON>]`: Build unittests.
- `TRACY_ENABLE=[<OFF>, ON]`: Enable integration with tracy profiler.
See profiling section below.
- `BUILD_TEST_CGAL=[<OFF>, ON]`: Builds a CGAL-based performance [comparison](https://github.com/elalish/manifold/tree/master/extras), requires `libcgal-dev`.
Offline building:
- `FETCHCONTENT_SOURCE_DIR_GLM`: path to glm source.
- `FETCHCONTENT_SOURCE_DIR_GOOGLETEST`: path to googletest source.
- `FETCHCONTENT_SOURCE_DIR_THRUST`: path to NVIDIA thrust source.
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.
### WASM
> Note that we have only tested emscripten version 3.1.45. It is known that
3.1.48 has some issues compiling manifold.
To build the JS WASM library, first install NodeJS and set up emscripten:
(on Mac):
@@ -77,13 +119,13 @@ node test/manifold_test.js
### Python
The CMake script will build the python binding `pymanifold` automatically. To
use the extension, please add `$BUILD_DIR/tools` to your `PYTHONPATH`, where
The CMake script will build the python binding `manifold3d` automatically. To
use the extension, please add `$BUILD_DIR/bindings/python` to your `PYTHONPATH`, where
`$BUILD_DIR` is the build directory for CMake. Examples using the python binding
can be found in `bindings/python/examples`. To see exported samples, run:
```
sudo apt install pkg-config libpython3-dev python3 python3-distutils python3-pip
pip install trimesh
pip install trimesh pytest
python3 run_all.py -e
```
@@ -91,16 +133,58 @@ Run the following code in the interpreter for
python binding documentation:
```
>>> import pymanifold
>>> help(pymanifold)
>>> import manifold3d
>>> help(manifold3d)
```
For more detailed documentation, please refer to the C++ API.
### Java / Clojure
Unofficial java bindings are currently maintained in [a fork](https://github.com/SovereignShop/manifold).
There is also a Clojure [library](https://github.com/SovereignShop/clj-manifold3d).
### 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 11 and black formatter for python.
If you have clang-format installed but without clang-11, you can specify the
clang-format executable by setting the `CLANG_FORMAT` environment variable.
### 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 Support
We use https://github.com/google/fuzztest for fuzzing the triangulator.
To enable fuzzing, make sure that you are using clang compiler (`-DCMAKE_CXX_COMPILER=clang -DCMAKE_C_COMPILER=clang`), running Linux, and enable fuzzing support by setting `-DMANIFOLD_FUZZ=ON`.
To run the fuzzer and minimize testcase, do
```
../minimizer.sh ./test/polygon_fuzz --fuzz=PolygonFuzz.TriangulationNoCrash
```
## 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).
+14
View File
@@ -0,0 +1,14 @@
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. Make a new branch called the version, e.g. v2.3.0.
1. Use VSCode to search and replace the old version with the new - so far in test-cmake.sh, flake.nix, pyproject.toml, and package.json.
1. Also update CMakeLists.txt version by searching for "set(MANIFOLD_VERSION_".
1. Commit, push, open a PR, verify tests pass, 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.
+6 -2
View File
@@ -12,10 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
if(NOT EMSCRIPTEN)
if(NOT EMSCRIPTEN AND MANIFOLD_CBIND)
add_subdirectory(c)
endif()
if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
add_subdirectory(python)
endif()
if(EMSCRIPTEN)
if(EMSCRIPTEN AND MANIFOLD_JSBIND)
add_subdirectory(wasm)
endif()
+31
View File
@@ -0,0 +1,31 @@
project(manifoldc)
# this is a shared library for FFI bindings
add_library(${PROJECT_NAME} SHARED manifoldc.cpp conv.cpp)
if(MSVC)
set_target_properties(${PROJECT_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY
${CMAKE_BINARY_DIR}/bin )
set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
${CMAKE_BINARY_DIR}/bin )
endif()
if(MANIFOLD_EXPORT)
target_link_libraries(${PROJECT_NAME} PRIVATE meshIO)
target_compile_options(${PROJECT_NAME} PUBLIC -DMANIFOLD_EXPORT)
endif()
target_link_libraries(
${PROJECT_NAME}
PRIVATE manifold sdf cross_section
)
target_include_directories(${PROJECT_NAME} PUBLIC
$<INSTALL_INTERFACE:include/${CMAKE_PROJECT_NAME}>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
install(TARGETS ${PROJECT_NAME} EXPORT manifoldTargets)
install(FILES include/manifoldc.h include/types.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME})
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "conv.h"
#include "manifold.h"
#include "public.h"
#include "types.h"
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
float y2, float z2) {
auto p1 = glm::vec3(x1, y1, z1);
auto p2 = glm::vec3(x2, y2, z2);
auto box = new (mem) Box(p1, p2);
return to_c(box);
}
ManifoldVec3 manifold_box_min(ManifoldBox *b) { return to_c((*from_c(b)).min); }
ManifoldVec3 manifold_box_max(ManifoldBox *b) { return to_c((*from_c(b)).max); }
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b) {
auto v = from_c(b)->Size();
return {v.x, v.y, v.z};
}
ManifoldVec3 manifold_box_center(ManifoldBox *b) {
auto v = from_c(b)->Center();
return {v.x, v.y, v.z};
}
float manifold_box_scale(ManifoldBox *b) { return from_c(b)->Scale(); }
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z) {
auto p = glm::vec3(x, y, z);
return from_c(b)->Contains(p);
}
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b) {
auto outer = *from_c(a);
auto inner = *from_c(b);
return outer.Contains(inner);
}
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z) {
auto box = *from_c(b);
auto p = glm::vec3(x, y, z);
box.Union(p);
}
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b) {
auto box = from_c(a)->Union(*from_c(b));
return to_c(new (mem) Box(box));
}
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
float y1, float z1, float x2, float y2,
float z2, float x3, float y3, float z3,
float x4, float y4, float z4) {
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
auto transformed = from_c(b)->Transform(mat);
return to_c(new (mem) Box(transformed));
}
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
float z) {
auto p = glm::vec3(x, y, z);
auto translated = (*from_c(b)) + p;
return to_c(new (mem) Box(translated));
}
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
float z) {
auto p = glm::vec3(x, y, z);
auto scaled = (*from_c(b)) * p;
return to_c(new (mem) Box(scaled));
}
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z) {
auto p = glm::vec3(x, y, z);
return from_c(b)->DoesOverlap(p);
}
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b) {
return from_c(a)->DoesOverlap(*from_c(b));
}
int manifold_box_is_finite(ManifoldBox *b) { return from_c(b)->IsFinite(); }
+250
View File
@@ -0,0 +1,250 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "conv.h"
#include <vector>
#include "cross_section.h"
#include "manifold.h"
#include "public.h"
#include "sdf.h"
#include "types.h"
ManifoldManifold *to_c(manifold::Manifold *m) {
return reinterpret_cast<ManifoldManifold *>(m);
}
ManifoldManifoldVec *to_c(ManifoldVec *ms) {
return reinterpret_cast<ManifoldManifoldVec *>(ms);
}
ManifoldCrossSection *to_c(manifold::CrossSection *cs) {
return reinterpret_cast<ManifoldCrossSection *>(cs);
}
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv) {
return reinterpret_cast<ManifoldCrossSectionVec *>(csv);
}
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *m) {
return reinterpret_cast<ManifoldSimplePolygon *>(m);
}
ManifoldPolygons *to_c(manifold::Polygons *m) {
return reinterpret_cast<ManifoldPolygons *>(m);
}
ManifoldMesh *to_c(manifold::Mesh *m) {
return reinterpret_cast<ManifoldMesh *>(m);
}
ManifoldMeshGL *to_c(manifold::MeshGL *m) {
return reinterpret_cast<ManifoldMeshGL *>(m);
}
ManifoldOpType to_c(manifold::OpType optype) {
ManifoldOpType op = MANIFOLD_ADD;
switch (optype) {
case manifold::OpType::Add:
break;
case manifold::OpType::Subtract:
op = MANIFOLD_SUBTRACT;
break;
case manifold::OpType::Intersect:
op = MANIFOLD_INTERSECT;
break;
};
return op;
}
ManifoldError to_c(manifold::Manifold::Error error) {
ManifoldError e = MANIFOLD_NO_ERROR;
switch (error) {
case Manifold::Error::NoError:
break;
case Manifold::Error::NonFiniteVertex:
e = MANIFOLD_NON_FINITE_VERTEX;
break;
case Manifold::Error::NotManifold:
e = MANIFOLD_NOT_MANIFOLD;
break;
case Manifold::Error::VertexOutOfBounds:
e = MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS;
break;
case Manifold::Error::PropertiesWrongLength:
e = MANIFOLD_PROPERTIES_WRONG_LENGTH;
break;
case Manifold::Error::MissingPositionProperties:
e = MANIFOLD_MISSING_POSITION_PROPERTIES;
break;
case Manifold::Error::MergeVectorsDifferentLengths:
e = MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS;
break;
case Manifold::Error::MergeIndexOutOfBounds:
e = MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS;
break;
case Manifold::Error::TransformWrongLength:
e = MANIFOLD_TRANSFORM_WRONG_LENGTH;
break;
case Manifold::Error::RunIndexWrongLength:
e = MANIFOLD_RUN_INDEX_WRONG_LENGTH;
break;
case Manifold::Error::FaceIDWrongLength:
e = MANIFOLD_FACE_ID_WRONG_LENGTH;
break;
case Manifold::Error::InvalidConstruction:
e = MANIFOLD_INVALID_CONSTRUCTION;
break;
};
return e;
}
ManifoldBox *to_c(manifold::Box *m) {
return reinterpret_cast<ManifoldBox *>(m);
}
ManifoldRect *to_c(manifold::Rect *m) {
return reinterpret_cast<ManifoldRect *>(m);
}
ManifoldVec2 to_c(glm::vec2 v) { return {v.x, v.y}; }
ManifoldVec3 to_c(glm::vec3 v) { return {v.x, v.y, v.z}; }
ManifoldIVec3 to_c(glm::ivec3 v) { return {v.x, v.y, v.z}; }
ManifoldProperties to_c(manifold::Properties p) {
return {p.surfaceArea, p.volume};
}
const manifold::Manifold *from_c(ManifoldManifold *m) {
return reinterpret_cast<manifold::Manifold const *>(m);
}
ManifoldVec *from_c(ManifoldManifoldVec *ms) {
return reinterpret_cast<ManifoldVec *>(ms);
}
const manifold::CrossSection *from_c(ManifoldCrossSection *cs) {
return reinterpret_cast<manifold::CrossSection *>(cs);
}
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv) {
return reinterpret_cast<CrossSectionVec *>(csv);
}
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m) {
return reinterpret_cast<manifold::SimplePolygon const *>(m);
}
const manifold::Polygons *from_c(ManifoldPolygons *m) {
return reinterpret_cast<manifold::Polygons const *>(m);
}
const manifold::Mesh *from_c(ManifoldMesh *m) {
return reinterpret_cast<manifold::Mesh const *>(m);
}
const manifold::MeshGL *from_c(ManifoldMeshGL *m) {
return reinterpret_cast<manifold::MeshGL const *>(m);
}
OpType from_c(ManifoldOpType optype) {
auto op = OpType::Add;
switch (optype) {
case MANIFOLD_ADD:
break;
case MANIFOLD_SUBTRACT:
op = OpType::Subtract;
break;
case MANIFOLD_INTERSECT:
op = OpType::Intersect;
break;
};
return op;
}
CrossSection::FillRule from_c(ManifoldFillRule fillrule) {
auto fr = CrossSection::FillRule::EvenOdd;
switch (fillrule) {
case MANIFOLD_FILL_RULE_EVEN_ODD:
break;
case MANIFOLD_FILL_RULE_NON_ZERO:
fr = CrossSection::FillRule::NonZero;
break;
case MANIFOLD_FILL_RULE_POSITIVE:
fr = CrossSection::FillRule::Positive;
break;
case MANIFOLD_FILL_RULE_NEGATIVE:
fr = CrossSection::FillRule::Negative;
break;
};
return fr;
}
CrossSection::JoinType from_c(ManifoldJoinType join_type) {
auto jt = CrossSection::JoinType::Square;
switch (join_type) {
case MANIFOLD_JOIN_TYPE_SQUARE:
break;
case MANIFOLD_JOIN_TYPE_ROUND:
jt = CrossSection::JoinType::Round;
break;
case MANIFOLD_JOIN_TYPE_MITER:
jt = CrossSection::JoinType::Miter;
break;
};
return jt;
}
const manifold::Box *from_c(ManifoldBox *m) {
return reinterpret_cast<manifold::Box const *>(m);
}
const manifold::Rect *from_c(ManifoldRect *m) {
return reinterpret_cast<manifold::Rect const *>(m);
}
glm::vec2 from_c(ManifoldVec2 v) { return glm::vec2(v.x, v.y); }
glm::vec3 from_c(ManifoldVec3 v) { return glm::vec3(v.x, v.y, v.z); }
glm::ivec3 from_c(ManifoldIVec3 v) { return glm::ivec3(v.x, v.y, v.z); }
glm::vec4 from_c(ManifoldVec4 v) { return glm::vec4(v.x, v.y, v.z, v.w); }
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length) {
auto vec = std::vector<glm::vec3>();
for (int i = 0; i < length; ++i) {
vec.push_back(from_c(vs[i]));
}
return vec;
}
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length) {
auto vec = std::vector<glm::ivec3>();
for (int i = 0; i < length; ++i) {
vec.push_back(from_c(vs[i]));
}
return vec;
}
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length) {
auto vec = std::vector<glm::vec4>();
for (int i = 0; i < length; ++i) {
vec.push_back(from_c(vs[i]));
}
return vec;
}
+250
View File
@@ -0,0 +1,250 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <vector>
#include "conv.h"
#include "cross_section.h"
#include "manifold.h"
#include "public.h"
#include "types.h"
ManifoldCrossSection *manifold_cross_section_empty(void *mem) {
return to_c(new (mem) CrossSection());
}
ManifoldCrossSection *manifold_cross_section_copy(void *mem,
ManifoldCrossSection *cs) {
auto cross = *from_c(cs);
return to_c(new (mem) CrossSection(cross));
}
ManifoldCrossSection *manifold_cross_section_of_simple_polygon(
void *mem, ManifoldSimplePolygon *p, ManifoldFillRule fr) {
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
}
ManifoldCrossSection *manifold_cross_section_of_polygons(void *mem,
ManifoldPolygons *p,
ManifoldFillRule fr) {
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
}
ManifoldCrossSectionVec *manifold_cross_section_empty_vec(void *mem) {
return to_c(new (mem) CrossSectionVec());
}
ManifoldCrossSectionVec *manifold_cross_section_vec(void *mem, size_t sz) {
return to_c(new (mem) CrossSectionVec(sz));
}
void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec *csv,
size_t sz) {
from_c(csv)->reserve(sz);
}
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec *csv) {
return from_c(csv)->size();
}
ManifoldCrossSection *manifold_cross_section_vec_get(
void *mem, ManifoldCrossSectionVec *csv, int idx) {
auto cs = (*from_c(csv))[idx];
return to_c(new (mem) CrossSection(cs));
}
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, int idx,
ManifoldCrossSection *cs) {
(*from_c(csv))[idx] = *from_c(cs);
}
void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec *csv,
ManifoldCrossSection *cs) {
return from_c(csv)->push_back(*from_c(cs));
}
ManifoldCrossSection *manifold_cross_section_square(void *mem, float x, float y,
int center) {
auto cs = CrossSection::Square(glm::vec2(x, y), center);
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_circle(void *mem, float radius,
int circular_segments) {
auto cs = CrossSection::Circle(radius, circular_segments);
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_boolean(void *mem,
ManifoldCrossSection *a,
ManifoldCrossSection *b,
ManifoldOpType op) {
auto cs = from_c(a)->Boolean(*from_c(b), from_c(op));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_batch_boolean(
void *mem, ManifoldCrossSectionVec *csv, ManifoldOpType op) {
auto cs = CrossSection::BatchBoolean(*from_c(csv), from_c(op));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_union(void *mem,
ManifoldCrossSection *a,
ManifoldCrossSection *b) {
auto cs = (*from_c(a)) + (*from_c(b));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_difference(
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b) {
auto cs = (*from_c(a)) - (*from_c(b));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_intersection(
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b) {
auto cs = (*from_c(a)) ^ (*from_c(b));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_hull(void *mem,
ManifoldCrossSection *cs) {
auto hulled = from_c(cs)->Hull();
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_batch_hull(
void *mem, ManifoldCrossSectionVec *css) {
auto hulled = CrossSection::Hull(*from_c(css));
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_hull_simple_polygon(
void *mem, ManifoldSimplePolygon *ps) {
auto hulled = CrossSection::Hull(*from_c(ps));
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_hull_polygons(
void *mem, ManifoldPolygons *ps) {
auto hulled = CrossSection::Hull(*from_c(ps));
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
ManifoldCrossSection *cs,
float x, float y) {
auto translated = from_c(cs)->Translate(glm::vec2(x, y));
return to_c(new (mem) CrossSection(translated));
}
ManifoldCrossSection *manifold_cross_section_rotate(void *mem,
ManifoldCrossSection *cs,
float deg) {
auto rotated = from_c(cs)->Rotate(deg);
return to_c(new (mem) CrossSection(rotated));
}
ManifoldCrossSection *manifold_cross_section_scale(void *mem,
ManifoldCrossSection *cs,
float x, float y) {
auto scaled = from_c(cs)->Scale(glm::vec2(x, y));
return to_c(new (mem) CrossSection(scaled));
}
ManifoldCrossSection *manifold_cross_section_mirror(void *mem,
ManifoldCrossSection *cs,
float ax_x, float ax_y) {
auto mirrored = from_c(cs)->Mirror(glm::vec2(ax_x, ax_y));
return to_c(new (mem) CrossSection(mirrored));
}
ManifoldCrossSection *manifold_cross_section_transform(void *mem,
ManifoldCrossSection *cs,
float x1, float y1,
float x2, float y2,
float x3, float y3) {
auto mat = glm::mat3x2(x1, y1, x2, y2, x3, y3);
auto transformed = from_c(cs)->Transform(mat);
return to_c(new (mem) CrossSection(transformed));
}
ManifoldCrossSection *manifold_cross_section_warp(
void *mem, ManifoldCrossSection *cs,
ManifoldVec2 (*fun)(float, float, void *), void *ctx) {
// Bind function with context argument to one without
using namespace std::placeholders;
std::function<ManifoldVec2(float, float)> f2 = std::bind(fun, _1, _2, ctx);
std::function<void(glm::vec2 & v)> warp = [f2](glm::vec2 &v) {
v = from_c(f2(v.x, v.y));
};
auto warped = from_c(cs)->Warp(warp);
return to_c(new (mem) CrossSection(warped));
}
ManifoldCrossSection *manifold_cross_section_simplify(void *mem,
ManifoldCrossSection *cs,
double epsilon) {
auto simplified = from_c(cs)->Simplify(epsilon);
return to_c(new (mem) CrossSection(simplified));
}
ManifoldCrossSection *manifold_cross_section_offset(
void *mem, ManifoldCrossSection *cs, double delta, ManifoldJoinType jt,
double miter_limit, int circular_segments) {
auto offset =
from_c(cs)->Offset(delta, from_c(jt), miter_limit, circular_segments);
return to_c(new (mem) CrossSection(offset));
}
double manifold_cross_section_area(ManifoldCrossSection *cs) {
return from_c(cs)->Area();
}
int manifold_cross_section_num_vert(ManifoldCrossSection *cs) {
return from_c(cs)->NumVert();
}
int manifold_cross_section_num_contour(ManifoldCrossSection *cs) {
return from_c(cs)->NumContour();
}
int manifold_cross_section_is_empty(ManifoldCrossSection *cs) {
return from_c(cs)->IsEmpty();
}
ManifoldRect *manifold_cross_section_bounds(void *mem,
ManifoldCrossSection *cs) {
auto rect = from_c(cs)->Bounds();
return to_c(new (mem) Rect(rect));
}
ManifoldPolygons *manifold_cross_section_to_polygons(void *mem,
ManifoldCrossSection *cs) {
auto ps = from_c(cs)->ToPolygons();
return to_c(new (mem) Polygons(ps));
}
ManifoldCrossSection *manifold_cross_section_compose(
void *mem, ManifoldCrossSectionVec *csv) {
auto cs = CrossSection::Compose(*from_c(csv));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSectionVec *manifold_cross_section_decompose(
void *mem, ManifoldCrossSection *cs) {
auto comps = from_c(cs)->Decompose();
return to_c(new (mem) CrossSectionVec(comps));
}
+81
View File
@@ -0,0 +1,81 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <cstring>
#include <vector>
#include "cross_section.h"
#include "manifold.h"
#include "public.h"
#include "sdf.h"
#include "types.h"
using namespace manifold;
using ManifoldVec = std::vector<Manifold>;
using CrossSectionVec = std::vector<CrossSection>;
ManifoldManifold *to_c(manifold::Manifold *m);
ManifoldManifoldVec *to_c(ManifoldVec *ms);
ManifoldCrossSection *to_c(manifold::CrossSection *cs);
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv);
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *p);
ManifoldPolygons *to_c(manifold::Polygons *ps);
ManifoldMesh *to_c(manifold::Mesh *m);
ManifoldMeshGL *to_c(manifold::MeshGL *m);
ManifoldBox *to_c(manifold::Box *m);
ManifoldRect *to_c(manifold::Rect *m);
ManifoldError to_c(manifold::Manifold::Error error);
ManifoldVec2 to_c(glm::vec2 v);
ManifoldVec3 to_c(glm::vec3 v);
ManifoldIVec3 to_c(glm::ivec3 v);
ManifoldProperties to_c(manifold::Properties p);
const manifold::Manifold *from_c(ManifoldManifold *m);
ManifoldVec *from_c(ManifoldManifoldVec *ms);
const manifold::CrossSection *from_c(ManifoldCrossSection *cs);
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv);
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m);
const manifold::Polygons *from_c(ManifoldPolygons *m);
const manifold::Mesh *from_c(ManifoldMesh *m);
const manifold::MeshGL *from_c(ManifoldMeshGL *m);
OpType from_c(ManifoldOpType op);
CrossSection::FillRule from_c(ManifoldFillRule fillrule);
CrossSection::JoinType from_c(ManifoldJoinType jt);
const manifold::Box *from_c(ManifoldBox *m);
const manifold::Rect *from_c(ManifoldRect *r);
glm::vec2 from_c(ManifoldVec2 v);
glm::vec3 from_c(ManifoldVec3 v);
glm::ivec3 from_c(ManifoldIVec3 v);
glm::vec4 from_c(ManifoldVec4 v);
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length);
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length);
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length);
template <typename T>
std::vector<T> vector_of_array(T *ts, size_t length) {
auto vec = std::vector<T>();
for (int i = 0; i < length; ++i) {
vec.push_back(ts[i]);
}
return vec;
}
template <typename T>
T *copy_data(void *mem, std::vector<T> v) {
T *ts = reinterpret_cast<T *>(mem);
memcpy(ts, v.data(), sizeof(T) * v.size());
return ts;
}
+444
View File
@@ -0,0 +1,444 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stddef.h>
#include <stdint.h>
#include "types.h"
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#endif
// Polygons
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
size_t length);
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
size_t length);
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p);
size_t manifold_polygons_length(ManifoldPolygons *ps);
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx);
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
int idx);
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
ManifoldPolygons *ps,
int idx);
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
int pt_idx);
// Mesh Construction
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
size_t n_props, uint32_t *tri_verts,
size_t n_tris);
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
size_t n_verts, size_t n_props,
uint32_t *tri_verts, size_t n_tris,
float *halfedge_tangent);
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m);
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m);
ManifoldMeshGL *manifold_meshgl_merge(void *mem, ManifoldMeshGL *m);
// SDF
// By default, the execution policy (sequential or parallel) of
// manifold_level_set will be chosen automatically depending on the size of the
// job and whether Manifold has been compiled with a PAR backend. If you are
// using these bindings from a language that has a runtime lock preventing the
// parallel execution of closures, then you should use manifold_level_set_seq to
// force sequential execution.
ManifoldMeshGL *manifold_level_set(void *mem,
float (*sdf)(float, float, float, void *),
ManifoldBox *bounds, float edge_length,
float level, void *ctx);
ManifoldMeshGL *manifold_level_set_seq(
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
float edge_length, float level, void *ctx);
// Manifold Vectors
ManifoldManifoldVec *manifold_manifold_empty_vec(void *mem);
ManifoldManifoldVec *manifold_manifold_vec(void *mem, size_t sz);
void manifold_manifold_vec_reserve(ManifoldManifoldVec *ms, size_t sz);
size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms);
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
int idx);
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
ManifoldManifold *m);
void manifold_manifold_vec_push_back(ManifoldManifoldVec *ms,
ManifoldManifold *m);
// Manifold Booleans
ManifoldManifold *manifold_boolean(void *mem, ManifoldManifold *a,
ManifoldManifold *b, ManifoldOpType op);
ManifoldManifold *manifold_batch_boolean(void *mem, ManifoldManifoldVec *ms,
ManifoldOpType op);
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
ManifoldManifold *b);
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
ManifoldManifold *b);
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
ManifoldManifold *b);
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
ManifoldManifold *a, ManifoldManifold *b);
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
ManifoldManifold *m,
float normal_x, float normal_y,
float normal_z, float offset);
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
float normal_x, float normal_y,
float normal_z, float offset);
// 3D to 2D
ManifoldCrossSection *manifold_slice(void *mem, ManifoldManifold *m,
float height);
ManifoldCrossSection *manifold_project(void *mem, ManifoldManifold *m);
// Convex Hulls
ManifoldManifold *manifold_hull(void *mem, ManifoldManifold *m);
ManifoldManifold *manifold_batch_hull(void *mem, ManifoldManifoldVec *ms);
ManifoldManifold *manifold_hull_pts(void *mem, ManifoldVec3 *ps, size_t length);
// Manifold Transformations
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
float y, float z);
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
float y, float z);
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
float y, float z);
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
float y1, float z1, float x2, float y2,
float z2, float x3, float y3, float z3,
float x4, float y4, float z4);
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
float ny, float nz);
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
ManifoldVec3 (*fun)(float, float, float,
void *),
void *ctx);
ManifoldManifold *manifold_smooth_by_normals(void *mem, ManifoldManifold *m,
int normalIdx);
ManifoldManifold *manifold_smooth_out(void *mem, ManifoldManifold *m,
float minSharpAngle, float minSmoothness);
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine);
ManifoldManifold *manifold_refine_to_length(void *mem, ManifoldManifold *m,
float length);
// Manifold Shapes / Constructors
ManifoldManifold *manifold_empty(void *mem);
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m);
ManifoldManifold *manifold_tetrahedron(void *mem);
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
int center);
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
float radius_high, int circular_segments,
int center);
ManifoldManifold *manifold_sphere(void *mem, float radius,
int circular_segments);
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh);
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
int *half_edges, float *smoothness,
int n_idxs);
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
float height, int slices,
float twist_degrees, float scale_x,
float scale_y);
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
int circular_segments);
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms);
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m);
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m);
// Manifold Info
int manifold_is_empty(ManifoldManifold *m);
ManifoldError manifold_status(ManifoldManifold *m);
int manifold_num_vert(ManifoldManifold *m);
int manifold_num_edge(ManifoldManifold *m);
int manifold_num_tri(ManifoldManifold *m);
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m);
float manifold_precision(ManifoldManifold *m);
int manifold_genus(ManifoldManifold *m);
ManifoldProperties manifold_get_properties(ManifoldManifold *m);
int manifold_get_circular_segments(float radius);
int manifold_original_id(ManifoldManifold *m);
uint32_t manifold_reserve_ids(uint32_t n);
ManifoldManifold *manifold_set_properties(
void *mem, ManifoldManifold *m, int num_prop,
void (*fun)(float *new_prop, ManifoldVec3 position, const float *old_prop,
void *ctx),
void *ctx);
ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
int gaussian_idx, int mean_idx);
float manifold_min_gap(ManifoldManifold *m, ManifoldManifold *other,
float searchLength);
ManifoldManifold *manifold_calculate_normals(void *mem, ManifoldManifold *m,
int normal_idx,
int min_sharp_angle);
// CrossSection Shapes/Constructors
ManifoldCrossSection *manifold_cross_section_empty(void *mem);
ManifoldCrossSection *manifold_cross_section_copy(void *mem,
ManifoldCrossSection *cs);
ManifoldCrossSection *manifold_cross_section_of_simple_polygon(
void *mem, ManifoldSimplePolygon *p, ManifoldFillRule fr);
ManifoldCrossSection *manifold_cross_section_of_polygons(void *mem,
ManifoldPolygons *p,
ManifoldFillRule fr);
ManifoldCrossSection *manifold_cross_section_square(void *mem, float x, float y,
int center);
ManifoldCrossSection *manifold_cross_section_circle(void *mem, float radius,
int circular_segments);
ManifoldCrossSection *manifold_cross_section_compose(
void *mem, ManifoldCrossSectionVec *csv);
ManifoldCrossSectionVec *manifold_cross_section_decompose(
void *mem, ManifoldCrossSection *cs);
// CrossSection Vectors
ManifoldCrossSectionVec *manifold_cross_section_empty_vec(void *mem);
ManifoldCrossSectionVec *manifold_cross_section_vec(void *mem, size_t sz);
void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec *csv,
size_t sz);
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec *csv);
ManifoldCrossSection *manifold_cross_section_vec_get(
void *mem, ManifoldCrossSectionVec *csv, int idx);
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, int idx,
ManifoldCrossSection *cs);
void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec *csv,
ManifoldCrossSection *cs);
// CrossSection Booleans
ManifoldCrossSection *manifold_cross_section_boolean(void *mem,
ManifoldCrossSection *a,
ManifoldCrossSection *b,
ManifoldOpType op);
ManifoldCrossSection *manifold_cross_section_batch_boolean(
void *mem, ManifoldCrossSectionVec *csv, ManifoldOpType op);
ManifoldCrossSection *manifold_cross_section_union(void *mem,
ManifoldCrossSection *a,
ManifoldCrossSection *b);
ManifoldCrossSection *manifold_cross_section_difference(
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b);
ManifoldCrossSection *manifold_cross_section_intersection(
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b);
// CrossSection Convex Hulls
ManifoldCrossSection *manifold_cross_section_hull(void *mem,
ManifoldCrossSection *cs);
ManifoldCrossSection *manifold_cross_section_batch_hull(
void *mem, ManifoldCrossSectionVec *css);
ManifoldCrossSection *manifold_cross_section_hull_simple_polygon(
void *mem, ManifoldSimplePolygon *ps);
ManifoldCrossSection *manifold_cross_section_hull_polygons(
void *mem, ManifoldPolygons *ps);
// CrossSection Transformation
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
ManifoldCrossSection *cs,
float x, float y);
ManifoldCrossSection *manifold_cross_section_rotate(void *mem,
ManifoldCrossSection *cs,
float deg);
ManifoldCrossSection *manifold_cross_section_scale(void *mem,
ManifoldCrossSection *cs,
float x, float y);
ManifoldCrossSection *manifold_cross_section_mirror(void *mem,
ManifoldCrossSection *cs,
float ax_x, float ax_y);
ManifoldCrossSection *manifold_cross_section_transform(void *mem,
ManifoldCrossSection *cs,
float x1, float y1,
float x2, float y2,
float x3, float y3);
ManifoldCrossSection *manifold_cross_section_warp(
void *mem, ManifoldCrossSection *cs, ManifoldVec2 (*fun)(float, float));
ManifoldCrossSection *manifold_cross_section_warp_context(
void *mem, ManifoldCrossSection *cs,
ManifoldVec2 (*fun)(float, float, void *), void *ctx);
ManifoldCrossSection *manifold_cross_section_simplify(void *mem,
ManifoldCrossSection *cs,
double epsilon);
ManifoldCrossSection *manifold_cross_section_offset(
void *mem, ManifoldCrossSection *cs, double delta, ManifoldJoinType jt,
double miter_limit, int circular_segments);
// CrossSection Info
double manifold_cross_section_area(ManifoldCrossSection *cs);
int manifold_cross_section_num_vert(ManifoldCrossSection *cs);
int manifold_cross_section_num_contour(ManifoldCrossSection *cs);
int manifold_cross_section_is_empty(ManifoldCrossSection *cs);
ManifoldRect *manifold_cross_section_bounds(void *mem,
ManifoldCrossSection *cs);
ManifoldPolygons *manifold_cross_section_to_polygons(void *mem,
ManifoldCrossSection *cs);
// Rectangle
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2);
ManifoldVec2 manifold_rect_min(ManifoldRect *r);
ManifoldVec2 manifold_rect_max(ManifoldRect *r);
ManifoldVec2 manifold_rect_dimensions(ManifoldRect *r);
ManifoldVec2 manifold_rect_center(ManifoldRect *r);
float manifold_rect_scale(ManifoldRect *r);
int manifold_rect_contains_pt(ManifoldRect *r, float x, float y);
int manifold_rect_contains_rect(ManifoldRect *a, ManifoldRect *b);
void manifold_rect_include_pt(ManifoldRect *r, float x, float y);
ManifoldRect *manifold_rect_union(void *mem, ManifoldRect *a, ManifoldRect *b);
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, float x1,
float y1, float x2, float y2, float x3,
float y3);
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, float x,
float y);
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y);
int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r);
int manifold_rect_is_empty(ManifoldRect *r);
int manifold_rect_is_finite(ManifoldRect *r);
// Bounding Box
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
float y2, float z2);
ManifoldVec3 manifold_box_min(ManifoldBox *b);
ManifoldVec3 manifold_box_max(ManifoldBox *b);
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b);
ManifoldVec3 manifold_box_center(ManifoldBox *b);
float manifold_box_scale(ManifoldBox *b);
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z);
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b);
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z);
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b);
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
float y1, float z1, float x2, float y2,
float z2, float x3, float y3, float z3,
float x4, float y4, float z4);
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
float z);
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
float z);
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z);
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b);
int manifold_box_is_finite(ManifoldBox *b);
// Static Quality Globals
void manifold_set_min_circular_angle(float degrees);
void manifold_set_min_circular_edge_length(float length);
void manifold_set_circular_segments(int number);
// Manifold Mesh Extraction
int manifold_meshgl_num_prop(ManifoldMeshGL *m);
int manifold_meshgl_num_vert(ManifoldMeshGL *m);
int manifold_meshgl_num_tri(ManifoldMeshGL *m);
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m);
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m);
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m);
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m);
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m);
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m);
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m);
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m);
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m);
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m);
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m);
// memory size
size_t manifold_manifold_size();
size_t manifold_manifold_vec_size();
size_t manifold_cross_section_size();
size_t manifold_cross_section_vec_size();
size_t manifold_simple_polygon_size();
size_t manifold_polygons_size();
size_t manifold_manifold_pair_size();
size_t manifold_meshgl_size();
size_t manifold_box_size();
size_t manifold_rect_size();
size_t manifold_curvature_size();
// destruction
void manifold_destruct_manifold(ManifoldManifold *m);
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms);
void manifold_destruct_cross_section(ManifoldCrossSection *m);
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv);
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p);
void manifold_destruct_polygons(ManifoldPolygons *p);
void manifold_destruct_meshgl(ManifoldMeshGL *m);
void manifold_destruct_box(ManifoldBox *b);
void manifold_destruct_rect(ManifoldRect *b);
// pointer free + destruction
void manifold_delete_manifold(ManifoldManifold *m);
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms);
void manifold_delete_cross_section(ManifoldCrossSection *cs);
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv);
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p);
void manifold_delete_polygons(ManifoldPolygons *p);
void manifold_delete_meshgl(ManifoldMeshGL *m);
void manifold_delete_box(ManifoldBox *b);
void manifold_delete_rect(ManifoldRect *b);
// MeshIO / Export
#ifdef MANIFOLD_EXPORT
ManifoldMaterial *manifold_material(void *mem);
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness);
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness);
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color);
void manifold_material_set_vert_color(ManifoldMaterial *mat,
ManifoldVec4 *vert_color, size_t n_vert);
ManifoldExportOptions *manifold_export_options(void *mem);
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
int faceted);
void manifold_export_options_set_material(ManifoldExportOptions *options,
ManifoldMaterial *mat);
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
ManifoldExportOptions *options);
ManifoldMeshGL *manifold_import_meshgl(void *mem, const char *filename,
int force_cleanup);
size_t manifold_material_size();
size_t manifold_export_options_size();
void manifold_destruct_material(ManifoldMaterial *m);
void manifold_destruct_export_options(ManifoldExportOptions *options);
void manifold_delete_material(ManifoldMaterial *m);
void manifold_delete_export_options(ManifoldExportOptions *options);
#endif
#ifdef __cplusplus
}
#endif
+106
View File
@@ -0,0 +1,106 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stddef.h>
// opaque pointers
typedef struct ManifoldManifold ManifoldManifold;
typedef struct ManifoldManifoldVec ManifoldManifoldVec;
typedef struct ManifoldCrossSection ManifoldCrossSection;
typedef struct ManifoldCrossSectionVec ManifoldCrossSectionVec;
typedef struct ManifoldSimplePolygon ManifoldSimplePolygon;
typedef struct ManifoldPolygons ManifoldPolygons;
typedef struct ManifoldMesh ManifoldMesh;
typedef struct ManifoldMeshGL ManifoldMeshGL;
typedef struct ManifoldBox ManifoldBox;
typedef struct ManifoldRect ManifoldRect;
#ifdef MANIFOLD_EXPORT
typedef struct ManifoldMaterial ManifoldMaterial;
typedef struct ManifoldExportOptions ManifoldExportOptions;
#endif
// structs
typedef struct ManifoldManifoldPair {
ManifoldManifold* first;
ManifoldManifold* second;
} ManifoldManifoldPair;
typedef struct ManifoldVec2 {
float x;
float y;
} ManifoldVec2;
typedef struct ManifoldVec3 {
float x;
float y;
float z;
} ManifoldVec3;
typedef struct ManifoldIVec3 {
int x;
int y;
int z;
} ManifoldIVec3;
typedef struct ManifoldVec4 {
float x;
float y;
float z;
float w;
} ManifoldVec4;
typedef struct ManifoldProperties {
float surface_area;
float volume;
} ManifoldProperties;
// enums
typedef enum ManifoldOpType {
MANIFOLD_ADD,
MANIFOLD_SUBTRACT,
MANIFOLD_INTERSECT
} ManifoldOpType;
typedef enum ManifoldError {
MANIFOLD_NO_ERROR,
MANIFOLD_NON_FINITE_VERTEX,
MANIFOLD_NOT_MANIFOLD,
MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS,
MANIFOLD_PROPERTIES_WRONG_LENGTH,
MANIFOLD_MISSING_POSITION_PROPERTIES,
MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS,
MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS,
MANIFOLD_TRANSFORM_WRONG_LENGTH,
MANIFOLD_RUN_INDEX_WRONG_LENGTH,
MANIFOLD_FACE_ID_WRONG_LENGTH,
MANIFOLD_INVALID_CONSTRUCTION,
} ManifoldError;
typedef enum ManifoldFillRule {
MANIFOLD_FILL_RULE_EVEN_ODD,
MANIFOLD_FILL_RULE_NON_ZERO,
MANIFOLD_FILL_RULE_POSITIVE,
MANIFOLD_FILL_RULE_NEGATIVE
} ManifoldFillRule;
typedef enum ManifoldJoinType {
MANIFOLD_JOIN_TYPE_SQUARE,
MANIFOLD_JOIN_TYPE_ROUND,
MANIFOLD_JOIN_TYPE_MITER,
} ManifoldJoinType;
+642
View File
@@ -0,0 +1,642 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <conv.h>
#include <cross_section.h>
#include <manifold.h>
#include <manifoldc.h>
#include <public.h>
#include <sdf.h>
#include <vector>
#include "box.cpp"
#include "cross.cpp"
#include "include/conv.h"
#include "include/types.h"
#include "rect.cpp"
#include "types.h"
#ifdef MANIFOLD_EXPORT
#include "meshio.cpp"
#endif
using namespace manifold;
namespace {
ManifoldMeshGL *level_set(void *mem,
float (*sdf_context)(float, float, float, void *),
ManifoldBox *bounds, float edge_length, float level,
bool seq, void *ctx) {
// Bind function with context argument to one without
using namespace std::placeholders;
std::function<float(float, float, float)> sdf =
std::bind(sdf_context, _1, _2, _3, ctx);
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
return (sdf(v.x, v.y, v.z));
};
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, !seq);
return to_c(new (mem) MeshGL(mesh));
}
} // namespace
#ifdef __cplusplus
extern "C" {
#endif
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
size_t length) {
auto vec = new (mem) std::vector<glm::vec2>;
for (int i = 0; i < length; ++i) {
vec->push_back({ps[i].x, ps[i].y});
}
return to_c(vec);
}
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
size_t length) {
auto vec = new (mem) std::vector<SimplePolygon>;
auto polys = reinterpret_cast<SimplePolygon **>(ps);
for (int i = 0; i < length; ++i) {
vec->push_back(*polys[i]);
}
return to_c(vec);
}
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p) {
return from_c(p)->size();
}
size_t manifold_polygons_length(ManifoldPolygons *ps) {
return from_c(ps)->size();
}
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx) {
return (*from_c(ps))[idx].size();
}
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
int idx) {
return to_c((*from_c(p))[idx]);
}
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
ManifoldPolygons *ps,
int idx) {
auto sp = (*from_c(ps))[idx];
return to_c(new (mem) SimplePolygon(sp));
}
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
int pt_idx) {
return to_c((*from_c(ps))[simple_idx][pt_idx]);
}
ManifoldManifoldVec *manifold_manifold_empty_vec(void *mem) {
return to_c(new (mem) ManifoldVec());
}
ManifoldManifoldVec *manifold_manifold_vec(void *mem, size_t sz) {
return to_c(new (mem) ManifoldVec(sz));
}
void manifold_manifold_vec_reserve(ManifoldManifoldVec *ms, size_t sz) {
from_c(ms)->reserve(sz);
}
size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms) {
return from_c(ms)->size();
}
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
int idx) {
auto m = (*from_c(ms))[idx];
return to_c(new (mem) Manifold(m));
}
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
ManifoldManifold *m) {
(*from_c(ms))[idx] = *from_c(m);
}
void manifold_manifold_vec_push_back(ManifoldManifoldVec *ms,
ManifoldManifold *m) {
return from_c(ms)->push_back(*from_c(m));
}
ManifoldManifold *manifold_boolean(void *mem, ManifoldManifold *a,
ManifoldManifold *b, ManifoldOpType op) {
auto m = from_c(a)->Boolean(*from_c(b), from_c(op));
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_batch_boolean(void *mem, ManifoldManifoldVec *ms,
ManifoldOpType op) {
auto m = Manifold::BatchBoolean(*from_c(ms), from_c(op));
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
ManifoldManifold *b) {
auto m = (*from_c(a)) + (*from_c(b));
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
ManifoldManifold *b) {
auto m = (*from_c(a)) - (*from_c(b));
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
ManifoldManifold *b) {
auto m = (*from_c(a)) ^ (*from_c(b));
return to_c(new (mem) Manifold(m));
}
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
ManifoldManifold *a, ManifoldManifold *b) {
auto pair = from_c(a)->Split(*from_c(b));
auto first = new (mem_first) Manifold(pair.first);
auto second = new (mem_second) Manifold(pair.second);
return {to_c(first), to_c(second)};
}
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
ManifoldManifold *m,
float normal_x, float normal_y,
float normal_z, float offset) {
auto normal = glm::vec3(normal_x, normal_y, normal_z);
auto pair = from_c(m)->SplitByPlane(normal, offset);
auto first = new (mem_first) Manifold(pair.first);
auto second = new (mem_second) Manifold(pair.second);
return {to_c(first), to_c(second)};
}
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
float normal_x, float normal_y,
float normal_z, float offset) {
auto normal = glm::vec3(normal_x, normal_y, normal_z);
auto trimmed = from_c(m)->TrimByPlane(normal, offset);
return to_c(new (mem) Manifold(trimmed));
}
ManifoldCrossSection *manifold_slice(void *mem, ManifoldManifold *m,
float height) {
auto poly = from_c(m)->Slice(height);
return to_c(new (mem) CrossSection(poly));
}
ManifoldCrossSection *manifold_project(void *mem, ManifoldManifold *m) {
auto poly = from_c(m)->Project();
return to_c(new (mem) CrossSection(poly));
}
ManifoldManifold *manifold_hull(void *mem, ManifoldManifold *m) {
auto hulled = from_c(m)->Hull();
return to_c(new (mem) Manifold(hulled));
}
ManifoldManifold *manifold_batch_hull(void *mem, ManifoldManifoldVec *ms) {
auto hulled = Manifold::Hull(*from_c(ms));
return to_c(new (mem) Manifold(hulled));
}
ManifoldManifold *manifold_hull_pts(void *mem, ManifoldVec3 *ps,
size_t length) {
std::vector<glm::vec3> vec(length);
for (int i = 0; i < length; ++i) {
vec[i] = {ps[i].x, ps[i].y, ps[i].z};
}
auto hulled = Manifold::Hull(vec);
return to_c(new (mem) Manifold(hulled));
}
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
float y, float z) {
auto v = glm::vec3(x, y, z);
auto translated = from_c(m)->Translate(v);
return to_c(new (mem) Manifold(translated));
}
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
float y, float z) {
auto rotated = from_c(m)->Rotate(x, y, z);
return to_c(new (mem) Manifold(rotated));
}
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
float y, float z) {
auto s = glm::vec3(x, y, z);
auto scaled = from_c(m)->Scale(s);
return to_c(new (mem) Manifold(scaled));
}
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
float y1, float z1, float x2, float y2,
float z2, float x3, float y3, float z3,
float x4, float y4, float z4) {
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
auto transformed = from_c(m)->Transform(mat);
return to_c(new (mem) Manifold(transformed));
}
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
float ny, float nz) {
auto mirrored = from_c(m)->Mirror({nx, ny, nz});
return to_c(new (mem) Manifold(mirrored));
}
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
ManifoldVec3 (*fun)(float, float, float,
void *),
void *ctx) {
// Bind function with context argument to one without
using namespace std::placeholders;
std::function<ManifoldVec3(float, float, float)> f3 =
std::bind(fun, _1, _2, _3, ctx);
std::function<void(glm::vec3 & v)> warp = [f3](glm::vec3 &v) {
v = from_c(f3(v.x, v.y, v.z));
};
auto warped = from_c(m)->Warp(warp);
return to_c(new (mem) Manifold(warped));
}
ManifoldMeshGL *manifold_level_set(void *mem,
float (*sdf)(float, float, float, void *),
ManifoldBox *bounds, float edge_length,
float level, void *ctx) {
return level_set(mem, sdf, bounds, edge_length, level, false, ctx);
}
ManifoldMeshGL *manifold_level_set_seq(
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
float edge_length, float level, void *ctx) {
return level_set(mem, sdf, bounds, edge_length, level, true, ctx);
}
ManifoldManifold *manifold_smooth_by_normals(void *mem, ManifoldManifold *m,
int normalIdx) {
auto smoothed = from_c(m)->SmoothByNormals(normalIdx);
return to_c(new (mem) Manifold(smoothed));
}
ManifoldManifold *manifold_smooth_out(void *mem, ManifoldManifold *m,
float minSharpAngle,
float minSmoothness) {
auto smoothed = from_c(m)->SmoothOut(minSharpAngle, minSmoothness);
return to_c(new (mem) Manifold(smoothed));
}
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine) {
auto refined = from_c(m)->Refine(refine);
return to_c(new (mem) Manifold(refined));
}
ManifoldManifold *manifold_refine_to_length(void *mem, ManifoldManifold *m,
float length) {
auto refined = from_c(m)->RefineToLength(length);
return to_c(new (mem) Manifold(refined));
}
ManifoldManifold *manifold_empty(void *mem) {
return to_c(new (mem) Manifold());
}
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m) {
return to_c(new (mem) Manifold(*from_c(m)));
}
ManifoldManifold *manifold_tetrahedron(void *mem) {
auto m = Manifold::Tetrahedron();
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
int center) {
auto size = glm::vec3(x, y, z);
auto m = Manifold::Cube(size, center);
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
float radius_high, int circular_segments,
int center) {
auto m = Manifold::Cylinder(height, radius_low, radius_high,
circular_segments, center);
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_sphere(void *mem, float radius,
int circular_segments) {
auto m = Manifold::Sphere(radius, circular_segments);
return to_c(new (mem) Manifold(m));
}
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
size_t n_props, uint32_t *tri_verts,
size_t n_tris) {
auto mesh = new (mem) MeshGL();
mesh->numProp = n_props;
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
return to_c(mesh);
}
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
size_t n_verts, size_t n_props,
uint32_t *tri_verts, size_t n_tris,
float *halfedge_tangent) {
auto mesh = new (mem) MeshGL();
mesh->numProp = n_props;
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
mesh->halfedgeTangent = vector_of_array(halfedge_tangent, n_tris * 3 * 4);
return to_c(mesh);
}
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
int *half_edges, float *smoothness,
int n_edges) {
auto smooth = std::vector<Smoothness>();
for (int i = 0; i < n_edges; ++i) {
smooth.push_back({half_edges[i], smoothness[i]});
}
auto m = Manifold::Smooth(*from_c(mesh), smooth);
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh) {
auto m = Manifold(*from_c(mesh));
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
float height, int slices,
float twist_degrees, float scale_x,
float scale_y) {
auto scale = glm::vec2(scale_x, scale_y);
auto m = Manifold::Extrude(*from_c(cs), height, slices, twist_degrees, scale);
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
int circular_segments) {
auto m = Manifold::Revolve(*from_c(cs), circular_segments);
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms) {
auto composed = Manifold::Compose(*from_c(ms));
return to_c(new (mem) Manifold(composed));
}
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m) {
auto comps = from_c(m)->Decompose();
return to_c(new (mem) std::vector<Manifold>(comps));
}
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m) {
auto mesh = from_c(m)->GetMeshGL();
return to_c(new (mem) MeshGL(mesh));
}
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m) {
return to_c(new (mem) MeshGL(*from_c(m)));
}
ManifoldMeshGL *manifold_meshgl_merge(void *mem, ManifoldMeshGL *m) {
auto duplicate = new (mem) MeshGL(*from_c(m));
if (duplicate->Merge()) {
return to_c(duplicate);
}
delete duplicate;
return m;
}
int manifold_meshgl_num_prop(ManifoldMeshGL *m) { return from_c(m)->numProp; }
int manifold_meshgl_num_vert(ManifoldMeshGL *m) { return from_c(m)->NumVert(); }
int manifold_meshgl_num_tri(ManifoldMeshGL *m) { return from_c(m)->NumTri(); }
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m) {
return from_c(m)->vertProperties.size();
}
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m) {
return from_c(m)->triVerts.size();
}
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m) {
return from_c(m)->mergeFromVert.size();
}
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m) {
return from_c(m)->runIndex.size();
}
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m) {
return from_c(m)->runOriginalID.size();
}
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m) {
return from_c(m)->runTransform.size();
}
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m) {
return from_c(m)->faceID.size();
}
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m) {
return from_c(m)->halfedgeTangent.size();
}
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->vertProperties);
}
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->triVerts);
}
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->mergeFromVert);
}
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->mergeToVert);
}
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->runIndex);
}
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->runOriginalID);
}
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->runTransform);
}
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->faceID);
}
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->halfedgeTangent);
}
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m) {
auto orig = from_c(m)->AsOriginal();
return to_c(new (mem) Manifold(orig));
}
int manifold_original_id(ManifoldManifold *m) {
return from_c(m)->OriginalID();
}
int manifold_is_empty(ManifoldManifold *m) { return from_c(m)->IsEmpty(); }
ManifoldError manifold_status(ManifoldManifold *m) {
auto error = from_c(m)->Status();
return to_c(error);
}
int manifold_num_vert(ManifoldManifold *m) { return from_c(m)->NumVert(); }
int manifold_num_edge(ManifoldManifold *m) { return from_c(m)->NumEdge(); }
int manifold_num_tri(ManifoldManifold *m) { return from_c(m)->NumTri(); }
int manifold_genus(ManifoldManifold *m) { return from_c(m)->Genus(); }
ManifoldProperties manifold_get_properties(ManifoldManifold *m) {
return to_c(from_c(m)->GetProperties());
}
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m) {
auto box = from_c(m)->BoundingBox();
return to_c(new (mem) Box(box));
}
float manifold_precision(ManifoldManifold *m) { return from_c(m)->Precision(); }
uint32_t manifold_reserve_ids(uint32_t n) { return Manifold::ReserveIDs(n); }
ManifoldManifold *manifold_set_properties(
void *mem, ManifoldManifold *m, int num_prop,
void (*fun)(float *new_prop, ManifoldVec3 position, const float *old_prop,
void *ctx),
void *ctx) {
// Bind function with context argument to one without
using namespace std::placeholders;
std::function<void(float *, ManifoldVec3, const float *)> f3 =
std::bind(fun, _1, _2, _3, ctx);
std::function<void(float *, glm::vec3, const float *)> f =
[f3](float *new_prop, glm::vec3 v, const float *old_prop) {
return (f3(new_prop, to_c(v), old_prop));
};
auto man = from_c(m)->SetProperties(num_prop, f);
return to_c(new (mem) Manifold(man));
};
ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
int gaussian_idx, int mean_idx) {
auto man = from_c(m)->CalculateCurvature(gaussian_idx, mean_idx);
return to_c(new (mem) Manifold(man));
}
float manifold_min_gap(ManifoldManifold *m, ManifoldManifold *other,
float searchLength) {
return from_c(m)->MinGap(*from_c(other), searchLength);
}
ManifoldManifold *manifold_calculate_normals(void *mem, ManifoldManifold *m,
int normal_idx,
int min_sharp_angle) {
auto man = from_c(m)->CalculateNormals(normal_idx, min_sharp_angle);
return to_c(new (mem) Manifold(man));
}
// Static Quality Globals
void manifold_set_min_circular_angle(float degrees) {
Quality::SetMinCircularAngle(degrees);
}
void manifold_set_min_circular_edge_length(float length) {
Quality::SetMinCircularEdgeLength(length);
}
void manifold_set_circular_segments(int number) {
Quality::SetCircularSegments(number);
}
int manifold_get_circular_segments(float radius) {
return Quality::GetCircularSegments(radius);
}
// memory size
size_t manifold_cross_section_size() { return sizeof(CrossSection); }
size_t manifold_cross_section_vec_size() {
return sizeof(std::vector<CrossSection>);
}
size_t manifold_simple_polygon_size() { return sizeof(SimplePolygon); }
size_t manifold_polygons_size() { return sizeof(Polygons); }
size_t manifold_manifold_size() { return sizeof(Manifold); }
size_t manifold_manifold_vec_size() { return sizeof(std::vector<Manifold>); }
size_t manifold_manifold_pair_size() { return sizeof(ManifoldManifoldPair); }
size_t manifold_meshgl_size() { return sizeof(MeshGL); }
size_t manifold_box_size() { return sizeof(Box); }
size_t manifold_rect_size() { return sizeof(Rect); }
// pointer free + destruction
void manifold_delete_cross_section(ManifoldCrossSection *c) {
delete from_c(c);
}
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv) {
delete from_c(csv);
}
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p) {
delete from_c(p);
}
void manifold_delete_polygons(ManifoldPolygons *p) { delete from_c(p); }
void manifold_delete_manifold(ManifoldManifold *m) { delete from_c(m); }
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms) {
delete from_c(ms);
}
void manifold_delete_meshgl(ManifoldMeshGL *m) { delete from_c(m); }
void manifold_delete_box(ManifoldBox *b) { delete from_c(b); }
void manifold_delete_rect(ManifoldRect *r) { delete from_c(r); }
// destruction
void manifold_destruct_cross_section(ManifoldCrossSection *cs) {
from_c(cs)->~CrossSection();
}
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv) {
from_c(csv)->~CrossSectionVec();
}
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p) {
from_c(p)->~SimplePolygon();
}
void manifold_destruct_polygons(ManifoldPolygons *p) { from_c(p)->~Polygons(); }
void manifold_destruct_manifold(ManifoldManifold *m) { from_c(m)->~Manifold(); }
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms) {
from_c(ms)->~ManifoldVec();
}
void manifold_destruct_meshgl(ManifoldMeshGL *m) { from_c(m)->~MeshGL(); }
void manifold_destruct_box(ManifoldBox *b) { from_c(b)->~Box(); }
void manifold_destruct_rect(ManifoldRect *r) { from_c(r)->~Rect(); }
#ifdef __cplusplus
}
#endif
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "meshIO.h"
#include "conv.h"
#include "types.h"
// C <-> C++ conversions
ManifoldMaterial *to_c(manifold::Material *m) {
return reinterpret_cast<ManifoldMaterial *>(m);
}
ManifoldExportOptions *to_c(manifold::ExportOptions *m) {
return reinterpret_cast<ManifoldExportOptions *>(m);
}
manifold::Material *from_c(ManifoldMaterial *mat) {
return reinterpret_cast<manifold::Material *>(mat);
}
manifold::ExportOptions *from_c(ManifoldExportOptions *options) {
return reinterpret_cast<manifold::ExportOptions *>(options);
}
#ifdef __cplusplus
extern "C" {
#endif
// material
ManifoldMaterial *manifold_material(void *mem) {
return to_c(new (mem) manifold::Material());
}
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness) {
from_c(mat)->roughness = roughness;
}
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness) {
from_c(mat)->metalness = metalness;
}
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color) {
from_c(mat)->color = from_c(color);
}
void manifold_material_set_vert_color(ManifoldMaterial *mat,
ManifoldVec4 *vert_color, size_t n_vert) {
from_c(mat)->vertColor = vector_of_vec_array(vert_color, n_vert);
}
// export options
ManifoldExportOptions *manifold_export_options(void *mem) {
return to_c(new (mem) manifold::ExportOptions());
}
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
int faceted) {
from_c(options)->faceted = faceted;
}
void manifold_export_options_set_material(ManifoldExportOptions *options,
ManifoldMaterial *mat) {
from_c(options)->mat = *from_c(mat);
}
// mesh IO
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
ManifoldExportOptions *options) {
manifold::ExportMesh(std::string(filename), *from_c(mesh), *from_c(options));
}
ManifoldMeshGL *manifold_import_meshgl(void *mem, const char *filename,
int force_cleanup) {
auto m = manifold::ImportMesh(std::string(filename), force_cleanup);
return to_c(new (mem) manifold::MeshGL(m));
}
// memory size
size_t manifold_material_size() { return sizeof(manifold::Material); }
size_t manifold_export_options_size() {
return sizeof(manifold::ExportOptions);
}
// memory free + destruction
void manifold_delete_material(ManifoldMaterial *m) { delete (from_c(m)); }
void manifold_delete_export_options(ManifoldExportOptions *m) {
delete (from_c(m));
}
// destruction
void manifold_destruct_material(ManifoldMaterial *m) { from_c(m)->~Material(); }
void manifold_destruct_export_options(ManifoldExportOptions *m) {
from_c(m)->~ExportOptions();
}
#ifdef __cplusplus
}
#endif
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "conv.h"
#include "cross_section.h"
#include "manifold.h"
#include "public.h"
#include "types.h"
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2) {
auto p1 = glm::vec2(x1, y1);
auto p2 = glm::vec2(x2, y2);
auto rect = new (mem) Rect(p1, p2);
return to_c(rect);
}
ManifoldVec2 manifold_rect_min(ManifoldRect *r) {
return to_c((*from_c(r)).min);
}
ManifoldVec2 manifold_rect_max(ManifoldRect *r) {
return to_c((*from_c(r)).max);
}
ManifoldVec2 manifold_rect_dimensions(ManifoldRect *r) {
auto v = from_c(r)->Size();
return {v.x, v.y};
}
ManifoldVec2 manifold_rect_center(ManifoldRect *r) {
auto v = from_c(r)->Center();
return {v.x, v.y};
}
float manifold_rect_scale(ManifoldRect *r) { return from_c(r)->Scale(); }
int manifold_rect_contains_pt(ManifoldRect *r, float x, float y) {
auto rect = *from_c(r);
auto p = glm::vec2(x, y);
return rect.Contains(p);
}
int manifold_rect_contains_rect(ManifoldRect *a, ManifoldRect *b) {
auto outer = *from_c(a);
auto inner = *from_c(b);
return outer.Contains(inner);
}
void manifold_rect_include_pt(ManifoldRect *r, float x, float y) {
auto rect = *from_c(r);
auto p = glm::vec2(x, y);
rect.Union(p);
}
ManifoldRect *manifold_rect_union(void *mem, ManifoldRect *a, ManifoldRect *b) {
auto rect = from_c(a)->Union(*from_c(b));
return to_c(new (mem) Rect(rect));
}
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, float x1,
float y1, float x2, float y2, float x3,
float y3) {
auto mat = glm::mat3x2(x1, y1, x2, y2, x3, y3);
auto transformed = from_c(r)->Transform(mat);
return to_c(new (mem) Rect(transformed));
}
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, float x,
float y) {
auto rect = *from_c(r);
auto p = glm::vec2(x, y);
auto translated = (*from_c(r)) + p;
return to_c(new (mem) Rect(translated));
}
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y) {
auto rect = *from_c(r);
auto p = glm::vec2(x, y);
auto scaled = (*from_c(r)) * p;
return to_c(new (mem) Rect(scaled));
}
int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r) {
return from_c(a)->DoesOverlap(*from_c(r));
}
int manifold_rect_is_empty(ManifoldRect *r) { return from_c(r)->IsEmpty(); }
int manifold_rect_is_finite(ManifoldRect *r) { return from_c(r)->IsFinite(); }
+41 -7
View File
@@ -15,10 +15,44 @@
project(python)
add_subdirectory(third_party)
pybind11_add_module(pymanifold pymanifold.cpp)
target_link_libraries(pymanifold PRIVATE manifold)
target_compile_options(pymanifold PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(pymanifold PUBLIC cxx_std_14)
target_include_directories(pymanifold
PRIVATE ${PYBIND11_DIR}/include
)
nanobind_add_module(
manifold3d
NB_STATIC STABLE_ABI LTO
autogen_docstrings.inl
manifold3d.cpp)
target_link_libraries(manifold3d PRIVATE manifold sdf polygon)
target_compile_options(manifold3d PRIVATE ${MANIFOLD_FLAGS} -DMODULE_NAME=manifold3d)
target_compile_features(manifold3d PUBLIC cxx_std_17)
set_target_properties(manifold3d PROPERTIES OUTPUT_NAME "manifold3d")
message(Python_EXECUTABLE = ${Python_EXECUTABLE})
# ideally we should generate a dependency file from python...
set(DOCSTRING_DEPS
${CMAKE_CURRENT_SOURCE_DIR}/../../src/manifold/src/manifold.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/manifold/src/constructors.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/manifold/src/sort.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/cross_section/src/cross_section.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/polygon/src/polygon.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/utilities/include/public.h
)
add_custom_command(
OUTPUT autogen_docstrings.inl
DEPENDS ${DOCSTRING_DEPS}
COMMAND ${Python_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/gen_docs.py
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
target_include_directories(manifold3d PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
if(SKBUILD)
install(
TARGETS manifold3d
LIBRARY DESTINATION ${SKBUILD_PLATLIB_DIR}
COMPONENT bindings
)
else()
install(
TARGETS manifold3d
LIBRARY DESTINATION ${Python_SITEARCH}
COMPONENT bindings
)
endif()
+40
View File
@@ -0,0 +1,40 @@
# Python Bindings
## Autogenerated Doc-Strings
Doc-strings for the python API wrapper are generated by gen_docs.py
The script is run automatically during the build to keep python
doc strings fully up-to-date with c++ sources.
It scrapes documentation comments and c++ function signatures
from the manifold c++ sources, in order to generate a c++ header file
exposing the comments as string variables named by function names.
This allows python bindings to re-use the c++ comments directly.
Some snake-casing of params is applied for python use case.
---
When modifying the Manifold C++ sources, you may need to update
gen_docs.py. For example, top-level free functions are white-listed,
so if you add a new one, you will need to add it in gen_docs.py.
Similarly, the list of source files to parse is also white listed,
so if you define functions in new files that need python wrappers,
you will also need to up gen_docs.py.
To verify that python docs are correct after changes, you can
run the following commends from the manifold repo root:
```
pip install .
python -c 'import manifold3d; help(manifold3d)'
```
Alternateively you could generate stubs with roughly the same info
```
pip install nanobind-stubgen
pip install .
nanobind-stubgen manifold3d
```
It will emit some warnings and write a file `manifold3d.pyi`
which will show all the function signatures and docstrings.
+128
View File
@@ -0,0 +1,128 @@
from manifold3d import *
import numpy as np
def all_root_level():
set_min_circular_angle(10)
set_min_circular_edge_length(1)
set_circular_segments(22)
n = get_circular_segments(1)
assert n == 22
poly = [[0, 0], [1, 0], [1, 1]]
tris = triangulate([poly])
tris = triangulate([np.array(poly)])
def all_cross_section():
poly = [[0, 0], [1, 0], [1, 1]]
c = CrossSection([np.array(poly)])
c = CrossSection([poly])
c = CrossSection() + c
a = c.area()
c = CrossSection.batch_boolean(
[
CrossSection.circle(1),
CrossSection.square((3, 3)),
CrossSection.circle(3).translate((1, 1)),
],
OpType.Add,
)
c = CrossSection.batch_hull([c, c.translate((1, 0))])
b = c.bounds()
c = CrossSection.circle(1)
c = CrossSection.compose([c, c.translate((1, 0))])
cs = c.decompose()
m = c.extrude(1)
c = c.hull()
c = CrossSection.hull_points(poly)
c = CrossSection.hull_points(np.array(poly))
e = c.is_empty()
c = c.mirror((0, 1))
n = c.num_contour()
n = c.num_vert()
c = c.offset(1, JoinType.Round)
m = c.revolve()
c = c.rotate(90)
c = c.scale((2, 2))
c = c.simplify()
c = CrossSection.square((1, 1))
p = c.to_polygons()
c = c.transform([[1, 0, 0], [0, 1, 0]])
c = c.translate((1, 1))
c = c.warp(lambda p: (p[0] + 1, p[1] / 2))
c = c.warp_batch(lambda ps: ps * [1, 0.5] + [1, 0])
def all_manifold():
mesh = Manifold.sphere(1).to_mesh()
m = Manifold(mesh)
m = Manifold() + m
m = m.as_original()
m = Manifold.batch_boolean(
[
Manifold.cylinder(4, 1),
Manifold.cube((3, 2, 1)),
Manifold.cylinder(5, 3).translate((1, 1, 1)),
],
OpType.Add,
)
m = Manifold.batch_hull([m, m.translate((0, 0, 1))])
b = m.bounding_box()
m = m.calculate_curvature(4, 5)
m = m.calculate_normals(0)
m = m.smooth_by_normals(0)
m = Manifold.compose([m, m.translate((5, 0, 0))])
m = Manifold.cube((1, 1, 1))
m = Manifold.cylinder(1, 1)
ms = m.decompose()
m = Manifold.extrude(CrossSection.circle(1), 1)
m = Manifold.revolve(CrossSection.circle(1))
g = m.genus()
a = m.surface_area()
v = m.volume()
m = m.hull()
m = m.hull_points(mesh.vert_properties)
e = m.is_empty()
m = m.mirror((0, 0, 1))
n = m.num_edge()
n = m.num_prop()
n = m.num_prop_vert()
n = m.num_tri()
n = m.num_vert()
i = m.original_id()
p = m.precision()
c = m.project()
m = m.refine(2)
m = m.refine_to_length(0.1)
m = m.smooth_out()
i = Manifold.reserve_ids(1)
m = m.scale((1, 2, 3))
m = m.set_properties(3, lambda pos, prop: pos)
c = m.slice(0.5)
m = Manifold.smooth(mesh, [0], [0.5])
m = Manifold.sphere(1)
m, n = m.split(m.translate((1, 0, 0)))
m, n = m.split_by_plane((0, 0, 1), 0)
e = m.status()
m = Manifold.tetrahedron()
mesh = m.to_mesh()
ok = mesh.merge()
m = m.transform([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]])
m = m.translate((0, 0, 0))
m = m.trim_by_plane((0, 0, 1), 0)
m = m.warp(lambda p: (p[0] + 1, p[1] / 2, p[2] * 2))
m = m.warp_batch(lambda ps: ps * [1, 0.5, 2] + [1, 0, 0])
m = Manifold.cube()
m2 = Manifold.cube().translate([2, 0, 0])
d = m.min_gap(m2, 2)
def run():
all_root_level()
all_cross_section()
all_manifold()
return Manifold()
if __name__ == "__main__":
run()
+53 -31
View File
@@ -14,66 +14,88 @@
limitations under the License.
"""
from pymanifold import Manifold
from manifold3d import Manifold
# https://gist.github.com/deckar01/ef11def51de7e71d9f288c6e5819fdb7
INCHES = 25.4
brick_depth = (3 + 5/8) * INCHES
brick_height = (2 + 1/4) * INCHES
brick_length = (7 + 5/8) * INCHES
brick_depth = (3 + 5 / 8) * INCHES
brick_height = (2 + 1 / 4) * INCHES
brick_length = (7 + 5 / 8) * INCHES
mortar_gap = (3/8) * INCHES
mortar_gap = (3 / 8) * INCHES
def brick():
return Manifold.cube(brick_length, brick_depth, brick_height)
return Manifold.cube([brick_length, brick_depth, brick_height])
def halfbrick():
return Manifold.cube((brick_length - mortar_gap)/2, brick_depth, brick_height)
return Manifold.cube([(brick_length - mortar_gap) / 2, brick_depth, brick_height])
def row(length):
bricks = [brick().translate((brick_length + mortar_gap)*x, 0, 0)
for x in range(length)]
return Manifold(bricks)
bricks = [
brick().translate([(brick_length + mortar_gap) * x, 0, 0])
for x in range(length)
]
return sum(bricks, Manifold())
def wall(length, height, alternate=0):
bricks = [row(length).translate(
((z+alternate) % 2)*(brick_depth+mortar_gap), 0,
(brick_height+mortar_gap)*z) for z in range(height)]
return Manifold(bricks)
bricks = [
row(length).translate(
[
((z + alternate) % 2) * (brick_depth + mortar_gap),
0,
(brick_height + mortar_gap) * z,
]
)
for z in range(height)
]
return sum(bricks, Manifold())
def walls(length, width, height):
return Manifold([
wall(length, height),
wall(width, height, 1).rotate(0, 0, 90).translate(brick_depth, 0, 0),
wall(length, height, 1).translate(
0, (width)*(brick_length+mortar_gap), 0),
wall(width, height).rotate(0, 0, 90)
.translate((length+0.5)*(brick_length+mortar_gap) - mortar_gap, 0, 0),
])
return sum(
[
wall(length, height),
wall(width, height, 1).rotate([0, 0, 90]).translate([brick_depth, 0, 0]),
wall(length, height, 1).translate(
[0, (width) * (brick_length + mortar_gap), 0]
),
wall(width, height)
.rotate([0, 0, 90])
.translate(
[(length + 0.5) * (brick_length + mortar_gap) - mortar_gap, 0, 0]
),
],
Manifold(),
)
def floor(length, width):
results = [walls(length, width, 1)]
if length > 1 and width > 1:
results.append(floor(length-1, width-1).translate(brick_depth +
mortar_gap, brick_depth+mortar_gap, 0))
results.append(
floor(length - 1, width - 1).translate(
[brick_depth + mortar_gap, brick_depth + mortar_gap, 0]
)
)
if length == 1 and width > 1:
results.append(row(width-1).rotate(0, 0, 90))
results.append(row(width - 1).rotate(0, 0, 90))
if width == 1 and length > 1:
results.append(row(length-1).translate(2*(brick_depth +
mortar_gap), brick_depth+mortar_gap, 0))
results.append(halfbrick().translate(
brick_depth+mortar_gap, brick_depth+mortar_gap, 0))
return Manifold(results)
results.append(
row(length - 1).translate(
[2 * (brick_depth + mortar_gap), brick_depth + mortar_gap, 0]
)
)
results.append(
halfbrick().translate([brick_depth + mortar_gap, brick_depth + mortar_gap, 0])
)
return sum(results, Manifold())
def run(width=10, length=10, height=10):
return walls(length, width, height) + floor(length, width)
+11 -8
View File
@@ -13,18 +13,21 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
from pymanifold import Manifold
from manifold3d import Manifold
from functools import reduce
# https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355#file-cube-with-half-spheres-dents-scad
def run(n=5, overlap=True):
a = Manifold.cube(n, n, 0.5).translate(-0.5, -0.5, -0.5)
a = Manifold.cube([n, n, 0.5]).translate([-0.5, -0.5, -0.5])
spheres = [Manifold.sphere(0.45 if overlap else 0.55, 50).translate(i, j, 0)
for i in range(n) for j in range(n)]
spheres = reduce(lambda a, b: a + b, spheres)
return a - spheres
spheres = [
Manifold.sphere(0.45 if overlap else 0.55, 50).translate([i, j, 0])
for i in range(n)
for j in range(n)
]
# spheres = reduce(lambda a, b: a + b, spheres)
return a - sum(spheres, Manifold())
+46
View File
@@ -0,0 +1,46 @@
from manifold3d import CrossSection, Manifold
def run():
# create a polygon
polygon_points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
polygons_points = [polygon_points]
# create a cross-section
cross_section = CrossSection(polygons_points)
polygons = cross_section.to_polygons()
polygon = polygons[0]
if set([tuple(p) for p in polygon]) != set(polygon_points):
raise Exception(
f"polygon={polygon} differs from polygon_points={polygon_points}"
)
# extrude a polygon to create a manifold
extruded_polygon = Manifold.extrude(cross_section, 10.0)
eps = 0.001
observed_volume = extruded_polygon.volume()
expected_volume = 10.0
if abs(observed_volume - expected_volume) > eps:
raise Exception(
f"observed_volume={observed_volume} differs from expected_volume={expected_volume}"
)
observed_surface_area = extruded_polygon.surface_area()
expected_surface_area = 42.0
if abs(observed_surface_area - expected_surface_area) > eps:
raise Exception(
f"observed_surface_area={observed_surface_area} differs from expected_surface_area={expected_surface_area}"
)
# get bounding box from manifold
observed_bbox = extruded_polygon.bounding_box()
expected_bbox = (0.0, 0.0, 0.0, 1.0, 1.0, 10.0)
if observed_bbox != expected_bbox:
raise Exception(
f"observed_bbox={observed_bbox} differs from expected_bbox={expected_bbox}"
)
return extruded_polygon
if __name__ == "__main__":
run()
+59
View File
@@ -0,0 +1,59 @@
"""
Copyright 2023 The Manifold Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import math
import numpy as np
from manifold3d import Mesh, Manifold
def gyroid(x, y, z):
xi = x - math.pi / 4.0
yi = y - math.pi / 4.0
zi = z - math.pi / 4.0
return (
math.cos(xi) * math.sin(yi)
+ math.cos(yi) * math.sin(zi)
+ math.cos(zi) * math.sin(xi)
)
def gyroid_levelset(level, period, size, n):
return Manifold(
Mesh.level_set(
gyroid,
[-period, -period, -period, period, period, period],
period / n,
level,
)
).scale([size / period] * 3)
def rhombic_dodecahedron(size):
box = Manifold.cube(size * math.sqrt(2.0) * np.array([1, 1, 2]), True)
result = box.rotate([90, 45, 0]) ^ box.rotate([90, 45, 90])
return result ^ box.rotate([0, 0, 45])
def gyroid_module(size=20, n=15):
period = math.pi * 2.0
result = (
gyroid_levelset(-0.4, period, size, n) ^ rhombic_dodecahedron(size)
) - gyroid_levelset(0.4, period, size, n)
return result.rotate([-45, 0, 90]).translate([0, 0, size / math.sqrt(2.0)])
def run(size=20, n=15):
return gyroid_module(size, n)
File diff suppressed because it is too large Load Diff
+14 -7
View File
@@ -18,15 +18,15 @@ import pathlib
import sys
import importlib
import trimesh
import numpy as np
from time import time
if __name__ == "__main__":
current_file = pathlib.Path(__file__)
current_dir = current_file.parent
files = [f.parts[-1][:-3]
for f in current_dir.glob('*.py') if f != current_file]
files = [f.parts[-1][:-3] for f in current_dir.glob("*.py") if f != current_file]
export_models = len(sys.argv) == 2 and sys.argv[-1] == '-e'
export_models = len(sys.argv) == 2 and sys.argv[-1] == "-e"
for f in files:
module = importlib.import_module(f)
@@ -34,9 +34,16 @@ if __name__ == "__main__":
model = module.run()
mesh = model.to_mesh()
if export_models:
if mesh.vert_properties.shape[1] > 3:
vertices = mesh.vert_properties[:, :3]
colors = (mesh.vert_properties[:, 3:] * 255).astype(np.uint8)
else:
vertices = mesh.vert_properties
colors = None
meshOut = trimesh.Trimesh(
vertices=mesh.vert_pos, faces=mesh.tri_verts)
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
print(f'Exported model to {f}.glb')
vertices=vertices, faces=mesh.tri_verts, vertex_colors=colors
)
trimesh.exchange.export.export_mesh(meshOut, f"{f}.glb", "glb")
print(f"Exported model to {f}.glb")
t1 = time()
print(f'Took {(t1-t0)*1000:.1f}ms for {f}')
print(f"Took {(t1-t0)*1000:.1f}ms for {f}")
+81
View File
@@ -0,0 +1,81 @@
"""
Copyright 2023 The Manifold Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from manifold3d import Manifold, Mesh
import numpy as np
def run():
# A smoothed manifold demonstrating selective edge sharpening with
# smooth() and refine(), see more details at:
# https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
height = 10
radius = 30
offset = 20
wiggles = 12
sharpness = 0.8
n = 50
triangles = []
positions = [[-offset, 0, height], [-offset, 0, -height]]
sharpenedEdges = []
delta = np.pi / wiggles
for i in range(2 * wiggles):
theta = (i - wiggles) * delta
amp = 0.5 * height * max(np.cos(0.8 * theta), 0)
positions.append(
[
radius * np.cos(theta),
radius * np.sin(theta),
amp * (1 if i % 2 == 0 else -1),
]
)
j = i + 1
if j == 2 * wiggles:
j = 0
smoothness = 1 - sharpness * np.cos((theta + delta / 2) / 2)
halfedge = 3 * len(triangles) + 1
sharpenedEdges.append((halfedge, smoothness))
triangles.append([0, 2 + i, 2 + j])
halfedge = 3 * len(triangles) + 1
sharpenedEdges.append((halfedge, smoothness))
triangles.append([1, 2 + j, 2 + i])
scallop = Mesh(
tri_verts=np.array(triangles, np.int32),
vert_properties=np.array(positions, np.float32),
)
def colorCurvature(_pos, oldProp):
a = max(0, min(1, oldProp[0] / 3 + 0.5))
b = a * a * (3 - 2 * a)
red = [1, 0, 0]
blue = [0, 0, 1]
return [(1 - b) * blue[i] + b * red[i] for i in range(3)]
edges, smoothing = zip(*sharpenedEdges)
return (
Manifold.smooth(scallop, edges, smoothing)
.refine(n)
.calculate_curvature(-1, 0)
.set_properties(3, colorCurvature)
)
+23
View File
@@ -0,0 +1,23 @@
"""
Copyright 2022 The Manifold Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from manifold3d import Manifold
def run():
a = Manifold.cube([1.0, 1.0, 1.0])
b = Manifold.cube([1.0, 1.0, 1.0]).rotate([45.0, 45.0, 45.0])
return a.split(b)[0]
+43
View File
@@ -0,0 +1,43 @@
from manifold3d import Manifold
import numpy as np
def fractal(holes, hole, w, position, depth, maxDepth):
w /= 3
holes.append(hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]))
if depth == maxDepth:
return
offsets = np.array(
[
[-w, -w],
[-w, 0.0],
[-w, w],
[0.0, w],
[w, w],
[w, 0.0],
[w, -w],
[0.0, -w],
]
)
for offset in offsets:
fractal(holes, hole, w, position + offset, depth + 1, maxDepth)
def posColors(pos, _):
return [-p + 0.5 for p in pos] + [1.0]
def run(n=1):
result = Manifold.cube([1, 1, 1], True)
holes = []
fractal(holes, result, 1.0, np.array([0.0, 0.0]), 1, n)
hole = Manifold.compose(holes)
result -= hole
result -= hole.rotate([90, 0, 0])
result -= hole.rotate([0, 90, 0])
return (
result.trim_by_plane([1, 1, 1], 0).set_properties(4, posColors).scale([100] * 3)
)
@@ -0,0 +1,98 @@
from manifold3d import *
import numpy as np
import pytest
# Creates a classic torus knot, defined as a string wrapping periodically
# around the surface of an imaginary donut. If p and q have a common
# factor then you will get multiple separate, interwoven knots. This is
# an example of using the warp() method, thus avoiding any direct
# handling of triangles.
def run(warp_single=False):
# The number of times the thread passes through the donut hole.
p = 1
# The number of times the thread circles the donut.
q = 3
# Radius of the interior of the imaginary donut.
majorRadius = 25
# Radius of the small cross-section of the imaginary donut.
minorRadius = 10
# Radius of the small cross-section of the actual object.
threadRadius = 3.75
# Number of linear segments making up the threadRadius circle. Default is
# getCircularSegments(threadRadius).
circularSegments = -1
# Number of segments along the length of the knot. Default makes roughly
# square facets.
linearSegments = -1
# These default values recreate Matlab Knot by Emmett Lalish:
# https://www.thingiverse.com/thing:7080
kLoops = np.gcd(p, q)
pk = p / kLoops
qk = q / kLoops
n = (
circularSegments
if circularSegments > 2
else get_circular_segments(threadRadius)
)
m = linearSegments if linearSegments > 2 else n * qk * majorRadius / threadRadius
offset = 2
circle = CrossSection.circle(1, n).translate([offset, 0])
def ax_rotate(x, theta):
a, b = (x + 1) % 3, (x + 2) % 3
s, c = np.sin(theta), np.cos(theta)
m = np.zeros((len(theta), 4, 4), dtype=np.float32)
m[:, a, a], m[:, a, b] = c, s
m[:, b, a], m[:, b, b] = -s, c
m[:, x, x], m[:, 3, 3] = 1, 1
return m
def func(pts):
npts = pts.shape[0]
x, y, z = pts[:, 0], pts[:, 1], pts[:, 2]
psi = qk * np.arctan2(x, y)
theta = psi * pk / qk
x1 = np.sqrt(x * x + y * y)
phi = np.arctan2(x1 - offset, z)
v = np.zeros((npts, 4), dtype=np.float32)
v[:, 0] = threadRadius * np.cos(phi)
v[:, 2] = threadRadius * np.sin(phi)
v[:, 3] = 1
r = majorRadius + minorRadius * np.cos(theta)
m1 = ax_rotate(0, -np.arctan2(pk * minorRadius, qk * r))
m1[:, 3, 0] = minorRadius
m2 = ax_rotate(1, theta)
m2[:, 3, 0] = majorRadius
m3 = ax_rotate(2, psi)
v = v[:, None, :] @ m1 @ m2 @ m3
pts[:] = v[:, 0, :3]
def func_single(v):
pts = np.array([v])
func(pts)
return pts[0]
if warp_single:
return Manifold.revolve(circle, int(m)).warp(func_single)
else:
return Manifold.revolve(circle, int(m)).warp_batch(func)
@pytest.mark.parametrize("warp_single", [True, False])
def test_warp(warp_single):
m = run(warp_single=warp_single)
assert m.volume() == pytest.approx(20785.76)
assert m.surface_area() == pytest.approx(11176.8)
assert m.genus() == 1
if __name__ == "__main__":
run()
@@ -0,0 +1,8 @@
from manifold3d import Manifold
def run():
# for some reason this causes collider error
obj = Manifold.cube()
obj += Manifold.cube().rotate([0, 0, 45 + 180])
return obj
+109
View File
@@ -0,0 +1,109 @@
from os.path import dirname
import re
base = dirname(dirname(dirname(__file__)))
def snake_case(name):
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
def python_param_modifier(comment):
# p = f":{snake_case(m[0][1:])}:"
comment = re.sub(r"@(param \w+)", lambda m: f':{snake_case(m[1])}:', comment)
# python API renames `MeshGL` to `Mesh`
comment = re.sub('mesh_gl', 'mesh', comment)
comment = re.sub('MeshGL', 'Mesh', comment)
return comment
def method_key(name):
name = re.sub(r"\+", "_plus", name)
name = re.sub(r"\-", "_minus", name)
name = re.sub(r"\^", "_xor", name)
name = re.sub(r"\=", "_eq", name)
name = re.sub(r"\:", "_", name)
name = re.sub(r"\~", "destroy_", name)
return name
parens_re = re.compile(r"[^(]+\(([^(]*(\(.*\))*[^(]*\))", flags=re.DOTALL)
args_re = re.compile(
r"^[^,^\(^\)]*(\(.*\))*[^,^\(^\)]*[\s\&\*]([0-9\w]+)\s*[,\)]", flags=re.DOTALL
)
def parse_args(s):
par = parens_re.match(s)
if not par:
return None
out = []
arg_str = par[1]
while m := re.search(args_re, arg_str):
out += [snake_case(m[2])]
arg_str = arg_str[m.span()[1] :]
return out
def collect(fname, matcher, param_modifier=python_param_modifier):
comment = ""
with open(fname) as f:
for line in f:
line = line.lstrip()
if line.startswith("/**"):
comment = ""
elif line.startswith("*/"):
pass
elif line.startswith("*") and comment is not None:
comment += line[1:].lstrip()
elif comment and (m := matcher(line)):
while (args := parse_args(line)) is None:
line += next(f)
if len(line) > 500:
break
method = method_key(snake_case(m[1]))
# comment = re.sub(param_re, param_modifier, comment)
comment = param_modifier(comment)
method = "__".join([method, *args])
assert method not in comments
comments[method] = comment
comment = ""
comments = {}
method_re = re.compile(r"(\w+::[\w\-\+\^\=\:]+)\(")
function_re = re.compile(r"([\w\-\+\^\=\:]+)\(")
# we don't handle inline functions in classes properly
# so instead just white-list functions we want
def select_functions(s):
m = function_re.search(s)
if m and "Triangulate" in m[0]:
return m
if m and "Circular" in m[0]:
return m
return None
collect(f"{base}/src/manifold/src/manifold.cpp", lambda s: method_re.search(s))
collect(f"{base}/src/manifold/src/constructors.cpp", lambda s: method_re.search(s))
collect(f"{base}/src/manifold/src/sort.cpp", lambda s: method_re.search(s))
collect(
f"{base}/src/cross_section/src/cross_section.cpp", lambda s: method_re.search(s)
)
collect(f"{base}/src/polygon/src/polygon.cpp", select_functions)
collect(f"{base}/src/utilities/include/public.h", select_functions)
comments = dict(sorted(comments.items()))
gen_h = "autogen_docstrings.inl"
with open(gen_h, "w") as f:
f.write("#pragma once\n\n")
f.write("// --- AUTO GENERATED ---\n")
f.write("// gen_docs.py is run by cmake build\n\n")
f.write("namespace manifold_docstrings {\n")
for key, doc in comments.items():
f.write(f'const char* {key} = R"___({doc.strip()})___";\n')
f.write("} // namespace manifold_docs")
+705
View File
@@ -0,0 +1,705 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <optional>
#include <string>
#include "autogen_docstrings.inl" // generated in build folder
#include "cross_section.h"
#include "manifold.h"
#include "nanobind/nanobind.h"
#include "nanobind/ndarray.h"
#include "nanobind/operators.h"
#include "nanobind/stl/function.h"
#include "nanobind/stl/optional.h"
#include "nanobind/stl/pair.h"
#include "nanobind/stl/tuple.h"
#include "nanobind/stl/vector.h"
#include "polygon.h"
#include "sdf.h"
namespace nb = nanobind;
using namespace manifold;
template <class T>
struct glm_name {};
template <>
struct glm_name<glm::vec3> {
static constexpr char const name[] = "Floatx3";
static constexpr char const multi_name[] = "FloatNx3";
};
template <>
struct glm_name<glm::vec2> {
static constexpr char const name[] = "Floatx2";
static constexpr char const multi_name[] = "FloatNx2";
};
template <>
struct glm_name<glm::ivec3> {
static constexpr char const name[] = "Intx3";
static constexpr char const multi_name[] = "IntNx3";
};
template <>
struct glm_name<glm::mat4x3> {
static constexpr char const name[] = "Float3x4";
};
template <>
struct glm_name<glm::mat3x2> {
static constexpr char const name[] = "Float2x3";
};
// handle glm::vecN
template <class T, int N, glm::qualifier Q>
struct nb::detail::type_caster<glm::vec<N, T, Q>> {
using glm_type = glm::vec<N, T, Q>;
NB_TYPE_CASTER(glm_type, const_name(glm_name<glm_type>::name));
bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
int size = PyObject_Size(src.ptr()); // negative on failure
if (size != N) return false;
make_caster<T> t_cast;
for (size_t i = 0; i < size; i++) {
if (!t_cast.from_python(src[i], flags, cleanup)) return false;
value[i] = t_cast.value;
}
return true;
}
static handle from_cpp(glm_type vec, rv_policy policy,
cleanup_list *cleanup) noexcept {
nb::list out;
for (int i = 0; i < N; i++) out.append(vec[i]);
return out.release();
}
};
// handle glm::matMxN
template <class T, int C, int R, glm::qualifier Q>
struct nb::detail::type_caster<glm::mat<C, R, T, Q>> {
using glm_type = glm::mat<C, R, T, Q>;
using numpy_type = nb::ndarray<nb::numpy, T, nb::shape<R, C>>;
NB_TYPE_CASTER(glm_type, const_name(glm_name<glm_type>::name));
bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
int rows = PyObject_Size(src.ptr()); // negative on failure
if (rows != R) return false;
for (size_t i = 0; i < R; i++) {
const nb::object &slice = src[i];
int cols = PyObject_Size(slice.ptr()); // negative on failure
if (cols != C) return false;
for (size_t j = 0; j < C; j++) {
make_caster<T> t_cast;
if (!t_cast.from_python(slice[j], flags, cleanup)) return false;
value[j][i] = t_cast.value;
}
}
return true;
}
static handle from_cpp(glm_type mat, rv_policy policy,
cleanup_list *cleanup) noexcept {
T *buffer = new T[R * C];
nb::capsule mem_mgr(buffer, [](void *p) noexcept { delete[](T *) p; });
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
// py is (Rows, Cols), glm is (Cols, Rows)
buffer[i * C + j] = mat[j][i];
}
}
numpy_type arr{buffer, {R, C}, std::move(mem_mgr)};
return ndarray_wrap(arr.handle(), int(ndarray_framework::numpy), policy,
cleanup);
}
};
// handle std::vector<glm::vecN>
template <class T, int N, glm::qualifier Q>
struct nb::detail::type_caster<std::vector<glm::vec<N, T, Q>>> {
using glm_type = glm::vec<N, T, Q>;
using numpy_type = nb::ndarray<nb::numpy, T, nb::shape<nb::any, N>>;
NB_TYPE_CASTER(std::vector<glm_type>,
const_name(glm_name<glm_type>::multi_name));
bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
make_caster<numpy_type> arr_cast;
if (arr_cast.from_python(src, flags, cleanup)) {
int num_vec = arr_cast.value.shape(0);
value.resize(num_vec);
for (int i = 0; i < num_vec; i++) {
for (int j = 0; j < N; j++) {
value[i][j] = arr_cast.value(i, j);
}
}
} else {
int num_vec = PyObject_Size(src.ptr()); // negative on failure
if (num_vec < 0) return false;
value.resize(num_vec);
for (int i = 0; i < num_vec; i++) {
make_caster<glm_type> vec_cast;
if (!vec_cast.from_python(src[i], flags, cleanup)) return false;
value[i] = vec_cast.value;
}
}
return true;
}
static handle from_cpp(Value vec, rv_policy policy,
cleanup_list *cleanup) noexcept {
size_t num_vec = vec.size();
T *buffer = new T[num_vec * N];
nb::capsule mem_mgr(buffer, [](void *p) noexcept { delete[](T *) p; });
for (int i = 0; i < num_vec; i++) {
for (int j = 0; j < N; j++) {
buffer[i * N + j] = vec[i][j];
}
}
numpy_type arr{buffer, {num_vec, N}, std::move(mem_mgr)};
return ndarray_wrap(arr.handle(), int(ndarray_framework::numpy), policy,
cleanup);
}
};
// handle VecView<glm::vec*>
template <class T, int N, glm::qualifier Q>
struct nb::detail::type_caster<manifold::VecView<glm::vec<N, T, Q>>> {
using glm_type = glm::vec<N, T, Q>;
using numpy_type = nb::ndarray<nb::numpy, T, nb::shape<nb::any, N>>;
NB_TYPE_CASTER(manifold::VecView<glm_type>,
const_name(glm_name<glm_type>::multi_name));
bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
make_caster<numpy_type> arr_cast;
if (!arr_cast.from_python(src, flags, cleanup)) return false;
// TODO try 2d iterators if numpy cast fails
int num_vec = arr_cast.value.shape(0);
if (num_vec != value.size()) return false;
for (int i = 0; i < num_vec; i++) {
for (int j = 0; j < N; j++) {
value[i][j] = arr_cast.value(i, j);
}
}
return true;
}
static handle from_cpp(Value vec, rv_policy policy,
cleanup_list *cleanup) noexcept {
size_t num_vec = vec.size();
static_assert(sizeof(vec[0]) == (N * sizeof(T)),
"VecView -> numpy requires packed structs");
numpy_type arr{&vec[0], {num_vec, N}};
return ndarray_wrap(arr.handle(), int(ndarray_framework::numpy), policy,
cleanup);
}
};
template <typename T>
std::vector<T> toVector(const T *arr, size_t size) {
return std::vector<T>(arr, arr + size);
}
using namespace manifold_docstrings;
// strip original :params: and replace with ours
const std::string manifold__rotate_xyz =
manifold__rotate__x_degrees__y_degrees__z_degrees;
const std::string manifold__rotate__v =
manifold__rotate_xyz.substr(0, manifold__rotate_xyz.find(":param")) +
":param v: [X, Y, Z] rotation in degrees.";
NB_MODULE(manifold3d, m) {
m.doc() = "Python binding for the Manifold library.";
m.def("set_min_circular_angle", Quality::SetMinCircularAngle,
nb::arg("angle"), set_min_circular_angle__angle);
m.def("set_min_circular_edge_length", Quality::SetMinCircularEdgeLength,
nb::arg("length"), set_min_circular_edge_length__length);
m.def("set_circular_segments", Quality::SetCircularSegments,
nb::arg("number"), set_circular_segments__number);
m.def("get_circular_segments", Quality::GetCircularSegments,
nb::arg("radius"), get_circular_segments__radius);
m.def("triangulate", &Triangulate, nb::arg("polygons"),
nb::arg("precision") = -1, // TODO document
triangulate__polygons__precision);
nb::class_<Manifold>(m, "Manifold")
.def(nb::init<>(), manifold__manifold)
.def(nb::init<const MeshGL &, const std::vector<float> &>(),
nb::arg("mesh"), nb::arg("property_tolerance") = nb::list(),
manifold__manifold__mesh_gl__property_tolerance)
.def(nb::self + nb::self, manifold__operator_plus__q)
.def(nb::self - nb::self, manifold__operator_minus__q)
.def(nb::self ^ nb::self, manifold__operator_xor__q)
.def(
"hull", [](const Manifold &self) { return self.Hull(); },
manifold__hull)
.def_static(
"batch_hull",
[](std::vector<Manifold> ms) { return Manifold::Hull(ms); },
nb::arg("manifolds"), manifold__hull__manifolds)
.def_static(
"hull_points",
[](std::vector<glm::vec3> pts) { return Manifold::Hull(pts); },
nb::arg("pts"), manifold__hull__pts)
.def("transform", &Manifold::Transform, nb::arg("m"),
manifold__transform__m)
.def("translate", &Manifold::Translate, nb::arg("t"),
manifold__translate__v)
.def("scale", &Manifold::Scale, nb::arg("v"), manifold__scale__v)
.def(
"scale",
[](const Manifold &m, float s) {
m.Scale({s, s, s});
},
nb::arg("s"),
"Scale this Manifold in space. This operation can be chained. "
"Transforms are combined and applied lazily.\n\n"
":param s: The scalar to multiply every vertex by component.")
.def("mirror", &Manifold::Mirror, nb::arg("v"), manifold__mirror__normal)
.def(
"rotate",
[](const Manifold &self, glm::vec3 v) {
return self.Rotate(v.x, v.y, v.z);
},
nb::arg("v"), manifold__rotate__v.c_str())
.def(
"warp",
[](const Manifold &self,
std::function<glm::vec3(glm::vec3)> warp_func) {
// need a wrapper because python cant modify a reference in-place
return self.Warp([&warp_func](glm::vec3 &v) { v = warp_func(v); });
},
nb::arg("warp_func"), manifold__warp__warp_func)
.def("warp_batch", &Manifold::WarpBatch, nb::arg("warp_func"),
manifold__warp_batch__warp_func)
.def(
"set_properties",
[](const Manifold &self, int newNumProp,
const std::function<nb::object(
glm::vec3, const nb::ndarray<nb::numpy, const float,
nb::c_contig> &)> &f) {
const int oldNumProp = self.NumProp();
return self.SetProperties(newNumProp, [newNumProp, oldNumProp, &f](
float *newProps,
glm::vec3 v,
const float *oldProps) {
auto result =
f(v, nb::ndarray<nb::numpy, const float, nb::c_contig>(
oldProps, {static_cast<unsigned long>(oldNumProp)}));
nb::ndarray<float, nb::shape<nb::any>> array;
std::vector<float> vec;
if (nb::try_cast(result, array)) {
if (array.ndim() != 1 || array.shape(0) != newNumProp)
throw std::runtime_error("Invalid vector shape, expected (" +
std::to_string(newNumProp) + ")");
for (int i = 0; i < newNumProp; i++) newProps[i] = array(i);
} else if (nb::try_cast(result, vec)) {
for (int i = 0; i < newNumProp; i++) newProps[i] = vec[i];
} else {
throw std::runtime_error(
"Callback in set_properties should return an array");
}
});
},
nb::arg("new_num_prop"), nb::arg("f"),
manifold__set_properties__num_prop__prop_func)
.def("calculate_curvature", &Manifold::CalculateCurvature,
nb::arg("gaussian_idx"), nb::arg("mean_idx"),
manifold__calculate_curvature__gaussian_idx__mean_idx)
.def("min_gap", &Manifold::MinGap, nb::arg("other"),
nb::arg("search_length"),
"Returns the minimum gap between two manifolds."
"Returns a float between 0 and searchLength.")
.def("calculate_normals", &Manifold::CalculateNormals,
nb::arg("normal_idx"), nb::arg("min_sharp_angle") = 60,
manifold__calculate_normals__normal_idx__min_sharp_angle)
.def("smooth_by_normals", &Manifold::SmoothByNormals,
nb::arg("normal_idx"), manifold__smooth_by_normals__normal_idx)
.def("smooth_out", &Manifold::SmoothOut, nb::arg("min_sharp_angle") = 60,
nb::arg("min_smoothness") = 0,
manifold__smooth_out__min_sharp_angle__min_smoothness)
.def("refine", &Manifold::Refine, nb::arg("n"), manifold__refine__n)
.def("refine_to_length", &Manifold::RefineToLength, nb::arg("length"),
manifold__refine_to_length__length)
.def("to_mesh", &Manifold::GetMeshGL,
nb::arg("normal_idx") = glm::ivec3(0),
manifold__get_mesh_gl__normal_idx)
.def("num_vert", &Manifold::NumVert, manifold__num_vert)
.def("num_edge", &Manifold::NumEdge, manifold__num_edge)
.def("num_tri", &Manifold::NumTri, manifold__num_tri)
.def("num_prop", &Manifold::NumProp, manifold__num_prop)
.def("num_prop_vert", &Manifold::NumPropVert, manifold__num_prop_vert)
.def("precision", &Manifold::Precision, manifold__precision)
.def("genus", &Manifold::Genus, manifold__genus)
.def(
"volume",
[](const Manifold &self) { return self.GetProperties().volume; },
"Get the volume of the manifold\n This is clamped to zero for a "
"given face if they are within the Precision().")
.def(
"surface_area",
[](const Manifold &self) { return self.GetProperties().surfaceArea; },
"Get the surface area of the manifold\n This is clamped to zero for "
"a given face if they are within the Precision().")
.def("original_id", &Manifold::OriginalID, manifold__original_id)
.def("as_original", &Manifold::AsOriginal, manifold__as_original)
.def("is_empty", &Manifold::IsEmpty, manifold__is_empty)
.def("decompose", &Manifold::Decompose, manifold__decompose)
.def("split", &Manifold::Split, nb::arg("cutter"),
manifold__split__cutter)
.def("split_by_plane", &Manifold::SplitByPlane, nb::arg("normal"),
nb::arg("origin_offset"),
manifold__split_by_plane__normal__origin_offset)
.def("trim_by_plane", &Manifold::TrimByPlane, nb::arg("normal"),
nb::arg("origin_offset"),
manifold__trim_by_plane__normal__origin_offset)
.def("slice", &Manifold::Slice, nb::arg("height"),
manifold__slice__height)
.def("project", &Manifold::Project, manifold__project)
.def("status", &Manifold::Status, manifold__status)
.def(
"bounding_box",
[](const Manifold &self) {
Box b = self.BoundingBox();
return nb::make_tuple(b.min[0], b.min[1], b.min[2], b.max[0],
b.max[1], b.max[2]);
},
"Gets the manifold bounding box as a tuple "
"(xmin, ymin, zmin, xmax, ymax, zmax).")
.def_static(
"smooth",
[](const MeshGL &mesh, std::vector<int> sharpened_edges,
std::vector<float> edge_smoothness) {
if (sharpened_edges.size() != edge_smoothness.size()) {
throw std::runtime_error(
"sharpened_edges.size() != edge_smoothness.size()");
}
std::vector<Smoothness> vec(sharpened_edges.size());
for (int i = 0; i < vec.size(); i++) {
vec[i] = {sharpened_edges[i], edge_smoothness[i]};
}
return Manifold::Smooth(mesh, vec);
},
nb::arg("mesh"), nb::arg("sharpened_edges") = nb::list(),
nb::arg("edge_smoothness") = nb::list(),
// todo params slightly diff
manifold__smooth__mesh_gl__sharpened_edges)
.def_static("batch_boolean", &Manifold::BatchBoolean,
nb::arg("manifolds"), nb::arg("op"),
manifold__batch_boolean__manifolds__op)
.def_static("compose", &Manifold::Compose, nb::arg("manifolds"),
manifold__compose__manifolds)
.def_static("tetrahedron", &Manifold::Tetrahedron, manifold__tetrahedron)
.def_static("cube", &Manifold::Cube, nb::arg("size") = glm::vec3{1, 1, 1},
nb::arg("center") = false, manifold__cube__size__center)
.def_static(
"extrude", &Manifold::Extrude, nb::arg("crossSection"),
nb::arg("height"), nb::arg("n_divisions") = 0,
nb::arg("twist_degrees") = 0.0f,
nb::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
manifold__extrude__cross_section__height__n_divisions__twist_degrees__scale_top)
.def_static(
"revolve", &Manifold::Revolve, nb::arg("crossSection"),
nb::arg("circular_segments") = 0, nb::arg("revolve_degrees") = 360.0,
manifold__revolve__cross_section__circular_segments__revolve_degrees)
.def_static(
"cylinder", &Manifold::Cylinder, nb::arg("height"),
nb::arg("radius_low"), nb::arg("radius_high") = -1.0f,
nb::arg("circular_segments") = 0, nb::arg("center") = false,
manifold__cylinder__height__radius_low__radius_high__circular_segments__center)
.def_static("sphere", &Manifold::Sphere, nb::arg("radius"),
nb::arg("circular_segments") = 0,
manifold__sphere__radius__circular_segments)
.def_static("reserve_ids", Manifold::ReserveIDs, nb::arg("n"),
manifold__reserve_ids__n);
nb::class_<MeshGL>(m, "Mesh")
.def(
// note that reshape requires mutable ndarray, but this will not
// affect the original array passed into the function
"__init__",
[](MeshGL *self,
nb::ndarray<float, nb::shape<nb::any, nb::any>, nb::c_contig>
&vertProp,
nb::ndarray<uint32_t, nb::shape<nb::any, 3>, nb::c_contig>
&triVerts,
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
nb::c_contig>> &mergeFromVert,
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
nb::c_contig>> &mergeToVert,
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
nb::c_contig>> &runIndex,
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
nb::c_contig>> &runOriginalID,
std::optional<nb::ndarray<float, nb::shape<nb::any, 4, 3>,
nb::c_contig>> &runTransform,
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
nb::c_contig>> &faceID,
const std::optional<nb::ndarray<float, nb::shape<nb::any, 3, 4>,
nb::c_contig>> &halfedgeTangent,
float precision) {
new (self) MeshGL();
MeshGL &out = *self;
out.numProp = vertProp.shape(1);
out.vertProperties = toVector<float>(vertProp.data(), vertProp.size());
out.triVerts = toVector<uint32_t>(triVerts.data(), triVerts.size());
if (mergeFromVert.has_value())
out.mergeFromVert =
toVector<uint32_t>(mergeFromVert->data(), mergeFromVert->size());
if (mergeToVert.has_value())
out.mergeToVert =
toVector<uint32_t>(mergeToVert->data(), mergeToVert->size());
if (runIndex.has_value())
out.runIndex = toVector<uint32_t>(runIndex->data(), runIndex->size());
if (runOriginalID.has_value())
out.runOriginalID =
toVector<uint32_t>(runOriginalID->data(), runOriginalID->size());
if (runTransform.has_value()) {
out.runTransform =
toVector<float>(runTransform->data(), runTransform->size());
}
if (faceID.has_value())
out.faceID = toVector<uint32_t>(faceID->data(), faceID->size());
if (halfedgeTangent.has_value()) {
out.halfedgeTangent =
toVector<float>(halfedgeTangent->data(), halfedgeTangent->size());
}
},
nb::arg("vert_properties"), nb::arg("tri_verts"),
nb::arg("merge_from_vert") = nb::none(),
nb::arg("merge_to_vert") = nb::none(),
nb::arg("run_index") = nb::none(),
nb::arg("run_original_id") = nb::none(),
nb::arg("run_transform") = nb::none(),
nb::arg("face_id") = nb::none(),
nb::arg("halfedge_tangent") = nb::none(), nb::arg("precision") = 0)
.def_prop_ro("vert_properties",
[](const MeshGL &self) {
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
self.vertProperties.data(),
{self.vertProperties.size() / self.numProp, self.numProp});
}, nb::rv_policy::reference_internal)
.def_prop_ro("tri_verts",
[](const MeshGL &self) {
return nb::ndarray<nb::numpy, const int, nb::c_contig>(
self.triVerts.data(), {self.triVerts.size() / 3, 3});
}, nb::rv_policy::reference_internal)
.def_prop_ro("run_transform",
[](const MeshGL &self) {
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
self.runTransform.data(), {self.runTransform.size() / 12, 4, 3});
}, nb::rv_policy::reference_internal)
.def_prop_ro("halfedge_tangent",
[](const MeshGL &self) {
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
self.halfedgeTangent.data(), {self.halfedgeTangent.size() / 12, 3, 4});
}, nb::rv_policy::reference_internal)
.def_ro("merge_from_vert", &MeshGL::mergeFromVert)
.def_ro("merge_to_vert", &MeshGL::mergeToVert)
.def_ro("run_index", &MeshGL::runIndex)
.def_ro("run_original_id", &MeshGL::runOriginalID)
.def_ro("face_id", &MeshGL::faceID)
.def_static(
"level_set",
[](const std::function<float(float, float, float)> &f,
std::vector<float> bounds, float edgeLength, float level = 0.0) {
// Same format as Manifold.bounding_box
Box bound = {glm::vec3(bounds[0], bounds[1], bounds[2]),
glm::vec3(bounds[3], bounds[4], bounds[5])};
std::function<float(glm::vec3)> cppToPython = [&f](glm::vec3 v) {
return f(v.x, v.y, v.z);
};
Mesh result = LevelSet(cppToPython, bound, edgeLength, level, false);
return MeshGL(result);
},
nb::arg("f"), nb::arg("bounds"), nb::arg("edgeLength"),
nb::arg("level") = 0.0,
"Constructs a level-set Mesh from the input Signed-Distance Function "
"(SDF) This uses a form of Marching Tetrahedra (akin to Marching "
"Cubes, but better for manifoldness). Instead of using a cubic grid, "
"it uses a body-centered cubic grid (two shifted cubic grids). This "
"means if your function's interior exceeds the given bounds, you "
"will see a kind of egg-crate shape closing off the manifold, which "
"is due to the underlying grid."
"\n\n"
":param f: The signed-distance functor, containing this function "
"signature: `def sdf(xyz : tuple) -> float:`, which returns the "
"signed distance of a given point in R^3. Positive values are "
"inside, negative outside."
":param bounds: An axis-aligned box that defines the extent of the "
"grid."
":param edgeLength: Approximate maximum edge length of the triangles "
"in the final result. This affects grid spacing, and hence has a "
"strong effect on performance."
":param level: You can inset your Mesh by using a positive value, or "
"outset it with a negative value."
":return Mesh: This mesh is guaranteed to be manifold."
"Use Manifold.from_mesh(mesh) to create a Manifold")
.def("merge", &MeshGL::Merge, mesh_gl__merge);
nb::enum_<Manifold::Error>(m, "Error")
.value("NoError", Manifold::Error::NoError)
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
.value("NotManifold", Manifold::Error::NotManifold)
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
.value("MissingPositionProperties",
Manifold::Error::MissingPositionProperties)
.value("MergeVectorsDifferentLengths",
Manifold::Error::MergeVectorsDifferentLengths)
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
nb::enum_<CrossSection::FillRule>(m, "FillRule")
.value("EvenOdd", CrossSection::FillRule::EvenOdd,
"Only odd numbered sub-regions are filled.")
.value("NonZero", CrossSection::FillRule::NonZero,
"Only non-zero sub-regions are filled.")
.value("Positive", CrossSection::FillRule::Positive,
"Only sub-regions with winding counts > 0 are filled.")
.value("Negative", CrossSection::FillRule::Negative,
"Only sub-regions with winding counts < 0 are filled.");
nb::enum_<CrossSection::JoinType>(m, "JoinType")
.value("Square", CrossSection::JoinType::Square,
"Squaring is applied uniformly at all joins where the internal "
"join angle is less that 90 degrees. The squared edge will be at "
"exactly the offset distance from the join vertex.")
.value(
"Round", CrossSection::JoinType::Round,
"Rounding is applied to all joins that have convex external angles, "
"and it maintains the exact offset distance from the join vertex.")
.value(
"Miter", CrossSection::JoinType::Miter,
"There's a necessary limit to mitered joins (to avoid narrow angled "
"joins producing excessively long and narrow "
"[spikes](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/"
"Classes/ClipperOffset/Properties/MiterLimit.htm)). So where mitered "
"joins would exceed a given maximum miter distance (relative to the "
"offset distance), these are 'squared' instead.");
nb::enum_<OpType>(m, "OpType", "Operation types for batch_boolean")
.value("Add", OpType::Add)
.value("Subtract", OpType::Subtract)
.value("Intersect", OpType::Intersect);
nb::class_<CrossSection>(
m, "CrossSection",
"Two-dimensional cross sections guaranteed to be without "
"self-intersections, or overlaps between polygons (from construction "
"onwards). This class makes use of the "
"[Clipper2](http://www.angusj.com/clipper2/Docs/Overview.htm) library "
"for polygon clipping (boolean) and offsetting operations.")
.def(nb::init<>(), cross_section__cross_section)
.def(nb::init<std::vector<std::vector<glm::vec2>>,
CrossSection::FillRule>(),
nb::arg("contours"),
nb::arg("fillrule") = CrossSection::FillRule::Positive,
cross_section__cross_section__contours__fillrule)
.def("area", &CrossSection::Area, cross_section__area)
.def("num_vert", &CrossSection::NumVert, cross_section__num_vert)
.def("num_contour", &CrossSection::NumContour, cross_section__num_contour)
.def("is_empty", &CrossSection::IsEmpty, cross_section__is_empty)
.def(
"bounds",
[](const CrossSection &self) {
Rect r = self.Bounds();
return nb::make_tuple(r.min[0], r.min[1], r.max[0], r.max[1]);
},
"Return bounding box of CrossSection as tuple("
"min_x, min_y, max_x, max_y)")
.def("translate", &CrossSection::Translate, nb::arg("v"),
cross_section__translate__v)
.def("rotate", &CrossSection::Rotate, nb::arg("degrees"),
cross_section__rotate__degrees)
.def("scale", &CrossSection::Scale, nb::arg("scale"),
cross_section__scale__scale)
.def(
"scale",
[](const CrossSection &self, float s) {
self.Scale({s, s});
},
nb::arg("s"),
"Scale this CrossSection in space. This operation can be chained. "
"Transforms are combined and applied lazily."
"\n\n"
":param s: The scalar to multiply every vertex by per component.")
.def("mirror", &CrossSection::Mirror, nb::arg("ax"),
cross_section__mirror__ax)
.def("transform", &CrossSection::Transform, nb::arg("m"),
cross_section__transform__m)
.def(
"warp",
[](const CrossSection &self,
std::function<glm::vec2(glm::vec2)> warp_func) {
// need a wrapper because python cant modify a reference in-place
return self.Warp([&warp_func](glm::vec2 &v) { v = warp_func(v); });
},
nb::arg("warp_func"), cross_section__warp__warp_func)
.def("warp_batch", &CrossSection::WarpBatch, nb::arg("warp_func"),
cross_section__warp_batch__warp_func)
.def("simplify", &CrossSection::Simplify, nb::arg("epsilon") = 1e-6,
cross_section__simplify__epsilon)
.def(
"offset", &CrossSection::Offset, nb::arg("delta"),
nb::arg("join_type"), nb::arg("miter_limit") = 2.0,
nb::arg("circular_segments") = 0,
cross_section__offset__delta__jointype__miter_limit__circular_segments)
.def(nb::self + nb::self, cross_section__operator_plus__q)
.def(nb::self - nb::self, cross_section__operator_minus__q)
.def(nb::self ^ nb::self, cross_section__operator_xor__q)
.def(
"hull", [](const CrossSection &self) { return self.Hull(); },
cross_section__hull)
.def_static(
"batch_hull",
[](std::vector<CrossSection> cs) { return CrossSection::Hull(cs); },
nb::arg("cross_sections"), cross_section__hull__cross_sections)
.def_static(
"hull_points",
[](std::vector<glm::vec2> pts) { return CrossSection::Hull(pts); },
nb::arg("pts"), cross_section__hull__pts)
.def("decompose", &CrossSection::Decompose, cross_section__decompose)
.def_static("batch_boolean", &CrossSection::BatchBoolean,
nb::arg("cross_sections"), nb::arg("op"),
cross_section__batch_boolean__cross_sections__op)
.def_static("compose", &CrossSection::Compose, nb::arg("cross_sections"),
cross_section__compose__cross_sections)
.def("to_polygons", &CrossSection::ToPolygons, cross_section__to_polygons)
.def(
"extrude", &Manifold::Extrude, nb::arg("height"),
nb::arg("n_divisions") = 0, nb::arg("twist_degrees") = 0.0f,
nb::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
manifold__extrude__cross_section__height__n_divisions__twist_degrees__scale_top)
.def("revolve", &Manifold::Revolve, nb::arg("circular_segments") = 0,
nb::arg("revolve_degrees") = 360.0,
manifold__revolve__cross_section__circular_segments__revolve_degrees)
.def_static("square", &CrossSection::Square, nb::arg("size"),
nb::arg("center") = false,
cross_section__square__size__center)
.def_static("circle", &CrossSection::Circle, nb::arg("radius"),
nb::arg("circular_segments") = 0,
cross_section__circle__radius__circular_segments);
}
-504
View File
@@ -1,504 +0,0 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "manifold.h"
#include "pybind11/functional.h"
#include "pybind11/numpy.h"
#include "pybind11/operators.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"
namespace py = pybind11;
using namespace manifold;
typedef std::tuple<float, float> Float2;
typedef std::tuple<float, float, float> Float3;
struct PolygonsWrapper {
std::unique_ptr<Polygons> polygons;
};
PYBIND11_MODULE(pymanifold, m) {
m.doc() =
"Python binding for the manifold library. Please check the C++ "
"documentation for APIs.\n"
"This binding will perform copying to make the API more familiar to "
"OpenSCAD users.";
py::class_<Manifold>(m, "Manifold")
.def(py::init<>())
.def(py::init([](std::vector<Manifold> &manifolds) {
Manifold result;
for (Manifold &manifold : manifolds) result += manifold;
return result;
}),
"Construct manifold as the union of a set of manifolds.")
.def(py::self + py::self, "Boolean union.")
.def(py::self - py::self, "Boolean difference.")
.def(py::self ^ py::self, "Boolean intersection.")
.def(
"transform",
[](Manifold self, py::array_t<float> &mat) {
auto mat_view = mat.unchecked<2>();
if (mat_view.shape(0) != 3 || mat_view.shape(1) != 4)
throw std::runtime_error("Invalid matrix shape");
glm::mat4x3 mat_glm;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
mat_glm[j][i] = mat_view(i, j);
}
}
return self.Transform(mat_glm);
},
py::arg("m"),
"Transform this Manifold in space. The first three columns form a "
"3x3 matrix\n"
"transform and the last is a translation vector. This operation can "
"be\n"
"chained. Transforms are combined and applied lazily.\n"
"\n"
":param m: The affine transform matrix to apply to all the vertices.")
.def(
"translate",
[](Manifold self, float x = 0.0f, float y = 0.0f, float z = 0.0f) {
return self.Translate(glm::vec3(x, y, z));
},
py::arg("x") = 0.0f, py::arg("y") = 0.0f, py::arg("z") = 0.0f,
"Move this Manifold in space. This operation can be chained. "
"Transforms are\n"
"combined and applied lazily.\n"
"\n"
":param x: X axis translation. (default 0.0).\n"
":param y: Y axis translation. (default 0.0).\n"
":param z: Z axis translation. (default 0.0).")
.def(
"translate",
[](Manifold self, py::array_t<float> &t) {
auto t_view = t.unchecked<1>();
if (t.shape(0) != 3)
throw std::runtime_error("Invalid vector shape");
return self.Translate(glm::vec3(t_view(0), t_view(1), t_view(2)));
},
py::arg("t"),
"Move this Manifold in space. This operation can be chained. "
"Transforms are\n"
"combined and applied lazily.\n"
"\n"
":param v: The vector to add to every vertex.")
.def("scale",
static_cast<Manifold (*)(Manifold, float)>(
[](Manifold self, float scale) {
return self.Scale(glm::vec3(scale));
}),
py::arg("scale"),
"Scale this Manifold in space. This operation can be chained. "
"Transforms are\n"
"combined and applied lazily.\n"
"\n"
":param scale: The scalar multiplier for each component of every "
"vertices.")
.def(
"scale",
[](Manifold self, py::array_t<float> &scale) {
auto scale_view = scale.unchecked<1>();
if (scale_view.shape(0) != 3)
throw std::runtime_error("Invalid vector shape");
glm::vec3 v(scale_view(0), scale_view(1), scale_view(2));
return self.Scale(v);
},
py::arg("v"),
"Scale this Manifold in space. This operation can be chained. "
"Transforms are\n"
"combined and applied lazily.\n"
"\n"
":param v: The vector to multiply every vertex by component.")
.def(
"rotate",
[](Manifold self, py::array_t<float> &v) {
auto v_view = v.unchecked<1>();
if (v_view.shape(0) != 3)
throw std::runtime_error("Invalid vector shape");
return self.Rotate(v_view(0), v_view(1), v_view(2));
},
py::arg("v"),
"Applies an Euler angle rotation to the manifold, first about the X "
"axis, then\n"
"Y, then Z, in degrees. We use degrees so that we can minimize "
"rounding error,\n"
"and eliminate it completely for any multiples of 90 degrees. "
"Additionally, more\n"
"efficient code paths are used to update the manifold when the "
"transforms only\n"
"rotate by multiples of 90 degrees. This operation can be chained. "
"Transforms\n"
"are combined and applied lazily.\n"
"\n"
":param v: [X, Y, Z] rotation in degrees.")
.def(
"rotate",
[](Manifold self, float xDegrees = 0.0f, float yDegrees = 0.0f,
float zDegrees = 0.0f) {
return self.Rotate(xDegrees, yDegrees, zDegrees);
},
py::arg("x_degrees") = 0.0f, py::arg("y_degrees") = 0.0f,
py::arg("z_degrees") = 0.0f,
"Applies an Euler angle rotation to the manifold, first about the X "
"axis, then\n"
"Y, then Z, in degrees. We use degrees so that we can minimize "
"rounding error,\n"
"and eliminate it completely for any multiples of 90 degrees. "
"Additionally, more\n"
"efficient code paths are used to update the manifold when the "
"transforms only\n"
"rotate by multiples of 90 degrees. This operation can be chained. "
"Transforms\n"
"are combined and applied lazily.\n"
"\n"
":param x: X rotation in degrees. (default 0.0).\n"
":param y: Y rotation in degrees. (default 0.0).\n"
":param z: Z rotation in degrees. (default 0.0).")
.def(
"warp",
[](Manifold self, const std::function<Float3(Float3)> &f) {
return self.Warp([&f](glm::vec3 &v) {
Float3 fv = f(std::make_tuple(v.x, v.y, v.z));
v.x = std::get<0>(fv);
v.y = std::get<1>(fv);
v.z = std::get<2>(fv);
});
},
py::arg("f"))
.def(
"refine", [](Manifold self, int n) { return self.Refine(n); },
py::arg("n"),
"Increase the density of the mesh by splitting every edge into n "
"pieces. For\n"
"instance, with n = 2, each triangle will be split into 4 triangles. "
"These\n"
"will all be coplanar (and will not be immediately collapsed) unless "
"the\n"
"Mesh/Manifold has halfedgeTangents specified (e.g. from the "
"Smooth()\n"
"constructor), in which case the new vertices will be moved to the\n"
"interpolated surface according to their barycentric coordinates.\n"
"\n"
":param n: The number of pieces to split every edge into. Must be > "
"1.")
.def("to_mesh", &Manifold::GetMesh)
.def_static("smooth", Manifold::Smooth,
"Constructs a smooth version of the input mesh by creating "
"tangents; this\n"
"method will throw if you have supplied tangents with your "
"mesh already. The\n"
"actual triangle resolution is unchanged; use the Refine() "
"method to\n"
"interpolate to a higher-resolution curve.\n"
"\n"
"By default, every edge is calculated for maximum smoothness "
"(very much\n"
"approximately), attempting to minimize the maximum mean "
"Curvature magnitude.\n"
"No higher-order derivatives are considered, as the "
"interpolation is\n"
"independent per triangle, only sharing constraints on their "
"boundaries.\n"
"\n"
":param mesh: input Mesh.\n"
":param sharpenedEdges: If desired, you can supply a vector "
"of sharpened\n"
"halfedges, which should in general be a small subset of all "
"halfedges. Order\n"
"of entries doesn't matter, as each one specifies the "
"desired smoothness\n"
"(between zero and one, with one the default for all "
"unspecified halfedges)\n"
"and the halfedge index (3 * triangle index + [0,1,2] where "
"0 is the edge\n"
"between triVert 0 and 1, etc).\n"
"\n"
"At a smoothness value of zero, a sharp crease is made. The "
"smoothness is\n"
"interpolated along each edge, so the specified value should "
"be thought of as\n"
"an average. Where exactly two sharpened edges meet at a "
"vertex, their\n"
"tangents are rotated to be colinear so that the sharpened "
"edge can be\n"
"continuous. Vertices with only one sharpened edge are "
"completely smooth,\n"
"allowing sharpened edges to smoothly vanish at termination. "
"A single vertex\n"
"can be sharpened by sharping all edges that are incident on "
"it, allowing\n"
"cones to be formed.")
.def_static(
"from_mesh", [](const Mesh &mesh) { return Manifold(mesh); },
py::arg("mesh"))
.def_static(
"tetrahedron", []() { return Manifold::Tetrahedron(); },
"Constructs a tetrahedron centered at the origin with one vertex at "
"(1,1,1)\n"
"and the rest at similarly symmetric points.")
.def_static(
"cube",
[](Float3 size, bool center = false) {
return Manifold::Cube(
glm::vec3(std::get<0>(size), std::get<1>(size),
std::get<2>(size)),
center);
},
py::arg("size") = std::make_tuple(1.0f, 1.0f, 1.0f),
py::arg("center") = false,
"Constructs a unit cube (edge lengths all one), by default in the "
"first\n"
"octant, touching the origin.\n"
"\n"
":param size: The X, Y, and Z dimensions of the box.\n"
":param center: Set to true to shift the center to the origin.")
.def_static(
"cube",
[](py::array_t<float> &size, bool center = false) {
auto size_view = size.unchecked<1>();
if (size_view.shape(0) != 3)
throw std::runtime_error("Invalid vector shape");
return Manifold::Cube(
glm::vec3(size_view(0), size_view(1), size_view(2)), center);
},
py::arg("size"), py::arg("center") = false,
"Constructs a unit cube (edge lengths all one), by default in the "
"first\n"
"octant, touching the origin.\n"
"\n"
":param size: The X, Y, and Z dimensions of the box.\n"
":param center: Set to true to shift the center to the origin.")
.def_static(
"cube",
[](float x, float y, float z, bool center = false) {
return Manifold::Cube(glm::vec3(x, y, z), center);
},
py::arg("x"), py::arg("y"), py::arg("z"), py::arg("center") = false,
"Constructs a unit cube (edge lengths all one), by default in the "
"first\n"
"octant, touching the origin.\n"
"\n"
":param x: The X dimensions of the box.\n"
":param y: The Y dimensions of the box.\n"
":param z: The Z dimensions of the box.\n"
":param center: Set to true to shift the center to the origin.")
.def_static(
"cylinder",
[](float height, float radiusLow, float radiusHigh = -1.0f,
int circularSegments = 0) {
return Manifold::Cylinder(height, radiusLow, radiusHigh,
circularSegments);
},
py::arg("height"), py::arg("radius_low"),
py::arg("radius_high") = -1.0f, py::arg("circular_segments") = 0,
"A convenience constructor for the common case of extruding a "
"circle. Can also\n"
"form cones if both radii are specified.\n"
"\n"
":param height: Z-extent\n"
":param radiusLow: Radius of bottom circle. Must be positive.\n"
":param radiusHigh: Radius of top circle. Can equal zero. Default "
"(-1) is equal to\n"
"radiusLow.\n"
":param circularSegments: How many line segments to use around the "
"circle.\n"
"Default (-1) is calculated by the static Defaults.\n"
":param center: Set to true to shift the center to the origin. "
"Default is\n"
"origin at the bottom.")
.def_static(
"sphere",
[](float radius, int circularSegments = 0) {
return Manifold::Sphere(radius, circularSegments);
},
py::arg("radius"), py::arg("circular_segments") = 0,
"Constructs a geodesic sphere of a given radius.\n"
"\n"
":param radius: Radius of the sphere. Must be positive.\n"
":param circularSegments: Number of segments along its\n"
"diameter. This number will always be rounded up to the nearest "
"factor of\n"
"four, as this sphere is constructed by refining an octahedron. This "
"means\n"
"there are a circle of vertices on all three of the axis planes. "
"Default is\n"
"calculated by the static Defaults.");
py::class_<PolygonsWrapper>(m, "Polygons")
.def(py::init([](std::vector<std::vector<Float2>> &polygons) {
std::vector<SimplePolygon> simplePolygons(polygons.size());
for (int i = 0; i < polygons.size(); i++) {
std::vector<PolyVert> vertices(polygons[i].size());
for (int j = 0; j < polygons[i].size(); j++) {
vertices[j] = {
{std::get<0>(polygons[i][j]), std::get<1>(polygons[i][j])},
j};
}
simplePolygons[i] = {vertices};
}
return PolygonsWrapper{std::make_unique<Polygons>(simplePolygons)};
}),
py::arg("polygons"),
"Construct the Polygons object from a list of simple polygon, "
"where each simple polygon is a list of points (pair of floats).")
.def(
"extrude",
[](PolygonsWrapper &self, float height, int nDivisions = 0,
float twistDegrees = 0.0f,
Float2 scaleTop = std::make_tuple(1.0f, 1.0f)) {
glm::vec2 scaleTopVec(std::get<0>(scaleTop), std::get<1>(scaleTop));
return Manifold::Extrude(*self.polygons, height, nDivisions,
twistDegrees, scaleTopVec);
},
py::arg("height"), py::arg("n_divisions") = 0,
py::arg("twist_degrees") = 0.0f,
py::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
"Constructs a manifold from the set of polygons by extruding them "
"along the\n"
"Z-axis.\n"
"\n"
":param height: Z-extent of extrusion.\n"
":param nDivisions: Number of extra copies of the crossSection to "
"insert into\n"
"the shape vertically; especially useful in combination with "
"twistDegrees to\n"
"avoid interpolation artifacts. Default is none.\n"
":param twistDegrees: Amount to twist the top crossSection relative "
"to the\n"
"bottom, interpolated linearly for the divisions in between.\n"
":param scaleTop: Amount to scale the top (independently in X and "
"Y). If the\n"
"scale is (0, 0), a pure cone is formed with only a single vertex at "
"the top.\n"
"Default (1, 1).")
.def(
"revolve",
[](PolygonsWrapper &self, int circularSegments = 0) {
return Manifold::Revolve(*self.polygons, circularSegments);
},
py::arg("circular_segments") = 0,
"Constructs a manifold from the set of polygons by revolving this "
"cross-section\n"
"around its Y-axis and then setting this as the Z-axis of the "
"resulting\n"
"manifold. If the polygons cross the Y-axis, only the part on the "
"positive X\n"
"side is used. Geometrically valid input will result in "
"geometrically valid\n"
"output.\n"
"\n"
":param circularSegments: Number of segments along its diameter. "
"Default is\n"
"calculated by the static Defaults.");
py::class_<Mesh>(m, "Mesh")
.def(py::init([](py::array_t<float> &vertPos, py::array_t<int> &triVerts,
py::array_t<float> &vertNormal,
py::array_t<float> &halfedgeTangent) {
auto vertPos_view = vertPos.unchecked<2>();
auto triVerts_view = triVerts.unchecked<2>();
auto vertNormal_view = vertNormal.unchecked<2>();
auto halfedgeTangent_view = halfedgeTangent.unchecked<2>();
if (vertPos_view.shape(1) != 3)
throw std::runtime_error("Invalid vert_pos shape");
if (triVerts_view.shape(1) != 3)
throw std::runtime_error("Invalid tri_verts shape");
if (vertNormal_view.shape(0) != 0) {
if (vertNormal_view.shape(1) != 3)
throw std::runtime_error("Invalid vert_normal shape");
if (vertNormal_view.shape(0) != vertPos_view.shape(0))
throw std::runtime_error(
"vert_normal must have the same length as vert_pos");
}
if (halfedgeTangent_view.shape(0) != 0) {
if (halfedgeTangent_view.shape(1) != 4)
throw std::runtime_error("Invalid halfedge_tangent shape");
if (halfedgeTangent_view.shape(0) != triVerts_view.shape(0) * 3)
throw std::runtime_error(
"halfedge_tangent must be three times as long as "
"tri_verts");
}
std::vector<glm::vec3> vertPos_vec(vertPos_view.shape(0));
std::vector<glm::ivec3> triVerts_vec(triVerts_view.shape(0));
std::vector<glm::vec3> vertNormal_vec(vertNormal_view.shape(0));
std::vector<glm::vec4> halfedgeTangent_vec(
halfedgeTangent_view.shape(0));
for (int i = 0; i < vertPos_view.shape(0); i++)
for (const int j : {0, 1, 2})
vertPos_vec[i][j] = vertPos_view(i, j);
for (int i = 0; i < triVerts_view.shape(0); i++)
for (const int j : {0, 1, 2})
triVerts_vec[i][j] = triVerts_view(i, j);
for (int i = 0; i < vertNormal_view.shape(0); i++)
for (const int j : {0, 1, 2})
vertNormal_vec[i][j] = vertNormal_view(i, j);
for (int i = 0; i < halfedgeTangent_view.shape(0); i++)
for (const int j : {0, 1, 2, 3})
halfedgeTangent_vec[i][j] = halfedgeTangent_view(i, j);
return Mesh({vertPos_vec, triVerts_vec, vertNormal_vec,
halfedgeTangent_vec});
}),
py::arg("vert_pos"), py::arg("tri_verts"), py::arg("vert_normal"),
py::arg("halfedge_tangent"))
.def_property_readonly("vert_pos",
[](Mesh &self) {
const int numVert = self.vertPos.size();
py::array_t<float> vert_pos({numVert, 3});
auto vert_pos_view =
vert_pos.mutable_unchecked<2>();
for (int i = 0; i < numVert; ++i)
for (const int j : {0, 1, 2})
vert_pos_view(i, j) = self.vertPos[i][j];
return vert_pos;
})
.def_property_readonly("tri_verts",
[](Mesh &self) {
const int numTri = self.triVerts.size();
py::array_t<int> tri_verts({numTri, 3});
auto tri_verts_view =
tri_verts.mutable_unchecked<2>();
for (int i = 0; i < numTri; ++i)
for (const int j : {0, 1, 2})
tri_verts_view(i, j) = self.triVerts[i][j];
return tri_verts;
})
.def_property_readonly(
"vert_normal",
[](Mesh &self) {
const int numVert = self.vertNormal.size();
py::array_t<float> vert_normal({numVert, 3});
auto vert_normal_view = vert_normal.mutable_unchecked<2>();
for (int i = 0; i < numVert; ++i)
for (const int j : {0, 1, 2})
vert_normal_view(i, j) = self.vertNormal[i][j];
return vert_normal;
})
.def_property_readonly("halfedge_tangent", [](Mesh &self) {
const int numEdge = self.halfedgeTangent.size();
py::array_t<float> halfedge_tangent({numEdge, 4});
auto halfedge_tangent_view = halfedge_tangent.mutable_unchecked<2>();
for (int i = 0; i < numEdge; ++i)
for (const int j : {0, 1, 2, 3})
halfedge_tangent_view(i, j) = self.halfedgeTangent[i][j];
return halfedge_tangent;
});
}
+1 -1
View File
@@ -1 +1 @@
add_subdirectory(pybind11)
add_subdirectory(nanobind)
+2
View File
@@ -0,0 +1,2 @@
manifold.js
manifold.wasm
+29 -13
View File
@@ -16,23 +16,39 @@ project(wasm)
add_executable(manifoldjs bindings.cpp)
# make sure that we recompile the wasm when bindings.js is being modified
set_source_files_properties(bindings.cpp OBJECT_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
target_link_libraries(manifoldjs manifold sdf)
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS} -fexceptions)
set_source_files_properties(bindings.cpp PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
target_link_libraries(manifoldjs manifold sdf cross_section polygon)
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS})
target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1)
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1 -sEXPORT_ES6=1)
target_compile_features(manifoldjs PUBLIC cxx_std_14)
target_compile_features(manifoldjs PUBLIC cxx_std_17)
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
file(COPY examples;test;. DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS examples;test)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/built)
# ensure that interface files are copied over when modified
add_custom_target(js_deps ALL
DEPENDS manifoldjs
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts)
# copy WASM build back here for publishing to npm
add_custom_command(
TARGET manifoldjs POST_BUILD
TARGET js_deps POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
${CMAKE_CURRENT_BINARY_DIR}/examples/)
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
${CMAKE_CURRENT_SOURCE_DIR})
# copy WASM build and TS declarations for Vite to package into ManifoldCAD.org
add_custom_command(
TARGET js_deps POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/manifold*
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/)
# copy TS declarations to public so they can be accessed by our editor
add_custom_command(
TARGET js_deps POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
${CMAKE_CURRENT_SOURCE_DIR}/examples/public/)
+5 -1
View File
@@ -20,7 +20,11 @@ The most significant contribution here is a guaranteed-manifold [mesh Boolean](h
## Note on memory management
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 https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287.
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).
## Examples
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_mesh_manifold](https://github.com/KhronosGroup/glTF/pull/2286).
## About the author
+119 -166
View File
@@ -15,120 +15,17 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <vector>
#include "cross_section.h"
#include "helpers.cpp"
#include "manifold.h"
#include "polygon.h"
#include "sdf.h"
using namespace emscripten;
#include <manifold.h>
#include <sdf.h>
using namespace manifold;
Manifold Union(const Manifold& a, const Manifold& b) { return a + b; }
Manifold Difference(const Manifold& a, const Manifold& b) { return a - b; }
Manifold Intersection(const Manifold& a, const Manifold& b) { return a ^ b; }
Manifold UnionN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, Manifold::OpType::ADD);
;
}
Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, Manifold::OpType::SUBTRACT);
;
}
Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, Manifold::OpType::INTERSECT);
;
}
std::vector<SimplePolygon> ToPolygon(
std::vector<std::vector<glm::vec2>>& polygons) {
std::vector<SimplePolygon> simplePolygons(polygons.size());
for (int i = 0; i < polygons.size(); i++) {
std::vector<PolyVert> vertices(polygons[i].size());
for (int j = 0; j < polygons[i].size(); j++) {
vertices[j] = {polygons[i][j], j};
}
simplePolygons[i] = {vertices};
}
return simplePolygons;
}
val GetMeshJS(const Manifold& manifold) {
MeshGL mesh = manifold.GetMeshGL();
val meshJS = val::object();
meshJS.set("triVerts",
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
.call<val>("slice"));
meshJS.set("vertPos",
val(typed_memory_view(mesh.vertPos.size(), mesh.vertPos.data()))
.call<val>("slice"));
meshJS.set("vertNormal", val(typed_memory_view(mesh.vertNormal.size(),
mesh.vertNormal.data()))
.call<val>("slice"));
meshJS.set("halfedgeTangent",
val(typed_memory_view(mesh.halfedgeTangent.size(),
mesh.halfedgeTangent.data()))
.call<val>("slice"));
return meshJS;
}
MeshGL MeshJS2GL(const val& mesh) {
MeshGL out;
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
out.vertPos = convertJSArrayToNumberVector<float>(mesh["vertPos"]);
if (mesh["vertNormal"] != val::undefined()) {
out.vertNormal = convertJSArrayToNumberVector<float>(mesh["vertNormal"]);
}
if (mesh["halfedgeTangent"] != val::undefined()) {
out.halfedgeTangent =
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
}
return out;
}
Manifold FromMeshJS(const val& mesh) { return Manifold(MeshJS2GL(mesh)); }
Manifold Smooth(const val& mesh,
const std::vector<Smoothness>& sharpenedEdges = {}) {
return Manifold::Smooth(MeshJS2GL(mesh), sharpenedEdges);
}
Manifold Extrude(std::vector<std::vector<glm::vec2>>& polygons, float height,
int nDivisions, float twistDegrees, glm::vec2 scaleTop) {
return Manifold::Extrude(ToPolygon(polygons), height, nDivisions,
twistDegrees, scaleTop);
}
Manifold Revolve(std::vector<std::vector<glm::vec2>>& polygons,
int circularSegments) {
return Manifold::Revolve(ToPolygon(polygons), circularSegments);
}
Manifold Transform(Manifold& manifold, std::vector<float>& mat) {
glm::mat4x3 matrix;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 3; j++) matrix[i][j] = mat[i * 3 + j];
return manifold.Transform(matrix);
}
Manifold Warp(Manifold& manifold, uintptr_t funcPtr) {
void (*f)(glm::vec3&) = reinterpret_cast<void (*)(glm::vec3&)>(funcPtr);
return manifold.Warp(f);
}
Manifold LevelSetJs(uintptr_t funcPtr, Box bounds, float edgeLength,
float level) {
float (*f)(const glm::vec3&) =
reinterpret_cast<float (*)(const glm::vec3&)>(funcPtr);
Mesh m = LevelSet(f, bounds, edgeLength, level);
return Manifold(m);
}
EMSCRIPTEN_BINDINGS(whatever) {
value_object<glm::vec2>("vec2")
.field("x", &glm::vec2::x)
@@ -151,18 +48,33 @@ EMSCRIPTEN_BINDINGS(whatever) {
.field("w", &glm::vec4::w);
enum_<Manifold::Error>("status")
.value("NO_ERROR", Manifold::Error::NO_ERROR)
.value("NON_FINITE_VERTEX", Manifold::Error::NON_FINITE_VERTEX)
.value("NOT_MANIFOLD", Manifold::Error::NOT_MANIFOLD)
.value("VERTEX_INDEX_OUT_OF_BOUNDS",
Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS)
.value("PROPERTIES_WRONG_LENGTH",
Manifold::Error::PROPERTIES_WRONG_LENGTH)
.value("TRI_PROPERTIES_WRONG_LENGTH",
Manifold::Error::TRI_PROPERTIES_WRONG_LENGTH)
.value("TRI_PROPERTIES_OUT_OF_BOUNDS",
Manifold::Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
.value("NoError", Manifold::Error::NoError)
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
.value("NotManifold", Manifold::Error::NotManifold)
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
.value("MissingPositionProperties",
Manifold::Error::MissingPositionProperties)
.value("MergeVectorsDifferentLengths",
Manifold::Error::MergeVectorsDifferentLengths)
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
enum_<CrossSection::FillRule>("fillrule")
.value("EvenOdd", CrossSection::FillRule::EvenOdd)
.value("NonZero", CrossSection::FillRule::NonZero)
.value("Positive", CrossSection::FillRule::Positive)
.value("Negative", CrossSection::FillRule::Negative);
enum_<CrossSection::JoinType>("jointype")
.value("Square", CrossSection::JoinType::Square)
.value("Round", CrossSection::JoinType::Round)
.value("Miter", CrossSection::JoinType::Miter);
value_object<Rect>("rect").field("min", &Rect::min).field("max", &Rect::max);
value_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
value_object<Smoothness>("smoothness")
@@ -173,77 +85,118 @@ EMSCRIPTEN_BINDINGS(whatever) {
.field("surfaceArea", &Properties::surfaceArea)
.field("volume", &Properties::volume);
value_object<BaryRef>("baryRef")
.field("meshID", &BaryRef::meshID)
.field("originalID", &BaryRef::originalID)
.field("tri", &BaryRef::tri)
.field("vertBary", &BaryRef::vertBary);
value_object<MeshRelation>("meshRelation")
.field("barycentric", &MeshRelation::barycentric)
.field("triBary", &MeshRelation::triBary);
value_object<Curvature>("curvature")
.field("maxMeanCurvature", &Curvature::maxMeanCurvature)
.field("minMeanCurvature", &Curvature::minMeanCurvature)
.field("maxGaussianCurvature", &Curvature::maxGaussianCurvature)
.field("minGaussianCurvature", &Curvature::minGaussianCurvature)
.field("vertMeanCurvature", &Curvature::vertMeanCurvature)
.field("vertGaussianCurvature", &Curvature::vertGaussianCurvature);
register_vector<glm::ivec3>("Vector_ivec3");
register_vector<glm::vec3>("Vector_vec3");
register_vector<glm::vec2>("Vector_vec2");
register_vector<std::vector<glm::vec2>>("Vector2_vec2");
register_vector<float>("Vector_f32");
register_vector<CrossSection>("Vector_crossSection");
register_vector<Manifold>("Vector_manifold");
register_vector<Smoothness>("Vector_smoothness");
register_vector<BaryRef>("Vector_baryRef");
register_vector<glm::vec4>("Vector_vec4");
class_<CrossSection>("CrossSection")
.constructor(&cross_js::OfPolygons)
.function("_add", &cross_js::Union)
.function("_subtract", &cross_js::Difference)
.function("_intersect", &cross_js::Intersection)
.function("_Warp", &cross_js::Warp)
.function("transform", &cross_js::Transform)
.function("_Translate", &CrossSection::Translate)
.function("rotate", &CrossSection::Rotate)
.function("_Scale", &CrossSection::Scale)
.function("_Mirror", &CrossSection::Mirror)
.function("_Decompose", &CrossSection::Decompose)
.function("isEmpty", &CrossSection::IsEmpty)
.function("area", &CrossSection::Area)
.function("numVert", &CrossSection::NumVert)
.function("numContour", &CrossSection::NumContour)
.function("_Bounds", &CrossSection::Bounds)
.function("simplify", &CrossSection::Simplify)
.function("_Offset", &cross_js::Offset)
.function("_ToPolygons", &CrossSection::ToPolygons)
.function("hull",
select_overload<CrossSection() const>(&CrossSection::Hull));
// CrossSection Static Methods
function("_Square", &CrossSection::Square);
function("_Circle", &CrossSection::Circle);
function("_crossSectionCompose", &CrossSection::Compose);
function("_crossSectionUnionN", &cross_js::UnionN);
function("_crossSectionDifferenceN", &cross_js::DifferenceN);
function("_crossSectionIntersectionN", &cross_js::IntersectionN);
function("_crossSectionCollectVertices", &cross_js::CollectVertices);
function("_crossSectionHullPoints",
select_overload<CrossSection(std::vector<glm::vec2>)>(
&CrossSection::Hull));
class_<Manifold>("Manifold")
.constructor(&FromMeshJS)
.function("add", &Union)
.function("subtract", &Difference)
.function("intersect", &Intersection)
.function("_GetMeshJS", &GetMeshJS)
.constructor(&man_js::FromMeshJS)
.function("add", &man_js::Union)
.function("subtract", &man_js::Difference)
.function("intersect", &man_js::Intersection)
.function("_Split", &man_js::Split)
.function("_SplitByPlane", &man_js::SplitByPlane)
.function("_TrimByPlane", &Manifold::TrimByPlane)
.function("_Slice", &Manifold::Slice)
.function("project", &Manifold::Project)
.function("hull", select_overload<Manifold() const>(&Manifold::Hull))
.function("_GetMeshJS", &js::GetMeshJS)
.function("refine", &Manifold::Refine)
.function("_Warp", &Warp)
.function("_Transform", &Transform)
.function("refineToLength", &Manifold::RefineToLength)
.function("smoothByNormals", &Manifold::SmoothByNormals)
.function("_SmoothOut", &Manifold::SmoothOut)
.function("_Warp", &man_js::Warp)
.function("_SetProperties", &man_js::SetProperties)
.function("transform", &man_js::Transform)
.function("_Translate", &Manifold::Translate)
.function("_Rotate", &Manifold::Rotate)
.function("_Scale", &Manifold::Scale)
.function("_Decompose", &Manifold::Decompose)
.function("_Mirror", &Manifold::Mirror)
.function("_Decompose", select_overload<std::vector<Manifold>() const>(
&Manifold::Decompose))
.function("isEmpty", &Manifold::IsEmpty)
.function("status", &Manifold::Status)
.function("numVert", &Manifold::NumVert)
.function("numEdge", &Manifold::NumEdge)
.function("numTri", &Manifold::NumTri)
.function("numProp", &Manifold::NumProp)
.function("numPropVert", &Manifold::NumPropVert)
.function("_boundingBox", &Manifold::BoundingBox)
.function("precision", &Manifold::Precision)
.function("genus", &Manifold::Genus)
.function("getProperties", &Manifold::GetProperties)
.function("_getCurvature", &Manifold::GetCurvature)
.function("minGap", &Manifold::MinGap)
.function("calculateCurvature", &Manifold::CalculateCurvature)
.function("_CalculateNormals", &Manifold::CalculateNormals)
.function("originalID", &Manifold::OriginalID)
.function("asOriginal", &Manifold::AsOriginal)
.function("_getMeshRelation", &Manifold::GetMeshRelation);
.function("asOriginal", &Manifold::AsOriginal);
// Manifold Static Methods
function("_Cube", &Manifold::Cube);
function("_Cylinder", &Manifold::Cylinder);
function("_Sphere", &Manifold::Sphere);
function("tetrahedron", &Manifold::Tetrahedron);
function("_Smooth", &Smooth);
function("_Extrude", &Extrude);
function("_Revolve", &Revolve);
function("_LevelSet", &LevelSetJs);
function("_Tetrahedron", &Manifold::Tetrahedron);
function("_Smooth", &js::Smooth);
function("_Extrude", &Manifold::Extrude);
function("_Triangulate", &Triangulate);
function("_Revolve", &Manifold::Revolve);
function("_LevelSet", &man_js::LevelSet);
function("_Merge", &js::Merge);
function("_ReserveIDs", &Manifold::ReserveIDs);
function("_manifoldCompose", &Manifold::Compose);
function("_manifoldUnionN", &man_js::UnionN);
function("_manifoldDifferenceN", &man_js::DifferenceN);
function("_manifoldIntersectionN", &man_js::IntersectionN);
function("_manifoldCollectVertices", &man_js::CollectVertices);
function("_manifoldHullPoints",
select_overload<Manifold(const std::vector<glm::vec3>&)>(
&Manifold::Hull));
function("_unionN", &UnionN);
function("_differenceN", &DifferenceN);
function("_intersectionN", &IntersectionN);
function("_Compose", &Manifold::Compose);
function("setMinCircularAngle", &Manifold::SetMinCircularAngle);
function("setMinCircularEdgeLength", &Manifold::SetMinCircularEdgeLength);
function("setCircularSegments", &Manifold::SetCircularSegments);
function("getCircularSegments", &Manifold::GetCircularSegments);
// Quality Globals
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
function("setMinCircularEdgeLength", &Quality::SetMinCircularEdgeLength);
function("setCircularSegments", &Quality::SetCircularSegments);
function("getCircularSegments", &Quality::GetCircularSegments);
function("resetToCircularDefaults", &Quality::ResetToDefaults);
}
+500 -140
View File
@@ -17,6 +17,8 @@ Module.setup = function() {
if (_ManifoldInitialized) return;
_ManifoldInitialized = true;
// conversion utilities
function toVec(vec, list, f = x => x) {
if (list) {
for (let x of list) {
@@ -33,6 +35,21 @@ Module.setup = function() {
return result;
}
function vec2polygons(vec, f = x => x) {
const result = [];
const nPoly = vec.size();
for (let i = 0; i < nPoly; i++) {
const v = vec.get(i);
const nPts = v.size();
const poly = [];
for (let j = 0; j < nPts; j++) {
poly.push(f(v.get(j)));
}
result.push(poly);
}
return result;
}
function polygons2vec(polygons) {
if (polygons[0].length < 3) {
polygons = [polygons];
@@ -50,7 +67,15 @@ Module.setup = function() {
polygonsVec.delete();
}
function vararg2vec(vec) {
function vararg2vec2(vec) {
if (vec[0] instanceof Array) return {x: vec[0][0], y: vec[0][1]};
if (typeof (vec[0]) == 'number')
// default to 0
return {x: vec[0] || 0, y: vec[1] || 0};
return vec[0];
}
function vararg2vec3(vec) {
if (vec[0] instanceof Array)
return {x: vec[0][0], y: vec[0][1], z: vec[0][2]};
if (typeof (vec[0]) == 'number')
@@ -59,6 +84,123 @@ Module.setup = function() {
return vec[0];
}
function fillRuleToInt(fillRule) {
return fillRule == 'EvenOdd' ? 0 :
fillRule == 'NonZero' ? 1 :
fillRule == 'Negative' ? 3 :
/* Positive */ 2;
}
function joinTypeToInt(joinType) {
return joinType == 'Round' ? 1 : joinType == 'Miter' ? 2 : /* Square */ 0;
}
// CrossSection methods
const CrossSectionCtor = Module.CrossSection;
function cross(polygons, fillRule = 'Positive') {
if (polygons instanceof CrossSectionCtor) {
return polygons;
} else {
const polygonsVec = polygons2vec(polygons);
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
disposePolygons(polygonsVec);
return cs;
}
};
Module.CrossSection.prototype.translate = function(...vec) {
return this._Translate(vararg2vec2(vec));
};
Module.CrossSection.prototype.scale = function(vec) {
// if only one factor provided, scale both x and y with it
if (typeof vec == 'number') {
return this._Scale({x: vec, y: vec});
}
return this._Scale(vararg2vec2([vec]));
};
Module.CrossSection.prototype.mirror = function(vec) {
return this._Mirror(vararg2vec2([vec]));
};
Module.CrossSection.prototype.warp = function(func) {
const wasmFuncPtr = addFunction(function(vec2Ptr) {
const x = getValue(vec2Ptr, 'float');
const y = getValue(vec2Ptr + 4, 'float');
const vert = [x, y];
func(vert);
setValue(vec2Ptr, vert[0], 'float');
setValue(vec2Ptr + 4, vert[1], 'float');
}, 'vi');
const out = this._Warp(wasmFuncPtr);
removeFunction(wasmFuncPtr);
return out;
};
Module.CrossSection.prototype.decompose = function() {
const vec = this._Decompose();
const result = fromVec(vec);
vec.delete();
return result;
};
Module.CrossSection.prototype.bounds = function() {
const result = this._Bounds();
return {
min: ['x', 'y'].map(f => result.min[f]),
max: ['x', 'y'].map(f => result.max[f]),
};
};
Module.CrossSection.prototype.offset = function(
delta, joinType = 'Square', miterLimit = 2.0, circularSegments = 0) {
return this._Offset(
delta, joinTypeToInt(joinType), miterLimit, circularSegments);
};
Module.CrossSection.prototype.extrude = function(
height, nDivisions = 0, twistDegrees = 0.0, scaleTop = [1.0, 1.0],
center = false) {
scaleTop = vararg2vec2([scaleTop]);
const man =
Module._Extrude(this, height, nDivisions, twistDegrees, scaleTop);
return (center ? man.translate([0., 0., -height / 2.]) : man);
};
Module.CrossSection.prototype.revolve = function(
circularSegments = 0, revolveDegrees = 360.0) {
return Module._Revolve(this, circularSegments, revolveDegrees);
};
Module.CrossSection.prototype.add = function(other) {
return this._add(cross(other));
};
Module.CrossSection.prototype.subtract = function(other) {
return this._subtract(cross(other));
};
Module.CrossSection.prototype.intersect = function(other) {
return this._intersect(cross(other));
};
Module.CrossSection.prototype.toPolygons = function() {
const vec = this._ToPolygons();
const result = vec2polygons(vec);
vec.delete();
return result;
};
// Manifold methods
Module.Manifold.prototype.smoothOut = function(
minSharpAngle = 60, minSmoothness = 0) {
return this._SmoothOut(minSharpAngle, minSmoothness);
};
Module.Manifold.prototype.warp = function(func) {
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'float');
@@ -72,35 +214,90 @@ Module.setup = function() {
}, 'vi');
const out = this._Warp(wasmFuncPtr);
removeFunction(wasmFuncPtr);
const status = out.status();
if (status.value !== 0) {
throw new Module.ManifoldError(status.value);
}
return out;
};
// note that the matrix is using column major (same as glm)
Module.Manifold.prototype.transform = function(mat) {
console.assert(mat.length == 4, 'expects a 3x4 matrix');
const vec = new Module.Vector_f32();
for (let col of mat) {
console.assert(col.length == 3, 'expects a 3x4 matrix');
for (let x of col) mat.push_back(x);
Module.Manifold.prototype.calculateNormals = function(
normalIdx, minSharpAngle = 60) {
return this._CalculateNormals(normalIdx, minSharpAngle);
};
Module.Manifold.prototype.setProperties = function(numProp, func) {
const oldNumProp = this.numProp();
const wasmFuncPtr = addFunction(function(newPtr, vec3Ptr, oldPtr) {
const newProp = [];
for (let i = 0; i < numProp; ++i) {
newProp[i] = getValue(newPtr + 4 * i, 'float');
}
const pos = [];
for (let i = 0; i < 3; ++i) {
pos[i] = getValue(vec3Ptr + 4 * i, 'float');
}
const oldProp = [];
for (let i = 0; i < oldNumProp; ++i) {
oldProp[i] = getValue(oldPtr + 4 * i, 'float');
}
func(newProp, pos, oldProp);
for (let i = 0; i < numProp; ++i) {
setValue(newPtr + 4 * i, newProp[i], 'float');
}
}, 'viii');
const out = this._SetProperties(numProp, wasmFuncPtr);
removeFunction(wasmFuncPtr);
return out;
};
Module.Manifold.prototype.translate = function(...vec) {
return this._Translate(vararg2vec3(vec));
};
Module.Manifold.prototype.rotate = function(xOrVec, y, z) {
if (Array.isArray(xOrVec)) {
return this._Rotate(...xOrVec);
} else {
return this._Rotate(xOrVec, y || 0, z || 0);
}
const result = this._Transform(vec);
};
Module.Manifold.prototype.scale = function(vec) {
// if only one factor provided, scale all three dimensions (xyz) with it
if (typeof vec == 'number') {
return this._Scale({x: vec, y: vec, z: vec});
}
return this._Scale(vararg2vec3([vec]));
};
Module.Manifold.prototype.mirror = function(vec) {
return this._Mirror(vararg2vec3([vec]));
};
Module.Manifold.prototype.trimByPlane = function(normal, offset = 0.) {
return this._TrimByPlane(vararg2vec3([normal]), offset);
};
Module.Manifold.prototype.slice = function(height = 0.) {
return this._Slice(height);
};
Module.Manifold.prototype.split = function(manifold) {
const vec = this._Split(manifold);
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.translate = function(...vec) {
return this._Translate(vararg2vec(vec));
};
Module.Manifold.prototype.rotate = function(vec) {
return this._Rotate(...vec);
};
Module.Manifold.prototype.scale = function(vec) {
if (typeof vec == 'number') {
return this._Scale({x: vec, y: vec, z: vec});
}
return this._Scale(vararg2vec([vec]));
Module.Manifold.prototype.splitByPlane = function(normal, offset = 0.) {
const vec = this._SplitByPlane(vararg2vec3([normal]), offset);
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.decompose = function() {
@@ -110,82 +307,6 @@ Module.setup = function() {
return result;
};
Module.Manifold.prototype.getCurvature = function() {
const result = this._getCurvature();
const oldMeanCurvature = result.vertMeanCurvature;
const oldGaussianCurvature = result.vertGaussianCurvature;
result.vertMeanCurvature = fromVec(oldMeanCurvature);
result.vertGaussianCurvature = fromVec(oldGaussianCurvature);
oldMeanCurvature.delete();
oldGaussianCurvature.delete();
return result;
};
class Mesh {
constructor({
triVerts = new Uint32Array(),
vertPos = new Float32Array(),
vertNormal,
halfedgeTangent
} = {}) {
this.triVerts = triVerts;
this.vertPos = vertPos;
this.vertNormal = vertNormal;
this.halfedgeTangent = halfedgeTangent;
}
get numTri() {
return this.triVerts.length / 3;
}
get numVert() {
return this.vertPos.length / 3;
}
verts(tri) {
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
}
position(vert) {
return this.vertPos.subarray(3 * vert, 3 * (vert + 1));
}
normal(vert) {
return this.vertNormal.subarray(3 * vert, 3 * (vert + 1));
}
tangent(halfedge) {
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
}
}
Module.Mesh = Mesh;
Module.Manifold.prototype.getMesh = function() {
return new Mesh(this._GetMeshJS());
};
Module.Manifold.prototype.getMeshRelation = function() {
const result = this._getMeshRelation();
const oldBarycentric = result.barycentric;
const oldTriBary = result.triBary;
const conversion1 = v => [v.x, v.y, v.z];
const conversion2 = v => [v[0], v[1], v[2]];
const conversion3 = v => {
return {
meshID: v.meshID,
originalID: v.originalID,
tri: v.tri,
vertBary: conversion2(v.vertBary)
};
};
result.barycentric = fromVec(oldBarycentric, conversion1);
result.triBary = fromVec(oldTriBary, conversion3);
oldBarycentric.delete();
oldTriBary.delete();
return result;
};
Module.Manifold.prototype.boundingBox = function() {
const result = this._boundingBox();
return {
@@ -194,26 +315,120 @@ Module.setup = function() {
};
};
class Mesh {
constructor({
numProp = 3,
triVerts = new Uint32Array(),
vertProperties = new Float32Array(),
mergeFromVert,
mergeToVert,
runIndex,
runOriginalID,
faceID,
halfedgeTangent,
runTransform
} = {}) {
this.numProp = numProp;
this.triVerts = triVerts;
this.vertProperties = vertProperties;
this.mergeFromVert = mergeFromVert;
this.mergeToVert = mergeToVert;
this.runIndex = runIndex;
this.runOriginalID = runOriginalID;
this.faceID = faceID;
this.halfedgeTangent = halfedgeTangent;
this.runTransform = runTransform;
}
get numTri() {
return this.triVerts.length / 3;
}
get numVert() {
return this.vertProperties.length / this.numProp;
}
get numRun() {
return this.runOriginalID.length;
}
merge() {
const {changed, mesh} = Module._Merge(this);
Object.assign(this, {...mesh});
return changed;
}
verts(tri) {
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
}
position(vert) {
return this.vertProperties.subarray(numProp * vert, numProp * vert + 3);
}
extras(vert) {
return this.vertProperties.subarray(
numProp * vert + 3, numProp * (vert + 1));
}
tangent(halfedge) {
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
}
transform(run) {
const mat4 = new Array(16);
for (const col of [0, 1, 2, 3]) {
for (const row of [0, 1, 2]) {
mat4[4 * col + row] = this.runTransform[12 * run + 3 * col + row];
}
}
mat4[15] = 1;
return mat4;
}
}
Module.Mesh = Mesh;
Module.Manifold.prototype.getMesh = function(normalIdx = [0, 0, 0]) {
if (normalIdx instanceof Array)
normalIdx = {0: normalIdx[0], 1: normalIdx[1], 2: normalIdx[2]};
return new Mesh(this._GetMeshJS(normalIdx));
};
Module.ManifoldError = function ManifoldError(code, ...args) {
let message = 'Unknown error';
switch (code) {
case Module.status.NON_FINITE_VERTEX.value:
case Module.status.NonFiniteVertex.value:
message = 'Non-finite vertex';
break;
case Module.status.NOT_MANIFOLD.value:
case Module.status.NotManifold.value:
message = 'Not manifold';
break;
case Module.status.VERTEX_INDEX_OUT_OF_BOUNDS.value:
case Module.status.VertexOutOfBounds.value:
message = 'Vertex index out of bounds';
break;
case Module.status.PROPERTIES_WRONG_LENGTH.value:
case Module.status.PropertiesWrongLength.value:
message = 'Properties have wrong length';
break;
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
message = 'Tri properties have wrong length';
case Module.status.MissingPositionProperties.value:
message = 'Less than three properties';
break;
case Module.status.TRI_PROPERTIES_OUT_OF_BOUNDS.value:
message = 'Tri properties out of bounds';
case Module.status.MergeVectorsDifferentLengths.value:
message = 'Merge vectors have different lengths';
break;
case Module.status.MergeIndexOutOfBounds.value:
message = 'Merge index out of bounds';
break;
case Module.status.TransformWrongLength.value:
message = 'Transform vector has wrong length';
break;
case Module.status.RunIndexWrongLength.value:
message = 'Run index vector has wrong length';
break;
case Module.status.FaceIDWrongLength.value:
message = 'Face ID vector has wrong length';
case Module.status.InvalidConstruction.value:
message = 'Manifold constructed with invalid parameters';
}
const base = Error.apply(this, [message, ...args]);
@@ -228,6 +443,95 @@ Module.setup = function() {
{value: Module.ManifoldError, writable: true, configurable: true}
});
// CrossSection Constructors
Module.CrossSection = function(polygons, fillRule = 'Positive') {
const polygonsVec = polygons2vec(polygons);
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
disposePolygons(polygonsVec);
return cs;
};
Module.CrossSection.ofPolygons = function(polygons, fillRule = 'Positive') {
return new Module.CrossSection(polygons, fillRule);
};
Module.CrossSection.square = function(...args) {
let size = undefined;
if (args.length == 0)
size = {x: 1, y: 1};
else if (typeof args[0] == 'number')
size = {x: args[0], y: args[0]};
else
size = vararg2vec2(args);
const center = args[1] || false;
return Module._Square(size, center);
};
Module.CrossSection.circle = function(radius, circularSegments = 0) {
return Module._Circle(radius, circularSegments);
};
// allows args to be either CrossSection or polygons (constructed with
// Positive fill)
function crossSectionBatchbool(name) {
return function(...args) {
if (args.length == 1) args = args[0];
const v = new Module.Vector_crossSection();
for (const cs of args) v.push_back(cross(cs));
const result = Module['_crossSection' + name](v);
v.delete();
return result;
};
}
Module.CrossSection.compose = crossSectionBatchbool('Compose');
Module.CrossSection.union = crossSectionBatchbool('UnionN');
Module.CrossSection.difference = crossSectionBatchbool('DifferenceN');
Module.CrossSection.intersection = crossSectionBatchbool('IntersectionN');
function pushVec2(vec, ps) {
toVec(vec, ps, p => {
if (p instanceof Array) return {x: p[0], y: p[1]};
return p;
})
}
Module.CrossSection.hull = function(...args) {
if (args.length == 1) args = args[0];
let pts = new Module.Vector_vec2();
for (const cs of args) {
if (cs instanceof CrossSectionCtor) {
Module._crossSectionCollectVertices(pts, cs);
} else if (
cs instanceof Array && cs.length == 2 && typeof cs[0] == 'number') {
pts.push_back({x: cs[0], y: cs[1]});
} else if (cs.x) {
pts.push_back(cs);
} else {
const wrap =
((cs[0].length == 2 && typeof cs[0][0] == 'number') || cs[0].x);
const polys = wrap ? [cs] : cs;
for (const poly of polys) pushVec2(pts, poly);
}
}
const result = Module._crossSectionHullPoints(pts);
pts.delete();
return result;
};
Module.CrossSection.prototype = Object.create(CrossSectionCtor.prototype);
// Because the constructor and prototype are being replaced, instanceof will
// not work as desired unless we refer back to the original like this
Object.defineProperty(Module.CrossSection, Symbol.hasInstance, {
get: () => (t) => {
return (t instanceof CrossSectionCtor);
}
});
// Manifold Constructors
const ManifoldCtor = Module.Manifold;
Module.Manifold = function(mesh) {
const manifold = new ManifoldCtor(mesh);
@@ -240,32 +544,38 @@ Module.setup = function() {
return manifold;
};
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
Module.Manifold.ofMesh = function(mesh) {
return new Module.Manifold(mesh);
};
Module.cube = function(...args) {
Module.Manifold.tetrahedron = function() {
return Module._Tetrahedron();
};
Module.Manifold.cube = function(...args) {
let size = undefined;
if (args.length == 0)
size = {x: 1, y: 1, z: 1};
else if (typeof args[0] == 'number')
size = {x: args[0], y: args[0], z: args[0]};
else
size = vararg2vec(args);
size = vararg2vec3(args);
const center = args[1] || false;
return Module._Cube(size, center);
};
Module.cylinder = function(
Module.Manifold.cylinder = function(
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
center = false) {
return Module._Cylinder(
height, radiusLow, radiusHigh, circularSegments, center);
};
Module.sphere = function(radius, circularSegments = 0) {
Module.Manifold.sphere = function(radius, circularSegments = 0) {
return Module._Sphere(radius, circularSegments);
};
Module.smooth = function(mesh, sharpenedEdges = []) {
Module.Manifold.smooth = function(mesh, sharpenedEdges = []) {
const sharp = new Module.Vector_smoothness();
toVec(sharp, sharpenedEdges);
const result = Module._Smooth(mesh, sharp);
@@ -273,33 +583,51 @@ Module.setup = function() {
return result;
};
Module.extrude = function(
Module.Manifold.extrude = function(
polygons, height, nDivisions = 0, twistDegrees = 0.0,
scaleTop = [1.0, 1.0]) {
if (scaleTop instanceof Array) scaleTop = {x: scaleTop[0], y: scaleTop[1]};
const polygonsVec = polygons2vec(polygons);
const result = Module._Extrude(
polygonsVec, height, nDivisions, twistDegrees, scaleTop);
disposePolygons(polygonsVec);
return result;
scaleTop = [1.0, 1.0], center = false) {
const cs = (polygons instanceof CrossSectionCtor) ?
polygons :
Module.CrossSection(polygons, 'Positive');
return cs.extrude(height, nDivisions, twistDegrees, scaleTop, center);
};
Module.revolve = function(polygons, circularSegments = 0) {
const polygonsVec = polygons2vec(polygons);
const result = Module._Revolve(polygonsVec, circularSegments);
disposePolygons(polygonsVec);
return result;
Module.Manifold.revolve = function(
polygons, circularSegments = 0, revolveDegrees = 360.0) {
const cs = (polygons instanceof CrossSectionCtor) ?
polygons :
Module.CrossSection(polygons, 'Positive');
return cs.revolve(circularSegments, revolveDegrees);
};
Module.compose = function(manifolds) {
Module.Manifold.reserveIDs = function(n) {
return Module._ReserveIDs(n);
};
Module.Manifold.compose = function(manifolds) {
const vec = new Module.Vector_manifold();
toVec(vec, manifolds);
const result = Module._Compose(vec);
const result = Module._manifoldCompose(vec);
vec.delete();
return result;
};
Module.levelSet = function(sdf, bounds, edgeLength, level = 0) {
function manifoldBatchbool(name) {
return function(...args) {
if (args.length == 1) args = args[0];
const v = new Module.Vector_manifold();
for (const m of args) v.push_back(m);
const result = Module['_manifold' + name + 'N'](v);
v.delete();
return result;
};
}
Module.Manifold.union = manifoldBatchbool('Union');
Module.Manifold.difference = manifoldBatchbool('Difference');
Module.Manifold.intersection = manifoldBatchbool('Intersection');
Module.Manifold.levelSet = function(sdf, bounds, edgeLength, level = 0) {
const bounds2 = {
min: {x: bounds.min[0], y: bounds.min[1], z: bounds.min[2]},
max: {x: bounds.max[0], y: bounds.max[1], z: bounds.max[2]},
@@ -316,18 +644,50 @@ Module.setup = function() {
return out;
};
function batchbool(name) {
return function(...args) {
if (args.length == 1) args = args[0];
const v = new Module.Vector_manifold();
for (const m of args) v.push_back(m);
const result = Module['_' + name + 'N'](v);
v.delete();
return result;
}
function pushVec3(vec, ps) {
toVec(vec, ps, p => {
if (p instanceof Array) return {x: p[0], y: p[1], z: p[2]};
return p;
})
}
Module.union = batchbool('union');
Module.difference = batchbool('difference');
Module.intersection = batchbool('intersection');
Module.Manifold.hull = function(...args) {
if (args.length == 1) args = args[0];
let pts = new Module.Vector_vec3();
for (const m of args) {
if (m instanceof ManifoldCtor) {
Module._manifoldCollectVertices(pts, m);
} else if (
m instanceof Array && m.length == 3 && typeof m[0] == 'number') {
pts.push_back({x: m[0], y: m[1], z: m[2]});
} else if (m.x) {
pts.push_back(m);
} else {
pushVec3(pts, m);
}
}
const result = Module._manifoldHullPoints(pts);
pts.delete();
return result;
};
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
// Because the constructor and prototype are being replaced, instanceof will
// not work as desired unless we refer back to the original like this
Object.defineProperty(Module.Manifold, Symbol.hasInstance, {
get: () => (t) => {
return (t instanceof ManifoldCtor);
}
});
// Top-level functions
Module.triangulate = function(polygons, precision = -1) {
const polygonsVec = polygons2vec(polygons);
const result = fromVec(
Module._Triangulate(polygonsVec, precision), (x) => [x[0], x[1], x[2]]);
disposePolygons(polygonsVec);
return result;
};
};
+1
View File
@@ -0,0 +1 @@
manifold*
+1
View File
@@ -0,0 +1 @@
declare module '@jscadui/3mf-export';
+45
View File
@@ -0,0 +1,45 @@
# WASM/JS Examples
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
discussion of the
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
of this repository.
+153 -12
View File
@@ -65,7 +65,7 @@ button:hover {
filter: brightness(0.9);
}
input {
.name {
border: none;
height: 36px;
background-color: transparent;
@@ -77,12 +77,12 @@ input {
-webkit-font-smoothing: antialiased;
}
input:focus {
.name:focus {
outline: none;
}
p {
margin: 1em;
margin: 18px;
text-align: left;
}
@@ -128,20 +128,16 @@ model-viewer {
transform: translate3d(-50%, -50%, 0);
}
.margin {
.header .margin {
font-size: 16px;
margin: 1em;
}
#file {
width: 250px;
margin: 18px 8px;
}
.dropdown {
display: none;
position: absolute;
z-index: 1;
width: 250px;
width: 100%;
max-height: 80vh;
overflow-y: auto;
}
@@ -150,6 +146,10 @@ model-viewer {
display: block;
}
.hide {
display: none;
}
.uparrow {
display: inline-block;
position: relative;
@@ -168,28 +168,89 @@ model-viewer {
position: relative;
}
.top-edit {
display: inline-block;
vertical-align: middle;
height: 36px;
top: 18px;
border: none;
border-radius: 18px;
}
.top-icon {
display: inline-block;
vertical-align: middle;
width: 36px;
background-size: 24px;
background-repeat: no-repeat;
background-position: center;
background-color: transparent;
}
.squish {
margin-right: -12px;
}
.icon {
display: inline-block;
background-color: transparent;
position: absolute;
width: 36px;
top: 0px;
right: 0px;
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
}
.undo {
background-image: url(/icons/undo.png);
}
.redo {
background-image: url(/icons/redo.png);
}
.star {
background-image: url(/icons/star.png);
}
.share {
background-image: url(/icons/share.png);
}
.cancel {
background-image: url(icons/close.png);
background-image: url(/icons/close.png);
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
}
.edit {
background-image: url(/icons/pencil.png);
right: 30px;
}
.trash {
background-image: url(/icons/trash.png);
}
.play {
background-image: url(/icons/play.png);
}
.pause {
background-image: url(/icons/pause.png);
}
.icon:hover {
background-color: rgba(0, 0, 0, 0.2);
}
.top-icon:hover {
background-color: rgba(0, 0, 0, 0.2);
}
.blue {
background-color: rgb(148, 221, 255);
}
@@ -202,7 +263,61 @@ model-viewer {
background-color: rgb(255, 177, 177);
}
@media only screen and (max-width: 600px) {
#file-margin {
position: relative;
width: 250px;
}
#save {
width: 125px;
}
#animation {
display: none;
align-items: center;
position: absolute;
width: 100%;
bottom: 0px;
margin: 2px;
}
#play {
position: relative;
background-color: #ccc;
margin: 8px;
}
.slider {
position: relative;
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 10px;
border-radius: 5px;
margin-right: 16px;
background: #ffffff55;
outline: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
border-radius: 50%;
background: #ccc;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 25px;
height: 25px;
border-radius: 50%;
background: #ccc;
cursor: pointer;
}
@media only screen and (max-width: 820px) {
.container {
flex-direction: column;
width: 100%;
@@ -210,5 +325,31 @@ model-viewer {
.col {
width: 100%;
height: 50%;
}
#file-margin {
width: calc(100% - 32px);
}
p {
margin: .2em 1em;
text-align: center;
}
.header .margin {
margin: 0.2em 16px;
}
#button-container {
flex-direction: row;
margin-left: 8px;
margin-right: 8px;
width: calc(100% - 16px);
}
#button-container .margin {
width: calc(50% - 16px);
margin: 8px;
}
}
+214 -48
View File
@@ -12,26 +12,68 @@
// See the License for the specific language governing permissions and
// limitations under the License.
const exampleFunctions = examples.functionBodies;
import ManifoldWorker from './worker-wrapper?worker';
const CODE_START = '<code>';
// Loaded globally by examples.js
const exampleFunctions = self.examples.functionBodies;
if (navigator.serviceWorker) {
navigator.serviceWorker.register(
'/service-worker.js', {scope: './index.html'});
}
let editor = undefined;
// Edit UI ------------------------------------------------------------
const undoButton = document.querySelector('#undo');
const redoButton = document.querySelector('#redo');
const formatButton = document.querySelector('#format');
const shareButton = document.querySelector('#share');
undoButton.onclick = () => editor.trigger('ignored', 'undo');
redoButton.onclick = () => editor.trigger('ignored', 'redo');
formatButton.onclick = () =>
editor.trigger('ignored', 'editor.action.formatDocument');
shareButton.onclick = () => {
const url = new URL(window.location.toString());
url.hash =
'#' +
encodeURIComponent(
currentFileElement.textContent + CODE_START + editor.getValue());
navigator.clipboard.writeText(url.toString());
console.log('Shareable link copied to clipboard!');
console.log('Consider shortening this URL using tinyURL.com');
};
// File UI ------------------------------------------------------------
const fileButton = document.querySelector('#file');
const currentElement = document.querySelector('#current');
const arrow = document.querySelector('.uparrow');
const dropdown = document.querySelector('.dropdown');
const currentFileElement = document.querySelector('#current');
const fileArrow = document.querySelector('#file .uparrow');
const fileDropdown = document.querySelector('#fileDropdown');
const saveContainer = document.querySelector('#save');
const saveDropdown = document.querySelector('#saveDropdown');
const saveArrow = document.querySelector('#save .uparrow');
const hideDropdown = function() {
dropdown.classList.remove('show');
arrow.classList.remove('down');
fileDropdown.classList.remove('show');
saveDropdown.classList.remove('show');
fileArrow.classList.remove('down');
saveArrow.classList.remove('down');
};
const toggleDropdown = function(event) {
const toggleFileDropdown = function(event) {
event.stopPropagation();
dropdown.classList.toggle('show');
arrow.classList.toggle('down');
fileDropdown.classList.toggle('show');
fileArrow.classList.toggle('down');
};
fileButton.onclick = toggleDropdown;
const toggleSaveDropdown = function(event) {
event.stopPropagation();
saveDropdown.classList.toggle('show');
saveArrow.classList.toggle('down');
};
fileButton.onclick = toggleFileDropdown;
saveArrow.parentElement.onclick = toggleSaveDropdown;
document.body.onclick = hideDropdown;
const prefix = 'ManifoldCAD';
@@ -54,7 +96,7 @@ function nthKey(n) {
function saveCurrent() {
if (editor) {
const currentName = currentElement.textContent;
const currentName = currentFileElement.textContent;
if (!exampleFunctions.get(currentName)) {
setScript(currentName, editor.getValue());
}
@@ -69,11 +111,12 @@ let isExample = true;
function switchTo(scriptName) {
if (editor) {
switching = true;
currentElement.textContent = scriptName;
currentFileElement.textContent = scriptName;
setScript('currentName', scriptName);
const code =
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
isExample = exampleFunctions.get(scriptName) != null;
const code = isExample ? exampleFunctions.get(scriptName).substring(1) :
getScript(scriptName) ?? '';
window.location.hash = '#' + scriptName;
editor.setValue(code);
}
}
@@ -93,6 +136,10 @@ function createDropdownItem(name) {
saveCurrent();
switchTo(label.textContent);
};
// Stop text input spaces from triggering the button
button.onkeyup = function(event) {
event.preventDefault();
};
return button;
}
@@ -115,8 +162,7 @@ function uniqueName(name) {
function addEdit(button) {
const label = button.firstChild;
const edit = addIcon(button);
edit.style.backgroundImage = 'url(icons/pencil.png)';
edit.style.right = '30px';
edit.classList.add('edit');
edit.onclick = function(event) {
event.stopPropagation();
@@ -124,6 +170,7 @@ function addEdit(button) {
const code = getScript(oldName);
const form = document.createElement('form');
const inputElement = document.createElement('input');
inputElement.classList.add('name');
inputElement.value = oldName;
label.textContent = '';
button.appendChild(form);
@@ -137,8 +184,8 @@ function addEdit(button) {
if (!input) return;
const newName = uniqueName(input);
label.textContent = newName;
if (currentElement.textContent == oldName) {
currentElement.textContent = newName;
if (currentFileElement.textContent == oldName) {
currentFileElement.textContent = newName;
}
removeScript(oldName);
setScript(newName, code);
@@ -156,8 +203,7 @@ function addEdit(button) {
};
const trash = addIcon(button);
trash.style.backgroundImage = 'url(icons/trash.png)';
trash.style.right = '0px';
trash.classList.add('trash');
let lastClick = 0;
trash.onclick = function(event) {
@@ -172,7 +218,7 @@ function addEdit(button) {
}, {once: true});
} else if (performance.now() - lastClick > 500) {
removeScript(label.textContent);
if (currentElement.textContent == label.textContent) {
if (currentFileElement.textContent == label.textContent) {
switchTo('Intro');
}
const container = button.parentElement;
@@ -182,16 +228,16 @@ function addEdit(button) {
}
const newButton = document.querySelector('#new');
function newItem(code) {
const name = uniqueName('New Script');
function newItem(code, scriptName = undefined) {
const name = uniqueName(scriptName ?? 'New Script');
setScript(name, code);
const nextButton = createDropdownItem(name);
newButton.insertAdjacentElement('afterend', nextButton.parentElement);
addEdit(nextButton);
nextButton.click();
return {button: nextButton, name};
};
newButton.onclick = function() {
newItem('');
newItem('').button.click();
};
const runButton = document.querySelector('#compile');
@@ -204,20 +250,53 @@ function initializeRun() {
if (autoExecute) {
runButton.click();
} else {
poster.textContent = 'Auto-run disabled due to prior failure';
poster.textContent = 'Auto-run disabled';
}
}
// Editor ------------------------------------------------------------
let tsWorker = undefined;
async function getManifoldDTS() {
const global = await fetch('/manifold-global-types.d.ts')
.then(response => response.text());
const encapsulated = await fetch('/manifold-encapsulated-types.d.ts')
.then(response => response.text());
return `
${global.replaceAll('export', '')}
${encapsulated.replace(/^import.*$/gm, '').replaceAll('export', 'declare')}
declare interface ManifoldToplevel {
CrossSection: typeof T.CrossSection;
Manifold: typeof T.Manifold;
Mesh: typeof T.Mesh;
triangulate: typeof T.triangulate;
setMinCircularAngle: typeof T.setMinCircularAngle;
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
setCircularSegments: typeof T.setCircularSegments;
getCircularSegments: typeof T.getCircularSegments;
resetToCircularDefaults: typeof T.resetToCircularDefaults;
setup: () => void;
}
declare const module: ManifoldToplevel;
`;
}
async function getEditorDTS() {
const global = await fetch('/editor.d.ts').then(response => response.text());
return `${global.replace(/^import.*$/gm, '')}`;
}
require.config({
paths:
{vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs'}
});
require(['vs/editor/editor.main'], async function() {
const content =
await fetch('manifold.d.ts').then(response => response.text());
monaco.languages.typescript.typescriptDefaults.addExtraLib(content);
monaco.languages.typescript.typescriptDefaults.addExtraLib(
await getManifoldDTS());
monaco.languages.typescript.typescriptDefaults.addExtraLib(
await getEditorDTS());
editor = monaco.editor.create(
document.getElementById('editor'),
{language: 'typescript', automaticLayout: true});
@@ -227,10 +306,10 @@ require(['vs/editor/editor.main'], async function() {
for (const [name] of exampleFunctions) {
const button = createDropdownItem(name);
dropdown.appendChild(button.parentElement);
fileDropdown.appendChild(button.parentElement);
}
let currentName = currentElement.textContent;
let currentName = currentFileElement.textContent;
for (let i = 0; i < window.localStorage.length; i++) {
const key = nthKey(i);
@@ -245,7 +324,24 @@ require(['vs/editor/editor.main'], async function() {
addEdit(button);
}
}
switchTo(currentName);
if (window.location.hash.length > 0) {
const fragment = decodeURIComponent(window.location.hash.substring(1));
const codeIdx = fragment.indexOf(CODE_START);
if (codeIdx != -1) {
autoExecute = true;
const name = fragment.substring(0, codeIdx);
switchTo(
newItem(fragment.substring(codeIdx + CODE_START.length), name).name);
} else {
if (fragment != currentName) {
autoExecute = true;
}
switchTo(fragment);
}
} else {
switchTo(currentName);
}
if (manifoldInitialized) {
initializeRun();
@@ -255,11 +351,12 @@ require(['vs/editor/editor.main'], async function() {
runButton.disabled = false;
if (switching) {
switching = false;
editor.setScrollTop(0);
return;
}
if (isExample) {
const cursor = editor.getPosition();
newItem(editor.getValue());
newItem(editor.getValue()).button.click();
editor.setPosition(cursor);
}
});
@@ -269,6 +366,51 @@ require(['vs/editor/editor.main'], async function() {
};
});
// Animation ------------------------------------------------------------
const mv = document.querySelector('model-viewer');
const animationContainer = document.querySelector('#animation');
const playButton = document.querySelector('#play');
const scrubber = document.querySelector('#scrubber');
let paused = false;
mv.addEventListener('load', () => {
const hasAnimation = mv.availableAnimations.length > 0;
animationContainer.style.display = hasAnimation ? 'flex' : 'none';
if (hasAnimation) {
play();
}
});
function play() {
mv.play();
playButton.classList.remove('play');
playButton.classList.add('pause');
paused = false;
scrubber.classList.add('hide');
}
function pause() {
mv.pause();
playButton.classList.remove('pause');
playButton.classList.add('play');
paused = true;
scrubber.max = mv.duration;
scrubber.value = mv.currentTime;
scrubber.classList.remove('hide');
}
playButton.onclick = function() {
if (paused) {
play();
} else {
pause();
}
};
scrubber.oninput = function() {
mv.currentTime = scrubber.value;
};
// Execution ------------------------------------------------------------
const consoleElement = document.querySelector('#console');
const oldLog = console.log;
@@ -304,12 +446,14 @@ function finishRun() {
`Took ${(Math.round((t1 - t0) / 10) / 100).toLocaleString()} seconds`);
}
const mv = document.querySelector('model-viewer');
let objectURL = null;
const output = {
glbURL: null,
threeMFURL: null
};
let manifoldWorker = null;
function createWorker() {
manifoldWorker = new Worker('worker.js');
manifoldWorker = new ManifoldWorker();
manifoldWorker.onmessage = function(e) {
if (e.data == null) {
if (tsWorker != null && !manifoldInitialized) {
@@ -327,14 +471,22 @@ function createWorker() {
finishRun();
runButton.disabled = true;
setScript('safe', 'true');
URL.revokeObjectURL(objectURL);
objectURL = e.data.objectURL;
mv.src = objectURL;
if (objectURL == null) {
if (output.threeMFURL != null) {
URL.revokeObjectURL(output.threeMFURL);
output.threeMFURL = null;
}
URL.revokeObjectURL(output.glbURL);
output.glbURL = e.data.glbURL;
output.threeMFURL = e.data.threeMFURL;
threemfButton.disabled = output.threeMFURL == null;
mv.src = output.glbURL;
if (output.glbURL == null) {
mv.showPoster();
poster.textContent = 'Error';
createWorker();
} else {
setScript('safe', 'true');
}
}
}
@@ -367,10 +519,24 @@ runButton.onclick = function() {
}
};
const downloadButton = document.querySelector('#download');
downloadButton.onclick = function() {
const link = document.createElement('a');
link.download = 'manifold.glb';
link.href = objectURL;
link.click();
};
function clickSave(saveButton, filename, outputName) {
const container = saveButton.parentElement;
return () => {
const oldSave = saveContainer.firstElementChild;
if (oldSave !== container) {
saveDropdown.insertBefore(oldSave, saveDropdown.firstElementChild);
saveContainer.insertBefore(container, saveDropdown);
container.appendChild(saveArrow.parentElement);
}
const link = document.createElement('a');
link.download = filename;
link.href = output[outputName];
link.click();
};
}
const glbButton = document.querySelector('#glb');
glbButton.onclick = clickSave(glbButton, 'manifold.glb', 'glbURL');
const threemfButton = document.querySelector('#threemf');
threemfButton.onclick = clickSave(threemfButton, 'manifold.3mf', 'threeMFURL');
-440
View File
@@ -1,440 +0,0 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(exports) {
exports.functions = {
Intro: function() {
// Write code in JavaScript or TypeScript and this editor will show the API
// docs. Type e.g. "box." to see the Manifold API. Type "Module." to see the
// static API - these functions can also be used bare. Use console.log() to
// print output (lower-right). This editor defines Z as up and units of mm.
const box = cube([100, 100, 100], true);
const ball = sphere(60, 100);
// You must name your final output "result".
const result = box.subtract(ball);
// All changes are automatically saved and restored between sessions.
// This app is purely local - there is no server communication.
// This means it will work equally well offline once loaded.
// To share your code with another browser/device, simply copy the text to a
// file.
// See the script drop-down above ("Intro") for usage examples.
return result;
},
TetrahedronPuzzle: function() {
// A tetrahedron cut into two identical halves that can screw together as a
// puzzle. This only outputs one of the halves. This demonstrates how
// redundant points along a polygon can be used to make twisted extrusions
// smoother. Based on the screw puzzle by George Hart:
// https://www.thingiverse.com/thing:186372
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
const gap = 0.2; // Spacing between the two halves to allow sliding.
const nDivisions =
50; // Number of divisions (both ways) in the screw surface.
const scale = edgeLength / (2 * Math.sqrt(2));
const tet = tetrahedron().scale(scale);
const box = [];
box.push([2, -2], [2, 2]);
for (let i = 0; i <= nDivisions; ++i) {
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
}
const screw = extrude(box, 2, nDivisions, 270)
.rotate([0, 0, -45])
.translate([0, 0, -1])
.scale(scale);
const result = tet.intersect(screw);
return result;
},
RoundedFrame: function() {
// Demonstrates how at 90-degree intersections, the sphere and cylinder
// facets match up perfectly, for any choice of global resolution
// parameters.
function roundedFrame(edgeLength, radius, circularSegments = 0) {
const edge = cylinder(edgeLength, radius, -1, circularSegments);
const corner = sphere(radius, circularSegments);
let edge1 = union(corner, edge);
edge1 = edge1.rotate([-90, 0, 0]).translate([
-edgeLength / 2, -edgeLength / 2, 0
]);
let edge2 = edge1.rotate([0, 0, 180]);
edge2 = union(edge2, edge1);
edge2 =
union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
let edge4 = edge2.rotate([0, 0, 90]);
edge4 = union(edge4, edge2);
let frame = edge4.translate([0, 0, -edgeLength / 2]);
frame = union(frame, frame.rotate([180, 0, 0]));
return frame;
}
setMinCircularAngle(3);
setMinCircularEdgeLength(0.5);
const result = roundedFrame(100, 10);
return result;
},
Heart: function() {
// Smooth, complex manifolds can be created using the warp() function. This
// example recreates the Exploitable Heart by Emmett Lalish:
// https://www.thingiverse.com/thing:6190
const func = (v) => {
const x2 = v[0] * v[0];
const y2 = v[1] * v[1];
const z = v[2];
const z2 = z * z;
const a = x2 + 9 / 4 * y2 + z2;
const b = z * z2 * (x2 + 9 / 80 * y2);
const a2 = a * a;
const a3 = a * a2;
const step = (r) => {
const r2 = r * r;
const r4 = r2 * r2;
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
// Derivative
const df = 6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
return f / df;
};
// Newton's method for root finding
let r = 1.5;
let dr = 1;
while (Math.abs(dr) > 0.0001) {
dr = step(r);
r -= dr;
}
// Update radius
v[0] *= r;
v[1] *= r;
v[2] *= r;
};
const ball = sphere(1, 200);
const heart = ball.warp(func);
const box = heart.boundingBox();
const result = heart.scale(100 / (box.max[0] - box.min[0]));
return result;
},
Scallop: function() {
// A smoothed manifold demonstrating selective edge sharpening with smooth()
// and refine(), see more details at:
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
const height = 10;
const radius = 30;
const offset = 20;
const wiggles = 12;
const sharpness = 0.8;
const n = 50;
const vertPos = [];
const triVerts = [];
vertPos.push(-offset, 0, height, -offset, 0, -height);
const sharpenedEdges = [];
const delta = 3.14159 / wiggles;
for (let i = 0; i < 2 * wiggles; ++i) {
const theta = (i - wiggles) * delta;
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
vertPos.push(
radius * Math.cos(theta), radius * Math.sin(theta),
amp * (i % 2 == 0 ? 1 : -1));
let j = i + 1;
if (j == 2 * wiggles) j = 0;
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
let halfedge = triVerts.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triVerts.push(0, 2 + i, 2 + j);
halfedge = triVerts.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triVerts.push(1, 2 + j, 2 + i);
}
const scallop = new Mesh();
scallop.triVerts = Uint32Array.from(triVerts);
scallop.vertPos = Float32Array.from(vertPos);
const result = smooth(scallop, sharpenedEdges).refine(n);
return result;
},
TorusKnot: function() {
// Creates a classic torus knot, defined as a string wrapping periodically
// around the surface of an imaginary donut. If p and q have a common factor
// then you will get multiple separate, interwoven knots. This is an example
// of using the warp() method, thus avoiding any direct handling of
// triangles.
// @param p The number of times the thread passes through the donut hole.
// @param q The number of times the thread circles the donut.
// @param majorRadius Radius of the interior of the imaginary donut.
// @param minorRadius Radius of the small cross-section of the imaginary
// donut.
// @param threadRadius Radius of the small cross-section of the actual
// object.
// @param circularSegments Number of linear segments making up the
// threadRadius circle. Default is getCircularSegments(threadRadius).
// @param linearSegments Number of segments along the length of the knot.
// Default makes roughly square facets.
function torusKnot(
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
linearSegments = 0) {
function gcd(a, b) {
return b == 0 ? a : gcd(b, a % b);
}
const kLoops = gcd(p, q);
p /= kLoops;
q /= kLoops;
const n = circularSegments > 2 ? circularSegments :
getCircularSegments(threadRadius);
const m = linearSegments > 2 ? linearSegments :
n * q * majorRadius / threadRadius;
const circle = [];
const dPhi = 2 * 3.14159 / n;
const offset = 2;
for (let i = 0; i < n; ++i) {
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
}
const point = new THREE.Vector3();
const axis = new THREE.Vector3();
const func =
(v) => {
const psi = q * Math.atan2(v[0], v[1]);
const theta = psi * p / q;
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
const phi = Math.atan2(x1 - offset, v[2]);
point.set(Math.cos(phi), 0, Math.sin(phi))
.multiplyScalar(threadRadius);
const r = majorRadius + minorRadius * Math.cos(theta);
point.applyAxisAngle(
axis.set(1, 0, 0), -Math.atan2(p * minorRadius, q * r));
point.x += minorRadius;
point.applyAxisAngle(axis.set(0, 1, 0), theta);
point.x += majorRadius;
point.applyAxisAngle(axis.set(0, 0, 1), psi);
v[0] = point.x;
v[1] = point.y;
v[2] = point.z;
}
let knot = revolve(circle, m).warp(func);
if (kLoops > 1) {
const knots = [];
for (let k = 0; k < kLoops; ++k) {
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
}
knot = compose(knots);
}
return knot;
}
// This recreates Matlab Knot by Emmett Lalish:
// https://www.thingiverse.com/thing:7080
const result = torusKnot(1, 3, 25, 10, 3.75);
return result;
},
MengerSponge: function() {
// This example demonstrates how symbolic perturbation correctly creates
// holes even though the subtracted objects are exactly coplanar.
function vec2add(a, b) {
return [a[0] + b[0], a[1] + b[1]];
}
function fractal(holes, hole, w, position, depth, maxDepth) {
w /= 3;
holes.push(
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
if (depth == maxDepth) return;
const offsets = [
[-w, -w], [-w, 0.0], [-w, w], [0.0, w], [w, w], [w, 0.0], [w, -w],
[0.0, -w]
];
for (let offset of offsets)
fractal(holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
}
function mengerSponge(n) {
let result = cube([1, 1, 1], true);
const holes = [];
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
const hole = compose(holes);
result = difference(result, hole);
result = difference(result, hole.rotate([90, 0, 0]));
result = difference(result, hole.rotate([0, 90, 0]));
return result;
}
const result = mengerSponge(3).scale(100);
return result;
},
StretchyBracelet: function() {
// Recreates Stretchy Bracelet by Emmett Lalish:
// https://www.thingiverse.com/thing:13505
function base(
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
outerRadius, cut, nCut, nDivision) {
function rotate(v, theta) {
return [
v[0] * Math.cos(theta) - v[1] * Math.sin(theta),
v[0] * Math.sin(theta) + v[1] * Math.cos(theta)
];
}
let b = cylinder(width, radius + twistRadius / 2);
const circle = [];
const dPhiDeg = 180 / nDivision;
for (let i = 0; i < 2 * nDivision; ++i) {
circle.push([
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
]);
}
let decor =
extrude(circle, width, nDivision, 180).scale([1, 0.5, 1]).translate([
0, radius, 0
]);
for (let i = 0; i < nDecor; i++)
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
const stretch = [];
const dPhiRad = 2 * Math.PI / nCut;
const p0 = [outerRadius, 0];
const p1 = [innerRadius, -cut];
const p2 = [innerRadius, cut];
for (let i = 0; i < nCut; ++i) {
stretch.push(rotate(p0, dPhiRad * i));
stretch.push(rotate(p1, dPhiRad * i));
stretch.push(rotate(p2, dPhiRad * i));
stretch.push(rotate(p0, dPhiRad * i));
}
b = intersection(extrude(stretch, width), b);
return b;
}
function stretchyBracelet(
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
nCut = 27, nDivision = 30) {
const twistRadius = Math.PI * radius / nDecor;
const decorRadius = twistRadius * 1.5;
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
const innerRadius = outerRadius - height;
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
const adjThickness = 0.5 * thickness * height / cut;
return difference(
base(
width, radius, decorRadius, twistRadius, nDecor,
innerRadius + thickness, outerRadius + adjThickness,
cut - adjThickness, nCut, nDivision),
base(
width, radius - thickness, decorRadius, twistRadius, nDecor,
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
nDivision));
}
const result = stretchyBracelet();
return result;
},
GyroidModule: function() {
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
// https://www.thingiverse.com/thing:25477. This sample demonstrates the use
// of a Signed Distance Function (SDF) to create smooth, complex manifolds.
const size = 20;
const n = 20;
const pi = 3.14159;
function gyroid(p) {
const x = p[0] - pi / 4;
const y = p[1] - pi / 4;
const z = p[2] - pi / 4;
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
Math.cos(z) * Math.sin(x);
}
function gyroidOffset(level) {
const period = 2 * pi;
const box = {
min: [-period, -period, -period],
max: [period, period, period]
};
return levelSet(gyroid, box, period / n, level).scale(size / period);
};
function rhombicDodecahedron() {
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
const result =
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
return result.intersect(box.rotate([0, 0, 45]));
}
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4))
result = result.subtract(gyroidOffset(0.4));
result = result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
return result;
}
};
exports.functionBodies = new Map();
for (const [func, code] of Object.entries(exports.functions)) {
const whole = code.toString();
const lines = whole.split('\n');
lines.splice(0, 1); // remove first line
lines.splice(-2, 2); // remove last two lines
// remove first four leading spaces
const body = '\n' + lines.map(l => l.slice(4)).join('\n');
const name =
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
exports.functionBodies.set(name, body);
};
})(typeof exports == 'undefined' ? this['examples'] = {} : exports);
+424
View File
@@ -0,0 +1,424 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Accessor, Document, Material, Mesh, Primitive, Texture, WebIO} from '@gltf-transform/core';
import {EXTManifold, ManifoldPrimitive} from './manifold-gltf';
import {Mesh as ManifoldMesh, MeshOptions} from './public/manifold-encapsulated-types';
export const attributeDefs = {
'POSITION': {type: Accessor.Type.VEC3, components: 3},
'NORMAL': {type: Accessor.Type.VEC3, components: 3},
'TANGENT': {type: Accessor.Type.VEC4, components: 4},
'TEXCOORD_0': {type: Accessor.Type.VEC2, components: 2},
'TEXCOORD_1': {type: Accessor.Type.VEC2, components: 2},
'COLOR_0': {type: Accessor.Type.VEC3, components: 3},
'JOINTS_0': {type: Accessor.Type.VEC4, components: 4},
'WEIGHTS_0': {type: Accessor.Type.VEC4, components: 4},
'SKIP_1': {type: null, components: 1},
'SKIP_2': {type: null, components: 2},
'SKIP_3': {type: null, components: 3},
'SKIP_4': {type: null, components: 4},
};
export type Attribute = keyof(typeof attributeDefs);
export interface Properties {
material: Material;
attributes: Attribute[];
}
/**
* Call this first to register the manifold extension so that readMesh and
* writeMesh will work.
*/
export function setupIO(io: WebIO) {
return io.registerExtensions([EXTManifold]);
}
/**
* Read an input mesh into Manifold-compatible data structures, whether it
* contains the EXT_mesh_manifold extension or not.
*
* @param mesh The Mesh to read.
* @param attributes An array of attributes representing the order of desired
* properties returned in the vertProperties array of the output mesh. If
* omitted, this will be populated with the union of all attributes defined
* in the primitives of the input mesh. If present, the first entry must be
* 'POSITION', and any attributes in the primitives that are not included in
* this list will be ignored, while those in the list but not defined in a
* primitive will be populated with zeros.
* @returns The returned mesh is suitable for initializing a Manifold or Mesh of
* the Manifold library if desired. See Manifold documentation if you prefer
* to use these GL arrays in a different library. The runProperties array
* gives the Material and attributes list associated with each triangle run,
* which in turn corresponds to a primitive of the input mesh. These
* attributes are the intersection of the attributes present on the
* primitive and those requested in the attributes input.
*/
export function readMesh(mesh: Mesh, attributes: Attribute[] = []):
{mesh: MeshOptions, runProperties: Properties[]}|null {
const primitives = mesh.listPrimitives();
if (primitives.length === 0) {
return null;
}
if (attributes.length === 0) {
const attributeSet = new Set<Attribute>();
for (const primitive of primitives) {
const semantics = primitive.listSemantics() as Attribute[];
for (const semantic of semantics) {
attributeSet.add(semantic);
}
}
let semantic: Attribute;
for (semantic in attributeDefs) {
if (attributeSet.has(semantic)) {
attributes.push(semantic);
attributeSet.delete(semantic);
}
}
for (const semantic of attributeSet.keys()) {
attributes.push(semantic);
}
}
if (attributes.length < 1 || attributes[0] !== 'POSITION')
throw new Error('First attribute must be "POSITION".');
let numProp = 0;
const attributeOffsets = attributes.map((numProp = 0, def => {
const last = numProp;
numProp += attributeDefs[def].components;
return last;
}));
const manifoldPrimitive =
mesh.getExtension('EXT_mesh_manifold') as ManifoldPrimitive;
let vertPropArray = Array<number>();
let triVertArray = Array<number>();
const runIndexArray = [0];
const mergeFromVertArray = Array<number>();
const mergeToVertArray = Array<number>();
const runProperties = Array<Properties>();
if (manifoldPrimitive != null) {
const numVert = primitives[0].getAttribute('POSITION')!.getCount();
const foundAttribute = attributes.map((a) => attributeDefs[a].type == null);
vertPropArray = new Array<number>(numProp * numVert);
for (const primitive of primitives) {
const indices = primitive.getIndices();
if (!indices) {
console.log('Skipping non-indexed primitive ', primitive.getName());
continue;
}
const attributesIn = primitive.listSemantics() as Attribute[];
attributes.forEach((attributeOut, idx) => {
if (foundAttribute[idx]) {
return;
}
for (const attributeIn of attributesIn) {
if (attributeIn === attributeOut) {
foundAttribute[idx] = true;
const accessor = primitive.getAttribute(attributeIn)!;
writeProperties(
vertPropArray, accessor, numProp, attributeOffsets[idx]);
}
}
});
triVertArray = [...triVertArray, ...indices.getArray()!];
runIndexArray.push(triVertArray.length);
runProperties.push({
material: primitive.getMaterial()!,
attributes: attributesIn.filter(b => attributes.some(a => a == b))
});
}
const mergeTriVert = manifoldPrimitive.getMergeIndices()?.getArray() ?? [];
const mergeTo = manifoldPrimitive.getMergeValues()?.getArray() ?? [];
const vert2merge = new Map<number, number>();
for (const [i, idx] of mergeTriVert.entries()) {
vert2merge.set(triVertArray[idx], mergeTo[i]);
}
for (const [from, to] of vert2merge.entries()) {
mergeFromVertArray.push(from);
mergeToVertArray.push(to);
}
} else {
for (const primitive of primitives) {
const indices = primitive.getIndices();
if (!indices) {
console.log('Skipping non-indexed primitive ', primitive.getName());
continue;
}
const numVert = vertPropArray.length / numProp;
vertPropArray =
[...vertPropArray, ...readPrimitive(primitive, numProp, attributes)];
triVertArray =
[...triVertArray, ...indices.getArray()!.map((i) => i + numVert)];
runIndexArray.push(triVertArray.length);
const attributesIn = primitive.listSemantics() as Attribute[];
runProperties.push({
material: primitive.getMaterial()!,
attributes: attributesIn.filter(b => attributes.some(a => a == b))
});
}
}
const vertProperties = new Float32Array(vertPropArray);
const triVerts = new Uint32Array(triVertArray);
const runIndex = new Uint32Array(runIndexArray);
const mergeFromVert = new Uint32Array(mergeFromVertArray);
const mergeToVert = new Uint32Array(mergeToVertArray);
const meshOut =
{numProp, triVerts, vertProperties, runIndex, mergeFromVert, mergeToVert};
return {mesh: meshOut, runProperties};
}
/**
* Write a Manifold Mesh into a glTF Mesh object, using the EXT_mesh_manifold
* extension to allow for lossless roundtrip of the manifold mesh through the
* glTF file.
*
* @param doc The glTF Document to which this Mesh will be added.
* @param manifoldMesh The Manifold Mesh to convert to glTF.
* @param id2properties A map from originalID to Properties that include the
* glTF Material and the set of attributes to output. All triangle runs with
* the same originalID will be combined into a single output primitive. Any
* originalIDs not found in the map will have the glTF default material and
* no attributes beyond 'POSITION'. Each attributes array must correspond to
* the manifoldMesh vertProperties, thus the first attribute must always be
* 'POSITION'. Any properties that should not be output for a given
* primitive must use the 'SKIP_*' attributes.
* @returns The glTF Mesh to add to the Document.
*/
export function writeMesh(
doc: Document, manifoldMesh: ManifoldMesh,
id2properties: Map<number, Properties>): Mesh {
if (doc.getRoot().listBuffers().length === 0) {
doc.createBuffer();
}
const buffer = doc.getRoot().listBuffers()[0];
const manifoldExtension = doc.createExtension(EXTManifold);
const mesh = doc.createMesh();
const runIndex = Array<number>();
const attributeUnion = Array<Attribute>();
const primitive2attributes = new Map<Primitive, Attribute[]>();
const numRun = manifoldMesh.runIndex.length - 1;
let lastID = -1;
for (let run = 0; run < numRun; ++run) {
const id = manifoldMesh.runOriginalID[run];
if (id == lastID) {
continue;
}
lastID = id;
runIndex.push(manifoldMesh.runIndex[run]);
const indices = doc.createAccessor('primitive indices of ID ' + id)
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array(1));
const primitive = doc.createPrimitive().setIndices(indices);
const properties = id2properties.get(id);
if (properties) {
const {material, attributes} = properties;
if (attributes.length < 1 || attributes[0] !== 'POSITION')
throw new Error('First attribute must be "POSITION".');
primitive.setMaterial(material);
primitive2attributes.set(primitive, attributes);
properties.attributes.forEach((attribute, i) => {
if (i >= attributeUnion.length) {
attributeUnion.push(attribute);
} else {
const size = attributeDefs[attribute].components;
const unionSize = attributeDefs[attributeUnion[i]].components;
if (size != unionSize) {
throw new Error(
'Attribute sizes do not correspond: ' + attribute + ' and ' +
attributeUnion[i]);
}
if (attributeDefs[attributeUnion[i]].type == null) {
attributeUnion[i] = attribute;
}
}
});
} else {
primitive2attributes.set(primitive, ['POSITION']);
}
mesh.addPrimitive(primitive);
}
runIndex.push(manifoldMesh.runIndex[numRun]);
const numVert = manifoldMesh.numVert;
const numProp = manifoldMesh.numProp;
let offset = 0;
attributeUnion.forEach((attribute, aIdx) => {
const def = attributeDefs[attribute];
if (def == null)
throw new Error(attribute + ' is not a recognized attribute.');
if (def.type == null) {
++offset;
return;
}
const n = def.components;
if (offset + n > numProp) throw new Error('Too many attribute channels.');
const array = new Float32Array(n * numVert);
for (let v = 0; v < numVert; ++v) {
for (let i = 0; i < n; ++i) {
let x = manifoldMesh.vertProperties[numProp * v + offset + i];
if (attribute == 'COLOR_0') {
x = Math.max(0, Math.min(1, x));
}
array[n * v + i] = x;
}
}
const accessor = doc.createAccessor(attribute)
.setBuffer(buffer)
.setType(def.type)
.setArray(array);
for (const primitive of mesh.listPrimitives()) {
const attributes = primitive2attributes.get(primitive)!;
if (attributes.length > aIdx &&
attributeDefs[attributes[aIdx]].type != null) {
primitive.setAttribute(attribute, accessor);
}
}
offset += n;
});
const manifoldPrimitive = manifoldExtension.createManifoldPrimitive();
mesh.setExtension('EXT_mesh_manifold', manifoldPrimitive);
const indices = doc.createAccessor('manifold indices')
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(manifoldMesh.triVerts);
manifoldPrimitive.setIndices(indices);
manifoldPrimitive.setRunIndex(runIndex);
const vert2merge = [...Array(manifoldMesh.numVert).keys()];
const ind = Array<number>();
const val = Array<number>();
if (manifoldMesh.mergeFromVert && manifoldMesh.mergeToVert) {
for (const [i, from] of manifoldMesh.mergeFromVert.entries()) {
vert2merge[from] = manifoldMesh.mergeToVert[i];
}
for (const [i, vert] of manifoldMesh.triVerts.entries()) {
const newVert = vert2merge[vert];
if (vert !== newVert) {
ind.push(i);
val.push(newVert);
}
}
}
if (ind.length > 0) {
const indicesAccessor = doc.createAccessor('merge from')
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array(ind));
const valuesAccessor = doc.createAccessor('merge to')
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array(val));
manifoldPrimitive.setMerge(indicesAccessor, valuesAccessor);
}
return mesh;
}
/**
* Helper function to dispose of a Mesh, useful when replacing an existing Mesh
* with one from writeMesh.
*/
export function disposeMesh(mesh: Mesh) {
if (!mesh) return;
const primitives = mesh.listPrimitives();
for (const primitive of primitives) {
primitive.getIndices()?.dispose();
for (const accessor of primitive.listAttributes()) {
accessor.dispose();
}
}
const manifoldPrimitive =
mesh.getExtension('EXT_mesh_manifold') as ManifoldPrimitive;
if (manifoldPrimitive) {
manifoldPrimitive.getIndices()?.dispose();
manifoldPrimitive.getMergeIndices()?.dispose();
manifoldPrimitive.getMergeValues()?.dispose();
}
mesh.dispose();
}
/**
* Helper function to download an image and apply it to the given texture.
*
* @param texture The texture to update
* @param uri The location of the image to download
*/
export async function loadTexture(texture: Texture, uri: string) {
const response = await fetch(uri);
const blob = await response.blob();
texture.setMimeType(blob.type);
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
}
function writeProperties(
vertProperties: number[], accessor: Accessor, numProp: number,
offset: number) {
const array = accessor.getArray()!;
const size = accessor.getElementSize();
const numVert = accessor.getCount();
for (let i = 0; i < numVert; ++i) {
for (let j = 0; j < size; ++j) {
vertProperties[numProp * i + offset + j] = array[i * size + j];
}
}
}
function readPrimitive(
primitive: Primitive, numProp: number, attributes: Attribute[]) {
const vertProperties: number[] = [];
let offset = 0;
for (const attribute of attributes) {
const size = attributeDefs[attribute].components;
if (attributeDefs[attribute].type == null) {
offset += size;
continue;
}
const accessor = primitive.getAttribute(attribute);
if (accessor) {
writeProperties(vertProperties, accessor, numProp, offset);
}
offset += size;
}
return vertProperties;
}
+63 -19
View File
@@ -5,34 +5,81 @@
<title>ManifoldCAD</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<link type="text/css" href="editor.css" rel="stylesheet" />
<meta name="description"
content="Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.">
<meta name="theme-color" content="#A288B3">
<meta property="og:title" content="ManifoldCAD">
<meta property="og:type" content="website">
<meta property="og:url" content="https://manifoldcad.org/" />
<meta property="og:image" content="https://elalish.github.io/manifold/samples/models/mengerSpongeSquare.png">
<meta property="og:image:width" content="850">
<meta property="og:image:height" content="850">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png">
<link rel="manifest" href="/manifest.json">
<link type="text/css" href="editor.css" rel="stylesheet">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "ManifoldCAD",
"applicationCategory": "DesignApplication",
"offers": {
"@type": "Offer",
"price": "0"
}
}
</script>
<script>
self.ModelViewerElement = self.ModelViewerElement || {};
self.ModelViewerElement.modelCacheSize = 0;
</script>
<script type="module" src="/examples.js"></script>
</head>
<body>
<div class="page">
<div class="container" style="background-color: rgb(162 136 179);">
<p style="flex: 2;"><a href="https://github.com/elalish/manifold">Manifold</a>CAD
<div class="container header" style="background-color: #A288B3;">
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD
&nbsp;&nbsp;<span class="top-edit blue">
<button type="button" id="undo" class="top-icon undo squish"></button>
<button type="button" id="redo" class="top-icon redo squish"></button>
<button type="button" id="format" class="top-icon star squish"></button>
<button type="button" id="share" class="top-icon share"></button>
</span>
</p>
<div class="margin" style="position: relative;">
<button type="button" id="file" class="blue"><b class="uparrow"></b>&nbsp;&nbsp;<span
<div class="margin" id="file-margin">
<button type="button" id="file" class="blue item"><b class="uparrow"></b>&nbsp;&nbsp;<span
id="current">Intro</span></button>
<div class="dropdown">
<div class="dropdown" id="fileDropdown">
<button type="button" class="green item" id="new">New</button>
</div>
</div>
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
<button type="button" id="download" class="blue margin">Save GLB</button>
<div class="container" id="button-container">
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
<div id="save" class="margin" style="position: relative; ">
<div class="item">
<button type="button" id="glb" class="blue item">Save GLB</button>
<button type="button" class="icon"><b class="uparrow" style="right: 2px;"></b></button>
</div>
<div class="dropdown" id="saveDropdown">
<div class="item">
<button type="button" id="threemf" class="blue item">Save 3MF</button>
</div>
</div>
</div>
</div>
</div>
<div class="container" style="flex: 2;">
<div id="editor" class="col"></div>
<div class="col container" style="flex-direction: column;">
<model-viewer camera-controls min-camera-orbit="auto 0deg auto" max-camera-orbit="auto 180deg auto"
shadow-intensity="1" interaction-prompt="none" alt="Editor 3D output">
shadow-intensity="1" tone-mapping="neutral" interaction-prompt="none" alt="Editor 3D output">
<div class="center" slot="poster">
<p id="poster" style="text-align: center; line-height: 36px;">
Loading...
</p>
<p id="poster" style="text-align: center; line-height: 36px;"> Loading... </p>
</div>
<div id="animation">
<button type="button" id="play" class="icon pause"></button>
<input type="range" min="0" max="1" step="0.01" value="0" class="slider" id="scrubber">
</div>
</model-viewer>
<div id="console"></div>
@@ -40,17 +87,14 @@
</div>
</div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"
integrity="sha512-6bIYsGqvLpAiEBXPdRQeFf5cueeBECtAKJjIHer3BhBZNTV3WLcLA8Tm3pDfxUwTMIS+kAZwTUvJ1IrMdX8C5w=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css"
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="examples.js"></script>
<script src="editor.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
crossorigin="anonymous" referrerpolicy="no-referrer">
<script type="module" src="editor.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
</html>
+65
View File
@@ -0,0 +1,65 @@
<!doctype html>
<html lang="en">
<head>
<title>Make manifold glTF</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<style>
body {
background-color: #f7f7f7;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
color: rgba(0, 0, 0, .87);
font-weight: 400;
-webkit-font-smoothing: antialiased;
}
p {
max-width: 700px;
margin: 1em;
text-align: left;
}
model-viewer {
display: block;
width: 100vw;
height: 100vw;
max-width: 600px;
max-height: 600px;
border: 3px solid black;
border-radius: 10px;
margin-top: 5px;
}
#poster {
position: absolute;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
}
</style>
</head>
<body>
<p>Load a glTF/GLB model and our <a href="https://github.com/elalish/manifold">Manifold</a> library will attempt to
merge it into a set of manifold objects. If the model is water-tight, you can download the new GLB which will have
our <a href="https://github.com/KhronosGroup/glTF/pull/2286">EXT_mesh_manifold</a> extension, thus preserving the
manifold data without losing any mesh properties.</p>
<p> If the View Manifold GLB checkbox is enabled, then some meshes in the model have open edges and don't represent a
solid object. Check this box to see only the manifold parts, which will also enable them to be downloaded. If the
download button is still disabled, it means there were no manifold meshes found. </p>
<input type="file" id="input">
<button id="download" disabled>Download manifold GLB</button>
<input type="checkbox" id="viewFinal" disabled>
<label for="viewFinal">View Manifold GLB</label>
<model-viewer camera-controls shadow-intensity="1" tone-mapping="neutral" alt="Loaded GLB model">
<span id="poster" slot="poster">Drop a GLB here</span>
</model-viewer>
</body>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
<script type="module" src="make-manifold.ts"></script>
</html>
+149
View File
@@ -0,0 +1,149 @@
// Copyright 2024 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Document, WebIO} from '@gltf-transform/core';
import {KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
import {prune} from '@gltf-transform/functions';
import {SimpleDropzone} from 'simple-dropzone';
import Module from './built/manifold.js';
import {disposeMesh, Properties, readMesh, setupIO, writeMesh} from './gltf-io';
// Set up gltf-transform
const io = setupIO(new WebIO());
io.registerExtensions(KHRONOS_EXTENSIONS);
// Set up Manifold WASM library
const wasm = await Module();
wasm.setup();
const {Manifold, Mesh} = wasm;
// UX elements
const mv = document.querySelector('model-viewer');
const inputEl = document.querySelector('#input') as HTMLInputElement;
const downloadButton = document.querySelector('#download') as HTMLButtonElement;
const checkbox = document.querySelector('#viewFinal') as HTMLInputElement;
const dropCtrl = new SimpleDropzone(mv, inputEl) as any;
// glTF objects in memory
let inputGLBurl = '';
let outputGLBurl = '';
// Status of manifoldness of all meshes
let allManifold = true;
let anyManifold = false;
// The processing is run when a glTF is drag-and-dropped onto this element.
dropCtrl.on('drop', async ({files}: {files: Map<string, File>}) => {
for (const [_path, file] of files) {
const filename = file.name.toLowerCase();
if (filename.match(/\.(gltf|glb)$/)) {
URL.revokeObjectURL(inputGLBurl);
inputGLBurl = URL.createObjectURL(file);
await writeGLB(await readGLB(inputGLBurl));
updateUI();
break;
}
}
});
// UI functions
function updateUI() {
if (allManifold) {
checkbox.checked = true;
checkbox.disabled = true;
} else if (anyManifold) {
checkbox.checked = false;
checkbox.disabled = false;
} else {
checkbox.checked = false;
checkbox.disabled = true;
}
onClick();
}
checkbox.onclick = onClick;
function onClick() {
(mv as any).src = checkbox.checked ? outputGLBurl : inputGLBurl;
downloadButton.disabled = !checkbox.checked;
};
downloadButton.onclick = () => {
const link = document.createElement('a');
link.download = 'manifold.glb';
link.href = outputGLBurl;
link.click();
};
// Write output glTF using gltf-transform, which contains only the meshes that
// are manifold, and using the EXT_mesh_manifold extension.
async function writeGLB(doc: Document): Promise<void> {
URL.revokeObjectURL(outputGLBurl);
if (!anyManifold) {
return;
}
const glb = await io.writeBinary(doc);
const blob = new Blob([glb], {type: 'application/octet-stream'});
outputGLBurl = URL.createObjectURL(blob);
}
// Read the glTF ObjectURL and return a gltf-transform document with all the
// non-manifold meshes stripped out.
async function readGLB(url: string): Promise<Document> {
allManifold = false;
anyManifold = false;
updateUI();
allManifold = true;
const docIn = await io.read(url);
const nodes = docIn.getRoot().listNodes();
for (const node of nodes) {
const mesh = node.getMesh();
if (!mesh) continue;
const tmp = readMesh(mesh);
if (!tmp) continue;
const id2properties = new Map<number, Properties>();
const numID = tmp.runProperties.length;
const firstID = Manifold.reserveIDs(numID);
tmp.mesh.runOriginalID = new Uint32Array(numID);
for (let i = 0; i < numID; ++i) {
tmp.mesh.runOriginalID[i] = firstID + i;
id2properties.set(firstID + i, tmp.runProperties[i]);
}
const manifoldMesh = new Mesh(tmp.mesh);
disposeMesh(mesh);
// Make the mesh manifold if it's close.
manifoldMesh.merge();
try {
// Test manifoldness - will throw if not.
const manifold = new Manifold(manifoldMesh);
// Replace the mesh with a manifold version
node.setMesh(writeMesh(docIn, manifold.getMesh(), id2properties));
manifold.delete();
anyManifold = true;
} catch (e) {
console.log(mesh.getName(), e);
allManifold = false;
}
}
// Prune the leftovers after non-manifold mesh removal.
await docIn.transform(prune());
return docIn;
}
+224
View File
@@ -0,0 +1,224 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Accessor, Extension, ExtensionProperty, GLTF, IProperty, PropertyType, ReaderContext, WriterContext} from '@gltf-transform/core';
const NAME = 'EXT_mesh_manifold';
const MERGE = 'MERGE';
interface IManifoldPrimitive extends IProperty {
mergeIndices: Accessor;
mergeValues: Accessor;
indices: Accessor;
runIndex: number[]|Uint32Array;
}
interface ManifoldDef {
manifoldPrimitive: GLTF.IMeshPrimitive;
mergeIndices: number;
mergeValues: number;
}
export class ManifoldPrimitive extends ExtensionProperty<IManifoldPrimitive> {
static EXTENSION_NAME = NAME;
public declare extensionName: typeof NAME;
public declare propertyType: 'ManifoldPrimitive';
public declare parentTypes: [PropertyType.MESH];
init() {
ManifoldPrimitive.EXTENSION_NAME = NAME;
this.propertyType = 'ManifoldPrimitive';
this.parentTypes = [PropertyType.MESH];
}
getDefaults() {
return Object.assign(
super.getDefaults(),
{manifoldPrimitive: null, mergeIndices: null, mergeValues: null});
}
getMergeIndices() {
return this.getRef('mergeIndices');
}
getMergeValues() {
return this.getRef('mergeValues');
}
setMerge(indicesAccessor: Accessor, valuesAccessor: Accessor) {
if (indicesAccessor.getCount() !== valuesAccessor.getCount())
throw new Error('merge vectors must be the same length.');
this.setRef('mergeIndices', indicesAccessor);
return this.setRef('mergeValues', valuesAccessor);
}
getRunIndex() {
return this.get('runIndex');
}
setRunIndex(runIndex: number[]|Uint32Array) {
return this.set('runIndex', runIndex);
}
setIndices(indices: Accessor) {
return this.setRef('indices', indices);
}
getIndices() {
return this.getRef('indices')!;
}
}
export class EXTManifold extends Extension {
extensionName = NAME;
prewriteTypes = [PropertyType.ACCESSOR];
static EXTENSION_NAME = NAME;
createManifoldPrimitive() {
return new ManifoldPrimitive(this.document.getGraph());
}
read(context: ReaderContext) {
const {json} = context.jsonDoc;
const meshDefs = json.meshes || [];
meshDefs.forEach((meshDef, meshIndex) => {
if (!meshDef.extensions || !meshDef.extensions[NAME]) return;
const mesh = context.meshes[meshIndex];
const manifoldPrimitive = this.createManifoldPrimitive();
mesh.setExtension(NAME, manifoldPrimitive);
const manifoldDef = meshDef.extensions[NAME] as ManifoldDef;
if (manifoldDef.manifoldPrimitive) {
let count = 0;
const runIndex = Array<number>();
runIndex.push(count);
for (const primitive of mesh.listPrimitives()) {
const indices = primitive.getIndices();
if (!indices) {
console.log('Skipping non-indexed primitive ', primitive.getName());
continue;
}
count += indices.getCount();
runIndex.push(count);
}
manifoldPrimitive.setRunIndex(runIndex);
manifoldPrimitive.setIndices(
context.accessors[manifoldDef.manifoldPrimitive.indices!]);
}
if (manifoldDef.mergeIndices != null && manifoldDef.mergeValues != null) {
manifoldPrimitive.setMerge(
context.accessors[manifoldDef.mergeIndices],
context.accessors[manifoldDef.mergeValues]);
}
});
return this;
}
prewrite(context: WriterContext) {
this.document.getRoot().listMeshes().forEach((mesh) => {
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
if (!manifoldPrimitive) return;
const indices = manifoldPrimitive.getIndices();
context.addAccessorToUsageGroup(
indices, WriterContext.BufferViewUsage.ELEMENT_ARRAY_BUFFER);
const mergeFrom = manifoldPrimitive.getMergeIndices();
const mergeTo = manifoldPrimitive.getMergeValues();
if (!mergeFrom || !mergeTo) return;
context.addAccessorToUsageGroup(mergeFrom, MERGE);
context.addAccessorToUsageGroup(mergeTo, MERGE);
});
return this;
}
write(context: WriterContext) {
const {json} = context.jsonDoc;
this.document.getRoot().listMeshes().forEach((mesh) => {
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
if (!manifoldPrimitive) return;
const meshIndex = context.meshIndexMap.get(mesh)!;
const meshDef = json.meshes![meshIndex];
const runIndex = manifoldPrimitive.getRunIndex();
const numPrimitive = runIndex.length - 1;
if (numPrimitive !== meshDef.primitives.length) {
throw new Error(
'The number of primitives must be exactly one less than the length of runIndex.');
}
const mergeIndicesIndex =
context.accessorIndexMap.get(manifoldPrimitive.getMergeIndices()!)!;
const mergeValuesIndex =
context.accessorIndexMap.get(manifoldPrimitive.getMergeValues()!)!;
const mergeIndices = json.accessors![mergeIndicesIndex];
const mergeValues = json.accessors![mergeValuesIndex];
const existingPrimitive = meshDef.primitives[0];
const primitive = {
indices: context.accessorIndexMap.get(manifoldPrimitive.getIndices())!,
mode: existingPrimitive.mode,
attributes: {'POSITION': existingPrimitive.attributes['POSITION']}
};
const indices = json.accessors![primitive.indices];
if (!indices) {
return;
}
if (mergeIndices && mergeValues) {
indices.sparse = {
count: mergeIndices.count,
indices: {
bufferView: mergeIndices.bufferView!,
byteOffset: mergeIndices.byteOffset,
componentType: mergeIndices.componentType
},
values: {
bufferView: mergeValues.bufferView!,
byteOffset: mergeValues.byteOffset,
}
};
}
for (let i = 0; i < numPrimitive; ++i) {
const accessor = json.accessors![meshDef.primitives[i].indices!];
accessor.bufferView = indices.bufferView;
accessor.byteOffset = indices.byteOffset! + 4 * runIndex[i];
accessor.count = runIndex[i + 1] - runIndex[i];
}
meshDef.extensions = meshDef.extensions || {};
meshDef.extensions[NAME] = {
manifoldPrimitive: primitive,
mergeIndices: mergeIndicesIndex,
mergeValues: mergeValuesIndex
};
// Test the manifold primitive by replacing the material primitives
// meshDef.primitives = [primitive];
});
return this;
}
}
@@ -0,0 +1,124 @@
// Copyright 2024 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Document, WebIO} from '@gltf-transform/core';
import {clearNodeTransform, flatten, prune} from '@gltf-transform/functions';
import Module, {Manifold, Mesh} from './built/manifold';
import {disposeMesh, Properties, readMesh, setupIO, writeMesh} from './gltf-io';
// Set up gltf-transform
const io = setupIO(new WebIO());
const doc = new Document();
// Set up Manifold WASM library
const wasm = await Module();
wasm.setup();
const {Manifold, Mesh} = wasm;
// Map of OriginalID to glTF material and attributes
const id2properties = new Map<number, Properties>();
// Wrapper for gltf-io readMesh() that processes a whole glTF and stores the
// properties in the above map. This function is simplified and intended only
// for reading single-object glTFs, as it simply unions any extra meshes
// together rather than returning a scene hierarchy.
async function readGLB(url: string) {
const manifolds = Array<Manifold>();
const docIn = await io.read(url);
await docIn.transform(flatten());
const nodes = docIn.getRoot().listNodes();
const ids = Array<number>();
for (const node of nodes) {
clearNodeTransform(node);
const gltfMesh = node.getMesh();
if (gltfMesh == null) continue;
const tmp = readMesh(gltfMesh);
if (tmp == null) continue;
const numID = tmp.runProperties.length;
const firstID = Manifold.reserveIDs(numID);
tmp.mesh.runOriginalID = new Uint32Array(numID);
for (let i = 0; i < numID; ++i) {
tmp.mesh.runOriginalID[i] = firstID + i;
ids.push(firstID + i);
id2properties.set(firstID + i, tmp.runProperties[i]);
}
manifolds.push(new Manifold(new Mesh(tmp.mesh)));
}
// pull in materials, TODO: replace with transfer() when available
const startIdx = doc.getRoot().listMaterials().length;
doc.merge(docIn);
doc.getRoot().listScenes().forEach((s) => s.dispose());
doc.getRoot().listBuffers().forEach((s) => s.dispose());
doc.getRoot().listAccessors().forEach((s) => s.dispose());
for (const [i, id] of ids.entries()) {
const material = doc.getRoot().listMaterials()[startIdx + i];
id2properties.get(id)!.material = material;
}
return Manifold.union(manifolds);
}
// Read static input glTFs
const space = await readGLB('/models/space.glb');
const moon = await readGLB('/models/moon.glb');
const node = doc.createNode();
doc.createScene().addChild(node);
// Set up UI for operations
type BooleanOp = 'union'|'difference'|'intersection';
function csg(operation: BooleanOp) {
push2MV(Manifold[operation](space, moon));
}
csg('difference');
const selectElement = document.querySelector('select')!;
selectElement.onchange = function() {
csg(selectElement.value as BooleanOp);
};
// The resulting glTF
let objectURL = '';
// Set up download UI
const downloadButton = document.querySelector('#download') as HTMLButtonElement;
downloadButton.onclick = function() {
const link = document.createElement('a');
link.download = 'manifold.glb';
link.href = objectURL;
link.click();
};
// <model-viewer> element for rendering resulting glTF.
const mv = document.querySelector('model-viewer');
// Use gltf-io and gltf-transform to convert the resulting Manifold to a glTF
// and display it with <model-viewer>.
async function push2MV(manifold: Manifold) {
disposeMesh(node.getMesh()!);
const mesh = writeMesh(doc, manifold.getMesh(), id2properties);
node.setMesh(mesh);
await doc.transform(prune());
const glb = await io.writeBinary(doc);
const blob = new Blob([glb], {type: 'application/octet-stream'});
URL.revokeObjectURL(objectURL);
objectURL = URL.createObjectURL(blob);
(mv as any).src = objectURL;
}
+13 -82
View File
@@ -6,7 +6,6 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<style>
body {
margin: 0;
@@ -36,92 +35,24 @@
</head>
<body>
<p>This example demonstrates feeding Manifold's output into <a
href="https://modelviewer.dev/">&lt;model-viewer&gt;</a>
via the three.js GLTFExporter. The most basic three.js example is <a href="three.html">here</a>.
</p>
<p>This example demonstrates reading and writing glTF models using <a
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a> and using this to feed Manifold's
output into <a href="https://modelviewer.dev/">&lt;model-viewer&gt;</a>. It also shows how to pass mesh properties
like UV coordinates through Manifold and how to re-associate textures and materials after operations, made simple
using our `gltf-io.ts` library. The resulting glTF includes the EXT_mesh_manifold extension, using our
`manifold-gltf.ts` extension to gltf-transform, which ensures lossless data transfer.</p>
<p>Please open dev tools to inspect our source code - everything is source mapped back to our TS files and commented
thoroughly. A more basic three.js example is <a href="three.html">here</a>. </p>
<select>
<option value="difference" selected>Difference</option>
<option value="intersection">Intersection</option>
<option value="union">Union</option>
</select>
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
<button id="download">Download manifold GLB</button>
<model-viewer camera-controls shadow-intensity="1" tone-mapping="neutral"
alt="Output of mesh Boolean operation"></model-viewer>
</body>
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<script src="https://unpkg.com/three@0.144.0/build/three.js"></script>
<script src="https://unpkg.com/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
<script src="https://unpkg.com/three@0.144.0/examples/js/exporters/GLTFExporter.js"></script>
<script src="manifold.js"></script>
<script>
let wasm;
Module().then(function (tmp) {
wasm = tmp;
wasm.setup();
const mesh = new THREE.Mesh(undefined, new THREE.MeshStandardMaterial({
color: 'yellow',
metalness: 1,
roughness: 0.2
}));
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
push2MV(mesh);
};
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
csg('difference');
});
const mv = document.querySelector('model-viewer');
let objectURL = null;
const exporter = new THREE.GLTFExporter();
function push2MV(mesh) {
exporter.parse(
mesh,
(gltf) => {
const blob = new Blob([gltf], { type: 'application/octet-stream' });
URL.revokeObjectURL(objectURL);
objectURL = URL.createObjectURL(blob);
mv.src = objectURL;
},
() => console.log('GLTF export failed!'),
{ binary: true }
);
}
// functions to convert between three.js and wasm
function geometry2mesh(geometry) {
const vertPos = geometry.attributes.position.array;
const vertNormal = geometry.attributes.normal.array;
const triVerts = geometry.index.array;
return new wasm.Mesh({vertPos, vertNormal, triVerts});
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
// most of three.js geometries aren't manifolds, so...
function simplify(geometry) {
delete geometry.attributes.normal;
delete geometry.attributes.uv;
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
simplified.computeVertexNormals();
return simplified;
}
</script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
<script type="module" src="model-viewer-script.ts"></script>
</html>
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
{
"name": "manifold-examples",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"build:watch": "vite build --watch",
"preview": "vite preview",
"test": "vitest run",
"test:ui": "vitest --ui",
"test:dev": "vitest"
},
"dependencies": {
"@gltf-transform/core": "^3.8.0",
"@gltf-transform/extensions": "^3.8.0",
"@gltf-transform/functions": "^3.8.0",
"@jscadui/3mf-export": "^0.5.0",
"@types/three": "^0.164.0",
"fflate": "^0.8.0",
"gl-matrix": "^3.4.3",
"simple-dropzone": "0.8.3",
"three": "0.164.1"
},
"devDependencies": {
"@vitest/ui": "^1.6.0",
"typescript": "5.2.2",
"vite": "^4.5.0",
"vitest": "^1.6.0"
}
}
File diff suppressed because it is too large Load Diff
+574
View File
@@ -0,0 +1,574 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/** @type {import("../../manifold-encapsulated-types")["Manifold"]} */
const Manifold = globalThis.Manifold;
export const examples = {
functions: {
Intro: function() {
// Write code in JavaScript or TypeScript and this editor will show the
// API docs. Type e.g. "box." to see the Manifold API. Type
// "CrossSection." or "Manifold." to list the 2D and 3D constructors,
// respectively. Type "module." to see the static API - these functions
// can also be used bare. Use console.log() to print output (lower-right).
// This editor defines Z as up and units of mm.
const {cube, sphere} = Manifold;
const box = cube([100, 100, 100], true);
const ball = sphere(60, 100);
// You must name your final output "result", or create at least one
// GLTFNode - see Menger Sponge and Gyroid Module examples.
const result = box.subtract(ball);
// For visual debug, wrap any shape with show() and it and all of its
// copies will be shown in transparent red, akin to # in OpenSCAD. Or try
// only() to ghost out everything else, akin to * in OpenSCAD.
// All changes are automatically saved and restored between sessions.
// This PWA is purely local - there is no server communication.
// This means it will work equally well offline once loaded.
// Consider installing it (icon in the search bar) for easy access.
// See the script drop-down above ("Intro") for usage examples. The
// gl-matrix package from npm is automatically imported for convenience -
// its API is available in the top-level glMatrix object.
// Use GLTFNode for disjoint manifolds rather than compose(), as this will
// keep them better organized in the GLB. This will also allow you to
// specify material properties, and even vertex colors via
// setProperties(). See Tetrahedron Puzzle example.
return result;
},
TetrahedronPuzzle: function() {
// A tetrahedron cut into two identical halves that can screw together as
// a puzzle. This demonstrates how redundant points along a polygon can be
// used to make twisted extrusions smoother. It also showcases animated
// assemblies. Based on the screw puzzle by George Hart:
// https://www.thingiverse.com/thing:186372
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
const gap = 0.2; // Spacing between the two halves to allow sliding.
const nDivisions = 50; // Divisions (both ways) in the screw surface.
const scale = edgeLength / (2 * Math.sqrt(2));
const tet = Manifold.tetrahedron().intersect(
Manifold.tetrahedron().rotate([0, 0, 90]).scale(2.5));
const box = [];
box.push([2, -2], [2, 2]);
for (let i = 0; i <= nDivisions; ++i) {
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
}
const cyan = [0, 1, 1];
const magenta = [1, 0, 1];
const fade = (color, pos) => {
for (let i = 0; i < 3; ++i) {
const x = pos[2] / 2;
color[i] = cyan[i] * x + magenta[i] * (1 - x);
}
};
// setProperties(3, fade) creates three channels of vertex properties
// according to the above fade function. setMaterial assigns these
// channels as colors, and sets the factor to white, since our default is
// yellow.
const screw = setMaterial(
Manifold.extrude(box, 2, nDivisions, 270).setProperties(3, fade),
{baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']});
const result =
tet.intersect(screw.rotate([0, 0, -45]).translate([0, 0, -1]))
.scale(scale);
// Assigned materials are only applied to a GLTFNode. Note that material
// definitions cascade, applying recursively to all child surfaces, but
// overridden by any materials defined lower down. The default material
// properties, as well as animation parameters can be set via
// globalDefaults.
const layFlat = new GLTFNode();
layFlat.rotation =
[45, -Math.atan(1 / Math.sqrt(2)) * 180 / Math.PI, 120];
layFlat.translation = [0, 0, scale * Math.sqrt(3) / 3];
const fixed = new GLTFNode(layFlat);
fixed.manifold = result;
fixed.rotation = [0, 0, 180];
// For 3MF export, only top-level objects are independently arrangeable.
const layFlat2 = layFlat.clone();
const moving = new GLTFNode(layFlat2);
moving.manifold = result;
// Use functions to create animation, which runs from t=0 to t=1.
moving.translation = (t) => {
const a = 1 - t;
const x = a > 0.5 ? scale * 2 * (0.5 - a) : 0;
return [x, x, -2 * x + scale * 4 * (0.5 - Math.abs(0.5 - a))];
};
moving.rotation = (t) => [0, 0, 270 * 2 * (1 - t)];
globalDefaults.animationLength = 10; // seconds
globalDefaults.animationMode = 'ping-pong';
return result;
},
RoundedFrame: function() {
// Demonstrates how at 90-degree intersections, the sphere and cylinder
// facets match up perfectly, for any choice of global resolution
// parameters.
const {sphere, cylinder, union, cube} = Manifold;
function roundedFrame(edgeLength, radius, circularSegments = 0) {
const edge = cylinder(edgeLength, radius, -1, circularSegments);
const corner = sphere(radius, circularSegments);
const edge1 = union(corner, edge).rotate([-90, 0, 0]).translate([
-edgeLength / 2, -edgeLength / 2, 0
]);
const edge2 = union(
union(edge1, edge1.rotate([0, 0, 180])),
edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
const edge4 = union(edge2, edge2.rotate([0, 0, 90])).translate([
0, 0, -edgeLength / 2
]);
return union(edge4, edge4.rotate([180, 0, 0]));
}
setMinCircularAngle(3);
setMinCircularEdgeLength(0.5);
const result = roundedFrame(100, 10);
// Demonstrate how you can use the .split method to perform
// a subtraction and an intersection at once
const [inside, outside] = result.split(cube(100, true));
const outsideNode = new GLTFNode();
outsideNode.manifold = outside;
const insideNode = new GLTFNode();
insideNode.manifold = inside;
insideNode.material = {baseColorFactor: [0, 1, 1]};
return result;
},
Heart: function() {
// Smooth, complex manifolds can be created using the warp() function.
// This example recreates the Exploitable Heart by Emmett Lalish:
// https://www.thingiverse.com/thing:6190
// It also demonstrates the use of setMorph to animate a warping function.
const func = (v) => {
const x2 = v[0] * v[0];
const y2 = v[1] * v[1];
const z = v[2];
const z2 = z * z;
const a = x2 + 9 / 4 * y2 + z2;
const b = z * z2 * (x2 + 9 / 80 * y2);
const a2 = a * a;
const a3 = a * a2;
const step = (r) => {
const r2 = r * r;
const r4 = r2 * r2;
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
// Derivative
const df =
6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
return f / df;
};
// Newton's method for root finding
let r = 1.5;
let dr = 1;
while (Math.abs(dr) > 0.0001) {
dr = step(r);
r -= dr;
}
// Update radius
v[0] *= r;
v[1] *= r;
v[2] *= r;
};
const ball = Manifold.sphere(1, 200);
const heart = ball.warp(func);
const box = heart.boundingBox();
const scale = 100 / (box.max[0] - box.min[0]);
setMorphStart(ball, func);
const node = new GLTFNode();
node.manifold = ball;
node.scale = [scale, scale, scale];
globalDefaults.animationLength = 5; // seconds
globalDefaults.animationMode = 'ping-pong';
const result = heart.scale(scale);
return result;
},
Scallop: function() {
// A smoothed manifold demonstrating selective edge sharpening with
// smooth() and refine(), see more details at:
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
const height = 10;
const radius = 30;
const offset = 20;
const wiggles = 12;
const sharpness = 0.8;
const n = 50;
const positions = [];
const triangles = [];
positions.push(-offset, 0, height, -offset, 0, -height);
const sharpenedEdges = [];
const delta = 3.14159 / wiggles;
for (let i = 0; i < 2 * wiggles; ++i) {
const theta = (i - wiggles) * delta;
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
positions.push(
radius * Math.cos(theta), radius * Math.sin(theta),
amp * (i % 2 == 0 ? 1 : -1));
let j = i + 1;
if (j == 2 * wiggles) j = 0;
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
let halfedge = triangles.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triangles.push(0, 2 + i, 2 + j);
halfedge = triangles.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triangles.push(1, 2 + j, 2 + i);
}
const triVerts = Uint32Array.from(triangles);
const vertProperties = Float32Array.from(positions);
const scallop = new Mesh({numProp: 3, triVerts, vertProperties});
const colorCurvature = (color, pos, oldProp) => {
const a = Math.max(0, Math.min(1, oldProp[0] / 3 + 0.5));
const b = a * a * (3 - 2 * a);
const red = [1, 0, 0];
const blue = [0, 0, 1];
for (let i = 0; i < 3; ++i) {
color[i] = (1 - b) * blue[i] + b * red[i];
}
};
const result = Manifold.smooth(scallop, sharpenedEdges)
.refine(n)
.calculateCurvature(-1, 0)
.setProperties(3, colorCurvature);
const node = new GLTFNode();
node.manifold = result;
node.material = {
baseColorFactor: [1, 1, 1],
metallic: 0,
attributes: ['COLOR_0']
};
return result;
},
TorusKnot: function() {
// Creates a classic torus knot, defined as a string wrapping periodically
// around the surface of an imaginary donut. If p and q have a common
// factor then you will get multiple separate, interwoven knots. This is
// an example of using the warp() method, thus avoiding any direct
// handling of triangles.
// The number of times the thread passes through the donut hole.
const p = 1;
// The number of times the thread circles the donut.
const q = 3;
// Radius of the interior of the imaginary donut.
const majorRadius = 25;
// Radius of the small cross-section of the imaginary donut.
const minorRadius = 10;
// Radius of the small cross-section of the actual object.
const threadRadius = 3.75;
// Number of linear segments making up the threadRadius circle. Default is
// getCircularSegments(threadRadius).
const circularSegments = -1;
// Number of segments along the length of the knot. Default makes roughly
// square facets.
const linearSegments = -1;
// These default values recreate Matlab Knot by Emmett Lalish:
// https://www.thingiverse.com/thing:7080
const {vec3} = glMatrix;
function gcd(a, b) {
return b == 0 ? a : gcd(b, a % b);
}
const kLoops = gcd(p, q);
const pk = p / kLoops;
const qk = q / kLoops;
const n = circularSegments > 2 ? circularSegments :
getCircularSegments(threadRadius);
const m = linearSegments > 2 ? linearSegments :
n * qk * majorRadius / threadRadius;
const offset = 2
const circle = CrossSection.circle(1, n).translate([offset, 0]);
const func = (v) => {
const psi = qk * Math.atan2(v[0], v[1]);
const theta = psi * pk / qk;
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
const phi = Math.atan2(x1 - offset, v[2]);
vec3.set(
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
const center = vec3.fromValues(0, 0, 0);
const r = majorRadius + minorRadius * Math.cos(theta);
vec3.rotateX(v, v, center, -Math.atan2(pk * minorRadius, qk * r));
v[0] += minorRadius;
vec3.rotateY(v, v, center, theta);
v[0] += majorRadius;
vec3.rotateZ(v, v, center, psi);
};
const result = Manifold.revolve(circle, m).warp(func);
for (let k = 0; k < kLoops; ++k) {
const node = new GLTFNode();
node.manifold = result;
node.rotation = [0, 0, 360 * (k / kLoops) * (qk / pk)];
node.translation = [0, 0, result.boundingBox().min[2]];
}
return result;
},
MengerSponge: function() {
// This example demonstrates how symbolic perturbation correctly creates
// holes even though the subtracted objects are exactly coplanar.
const {vec2} = glMatrix;
function fractal(holes, hole, w, position, depth, maxDepth) {
w /= 3;
holes.push(
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
if (depth == maxDepth) return;
const offsets = [
vec2.fromValues(-w, -w), vec2.fromValues(-w, 0.0),
vec2.fromValues(-w, w), vec2.fromValues(0.0, w),
vec2.fromValues(w, w), vec2.fromValues(w, 0.0),
vec2.fromValues(w, -w), vec2.fromValues(0.0, -w)
];
for (let offset of offsets)
fractal(
holes, hole, w, vec2.add(offset, position, offset), depth + 1,
maxDepth);
}
function mengerSponge(n) {
let result = Manifold.cube([1, 1, 1], true);
const holes = [];
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
const hole = Manifold.compose(holes);
result = Manifold.difference([
result,
hole,
hole.rotate([90, 0, 0]),
hole.rotate([0, 90, 0]),
]);
return result;
}
const posColors = (newProp, pos) => {
for (let i = 0; i < 3; ++i) {
newProp[i] = (1 - pos[i]) / 2;
}
};
const result = mengerSponge(3)
.trimByPlane([1, 1, 1], 0)
.setProperties(3, posColors)
.scale(100);
const node = new GLTFNode();
node.manifold = result;
node.material = {baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']};
return result;
},
StretchyBracelet: function() {
// Recreates Stretchy Bracelet by Emmett Lalish:
// https://www.thingiverse.com/thing:13505
const {vec2} = glMatrix;
function base(
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
outerRadius, cut, nCut, nDivision) {
let b = Manifold.cylinder(width, radius + twistRadius / 2);
const circle = [];
const dPhiDeg = 180 / nDivision;
for (let i = 0; i < 2 * nDivision; ++i) {
circle.push([
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
]);
}
let decor = Manifold.extrude(circle, width, nDivision, 180)
.scale([1, 0.5, 1])
.translate([0, radius, 0]);
for (let i = 0; i < nDecor; i++)
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
const stretch = [];
const dPhiRad = 2 * Math.PI / nCut;
const o = vec2.fromValues(0, 0);
const p0 = vec2.fromValues(outerRadius, 0);
const p1 = vec2.fromValues(innerRadius, -cut);
const p2 = vec2.fromValues(innerRadius, cut);
for (let i = 0; i < nCut; ++i) {
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
stretch.push(vec2.rotate([0, 0], p1, o, dPhiRad * i));
stretch.push(vec2.rotate([0, 0], p2, o, dPhiRad * i));
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
}
const result =
Manifold.intersection(Manifold.extrude(stretch, width), b);
return result;
}
function stretchyBracelet(
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
nCut = 27, nDivision = 30) {
const twistRadius = Math.PI * radius / nDecor;
const decorRadius = twistRadius * 1.5;
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
const innerRadius = outerRadius - height;
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
const adjThickness = 0.5 * thickness * height / cut;
return Manifold.difference(
base(
width, radius, decorRadius, twistRadius, nDecor,
innerRadius + thickness, outerRadius + adjThickness,
cut - adjThickness, nCut, nDivision),
base(
width, radius - thickness, decorRadius, twistRadius, nDecor,
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
nDivision));
}
const result = stretchyBracelet();
return result;
},
GyroidModule: function() {
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
// https://www.thingiverse.com/thing:25477. This sample demonstrates the
// use of a Signed Distance Function (SDF) to create smooth, complex
// manifolds.
const {vec3} = glMatrix;
// number of modules along pyramid edge (use 1 for print orientation)
const m = 4;
// module size
const size = 20;
// SDF resolution
const n = 20;
const pi = 3.14159;
function gyroid(p) {
const x = p[0] - pi / 4;
const y = p[1] - pi / 4;
const z = p[2] - pi / 4;
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
Math.cos(z) * Math.sin(x);
}
function gyroidOffset(level) {
const period = 2 * pi;
const box = {
min: vec3.fromValues(-period, -period, -period),
max: vec3.fromValues(period, period, period)
};
return Manifold.levelSet(gyroid, box, period / n, level)
.scale(size / period);
};
function rhombicDodecahedron() {
const box = Manifold.cube([1, 1, 2], true).scale(size * Math.sqrt(2));
const result =
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
return result.intersect(box.rotate([0, 0, 45]));
}
const gyroidModule = rhombicDodecahedron()
.intersect(gyroidOffset(-0.4))
.subtract(gyroidOffset(0.4));
if (m > 1) {
for (let i = 0; i < m; ++i) {
for (let j = i; j < m; ++j) {
for (let k = j; k < m; ++k) {
const node = new GLTFNode();
node.manifold = gyroidModule;
node.translation =
[(k + i - j) * size, (k - i) * size, (-j) * size];
node.material = {
baseColorFactor:
[(k + i - j + 1) / m, (k - i + 1) / m, (j + 1) / m]
};
}
}
}
}
const result = gyroidModule.rotate([-45, 0, 90]).translate([
0, 0, size / Math.sqrt(2)
]);
return result;
}
},
functionBodies: new Map()
};
for (const [func, code] of Object.entries(examples.functions)) {
const whole = code.toString();
const lines = whole.split('\n');
lines.splice(0, 1); // remove first line
lines.splice(-2, 2); // remove last two lines
// remove first six leading spaces
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
const name =
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
examples.functionBodies.set(name, body);
};
if (typeof self !== 'undefined') {
self.examples = examples;
}

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Before

Width:  |  Height:  |  Size: 375 B

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

@@ -0,0 +1,42 @@
{
"name": "ManifoldCAD",
"icons": [
{
"src": "/icons/mengerSponge192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/icons/mengerSponge512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"id": "/",
"start_url": "/",
"background_color": "#FFFFFF",
"display": "standalone",
"orientation": "landscape",
"scope": "/",
"theme_color": "#A288B3",
"description": "Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.",
"categories": [
"design",
"developer",
"education",
"graphics",
"productivity"
],
"screenshots": [
{
"src": "/icons/manifoldCAD.png",
"type": "image/png",
"sizes": "2906x1980"
},
{
"src": "/icons/manifoldCADonly.png",
"type": "image/png",
"sizes": "2906x1980"
}
]
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,41 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Increment version when updating CDN URLs to clean up cache.
const cacheName = 'manifoldCAD-cache-v1';
self.addEventListener('activate', e => {
e.waitUntil(caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if (key !== cacheName) {
return caches.delete(key);
}
}));
}));
});
// Serves from cache, then updates the cache in the background from the network,
// if available. Update available on refresh.
self.addEventListener(
'fetch',
e => {e.respondWith(caches.match(e.request).then(cachedResponse => {
const networkFetch = fetch(e.request).then(response => {
const clone = response.clone();
caches.open(cacheName).then(cache => {
cache.put(e.request, clone);
});
return response;
});
return cachedResponse || networkFetch;
}))});
+1
View File
@@ -0,0 +1 @@
declare module 'simple-dropzone';
+11 -77
View File
@@ -6,7 +6,6 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<style>
body {
margin: 0;
@@ -36,86 +35,21 @@
</head>
<body>
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
involving GLTF export and &lt;model-viewer&gt; can be found <a href="model-viewer.html">here</a>.
</p>
<p>This example demonstrates interop between Manifold and Three.js with minimal code - please open dev tools to
inspect the source. Here we generate two multi-material Three.js meshes and convert them to Manifolds while building
a mapping from material to Manifold ID. Then Boolean operations are performed on the Manifolds and the result is
converted back to a Three.js mesh using the material mapping. The input cube has half red faces and half
normal-shaded, while the icosahedron has half blue faces and half normal-shaded. The resulting mesh has three
materials, since one (normal-shaded) was common between the input models.</p>
<p>A slightly more complex example involving our library for GLTF import/export and &lt;model-viewer&gt; can be found
<a href="model-viewer.html">here</a>. </p>
<select>
<option value="difference" selected>Difference</option>
<option value="union" selected>Union</option>
<option value="difference">Difference</option>
<option value="intersection">Intersection</option>
<option value="union">Union</option>
</select><br />
<canvas id="output"></canvas>
</body>
<script src="https://unpkg.com/three@0.142.0/build/three.js"></script>
<script src="https://unpkg.com/three@0.142.0/examples/js/utils/BufferGeometryUtils.js"></script>
<script src="manifold.js"></script>
<script>
let wasm;
Module().then(function (tmp) {
wasm = tmp;
// we have manifold module, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;
wasm.setup();
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
flatShading: true
}));
scene.add(mesh);
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
};
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
csg('difference');
const output = document.querySelector("#output");
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function (time) {
mesh.rotation.x = time / 2000;
mesh.rotation.y = time / 1000;
renderer.render(scene, camera);
});
});
// functions to convert between three.js and wasm
function geometry2mesh(geometry) {
const vertPos = geometry.attributes.position.array;
const vertNormal = geometry.attributes.normal.array;
const triVerts = geometry.index.array;
return new wasm.Mesh({vertPos, vertNormal, triVerts});
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
// most of three.js geometries aren't manifolds, so...
function simplify(geometry) {
delete geometry.attributes.normal;
delete geometry.attributes.uv;
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
simplified.computeVertexNormals();
return simplified;
}
</script>
<script type="module" src="three.ts"></script>
</html>
+148
View File
@@ -0,0 +1,148 @@
// Copyright 2024 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {BoxGeometry, BufferAttribute, BufferGeometry, IcosahedronGeometry, Mesh as ThreeMesh, MeshLambertMaterial, MeshNormalMaterial, PerspectiveCamera, PointLight, Scene, WebGLRenderer} from 'three';
import Module, {Mesh} from './built/manifold';
// Load Manifold WASM library
const wasm = await Module();
wasm.setup();
const {Manifold, Mesh} = wasm;
// Define our set of materials
const materials = [
new MeshNormalMaterial({flatShading: true}),
new MeshLambertMaterial({color: 'red', flatShading: true}),
new MeshLambertMaterial({color: 'blue', flatShading: true})
];
const result = new ThreeMesh(undefined, materials);
// Set up Manifold IDs corresponding to materials
const firstID = Manifold.reserveIDs(materials.length);
// ids vector is parallel to materials vector - same indexing
const ids = [...Array<number>(materials.length)].map((_, idx) => firstID + idx);
// Build a mapping to get back from ID to material index
const id2matIndex = new Map();
ids.forEach((id, idx) => id2matIndex.set(id, idx));
// Set up Three.js scene
const scene = new Scene();
const camera = new PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;
camera.add(new PointLight(0xffffff, 1));
scene.add(camera);
scene.add(result);
// Set up Three.js renderer
const output = document.querySelector('#output')!;
const renderer = new WebGLRenderer({canvas: output, antialias: true});
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function(time: number) {
result.rotation.x = time / 2000;
result.rotation.y = time / 1000;
renderer.render(scene, camera);
});
// Create input meshes in Three.js
const cube = new BoxGeometry(0.2, 0.2, 0.2);
cube.clearGroups();
cube.addGroup(0, 18, 0); // First 6 faces colored by normal
cube.addGroup(18, Infinity, 1); // Rest of faces are red
const icosahedron = new IcosahedronGeometry(0.16);
icosahedron.clearGroups();
icosahedron.addGroup(30, Infinity, 2); // Last faces are blue
icosahedron.addGroup(0, 30, 0); // First 10 faces colored by normal
// The above groups are in reversed order to demonstrate the need for sorting.
// Convert Three.js input meshes to Manifolds
const manifoldCube = new Manifold(geometry2mesh(cube));
const manifoldIcosahedron = new Manifold(geometry2mesh(icosahedron));
// Set up UI for operations
type BooleanOp = 'union'|'difference'|'intersection';
function csg(operation: BooleanOp) {
result.geometry?.dispose();
result.geometry = mesh2geometry(
Manifold[operation](manifoldCube, manifoldIcosahedron).getMesh());
}
csg('union');
const selectElement = document.querySelector('select')!;
selectElement.onchange = function() {
csg(selectElement.value as BooleanOp);
};
// Convert Three.js BufferGeometry to Manifold Mesh
function geometry2mesh(geometry: BufferGeometry) {
// Only using position in this sample for simplicity. Can interleave any other
// desired attributes here such as UV, normal, etc.
const vertProperties = geometry.attributes.position.array as Float32Array;
// Manifold only uses indexed geometry, so generate an index if necessary.
const triVerts = geometry.index != null ?
geometry.index.array as Uint32Array :
new Uint32Array(vertProperties.length / 3).map((_, idx) => idx);
// Create a triangle run for each group (material) - akin to a draw call.
const starts = [...Array(geometry.groups.length)].map(
(_, idx) => geometry.groups[idx].start);
// Map the materials to ID.
const originalIDs = [...Array(geometry.groups.length)].map(
(_, idx) => ids[geometry.groups[idx].materialIndex!]);
// List the runs in sequence.
const indices = Array.from(starts.keys())
indices.sort((a, b) => starts[a] - starts[b])
const runIndex = new Uint32Array(indices.map(i => starts[i]));
const runOriginalID = new Uint32Array(indices.map(i => originalIDs[i]));
// Create the MeshGL for I/O with Manifold library.
const mesh =
new Mesh({numProp: 3, vertProperties, triVerts, runIndex, runOriginalID});
// Automatically merge vertices with nearly identical positions to create a
// Manifold. This only fills in the mergeFromVert and mergeToVert vectors -
// these are automatically filled in for any mesh returned by Manifold. These
// are necessary because GL drivers require duplicate verts when any
// properties change, e.g. a UV boundary or sharp corner.
mesh.merge();
return mesh;
}
// Convert Manifold Mesh to Three.js BufferGeometry
function mesh2geometry(mesh: Mesh) {
const geometry = new BufferGeometry();
// Assign buffers
geometry.setAttribute(
'position', new BufferAttribute(mesh.vertProperties, 3));
geometry.setIndex(new BufferAttribute(mesh.triVerts, 1));
// Create a group (material) for each ID. Note that there may be multiple
// triangle runs returned with the same ID, though these will always be
// sequential since they are sorted by ID. In this example there are two runs
// for the MeshNormalMaterial, one corresponding to each input mesh that had
// this ID. This allows runTransform to return the total transformation matrix
// applied to each triangle run from its input mesh - even after many
// consecutive operations.
let id = mesh.runOriginalID[0];
let start = mesh.runIndex[0];
for (let run = 0; run < mesh.numRun; ++run) {
const nextID = mesh.runOriginalID[run + 1];
if (nextID !== id) {
const end = mesh.runIndex[run + 1];
geometry.addGroup(start, end - start, id2matIndex.get(id));
id = nextID;
start = end;
}
}
return geometry;
}
+24
View File
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "es2022",
"module": "es2022",
"moduleResolution": "Bundler",
"lib": [
"es2022",
"dom"
],
"sourceMap": true,
"inlineSources": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"declaration": true,
"skipLibCheck": true,
},
"include": [
"./*.ts"
],
"exclude": []
}
+27
View File
@@ -0,0 +1,27 @@
// vite.config.js
import {resolve} from 'path'
import {defineConfig} from 'vite'
export default defineConfig({
test: {testTimeout: 15000},
worker: {
format: 'es',
},
build: {
target: 'esnext',
sourcemap: true,
rollupOptions: {
input: {
manifoldCAD: resolve(__dirname, 'index.html'),
makeManifold: resolve(__dirname, 'make-manifold.html'),
modelViewer: resolve(__dirname, 'model-viewer.html'),
three: resolve(__dirname, 'three.html'),
},
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
}
},
},
})
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2024 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {cleanup, evaluateCADToModel} from './worker';
// Setup complete
self.postMessage(null);
if (self.console) {
const oldLog = self.console.log;
self.console.log = function(...args) {
let message = '';
for (const arg of args) {
if (arg == null) {
message += 'undefined';
} else if (typeof arg == 'object') {
message += JSON.stringify(arg, null, 4);
} else {
message += arg.toString();
}
}
self.postMessage({log: message});
oldLog(...args);
};
}
self.onmessage = async (e) => {
try {
const result = await evaluateCADToModel(e.data);
self.postMessage(result);
} catch (error: any) {
console.log(error.toString());
self.postMessage({objectURL: null});
} finally {
cleanup();
}
};
-154
View File
@@ -1,154 +0,0 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const threePath = 'https://cdn.jsdelivr.net/npm/three@0.144.0/';
importScripts(
'manifold.js', threePath + 'build/three.js',
threePath + 'examples/js/exporters/GLTFExporter.js');
// manifold member functions that returns a new manifold
const memberFunctions = [
'add', 'subtract', 'intersect', 'refine', 'transform', 'translate', 'rotate',
'scale', 'asOriginal', 'decompose'
];
// top level functions that constructs a new manifold
const constructors = [
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'union',
'difference', 'intersection', 'compose', 'levelSet', 'smooth'
];
const utils = [
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
'getCircularSegments', 'Mesh'
];
const exposedFunctions = constructors.concat(utils);
let wasm;
Module().then(function(tmp) {
wasm = tmp;
wasm.setup();
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
let manifoldRegistry = [];
for (const name of memberFunctions) {
const originalFn = wasm.Manifold.prototype[name];
wasm.Manifold.prototype['_' + name] = originalFn;
wasm.Manifold.prototype[name] = function(...args) {
const result = this['_' + name](...args);
manifoldRegistry.push(result);
return result;
}
}
for (const name of constructors) {
const originalFn = wasm[name];
wasm[name] = function(...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
}
}
wasm.cleanup =
function() {
for (const obj of manifoldRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
manifoldRegistry = [];
}
postMessage(null);
});
const oldLog = console.log;
console.log = function(...args) {
let message = '';
for (const arg of args) {
if (typeof arg == 'object') {
message += JSON.stringify(arg, null, 4);
} else {
message += arg.toString();
}
}
postMessage({log: message});
oldLog(...args);
};
onmessage =
(e) => {
const content = e.data + '\nexportGLB(result);\n';
try {
const f = new Function(...exposedFunctions, content);
f(...exposedFunctions.map(name => wasm[name]));
} catch (error) {
console.log(error.toString());
postMessage({objectURL: null});
} finally {
wasm.cleanup();
}
}
// Export & Rendering
// ------------------------------------------------------------
const mesh = new THREE.Mesh(
undefined,
new THREE.MeshStandardMaterial(
{color: 'yellow', metalness: 1, roughness: 0.2}));
const rotation = new THREE.Matrix4();
rotation.set(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
mesh.setRotationFromMatrix(rotation); // Z-up -> Y-up
mesh.scale.setScalar(0.001); // mm -> m
const exporter = new THREE.GLTFExporter();
function exportGLB(manifold) {
console.log(`Triangles: ${manifold.numTri().toLocaleString()}`);
const box = manifold.boundingBox();
const size = [0, 0, 0];
for (let i = 0; i < 3; i++) {
size[i] = Math.round((box.max[i] - box.min[i]) * 10) / 10;
}
console.log(`Bounding Box: X = ${size[0].toLocaleString()} mm, Y = ${
size[1].toLocaleString()} mm, Z = ${size[2].toLocaleString()} mm`);
const volume = Math.round(manifold.getProperties().volume / 10);
console.log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
(volume / 100).toLocaleString()} cm^3`);
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(manifold.getMesh());
exporter.parse(
mesh,
(gltf) => {
const blob = new Blob([gltf], {type: 'application/octet-stream'});
postMessage({objectURL: URL.createObjectURL(blob)});
},
() => {
console.log('glTF export failed!');
postMessage({objectURL: null});
},
{binary: true});
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {WebIO} from '@gltf-transform/core';
import assert from 'node:assert';
import {afterEach, expect, suite, test} from 'vitest';
import {readMesh, setupIO} from './gltf-io';
// @ts-ignore
import {examples} from './public/examples.js';
import {Mesh} from './public/manifold';
import {cleanup, evaluateCADToModel, module} from './worker';
const io = setupIO(new WebIO());
async function runExample(name: string) {
const code = examples.functionBodies.get(name);
const result = await evaluateCADToModel(code);
cleanup();
assert.ok(result?.glbURL);
const docIn = await io.read(result.glbURL);
URL.revokeObjectURL(result.glbURL);
const nodes = docIn.getRoot().listNodes();
for (const node of nodes) {
const docMesh = node.getMesh();
if (!docMesh) {
continue;
}
const {mesh} = readMesh(docMesh)!;
const manifold = new module.Manifold(mesh as Mesh);
const prop = manifold.getProperties();
const genus = manifold.genus();
manifold.delete();
return {...prop, genus};
}
assert.ok(false);
}
// allow vitest to report progress after each test
// before going into heavy computation which blocks main thread
afterEach(async () => {await new Promise(resolve => setTimeout(resolve, 500))})
suite('Examples', () => {
test('Intro', async () => {
const result = await runExample('Intro');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
});
test('Tetrahedron Puzzle', async () => {
const result = await runExample('Tetrahedron Puzzle');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(7240, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(3235, 1, 'Surface Area');
});
test('Rounded Frame', async () => {
const result = await runExample('Rounded Frame');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(270807, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(74599, 1, 'Surface Area');
});
test('Heart', async () => {
const result = await runExample('Heart');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(3.342, 0.001, 'Volume');
expect(result.surfaceArea).to.be.closeTo(11.51, 0.01, 'Surface Area');
});
test('Scallop', async () => {
const result = await runExample('Scallop');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(39900, 100, 'Volume');
expect(result.surfaceArea).to.be.closeTo(7930, 10, 'Surface Area');
});
test('Torus Knot', async () => {
const result = await runExample('Torus Knot');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(20786, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(11177, 1, 'Surface Area');
});
test('Menger Sponge', async () => {
const result = await runExample('Menger Sponge');
expect(result.genus).to.equal(729, 'Genus');
expect(result.volume).to.be.closeTo(203222, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(130475, 10, 'Surface Area');
});
test('Stretchy Bracelet', async () => {
const result = await runExample('Stretchy Bracelet');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
});
test('Gyroid Module', async () => {
const result = await runExample('Gyroid Module');
expect(result.genus).to.equal(15, 'Genus');
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
});
});
+788
View File
@@ -0,0 +1,788 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Accessor, Animation, AnimationSampler, Document, mat4, Material, Mesh as GLTFMesh, Node, WebIO} from '@gltf-transform/core';
import {KHRMaterialsUnlit, KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
import {fileForContentTypes, FileForRelThumbnail, to3dmodel} from '@jscadui/3mf-export';
import {strToU8, Zippable, zipSync} from 'fflate'
import * as glMatrix from 'gl-matrix';
import Module, {CrossSection, Manifold, ManifoldToplevel, Mesh, Vec3} from './built/manifold';
import {Properties, setupIO, writeMesh} from './gltf-io';
import {GLTFMaterial, Quat} from './public/editor';
interface GlobalDefaults {
roughness: number;
metallic: number;
baseColorFactor: [number, number, number];
alpha: number;
unlit: boolean;
animationLength: number;
animationMode: 'loop'|'ping-pong';
}
interface WorkerStatic extends ManifoldToplevel {
GLTFNode: typeof GLTFNode;
show(manifold: Manifold): Manifold;
only(manifold: Manifold): Manifold;
setMaterial(manifold: Manifold, material: GLTFMaterial): Manifold;
setMorphStart(manifold: Manifold, func: (v: Vec3) => void): void;
setMorphEnd(manifold: Manifold, func: (v: Vec3) => void): void;
cleanup(): void;
}
export const module = await Module() as WorkerStatic;
module.setup();
// Faster on modern browsers than Float32Array
glMatrix.glMatrix.setMatrixArrayType(Array);
const io = setupIO(new WebIO());
io.registerExtensions(KHRONOS_EXTENSIONS);
// manifold static methods (that return a new manifold)
const manifoldStaticFunctions = [
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'compose',
'union', 'difference', 'intersection', 'levelSet', 'smooth', 'ofMesh', 'hull'
];
// manifold member functions (that return a new manifold)
const manifoldMemberFunctions = [
'add',
'subtract',
'intersect',
'decompose',
'warp',
'transform',
'translate',
'rotate',
'scale',
'mirror',
'calculateCurvature',
'calculateNormals',
'smoothByNormals',
'smoothOut',
'refine',
'refineToLength',
'setProperties',
'asOriginal',
'trimByPlane',
'split',
'splitByPlane',
'slice',
'project',
'hull'
];
// CrossSection static methods (that return a new cross-section)
const crossSectionStaticFunctions = [
'square', 'circle', 'union', 'difference', 'intersection', 'compose',
'ofPolygons', 'hull'
];
// CrossSection member functions (that return a new cross-section)
const crossSectionMemberFunctions = [
'add', 'subtract', 'intersect', 'rectClip', 'decompose', 'transform',
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset', 'hull'
];
// top level functions that construct a new manifold/mesh
const toplevelConstructors = ['show', 'only', 'setMaterial'];
const toplevel = [
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
'getCircularSegments', 'resetToCircularDefaults', 'Mesh', 'GLTFNode',
'Manifold', 'CrossSection', 'setMorphStart', 'setMorphEnd'
];
const exposedFunctions = toplevelConstructors.concat(toplevel);
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
const memoryRegistry = new Array<Manifold|CrossSection>();
function addMembers(
className: string, methodNames: Array<string>, areStatic: boolean) {
//@ts-ignore
const cls = module[className];
const obj = areStatic ? cls : cls.prototype;
for (const name of methodNames) {
const originalFn = obj[name];
obj[name] = function(...args: any) {
//@ts-ignore
const result = originalFn(...args);
memoryRegistry.push(result);
return result;
};
}
}
addMembers('Manifold', manifoldMemberFunctions, false);
addMembers('Manifold', manifoldStaticFunctions, true);
addMembers('CrossSection', crossSectionMemberFunctions, false);
addMembers('CrossSection', crossSectionStaticFunctions, true);
for (const name of toplevelConstructors) {
//@ts-ignore
const originalFn = module[name];
//@ts-ignore
module[name] = function(...args: any) {
const result = originalFn(...args);
memoryRegistry.push(result);
return result;
};
}
// Debug setup to show source meshes
let ghost = false;
const shown = new Map<number, Mesh>();
const singles = new Map<number, Mesh>();
const FPS = 30;
const GLOBAL_DEFAULTS = {
roughness: 0.2,
metallic: 1,
baseColorFactor: [1, 1, 0] as [number, number, number],
alpha: 1,
unlit: false,
animationLength: 1,
animationMode: 'loop'
};
const SHOW = {
baseColorFactor: [1, 0, 0],
alpha: 0.25,
roughness: 1,
metallic: 0
} as GLTFMaterial;
const GHOST = {
baseColorFactor: [0.5, 0.5, 0.5],
alpha: 0.25,
roughness: 1,
metallic: 0
} as GLTFMaterial;
const nodes = new Array<GLTFNode>();
const id2material = new Map<number, GLTFMaterial>();
const materialCache = new Map<GLTFMaterial, Material>();
const object2globalID = new Map<GLTFNode|Manifold, number>();
const manifold2morph = new Map<Manifold, Morph>();
// lib3mf doesn't like objectid=0
let nextGlobalID = 1;
let animation: Animation;
let timesAccessor: Accessor;
let weightsAccessor: Accessor;
let weightsSampler: AnimationSampler;
let hasAnimation: boolean;
export function cleanup() {
for (const obj of memoryRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
memoryRegistry.length = 0;
ghost = false;
shown.clear();
singles.clear();
nodes.length = 0;
id2material.clear();
materialCache.clear();
object2globalID.clear();
manifold2morph.clear();
nextGlobalID = 1;
}
interface Morph {
start?: (v: Vec3) => void;
end?: (v: Vec3) => void;
}
interface Mesh3MF {
id: string;
vertices: Float32Array;
indices: Uint32Array;
name?: string;
}
interface Child3MF {
objectID: string;
transform?: mat4;
}
interface Component3MF {
id: string;
children: Array<Child3MF>;
name?: string;
}
interface Header {
unit?: 'micron'|'millimeter'|'centimeter'|'inch'|'foot'|'meter';
title?: string;
author?: string;
description?: string;
application?: string;
creationDate?: string;
license?: string;
modificationDate?: string;
}
interface To3MF {
meshes: Array<Mesh3MF>;
components: Array<Component3MF>;
items: Array<Child3MF>;
precision: number;
header: Header;
}
class GLTFNode {
private _parent?: GLTFNode;
manifold?: Manifold;
translation?: Vec3|((t: number) => Vec3);
rotation?: Vec3|((t: number) => Vec3);
scale?: Vec3|((t: number) => Vec3);
material?: GLTFMaterial;
name?: string;
constructor(parent?: GLTFNode) {
this._parent = parent;
nodes.push(this);
}
clone(parent?: GLTFNode) {
const copy = {...this};
copy._parent = parent;
nodes.push(copy);
return copy;
}
get parent() {
return this._parent;
}
}
module.GLTFNode = GLTFNode;
const globalDefaults = {...GLOBAL_DEFAULTS};
module.setMaterial = (manifold: Manifold, material: GLTFMaterial): Manifold => {
const out = manifold.asOriginal();
id2material.set(out.originalID(), material);
return out;
};
module.setMorphStart = (manifold: Manifold, func: (v: Vec3) => void): void => {
const morph = manifold2morph.get(manifold);
if (morph != null) {
morph.start = func;
} else {
manifold2morph.set(manifold, {start: func});
}
};
module.setMorphEnd = (manifold: Manifold, func: (v: Vec3) => void): void => {
const morph = manifold2morph.get(manifold);
if (morph != null) {
morph.end = func;
} else {
manifold2morph.set(manifold, {end: func});
}
};
function debug(manifold: Manifold, map: Map<number, Mesh>) {
let result = manifold.asOriginal();
map.set(result.originalID(), result.getMesh());
return result;
};
module.show = (manifold) => {
return debug(manifold, shown);
};
module.only = (manifold) => {
ghost = true;
return debug(manifold, singles);
};
// Swallow informational logs in testing framework
function log(...args: any[]) {
if (typeof self !== 'undefined' && self.console) {
self.console.log(...args);
}
}
function euler2quat(rotation: Vec3): Quat {
const {quat} = glMatrix;
const deg2rad = Math.PI / 180;
const q = quat.create() as Quat;
quat.rotateZ(q, q, deg2rad * rotation[2]);
quat.rotateY(q, q, deg2rad * rotation[1]);
quat.rotateX(q, q, deg2rad * rotation[0]);
return q;
}
function addMotion(
doc: Document, type: 'translation'|'rotation'|'scale', node: GLTFNode,
out: Node): Vec3|null {
const motion = node[type];
if (motion == null) {
return null;
}
if (typeof motion !== 'function') {
return motion;
}
const nFrames = timesAccessor.getCount();
const nEl = type == 'rotation' ? 4 : 3;
const frames = new Float32Array(nEl * nFrames);
for (let i = 0; i < nFrames; ++i) {
const x = i / (nFrames - 1);
const m = motion(
globalDefaults.animationMode !== 'ping-pong' ?
x :
(1 - Math.cos(x * 2 * Math.PI)) / 2);
frames.set(nEl === 4 ? euler2quat(m) : m, nEl * i);
}
const framesAccessor =
doc.createAccessor(node.name + ' ' + type + ' frames')
.setBuffer(doc.getRoot().listBuffers()[0])
.setArray(frames)
.setType(nEl === 4 ? Accessor.Type.VEC4 : Accessor.Type.VEC3);
const sampler = doc.createAnimationSampler()
.setInput(timesAccessor)
.setOutput(framesAccessor)
.setInterpolation('LINEAR');
const channel = doc.createAnimationChannel()
.setTargetPath(type)
.setTargetNode(out)
.setSampler(sampler);
animation.addSampler(sampler);
animation.addChannel(channel);
hasAnimation = true;
return motion(0);
}
function setMorph(doc: Document, node: Node, manifold: Manifold) {
if (manifold2morph.has(manifold)) {
const channel = doc.createAnimationChannel()
.setTargetPath('weights')
.setTargetNode(node)
.setSampler(weightsSampler);
animation.addChannel(channel);
hasAnimation = true;
}
}
function morphStart(manifoldMesh: Mesh, morph?: Morph): number[] {
const inputPositions: number[] = [];
if (morph == null) {
return inputPositions;
}
for (let i = 0; i < manifoldMesh.numVert; ++i) {
for (let j = 0; j < 3; ++j)
inputPositions[i * 3 + j] =
manifoldMesh.vertProperties[i * manifoldMesh.numProp + j];
}
if (morph.start) {
for (let i = 0; i < manifoldMesh.numVert; ++i) {
const vertProp = manifoldMesh.vertProperties;
const offset = i * manifoldMesh.numProp;
const pos = inputPositions.slice(offset, offset + 3) as Vec3;
morph.start(pos);
for (let j = 0; j < 3; ++j) vertProp[offset + j] = pos[j];
}
}
return inputPositions;
}
function morphEnd(
doc: Document, manifoldMesh: Mesh, mesh: GLTFMesh, inputPositions: number[],
morph?: Morph) {
if (morph == null) {
return;
}
mesh.setWeights([0]);
mesh.listPrimitives().forEach((primitive, i) => {
if (morph.end) {
for (let i = 0; i < manifoldMesh.numVert; ++i) {
const pos = inputPositions.slice(3 * i, 3 * (i + 1)) as Vec3;
morph.end(pos);
inputPositions.splice(3 * i, 3, ...pos);
}
}
const startPosition = primitive.getAttribute('POSITION')!.getArray()!;
const array = new Float32Array(startPosition.length);
const offset = manifoldMesh.runIndex[i];
for (let j = 0; j < array.length; ++j) {
array[j] = inputPositions[offset + j] - startPosition[j];
}
const morphAccessor = doc.createAccessor(mesh.getName() + ' morph target')
.setBuffer(doc.getRoot().listBuffers()[0])
.setArray(array)
.setType(Accessor.Type.VEC3);
const morphTarget =
doc.createPrimitiveTarget().setAttribute('POSITION', morphAccessor);
primitive.addTarget(morphTarget);
});
}
function createGLTFnode(doc: Document, node: GLTFNode): Node {
const out = doc.createNode(node.name);
const pos = addMotion(doc, 'translation', node, out);
if (pos != null) {
out.setTranslation(pos);
}
const rot = addMotion(doc, 'rotation', node, out);
if (rot != null) {
out.setRotation(euler2quat(rot));
}
const scale = addMotion(doc, 'scale', node, out);
if (scale != null) {
out.setScale(scale);
}
return out;
}
function getBackupMaterial(node?: GLTFNode): GLTFMaterial {
if (node == null) {
return {};
}
if (node.material == null) {
node.material = getBackupMaterial(node.parent);
}
return node.material;
}
function makeDefaultedMaterial(
doc: Document, matIn: GLTFMaterial = {}): Material {
const defaults = {...globalDefaults};
Object.assign(defaults, matIn);
const {roughness, metallic, baseColorFactor, alpha, unlit} = defaults;
const material = doc.createMaterial(matIn.name ?? '');
if (unlit) {
const unlit = doc.createExtension(KHRMaterialsUnlit).createUnlit();
material.setExtension('KHR_materials_unlit', unlit);
}
if (alpha < 1) {
material.setAlphaMode(Material.AlphaMode.BLEND).setDoubleSided(true);
}
return material.setRoughnessFactor(roughness)
.setMetallicFactor(metallic)
.setBaseColorFactor([...baseColorFactor, alpha]);
}
function getCachedMaterial(doc: Document, matDef: GLTFMaterial): Material {
if (!materialCache.has(matDef)) {
materialCache.set(matDef, makeDefaultedMaterial(doc, matDef));
}
return materialCache.get(matDef)!;
}
function addMesh(
doc: Document, to3mf: To3MF, node: Node, manifold: Manifold,
backupMaterial: GLTFMaterial = {}) {
const numTri = manifold.numTri();
if (numTri == 0) {
log('Empty manifold, skipping.');
return;
}
log(`Triangles: ${numTri.toLocaleString()}`);
const box = manifold.boundingBox();
const size = [0, 0, 0];
for (let i = 0; i < 3; i++) {
size[i] = Math.round((box.max[i] - box.min[i]) * 10) / 10;
}
log(`Bounding Box: X = ${size[0].toLocaleString()} mm, Y = ${
size[1].toLocaleString()} mm, Z = ${size[2].toLocaleString()} mm`);
const volume = Math.round(manifold.getProperties().volume / 10);
log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
(volume / 100).toLocaleString()} cm^3`);
// From Z-up to Y-up (glTF)
const manifoldMesh = manifold.getMesh();
const id2properties = new Map<number, Properties>();
for (const id of manifoldMesh.runOriginalID!) {
const material = id2material.get(id) || backupMaterial;
id2properties.set(id, {
material: getCachedMaterial(doc, ghost ? GHOST : material),
attributes: ['POSITION', ...material.attributes ?? []]
});
}
const morph = manifold2morph.get(manifold);
const inputPositions = morphStart(manifoldMesh, morph);
const mesh = writeMesh(doc, manifoldMesh, id2properties);
mesh.setName(node.getName());
node.setMesh(mesh);
morphEnd(doc, manifoldMesh, mesh, inputPositions, morph);
const vertices = manifoldMesh.numProp === 3 ?
manifoldMesh.vertProperties :
new Float32Array(manifoldMesh.numVert * 3);
if (manifoldMesh.numProp > 3) {
for (let i = 0; i < manifoldMesh.numVert; ++i) {
for (let j = 0; j < 3; ++j)
vertices[i * 3 + j] =
manifoldMesh.vertProperties[i * manifoldMesh.numProp + j];
}
}
object2globalID.set(manifold, nextGlobalID);
to3mf.meshes.push(
{vertices, indices: manifoldMesh.triVerts, id: `${nextGlobalID++}`});
for (const [run, id] of manifoldMesh.runOriginalID!.entries()) {
const show = shown.has(id);
const inMesh = show ? shown.get(id) : singles.get(id);
if (inMesh == null) {
continue;
}
id2properties.get(id)!.material = getCachedMaterial(
doc, show ? SHOW : (id2material.get(id) || backupMaterial));
const debugNode = doc.createNode('debug')
.setMesh(writeMesh(doc, inMesh, id2properties))
.setMatrix(manifoldMesh.transform(run));
node.addChild(debugNode);
}
}
function cloneNode(toNode: Node, fromNode: Node) {
toNode.setMesh(fromNode.getMesh());
fromNode.listChildren().forEach((child) => {
const clone = child.clone();
toNode.addChild(clone);
});
}
function cloneNodeNewMaterial(
doc: Document, toNode: Node, fromNode: Node, backupMaterial: Material,
oldBackupMaterial: Material) {
cloneNode(toNode, fromNode);
const mesh = doc.createMesh();
toNode.setMesh(mesh);
fromNode.getMesh()!.listPrimitives().forEach((primitive) => {
const newPrimitive = primitive.clone();
if (primitive.getMaterial() === oldBackupMaterial) {
newPrimitive.setMaterial(backupMaterial);
}
mesh.addPrimitive(newPrimitive);
});
}
function createNodeFromCache(
doc: Document, to3MF: To3MF, nodeDef: GLTFNode,
manifold2node: Map<Manifold, Map<GLTFMaterial, Node>>): Node {
const node = createGLTFnode(doc, nodeDef);
const {manifold} = nodeDef;
if (manifold != null) {
setMorph(doc, node, manifold);
const backupMaterial = getBackupMaterial(nodeDef);
const cachedNodes = manifold2node.get(manifold);
if (cachedNodes == null) {
addMesh(doc, to3MF, node, manifold, backupMaterial);
const cache = new Map<GLTFMaterial, Node>();
cache.set(backupMaterial, node);
manifold2node.set(manifold, cache);
} else {
const cachedNode = cachedNodes.get(backupMaterial);
if (cachedNode == null) {
const [oldBackupMaterial, oldNode] = cachedNodes.entries().next().value;
cloneNodeNewMaterial(
doc, node, oldNode, getCachedMaterial(doc, backupMaterial),
getCachedMaterial(doc, oldBackupMaterial));
cachedNodes.set(backupMaterial, node);
} else {
cloneNode(node, cachedNode);
}
}
}
object2globalID.set(nodeDef, nextGlobalID);
to3MF.components.push({
id: `${nextGlobalID++}`,
name: nodeDef.name,
children:
manifold == null ? [] : [{objectID: `${object2globalID.get(manifold)}`}]
});
return node;
}
async function exportModels(defaults: GlobalDefaults, manifold?: Manifold) {
Object.assign(globalDefaults, GLOBAL_DEFAULTS);
Object.assign(globalDefaults, defaults);
const doc = new Document();
const halfRoot2 = Math.sqrt(2) / 2;
const mm2m = 1 / 1000;
const wrapper = doc.createNode('wrapper')
.setRotation([-halfRoot2, 0, 0, halfRoot2])
.setScale([mm2m, mm2m, mm2m]);
doc.createScene().addChild(wrapper);
animation = doc.createAnimation('');
hasAnimation = false;
const nFrames = Math.round(globalDefaults.animationLength * FPS) + 1;
const times = new Float32Array(nFrames);
const weights = new Float32Array(nFrames);
for (let i = 0; i < nFrames; ++i) {
const x = i / (nFrames - 1);
times[i] = x * globalDefaults.animationLength;
weights[i] = globalDefaults.animationMode !== 'ping-pong' ?
x :
(1 - Math.cos(x * 2 * Math.PI)) / 2;
}
timesAccessor = doc.createAccessor('animation times')
.setBuffer(doc.createBuffer())
.setArray(times)
.setType(Accessor.Type.SCALAR);
weightsAccessor = doc.createAccessor('animation weights')
.setBuffer(doc.getRoot().listBuffers()[0])
.setArray(weights)
.setType(Accessor.Type.SCALAR);
weightsSampler = doc.createAnimationSampler()
.setInput(timesAccessor)
.setOutput(weightsAccessor)
.setInterpolation('LINEAR');
animation.addSampler(weightsSampler);
const to3mf = {
meshes: [],
components: [],
items: [],
precision: 7,
header: {
unit: 'millimeter',
title: 'ManifoldCAD.org model',
description: 'ManifoldCAD.org model',
application: 'ManifoldCAD.org',
}
} as To3MF;
if (nodes.length > 0) {
const node2gltf = new Map<GLTFNode, Node>();
const manifold2node = new Map<Manifold, Map<GLTFMaterial, Node>>();
let leafNodes = 0;
for (const nodeDef of nodes) {
node2gltf.set(
nodeDef, createNodeFromCache(doc, to3mf, nodeDef, manifold2node));
if (nodeDef.manifold) {
++leafNodes;
}
}
for (const nodeDef of nodes) {
const gltfNode = node2gltf.get(nodeDef)!;
const child = {
objectID: `${object2globalID.get(nodeDef)}`,
transform: gltfNode.getMatrix()
};
const {parent} = nodeDef;
if (parent == null) {
wrapper.addChild(gltfNode);
to3mf.items.push(child);
} else {
node2gltf.get(parent)!.addChild(gltfNode);
const parent3mf = to3mf.components.find(
(comp) => comp.id == `${object2globalID.get(parent)}`)!;
parent3mf.children.push(child);
}
}
log('Total glTF nodes: ', nodes.length,
', Total mesh references: ', leafNodes);
} else {
if (manifold == null) {
log('No output because "result" is undefined and no "GLTFNode"s were created.');
return;
}
const node = doc.createNode();
addMesh(doc, to3mf, node, manifold);
wrapper.addChild(node);
to3mf.items.push({objectID: `${object2globalID.get(manifold)}`});
}
if (!hasAnimation) {
timesAccessor.dispose();
weightsAccessor.dispose();
weightsSampler.dispose();
animation.dispose();
}
const glb = await io.writeBinary(doc);
const blobGLB = new Blob([glb], {type: 'application/octet-stream'});
const fileForRelThumbnail = new FileForRelThumbnail();
fileForRelThumbnail.add3dModel('3D/3dmodel.model')
const model = to3dmodel(to3mf as any);
const files: Zippable = {};
files['3D/3dmodel.model'] = strToU8(model);
files[fileForContentTypes.name] = strToU8(fileForContentTypes.content);
files[fileForRelThumbnail.name] = strToU8(fileForRelThumbnail.content);
const zipFile = zipSync(files);
const blob3MF = new Blob(
[zipFile],
{type: 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml'});
return ({
glbURL: URL.createObjectURL(blobGLB),
threeMFURL: URL.createObjectURL(blob3MF)
});
}
function evaluateCADToManifold(code: string) {
const globalDefaults = {} as GlobalDefaults;
const context = {
globalDefaults,
exportModels,
glMatrix,
module,
...Object.fromEntries(
exposedFunctions.map((name) => [name, (module as any)[name]]),
),
};
const evalFn = new Function(
...Object.keys(context),
'resetToCircularDefaults();\n' + code +
'\n return typeof result === "undefined" ? undefined : result;',
);
const manifold = evalFn(...Object.values(context));
return {globalDefaults, manifold};
}
export async function evaluateCADToModel(code: string) {
const {globalDefaults, manifold} = evaluateCADToManifold(code);
return await exportModels(globalDefaults, manifold);
}
+232
View File
@@ -0,0 +1,232 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <vector>
#include "cross_section.h"
#include "manifold.h"
#include "polygon.h"
#include "sdf.h"
using namespace emscripten;
using namespace manifold;
namespace js {
val MeshGL2JS(const MeshGL& mesh) {
val meshJS = val::object();
meshJS.set("numProp", mesh.numProp);
meshJS.set("triVerts",
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
.call<val>("slice"));
meshJS.set("vertProperties",
val(typed_memory_view(mesh.vertProperties.size(),
mesh.vertProperties.data()))
.call<val>("slice"));
meshJS.set("mergeFromVert", val(typed_memory_view(mesh.mergeFromVert.size(),
mesh.mergeFromVert.data()))
.call<val>("slice"));
meshJS.set("mergeToVert", val(typed_memory_view(mesh.mergeToVert.size(),
mesh.mergeToVert.data()))
.call<val>("slice"));
meshJS.set("runIndex",
val(typed_memory_view(mesh.runIndex.size(), mesh.runIndex.data()))
.call<val>("slice"));
meshJS.set("runOriginalID", val(typed_memory_view(mesh.runOriginalID.size(),
mesh.runOriginalID.data()))
.call<val>("slice"));
meshJS.set("faceID",
val(typed_memory_view(mesh.faceID.size(), mesh.faceID.data()))
.call<val>("slice"));
meshJS.set("halfedgeTangent",
val(typed_memory_view(mesh.halfedgeTangent.size(),
mesh.halfedgeTangent.data()))
.call<val>("slice"));
meshJS.set("runTransform", val(typed_memory_view(mesh.runTransform.size(),
mesh.runTransform.data()))
.call<val>("slice"));
return meshJS;
}
MeshGL MeshJS2GL(const val& mesh) {
MeshGL out;
out.numProp = mesh["numProp"].as<int>();
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
out.vertProperties =
convertJSArrayToNumberVector<float>(mesh["vertProperties"]);
if (mesh["mergeFromVert"] != val::undefined()) {
out.mergeFromVert =
convertJSArrayToNumberVector<uint32_t>(mesh["mergeFromVert"]);
}
if (mesh["mergeToVert"] != val::undefined()) {
out.mergeToVert =
convertJSArrayToNumberVector<uint32_t>(mesh["mergeToVert"]);
}
if (mesh["runIndex"] != val::undefined()) {
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
}
if (mesh["runOriginalID"] != val::undefined()) {
out.runOriginalID =
convertJSArrayToNumberVector<uint32_t>(mesh["runOriginalID"]);
}
if (mesh["faceID"] != val::undefined()) {
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
}
if (mesh["halfedgeTangent"] != val::undefined()) {
out.halfedgeTangent =
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
}
if (mesh["runTransform"] != val::undefined()) {
out.runTransform =
convertJSArrayToNumberVector<float>(mesh["runTransform"]);
}
return out;
}
val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
MeshGL mesh = manifold.GetMeshGL(normalIdx);
return MeshGL2JS(mesh);
}
val Merge(const val& mesh) {
val out = val::object();
MeshGL meshGL = MeshJS2GL(mesh);
bool changed = meshGL.Merge();
out.set("changed", changed);
out.set("mesh", changed ? MeshGL2JS(meshGL) : mesh);
return out;
}
Manifold Smooth(const val& mesh,
const std::vector<Smoothness>& sharpenedEdges = {}) {
return Manifold::Smooth(MeshJS2GL(mesh), sharpenedEdges);
}
} // namespace js
namespace cross_js {
CrossSection OfPolygons(std::vector<std::vector<glm::vec2>> polygons,
int fill_rule) {
auto fr = fill_rule == 0 ? CrossSection::FillRule::EvenOdd
: fill_rule == 1 ? CrossSection::FillRule::NonZero
: fill_rule == 2 ? CrossSection::FillRule::Positive
: CrossSection::FillRule::Negative;
return CrossSection(polygons, fr);
}
CrossSection Union(const CrossSection& a, const CrossSection& b) {
return a + b;
}
CrossSection Difference(const CrossSection& a, const CrossSection& b) {
return a - b;
}
CrossSection Intersection(const CrossSection& a, const CrossSection& b) {
return a ^ b;
}
CrossSection UnionN(const std::vector<CrossSection>& cross_sections) {
return CrossSection::BatchBoolean(cross_sections, OpType::Add);
}
CrossSection DifferenceN(const std::vector<CrossSection>& cross_sections) {
return CrossSection::BatchBoolean(cross_sections, OpType::Subtract);
}
CrossSection IntersectionN(const std::vector<CrossSection>& cross_sections) {
return CrossSection::BatchBoolean(cross_sections, OpType::Intersect);
}
CrossSection Transform(CrossSection& cross_section, const val& mat) {
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
glm::mat3x2 matrix;
for (const int col : {0, 1, 2})
for (const int row : {0, 1}) matrix[col][row] = array[col * 3 + row];
return cross_section.Transform(matrix);
}
CrossSection Warp(CrossSection& cross_section, uintptr_t funcPtr) {
void (*f)(glm::vec2&) = reinterpret_cast<void (*)(glm::vec2&)>(funcPtr);
return cross_section.Warp(f);
}
CrossSection Offset(CrossSection& cross_section, double delta, int join_type,
double miter_limit, double arc_tolerance) {
auto jt = join_type == 0 ? CrossSection::JoinType::Square
: join_type == 1 ? CrossSection::JoinType::Round
: CrossSection::JoinType::Miter;
return cross_section.Offset(delta, jt, miter_limit, arc_tolerance);
}
void CollectVertices(std::vector<glm::vec2>& verts, const CrossSection& cs) {
auto polys = cs.ToPolygons();
for (auto poly : polys) verts.insert(verts.end(), poly.begin(), poly.end());
}
} // namespace cross_js
namespace man_js {
Manifold FromMeshJS(const val& mesh) { return Manifold(js::MeshJS2GL(mesh)); }
Manifold Union(const Manifold& a, const Manifold& b) { return a + b; }
Manifold Difference(const Manifold& a, const Manifold& b) { return a - b; }
Manifold Intersection(const Manifold& a, const Manifold& b) { return a ^ b; }
Manifold UnionN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, OpType::Add);
}
Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, OpType::Subtract);
}
Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, OpType::Intersect);
}
Manifold Transform(Manifold& manifold, const val& mat) {
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
glm::mat4x3 matrix;
for (const int col : {0, 1, 2, 3})
for (const int row : {0, 1, 2}) matrix[col][row] = array[col * 4 + row];
return manifold.Transform(matrix);
}
Manifold Warp(Manifold& manifold, uintptr_t funcPtr) {
void (*f)(glm::vec3&) = reinterpret_cast<void (*)(glm::vec3&)>(funcPtr);
return manifold.Warp(f);
}
Manifold SetProperties(Manifold& manifold, int numProp, uintptr_t funcPtr) {
void (*f)(float*, glm::vec3, const float*) =
reinterpret_cast<void (*)(float*, glm::vec3, const float*)>(funcPtr);
return manifold.SetProperties(numProp, f);
}
Manifold LevelSet(uintptr_t funcPtr, Box bounds, float edgeLength,
float level) {
float (*f)(const glm::vec3&) =
reinterpret_cast<float (*)(const glm::vec3&)>(funcPtr);
Mesh m = LevelSet(f, bounds, edgeLength, level);
return Manifold(m);
}
std::vector<Manifold> Split(Manifold& a, Manifold& b) {
auto [r1, r2] = a.Split(b);
return {r1, r2};
}
std::vector<Manifold> SplitByPlane(Manifold& m, glm::vec3 normal,
float originOffset) {
auto [a, b] = m.SplitByPlane(normal, originOffset);
return {a, b};
}
void CollectVertices(std::vector<glm::vec3>& verts, const Manifold& manifold) {
Mesh mesh = manifold.GetMesh();
verts.insert(verts.end(), mesh.vertPos.begin(), mesh.vertPos.end());
}
} // namespace man_js
+960
View File
@@ -0,0 +1,960 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Box, FillRule, JoinType, Mat3, Mat4, Polygons, Properties, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
/**
* Triangulates a set of /epsilon-valid polygons.
*
* @param polygons The set of polygons, wound CCW and representing multiple
* polygons and/or holes.
* @param precision The value of epsilon, bounding the uncertainty of the input
* @return The triangles, referencing the original polygon points in order.
*/
export function triangulate(polygons: Polygons, precision?: number): Vec3[];
/**
* @name Defaults
* These static properties control how circular shapes are quantized by
* default on construction. If circularSegments is specified, it takes
* precedence. If it is zero, then instead the minimum is used of the segments
* calculated based on edge length and angle, rounded up to the nearest
* multiple of four. To get numbers not divisible by four, circularSegments
* must be specified.
*/
///@{
export function setMinCircularAngle(angle: number): void;
export function setMinCircularEdgeLength(length: number): void;
export function setCircularSegments(segments: number): void;
export function getCircularSegments(radius: number): number;
export function resetToCircularDefaults(): void;
///@}
export class CrossSection {
/**
* Create a 2d cross-section from a set of contours (complex polygons). A
* boolean union operation (with Positive filling rule by default) is
* performed to combine overlapping polygons and ensure the resulting
* CrossSection is free of intersections.
*
* @param contours A set of closed paths describing zero or more complex
* polygons.
* @param fillRule The filling rule used to interpret polygon sub-regions in
* contours.
*/
constructor(polygons: Polygons, fillRule?: FillRule);
// Shapes
static square(size?: Vec2|number, center?: boolean): CrossSection;
static circle(radius: number, circularSegments?: number): CrossSection;
// Extrusions (2d to 3d manifold)
/**
* Constructs a manifold by extruding the cross-section along Z-axis.
*
* @param height Z-extent of extrusion.
* @param nDivisions Number of extra copies of the crossSection to insert into
* the shape vertically; especially useful in combination with twistDegrees to
* avoid interpolation artifacts. Default is none.
* @param twistDegrees Amount to twist the top crossSection relative to the
* bottom, interpolated linearly for the divisions in between.
* @param scaleTop Amount to scale the top (independently in X and Y). If the
* scale is {0, 0}, a pure cone is formed with only a single vertex at the
* top. Default {1, 1}.
* @param center If true, the extrusion is centered on the z-axis through the
* origin
* as opposed to resting on the XY plane as is default.
*/
extrude(
height: number, nDivisions?: number, twistDegrees?: number,
scaleTop?: Vec2|number, center?: boolean): Manifold;
/**
* Constructs a manifold by revolving this cross-section around its Y-axis and
* then setting this as the Z-axis of the resulting manifold. If the contours
* cross the Y-axis, only the part on the positive X side is used.
* Geometrically valid input will result in geometrically valid output.
*
* @param circularSegments Number of segments along its diameter. Default is
* calculated by the static Defaults.
*/
revolve(circularSegments?: number, revolveDegrees?: number): Manifold;
// Transformations
/**
* Transform this CrossSection in space. Stored in column-major order. This
* operation can be chained. Transforms are combined and applied lazily.
*
* @param m The affine transformation matrix to apply to all the vertices. The
* last row is ignored.
*/
transform(m: Mat3): CrossSection;
/**
* Move this CrossSection in space. This operation can be chained. Transforms
* are combined and applied lazily.
*
* @param v The vector to add to every vertex.
*/
translate(v: Vec2): CrossSection;
translate(x: number, y?: number): CrossSection;
/**
* Applies a (Z-axis) rotation to the CrossSection, in degrees. This operation
* can be chained. Transforms are combined and applied lazily.
*
* @param degrees degrees about the Z-axis to rotate.
*/
rotate(v: number): CrossSection;
/**
* Scale this CrossSection in space. This operation can be chained. Transforms
* are combined and applied lazily.
*
* @param v The vector to multiply every vertex by per component.
*/
scale(v: Vec2|number): CrossSection;
/**
* Mirror this CrossSection over the arbitrary axis described by the unit form
* of the given vector. If the length of the vector is zero, an empty
* CrossSection is returned. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param ax the axis to be mirrored over
*/
mirror(v: Vec2): CrossSection;
/**
* Move the vertices of this CrossSection (creating a new one) according to
* any arbitrary input function, followed by a union operation (with a
* Positive fill rule) that ensures any introduced intersections are not
* included in the result.
*
* @param warpFunc A function that modifies a given vertex position.
*/
warp(warpFunc: (vert: Vec2) => void): CrossSection;
/**
* Inflate the contours in CrossSection by the specified delta, handling
* corners according to the given JoinType.
*
* @param delta Positive deltas will cause the expansion of outlining contours
* to expand, and retraction of inner (hole) contours. Negative deltas will
* have the opposite effect.
* @param joinType The join type specifying the treatment of contour joins
* (corners).
* @param miterLimit The maximum distance in multiples of delta that vertices
* can be offset from their original positions with before squaring is
* applied, **when the join type is Miter** (default is 2, which is the
* minimum allowed). See the [Clipper2
* MiterLimit](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm)
* page for a visual example.
* @param circularSegments Number of segments per 360 degrees of
* <B>JoinType::Round</B> corners (roughly, the number of vertices that
* will be added to each contour). Default is calculated by the static Quality
* defaults according to the radius.
*/
offset(
delta: number, joinType?: JoinType, miterLimit?: number,
circularSegments?: number): CrossSection;
/**
* Remove vertices from the contours in this CrossSection that are less than
* the specified distance epsilon from an imaginary line that passes through
* its two adjacent vertices. Near duplicate vertices and collinear points
* will be removed at lower epsilons, with elimination of line segments
* becoming increasingly aggressive with larger epsilons.
*
* It is recommended to apply this function following Offset, in order to
* clean up any spurious tiny line segments introduced that do not improve
* quality in any meaningful way. This is particularly important if further
* offseting operations are to be performed, which would compound the issue.
*
* @param epsilon minimum distance vertices must diverge from the hypothetical
* outline without them in order to be included in the output (default
* 1e-6)
*/
simplify(epsilon?: number): CrossSection;
// Clipping Operations
/**
* Boolean union
*/
add(other: CrossSection|Polygons): CrossSection;
/**
* Boolean difference
*/
subtract(other: CrossSection|Polygons): CrossSection;
/**
* Boolean intersection
*/
intersect(other: CrossSection|Polygons): CrossSection;
/**
* Boolean union of the cross-sections a and b
*/
static union(a: CrossSection|Polygons, b: CrossSection|Polygons):
CrossSection;
/**
* Boolean difference of the cross-section b from the cross-section a
*/
static difference(a: CrossSection|Polygons, b: CrossSection|Polygons):
CrossSection;
/**
* Boolean intersection of the cross-sections a and b
*/
static intersection(a: CrossSection|Polygons, b: CrossSection|Polygons):
CrossSection;
/**
* Boolean union of a list of cross-sections
*/
static union(polygons: (CrossSection|Polygons)[]): CrossSection;
/**
* Boolean difference of the tail of a list of cross-sections from its head
*/
static difference(polygons: (CrossSection|Polygons)[]): CrossSection;
/**
* Boolean intersection of a list of cross-sections
*/
static intersection(polygons: (CrossSection|Polygons)[]): CrossSection;
// Convex Hulls
/**
* Compute the convex hull of the contours in this CrossSection.
*/
hull(): CrossSection;
/**
* Compute the convex hull of all points in a list of polygons/cross-sections.
*/
static hull(polygons: (CrossSection|Polygons)[]): CrossSection;
// Topological Operations
/**
* Construct a CrossSection from a vector of other Polygons (batch
* boolean union).
*/
static compose(polygons: (CrossSection|Polygons)[]): CrossSection;
/**
* This operation returns a vector of CrossSections that are topologically
* disconnected, each containing one outline contour with zero or more
* holes.
*/
decompose(): CrossSection[];
// Polygon Conversion
/**
* Create a 2d cross-section from a set of contours (complex polygons). A
* boolean union operation (with Positive filling rule by default) is
* performed to combine overlapping polygons and ensure the resulting
* CrossSection is free of intersections.
*
* @param contours A set of closed paths describing zero or more complex
* polygons.
* @param fillRule The filling rule used to interpret polygon sub-regions in
* contours.
*/
static ofPolygons(polygons: Polygons, fillRule?: FillRule): CrossSection;
/**
* Return the contours of this CrossSection as a list of simple polygons.
*/
toPolygons(): SimplePolygon[];
// Properties
/**
* Return the total area covered by complex polygons making up the
* CrossSection.
*/
area(): number;
/**
* Does the CrossSection (not) have any contours?
*/
isEmpty(): boolean;
/**
* The number of vertices in the CrossSection.
*/
numVert(): number;
/**
* The number of contours in the CrossSection.
*/
numContour(): number;
/**
* Returns the axis-aligned bounding rectangle of all the CrossSection's
* vertices.
*/
bounds(): Rect;
// Memory
/**
* Frees the WASM memory of this CrossSection, since these cannot be
* garbage-collected automatically.
*/
delete(): void;
}
export class Manifold {
/**
* Convert a Mesh into a Manifold, retaining its properties and merging only
* the positions according to the merge vectors. Will throw an error if the
* result is not an oriented 2-manifold. Will collapse degenerate triangles
* and unnecessary vertices.
*
* All fields are read, making this structure suitable for a lossless
* round-trip of data from getMesh(). For multi-material input, use
* reserveIDs() to set a unique originalID for each material, and sort the
* materials into triangle runs.
*/
constructor(mesh: Mesh);
// Shapes
/**
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
* and the rest at similarly symmetric points.
*/
static tetrahedron(): Manifold;
/**
* Constructs a unit cube (edge lengths all one), by default in the first
* octant, touching the origin.
*
* @param size The X, Y, and Z dimensions of the box.
* @param center Set to true to shift the center to the origin.
*/
static cube(size?: Vec3|number, center?: boolean): Manifold;
/**
* A convenience constructor for the common case of extruding a circle. Can
* also form cones if both radii are specified.
*
* @param height Z-extent
* @param radiusLow Radius of bottom circle. Must be positive.
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
* radiusLow.
* @param circularSegments How many line segments to use around the circle.
* Default is calculated by the static Defaults.
* @param center Set to true to shift the center to the origin. Default is
* origin at the bottom.
*/
static cylinder(
height: number, radiusLow: number, radiusHigh?: number,
circularSegments?: number, center?: boolean): Manifold;
/**
* Constructs a geodesic sphere of a given radius.
*
* @param radius Radius of the sphere. Must be positive.
* @param circularSegments Number of segments along its
* diameter. This number will always be rounded up to the nearest factor of
* four, as this sphere is constructed by refining an octahedron. This means
* there are a circle of vertices on all three of the axis planes. Default is
* calculated by the static Defaults.
*/
static sphere(radius: number, circularSegments?: number): Manifold;
// Extrusions from 2d shapes
/**
* Constructs a manifold from a set of polygons/cross-section by extruding
* them along the Z-axis.
*
* @param polygons A set of non-overlapping polygons to extrude.
* @param height Z-extent of extrusion.
* @param nDivisions Number of extra copies of the crossSection to insert into
* the shape vertically; especially useful in combination with twistDegrees to
* avoid interpolation artifacts. Default is none.
* @param twistDegrees Amount to twist the top crossSection relative to the
* bottom, interpolated linearly for the divisions in between.
* @param scaleTop Amount to scale the top (independently in X and Y). If the
* scale is {0, 0}, a pure cone is formed with only a single vertex at the
* top. Default {1, 1}.
* @param center If true, the extrusion is centered on the z-axis through the
* origin
* as opposed to resting on the XY plane as is default.
*/
static extrude(
polygons: CrossSection|Polygons, height: number, nDivisions?: number,
twistDegrees?: number, scaleTop?: Vec2|number,
center?: boolean): Manifold;
/**
* Constructs a manifold from a set of polygons/cross-section by revolving
* them around the Y-axis and then setting this as the Z-axis of the resulting
* manifold. If the polygons cross the Y-axis, only the part on the positive X
* side is used. Geometrically valid input will result in geometrically valid
* output.
*
* @param polygons A set of non-overlapping polygons to revolve.
* @param circularSegments Number of segments along its diameter. Default is
* calculated by the static Defaults.
* @param revolveDegrees Number of degrees to revolve. Default is 360 degrees.
*/
static revolve(
polygons: CrossSection|Polygons, circularSegments?: number,
revolveDegrees?: number): Manifold;
// Mesh Conversion
/**
* Convert a Mesh into a Manifold, retaining its properties and merging only
* the positions according to the merge vectors. Will throw an error if the
* result is not an oriented 2-manifold. Will collapse degenerate triangles
* and unnecessary vertices.
*
* All fields are read, making this structure suitable for a lossless
* round-trip of data from getMesh(). For multi-material input, use
* reserveIDs() to set a unique originalID for each material, and sort the
* materials into triangle runs.
*/
static ofMesh(mesh: Mesh): Manifold;
/**
* Constructs a smooth version of the input mesh by creating tangents; this
* method will throw if you have supplied tangents with your mesh already. The
* actual triangle resolution is unchanged; use the Refine() method to
* interpolate to a higher-resolution curve.
*
* By default, every edge is calculated for maximum smoothness (very much
* approximately), attempting to minimize the maximum mean Curvature
* magnitude. No higher-order derivatives are considered, as the interpolation
* is independent per triangle, only sharing constraints on their boundaries.
*
* @param mesh input Mesh.
* @param sharpenedEdges If desired, you can supply a vector of sharpened
* halfedges, which should in general be a small subset of all halfedges.
* Order of entries doesn't matter, as each one specifies the desired
* smoothness (between zero and one, with one the default for all unspecified
* halfedges) and the halfedge index (3 * triangle index + [0,1,2] where 0 is
* the edge between triVert 0 and 1, etc).
*
* At a smoothness value of zero, a sharp crease is made. The smoothness is
* interpolated along each edge, so the specified value should be thought of
* as an average. Where exactly two sharpened edges meet at a vertex, their
* tangents are rotated to be colinear so that the sharpened edge can be
* continuous. Vertices with only one sharpened edge are completely smooth,
* allowing sharpened edges to smoothly vanish at termination. A single vertex
* can be sharpened by sharping all edges that are incident on it, allowing
* cones to be formed.
*/
static smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
// Signed Distance Functions
/**
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
* cubic grid (two shifted cubic grids). This means if your function's
* interior exceeds the given bounds, you will see a kind of egg-crate shape
* closing off the manifold, which is due to the underlying grid.
*
* @param sdf The signed-distance function which returns the signed distance
* of
* a given point in R^3. Positive values are inside, negative outside.
* @param bounds An axis-aligned box that defines the extent of the grid.
* @param edgeLength Approximate maximum edge length of the triangles in the
* final result. This affects grid spacing, and hence has a strong effect on
* performance.
* @param level You can inset your Mesh by using a positive value, or outset
* it with a negative value.
*/
static levelSet(
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
level?: number): Manifold;
// Transformations
/**
* Transform this Manifold in space. Stored in column-major order. This
* operation can be chained. Transforms are combined and applied lazily.
*
* @param m The affine transformation matrix to apply to all the vertices. The
* last row is ignored.
*/
transform(m: Mat4): Manifold;
/**
* Move this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to add to every vertex.
*/
translate(v: Vec3): Manifold;
translate(x: number, y?: number, z?: number): Manifold;
/**
* Applies an Euler angle rotation to the manifold, first about the X axis,
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
* error, and eliminate it completely for any multiples of 90 degrees.
* Additionally, more efficient code paths are used to update the manifold
* when the transforms only rotate by multiples of 90 degrees. This operation
* can be chained. Transforms are combined and applied lazily.
*
* @param v [X, Y, Z] rotation in degrees.
*/
rotate(v: Vec3): Manifold;
rotate(x: number, y?: number, z?: number): Manifold;
/**
* Scale this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to multiply every vertex by per component.
*/
scale(v: Vec3|number): Manifold;
/**
* Mirror this Manifold over the plane described by the unit form of the given
* normal vector. If the length of the normal is zero, an empty Manifold is
* returned. This operation can be chained. Transforms are combined and
* applied lazily.
*
* @param normal The normal vector of the plane to be mirrored over
*/
mirror(v: Vec3): Manifold;
/**
* This function does not change the topology, but allows the vertices to be
* moved according to any arbitrary input function. It is easy to create a
* function that warps a geometrically valid object into one which overlaps,
* but that is not checked here, so it is up to the user to choose their
* function with discretion.
*
* @param warpFunc A function that modifies a given vertex position.
*/
warp(warpFunc: (vert: Vec3) => void): Manifold;
/**
* Smooths out the Manifold by filling in the halfedgeTangent vectors. The
* geometry will remain unchanged until Refine or RefineToLength is called to
* interpolate the surface. This version uses the supplied vertex normal
* properties to define the tangent vectors.
*
* @param normalIdx The first property channel of the normals. NumProp must be
* at least normalIdx + 3. Any vertex where multiple normals exist and don't
* agree will result in a sharp edge.
*/
smoothByNormals(normalIdx: number): Manifold;
/**
* Smooths out the Manifold by filling in the halfedgeTangent vectors. The
* geometry will remain unchanged until Refine or RefineToLength is called to
* interpolate the surface. This version uses the geometry of the triangles
* and pseudo-normals to define the tangent vectors.
*
* @param minSharpAngle degrees, default 60. Any edges with angles greater
* than this value will remain sharp. The rest will be smoothed to G1
* continuity, with the caveat that flat faces of three or more triangles will
* always remain flat. With a value of zero, the model is faceted, but in this
* case there is no point in smoothing.
*
* @param minSmoothness range: 0 - 1, default 0. The smoothness applied to
* sharp angles. The default gives a hard edge, while values > 0 will give a
* small fillet on these sharp edges. A value of 1 is equivalent to a
* minSharpAngle of 180 - all edges will be smooth.
*/
smoothOut(minSharpAngle?: number, minSmoothness?: number): Manifold;
/**
* Increase the density of the mesh by splitting every edge into n pieces. For
* instance, with n = 2, each triangle will be split into 4 triangles. These
* will all be coplanar (and will not be immediately collapsed) unless the
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
* constructor), in which case the new vertices will be moved to the
* interpolated surface according to their barycentric coordinates.
*
* @param n The number of pieces to split every edge into. Must be > 1.
*/
refine(n: number): Manifold;
/**
* Increase the density of the mesh by splitting each edge into pieces of
* roughly the input length. Interior verts are added to keep the rest of the
* triangulation edges also of roughly the same length. If halfedgeTangents
* are present (e.g. from the Smooth() constructor), the new vertices will be
* moved to the interpolated surface according to their barycentric
* coordinates.
*
* @param length The length that edges will be broken down to.
*/
refineToLength(length: number): Manifold;
/**
* Create a new copy of this manifold with updated vertex properties by
* supplying a function that takes the existing position and properties as
* input. You may specify any number of output properties, allowing creation
* and removal of channels. Note: undefined behavior will result if you read
* past the number of input properties or write past the number of output
* properties.
*
* @param numProp The new number of properties per vertex.
* @param propFunc A function that modifies the properties of a given vertex.
*/
setProperties(
numProp: number,
propFunc: (newProp: number[], position: Vec3, oldProp: number[]) => void):
Manifold;
/**
* Curvature is the inverse of the radius of curvature, and signed such that
* positive is convex and negative is concave. There are two orthogonal
* principal curvatures at any point on a manifold, with one maximum and the
* other minimum. Gaussian curvature is their product, while mean
* curvature is their sum. This approximates them for every vertex and assigns
* them as vertex properties on the given channels.
*
* @param gaussianIdx The property channel index in which to store the
* Gaussian curvature. An index < 0 will be ignored (stores nothing). The
* property set will be automatically expanded to include the channel
* index specified.
* @param meanIdx The property channel index in which to store the mean
* curvature. An index < 0 will be ignored (stores nothing). The property
* set will be automatically expanded to include the channel index
* specified.
*/
calculateCurvature(gaussianIdx: number, meanIdx: number): Manifold;
/**
* Fills in vertex properties for normal vectors, calculated from the mesh
* geometry. Flat faces composed of three or more triangles will remain flat.
*
* @param normalIdx The property channel in which to store the X
* values of the normals. The X, Y, and Z channels will be sequential. The
* property set will be automatically expanded to include up through normalIdx
* + 2.
*
* @param minSharpAngle Any edges with angles greater than this value will
* remain sharp, getting different normal vector properties on each side of
* the edge. By default, no edges are sharp and all normals are shared. With a
* value of zero, the model is faceted and all normals match their triangle
* normals, but in this case it would be better not to calculate normals at
* all.
*/
calculateNormals(normalIdx: number, minSharpAngle: number): Manifold;
// Boolean Operations
/**
* Boolean union
*/
add(other: Manifold): Manifold;
/**
* Boolean difference
*/
subtract(other: Manifold): Manifold;
/**
* Boolean intersection
*/
intersect(other: Manifold): Manifold;
/**
* Boolean union of the manifolds a and b
*/
static union(a: Manifold, b: Manifold): Manifold;
/**
* Boolean difference of the manifold b from the manifold a
*/
static difference(a: Manifold, b: Manifold): Manifold;
/**
* Boolean intersection of the manifolds a and b
*/
static intersection(a: Manifold, b: Manifold): Manifold;
/**
* Boolean union of a list of manifolds
*/
static union(manifolds: Manifold[]): Manifold;
/**
* Boolean difference of the tail of a list of manifolds from its head
*/
static difference(manifolds: Manifold[]): Manifold;
/**
* Boolean intersection of a list of manifolds
*/
static intersection(manifolds: Manifold[]): Manifold;
/**
* Split cuts this manifold in two using the cutter manifold. The first result
* is the intersection, second is the difference. This is more efficient than
* doing them separately.
*
* @param cutter
*/
split(cutter: Manifold): Manifold[];
/**
* Convenient version of Split() for a half-space.
*
* @param normal This vector is normal to the cutting plane and its length
* does
* not matter. The first result is in the direction of this vector, the second
* result is on the opposite side.
* @param originOffset The distance of the plane from the origin in the
* direction of the normal vector.
*/
splitByPlane(normal: Vec3, originOffset: number): Manifold[];
/**
* Removes everything behind the given half-space plane.
*
* @param normal This vector is normal to the cutting plane and its length
* does not matter. The result is in the direction of this vector from the
* plane.
* @param originOffset The distance of the plane from the origin in the
* direction of the normal vector.
*/
trimByPlane(normal: Vec3, originOffset: number): Manifold;
/**
* Returns the cross section of this object parallel to the X-Y plane at the
* specified height. Using a height equal to the bottom
* of the bounding box will return the bottom faces, while using a height
* equal to the top of the bounding box will return empty.
*
* @param height Z-level of slice.
*/
slice(height: number): CrossSection;
/**
* Returns a cross section representing the projected outline of this object
* onto the X-Y plane.
*/
project(): CrossSection;
// Convex Hulls
/**
* Compute the convex hull of all points in this Manifold.
*/
hull(): Manifold;
/**
* Compute the convex hull of all points contained within a set of Manifolds
* and point vectors.
*/
static hull(points: (Manifold|Vec3)[]): Manifold;
// Topological Operations
/**
* Constructs a new manifold from a list of other manifolds. This is a purely
* topological operation, so care should be taken to avoid creating
* overlapping results. It is the inverse operation of Decompose().
*
* @param manifolds A list of Manifolds to lazy-union together.
*/
static compose(manifolds: Manifold[]): Manifold;
/**
* This operation returns a vector of Manifolds that are topologically
* disconnected. If everything is connected, the vector is length one,
* containing a copy of the original. It is the inverse operation of
* Compose().
*/
decompose(): Manifold[];
// Property Access
/**
* Does the Manifold have any triangles?
*/
isEmpty(): boolean;
/**
* The number of vertices in the Manifold.
*/
numVert(): number;
/**
* The number of triangles in the Manifold.
*/
numTri(): number;
/**
* The number of edges in the Manifold.
*/
numEdge(): number;
/**
* The number of properties per vertex in the Manifold.
*/
numProp(): number;
/**
* The number of property vertices in the Manifold. This will always be >=
* numVert, as some physical vertices may be duplicated to account for
* different properties on different neighboring triangles.
*/
numPropVert(): number
/**
* Returns the axis-aligned bounding box of all the Manifold's vertices.
*/
boundingBox(): Box;
/**
* Returns the precision of this Manifold's vertices, which tracks the
* approximate rounding error over all the transforms and operations that have
* led to this state. Any triangles that are colinear within this precision
* are considered degenerate and removed. This is the value of &epsilon;
* defining
* [&epsilon;-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
*/
precision(): number;
/**
* The genus is a topological property of the manifold, representing the
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
* a single mesh, so it is best to call Decompose() first.
*/
genus(): number;
/**
* Returns the surface area and volume of the manifold. These properties are
* clamped to zero for a given face if they are within the Precision(). This
* means degenerate manifolds can by identified by testing these properties as
* == 0.
*/
getProperties(): Properties;
/*
* Returns the minimum gap between two manifolds. Returns a float between
* 0 and searchLength.
*/
minGap(other: Manifold, searchLength: number): number;
// Export
/**
* Returns a Mesh that is designed to easily push into a renderer, including
* all interleaved vertex properties that may have been input. It also
* includes relations to all the input meshes that form a part of this result
* and the transforms applied to each.
*
* @param normalIdx If the original Mesh inputs that formed this manifold had
* properties corresponding to normal vectors, you can specify which property
* channels these are (x, y, z), which will cause this output Mesh to
* automatically update these normals according to the applied transforms and
* front/back side. Each channel must be >= 3 and < numProp, and all original
* Meshes must use the same channels for their normals.
*/
getMesh(normalIdx?: Vec3): Mesh;
// ID Management
/**
* If you copy a manifold, but you want this new copy to have new properties
* (e.g. a different UV mapping), you can reset its IDs to a new original,
* meaning it will now be referenced by its descendants instead of the meshes
* it was built from, allowing you to differentiate the copies when applying
* your properties to the final result.
*
* This function also condenses all coplanar faces in the relation, and
* collapses those edges. If you want to have inconsistent properties across
* these faces, meaning you want to preserve some of these edges, you should
* instead call GetMesh(), calculate your properties and use these to
* construct a new manifold.
*/
asOriginal(): Manifold;
/**
* If this mesh is an original, this returns its ID that can be referenced
* by product manifolds. If this manifold is a product, this
* returns -1.
*/
originalID(): number;
/**
* Returns the first of n sequential new unique mesh IDs for marking sets of
* triangles that can be looked up after further operations. Assign to
* Mesh.runOriginalID vector.
*/
static reserveIDs(count: number): number;
// Memory
/**
* Frees the WASM memory of this Manifold, since these cannot be
* garbage-collected automatically.
*/
delete(): void;
}
export interface MeshOptions {
numProp: number;
vertProperties: Float32Array;
triVerts: Uint32Array;
mergeFromVert?: Uint32Array;
mergeToVert?: Uint32Array;
runIndex?: Uint32Array;
runOriginalID?: Uint32Array;
runTransform?: Float32Array;
faceID?: Uint32Array;
halfedgeTangent?: Float32Array;
}
export class Mesh {
constructor(options: MeshOptions);
numProp: number;
vertProperties: Float32Array;
triVerts: Uint32Array;
mergeFromVert: Uint32Array;
mergeToVert: Uint32Array;
runIndex: Uint32Array;
runOriginalID: Uint32Array;
runTransform: Float32Array;
faceID: Uint32Array;
halfedgeTangent: Float32Array;
get numTri(): number;
get numVert(): number;
get numRun(): number;
merge(): boolean;
verts(tri: number): SealedUint32Array<3>;
position(vert: number): SealedFloat32Array<3>;
extras(vert: number): Float32Array;
tangent(halfedge: number): SealedFloat32Array<4>;
transform(run: number): Mat4;
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export interface SealedUint32Array<N extends number> extends Uint32Array {
length: N;
}
export interface SealedFloat32Array<N extends number> extends Float32Array {
length: N;
}
export type Vec2 = [number, number];
export type Vec3 = [number, number, number];
// 3x3 matrix stored in column-major order
export type Mat3 = [
number,
number,
number,
number,
number,
number,
number,
number,
number,
];
// 4x4 matrix stored in column-major order
export type Mat4 = [
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
];
export type SimplePolygon = Vec2[];
export type Polygons = SimplePolygon|SimplePolygon[];
export type Rect = {
min: Vec2,
max: Vec2
};
export type Box = {
min: Vec3,
max: Vec3
};
export type Smoothness = {
halfedge: number,
smoothness: number
};
export type Properties = {
surfaceArea: number,
volume: number
};
export type FillRule = 'EvenOdd'|'NonZero'|'Positive'|'Negative'
export type JoinType = 'Square'|'Round'|'Miter'
+17 -433
View File
@@ -12,441 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
type Vec2 = [number, number];
type Vec3 = [number, number, number];
type Vec4 = [number, number, number, number];
type Matrix3x4 = [Vec3, Vec3, Vec3, Vec3];
type SimplePolygon = Vec2[];
type Polygons = SimplePolygon|SimplePolygon[];
type Box = {
min: Vec3,
max: Vec3
};
type Smoothness = {
halfedge: number,
smoothness: number
};
type Properties = {
surfaceArea: number,
volume: number
};
type BaryRef = {
meshID: number,
originalID: number,
tri: number,
vertBary: Vec3
};
type Curvature = {
maxMeanCurvature: number,
minMeanCurvature: number,
maxGaussianCurvature: number,
minGaussianCurvature: number,
vertMeanCurvature: number[],
vertGaussianCurvature: number[]
};
type MeshRelation = {
barycentric: Vec3[],
triBary: BaryRef[],
};
import * as T from './manifold-encapsulated-types';
export * from './manifold-global-types';
declare class Mesh {
vertPos: Float32Array;
triVerts: Uint32Array;
vertNormal?: Float32Array;
halfedgeTangent?: Float32Array;
get numTri(): number;
get numVert(): number;
verts(tri: number): Uint32Array<3>;
position(vert: number): Float32Array<3>;
normal(vert: number): Float32Array<3>;
tangent(halfedge: number): Float32Array<4>;
}
export type CrossSection = T.CrossSection;
export type Manifold = T.Manifold;
export type Mesh = T.Mesh;
declare class Manifold {
/**
* Create a Manifold from a Mesh object.
*/
constructor(mesh: Mesh);
/**
* Transform this Manifold in space. The first three columns form a 3x3 matrix
* transform and the last is a translation vector. This operation can be
* chained. Transforms are combined and applied lazily.
*
* @param m The affine transform matrix to apply to all the vertices.
*/
transform(m: Matrix3x4): Manifold;
/**
* Move this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to add to every vertex.
*/
translate(v: Vec3): Manifold;
/**
* Applies an Euler angle rotation to the manifold, first about the X axis,
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
* error, and eliminate it completely for any multiples of 90 degrees.
* Additionally, more efficient code paths are used to update the manifold
* when the transforms only rotate by multiples of 90 degrees. This operation
* can be chained. Transforms are combined and applied lazily.
*
* @param v [X, Y, Z] rotation in degrees.
*/
rotate(v: Vec3): Manifold;
/**
* Scale this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to multiply every vertex by per component.
*/
scale(v: Vec3|number): Manifold;
/**
* Boolean union
*/
add(other: Manifold): Manifold;
/**
* Boolean difference
*/
subtract(other: Manifold): Manifold;
/**
* Boolean intersection
*/
intersect(other: Manifold): Manifold;
/**
* Increase the density of the mesh by splitting every edge into n pieces. For
* instance, with n = 2, each triangle will be split into 4 triangles. These
* will all be coplanar (and will not be immediately collapsed) unless the
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
* constructor), in which case the new vertices will be moved to the
* interpolated surface according to their barycentric coordinates.
*
* @param n The number of pieces to split every edge into. Must be > 1.
*/
refine(n: number): Manifold;
/**
* This function does not change the topology, but allows the vertices to be
* moved according to any arbitrary input function. It is easy to create a
* function that warps a geometrically valid object into one which overlaps,
* but that is not checked here, so it is up to the user to choose their
* function with discretion.
*
* @param warpFunc A function that modifies a given vertex position.
*/
warp(warpFunc: (vert: Vec3) => void): Manifold;
/**
* This operation returns a vector of Manifolds that are topologically
* disconnected. If everything is connected, the vector is length one,
* containing a copy of the original. It is the inverse operation of
* Compose().
*/
decompose(): Manifold[];
/**
* Does the Manifold have any triangles?
*/
isEmpty(): boolean;
/**
* The number of vertices in the Manifold.
*/
numVert(): number;
/**
* The number of triangles in the Manifold.
*/
numTri(): number;
/**
* The number of edges in the Manifold.
*/
numEdge(): number;
/**
* Returns the axis-aligned bounding box of all the Manifold's vertices.
*/
boundingBox(): Box;
/**
* Returns the precision of this Manifold's vertices, which tracks the
* approximate rounding error over all the transforms and operations that have
* led to this state. Any triangles that are colinear within this precision
* are considered degenerate and removed. This is the value of &epsilon;
* defining
* [&epsilon;-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
*/
precision(): number;
/**
* The genus is a topological property of the manifold, representing the
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
* a single mesh, so it is best to call Decompose() first.
*/
genus(): number;
/**
* Returns the surface area and volume of the manifold. These properties are
* clamped to zero for a given face if they are within the Precision(). This
* means degenerate manifolds can by identified by testing these properties as
* == 0.
*/
getProperties(): Properties;
/**
* Curvature is the inverse of the radius of curvature, and signed such that
* positive is convex and negative is concave. There are two orthogonal
* principal curvatures at any point on a manifold, with one maximum and the
* other minimum. Gaussian curvature is their product, while mean
* curvature is their sum. This approximates them for every vertex (returned
* as vectors in the structure) and also returns their minimum and maximum
* values.
*/
getCurvature(): Curvature;
/**
* This returns a Mesh of simple vectors of vertices and triangles suitable
* for saving or other operations outside of the context of this library.
*/
getMesh(): Mesh;
/**
* Gets the relationship to the previous meshes, for the purpose of assigning
* properties like texture coordinates. The triBary vector is the same length
* as Mesh.triVerts: BaryRef.originalID indicates the source mesh and
* BaryRef.tri is that mesh's triangle index to which these barycentric
* coordinates refer. BaryRef.vertBary gives an index for each vertex into the
* barycentric vector if that index is >= 0, indicating it is a new vertex. If
* the index is < 0, this indicates it is an original vertex, the index + 3
* vert of the referenced triangle.
*
* BaryRef.meshID is a unique ID to the particular instance of a given mesh.
* For instance, if you want to convert the triangle mesh to a polygon mesh,
* all the triangles from a given face will have the same .meshID and .tri
* values.
*/
getMeshRelation(): MeshRelation;
/**
* If you copy a manifold, but you want this new copy to have new properties
* (e.g. a different UV mapping), you can reset its meshIDs to a new original,
* meaning it will now be referenced by its descendants instead of the meshes
* it was built from, allowing you to differentiate the copies when applying
* your properties to the final result.
*
* This function also condenses all coplanar faces in the relation, and
* collapses those edges. If you want to have inconsistent properties across
* these faces, meaning you want to preserve some of these edges, you should
* instead call GetMesh(), calculate your properties and use these to
* construct a new manifold.
*/
asOriginal(): Manifold;
/**
* If this mesh is an original, this returns its meshID that can be referenced
* by product manifolds' MeshRelation. If this manifold is a product, this
* returns -1.
*/
originalID(): number;
}
/**
* Constructs a unit cube (edge lengths all one), by default in the first
* octant, touching the origin.
*
* @param size The X, Y, and Z dimensions of the box.
* @param center Set to true to shift the center to the origin.
*/
declare function cube(size?: Vec3|number, center?: boolean): Manifold;
/**
* A convenience constructor for the common case of extruding a circle. Can also
* form cones if both radii are specified.
*
* @param height Z-extent
* @param radiusLow Radius of bottom circle. Must be positive.
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
* radiusLow.
* @param circularSegments How many line segments to use around the circle.
* Default is calculated by the static Defaults.
* @param center Set to true to shift the center to the origin. Default is
* origin at the bottom.
*/
declare function cylinder(
height: number, radiusLow: number, radiusHigh?: number,
circularSegments?: number, center?: boolean): Manifold;
/**
* Constructs a geodesic sphere of a given radius.
*
* @param radius Radius of the sphere. Must be positive.
* @param circularSegments Number of segments along its
* diameter. This number will always be rounded up to the nearest factor of
* four, as this sphere is constructed by refining an octahedron. This means
* there are a circle of vertices on all three of the axis planes. Default is
* calculated by the static Defaults.
*/
declare function sphere(radius: number, circularSegments?: number): Manifold;
/**
* Constructs a smooth version of the input mesh by creating tangents; this
* method will throw if you have supplied tangents with your mesh already. The
* actual triangle resolution is unchanged; use the Refine() method to
* interpolate to a higher-resolution curve.
*
* By default, every edge is calculated for maximum smoothness (very much
* approximately), attempting to minimize the maximum mean Curvature magnitude.
* No higher-order derivatives are considered, as the interpolation is
* independent per triangle, only sharing constraints on their boundaries.
*
* @param mesh input Mesh.
* @param sharpenedEdges If desired, you can supply a vector of sharpened
* halfedges, which should in general be a small subset of all halfedges. Order
* of entries doesn't matter, as each one specifies the desired smoothness
* (between zero and one, with one the default for all unspecified halfedges)
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
* between triVert 0 and 1, etc).
*
* At a smoothness value of zero, a sharp crease is made. The smoothness is
* interpolated along each edge, so the specified value should be thought of as
* an average. Where exactly two sharpened edges meet at a vertex, their
* tangents are rotated to be colinear so that the sharpened edge can be
* continuous. Vertices with only one sharpened edge are completely smooth,
* allowing sharpened edges to smoothly vanish at termination. A single vertex
* can be sharpened by sharping all edges that are incident on it, allowing
* cones to be formed.
*/
declare function smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
/**
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
* and the rest at similarly symmetric points.
*/
declare function tetrahedron(): Manifold;
/**
* Constructs a manifold from a set of polygons by extruding them along the
* Z-axis.
*
* @param crossSection A set of non-overlapping polygons to extrude.
* @param height Z-extent of extrusion.
* @param nDivisions Number of extra copies of the crossSection to insert into
* the shape vertically; especially useful in combination with twistDegrees to
* avoid interpolation artifacts. Default is none.
* @param twistDegrees Amount to twist the top crossSection relative to the
* bottom, interpolated linearly for the divisions in between.
* @param scaleTop Amount to scale the top (independently in X and Y). If the
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
* Default {1, 1}.
*/
declare function extrude(
crossSection: Polygons, height: number, nDivisions?: number,
twistDegrees?: number, scaleTop?: Vec2): Manifold;
/**
* Constructs a manifold from a set of polygons by revolving this cross-section
* around its Y-axis and then setting this as the Z-axis of the resulting
* manifold. If the polygons cross the Y-axis, only the part on the positive X
* side is used. Geometrically valid input will result in geometrically valid
* output.
*
* @param crossSection A set of non-overlapping polygons to revolve.
* @param circularSegments Number of segments along its diameter. Default is
* calculated by the static Defaults.
*/
declare function revolve(
crossSection: Polygons, circularSegments?: number): Manifold;
declare function union(a: Manifold, b: Manifold): Manifold;
declare function difference(a: Manifold, b: Manifold): Manifold;
declare function intersection(a: Manifold, b: Manifold): Manifold;
declare function union(manifolds: Manifold[]): Manifold;
declare function difference(manifolds: Manifold[]): Manifold;
declare function intersection(manifolds: Manifold[]): Manifold;
/**
* Constructs a new manifold from a list of other manifolds. This is a purely
* topological operation, so care should be taken to avoid creating
* overlapping results. It is the inverse operation of Decompose().
*
* @param manifolds A list of Manifolds to lazy-union together.
*/
declare function compose(manifolds: Manifold[]): Manifold;
/**
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
* cubic grid (two shifted cubic grids). This means if your function's interior
* exceeds the given bounds, you will see a kind of egg-crate shape closing off
* the manifold, which is due to the underlying grid.
*
* @param sdf The signed-distance function which returns the signed distance of
* a given point in R^3. Positive values are inside, negative outside.
* @param bounds An axis-aligned box that defines the extent of the grid.
* @param edgeLength Approximate maximum edge length of the triangles in the
* final result. This affects grid spacing, and hence has a strong effect on
* performance.
* @param level You can inset your Mesh by using a positive value, or outset
* it with a negative value.
*/
declare function levelSet(
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
level?: number): Manifold;
/**
* @name Defaults
* These static properties control how circular shapes are quantized by
* default on construction. If circularSegments is specified, it takes
* precedence. If it is zero, then instead the minimum is used of the segments
* calculated based on edge length and angle, rounded up to the nearest
* multiple of four. To get numbers not divisible by four, circularSegments
* must be specified.
*/
///@{
declare function setMinCircularAngle(angle: number): void;
declare function setMinCircularEdgeLength(length: number): void;
declare function setCircularSegments(segments: number): void;
declare function getCircularSegments(radius: number): number;
///@}
/**
* Create a Manifold from a serialized Mesh object (MeshVec). Unlike the
* constructor, this method does not dispose the Mesh after using it.
*
* @param meshVec The serialized Mesh object to convert into a Manifold.
*/
declare function ManifoldFromMeshVec(meshVec: MeshVec): Manifold;
declare interface ManifoldStatic {
cube: typeof cube;
cylinder: typeof cylinder;
sphere: typeof sphere;
smooth: typeof smooth;
tetrahedron: typeof tetrahedron;
extrude: typeof extrude;
revolve: typeof revolve;
union: typeof union;
difference: typeof difference;
intersection: typeof intersection;
compose: typeof compose;
levelSet: typeof levelSet;
setMinCircularAngle: typeof setMinCircularAngle;
setMinCircularEdgeLength: typeof setMinCircularEdgeLength;
setCircularSegments: typeof setCircularSegments;
getCircularSegments: typeof getCircularSegments;
ManifoldFromMeshVec: typeof ManifoldFromMeshVec;
Manifold: typeof Manifold;
export interface ManifoldToplevel {
CrossSection: typeof T.CrossSection;
Manifold: typeof T.Manifold;
Mesh: typeof T.Mesh;
triangulate: typeof T.triangulate;
setMinCircularAngle: typeof T.setMinCircularAngle;
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
setCircularSegments: typeof T.setCircularSegments;
getCircularSegments: typeof T.getCircularSegments;
resetToCircularDefaults: typeof T.resetToCircularDefaults;
setup: () => void;
}
declare function Module(): Promise<ManifoldStatic>;
export default function Module(config?: {locateFile: () => string}):
Promise<ManifoldToplevel>;
+2 -1694
View File
File diff suppressed because it is too large Load Diff
+5 -12
View File
@@ -1,17 +1,18 @@
{
"name": "manifold-3d",
"version": "1.0.0",
"version": "2.5.1",
"description": "Geometry library for topological robustness",
"main": "manifold.js",
"files": [
"manifold.js",
"manifold.wasm",
"manifold.d.ts",
"examples/examples.js",
"test/test.js"
"manifold-encapsulated-types.d.ts",
"manifold-global-types.d.ts"
],
"typings": "manifold.d.ts",
"types": "manifold.d.ts",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/elalish/manifold.git"
@@ -34,13 +35,5 @@
"bugs": {
"url": "https://github.com/elalish/manifold/issues"
},
"homepage": "https://github.com/elalish/manifold#readme",
"scripts": {
"test": "mocha -u tdd -t 30000"
},
"devDependencies": {
"chai": "*",
"mocha": "*",
"three": "^0.146.0"
}
"homepage": "https://github.com/elalish/manifold#readme"
}
-153
View File
@@ -1,153 +0,0 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const expect = require('chai').expect;
const wasm = require('../manifold.js');
const examples = require('../examples/examples');
const THREE = require('three');
// manifold member functions that returns a new manifold
const memberFunctions = [
'add', 'subtract', 'intersect', 'refine', 'transform', 'translate', 'rotate',
'scale', 'asOriginal', 'decompose'
];
// top level functions that constructs a new manifold
const constructors = [
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'union',
'difference', 'intersection', 'compose', 'levelSet', 'smooth'
];
const utils = [
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
'getCircularSegments', 'Mesh'
];
const exposedFunctions = constructors.concat(utils);
wasm().then(function(Module) {
Module.setup();
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
let manifoldRegistry = [];
for (const name of memberFunctions) {
const originalFn = Module.Manifold.prototype[name];
Module.Manifold.prototype['_' + name] = originalFn;
Module.Manifold.prototype[name] = function(...args) {
const result = this['_' + name](...args);
manifoldRegistry.push(result);
return result;
}
}
for (const name of constructors) {
const originalFn = Module[name];
Module[name] = function(...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
}
}
Module.cleanup =
function() {
for (const obj of manifoldRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
manifoldRegistry = [];
}
function runExample(name) {
try {
const content = examples.functionBodies.get(name) + '\nreturn result;\n';
;
const f = new Function(...exposedFunctions, 'THREE', content);
const manifold = f(...exposedFunctions.map(name => Module[name]), THREE);
const prop = manifold.getProperties();
const genus = manifold.genus();
return {...prop, genus};
} finally {
Module.cleanup();
}
}
suite('Examples', () => {
test('Intro', () => {
const result = runExample('Intro');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
});
test('Tetrahedron Puzzle', () => {
const result = runExample('Tetrahedron Puzzle');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
});
test('Rounded Frame', () => {
const result = runExample('Rounded Frame');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(353706, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
});
test('Heart', () => {
const result = runExample('Heart');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(282743, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
});
test('Scallop', () => {
const result = runExample('Scallop');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
});
test('Torus Knot', () => {
const result = runExample('Torus Knot');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(20960, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(11202, 1, 'Surface Area');
});
test('Menger Sponge', () => {
const result = runExample('Menger Sponge');
expect(result.genus).to.equal(1409, 'Genus');
expect(result.volume).to.be.closeTo(406457, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(247590, 1, 'Surface Area');
});
test('Stretchy Bracelet', () => {
const result = runExample('Stretchy Bracelet');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
});
test('Gyroid Module', () => {
const result = runExample('Gyroid Module');
expect(result.genus).to.equal(15, 'Genus');
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
});
});
});

Some files were not shown because too many files have changed in this diff Show More