├── .clang-format ├── .codecov.yml ├── .gersemirc ├── .github ├── dependabot.yml └── workflows │ ├── build_wheels.yml │ ├── check_format.yml │ ├── deploy.yml │ ├── manifold.yml │ ├── publish_npm.yml │ └── release_file.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── AUTHORS ├── CMakeLists.txt ├── CONTRIBUTING.md ├── Doxyfile ├── LICENSE ├── README.md ├── RELEASE_CHECKLIST.md ├── bindings ├── CMakeLists.txt ├── c │ ├── CMakeLists.txt │ ├── box.cpp │ ├── conv.cpp │ ├── conv.h │ ├── cross.cpp │ ├── include │ │ └── manifold │ │ │ ├── manifoldc.h │ │ │ └── types.h │ ├── manifoldc.cpp │ ├── meshIOc.cpp │ └── rect.cpp ├── python │ ├── CMakeLists.txt │ ├── README.md │ ├── docstring_override.txt │ ├── examples │ │ ├── all_apis.py │ │ ├── bricks.py │ │ ├── cube_with_dents.py │ │ ├── extrude.py │ │ ├── gyroid_module.py │ │ ├── maze.py │ │ ├── run_all.py │ │ ├── scallop.py │ │ ├── split_cube.py │ │ ├── sponge.py │ │ ├── test_torus_knot.py │ │ └── union_failure.py │ ├── gen_docs.py │ ├── manifold3d.cpp │ └── stub_pattern.txt └── wasm │ ├── .gitignore │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── bindings.cpp │ ├── bindings.js │ ├── documents │ └── Get Started.md │ ├── examples │ ├── .npmignore │ ├── 3mf-export.d.ts │ ├── README.md │ ├── bindings.test.ts │ ├── editor.css │ ├── editor.js │ ├── gltf-io.ts │ ├── index.html │ ├── make-manifold.html │ ├── make-manifold.ts │ ├── manifold-gltf.ts │ ├── model-viewer-script.ts │ ├── model-viewer.html │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── editor.d.ts │ │ ├── examples.js │ │ ├── icons │ │ │ ├── close.png │ │ │ ├── manifoldCAD.png │ │ │ ├── manifoldCADonly.png │ │ │ ├── mengerSponge192.png │ │ │ ├── mengerSponge512.png │ │ │ ├── mengerSponge64.png │ │ │ ├── pause.png │ │ │ ├── pencil.png │ │ │ ├── play.png │ │ │ ├── redo.png │ │ │ ├── share.png │ │ │ ├── star.png │ │ │ ├── trash.png │ │ │ └── undo.png │ │ ├── manifest.json │ │ ├── models │ │ │ ├── moon.glb │ │ │ └── space.glb │ │ └── service-worker.js │ ├── simple-dropzone.d.ts │ ├── three.html │ ├── three.ts │ ├── tsconfig.json │ ├── vite-fixup-plugin.js │ ├── vite.config.js │ ├── worker-wrapper.ts │ ├── worker.test.ts │ └── worker.ts │ ├── helpers.cpp │ ├── manifold-encapsulated-types.d.ts │ ├── manifold-global-types.d.ts │ ├── manifold.d.ts │ ├── package-lock.json │ ├── package.json │ └── typedoc.json ├── cmake ├── configHelper.cmake ├── info.cmake ├── manifold.pc.in ├── manifoldConfig.cmake.in ├── manifoldDeps.cmake └── version.h.in ├── docs ├── ParallelBVH.pdf ├── RobustBoolean.pdf ├── TriangleInterpolation.pdf └── Triangulation.pdf ├── extras ├── CMakeLists.txt ├── convert_file.cpp ├── ember_tests │ ├── README.md │ ├── analyze_ember_tests.py │ ├── bug-case.json │ ├── do_ember_tests.py │ ├── m3max_benchmark.csv │ ├── man_bench.cpp │ └── testfiles │ │ └── ember-benchmark-cases.json ├── large_scene_test.cpp ├── minimize_testcase.cpp ├── perf_test.cpp └── stl_test.cpp ├── flake.lock ├── flake.nix ├── include └── manifold │ ├── common.h │ ├── cross_section.h │ ├── linalg.h │ ├── manifold.h │ ├── meshIO.h │ ├── optional_assert.h │ ├── polygon.h │ └── vec_view.h ├── pyproject.toml ├── pytest.ini ├── samples ├── CMakeLists.txt ├── include │ └── samples.h ├── models │ ├── apollo.png │ ├── apollo.webp │ ├── apolloPan.webp │ ├── apollo_exterior-150k-4096.glb │ ├── favicon.png │ ├── mengerSponge192.png │ ├── mengerSponge3.glb │ ├── mengerSponge3.png │ ├── mengerSponge3.webp │ ├── mengerSponge4.glb │ ├── mengerSponge512.png │ ├── mengerSponge64.png │ ├── mengerSpongeSquare.png │ ├── perf.glb │ ├── perf.png │ ├── perf.webp │ ├── rounding.glb │ ├── rounding.webp │ ├── scallop.glb │ ├── scallop.png │ ├── scallop.webp │ ├── scallopFacets.glb │ └── scallopFacets.webp └── src │ ├── bracelet.cpp │ ├── condensed_matter.cpp │ ├── gyroid_module.cpp │ ├── knot.cpp │ ├── menger_sponge.cpp │ ├── rounded_frame.cpp │ ├── scallop.cpp │ └── tet_puzzle.cpp ├── scripts ├── format.sh ├── gersemi-check.sh ├── minimizer.sh ├── test-cmake-subdir.sh ├── test-cmake.sh └── test-pkgconfig.sh ├── src ├── CMakeLists.txt ├── boolean3.cpp ├── boolean3.h ├── boolean_result.cpp ├── collider.h ├── constructors.cpp ├── cross_section │ └── cross_section.cpp ├── csg_tree.cpp ├── csg_tree.h ├── disjoint_sets.h ├── edge_op.cpp ├── face_op.cpp ├── hashtable.h ├── impl.cpp ├── impl.h ├── iters.h ├── manifold.cpp ├── meshIO │ └── meshIO.cpp ├── mesh_fixes.h ├── parallel.h ├── polygon.cpp ├── properties.cpp ├── quickhull.cpp ├── quickhull.h ├── sdf.cpp ├── shared.h ├── smoothing.cpp ├── sort.cpp ├── subdivision.cpp ├── svd.h ├── tree2d.cpp ├── tree2d.h ├── tri_dist.h ├── utils.h └── vec.h └── test ├── CMakeLists.txt ├── boolean_complex_test.cpp ├── boolean_test.cpp ├── cross_section_test.cpp ├── hull_test.cpp ├── manifold_fuzz.cpp ├── manifold_test.cpp ├── manifoldc_test.cpp ├── models ├── Cray_left.glb ├── Cray_right.glb ├── Generic_Twin_7081.1.t0_left.glb ├── Generic_Twin_7081.1.t0_right.glb ├── Generic_Twin_7863.1.t0_left.glb ├── Generic_Twin_7863.1.t0_right.glb ├── Generic_Twin_91.1.t0.glb ├── Havocglass8_left.glb ├── Havocglass8_right.glb ├── Offset1.obj ├── Offset2.obj ├── Offset3.obj ├── Offset4.obj ├── hull-body.glb ├── hull-mask.glb ├── self_intersectA.glb └── self_intersectB.glb ├── polygon_fuzz.cpp ├── polygon_test.cpp ├── polygons ├── polygon_corpus.txt ├── sponge.txt ├── zebra.txt └── zebra3.txt ├── properties_test.cpp ├── samples_test.cpp ├── sdf_test.cpp ├── smooth_test.cpp ├── test.h └── test_main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | PointerAlignment: Left 3 | ReferenceAlignment: Left 4 | DerivePointerAlignment: false -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | patch: 7 | default: 8 | informational: true -------------------------------------------------------------------------------- /.gersemirc: -------------------------------------------------------------------------------- 1 | line_length: 80 2 | indent: 2 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/build_wheels.yml: -------------------------------------------------------------------------------- 1 | name: build_wheels 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build_wheels: 10 | name: Build wheels on ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | # macos-13: x86-64 15 | # macos-14: arm64 16 | os: [ubuntu-22.04, ubuntu-22.04-arm, windows-2019, macos-14, macos-13] 17 | steps: 18 | - run: | 19 | git config --global submodule.fetchJobs 8 20 | git config --global core.longpaths true 21 | - if: matrix.os == 'macos-14' 22 | run: echo "CIBW_ARCHS=arm64 universal2" >> "$GITHUB_ENV" 23 | - uses: actions/checkout@v4 24 | - name: Build wheels 25 | uses: pypa/cibuildwheel@v2.23.0 26 | - uses: actions/upload-artifact@v4 27 | with: 28 | name: python-${{ matrix.os }} 29 | path: ./wheelhouse/*.whl 30 | 31 | make_sdist: 32 | name: Make SDist 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - name: Build SDist 37 | run: pipx run build --sdist 38 | - name: Confirm SDist can be built 39 | run: | 40 | # FIXME: why doesn't scikit_build_core automatically download ninja? 41 | sudo apt-get update 42 | sudo apt-get install ninja-build 43 | pip wheel dist/*.tar.gz 44 | - uses: actions/upload-artifact@v4 45 | with: 46 | name: python-sdist 47 | path: dist/*.tar.gz 48 | 49 | pypi-publish: 50 | name: Upload release to PyPI 51 | needs: [build_wheels, make_sdist] 52 | runs-on: ubuntu-latest 53 | environment: 54 | name: pypi 55 | url: https://pypi.org/p/manifold3d 56 | permissions: 57 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 58 | if: ${{ github.event_name == 'release' }} 59 | steps: 60 | - uses: actions/download-artifact@v4 61 | with: 62 | pattern: python-* 63 | merge-multiple: true 64 | path: dist 65 | - name: Publish package distributions to PyPI 66 | uses: pypa/gh-action-pypi-publish@release/v1 67 | -------------------------------------------------------------------------------- /.github/workflows/check_format.yml: -------------------------------------------------------------------------------- 1 | name: Check code formatting 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | check_format: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: DoozyX/clang-format-lint-action@v0.20 16 | with: 17 | source: '.' 18 | exclude: '*/third_party' 19 | extensions: 'h,cpp,js,ts,html' 20 | clangFormatVersion: 20 21 | - uses: actions/setup-python@v5 22 | with: 23 | python-version: '3.12' 24 | cache: 'pip' 25 | - uses: psf/black@stable 26 | with: 27 | options: "--check --verbose" 28 | src: "./bindings/python/examples" 29 | - name: "gersemi cmake check" 30 | run: | 31 | pip3 install gersemi 32 | ./scripts/gersemi-check.sh 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy documentation 2 | 3 | on: 4 | workflow_run: 5 | workflows: [CI] 6 | types: 7 | - completed 8 | branches: [master] 9 | 10 | jobs: 11 | deploy_github_pages: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Doxygen Action 18 | uses: mattnotmitt/doxygen-action@edge 19 | 20 | - name: Move deployables 21 | run: | 22 | mkdir public 23 | mv ./samples ./public 24 | mv ./docs ./public 25 | 26 | - name: Download built examples 27 | uses: dawidd6/action-download-artifact@v6 28 | with: 29 | workflow: manifold.yml 30 | workflow_conclusion: completed 31 | # specific to the triggering workflow 32 | run_id: ${{github.event.workflow_run.id}} 33 | # do not download from old run 34 | check_artifacts: true 35 | name: wasm 36 | path: ./public 37 | 38 | - name: Deploy Javascript Docs to Github Pages 39 | run: | 40 | cd bindings/wasm 41 | npm install 42 | npm run docs 43 | mv ./docs ../../public/jsdocs 44 | 45 | - name: Deploy to Github Pages 46 | uses: peaceiris/actions-gh-pages@v3 47 | with: 48 | github_token: ${{ secrets.GITHUB_TOKEN }} 49 | publish_branch: gh-pages 50 | force_orphan: true 51 | cname: manifoldcad.org 52 | publish_dir: ./public 53 | -------------------------------------------------------------------------------- /.github/workflows/publish_npm.yml: -------------------------------------------------------------------------------- 1 | name: publish_npm 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish_npm: 10 | runs-on: ubuntu-22.04 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup Node 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20 19 | registry-url: 'https://registry.npmjs.org' 20 | 21 | - name: Download built examples 22 | uses: dawidd6/action-download-artifact@v6 23 | with: 24 | workflow: manifold.yml 25 | workflow_conclusion: completed 26 | branch: master 27 | check_artifacts: true 28 | name: wasm 29 | path: ./bindings/wasm/ 30 | 31 | - name: Publish to npm 32 | run: | 33 | cd ./bindings/wasm/ 34 | npm publish 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 37 | -------------------------------------------------------------------------------- /.github/workflows/release_file.yml: -------------------------------------------------------------------------------- 1 | name: Release File 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | upload_archive: 10 | name: Upload archive 11 | runs-on: ubuntu-latest 12 | steps: 13 | - run: | 14 | ref=${{ github.ref_name }} 15 | echo "release_name=manifold-${ref#v}" >> $GITHUB_ENV 16 | - uses: actions/checkout@v4 17 | with: 18 | path: ${{ env.release_name }} 19 | submodules: recursive 20 | - name: Build archive 21 | run: > 22 | tar --exclude=".git*" -cz ${{ env.release_name }} -f ${{ env.release_name }}.tar.gz 23 | - name: Log checksum 24 | run: > 25 | sha256sum ${{ env.release_name }}.tar.gz 26 | - name: Add file to release 27 | run: | 28 | cd ${{ env.release_name }} || exit 1 29 | gh release upload ${{ github.ref_name }} ../${{ env.release_name }}.tar.gz 30 | env: 31 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/osx,c++,cuda,linux,cmake 2 | # Edit at https://www.gitignore.io/?templates=osx,c++,cuda,linux,cmake 3 | 4 | ### C++ ### 5 | # Prerequisites 6 | *.d 7 | 8 | # Compiled Object files 9 | *.slo 10 | *.lo 11 | *.o 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | *.smod 25 | 26 | # Compiled Static libraries 27 | *.lai 28 | *.la 29 | *.a 30 | *.lib 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | 37 | ### CMake ### 38 | CMakeLists.txt.user 39 | CMakeCache.txt 40 | CMakeFiles 41 | CMakeScripts 42 | Testing 43 | Makefile 44 | cmake_install.cmake 45 | install_manifest.txt 46 | compile_commands.json 47 | CTestTestfile.cmake 48 | _deps 49 | 50 | ### CMake Patch ### 51 | # External projects 52 | *-prefix/ 53 | 54 | ### CUDA ### 55 | *.i 56 | *.ii 57 | *.gpu 58 | *.ptx 59 | *.cubin 60 | *.fatbin 61 | 62 | ### Linux ### 63 | *~ 64 | 65 | # temporary files which can be created if a process still has a handle open of a deleted file 66 | .fuse_hidden* 67 | 68 | # KDE directory preferences 69 | .directory 70 | 71 | # Linux trash folder which might appear on any partition or disk 72 | .Trash-* 73 | 74 | # .nfs files are created when an open file is removed but is still being accessed 75 | .nfs* 76 | 77 | ### OSX ### 78 | # General 79 | .DS_Store 80 | .AppleDouble 81 | .LSOverride 82 | 83 | # Icon must end with two \r 84 | Icon 85 | 86 | # Thumbnails 87 | ._* 88 | 89 | # Files that might appear in the root of a volume 90 | .DocumentRevisions-V100 91 | .fseventsd 92 | .Spotlight-V100 93 | .TemporaryItems 94 | .Trashes 95 | .VolumeIcon.icns 96 | .com.apple.timemachine.donotpresent 97 | 98 | # Directories potentially created on remote AFP share 99 | .AppleDB 100 | .AppleDesktop 101 | Network Trash Folder 102 | Temporary Items 103 | .apdisk 104 | 105 | # End of https://www.gitignore.io/api/osx,c++,cuda,linux,cmake 106 | 107 | build 108 | buildWASM 109 | docs/html 110 | node_modules/ 111 | bindings/wasm/examples/built 112 | bindings/wasm/examples/dist 113 | bindings/wasm/examples/public/manifold* 114 | bindings/wasm/docs 115 | __pycache__ 116 | .vscode/c_cpp_properties.json 117 | *.sublime-project 118 | *.sublime-workspace 119 | .cache 120 | CMakeUserPresets.json 121 | CMakeSettings.json 122 | 123 | # PyCharm 124 | .idea 125 | 126 | # Temporary files 127 | *~ 128 | \#*\# 129 | .\#* 130 | [._]*.s[a-v][a-z] 131 | [._]*.sw[a-p] 132 | [._]s[a-v][a-z] 133 | [._]sw[a-p] 134 | 135 | # Benchmark stl files 136 | extras/ember_tests/testfiles/raw_meshes 137 | 138 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "manifold test", 9 | "type": "lldb-dap", 10 | "MIMode": "lldb", 11 | "request": "launch", 12 | "program": "${workspaceFolder}/build/test/manifold_test", 13 | "args": [ 14 | "--gtest_catch_exceptions=0", 15 | "--gtest_filter=Manifold.Simplify" 16 | ], 17 | "stopAtEntry": false, 18 | "cwd": "${workspaceFolder}/build/test", 19 | "environment": [ 20 | { 21 | "name": "MALLOC_CHECK_", 22 | "value": "2" 23 | } 24 | ], 25 | "externalConsole": false 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of Manifold's significant contributors. 2 | # 3 | # This does not necessarily list everyone who has contributed code, 4 | # especially since many employees of one corporation may be contributing. 5 | # To see the full list of contributors, see the revision history in 6 | # source control. 7 | Emmett Lalish 8 | Chun Kit LAM 9 | Geoff deRosenroll 10 | Google LLC 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement (CLA). You (or your employer) retain the copyright to your 10 | contribution; this simply gives us permission to use and redistribute your 11 | contributions as part of the project. Head over to 12 | to see your current agreements on file or 13 | to sign a new one. 14 | 15 | You generally only need to submit a CLA once, so if you've already submitted one 16 | (even if it was for a different project), you probably don't need to do it 17 | again. 18 | 19 | ## Code Reviews 20 | 21 | All submissions, including submissions by project members, require review. We 22 | use GitHub pull requests for this purpose. Consult 23 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 24 | information on using pull requests. 25 | 26 | ## Community Guidelines 27 | 28 | This project follows 29 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). -------------------------------------------------------------------------------- /RELEASE_CHECKLIST.md: -------------------------------------------------------------------------------- 1 | 1. Go through Github issues to verify bugs have been fixed and closed. 2 | 1. Verify [ManifoldCAD.org](https://manifoldcad.org) - check a few examples, run them, download a GLB and a 3MF. 3 | 1. Verify our three.js [example](https://manifoldcad.org/three) is functional. 4 | 1. Verify our model-viewer [example](https://manifoldcad.org/model-viewer) is functional - select Union and Intersection. 5 | 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. 6 | 1. Make a new branch called the version, e.g. v2.3.0. 7 | 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. 8 | 1. Also update CMakeLists.txt version by searching for "set(MANIFOLD_VERSION_". 9 | 1. Commit, push, open a PR, verify tests pass, manually trigger PyPI CI, merge. 10 | 1. On Github, draft a new release, make a new tag with the version number, add release notes, and publish. 11 | 1. Check the Actions and verify that both PyPI and npm publishing actions ran successfully. 12 | 1. Verify the npm [package](https://www.npmjs.com/package/manifold-3d?activeTab=code) looks good - unpacked size should be close to 1MB. 13 | 1. Verify PyPI [package](https://pypi.org/project/manifold3d/#files) looks good - a bunch of built distributions ranging from ~600kB to ~1.1MB. 14 | 1. If there's a problem with release deployment, the release workflows can be triggered separately, manually for any branch, under the Actions tab. -------------------------------------------------------------------------------- /bindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Manifold Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | if(NOT MANIFOLD_CROSS_SECTION) 16 | return() 17 | endif() 18 | 19 | if(MANIFOLD_CBIND) 20 | add_subdirectory(c) 21 | endif() 22 | 23 | if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND) 24 | add_subdirectory(python) 25 | endif() 26 | 27 | if(EMSCRIPTEN AND MANIFOLD_JSBIND) 28 | add_subdirectory(wasm) 29 | endif() 30 | -------------------------------------------------------------------------------- /bindings/c/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The Manifold Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | include_directories( 16 | ${CMAKE_CURRENT_SOURCE_DIR} 17 | ${CMAKE_CURRENT_SOURCE_DIR}/include 18 | ${CMAKE_CURRENT_SOURCE_DIR}/../../include 19 | ) 20 | 21 | add_library( 22 | manifoldc 23 | manifoldc.cpp 24 | conv.cpp 25 | box.cpp 26 | cross.cpp 27 | rect.cpp 28 | ) 29 | exportlib(manifoldc) 30 | 31 | if(MANIFOLD_EXPORT) 32 | target_sources(manifoldc PRIVATE meshIOc.cpp) 33 | target_compile_options(manifoldc PUBLIC -DMANIFOLD_EXPORT) 34 | endif() 35 | 36 | target_link_libraries(manifoldc PRIVATE manifold) 37 | 38 | target_include_directories( 39 | manifoldc 40 | PUBLIC 41 | $ 42 | $ 43 | ) 44 | target_compile_options(manifoldc PRIVATE ${MANIFOLD_FLAGS}) 45 | 46 | install(TARGETS manifoldc EXPORT manifoldTargets) 47 | install( 48 | FILES include/manifold/manifoldc.h include/manifold/types.h 49 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/manifold 50 | ) 51 | -------------------------------------------------------------------------------- /bindings/c/box.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "conv.h" 16 | #include "manifold/common.h" 17 | #include "manifold/types.h" 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | ManifoldBox* manifold_box(void* mem, double x1, double y1, double z1, double x2, 23 | double y2, double z2) { 24 | auto p1 = vec3(x1, y1, z1); 25 | auto p2 = vec3(x2, y2, z2); 26 | auto box = new (mem) Box(p1, p2); 27 | return to_c(box); 28 | } 29 | 30 | ManifoldVec3 manifold_box_min(ManifoldBox* b) { return to_c((*from_c(b)).min); } 31 | 32 | ManifoldVec3 manifold_box_max(ManifoldBox* b) { return to_c((*from_c(b)).max); } 33 | 34 | ManifoldVec3 manifold_box_dimensions(ManifoldBox* b) { 35 | auto v = from_c(b)->Size(); 36 | return {v.x, v.y, v.z}; 37 | } 38 | 39 | ManifoldVec3 manifold_box_center(ManifoldBox* b) { 40 | auto v = from_c(b)->Center(); 41 | return {v.x, v.y, v.z}; 42 | } 43 | 44 | double manifold_box_scale(ManifoldBox* b) { return from_c(b)->Scale(); } 45 | 46 | int manifold_box_contains_pt(ManifoldBox* b, double x, double y, double z) { 47 | auto p = vec3(x, y, z); 48 | return from_c(b)->Contains(p); 49 | } 50 | 51 | int manifold_box_contains_box(ManifoldBox* a, ManifoldBox* b) { 52 | auto outer = *from_c(a); 53 | auto inner = *from_c(b); 54 | return outer.Contains(inner); 55 | } 56 | 57 | void manifold_box_include_pt(ManifoldBox* b, double x, double y, double z) { 58 | auto box = *from_c(b); 59 | auto p = vec3(x, y, z); 60 | box.Union(p); 61 | } 62 | 63 | ManifoldBox* manifold_box_union(void* mem, ManifoldBox* a, ManifoldBox* b) { 64 | auto box = from_c(a)->Union(*from_c(b)); 65 | return to_c(new (mem) Box(box)); 66 | } 67 | 68 | ManifoldBox* manifold_box_transform(void* mem, ManifoldBox* b, double x1, 69 | double y1, double z1, double x2, double y2, 70 | double z2, double x3, double y3, double z3, 71 | double x4, double y4, double z4) { 72 | auto mat = mat3x4({x1, y1, z1}, {x2, y2, z2}, {x3, y3, z3}, {x4, y4, z4}); 73 | auto transformed = from_c(b)->Transform(mat); 74 | return to_c(new (mem) Box(transformed)); 75 | } 76 | 77 | ManifoldBox* manifold_box_translate(void* mem, ManifoldBox* b, double x, 78 | double y, double z) { 79 | auto p = vec3(x, y, z); 80 | auto translated = (*from_c(b)) + p; 81 | return to_c(new (mem) Box(translated)); 82 | } 83 | 84 | ManifoldBox* manifold_box_mul(void* mem, ManifoldBox* b, double x, double y, 85 | double z) { 86 | auto p = vec3(x, y, z); 87 | auto scaled = (*from_c(b)) * p; 88 | return to_c(new (mem) Box(scaled)); 89 | } 90 | 91 | int manifold_box_does_overlap_pt(ManifoldBox* b, double x, double y, double z) { 92 | auto p = vec3(x, y, z); 93 | return from_c(b)->DoesOverlap(p); 94 | } 95 | 96 | int manifold_box_does_overlap_box(ManifoldBox* a, ManifoldBox* b) { 97 | return from_c(a)->DoesOverlap(*from_c(b)); 98 | } 99 | 100 | int manifold_box_is_finite(ManifoldBox* b) { return from_c(b)->IsFinite(); } 101 | #ifdef __cplusplus 102 | } 103 | #endif 104 | -------------------------------------------------------------------------------- /bindings/c/conv.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | #include 17 | #include 18 | 19 | #include "manifold/common.h" 20 | #include "manifold/cross_section.h" 21 | #include "manifold/manifold.h" 22 | #include "manifold/types.h" 23 | 24 | using namespace manifold; 25 | using ManifoldVec = std::vector; 26 | using CrossSectionVec = std::vector; 27 | 28 | ManifoldManifold* to_c(manifold::Manifold* m); 29 | ManifoldManifoldVec* to_c(ManifoldVec* ms); 30 | ManifoldCrossSection* to_c(manifold::CrossSection* cs); 31 | ManifoldCrossSectionVec* to_c(CrossSectionVec* csv); 32 | ManifoldSimplePolygon* to_c(manifold::SimplePolygon* p); 33 | ManifoldPolygons* to_c(manifold::Polygons* ps); 34 | ManifoldMeshGL* to_c(manifold::MeshGL* m); 35 | ManifoldMeshGL64* to_c(manifold::MeshGL64* m); 36 | ManifoldBox* to_c(manifold::Box* m); 37 | ManifoldRect* to_c(manifold::Rect* m); 38 | ManifoldError to_c(manifold::Manifold::Error error); 39 | ManifoldVec2 to_c(vec2 v); 40 | ManifoldVec3 to_c(vec3 v); 41 | ManifoldIVec3 to_c(ivec3 v); 42 | ManifoldTriangulation* to_c(std::vector* m); 43 | 44 | const manifold::Manifold* from_c(ManifoldManifold* m); 45 | ManifoldVec* from_c(ManifoldManifoldVec* ms); 46 | const manifold::CrossSection* from_c(ManifoldCrossSection* cs); 47 | CrossSectionVec* from_c(ManifoldCrossSectionVec* csv); 48 | const manifold::SimplePolygon* from_c(ManifoldSimplePolygon* m); 49 | const manifold::Polygons* from_c(ManifoldPolygons* m); 50 | const manifold::MeshGL* from_c(ManifoldMeshGL* m); 51 | const manifold::MeshGL64* from_c(ManifoldMeshGL64* m); 52 | OpType from_c(ManifoldOpType op); 53 | CrossSection::FillRule from_c(ManifoldFillRule fillrule); 54 | CrossSection::JoinType from_c(ManifoldJoinType jt); 55 | const manifold::Box* from_c(ManifoldBox* m); 56 | const manifold::Rect* from_c(ManifoldRect* r); 57 | vec2 from_c(ManifoldVec2 v); 58 | vec3 from_c(ManifoldVec3 v); 59 | ivec3 from_c(ManifoldIVec3 v); 60 | vec4 from_c(ManifoldVec4 v); 61 | const std::vector* from_c(ManifoldTriangulation* m); 62 | 63 | std::vector vector_of_vec_array(ManifoldVec3* vs, size_t length); 64 | std::vector vector_of_vec_array(ManifoldIVec3* vs, size_t length); 65 | std::vector vector_of_vec_array(ManifoldVec4* vs, size_t length); 66 | 67 | template 68 | std::vector vector_of_array(T* ts, size_t length) { 69 | auto vec = std::vector(); 70 | for (size_t i = 0; i < length; ++i) { 71 | vec.push_back(ts[i]); 72 | } 73 | return vec; 74 | } 75 | 76 | template 77 | T* copy_data(void* mem, std::vector v) { 78 | T* ts = reinterpret_cast(mem); 79 | memcpy(ts, v.data(), sizeof(T) * v.size()); 80 | return ts; 81 | } 82 | -------------------------------------------------------------------------------- /bindings/c/include/manifold/types.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | #include 17 | 18 | // opaque pointers 19 | 20 | typedef struct ManifoldManifold ManifoldManifold; 21 | typedef struct ManifoldManifoldVec ManifoldManifoldVec; 22 | typedef struct ManifoldCrossSection ManifoldCrossSection; 23 | typedef struct ManifoldCrossSectionVec ManifoldCrossSectionVec; 24 | typedef struct ManifoldSimplePolygon ManifoldSimplePolygon; 25 | typedef struct ManifoldPolygons ManifoldPolygons; 26 | typedef struct ManifoldMeshGL ManifoldMeshGL; 27 | typedef struct ManifoldMeshGL64 ManifoldMeshGL64; 28 | typedef struct ManifoldBox ManifoldBox; 29 | typedef struct ManifoldRect ManifoldRect; 30 | typedef struct ManifoldTriangulation ManifoldTriangulation; 31 | 32 | #ifdef MANIFOLD_EXPORT 33 | typedef struct ManifoldMaterial ManifoldMaterial; 34 | typedef struct ManifoldExportOptions ManifoldExportOptions; 35 | #endif 36 | 37 | // structs 38 | 39 | typedef struct ManifoldManifoldPair { 40 | ManifoldManifold* first; 41 | ManifoldManifold* second; 42 | } ManifoldManifoldPair; 43 | 44 | typedef struct ManifoldVec2 { 45 | double x; 46 | double y; 47 | } ManifoldVec2; 48 | 49 | typedef struct ManifoldVec3 { 50 | double x; 51 | double y; 52 | double z; 53 | } ManifoldVec3; 54 | 55 | typedef struct ManifoldIVec3 { 56 | int x; 57 | int y; 58 | int z; 59 | } ManifoldIVec3; 60 | 61 | typedef struct ManifoldVec4 { 62 | double x; 63 | double y; 64 | double z; 65 | double w; 66 | } ManifoldVec4; 67 | 68 | typedef struct ManifoldProperties { 69 | double surface_area; 70 | double volume; 71 | } ManifoldProperties; 72 | 73 | // enums 74 | 75 | typedef enum ManifoldOpType { 76 | MANIFOLD_ADD, 77 | MANIFOLD_SUBTRACT, 78 | MANIFOLD_INTERSECT 79 | } ManifoldOpType; 80 | 81 | typedef enum ManifoldError { 82 | MANIFOLD_NO_ERROR, 83 | MANIFOLD_NON_FINITE_VERTEX, 84 | MANIFOLD_NOT_MANIFOLD, 85 | MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS, 86 | MANIFOLD_PROPERTIES_WRONG_LENGTH, 87 | MANIFOLD_MISSING_POSITION_PROPERTIES, 88 | MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS, 89 | MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS, 90 | MANIFOLD_TRANSFORM_WRONG_LENGTH, 91 | MANIFOLD_RUN_INDEX_WRONG_LENGTH, 92 | MANIFOLD_FACE_ID_WRONG_LENGTH, 93 | MANIFOLD_INVALID_CONSTRUCTION, 94 | MANIFOLD_RESULT_TOO_LARGE, 95 | } ManifoldError; 96 | 97 | typedef enum ManifoldFillRule { 98 | MANIFOLD_FILL_RULE_EVEN_ODD, 99 | MANIFOLD_FILL_RULE_NON_ZERO, 100 | MANIFOLD_FILL_RULE_POSITIVE, 101 | MANIFOLD_FILL_RULE_NEGATIVE 102 | } ManifoldFillRule; 103 | 104 | typedef enum ManifoldJoinType { 105 | MANIFOLD_JOIN_TYPE_SQUARE, 106 | MANIFOLD_JOIN_TYPE_ROUND, 107 | MANIFOLD_JOIN_TYPE_MITER, 108 | } ManifoldJoinType; 109 | 110 | // function pointer 111 | typedef double (*ManifoldSdf)(double, double, double, void*); 112 | -------------------------------------------------------------------------------- /bindings/c/meshIOc.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "conv.h" 16 | #include "manifold/meshIO.h" 17 | #include "manifold/types.h" 18 | 19 | // C <-> C++ conversions 20 | 21 | ManifoldMaterial* to_c(manifold::Material* m) { 22 | return reinterpret_cast(m); 23 | } 24 | 25 | ManifoldExportOptions* to_c(manifold::ExportOptions* m) { 26 | return reinterpret_cast(m); 27 | } 28 | 29 | manifold::Material* from_c(ManifoldMaterial* mat) { 30 | return reinterpret_cast(mat); 31 | } 32 | 33 | manifold::ExportOptions* from_c(ManifoldExportOptions* options) { 34 | return reinterpret_cast(options); 35 | } 36 | 37 | #ifdef __cplusplus 38 | extern "C" { 39 | #endif 40 | 41 | // material 42 | 43 | ManifoldMaterial* manifold_material(void* mem) { 44 | return to_c(new (mem) manifold::Material()); 45 | } 46 | 47 | void manifold_material_set_roughness(ManifoldMaterial* mat, double roughness) { 48 | from_c(mat)->roughness = roughness; 49 | } 50 | 51 | void manifold_material_set_metalness(ManifoldMaterial* mat, double metalness) { 52 | from_c(mat)->metalness = metalness; 53 | } 54 | 55 | void manifold_material_set_color(ManifoldMaterial* mat, ManifoldVec3 color) { 56 | from_c(mat)->color = from_c(color); 57 | } 58 | 59 | // export options 60 | 61 | ManifoldExportOptions* manifold_export_options(void* mem) { 62 | return to_c(new (mem) manifold::ExportOptions()); 63 | } 64 | 65 | void manifold_export_options_set_faceted(ManifoldExportOptions* options, 66 | int faceted) { 67 | from_c(options)->faceted = faceted; 68 | } 69 | 70 | void manifold_export_options_set_material(ManifoldExportOptions* options, 71 | ManifoldMaterial* mat) { 72 | from_c(options)->mat = *from_c(mat); 73 | } 74 | 75 | // mesh IO 76 | 77 | void manifold_export_meshgl(const char* filename, ManifoldMeshGL* mesh, 78 | ManifoldExportOptions* options) { 79 | manifold::ExportMesh(std::string(filename), *from_c(mesh), *from_c(options)); 80 | } 81 | 82 | ManifoldMeshGL* manifold_import_meshgl(void* mem, const char* filename, 83 | int force_cleanup) { 84 | auto m = manifold::ImportMesh(std::string(filename), force_cleanup); 85 | return to_c(new (mem) MeshGL(m)); 86 | } 87 | 88 | // memory size 89 | size_t manifold_material_size() { return sizeof(manifold::Material); } 90 | 91 | size_t manifold_export_options_size() { 92 | return sizeof(manifold::ExportOptions); 93 | } 94 | 95 | // memory free + destruction 96 | void manifold_delete_material(ManifoldMaterial* m) { delete (from_c(m)); } 97 | 98 | void manifold_delete_export_options(ManifoldExportOptions* m) { 99 | delete (from_c(m)); 100 | } 101 | 102 | // destruction 103 | void manifold_destruct_material(ManifoldMaterial* m) { from_c(m)->~Material(); } 104 | 105 | void manifold_destruct_export_options(ManifoldExportOptions* m) { 106 | from_c(m)->~ExportOptions(); 107 | } 108 | 109 | #ifdef __cplusplus 110 | } 111 | #endif 112 | -------------------------------------------------------------------------------- /bindings/c/rect.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "conv.h" 16 | #include "manifold/common.h" 17 | #include "manifold/types.h" 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | ManifoldRect* manifold_rect(void* mem, double x1, double y1, double x2, 23 | double y2) { 24 | auto p1 = vec2(x1, y1); 25 | auto p2 = vec2(x2, y2); 26 | auto rect = new (mem) Rect(p1, p2); 27 | return to_c(rect); 28 | } 29 | 30 | ManifoldVec2 manifold_rect_min(ManifoldRect* r) { 31 | return to_c((*from_c(r)).min); 32 | } 33 | 34 | ManifoldVec2 manifold_rect_max(ManifoldRect* r) { 35 | return to_c((*from_c(r)).max); 36 | } 37 | 38 | ManifoldVec2 manifold_rect_dimensions(ManifoldRect* r) { 39 | auto v = from_c(r)->Size(); 40 | return {v.x, v.y}; 41 | } 42 | 43 | ManifoldVec2 manifold_rect_center(ManifoldRect* r) { 44 | auto v = from_c(r)->Center(); 45 | return {v.x, v.y}; 46 | } 47 | 48 | double manifold_rect_scale(ManifoldRect* r) { return from_c(r)->Scale(); } 49 | 50 | int manifold_rect_contains_pt(ManifoldRect* r, double x, double y) { 51 | auto rect = *from_c(r); 52 | auto p = vec2(x, y); 53 | return rect.Contains(p); 54 | } 55 | 56 | int manifold_rect_contains_rect(ManifoldRect* a, ManifoldRect* b) { 57 | auto outer = *from_c(a); 58 | auto inner = *from_c(b); 59 | return outer.Contains(inner); 60 | } 61 | 62 | void manifold_rect_include_pt(ManifoldRect* r, double x, double y) { 63 | auto rect = *from_c(r); 64 | auto p = vec2(x, y); 65 | rect.Union(p); 66 | } 67 | 68 | ManifoldRect* manifold_rect_union(void* mem, ManifoldRect* a, ManifoldRect* b) { 69 | auto rect = from_c(a)->Union(*from_c(b)); 70 | return to_c(new (mem) Rect(rect)); 71 | } 72 | 73 | ManifoldRect* manifold_rect_transform(void* mem, ManifoldRect* r, double x1, 74 | double y1, double x2, double y2, 75 | double x3, double y3) { 76 | auto mat = mat2x3({x1, y1}, {x2, y2}, {x3, y3}); 77 | auto transformed = from_c(r)->Transform(mat); 78 | return to_c(new (mem) Rect(transformed)); 79 | } 80 | 81 | ManifoldRect* manifold_rect_translate(void* mem, ManifoldRect* r, double x, 82 | double y) { 83 | auto p = vec2(x, y); 84 | auto translated = (*from_c(r)) + p; 85 | return to_c(new (mem) Rect(translated)); 86 | } 87 | 88 | ManifoldRect* manifold_rect_mul(void* mem, ManifoldRect* r, double x, 89 | double y) { 90 | auto p = vec2(x, y); 91 | auto scaled = (*from_c(r)) * p; 92 | return to_c(new (mem) Rect(scaled)); 93 | } 94 | 95 | int manifold_rect_does_overlap_rect(ManifoldRect* a, ManifoldRect* r) { 96 | return from_c(a)->DoesOverlap(*from_c(r)); 97 | } 98 | 99 | int manifold_rect_is_empty(ManifoldRect* r) { return from_c(r)->IsEmpty(); } 100 | 101 | int manifold_rect_is_finite(ManifoldRect* r) { return from_c(r)->IsFinite(); } 102 | #ifdef __cplusplus 103 | } 104 | #endif 105 | -------------------------------------------------------------------------------- /bindings/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Manifold Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | nanobind_add_module(manifold3d NB_STATIC STABLE_ABI LTO autogen_docstrings.inl 16 | manifold3d.cpp 17 | ) 18 | 19 | if(MANIFOLD_PYBIND_STUBGEN) 20 | nanobind_add_stub( 21 | manifold3d_stub 22 | MODULE manifold3d 23 | OUTPUT manifold3d.pyi 24 | PYTHON_PATH $ 25 | DEPENDS manifold3d 26 | PATTERN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/stub_pattern.txt 27 | ) 28 | endif() 29 | 30 | target_link_libraries(manifold3d PRIVATE manifold) 31 | target_compile_options( 32 | manifold3d 33 | PRIVATE ${MANIFOLD_FLAGS} -DMODULE_NAME=manifold3d 34 | ) 35 | set_target_properties(manifold3d PROPERTIES OUTPUT_NAME "manifold3d") 36 | 37 | message(Python_EXECUTABLE = ${Python_EXECUTABLE}) 38 | # ideally we should generate a dependency file from python... 39 | set( 40 | DOCSTRING_DEPS 41 | ${PROJECT_SOURCE_DIR}/src/manifold.cpp 42 | ${PROJECT_SOURCE_DIR}/src/constructors.cpp 43 | ${PROJECT_SOURCE_DIR}/src/sort.cpp 44 | ${PROJECT_SOURCE_DIR}/src/cross_section/cross_section.cpp 45 | ${PROJECT_SOURCE_DIR}/src/polygon.cpp 46 | ${PROJECT_SOURCE_DIR}/include/manifold/common.h 47 | ${CMAKE_CURRENT_SOURCE_DIR}/gen_docs.py 48 | ${CMAKE_CURRENT_SOURCE_DIR}/docstring_override.txt 49 | ) 50 | add_custom_command( 51 | OUTPUT autogen_docstrings.inl 52 | DEPENDS ${DOCSTRING_DEPS} 53 | COMMAND ${Python_EXECUTABLE} 54 | ARGS ${CMAKE_CURRENT_SOURCE_DIR}/gen_docs.py 55 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 56 | ) 57 | target_include_directories(manifold3d PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 58 | 59 | if(SKBUILD) 60 | set(MANIFOLD_PYBIND_LIBDIR ${SKBUILD_PLATLIB_DIR}) 61 | else() 62 | set(MANIFOLD_PYBIND_LIBDIR ${Python_SITEARCH}) 63 | endif() 64 | 65 | install( 66 | TARGETS manifold3d 67 | LIBRARY DESTINATION ${MANIFOLD_PYBIND_LIBDIR} COMPONENT bindings 68 | ) 69 | if(MANIFOLD_PYBIND_STUBGEN) 70 | install( 71 | FILES ${CMAKE_CURRENT_BINARY_DIR}/manifold3d.pyi 72 | DESTINATION ${MANIFOLD_PYBIND_LIBDIR} 73 | COMPONENT bindings 74 | ) 75 | endif() 76 | -------------------------------------------------------------------------------- /bindings/python/README.md: -------------------------------------------------------------------------------- 1 | # Python Bindings 2 | 3 | ## Autogenerated Doc-Strings 4 | 5 | Doc-strings for the python API wrapper are generated by gen_docs.py 6 | The script is run automatically during the build to keep python 7 | doc strings fully up-to-date with c++ sources. 8 | 9 | It scrapes documentation comments and c++ function signatures 10 | from the manifold c++ sources, in order to generate a c++ header file 11 | exposing the comments as string variables named by function names. 12 | This allows python bindings to re-use the c++ comments directly. 13 | 14 | Some snake-casing of params is applied for python use case. 15 | 16 | --- 17 | 18 | When modifying the Manifold C++ sources, you may need to update 19 | gen_docs.py. For example, top-level free functions are white-listed, 20 | so if you add a new one, you will need to add it in gen_docs.py. 21 | 22 | Similarly, the list of source files to parse is also white listed, 23 | so if you define functions in new files that need python wrappers, 24 | you will also need to up gen_docs.py. 25 | 26 | To verify that python docs are correct after changes, you can 27 | run the following commends from the manifold repo root: 28 | ``` 29 | pip install . 30 | python -c 'import manifold3d; help(manifold3d)' 31 | ``` 32 | 33 | Alternatively you could generate stubs with roughly the same info 34 | ``` 35 | pip install nanobind-stubgen 36 | pip install . 37 | nanobind-stubgen manifold3d 38 | ``` 39 | It will emit some warnings and write a file `manifold3d.pyi` 40 | which will show all the function signatures and docstrings. 41 | -------------------------------------------------------------------------------- /bindings/python/docstring_override.txt: -------------------------------------------------------------------------------- 1 | cross_section__warp__warp_func: 2 | Move the vertices of this CrossSection (creating a new one) according to 3 | any arbitrary input function, followed by a union operation (with a 4 | Positive fill rule) that ensures any introduced intersections are not 5 | included in the result. 6 | :param warp_func: A function that takes the original vertex position and 7 | return the new position. 8 | cross_section__warp_batch__warp_func: 9 | Same as CrossSection::warp but calls warpFunc with 10 | an ndarray[n, 2] instead of processing only one vertex at a time. 11 | :param warp_func: A function that takes multiple vertex positions as an 12 | ndarray[n, 2] and returns the new vertex positions. 13 | manifold__warp__warp_func: 14 | This function does not change the topology, but allows the vertices to be 15 | moved according to any arbitrary input function. It is easy to create a 16 | function that warps a geometrically valid object into one which overlaps, but 17 | that is not checked here, so it is up to the user to choose their function 18 | with discretion. 19 | :param warp_func: A function that takes the original vertex position and 20 | return the new position. 21 | manifold__warp_batch__warp_func: 22 | Same as Manifold::warp but calls warpFunc with with 23 | an ndarray[n, 3] instead of processing only one vertex at a time. 24 | :param warp_func: A function that takes multiple vertex positions as an 25 | ndarray[n, 3] and returns the new vertex positions. The result should have the 26 | same shape as the input. 27 | manifold__smooth__mesh_gl__sharpened_edges: 28 | Constructs a smooth version of the input mesh by creating tangents; this 29 | method will throw if you have supplied tangents with your mesh already. The 30 | actual triangle resolution is unchanged; use the Refine() method to 31 | interpolate to a higher-resolution curve. 32 | By default, every edge is calculated for maximum smoothness (very much 33 | approximately), attempting to minimize the maximum mean Curvature magnitude. 34 | No higher-order derivatives are considered, as the interpolation is 35 | independent per triangle, only sharing constraints on their boundaries. 36 | :param mesh: input Mesh. 37 | :param sharpened_edges: If desired, you can supply a vector of sharpened 38 | halfedges, which should in general be a small subset of all halfedges. Order 39 | of entries doesn't matter, as each one specifies the desired smoothness 40 | (between zero and one, with one the default for all unspecified halfedges) 41 | and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge 42 | between triVert 0 and 1, etc). 43 | :param edge_smoothness: Smoothness values associated to each halfedge defined 44 | in sharpened_edges. At a smoothness value of zero, a sharp crease is made. The 45 | smoothness is interpolated along each edge, so the specified value should be 46 | thought of as an average. Where exactly two sharpened edges meet at a vertex, 47 | their tangents are rotated to be colinear so that the sharpened edge can be 48 | continuous. Vertices with only one sharpened edge are completely smooth, 49 | allowing sharpened edges to smoothly vanish at termination. A single vertex 50 | can be sharpened by sharping all edges that are incident on it, allowing cones 51 | to be formed. 52 | -------------------------------------------------------------------------------- /bindings/python/examples/all_apis.py: -------------------------------------------------------------------------------- 1 | from manifold3d import * 2 | import numpy as np 3 | 4 | 5 | def all_root_level(): 6 | set_min_circular_angle(10) 7 | set_min_circular_edge_length(1) 8 | set_circular_segments(22) 9 | n = get_circular_segments(1) 10 | assert n == 22 11 | poly = [[0, 0], [1, 0], [1, 1]] 12 | tris = triangulate([poly]) 13 | tris = triangulate([np.array(poly)]) 14 | 15 | 16 | def all_cross_section(): 17 | poly = [[0, 0], [1, 0], [1, 1]] 18 | c = CrossSection([np.array(poly)]) 19 | c = CrossSection([poly]) 20 | c = CrossSection() + c 21 | a = c.area() 22 | c = CrossSection.batch_boolean( 23 | [ 24 | CrossSection.circle(1), 25 | CrossSection.square((3, 3)), 26 | CrossSection.circle(3).translate((1, 1)), 27 | ], 28 | OpType.Add, 29 | ) 30 | c = CrossSection.batch_hull([c, c.translate((1, 0))]) 31 | b = c.bounds() 32 | c = CrossSection.circle(1) 33 | c = CrossSection.compose([c, c.translate((1, 0))]) 34 | cs = c.decompose() 35 | m = c.extrude(1) 36 | c = c.hull() 37 | c = CrossSection.hull_points(poly) 38 | c = CrossSection.hull_points(np.array(poly)) 39 | e = c.is_empty() 40 | c = c.mirror((0, 1)) 41 | n = c.num_contour() 42 | n = c.num_vert() 43 | c = c.offset(1) 44 | m = c.revolve() 45 | c = c.rotate(90) 46 | c = c.scale((2, 2)) 47 | c = c.simplify() 48 | c = CrossSection.square((1, 1)) 49 | p = c.to_polygons() 50 | c = c.transform([[1, 0, 0], [0, 1, 0]]) 51 | c = c.translate((1, 1)) 52 | c = c.warp(lambda p: (p[0] + 1, p[1] / 2)) 53 | c = c.warp_batch(lambda ps: ps * [1, 0.5] + [1, 0]) 54 | 55 | 56 | def all_manifold(): 57 | mesh = Manifold.sphere(1).to_mesh() 58 | m = Manifold(mesh) 59 | m = Manifold() + m 60 | m = m.as_original() 61 | m = Manifold.batch_boolean( 62 | [ 63 | Manifold.cylinder(4, 1), 64 | Manifold.cube((3, 2, 1)), 65 | Manifold.cylinder(5, 3).translate((1, 1, 1)), 66 | ], 67 | OpType.Add, 68 | ) 69 | m = Manifold.batch_hull([m, m.translate((0, 0, 1))]) 70 | b = m.bounding_box() 71 | m = m.calculate_curvature(4, 5) 72 | m = m.calculate_normals(0) 73 | m = m.smooth_by_normals(0) 74 | m = Manifold.compose([m, m.translate((5, 0, 0))]) 75 | m = Manifold.cube((1, 1, 1)) 76 | m = Manifold.cylinder(1, 1) 77 | ms = m.decompose() 78 | m = Manifold.extrude(CrossSection.circle(1), 1) 79 | m = Manifold.revolve(CrossSection.circle(1)) 80 | g = m.genus() 81 | a = m.surface_area() 82 | v = m.volume() 83 | m = m.hull() 84 | m = m.hull_points(mesh.vert_properties) 85 | e = m.is_empty() 86 | m = m.mirror((0, 0, 1)) 87 | n = m.num_edge() 88 | n = m.num_prop() 89 | n = m.num_prop_vert() 90 | n = m.num_tri() 91 | n = m.num_vert() 92 | i = m.original_id() 93 | p = m.get_tolerance() 94 | pp = m.set_tolerance(0.0001) 95 | c = m.project() 96 | m = m.refine(2) 97 | m = m.refine_to_length(0.1) 98 | m = m.refine_to_tolerance(0.01) 99 | m = m.smooth_out() 100 | i = Manifold.reserve_ids(1) 101 | m = m.scale((1, 2, 3)) 102 | m = m.set_properties(3, lambda pos, prop: pos) 103 | c = m.slice(0.5) 104 | m = Manifold.smooth(mesh, [0], [0.5]) 105 | m = Manifold.sphere(1) 106 | m, n = m.split(m.translate((1, 0, 0))) 107 | m, n = m.split_by_plane((0, 0, 1), 0) 108 | e = m.status() 109 | m = Manifold.tetrahedron() 110 | mesh = m.to_mesh() 111 | ok = mesh.merge() 112 | m = m.transform([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]]) 113 | m = m.translate((0, 0, 0)) 114 | m = m.trim_by_plane((0, 0, 1), 0) 115 | m = m.warp(lambda p: (p[0] + 1, p[1] / 2, p[2] * 2)) 116 | m = m.warp_batch(lambda ps: ps * 2 + [1, 0, 0]) 117 | m = Manifold.cube() 118 | m2 = Manifold.cube().translate([2, 0, 0]) 119 | d = m.min_gap(m2, 2) 120 | mesh2 = m.to_mesh64() 121 | ok = mesh.merge() 122 | 123 | 124 | def run(): 125 | all_root_level() 126 | all_cross_section() 127 | all_manifold() 128 | return Manifold() 129 | 130 | 131 | if __name__ == "__main__": 132 | run() 133 | -------------------------------------------------------------------------------- /bindings/python/examples/bricks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2022 The Manifold Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from manifold3d import Manifold 18 | 19 | # https://gist.github.com/deckar01/ef11def51de7e71d9f288c6e5819fdb7 20 | 21 | INCHES = 25.4 22 | 23 | brick_depth = (3 + 5 / 8) * INCHES 24 | brick_height = (2 + 1 / 4) * INCHES 25 | brick_length = (7 + 5 / 8) * INCHES 26 | 27 | mortar_gap = (3 / 8) * INCHES 28 | 29 | 30 | def brick(): 31 | return Manifold.cube([brick_length, brick_depth, brick_height]) 32 | 33 | 34 | def halfbrick(): 35 | return Manifold.cube([(brick_length - mortar_gap) / 2, brick_depth, brick_height]) 36 | 37 | 38 | def row(length): 39 | bricks = [ 40 | brick().translate([(brick_length + mortar_gap) * x, 0, 0]) 41 | for x in range(length) 42 | ] 43 | return sum(bricks, Manifold()) 44 | 45 | 46 | def wall(length, height, alternate=0): 47 | bricks = [ 48 | row(length).translate( 49 | [ 50 | ((z + alternate) % 2) * (brick_depth + mortar_gap), 51 | 0, 52 | (brick_height + mortar_gap) * z, 53 | ] 54 | ) 55 | for z in range(height) 56 | ] 57 | return sum(bricks, Manifold()) 58 | 59 | 60 | def walls(length, width, height): 61 | return sum( 62 | [ 63 | wall(length, height), 64 | wall(width, height, 1).rotate([0, 0, 90]).translate([brick_depth, 0, 0]), 65 | wall(length, height, 1).translate( 66 | [0, (width) * (brick_length + mortar_gap), 0] 67 | ), 68 | wall(width, height) 69 | .rotate([0, 0, 90]) 70 | .translate( 71 | [(length + 0.5) * (brick_length + mortar_gap) - mortar_gap, 0, 0] 72 | ), 73 | ], 74 | Manifold(), 75 | ) 76 | 77 | 78 | def floor(length, width): 79 | results = [walls(length, width, 1)] 80 | if length > 1 and width > 1: 81 | results.append( 82 | floor(length - 1, width - 1).translate( 83 | [brick_depth + mortar_gap, brick_depth + mortar_gap, 0] 84 | ) 85 | ) 86 | if length == 1 and width > 1: 87 | results.append(row(width - 1).rotate((0, 0, 90))) 88 | if width == 1 and length > 1: 89 | results.append( 90 | row(length - 1).translate( 91 | [2 * (brick_depth + mortar_gap), brick_depth + mortar_gap, 0] 92 | ) 93 | ) 94 | results.append( 95 | halfbrick().translate([brick_depth + mortar_gap, brick_depth + mortar_gap, 0]) 96 | ) 97 | return sum(results, Manifold()) 98 | 99 | 100 | def run(width=10, length=10, height=10): 101 | return walls(length, width, height) + floor(length, width) 102 | -------------------------------------------------------------------------------- /bindings/python/examples/cube_with_dents.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2022 The Manifold Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from manifold3d import Manifold 18 | from functools import reduce 19 | 20 | # https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355#file-cube-with-half-spheres-dents-scad 21 | 22 | 23 | def run(n=5, overlap=True): 24 | a = Manifold.cube([n, n, 0.5]).translate([-0.5, -0.5, -0.5]) 25 | 26 | spheres = [ 27 | Manifold.sphere(0.45 if overlap else 0.55, 50).translate([i, j, 0]) 28 | for i in range(n) 29 | for j in range(n) 30 | ] 31 | # spheres = reduce(lambda a, b: a + b, spheres) 32 | 33 | return a - sum(spheres, Manifold()) 34 | -------------------------------------------------------------------------------- /bindings/python/examples/extrude.py: -------------------------------------------------------------------------------- 1 | from manifold3d import CrossSection, Manifold 2 | 3 | 4 | def run(): 5 | # create a polygon 6 | polygon_points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)] 7 | polygons_points = [polygon_points] 8 | 9 | # create a cross-section 10 | cross_section = CrossSection(polygons_points) 11 | polygons = cross_section.to_polygons() 12 | polygon = polygons[0] 13 | if set([tuple(p) for p in polygon]) != set(polygon_points): 14 | raise Exception( 15 | f"polygon={polygon} differs from polygon_points={polygon_points}" 16 | ) 17 | 18 | # extrude a polygon to create a manifold 19 | extruded_polygon = Manifold.extrude(cross_section, 10.0) 20 | eps = 0.001 21 | observed_volume = extruded_polygon.volume() 22 | expected_volume = 10.0 23 | if abs(observed_volume - expected_volume) > eps: 24 | raise Exception( 25 | f"observed_volume={observed_volume} differs from expected_volume={expected_volume}" 26 | ) 27 | observed_surface_area = extruded_polygon.surface_area() 28 | expected_surface_area = 42.0 29 | if abs(observed_surface_area - expected_surface_area) > eps: 30 | raise Exception( 31 | f"observed_surface_area={observed_surface_area} differs from expected_surface_area={expected_surface_area}" 32 | ) 33 | 34 | # get bounding box from manifold 35 | observed_bbox = extruded_polygon.bounding_box() 36 | expected_bbox = (0.0, 0.0, 0.0, 1.0, 1.0, 10.0) 37 | if observed_bbox != expected_bbox: 38 | raise Exception( 39 | f"observed_bbox={observed_bbox} differs from expected_bbox={expected_bbox}" 40 | ) 41 | 42 | return extruded_polygon 43 | 44 | 45 | if __name__ == "__main__": 46 | run() 47 | -------------------------------------------------------------------------------- /bindings/python/examples/gyroid_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2023 The Manifold Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | import math 18 | import numpy as np 19 | from manifold3d import Manifold 20 | 21 | 22 | def gyroid(x, y, z): 23 | xi = x - math.pi / 4.0 24 | yi = y - math.pi / 4.0 25 | zi = z - math.pi / 4.0 26 | return ( 27 | math.cos(xi) * math.sin(yi) 28 | + math.cos(yi) * math.sin(zi) 29 | + math.cos(zi) * math.sin(xi) 30 | ) 31 | 32 | 33 | def gyroid_levelset(level, period, size, n): 34 | return Manifold.level_set( 35 | gyroid, 36 | [-period, -period, -period, period, period, period], 37 | period / n, 38 | level, 39 | ).scale([size / period] * 3) 40 | 41 | 42 | def rhombic_dodecahedron(size): 43 | box = Manifold.cube(size * math.sqrt(2.0) * np.array([1, 1, 2]), True) 44 | result = box.rotate([90, 45, 0]) ^ box.rotate([90, 45, 90]) 45 | return result ^ box.rotate([0, 0, 45]) 46 | 47 | 48 | def gyroid_module(size=20, n=15): 49 | period = math.pi * 2.0 50 | result = ( 51 | gyroid_levelset(-0.4, period, size, n) ^ rhombic_dodecahedron(size) 52 | ) - gyroid_levelset(0.4, period, size, n) 53 | return result.rotate([-45, 0, 90]).translate([0, 0, size / math.sqrt(2.0)]) 54 | 55 | 56 | def run(size=20, n=15): 57 | return gyroid_module(size, n) 58 | -------------------------------------------------------------------------------- /bindings/python/examples/run_all.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2022 The Manifold Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | import pathlib 18 | import sys 19 | import importlib 20 | import trimesh 21 | import numpy as np 22 | from time import time 23 | 24 | if __name__ == "__main__": 25 | current_file = pathlib.Path(__file__) 26 | current_dir = current_file.parent 27 | files = [f.parts[-1][:-3] for f in current_dir.glob("*.py") if f != current_file] 28 | 29 | export_models = len(sys.argv) == 2 and sys.argv[-1] == "-e" 30 | 31 | for f in files: 32 | module = importlib.import_module(f) 33 | t0 = time() 34 | model = module.run() 35 | mesh = model.to_mesh() 36 | if export_models: 37 | if mesh.vert_properties.shape[1] > 3: 38 | vertices = mesh.vert_properties[:, :3] 39 | colors = (mesh.vert_properties[:, 3:] * 255).astype(np.uint8) 40 | else: 41 | vertices = mesh.vert_properties 42 | colors = None 43 | meshOut = trimesh.Trimesh( 44 | vertices=vertices, faces=mesh.tri_verts, vertex_colors=colors 45 | ) 46 | trimesh.exchange.export.export_mesh(meshOut, f"{f}.glb", "glb") 47 | print(f"Exported model to {f}.glb") 48 | t1 = time() 49 | print(f"Took {(t1-t0)*1000:.1f}ms for {f}") 50 | -------------------------------------------------------------------------------- /bindings/python/examples/scallop.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2023 The Manifold Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from manifold3d import Manifold, Mesh 18 | import numpy as np 19 | 20 | 21 | def run(): 22 | # A smoothed manifold demonstrating selective edge sharpening with 23 | # smooth() and refine(), see more details at: 24 | # https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html 25 | 26 | height = 10 27 | radius = 30 28 | offset = 20 29 | wiggles = 12 30 | sharpness = 0.8 31 | n = 50 32 | 33 | triangles = [] 34 | positions = [[-offset, 0, height], [-offset, 0, -height]] 35 | sharpenedEdges = [] 36 | 37 | delta = np.pi / wiggles 38 | for i in range(2 * wiggles): 39 | theta = (i - wiggles) * delta 40 | amp = 0.5 * height * max(np.cos(0.8 * theta), 0) 41 | 42 | positions.append( 43 | [ 44 | radius * np.cos(theta), 45 | radius * np.sin(theta), 46 | amp * (1 if i % 2 == 0 else -1), 47 | ] 48 | ) 49 | 50 | j = i + 1 51 | if j == 2 * wiggles: 52 | j = 0 53 | 54 | smoothness = 1 - sharpness * np.cos((theta + delta / 2) / 2) 55 | halfedge = 3 * len(triangles) + 1 56 | sharpenedEdges.append((halfedge, smoothness)) 57 | triangles.append([0, 2 + i, 2 + j]) 58 | 59 | halfedge = 3 * len(triangles) + 1 60 | sharpenedEdges.append((halfedge, smoothness)) 61 | triangles.append([1, 2 + j, 2 + i]) 62 | 63 | scallop = Mesh( 64 | tri_verts=np.array(triangles, np.int32), 65 | vert_properties=np.array(positions, np.float32), 66 | ) 67 | 68 | def colorCurvature(_pos, oldProp): 69 | a = max(0, min(1, oldProp[0] / 3 + 0.5)) 70 | b = a * a * (3 - 2 * a) 71 | red = [1, 0, 0] 72 | blue = [0, 0, 1] 73 | return [(1 - b) * blue[i] + b * red[i] for i in range(3)] 74 | 75 | edges, smoothing = zip(*sharpenedEdges) 76 | return ( 77 | Manifold.smooth(scallop, edges, smoothing) 78 | .refine(n) 79 | .calculate_curvature(-1, 0) 80 | .set_properties(3, colorCurvature) 81 | ) 82 | -------------------------------------------------------------------------------- /bindings/python/examples/split_cube.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2022 The Manifold Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from manifold3d import Manifold 18 | 19 | 20 | def run(): 21 | a = Manifold.cube([1.0, 1.0, 1.0]) 22 | b = Manifold.cube([1.0, 1.0, 1.0]).rotate([45.0, 45.0, 45.0]) 23 | return a.split(b)[0] 24 | -------------------------------------------------------------------------------- /bindings/python/examples/sponge.py: -------------------------------------------------------------------------------- 1 | from manifold3d import Manifold 2 | import numpy as np 3 | 4 | 5 | def fractal(holes, hole, w, position, depth, maxDepth): 6 | w /= 3 7 | holes.append(hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0])) 8 | if depth == maxDepth: 9 | return 10 | offsets = np.array( 11 | [ 12 | [-w, -w], 13 | [-w, 0.0], 14 | [-w, w], 15 | [0.0, w], 16 | [w, w], 17 | [w, 0.0], 18 | [w, -w], 19 | [0.0, -w], 20 | ] 21 | ) 22 | for offset in offsets: 23 | fractal(holes, hole, w, position + offset, depth + 1, maxDepth) 24 | 25 | 26 | def posColors(pos, _): 27 | return [-p + 0.5 for p in pos] + [1.0] 28 | 29 | 30 | def run(n=1): 31 | result = Manifold.cube([1, 1, 1], True) 32 | holes = [] 33 | fractal(holes, result, 1.0, np.array([0.0, 0.0]), 1, n) 34 | 35 | hole = Manifold.compose(holes) 36 | 37 | result -= hole 38 | result -= hole.rotate([90, 0, 0]) 39 | result -= hole.rotate([0, 90, 0]) 40 | 41 | return ( 42 | result.trim_by_plane([1, 1, 1], 0) 43 | .set_properties(4, posColors) 44 | .scale([100, 100, 100]) 45 | ) 46 | -------------------------------------------------------------------------------- /bindings/python/examples/test_torus_knot.py: -------------------------------------------------------------------------------- 1 | from manifold3d import * 2 | import numpy as np 3 | import pytest 4 | 5 | # Creates a classic torus knot, defined as a string wrapping periodically 6 | # around the surface of an imaginary donut. If p and q have a common 7 | # factor then you will get multiple separate, interwoven knots. This is 8 | # an example of using the warp() method, thus avoiding any direct 9 | # handling of triangles. 10 | 11 | 12 | def run(warp_single=False): 13 | # The number of times the thread passes through the donut hole. 14 | p = 1 15 | # The number of times the thread circles the donut. 16 | q = 3 17 | # Radius of the interior of the imaginary donut. 18 | majorRadius = 25 19 | # Radius of the small cross-section of the imaginary donut. 20 | minorRadius = 10 21 | # Radius of the small cross-section of the actual object. 22 | threadRadius = 3.75 23 | # Number of linear segments making up the threadRadius circle. Default is 24 | # getCircularSegments(threadRadius). 25 | circularSegments = -1 26 | # Number of segments along the length of the knot. Default makes roughly 27 | # square facets. 28 | linearSegments = -1 29 | 30 | # These default values recreate Matlab Knot by Emmett Lalish: 31 | # https://www.thingiverse.com/thing:7080 32 | 33 | kLoops = np.gcd(p, q) 34 | pk = p / kLoops 35 | qk = q / kLoops 36 | n = ( 37 | circularSegments 38 | if circularSegments > 2 39 | else get_circular_segments(threadRadius) 40 | ) 41 | m = linearSegments if linearSegments > 2 else n * qk * majorRadius / threadRadius 42 | 43 | offset = 2 44 | circle = CrossSection.circle(1, n).translate([offset, 0]) 45 | 46 | def ax_rotate(x, theta): 47 | a, b = (x + 1) % 3, (x + 2) % 3 48 | s, c = np.sin(theta), np.cos(theta) 49 | m = np.zeros((len(theta), 4, 4), dtype=np.float32) 50 | m[:, a, a], m[:, a, b] = c, s 51 | m[:, b, a], m[:, b, b] = -s, c 52 | m[:, x, x], m[:, 3, 3] = 1, 1 53 | return m 54 | 55 | def func(pts): 56 | npts = pts.shape[0] 57 | x, y, z = pts[:, 0], pts[:, 1], pts[:, 2] 58 | psi = qk * np.arctan2(x, y) 59 | theta = psi * pk / qk 60 | x1 = np.sqrt(x * x + y * y) 61 | phi = np.arctan2(x1 - offset, z) 62 | 63 | v = np.zeros((npts, 4), dtype=np.float32) 64 | v[:, 0] = threadRadius * np.cos(phi) 65 | v[:, 2] = threadRadius * np.sin(phi) 66 | v[:, 3] = 1 67 | r = majorRadius + minorRadius * np.cos(theta) 68 | 69 | m1 = ax_rotate(0, -np.arctan2(pk * minorRadius, qk * r)) 70 | m1[:, 3, 0] = minorRadius 71 | m2 = ax_rotate(1, theta) 72 | m2[:, 3, 0] = majorRadius 73 | m3 = ax_rotate(2, psi) 74 | 75 | v = v[:, None, :] @ m1 @ m2 @ m3 76 | return v[:, 0, :3] 77 | 78 | def func_single(v): 79 | pts = np.array([v]) 80 | return func(pts)[0] 81 | 82 | if warp_single: 83 | return Manifold.revolve(circle, int(m)).warp(func_single) 84 | else: 85 | return Manifold.revolve(circle, int(m)).warp_batch(func) 86 | 87 | 88 | @pytest.mark.parametrize("warp_single", [True, False]) 89 | def test_warp(warp_single): 90 | m = run(warp_single=warp_single) 91 | assert m.volume() == pytest.approx(20785.76) 92 | assert m.surface_area() == pytest.approx(11176.8) 93 | assert m.genus() == 1 94 | 95 | 96 | if __name__ == "__main__": 97 | run() 98 | -------------------------------------------------------------------------------- /bindings/python/examples/union_failure.py: -------------------------------------------------------------------------------- 1 | from manifold3d import Manifold 2 | 3 | 4 | def run(): 5 | # for some reason this causes collider error 6 | obj = Manifold.cube() 7 | obj += Manifold.cube().rotate([0, 0, 45 + 180]) 8 | return obj 9 | -------------------------------------------------------------------------------- /bindings/python/gen_docs.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname 2 | import re 3 | 4 | base = dirname(dirname(dirname(__file__))) 5 | 6 | 7 | def snake_case(name): 8 | return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower() 9 | 10 | 11 | def python_param_modifier(comment): 12 | # p = f":{snake_case(m[0][1:])}:" 13 | comment = re.sub(r"@(param \w+)", lambda m: f":{snake_case(m[1])}:", comment) 14 | # python API renames `MeshGL` to `Mesh` 15 | comment = re.sub("mesh_gl", "mesh", comment) 16 | comment = re.sub("MeshGL", "Mesh", comment) 17 | return comment 18 | 19 | 20 | def method_key(name): 21 | name = re.sub(r"\+", "_plus", name) 22 | name = re.sub(r"\-", "_minus", name) 23 | name = re.sub(r"\^", "_xor", name) 24 | name = re.sub(r"\=", "_eq", name) 25 | name = re.sub(r"\:", "_", name) 26 | name = re.sub(r"\~", "destroy_", name) 27 | return name 28 | 29 | 30 | parens_re = re.compile(r"[^(]+\(([^(]*(\(.*\))*[^(]*\))", flags=re.DOTALL) 31 | args_re = re.compile( 32 | r"^[^,^\(^\)]*(\(.*\))*[^,^\(^\)]*[\s\&\*]([0-9\w]+)\s*[,\)]", flags=re.DOTALL 33 | ) 34 | 35 | 36 | def parse_args(s): 37 | par = parens_re.match(s) 38 | if not par: 39 | return None 40 | out = [] 41 | arg_str = par[1] 42 | while m := re.search(args_re, arg_str): 43 | out += [snake_case(m[2])] 44 | arg_str = arg_str[m.span()[1] :] 45 | return out 46 | 47 | 48 | def collect(fname, matcher, param_modifier=python_param_modifier): 49 | comment = "" 50 | with open(fname) as f: 51 | for line in f: 52 | line = line.lstrip() 53 | if line.startswith("/**"): 54 | comment = "" 55 | elif line.startswith("*/"): 56 | pass 57 | elif line.startswith("*") and comment is not None: 58 | comment += line[1:].lstrip() 59 | elif comment and (m := matcher(line)): 60 | while (args := parse_args(line)) is None: 61 | line += next(f) 62 | if len(line) > 500: 63 | break 64 | 65 | method = method_key(snake_case(m[1])) 66 | # comment = re.sub(param_re, param_modifier, comment) 67 | comment = param_modifier(comment) 68 | method = "__".join([method, *args]) 69 | assert method not in comments 70 | comments[method] = comment 71 | comment = "" 72 | 73 | 74 | comments = {} 75 | 76 | method_re = re.compile(r"(\w+::[\w\-\+\^\=\:]+)\(") 77 | function_re = re.compile(r"([\w\-\+\^\=\:]+)\(") 78 | 79 | 80 | # we don't handle inline functions in classes properly 81 | # so instead just white-list functions we want 82 | def select_functions(s): 83 | m = function_re.search(s) 84 | if m and "Triangulate" in m[0]: 85 | return m 86 | if m and "Circular" in m[0]: 87 | return m 88 | return None 89 | 90 | 91 | collect(f"{base}/src/manifold.cpp", lambda s: method_re.search(s)) 92 | collect(f"{base}/src/constructors.cpp", lambda s: method_re.search(s)) 93 | collect(f"{base}/src/sort.cpp", lambda s: method_re.search(s)) 94 | collect(f"{base}/src/sdf.cpp", lambda s: method_re.search(s)) 95 | collect(f"{base}/src/cross_section/cross_section.cpp", lambda s: method_re.search(s)) 96 | collect(f"{base}/src/polygon.cpp", select_functions) 97 | collect(f"{base}/include/manifold/common.h", select_functions) 98 | 99 | comments = dict(sorted(comments.items())) 100 | 101 | with open(f"{base}/bindings/python/docstring_override.txt") as f: 102 | key = "" 103 | for l in f: 104 | if l.startswith(" "): 105 | comments[key] += l[2:] 106 | else: 107 | key = l[:-2] 108 | if key not in comments.keys(): 109 | print(f"Error, unknown docstring override key {key}") 110 | exit(-1) 111 | comments[key] = "" 112 | 113 | gen_h = "autogen_docstrings.inl" 114 | with open(gen_h, "w") as f: 115 | f.write("#pragma once\n\n") 116 | f.write("// --- AUTO GENERATED ---\n") 117 | f.write("// gen_docs.py is run by cmake build\n\n") 118 | f.write("namespace manifold_docstrings {\n") 119 | for key, doc in comments.items(): 120 | f.write(f'const char* {key} = R"___({doc.strip()})___";\n') 121 | f.write("} // namespace manifold_docs") 122 | -------------------------------------------------------------------------------- /bindings/python/stub_pattern.txt: -------------------------------------------------------------------------------- 1 | manifold3d.__prefix__: 2 | import numpy as np 3 | from typing import Literal, TypeVar, Union 4 | 5 | N = TypeVar('N', bound=np.generic) 6 | DoubleNx2 = np.ndarray[tuple[N, Literal[2]], np.dtype[np.double]] 7 | Doublex2 = Union[np.ndarray[tuple[Literal[2]], np.dtype[np.double]], tuple[float, float], list[float]] 8 | Doublex3 = Union[np.ndarray[tuple[Literal[3]], np.dtype[np.double]], tuple[float, float, float], list[float]] 9 | Double2x3 = np.ndarray[tuple[Literal[2], Literal[3]], np.dtype[np.double]] 10 | Double3x4 = np.ndarray[tuple[Literal[3], Literal[4]], np.dtype[np.double]] 11 | DoubleNx3 = np.ndarray[tuple[N, Literal[3]], np.dtype[np.double]] 12 | Intx3 = Union[np.ndarray[tuple[Literal[3]], np.dtype[np.integer]], tuple[int, int, int], list[int]] 13 | IntNx3 = np.ndarray[tuple[N, Literal[3]], np.dtype[np.integer]] 14 | -------------------------------------------------------------------------------- /bindings/wasm/.gitignore: -------------------------------------------------------------------------------- 1 | manifold.js 2 | manifold.wasm 3 | -------------------------------------------------------------------------------- /bindings/wasm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Manifold Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | add_executable(manifoldjs bindings.cpp) 16 | 17 | set_source_files_properties( 18 | bindings.cpp 19 | PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js 20 | ) 21 | target_link_libraries( 22 | manifoldjs 23 | PRIVATE manifold $<$:TBB::tbb> 24 | ) 25 | target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS}) 26 | target_link_options( 27 | manifoldjs 28 | PUBLIC 29 | --pre-js 30 | ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js 31 | --bind 32 | -sALLOW_TABLE_GROWTH=1 33 | -sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction 34 | -sMODULARIZE=1 35 | -sEXPORT_ES6=1 36 | ) 37 | 38 | set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold") 39 | 40 | file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/built) 41 | 42 | # ensure that interface files are copied over when modified 43 | add_custom_target( 44 | js_deps 45 | ALL 46 | DEPENDS manifoldjs ${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts 47 | ) 48 | 49 | add_custom_command( 50 | TARGET js_deps 51 | POST_BUILD 52 | # copy WASM build back here for publishing to npm 53 | COMMAND 54 | ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/manifold.* 55 | ${CMAKE_CURRENT_SOURCE_DIR} 56 | # copy WASM build and TS declarations for Vite to package into ManifoldCAD.org 57 | COMMAND 58 | ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/manifold* 59 | ${CMAKE_CURRENT_SOURCE_DIR}/examples/built/ 60 | # copy TS declarations to public so they can be accessed by our editor 61 | COMMAND 62 | ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts 63 | ${CMAKE_CURRENT_SOURCE_DIR}/examples/public/ 64 | ) 65 | -------------------------------------------------------------------------------- /bindings/wasm/documents/Get Started.md: -------------------------------------------------------------------------------- 1 | # Get Started 2 | 3 | 4 | ## Installation 5 | 6 | In your project root folder run: 7 | 8 | ```bash 9 | npm i manifold-3d 10 | ``` 11 | 12 | To start using Manifold, import it and initialize it: 13 | 14 | ```js 15 | import Module from 'manifold-3d'; 16 | 17 | const wasm = await Module(); 18 | wasm.setup(); 19 | const { Manifold } = wasm; 20 | ``` 21 | 22 | Intro example 23 | 24 | ```js 25 | const {cube, sphere} = Manifold; 26 | const box = cube([100, 100, 100], true); 27 | const ball = sphere(60, 100); 28 | const result = box.subtract(ball); 29 | ``` 30 | 31 | ## Next steps 32 | 33 | In order to visualize Manifold mesh using Three.js library please check out our example [here](https://github.com/elalish/manifold/blob/master/bindings/wasm/examples/three.ts) -------------------------------------------------------------------------------- /bindings/wasm/examples/.npmignore: -------------------------------------------------------------------------------- 1 | manifold* -------------------------------------------------------------------------------- /bindings/wasm/examples/3mf-export.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@jscadui/3mf-export'; 2 | -------------------------------------------------------------------------------- /bindings/wasm/examples/README.md: -------------------------------------------------------------------------------- 1 | # WASM/JS Examples 2 | 3 | This is the home of our editor, [ManifoldCAD.org](https://manifoldcad.org/), as well as our other public examples of integrations with `three.js`, ``, 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. 4 | 5 | ## Local development 6 | 7 | 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: 8 | 9 | ``` 10 | npm install 11 | npm test 12 | ``` 13 | 14 | To develop the manifoldCAD.org editor as well as our other example pages, run 15 | ``` 16 | npm run dev 17 | ``` 18 | 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 19 | ``` 20 | npm run build 21 | ``` 22 | See `package.json` for other useful scripts. 23 | 24 | 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/`. 25 | 26 | To debug the WASM build directly in Chrome dev tools, simply build in debug mode: 27 | ``` 28 | emcmake cmake -DCMAKE_BUILD_TYPE=Debug .. && emmake make 29 | ``` 30 | and install the [DWARF](goo.gle/wasm-debugging-extension) Chrome extension. 31 | 32 | When testing [ManifoldCAD.org](https://manifoldcad.org/) (either locally or the 33 | deployed version) note that it uses a service worker for faster loading. This 34 | means you need to open the page twice to see updates (the first time loads the 35 | old version and caches the new one, the second time loads the new version from 36 | cache). To see changes on each reload, open Chrome dev tools, go to the 37 | Application tab and check "update on reload". 38 | 39 | ### Note for firefox users 40 | 41 | To use the manifoldCAD.org editor (`npm run dev`), you'll likely have to set 42 | `dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the 43 | discussion of the 44 | [issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102) 45 | of this repository. 46 | -------------------------------------------------------------------------------- /bindings/wasm/examples/bindings.test.ts: -------------------------------------------------------------------------------- 1 | import {beforeAll, expect, suite, test} from 'vitest'; 2 | 3 | import Module, {type ManifoldToplevel} from './built/manifold' 4 | 5 | let manifoldModule: ManifoldToplevel; 6 | 7 | beforeAll(async () => { 8 | manifoldModule = await Module(); 9 | manifoldModule.setup(); 10 | }); 11 | 12 | suite('CrossSection Bindings', () => { 13 | test('ToPolygons return correct shape', () => { 14 | const polygons = manifoldModule.CrossSection.square().toPolygons(); 15 | expect(polygons).toHaveLength(1); 16 | expect(polygons[0]).toHaveLength(4); 17 | expect(polygons[0]).toContainEqual([0, 0]); 18 | expect(polygons[0]).toContainEqual([0, 1]); 19 | expect(polygons[0]).toContainEqual([1, 0]); 20 | expect(polygons[0]).toContainEqual([1, 1]); 21 | }); 22 | 23 | test('project creates a valid polygon', () => { 24 | const cs = manifoldModule.Manifold.sphere(1).project(); 25 | expect(cs.numContour()).toEqual(1); 26 | expect(cs.area()).to.be.greaterThan(0); 27 | }); 28 | 29 | test('simplify argument is defaulted', () => { 30 | const cs = manifoldModule.CrossSection.circle(1).simplify(); 31 | expect(cs.numContour()).toEqual(1); 32 | expect(cs.area()).to.be.greaterThan(0); 33 | }); 34 | }); -------------------------------------------------------------------------------- /bindings/wasm/examples/make-manifold.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Make manifold glTF 6 | 7 | 8 | 9 | 44 | 45 | 46 | 47 |

Load a glTF/GLB model and our Manifold library will attempt to 48 | merge it into a set of manifold objects. If the model is water-tight, you can download the new GLB which will have 49 | our EXT_mesh_manifold extension, thus preserving the 50 | manifold data without losing any mesh properties.

51 |

If the View Manifold GLB checkbox is enabled, then some meshes in the model have open edges and don't represent a 52 | solid object. Check this box to see only the manifold parts, which will also enable them to be downloaded. If the 53 | download button is still disabled, it means there were no manifold meshes found.

54 | 55 | 56 | 57 | 58 | 59 | Drop a GLB here 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /bindings/wasm/examples/model-viewer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Manifold - <code></code><model-viewer></code> Example 6 | 7 | 8 | 9 | 35 | 36 | 37 | 38 |

This example demonstrates reading and writing glTF models using gltf-transform and using this to feed Manifold's 40 | output into <model-viewer>. It also shows how to pass mesh properties 41 | like UV coordinates through Manifold and how to re-associate textures and materials after operations, made simple 42 | using our `gltf-io.ts` library. The resulting glTF includes the EXT_mesh_manifold extension, using our 43 | `manifold-gltf.ts` extension to gltf-transform, which ensures lossless data transfer.

44 |

Please open dev tools to inspect our source code - everything is source mapped back to our TS files and commented 45 | thoroughly. A more basic three.js example is here.

46 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /bindings/wasm/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manifold-examples", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc --noEmit && vite build", 9 | "build:watch": "vite build --watch", 10 | "preview": "vite preview", 11 | "test": "vitest run", 12 | "test:ui": "vitest --ui", 13 | "test:dev": "vitest" 14 | }, 15 | "dependencies": { 16 | "@gltf-transform/core": "^3.8.0", 17 | "@gltf-transform/extensions": "^3.8.0", 18 | "@gltf-transform/functions": "^3.8.0", 19 | "@jscadui/3mf-export": "^0.5.0", 20 | "@types/three": "^0.164.0", 21 | "fflate": "^0.8.0", 22 | "gl-matrix": "^3.4.3", 23 | "simple-dropzone": "0.8.3", 24 | "three": "0.164.1" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^22.10.2", 28 | "@vitest/ui": "^1.6.0", 29 | "typescript": "5.2.2", 30 | "vite": "^4.5.0", 31 | "vitest": "^1.6.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/close.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/manifoldCAD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/manifoldCAD.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/manifoldCADonly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/manifoldCADonly.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/mengerSponge192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/mengerSponge192.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/mengerSponge512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/mengerSponge512.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/mengerSponge64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/mengerSponge64.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/pause.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/pencil.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/play.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/redo.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/share.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/star.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/trash.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/icons/undo.png -------------------------------------------------------------------------------- /bindings/wasm/examples/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ManifoldCAD", 3 | "icons": [ 4 | { 5 | "src": "/icons/mengerSponge192.png", 6 | "type": "image/png", 7 | "sizes": "192x192" 8 | }, 9 | { 10 | "src": "/icons/mengerSponge512.png", 11 | "type": "image/png", 12 | "sizes": "512x512" 13 | } 14 | ], 15 | "id": "/", 16 | "start_url": "/", 17 | "background_color": "#FFFFFF", 18 | "display": "standalone", 19 | "orientation": "landscape", 20 | "scope": "/", 21 | "theme_color": "#A288B3", 22 | "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.", 23 | "categories": [ 24 | "design", 25 | "developer", 26 | "education", 27 | "graphics", 28 | "productivity" 29 | ], 30 | "screenshots": [ 31 | { 32 | "src": "/icons/manifoldCAD.png", 33 | "type": "image/png", 34 | "sizes": "2906x1980" 35 | }, 36 | { 37 | "src": "/icons/manifoldCADonly.png", 38 | "type": "image/png", 39 | "sizes": "2906x1980" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /bindings/wasm/examples/public/models/moon.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/models/moon.glb -------------------------------------------------------------------------------- /bindings/wasm/examples/public/models/space.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/bindings/wasm/examples/public/models/space.glb -------------------------------------------------------------------------------- /bindings/wasm/examples/public/service-worker.js: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Increment version when updating CDN URLs to clean up cache. 16 | const cacheName = 'manifoldCAD-cache-v1'; 17 | 18 | self.addEventListener('activate', e => { 19 | e.waitUntil(caches.keys().then((keyList) => { 20 | return Promise.all(keyList.map((key) => { 21 | if (key !== cacheName) { 22 | return caches.delete(key); 23 | } 24 | })); 25 | })); 26 | }); 27 | 28 | // Serves from cache, then updates the cache in the background from the network, 29 | // if available. Update available on refresh. 30 | self.addEventListener( 31 | 'fetch', 32 | e => {e.respondWith(caches.match(e.request).then(cachedResponse => { 33 | const networkFetch = fetch(e.request).then(response => { 34 | const clone = response.clone(); 35 | caches.open(cacheName).then(cache => { 36 | cache.put(e.request, clone); 37 | }); 38 | return response; 39 | }); 40 | return cachedResponse || networkFetch; 41 | }))}); 42 | -------------------------------------------------------------------------------- /bindings/wasm/examples/simple-dropzone.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'simple-dropzone'; -------------------------------------------------------------------------------- /bindings/wasm/examples/three.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Manifold - three.js Example 6 | 7 | 8 | 9 | 35 | 36 | 37 | 38 |

This example demonstrates interop between Manifold and Three.js with minimal code - please open dev tools to 39 | inspect the source. Here we generate two multi-material Three.js meshes and convert them to Manifolds while building 40 | a mapping from material to Manifold ID. Then Boolean operations are performed on the Manifolds and the result is 41 | converted back to a Three.js mesh using the material mapping. The input cube has half red faces and half 42 | normal-shaded, while the icosahedron has half blue faces and half normal-shaded. The resulting mesh has three 43 | materials, since one (normal-shaded) was common between the input models.

44 |

A slightly more complex example involving our library for GLTF import/export and <model-viewer> can be found 45 | here.

46 |
51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /bindings/wasm/examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "moduleResolution": "Bundler", 6 | "lib": [ 7 | "es2022", 8 | "dom" 9 | ], 10 | "sourceMap": true, 11 | "inlineSources": true, 12 | "strict": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "declaration": true, 18 | "skipLibCheck": true, 19 | }, 20 | "include": [ 21 | "./*.ts" 22 | ], 23 | "exclude": [] 24 | } -------------------------------------------------------------------------------- /bindings/wasm/examples/vite-fixup-plugin.js: -------------------------------------------------------------------------------- 1 | // https://gist.github.com/jamsinclair/6ad148d0590291077a4ce389c2b274ea 2 | import {createFilter} from 'vite'; 3 | 4 | function isEmscriptenFile(code) { 5 | return /var\s+Module\s*=|WebAssembly\.instantiate/.test(code) && 6 | /var\s+workerOptions\s*=/.test(code); 7 | } 8 | 9 | /** 10 | * Vite plugin that replaces Emscripten workerOptions with static object literal 11 | * to fix error with Vite See project issue: 12 | * https://github.com/emscripten-core/emscripten/issues/22394 13 | * 14 | * Defaults to running for all .js and .ts files. If there are any issues you 15 | * can use the include/exclude options. 16 | * 17 | * @param {Object} options 18 | * @property {string[]} [include] - Glob patterns to include 19 | * @property {string[]} [exclude] - Glob patterns to exclude 20 | * @returns {import('vite').Plugin} 21 | */ 22 | export default function emscriptenStaticWorkerOptions(options = {}) { 23 | const filter = createFilter(options.include || /\.[jt]s$/, options.exclude); 24 | 25 | return { 26 | name: 'emscripten-static-worker-options', 27 | enforce: 'pre', 28 | transform(code, id) { 29 | if (!filter(id)) return null; 30 | 31 | if (!isEmscriptenFile(code)) return null; 32 | 33 | const workerOptionsMatch = 34 | code.match(/var\s+workerOptions\s*=\s*({[^}]+})/); 35 | if (!workerOptionsMatch) return null; 36 | 37 | const optionsObjectStr = workerOptionsMatch[1]; 38 | const optionsDeclarationStr = workerOptionsMatch[0]; 39 | 40 | const modifiedCode = 41 | code.replace(optionsDeclarationStr, '') 42 | .replace( 43 | new RegExp('workerOptions(?![\\w$])', 'g'), optionsObjectStr); 44 | 45 | return {code: modifiedCode, map: null}; 46 | } 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /bindings/wasm/examples/vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import {resolve} from 'path' 3 | import {defineConfig} from 'vite' 4 | 5 | import emscriptenStaticWorkerOptions from './vite-fixup-plugin.js' 6 | 7 | export default defineConfig({ 8 | test: {testTimeout: 15000}, 9 | worker: {format: 'es', plugins: [emscriptenStaticWorkerOptions]}, 10 | server: { 11 | headers: { 12 | 'Cross-Origin-Embedder-Policy': 'require-corp', 13 | 'Cross-Origin-Opener-Policy': 'same-origin', 14 | }, 15 | }, 16 | build: { 17 | target: 'esnext', 18 | sourcemap: true, 19 | rollupOptions: { 20 | input: { 21 | manifoldCAD: resolve(__dirname, 'index.html'), 22 | makeManifold: resolve(__dirname, 'make-manifold.html'), 23 | modelViewer: resolve(__dirname, 'model-viewer.html'), 24 | three: resolve(__dirname, 'three.html'), 25 | }, 26 | output: { 27 | entryFileNames: `assets/[name].js`, 28 | chunkFileNames: `assets/[name].js`, 29 | assetFileNames: `assets/[name].[ext]` 30 | } 31 | }, 32 | }, 33 | }) 34 | -------------------------------------------------------------------------------- /bindings/wasm/examples/worker-wrapper.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {cleanup, evaluateCADToModel} from './worker'; 16 | 17 | // Setup complete 18 | self.postMessage(null); 19 | 20 | if (self.console) { 21 | const oldLog = self.console.log; 22 | self.console.log = function(...args) { 23 | let message = ''; 24 | for (const arg of args) { 25 | if (arg == null) { 26 | message += 'undefined'; 27 | } else if (typeof arg == 'object') { 28 | message += JSON.stringify(arg, null, 4); 29 | } else { 30 | message += arg.toString(); 31 | } 32 | } 33 | self.postMessage({log: message}); 34 | oldLog(...args); 35 | }; 36 | } 37 | 38 | self.onmessage = async (e) => { 39 | try { 40 | const result = await evaluateCADToModel(e.data); 41 | self.postMessage(result); 42 | } catch (error: any) { 43 | console.log(error.toString()); 44 | self.postMessage({objectURL: null}); 45 | } finally { 46 | cleanup(); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /bindings/wasm/manifold-global-types.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export interface SealedUint32Array extends Uint32Array { 16 | length: N; 17 | } 18 | 19 | export interface SealedFloat32Array extends Float32Array { 20 | length: N; 21 | } 22 | 23 | export type Vec2 = [number, number]; 24 | export type Vec3 = [number, number, number]; 25 | // 3x3 matrix stored in column-major order 26 | export type Mat3 = [ 27 | number, 28 | number, 29 | number, 30 | number, 31 | number, 32 | number, 33 | number, 34 | number, 35 | number, 36 | ]; 37 | // 4x4 matrix stored in column-major order 38 | export type Mat4 = [ 39 | number, 40 | number, 41 | number, 42 | number, 43 | number, 44 | number, 45 | number, 46 | number, 47 | number, 48 | number, 49 | number, 50 | number, 51 | number, 52 | number, 53 | number, 54 | number, 55 | ]; 56 | export type SimplePolygon = Vec2[]; 57 | export type Polygons = SimplePolygon|SimplePolygon[]; 58 | export type Rect = { 59 | min: Vec2, 60 | max: Vec2 61 | }; 62 | export type Box = { 63 | min: Vec3, 64 | max: Vec3 65 | }; 66 | export type Smoothness = { 67 | halfedge: number, 68 | smoothness: number 69 | }; 70 | export type FillRule = 'EvenOdd'|'NonZero'|'Positive'|'Negative' 71 | export type JoinType = 'Square'|'Round'|'Miter' 72 | export type ErrorStatus = 'NoError'|'NonFiniteVertex'|'NotManifold'| 73 | 'VertexOutOfBounds'|'PropertiesWrongLength'|'MissingPositionProperties'| 74 | 'MergeVectorsDifferentLengths'|'MergeIndexOutOfBounds'| 75 | 'TransformWrongLength'|'RunIndexWrongLength'|'FaceIDWrongLength'| 76 | 'InvalidConstruction' 77 | -------------------------------------------------------------------------------- /bindings/wasm/manifold.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as T from './manifold-encapsulated-types'; 16 | export * from './manifold-global-types'; 17 | 18 | export type CrossSection = T.CrossSection; 19 | export type Manifold = T.Manifold; 20 | export type Mesh = T.Mesh; 21 | 22 | export interface ManifoldToplevel { 23 | CrossSection: typeof T.CrossSection; 24 | Manifold: typeof T.Manifold; 25 | Mesh: typeof T.Mesh; 26 | triangulate: typeof T.triangulate; 27 | setMinCircularAngle: typeof T.setMinCircularAngle; 28 | setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength; 29 | setCircularSegments: typeof T.setCircularSegments; 30 | getCircularSegments: typeof T.getCircularSegments; 31 | resetToCircularDefaults: typeof T.resetToCircularDefaults; 32 | setup: () => void; 33 | } 34 | 35 | export default function Module(config?: {locateFile: () => string}): 36 | Promise; 37 | -------------------------------------------------------------------------------- /bindings/wasm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manifold-3d", 3 | "version": "3.1.1", 4 | "description": "Geometry library for topological robustness", 5 | "main": "manifold.js", 6 | "scripts": { 7 | "docs": "npx typedoc" 8 | }, 9 | "files": [ 10 | "manifold.js", 11 | "manifold.wasm", 12 | "manifold.d.ts", 13 | "manifold-encapsulated-types.d.ts", 14 | "manifold-global-types.d.ts" 15 | ], 16 | "typings": "manifold.d.ts", 17 | "types": "manifold.d.ts", 18 | "type": "module", 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/elalish/manifold.git" 22 | }, 23 | "keywords": [ 24 | "3D", 25 | "mesh", 26 | "manifold", 27 | "geometry", 28 | "solid", 29 | "CAD", 30 | "triangle", 31 | "SDF" 32 | ], 33 | "contributors": [ 34 | "Emmett Lalish ", 35 | "Chun Kit LAM " 36 | ], 37 | "license": "Apache-2.0", 38 | "bugs": { 39 | "url": "https://github.com/elalish/manifold/issues" 40 | }, 41 | "homepage": "https://github.com/elalish/manifold#readme", 42 | "devDependencies": { 43 | "typedoc": "^0.26.6" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bindings/wasm/typedoc.json: -------------------------------------------------------------------------------- 1 | // typedoc.json 2 | { 3 | "entryPoints": [ 4 | "manifold-encapsulated-types.d.ts", 5 | "manifold-global-types.d.ts" 6 | ], 7 | "projectDocuments": ["documents/*.md"], 8 | "out": "docs" 9 | } -------------------------------------------------------------------------------- /cmake/configHelper.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The Manifold Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | function(exportbin TARGET) 16 | if(MSVC) 17 | set_target_properties( 18 | ${TARGET} 19 | PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin 20 | ) 21 | endif() 22 | endfunction() 23 | 24 | function(exportlib TARGET) 25 | add_library(manifold::${TARGET} ALIAS ${TARGET}) 26 | set_target_properties( 27 | ${TARGET} 28 | PROPERTIES VERSION "${MANIFOLD_VERSION}" SOVERSION ${MANIFOLD_VERSION_MAJOR} 29 | ) 30 | if(MSVC) 31 | set_target_properties( 32 | ${TARGET} 33 | PROPERTIES 34 | WINDOWS_EXPORT_ALL_SYMBOLS ON 35 | LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib 36 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib 37 | ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib 38 | ) 39 | endif() 40 | endfunction() 41 | -------------------------------------------------------------------------------- /cmake/info.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The Manifold Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # configuration summary, idea from openscad 16 | # https://github.com/openscad/openscad/blob/master/cmake/Modules/info.cmake 17 | 18 | get_target_property(MANIFOLD_COMPILE_OPTIONS manifold COMPILE_OPTIONS) 19 | get_target_property(MANIFOLD_LINK_OPTIONS manifold LINK_OPTIONS) 20 | if(MANIFOLD_LINK_OPTIONS STREQUAL "MANIFOLD_LINK_OPTIONS-NOTFOUND") 21 | set(MANIFOLD_LINK_OPTIONS "") 22 | endif() 23 | 24 | message(STATUS "====================================") 25 | message(STATUS "Manifold Build Configuration Summary") 26 | message(STATUS "====================================") 27 | message(STATUS " ") 28 | if(MXECROSS) 29 | message(STATUS "Environment: MXE") 30 | elseif(APPLE) 31 | message(STATUS "Environment: macOS") 32 | elseif(WIN32) 33 | if(MINGW) 34 | message(STATUS "Environment: msys2") 35 | else() 36 | message(STATUS "Environment: Windows") 37 | endif() 38 | elseif(LINUX) 39 | message(STATUS "Environment: Linux") 40 | elseif(UNIX) 41 | message(STATUS "Environment: Unknown Unix") 42 | else() 43 | message(STATUS "Environment: Unknown") 44 | endif() 45 | message(STATUS " ") 46 | message(STATUS "CMAKE_VERSION: ${CMAKE_VERSION}") 47 | message(STATUS "CMAKE_TOOLCHAIN_FILE: ${CMAKE_TOOLCHAIN_FILE}") 48 | message(STATUS "CMAKE_GENERATOR: ${CMAKE_GENERATOR}") 49 | message(STATUS "CPACK_CMAKE_GENERATOR: ${CPACK_CMAKE_GENERATOR}") 50 | message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") 51 | message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}") 52 | message(STATUS "CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") 53 | message(STATUS "CMAKE_CXX_COMPILER_VERSION: ${CMAKE_CXX_COMPILER_VERSION}") 54 | if(APPLE) 55 | message( 56 | STATUS 57 | "CMAKE_OSX_DEPLOYMENT_TARGET: ${CMAKE_OSX_DEPLOYMENT_TARGET}" 58 | ) 59 | message(STATUS "CMAKE_OSX_ARCHITECTURES: ${CMAKE_OSX_ARCHITECTURES}") 60 | endif() 61 | message(STATUS "BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") 62 | message(STATUS " ") 63 | message(STATUS "MANIFOLD_VERSION: ${MANIFOLD_VERSION}") 64 | message(STATUS "MANIFOLD_PAR: ${MANIFOLD_PAR}") 65 | message(STATUS "MANIFOLD_CROSS_SECTION: ${MANIFOLD_CROSS_SECTION}") 66 | message(STATUS "MANIFOLD_EXPORT: ${MANIFOLD_EXPORT}") 67 | message(STATUS "MANIFOLD_TEST: ${MANIFOLD_TEST}") 68 | message(STATUS "MANIFOLD_DEBUG: ${MANIFOLD_DEBUG}") 69 | message(STATUS "MANIFOLD_ASSERT: ${MANIFOLD_ASSERT}") 70 | message(STATUS "MANIFOLD_CBIND: ${MANIFOLD_CBIND}") 71 | message(STATUS "MANIFOLD_PYBIND: ${MANIFOLD_PYBIND}") 72 | message(STATUS "MANIFOLD_JSBIND: ${MANIFOLD_JSBIND}") 73 | message(STATUS "MANIFOLD_FLAGS: ${MANIFOLD_FLAGS}") 74 | # these two include global flags added through add_compile_options and 75 | # add_link_options 76 | message(STATUS "MANIFOLD_COMPILE_OPTIONS: ${MANIFOLD_COMPILE_OPTIONS}") 77 | message(STATUS "MANIFOLD_LINK_OPTIONS: ${MANIFOLD_LINK_OPTIONS}") 78 | message(STATUS "MANIFOLD_DOWNLOADS: ${MANIFOLD_DOWNLOADS}") 79 | message(STATUS "MANIFOLD_USE_BUILTIN_TBB: ${MANIFOLD_USE_BUILTIN_TBB}") 80 | message( 81 | STATUS 82 | "MANIFOLD_USE_BUILTIN_CLIPPER2: ${MANIFOLD_USE_BUILTIN_CLIPPER2}" 83 | ) 84 | message( 85 | STATUS 86 | "MANIFOLD_USE_BUILTIN_NANOBIND: ${MANIFOLD_USE_BUILTIN_NANOBIND}" 87 | ) 88 | message(STATUS "MANIFOLD_FUZZ: ${MANIFOLD_FUZZ}") 89 | message(STATUS "TRACY_ENABLE: ${TRACY_ENABLE}") 90 | message(STATUS "TRACY_MEMORY_USAGE: ${TRACY_MEMORY_USAGE}") 91 | message(STATUS " ") 92 | -------------------------------------------------------------------------------- /cmake/manifold.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=@CMAKE_INSTALL_FULL_LIBDIR@ 4 | includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ 5 | 6 | Name: manifold@PCFILE_LIB_SUFFIX@ 7 | Description: Geometry library for topological robustness 8 | Version: @MANIFOLD_VERSION@ 9 | URL: https://github.com/elalish/manifold 10 | Requires-private: @TEMPLATE_OPTIONAL_TBB@ @TEMPLATE_OPTIONAL_CLIPPER@ 11 | Libs: -L${libdir} -lmanifold@PCFILE_LIB_SUFFIX@ 12 | Cflags: -I${includedir} -std=c++17 13 | -------------------------------------------------------------------------------- /cmake/manifoldConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # Compute the installation prefix relative to this file, so we can have the 2 | # subsequent find_package calls first check for bundled dependencies. 3 | get_filename_component(_FIND_ROOT "${CMAKE_CURRENT_LIST_FILE}" PATH) 4 | # CMAKE_CURRENT_LIST_FILE path is several directories below the path we want 5 | # for _FIND_ROOT, so we need multiple get_filename_component calls to trim it 6 | # back to the the root path. 7 | get_filename_component(_FIND_ROOT "${_FIND_ROOT}" PATH) 8 | get_filename_component(_FIND_ROOT "${_FIND_ROOT}" PATH) 9 | get_filename_component(_FIND_ROOT "${_FIND_ROOT}" PATH) 10 | if(_FIND_ROOT STREQUAL "/") 11 | set(_FIND_ROOT "") 12 | endif() 13 | 14 | set(MANIFOLD_CROSS_SECTION "@MANIFOLD_CROSS_SECTION@") 15 | set(MANIFOLD_USE_BUILTIN_CLIPPER2 "@MANIFOLD_USE_BUILTIN_CLIPPER2@") 16 | if(MANIFOLD_CROSS_SECTION AND NOT MANIFOLD_USE_BUILTIN_CLIPPER2) 17 | set(Clipper2_ROOT "${_FIND_ROOT}") 18 | find_package(Clipper2 REQUIRED) 19 | endif() 20 | set(MANIFOLD_PAR "@MANIFOLD_PAR@") 21 | set(MANIFOLD_USE_BUILTIN_TBB "@MANIFOLD_USE_BUILTIN_TBB@") 22 | if(MANIFOLD_PAR STREQUAL "ON" AND NOT MANIFOLD_USE_BUILTIN_TBB) 23 | find_package(TBB REQUIRED) 24 | endif() 25 | set(MANIFOLD_EXPORT "@MANIFOLD_EXPORT@") 26 | if(MANIFOLD_EXPORT) 27 | find_package(assimp REQUIRED) 28 | endif(MANIFOLD_EXPORT) 29 | include("${CMAKE_CURRENT_LIST_DIR}/manifoldTargets.cmake") 30 | -------------------------------------------------------------------------------- /cmake/version.h.in: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #define MANIFOLD_VERSION_MAJOR @MANIFOLD_VERSION_MAJOR@ 18 | #define MANIFOLD_VERSION_MINOR @MANIFOLD_VERSION_MINOR@ 19 | #define MANIFOLD_VERSION_PATCH @MANIFOLD_VERSION_PATCH@ 20 | 21 | // Comparable version numbers to check version in code or preprocessing. 22 | // Check if your minimum requirements are met with e.g. 23 | // MANIFOLD_VERSION > MANIFOLD_VERSION_NUMBER(2, 5, 1) 24 | #define MANIFOLD_VERSION_NUMBER(v_major, v_minor, v_patch) \ 25 | (((v_major) * 1000000) + ((v_minor) * 1000) + (v_patch)) 26 | 27 | #define MANIFOLD_VERSION MANIFOLD_VERSION_NUMBER(MANIFOLD_VERSION_MAJOR, \ 28 | MANIFOLD_VERSION_MINOR, \ 29 | MANIFOLD_VERSION_PATCH) 30 | -------------------------------------------------------------------------------- /docs/ParallelBVH.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/docs/ParallelBVH.pdf -------------------------------------------------------------------------------- /docs/RobustBoolean.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/docs/RobustBoolean.pdf -------------------------------------------------------------------------------- /docs/TriangleInterpolation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/docs/TriangleInterpolation.pdf -------------------------------------------------------------------------------- /docs/Triangulation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/docs/Triangulation.pdf -------------------------------------------------------------------------------- /extras/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Manifold Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | if(NOT MANIFOLD_TEST) 16 | return() 17 | endif() 18 | 19 | add_executable(perfTest perf_test.cpp) 20 | target_link_libraries(perfTest manifold) 21 | target_compile_options(perfTest PRIVATE ${MANIFOLD_FLAGS}) 22 | exportbin(perfTest) 23 | 24 | if(MANIFOLD_PAR AND MANIFOLD_EXPORT) 25 | add_executable(man_bench ember_tests/man_bench.cpp) 26 | target_link_libraries(man_bench PRIVATE manifold TBB::tbb) 27 | target_compile_options( 28 | man_bench 29 | PRIVATE ${MANIFOLD_FLAGS} -DCONTROL_PARALLELISM 30 | ) 31 | endif() 32 | 33 | if(MANIFOLD_PAR AND NOT MSVC) 34 | add_executable(stlTest stl_test.cpp) 35 | target_link_libraries(stlTest PRIVATE manifold TBB::tbb) 36 | target_compile_options(stlTest PRIVATE ${MANIFOLD_FLAGS}) 37 | endif() 38 | 39 | add_executable(largeSceneTest large_scene_test.cpp) 40 | target_link_libraries(largeSceneTest manifold) 41 | target_compile_options(largeSceneTest PRIVATE ${MANIFOLD_FLAGS}) 42 | exportbin(largeSceneTest) 43 | 44 | if(MANIFOLD_DEBUG) 45 | add_executable(minimizeTestcase minimize_testcase.cpp) 46 | target_link_libraries( 47 | minimizeTestcase 48 | manifold 49 | $<$:TBB::tbb> 50 | ) 51 | target_compile_options(minimizeTestcase PRIVATE ${MANIFOLD_FLAGS}) 52 | exportbin(minimizeTestcase) 53 | endif() 54 | 55 | if(MANIFOLD_EXPORT) 56 | add_executable(convertFile convert_file.cpp) 57 | target_link_libraries(convertFile manifold manifold) 58 | target_compile_options(convertFile PRIVATE ${MANIFOLD_FLAGS}) 59 | exportbin(convertFile) 60 | endif() 61 | -------------------------------------------------------------------------------- /extras/convert_file.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "manifold/manifold.h" 18 | #include "manifold/meshIO.h" 19 | 20 | using namespace manifold; 21 | 22 | /* 23 | */ 24 | int main(int argc, char** argv) { 25 | if (argc < 2) { 26 | std::cout << "Specify an input filename." << std::endl; 27 | return 1; 28 | } 29 | 30 | manifold::ManifoldParams().verbose = true; 31 | 32 | const std::string filename(argv[1]); 33 | 34 | MeshGL input = ImportMesh(filename); 35 | std::cout << input.NumVert() << " vertices, " << input.NumTri() 36 | << " triangles" << std::endl; 37 | 38 | if (input.Merge()) 39 | std::cout << filename << " is not manifold, attempting to fix." 40 | << std::endl; 41 | 42 | const Manifold manifold(input); 43 | if (manifold.Status() != Manifold::Error::NoError) { 44 | std::cout << "Could not make a valid manifold, error: " 45 | << (int)manifold.Status() << std::endl; 46 | return 2; 47 | } 48 | 49 | const std::vector parts = manifold.Decompose(); 50 | std::cout << parts.size() << " objects:" << std::endl; 51 | for (const Manifold& part : parts) { 52 | std::cout << part.NumVert() << " vertices, " << part.NumTri() 53 | << " triangles, volume = " << part.Volume() 54 | << ", surface area = " << part.SurfaceArea() << std::endl; 55 | } 56 | 57 | if (argc == 3) { 58 | std::string outName = argv[2]; 59 | 60 | std::cout << "Writing " << outName << std::endl; 61 | 62 | ExportMesh(outName, manifold.GetMeshGL(), {}); 63 | } 64 | 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /extras/ember_tests/README.md: -------------------------------------------------------------------------------- 1 | # Ember Benchmark 2 | 3 | The paper EMBER: Exact Mesh Boolean via Efficient & Robust 4 | Local Arrangments (see https://dl.acm.org/doi/10.1145/3528223.3530181) 5 | has supplemental material that describes 1000 tests using meshes 6 | from the Thinki10K dataset (see https://ten-thousand-models.appspot.com/) 7 | 8 | ## Dataset 9 | 10 | Download the dataset from https://ten-thousand-models.appspot.com , 11 | decompress and put the `raw_meshes` directory inside `testfiles`. 12 | 13 | ## Running the benchmark 14 | 15 | 1. Download the dataset. 16 | 2. Build the binary with `MANIFOLD_PAR` and `MANIFOLD_EXPORT` enabled. The 17 | binary should be inside `build/extras` (relative to the project root). 18 | 3. Run `python do_ember_tests.py`. This will take 10 minutes to an hour 19 | depending on your machine. At the end, a `benchmark.csv` file will be 20 | generated. 21 | 4. Run `python analyze_ember_tests.py` to analyze the result. 22 | 23 | ## Files 24 | 25 | - `man_bench` is a binary that reads two .stl files and has corresponding 26 | transform arguments. It converts those to MeshGL, then to Manifold, 27 | then runs a boolean difference, and converts the result back to MeshGL. 28 | It times the total time take to do this (not including loading the 29 | stl files into MeshGL format), and appends a line to a benchmarks.csv 30 | file with the argument and timing information. 31 | - `testfiles/test_file/ember-benchmark-cases.json`: JSON file describing each 32 | test case. 33 | - `do_ember_tests.py` reads the description of the files and transforms 34 | from the supplemental material of the Ember paper, and executes 35 | man_bench on each pair. This creates a benchmark.csv file (remember 36 | to remove that file again if you want a clean new run of data). 37 | - `analyze_ember_tests.py` reads the benchmark.csv file and calculates 38 | statistics and does a plot. 39 | - `bug-case.json` is a single test case that takes abnormally long. 40 | - `m3max_benchmarks.csv` is the benchmarks file gotten by running on a 41 | Macbook Pro M3 Max (12 performance cores, 4 efficiency cores). 42 | 43 | -------------------------------------------------------------------------------- /extras/ember_tests/analyze_ember_tests.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import sys 3 | import copy 4 | import math 5 | 6 | # comment this out and set do_plot to False if don't have matplotlib 7 | import matplotlib.pyplot as plt 8 | 9 | do_plot = True 10 | 11 | benchmark_csv_file = "benchmark.csv" 12 | 13 | if len(sys.argv) > 1: 14 | benchmark_csv_file = sys.argv[1] 15 | 16 | class TimeSeries: 17 | def __init__(self, name, csv_key, label): 18 | self.name = name 19 | self.csv_key = csv_key 20 | self.label = label 21 | self.series = [] 22 | self.average = 0.0 23 | self.min = 0.0 24 | self.max = 0.0 25 | 26 | def set_stats(self): 27 | n = len(self.series) 28 | self.average = sum(self.series) / n if n > 0 else 0.0 29 | self.min = min(self.series) if n > 0 else 0.0 30 | self.max = max(self.series) if n > 0 else 0.0 31 | 32 | ts_total = TimeSeries('total', 'total (ms)', 'Total time') 33 | ts_to_man = TimeSeries('to_manifold', 'to manifold (ms)', 'Converting MeshGL to Manifold') 34 | ts_bool = TimeSeries('boolean', 'boolean (ms)', 'Manifold boolean') 35 | ts_from_man = TimeSeries('from_manifold', 'from manifold (ms)', 'Converting Manifold to MeshGL') 36 | 37 | ts_to_analyze = [ts_total, ts_to_man, ts_bool, ts_from_man] 38 | # ts_to_plot needs to be a subset of ts_to_analyze 39 | ts_to_plot = [ts_total] 40 | threads_key = 'threads' 41 | 42 | def get_csv_data(filename): 43 | ans = [] 44 | with open(filename, newline='') as csvfile: 45 | reader = csv.DictReader(csvfile) 46 | for row in reader: 47 | ans.append(row) 48 | return ans 49 | 50 | data = get_csv_data(benchmark_csv_file) 51 | 52 | if not data or len(data) == 0: 53 | print("no data") 54 | sys.exit(0) 55 | 56 | if threads_key not in data[0] or any([ts.csv_key not in data[0] for ts in ts_to_analyze]): 57 | print("data[0]", data[0]) 58 | print("threads key", threads_key in data[0]) 59 | for ts in ts_to_analyze: 60 | print(ts.csv_key, ts.csv_key in data[0]) 61 | print("missing a needed column in csv file") 62 | sys.exit(0) 63 | 64 | thread_values_set = set([int(row[threads_key]) for row in data]) 65 | thread_values = list(thread_values_set) 66 | thread_values.sort() 67 | max_threads = max(thread_values) 68 | ts_by_threads = [copy.deepcopy(ts_to_analyze) for _ in range(max_threads + 1)] 69 | for t in thread_values: 70 | for ts in ts_by_threads[t]: 71 | ts.series = [float(row[ts.csv_key]) \ 72 | for row in data if int(row[threads_key]) == t] 73 | ts.set_stats() 74 | 75 | 76 | def print_stats(): 77 | for t in thread_values: 78 | if len(thread_values) > 1: 79 | print("\nThreads=", t) 80 | for ts in ts_by_threads[t]: 81 | print(ts.label) 82 | print(" avg", ":", "%.3f" % ts.average) 83 | print(" min", ":", "%.3f" % ts.min) 84 | print(" max", ":", "%.3f" % ts.max) 85 | 86 | 87 | def plot_hists_by_ts(): 88 | nthreads = len(thread_values) 89 | nts = len(ts_to_plot) 90 | fig, axs = plt.subplots(nrows=nts, sharex = True) 91 | # plt.xscale('log') 92 | fig.set_figheight(6.0) 93 | fig.set_figwidth(8.0) 94 | for ts_index in range(nts): 95 | ts_name = ts_to_plot[ts_index].name 96 | ts_label = ts_to_plot[ts_index].label 97 | ax = axs[ts_index] if nts > 1 else axs 98 | all_data = [] 99 | for threads in thread_values: 100 | tss = ts_by_threads[threads] 101 | ts = next((x for x in tss if x.name == ts_name), None) 102 | if not ts: 103 | continue 104 | all_data.append(ts.series) 105 | ax.violinplot(all_data, showmeans=True, showmedians = False, 106 | showextrema = False, orientation = 'horizontal') 107 | ax.set_title(ts_label) 108 | ax.set_yticks([i+1 for i in range(nthreads)], 109 | [str(k) for k in thread_values]) 110 | ax.set_ylim(0.25, nthreads + 0.75) 111 | ax.set_xlim(0.0, 300.0) 112 | ax.set_ylabel("threads") 113 | ax.set_xlabel("ms") 114 | plt.show() 115 | 116 | print_stats() 117 | 118 | if do_plot: 119 | plot_hists_by_ts() 120 | -------------------------------------------------------------------------------- /extras/ember_tests/bug-case.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id_a": 102250, 4 | "id_b": 49378, 5 | "transform_a": { 6 | "col0": { 7 | "x": -0.00199867, 8 | "y": -0.00311826, 9 | "z": 0.00158026, 10 | "w": 0 11 | }, 12 | "col1": { 13 | "x": 0.00304193, 14 | "y": -0.000654351, 15 | "z": 0.00255616, 16 | "w": 0 17 | }, 18 | "col2": { 19 | "x": -0.00172262, 20 | "y": 0.00246247, 21 | "z": 0.00268035, 22 | "w": 0 23 | }, 24 | "col3": { 25 | "x": 0.0137561, 26 | "y": -0.0282207, 27 | "z": 0.000198535, 28 | "w": 1 29 | } 30 | }, 31 | "transform_b": { 32 | "col0": { 33 | "x": -0.00447804, 34 | "y": 0.00437019, 35 | "z": -0.0048547, 36 | "w": 0 37 | }, 38 | "col1": { 39 | "x": -0.00508236, 40 | "y": 0.0013663, 41 | "z": 0.00591797, 42 | "w": 0 43 | }, 44 | "col2": { 45 | "x": 0.00410321, 46 | "y": 0.00646174, 47 | "z": 0.002032, 48 | "w": 0 49 | }, 50 | "col3": { 51 | "x": -0.241088, 52 | "y": -0.381816, 53 | "z": -0.120358, 54 | "w": 1 55 | } 56 | } 57 | } 58 | ] 59 | -------------------------------------------------------------------------------- /extras/ember_tests/do_ember_tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import subprocess 4 | import sys 5 | import pathlib 6 | 7 | ember_tests = pathlib.Path(__file__).parent 8 | root = ember_tests.parent.parent 9 | 10 | meshdir = ember_tests.joinpath("testfiles/raw_meshes") 11 | testspecfile = ember_tests.joinpath("testfiles/ember-benchmark-cases.json") 12 | man_bench = root.joinpath("build/extras/man_bench") 13 | 14 | threads_cases = [1, 2, 4, 6, 8, 10, 12, 14, 16] 15 | 16 | meshfiles = os.listdir(meshdir) 17 | 18 | id_to_file = {} 19 | for f in meshfiles: 20 | base, ext = os.path.splitext(f) 21 | if base and ext: 22 | try: 23 | baseid = int(base) 24 | id_to_file[baseid] = f 25 | except: 26 | pass 27 | 28 | 29 | def file_for_id(id): 30 | if id in id_to_file: 31 | file = id_to_file[id] 32 | fullfile = meshdir.joinpath(file) 33 | return fullfile 34 | return None 35 | 36 | 37 | with open(testspecfile, "r") as file: 38 | spec_data = json.load(file) 39 | 40 | for threads in threads_cases: 41 | spec_index = 0 42 | for spec in spec_data: 43 | id1 = spec["id_a"] 44 | id2 = spec["id_b"] 45 | tr1 = spec["transform_a"] 46 | tr2 = spec["transform_b"] 47 | file1 = file_for_id(id1) 48 | if not file1: 49 | print("no test file for ", id1) 50 | file2 = file_for_id(id2) 51 | if not file2: 52 | print("no test file for ", id2) 53 | args = [man_bench, file1, file2] 54 | for i in [1, 2]: 55 | args.append("-t%d" % i) 56 | tr = tr1 if i == 1 else tr2 57 | for col in ["col0", "col1", "col2", "col3"]: 58 | for row in ["x", "y", "z"]: 59 | v = tr[col][row] 60 | args.append("%f" % v) 61 | if threads != 0: 62 | args.append("--threads") 63 | args.append("%d" % threads) 64 | print("test", spec_index, "threads", threads) 65 | cp = subprocess.run(args) 66 | if cp.returncode != 0: 67 | print("return code", cp.returncode) 68 | sys.exit(1) 69 | spec_index = spec_index + 1 70 | -------------------------------------------------------------------------------- /extras/large_scene_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include "manifold/manifold.h" 19 | 20 | using namespace manifold; 21 | 22 | /* 23 | Build & execute with the following command: 24 | 25 | ( mkdir -p build && cd build && \ 26 | cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_PAR=ON .. && \ 27 | make -j && \ 28 | time ./extras/largeSceneTest 50 ) 29 | */ 30 | int main(int argc, char** argv) { 31 | int n = 20; 32 | if (argc == 2) n = atoi(argv[1]); 33 | 34 | std::cout << "n = " << n << std::endl; 35 | 36 | auto start = std::chrono::high_resolution_clock::now(); 37 | Manifold scene; 38 | 39 | for (int i = 0; i < n; ++i) { 40 | for (int j = 0; j < n; ++j) { 41 | for (int k = 0; k < n; ++k) { 42 | if (i == 0 && j == 0 && k == 0) continue; 43 | 44 | Manifold sphere = Manifold::Sphere(1).Translate(vec3(i, j, k)); 45 | scene = scene.Boolean(sphere, OpType::Add); 46 | } 47 | } 48 | } 49 | scene.NumTri(); 50 | auto end = std::chrono::high_resolution_clock::now(); 51 | std::chrono::duration elapsed = end - start; 52 | std::cout << "nTri = " << scene.NumTri() << ", time = " << elapsed.count() 53 | << " sec" << std::endl; 54 | } 55 | -------------------------------------------------------------------------------- /extras/perf_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include "manifold/manifold.h" 19 | 20 | using namespace manifold; 21 | 22 | int main(int argc, char** argv) { 23 | for (int i = 0; i < 8; ++i) { 24 | Manifold sphere = Manifold::Sphere(1, (8 << i) * 4); 25 | Manifold sphere2 = sphere.Translate(vec3(0.5)); 26 | auto start = std::chrono::high_resolution_clock::now(); 27 | Manifold diff = sphere - sphere2; 28 | diff.NumTri(); 29 | auto end = std::chrono::high_resolution_clock::now(); 30 | std::chrono::duration elapsed = end - start; 31 | std::cout << "nTri = " << sphere.NumTri() << ", time = " << elapsed.count() 32 | << " sec" << std::endl; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "clipper2-src": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1739063104, 7 | "narHash": "sha256-HlqSh/b05pi1I5iS2QjBUagseUJcQ7ehEgfwJNx39NI=", 8 | "owner": "AngusJohnson", 9 | "repo": "Clipper2", 10 | "rev": "c86619cf4feb470a91464b06cfdaad0ca6012914", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "AngusJohnson", 15 | "repo": "Clipper2", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-utils": { 20 | "inputs": { 21 | "systems": "systems" 22 | }, 23 | "locked": { 24 | "lastModified": 1726560853, 25 | "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", 26 | "owner": "numtide", 27 | "repo": "flake-utils", 28 | "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "numtide", 33 | "repo": "flake-utils", 34 | "type": "github" 35 | } 36 | }, 37 | "gersemi-src": { 38 | "flake": false, 39 | "locked": { 40 | "lastModified": 1729957417, 41 | "narHash": "sha256-t9W27lwNKRFAraynAGEawFb1qCW9/b3RCm/jeb9zJXg=", 42 | "owner": "BlankSpruce", 43 | "repo": "gersemi", 44 | "rev": "27f279333ec3308f9910d53fe4dd69d622e4bc55", 45 | "type": "github" 46 | }, 47 | "original": { 48 | "owner": "BlankSpruce", 49 | "ref": "0.17.0", 50 | "repo": "gersemi", 51 | "type": "github" 52 | } 53 | }, 54 | "gtest-src": { 55 | "flake": false, 56 | "locked": { 57 | "lastModified": 1690989893, 58 | "narHash": "sha256-t0RchAHTJbuI5YW4uyBPykTvcjy90JW9AOPNjIhwh6U=", 59 | "owner": "google", 60 | "repo": "googletest", 61 | "rev": "f8d7d77c06936315286eb55f8de22cd23c188571", 62 | "type": "github" 63 | }, 64 | "original": { 65 | "owner": "google", 66 | "ref": "v1.14.0", 67 | "repo": "googletest", 68 | "type": "github" 69 | } 70 | }, 71 | "nixpkgs": { 72 | "locked": { 73 | "lastModified": 1739214665, 74 | "narHash": "sha256-26L8VAu3/1YRxS8MHgBOyOM8xALdo6N0I04PgorE7UM=", 75 | "owner": "NixOS", 76 | "repo": "nixpkgs", 77 | "rev": "64e75cd44acf21c7933d61d7721e812eac1b5a0a", 78 | "type": "github" 79 | }, 80 | "original": { 81 | "id": "nixpkgs", 82 | "ref": "nixos-unstable", 83 | "type": "indirect" 84 | } 85 | }, 86 | "onetbb-src": { 87 | "flake": false, 88 | "locked": { 89 | "lastModified": 1730395391, 90 | "narHash": "sha256-XOlC1+rf65oEGKDba9N561NuFo1YJhn3Q1CTGtvkn7A=", 91 | "owner": "oneapi-src", 92 | "repo": "oneTBB", 93 | "rev": "0c0ff192a2304e114bc9e6557582dfba101360ff", 94 | "type": "github" 95 | }, 96 | "original": { 97 | "owner": "oneapi-src", 98 | "ref": "v2022.0.0", 99 | "repo": "oneTBB", 100 | "type": "github" 101 | } 102 | }, 103 | "root": { 104 | "inputs": { 105 | "clipper2-src": "clipper2-src", 106 | "flake-utils": "flake-utils", 107 | "gersemi-src": "gersemi-src", 108 | "gtest-src": "gtest-src", 109 | "nixpkgs": "nixpkgs", 110 | "onetbb-src": "onetbb-src" 111 | } 112 | }, 113 | "systems": { 114 | "locked": { 115 | "lastModified": 1681028828, 116 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 117 | "owner": "nix-systems", 118 | "repo": "default", 119 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 120 | "type": "github" 121 | }, 122 | "original": { 123 | "owner": "nix-systems", 124 | "repo": "default", 125 | "type": "github" 126 | } 127 | } 128 | }, 129 | "root": "root", 130 | "version": 7 131 | } 132 | -------------------------------------------------------------------------------- /include/manifold/meshIO.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | #include 17 | 18 | #include "manifold/manifold.h" 19 | 20 | namespace manifold { 21 | 22 | /** @addtogroup MeshIO 23 | * @ingroup Optional 24 | * @brief 3D model file I/O based on Assimp 25 | * @{ 26 | */ 27 | 28 | /** 29 | * PBR material properties for GLB/glTF files. 30 | */ 31 | struct Material { 32 | /// Roughness value between 0 (shiny) and 1 (matte). 33 | double roughness = 0.2; 34 | /// Metalness value, generally either 0 (dielectric) or 1 (metal). 35 | double metalness = 1; 36 | /// Color (RGB) multiplier to apply to the whole mesh (each value between 0 37 | /// and 1). 38 | vec3 color = vec3(1.0); 39 | /// Alpha multiplier to apply to the whole mesh (each value between 0 40 | /// and 1). 41 | double alpha = 1.0; 42 | /// Gives the property index where the first normal channel 43 | /// can be found. 0 indicates the first three property channels following 44 | /// position. A negative value does not save normals. 45 | int normalIdx = -1; 46 | /// Gives the property index where the first color channel 47 | /// can be found. 0 indicates the first three property channels following 48 | /// position. A negative value does not save vertex colors. 49 | int colorIdx = -1; 50 | /// Gives the property index where the alpha channel 51 | /// can be found. 0 indicates the first property channel following 52 | /// position. A negative value does not save vertex alpha. 53 | int alphaIdx = -1; 54 | }; 55 | 56 | /** 57 | * These options only currently affect .glb and .gltf files. 58 | */ 59 | struct ExportOptions { 60 | /// When false, vertex normals are exported, causing the mesh to appear smooth 61 | /// through normal interpolation. 62 | bool faceted = true; 63 | /// PBR material properties. 64 | Material mat = {}; 65 | }; 66 | 67 | MeshGL ImportMesh(const std::string& filename, bool forceCleanup = false); 68 | 69 | void ExportMesh(const std::string& filename, const MeshGL& mesh, 70 | const ExportOptions& options); 71 | /** @} */ 72 | } // namespace manifold 73 | -------------------------------------------------------------------------------- /include/manifold/optional_assert.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #ifdef MANIFOLD_DEBUG 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | /** @addtogroup Debug 25 | * @{ 26 | */ 27 | struct userErr : public virtual std::runtime_error { 28 | using std::runtime_error::runtime_error; 29 | }; 30 | struct topologyErr : public virtual std::runtime_error { 31 | using std::runtime_error::runtime_error; 32 | }; 33 | struct geometryErr : public virtual std::runtime_error { 34 | using std::runtime_error::runtime_error; 35 | }; 36 | using logicErr = std::logic_error; 37 | #endif 38 | 39 | #if defined(MANIFOLD_ASSERT) && defined(MANIFOLD_DEBUG) 40 | 41 | template 42 | void AssertFail(const char* file, int line, const char* cond, const char* msg) { 43 | std::ostringstream output; 44 | output << "Error in file: " << file << " (" << line << "): \'" << cond 45 | << "\' is false: " << msg; 46 | throw Ex(output.str()); 47 | } 48 | 49 | template 50 | void AssertFail(const char* file, int line, const std::string& cond, 51 | const std::string& msg) { 52 | std::ostringstream output; 53 | output << "Error in file: " << file << " (" << line << "): \'" << cond 54 | << "\' is false: " << msg; 55 | throw Ex(output.str()); 56 | } 57 | 58 | // DEBUG_ASSERT is slightly slower due to the function call, but gives more 59 | // detailed info. 60 | #define DEBUG_ASSERT(condition, EX, msg) \ 61 | if (!(condition)) AssertFail(__FILE__, __LINE__, #condition, msg); 62 | // ASSERT has almost no overhead, so better to use for frequent calls like 63 | // vector bounds checking. 64 | #define ASSERT(condition, EX) \ 65 | if (!(condition)) throw(EX); 66 | #else 67 | #define DEBUG_ASSERT(condition, EX, msg) 68 | #define ASSERT(condition, EX) 69 | #endif 70 | /** @} */ 71 | -------------------------------------------------------------------------------- /include/manifold/polygon.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | #include "manifold/common.h" 17 | 18 | namespace manifold { 19 | 20 | /** @addtogroup Structs 21 | * @{ 22 | */ 23 | 24 | /** 25 | * @brief Polygon vertex. 26 | */ 27 | struct PolyVert { 28 | /// X-Y position 29 | vec2 pos; 30 | /// ID or index into another vertex vector 31 | int idx; 32 | }; 33 | 34 | /** 35 | * @brief Single polygon contour, wound CCW, with indices. First and last point 36 | * are implicitly connected. Should ensure all input is 37 | * [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid). 38 | */ 39 | using SimplePolygonIdx = std::vector; 40 | 41 | /** 42 | * @brief Set of indexed polygons with holes. Order of contours is arbitrary. 43 | * Can contain any depth of nested holes and any number of separate polygons. 44 | * Should ensure all input is 45 | * [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid). 46 | */ 47 | using PolygonsIdx = std::vector; 48 | /** @} */ 49 | 50 | /** @addtogroup Triangulation 51 | * @ingroup Core 52 | * @brief Polygon triangulation 53 | * @{ 54 | */ 55 | std::vector TriangulateIdx(const PolygonsIdx& polys, double epsilon = -1, 56 | bool allowConvex = true); 57 | 58 | std::vector Triangulate(const Polygons& polygons, double epsilon = -1, 59 | bool allowConvex = true); 60 | /** @} */ 61 | } // namespace manifold 62 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "manifold3d" 3 | version = "3.1.1" 4 | authors = [ 5 | { name="Emmett Lalish", email="elalish@gmail.com" }, 6 | ] 7 | description = "Library for geometric robustness" 8 | readme = "README.md" 9 | classifiers = [ 10 | "Development Status :: 5 - Production/Stable", 11 | "License :: OSI Approved :: Apache Software License", 12 | "Operating System :: OS Independent", 13 | "Programming Language :: C++", 14 | "Topic :: Multimedia :: Graphics :: 3D Modeling", 15 | ] 16 | requires-python = ">=3.8" 17 | 18 | dependencies = [ 19 | "numpy; python_version<'3.12'", 20 | "numpy>=1.26.0b1; python_version>='3.12'" 21 | ] 22 | 23 | [project.urls] 24 | "Homepage" = "https://github.com/elalish/manifold" 25 | "Bug Tracker" = "https://github.com/elalish/manifold/issues" 26 | 27 | [build-system] 28 | requires = [ 29 | "nanobind>=1.8.0,<3.0.0", 30 | "scikit-build-core", 31 | ] 32 | build-backend = "scikit_build_core.build" 33 | 34 | [tool.scikit-build] 35 | cmake.version = ">=3.18" 36 | ninja.version = ">=1.11" 37 | sdist.exclude = [ 38 | ".github", 39 | "bindings/c", 40 | "bindings/wasm", 41 | "docs", 42 | "extras", 43 | "samples", 44 | "test", 45 | "oneTBB", # we may have this when we build with cibuildwheel 46 | ] 47 | wheel.packages = ["manifold3d"] 48 | cmake.args = ["-DMANIFOLD_PYBIND=ON", "-DMANIFOLD_CBIND=OFF", "-DMANIFOLD_PAR=ON", "-DMANIFOLD_TEST=OFF", "-DBUILD_SHARED_LIBS=OFF"] 49 | install.components = ["bindings"] 50 | 51 | [tool.cibuildwheel] 52 | build-frontend = "build" 53 | test-requires = ["trimesh", "pytest"] 54 | test-command = "python {project}/bindings/python/examples/run_all.py" 55 | # Setuptools bug causes collision between pypy and cpython artifacts 56 | manylinux-x86_64-image = "manylinux_2_28" 57 | musllinux-x86_64-image = "musllinux_1_2" 58 | skip = ["*-win32", "*_i686", "pp*", "*-musllinux*"] 59 | 60 | # only clone TBB once 61 | before-all = "git clone --depth 1 --branch v2021.10.0 https://github.com/oneapi-src/oneTBB.git" 62 | [tool.cibuildwheel.config-settings] 63 | "cmake.define.FETCHCONTENT_SOURCE_DIR_TBB" = "./oneTBB" 64 | "cmake.define.FETCHCONTENT_UPDATES_DISCONNECTED" = "ON" 65 | 66 | [tool.cibuildwheel.macos] 67 | environment = "MACOSX_DEPLOYMENT_TARGET=10.14" 68 | 69 | [tool.cibuildwheel.windows] 70 | before-build = "pip install delvewheel" 71 | repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" 72 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = 3 | bindings/python/examples -------------------------------------------------------------------------------- /samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The Manifold Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | if(NOT MANIFOLD_TEST) 16 | return() 17 | endif() 18 | 19 | add_library( 20 | samples 21 | src/menger_sponge.cpp 22 | src/rounded_frame.cpp 23 | src/scallop.cpp 24 | src/tet_puzzle.cpp 25 | src/gyroid_module.cpp 26 | src/condensed_matter.cpp 27 | ) 28 | if(MANIFOLD_CROSS_SECTION) 29 | target_sources(samples PUBLIC src/bracelet.cpp src/knot.cpp) 30 | endif() 31 | 32 | target_include_directories(samples PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) 33 | 34 | target_link_libraries(samples PUBLIC manifold) 35 | 36 | target_compile_options(samples PRIVATE ${MANIFOLD_FLAGS}) 37 | 38 | exportlib(samples) 39 | -------------------------------------------------------------------------------- /samples/include/samples.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | #include "manifold/manifold.h" 17 | 18 | namespace manifold { 19 | 20 | /** @addtogroup Samples 21 | * @brief Examples of usage and interesting designs 22 | * 23 | * These are mostly 3D-printable designs I've invented over the years, 24 | * translated from their original OpenSCAD to C++ to demonstrate the usage of 25 | * this library. You can find the originals here: 26 | * http://www.thingiverse.com/emmett These also each have tests you can find in 27 | * test/samples_test.cpp, which have nice parameter choices for making some of 28 | * the specific designs I print. While the source code is under the Apache 29 | * License above, I license all of my designs (the output of those tests if you 30 | * uncomment the export lines) under CC-BY-SA: 31 | * https://creativecommons.org/licenses/by-sa/2.0/, which means you're welcome 32 | * to print and sell them, so long as you attribute the design to Emmett Lalish 33 | * and share any derivative works under the same license. 34 | * @{ 35 | */ 36 | 37 | Manifold TorusKnot(int p, int q, double majorRadius, double minorRadius, 38 | double threadRadius, int circularSegments = 0, 39 | int linearSegments = 0); 40 | 41 | Manifold StretchyBracelet(double radius = 30.0, double height = 8.0, 42 | double width = 15.0, double thickness = 0.4, 43 | int nDecor = 20, int nCut = 27, int nDivision = 30); 44 | 45 | Manifold MengerSponge(int n = 3); 46 | 47 | Manifold RoundedFrame(double edgeLength, double radius, 48 | int circularSegments = 0); 49 | 50 | Manifold TetPuzzle(double edgeLength, double gap, int nDivisions); 51 | 52 | Manifold Scallop(); 53 | 54 | Manifold GyroidModule(double size = 20, int n = 20); 55 | 56 | Manifold CondensedMatter(int fn = 16); 57 | /** @} */ // end of Samples 58 | } // namespace manifold 59 | -------------------------------------------------------------------------------- /samples/models/apollo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/apollo.png -------------------------------------------------------------------------------- /samples/models/apollo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/apollo.webp -------------------------------------------------------------------------------- /samples/models/apolloPan.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/apolloPan.webp -------------------------------------------------------------------------------- /samples/models/apollo_exterior-150k-4096.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/apollo_exterior-150k-4096.glb -------------------------------------------------------------------------------- /samples/models/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/favicon.png -------------------------------------------------------------------------------- /samples/models/mengerSponge192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/mengerSponge192.png -------------------------------------------------------------------------------- /samples/models/mengerSponge3.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/mengerSponge3.glb -------------------------------------------------------------------------------- /samples/models/mengerSponge3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/mengerSponge3.png -------------------------------------------------------------------------------- /samples/models/mengerSponge3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/mengerSponge3.webp -------------------------------------------------------------------------------- /samples/models/mengerSponge4.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/mengerSponge4.glb -------------------------------------------------------------------------------- /samples/models/mengerSponge512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/mengerSponge512.png -------------------------------------------------------------------------------- /samples/models/mengerSponge64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/mengerSponge64.png -------------------------------------------------------------------------------- /samples/models/mengerSpongeSquare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/mengerSpongeSquare.png -------------------------------------------------------------------------------- /samples/models/perf.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/perf.glb -------------------------------------------------------------------------------- /samples/models/perf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/perf.png -------------------------------------------------------------------------------- /samples/models/perf.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/perf.webp -------------------------------------------------------------------------------- /samples/models/rounding.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/rounding.glb -------------------------------------------------------------------------------- /samples/models/rounding.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/rounding.webp -------------------------------------------------------------------------------- /samples/models/scallop.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/scallop.glb -------------------------------------------------------------------------------- /samples/models/scallop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/scallop.png -------------------------------------------------------------------------------- /samples/models/scallop.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/scallop.webp -------------------------------------------------------------------------------- /samples/models/scallopFacets.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/scallopFacets.glb -------------------------------------------------------------------------------- /samples/models/scallopFacets.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/samples/models/scallopFacets.webp -------------------------------------------------------------------------------- /samples/src/bracelet.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "manifold/cross_section.h" 16 | #include "samples.h" 17 | 18 | namespace { 19 | 20 | using namespace manifold; 21 | 22 | Manifold Base(double width, double radius, double decorRadius, 23 | double twistRadius, int nDecor, double innerRadius, 24 | double outerRadius, double cut, int nCut, int nDivision) { 25 | Manifold base = Manifold::Cylinder(width, radius + twistRadius / 2); 26 | 27 | CrossSection circle = 28 | CrossSection::Circle(decorRadius, nDivision).Translate({twistRadius, 0}); 29 | Manifold decor = Manifold::Extrude(circle.ToPolygons(), width, nDivision, 180) 30 | .Scale({1.0, 0.5, 1.0}) 31 | .Translate({0.0, radius, 0.0}); 32 | 33 | for (int i = 0; i < nDecor; ++i) { 34 | base += decor.Rotate(0, 0, (360.0 / nDecor) * i); 35 | } 36 | 37 | Polygons stretch(1); 38 | double dPhiRad = 2 * kPi / nCut; 39 | vec2 p0(outerRadius, 0.0); 40 | vec2 p1(innerRadius, -cut); 41 | vec2 p2(innerRadius, cut); 42 | for (int i = 0; i < nCut; ++i) { 43 | stretch[0].push_back(la::rot(dPhiRad * i, p0)); 44 | stretch[0].push_back(la::rot(dPhiRad * i, p1)); 45 | stretch[0].push_back(la::rot(dPhiRad * i, p2)); 46 | stretch[0].push_back(la::rot(dPhiRad * i, p0)); 47 | } 48 | 49 | base = Manifold::Extrude(stretch, width) ^ base; 50 | // Remove extra edges in coplanar faces 51 | base = base.AsOriginal(); 52 | 53 | return base; 54 | } 55 | } // namespace 56 | 57 | namespace manifold { 58 | 59 | /** 60 | * My Stretchy Bracelet: this is one of my most popular designs, largely because 61 | * it's quick and easy to 3D print. The defaults are picked to work well; change 62 | * the radius to fit your wrist. Changing the other values too much may break 63 | * the design. 64 | * 65 | * @param radius The overall size; the radius left for your wrist is roughly 66 | * radius - height. 67 | * @param height Thickness of the bracelet around your wrist. 68 | * @param width The length along your arm (the height of the print). 69 | * @param thickness The width of the material, which should be equal to your 70 | * printer's nozzle diameter. 71 | * @param nDecor The number of twisty shapes around the outside. 72 | * @param nCut The number of cuts that enable stretching. 73 | * @param nDivision the number of divisions along the width. 74 | */ 75 | Manifold StretchyBracelet(double radius, double height, double width, 76 | double thickness, int nDecor, int nCut, 77 | int nDivision) { 78 | double twistRadius = kPi * radius / nDecor; 79 | double decorRadius = twistRadius * 1.5; 80 | double outerRadius = radius + (decorRadius + twistRadius) * 0.5; 81 | double innerRadius = outerRadius - height; 82 | double cut = 0.5 * (kPi * 2 * innerRadius / nCut - thickness); 83 | double adjThickness = 0.5 * thickness * height / cut; 84 | 85 | return Base(width, radius, decorRadius, twistRadius, nDecor, 86 | innerRadius + thickness, outerRadius + adjThickness, 87 | cut - adjThickness, nCut, nDivision) - 88 | Base(width, radius - thickness, decorRadius, twistRadius, nDecor, 89 | innerRadius, outerRadius + 3 * adjThickness, cut, nCut, 90 | nDivision); 91 | } 92 | } // namespace manifold 93 | -------------------------------------------------------------------------------- /samples/src/gyroid_module.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "manifold/manifold.h" 16 | #include "samples.h" 17 | 18 | namespace { 19 | using namespace manifold; 20 | 21 | struct Gyroid { 22 | double operator()(vec3 p) const { 23 | p -= kPi / 4; 24 | return cos(p.x) * sin(p.y) + cos(p.y) * sin(p.z) + cos(p.z) * sin(p.x); 25 | } 26 | }; 27 | 28 | Manifold RhombicDodecahedron(double size) { 29 | Manifold box = Manifold::Cube(size * la::sqrt(2.0) * vec3(1, 1, 2), true); 30 | Manifold result = box.Rotate(90, 45) ^ box.Rotate(90, 45, 90); 31 | return result ^ box.Rotate(0, 0, 45); 32 | } 33 | 34 | } // namespace 35 | 36 | namespace manifold { 37 | 38 | /** 39 | * Creates a rhombic dodecahedral module of a gyroid manifold, which can be 40 | * assembled together to tile space continuously. This one is designed to be 41 | * 3D-printable, as it is oriented with minimal overhangs. This sample 42 | * demonstrates the use of a Signed Distance Function (SDF) to create smooth, 43 | * complex manifolds. 44 | * 45 | * @param size Creates a module scaled to this dimension between opposite faces. 46 | * @param n The number of divisions for SDF evaluation across the gyroid's 47 | * period. 48 | */ 49 | Manifold GyroidModule(double size, int n) { 50 | auto gyroid = [&](double level) { 51 | const double period = kTwoPi; 52 | return Manifold::LevelSet(Gyroid(), {vec3(-period), vec3(period)}, 53 | period / n, level) 54 | .Scale(vec3(size / period)); 55 | }; 56 | 57 | Manifold result = (RhombicDodecahedron(size) ^ gyroid(-0.4)) - gyroid(0.4); 58 | 59 | return result.Rotate(-45, 0, 90).Translate({0, 0, size / la::sqrt(2.0)}); 60 | } 61 | } // namespace manifold 62 | -------------------------------------------------------------------------------- /samples/src/knot.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "manifold/cross_section.h" 16 | #include "samples.h" 17 | 18 | namespace { 19 | 20 | int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } 21 | } // namespace 22 | 23 | namespace manifold { 24 | 25 | /** 26 | * Creates a classic torus knot, defined as a string wrapping periodically 27 | * around the surface of an imaginary donut. If p and q have a common factor 28 | * then you will get multiple separate, interwoven knots. This is an example of 29 | * using the Manifold.Warp() method, thus avoiding any handling of triangles. 30 | * 31 | * @param p The number of times the thread passes through the donut hole. 32 | * @param q The number of times the thread circles the donut. 33 | * @param majorRadius Radius of the interior of the imaginary donut. 34 | * @param minorRadius Radius of the small cross-section of the imaginary donut. 35 | * @param threadRadius Radius of the small cross-section of the actual object. 36 | * @param circularSegments Number of linear segments making up the threadRadius 37 | * circle. Default is Quality.GetCircularSegments(). 38 | * @param linearSegments Number of segments along the length of the knot. 39 | * Default makes roughly square facets. 40 | */ 41 | Manifold TorusKnot(int p, int q, double majorRadius, double minorRadius, 42 | double threadRadius, int circularSegments, 43 | int linearSegments) { 44 | int kLoops = gcd(p, q); 45 | p /= kLoops; 46 | q /= kLoops; 47 | int n = circularSegments > 2 ? circularSegments 48 | : Quality::GetCircularSegments(threadRadius); 49 | int m = 50 | linearSegments > 2 ? linearSegments : n * q * majorRadius / threadRadius; 51 | 52 | CrossSection circle = CrossSection::Circle(1., n).Translate({2, 0}); 53 | Manifold knot = Manifold::Revolve(circle.ToPolygons(), m); 54 | 55 | knot = knot.Warp([p, q, majorRadius, minorRadius, threadRadius](vec3& v) { 56 | double psi = q * atan2(v.x, v.y); 57 | double theta = psi * p / q; 58 | vec2 xy = vec2(v); 59 | double x1 = sqrt(la::dot(xy, xy)); 60 | double phi = atan2(x1 - 2, v.z); 61 | v = vec3(cos(phi), 0.0, sin(phi)); 62 | v *= threadRadius; 63 | double r = majorRadius + minorRadius * cos(theta); 64 | v = la::rotx(-double(atan2(p * minorRadius, q * r)), v); 65 | v.x += minorRadius; 66 | v = la::roty(theta, v); 67 | v.x += majorRadius; 68 | v = la::rotz(psi, v); 69 | }); 70 | 71 | if (kLoops > 1) { 72 | std::vector knots; 73 | for (double k = 0; k < kLoops; ++k) { 74 | knots.push_back( 75 | knot.Rotate(0, 0, 360.0 * (k / kLoops) * (q / double(p)))); 76 | } 77 | knot = Manifold::Compose(knots); 78 | } 79 | 80 | return knot; 81 | } 82 | } // namespace manifold 83 | -------------------------------------------------------------------------------- /samples/src/menger_sponge.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "samples.h" 16 | 17 | namespace { 18 | 19 | using namespace manifold; 20 | 21 | void Fractal(std::vector& holes, Manifold& hole, double w, 22 | vec2 position, int depth, int maxDepth) { 23 | w /= 3; 24 | holes.push_back(hole.Scale({w, w, 1.0}).Translate(vec3(position, 0.0))); 25 | if (depth == maxDepth) return; 26 | 27 | vec2 offsets[8] = {{-w, -w}, {-w, 0.0}, {-w, w}, {0.0, w}, 28 | {w, w}, {w, 0.0}, {w, -w}, {0.0, -w}}; 29 | for (int i = 0; i < 8; ++i) { 30 | Fractal(holes, hole, w, position + offsets[i], depth + 1, maxDepth); 31 | } 32 | } 33 | } // namespace 34 | 35 | namespace manifold { 36 | 37 | /** 38 | * The classic cubic fractal. 39 | * 40 | * @param n Fractal depth. Warning: scales exponentially, n = 4 has almost 41 | * 400,000 triangles! 42 | */ 43 | Manifold MengerSponge(int n) { 44 | Manifold result = Manifold::Cube(vec3(1.0), true); 45 | 46 | std::vector holes; 47 | Fractal(holes, result, 1.0, {0.0, 0.0}, 1, n); 48 | 49 | Manifold hole = Manifold::Compose(holes); 50 | 51 | result -= hole; 52 | hole = hole.Rotate(90); 53 | result -= hole; 54 | hole = hole.Rotate(0, 0, 90); 55 | result -= hole; 56 | 57 | // Alternative order causes degenerate triangles 58 | // Manifold tmp1 = hole.Rotate(90) + hole.Rotate(90).Rotate(0, 0, 90); 59 | // Manifold tmp2 = hole + tmp1; 60 | // result = result - tmp2; 61 | 62 | return result; 63 | } 64 | } // namespace manifold 65 | -------------------------------------------------------------------------------- /samples/src/rounded_frame.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "samples.h" 16 | 17 | namespace manifold { 18 | 19 | /** 20 | * A cubic frame with cylinders for edges and spheres at the corners. 21 | * Demonstrates how at 90-degree intersections, the sphere and cylinder facets 22 | * match up perfectly. 23 | * 24 | * @param edgeLength Distance between the corners. 25 | * @param radius Radius of the frame members. 26 | * @param circularSegments Number of segments in the cylinders and spheres. 27 | * Defaults to Quality.GetCircularSegments(). 28 | */ 29 | Manifold RoundedFrame(double edgeLength, double radius, int circularSegments) { 30 | Manifold edge = Manifold::Cylinder(edgeLength, radius, -1, circularSegments); 31 | Manifold corner = Manifold::Sphere(radius, circularSegments); 32 | 33 | Manifold edge1 = corner + edge; 34 | edge1 = edge1.Rotate(-90).Translate({-edgeLength / 2, -edgeLength / 2, 0}); 35 | 36 | Manifold edge2 = edge1.Rotate(0, 0, 180); 37 | edge2 += edge1; 38 | edge2 += edge.Translate({-edgeLength / 2, -edgeLength / 2, 0}); 39 | 40 | Manifold edge4 = edge2.Rotate(0, 0, 90); 41 | edge4 += edge2; 42 | 43 | Manifold frame = edge4.Translate({0, 0, -edgeLength / 2}); 44 | frame += frame.Rotate(180); 45 | 46 | return frame; 47 | } 48 | } // namespace manifold 49 | -------------------------------------------------------------------------------- /samples/src/scallop.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "samples.h" 16 | 17 | namespace manifold { 18 | 19 | /** 20 | * A smoothed manifold demonstrating selective edge sharpening with 21 | * Manifold.Smooth(). Use Manifold.Refine() before export to see the curvature. 22 | */ 23 | Manifold Scallop() { 24 | constexpr double height = 1; 25 | constexpr double radius = 3; 26 | constexpr double offset = 2; 27 | constexpr int wiggles = 12; 28 | constexpr double sharpness = 0.8; 29 | 30 | MeshGL64 scallop; 31 | std::vector sharpenedEdges; 32 | scallop.numProp = 3; 33 | scallop.vertProperties = {-offset, 0, height, -offset, 0, -height}; 34 | 35 | const double delta = kPi / wiggles; 36 | for (int i = 0; i < 2 * wiggles; ++i) { 37 | double theta = (i - wiggles) * delta; 38 | double amp = 0.5 * height * la::max(la::cos(0.8 * theta), 0.0); 39 | 40 | scallop.vertProperties.insert( 41 | scallop.vertProperties.end(), 42 | {radius * la::cos(theta), radius * la::sin(theta), 43 | amp * (i % 2 == 0 ? 1 : -1)}); 44 | int j = i + 1; 45 | if (j == 2 * wiggles) j = 0; 46 | 47 | double smoothness = 1 - sharpness * la::cos((theta + delta / 2) / 2); 48 | size_t halfedge = scallop.triVerts.size() + 1; 49 | sharpenedEdges.push_back({halfedge, smoothness}); 50 | scallop.triVerts.insert( 51 | scallop.triVerts.end(), 52 | {0, static_cast(2 + i), static_cast(2 + j)}); 53 | 54 | halfedge = scallop.triVerts.size() + 1; 55 | sharpenedEdges.push_back({halfedge, smoothness}); 56 | scallop.triVerts.insert( 57 | scallop.triVerts.end(), 58 | {1, static_cast(2 + j), static_cast(2 + i)}); 59 | } 60 | 61 | return Manifold::Smooth(scallop, sharpenedEdges); 62 | } 63 | } // namespace manifold 64 | -------------------------------------------------------------------------------- /samples/src/tet_puzzle.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "samples.h" 16 | 17 | namespace manifold { 18 | 19 | /** 20 | * A tetrahedron cut into two identical halves that can screw together as a 21 | * puzzle. This only outputs one of the halves. This demonstrates how redundant 22 | * points along a polygon can be used to make twisted extrusions smoother. 23 | * 24 | * @param edgeLength Length of each edge of the overall tetrahedron. 25 | * @param gap Spacing between the two halves to allow sliding. 26 | * @param nDivisions Number of divisions (both ways) in the screw surface. 27 | */ 28 | Manifold TetPuzzle(double edgeLength, double gap, int nDivisions) { 29 | const vec3 scale(edgeLength / (2 * sqrt(2))); 30 | 31 | Manifold tet = Manifold::Tetrahedron().Scale(scale); 32 | 33 | Polygons box; 34 | box.push_back({{2, -2}, {2, 2}}); 35 | 36 | for (int i = 0; i <= nDivisions; ++i) { 37 | box[0].push_back({gap / 2, 2 - i * 4.0 / nDivisions}); 38 | } 39 | 40 | Manifold screw = Manifold::Extrude(box, 2, nDivisions, 270) 41 | .Rotate(0, 0, -45) 42 | .Translate({0, 0, -1}) 43 | .Scale(scale); 44 | 45 | return tet ^ screw; 46 | } 47 | } // namespace manifold 48 | -------------------------------------------------------------------------------- /scripts/format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | shopt -s extglob 4 | if [ -z "$CLANG_FORMAT" ]; then 5 | CLANG_FORMAT=clang-format 6 | fi 7 | 8 | $CLANG_FORMAT -i extras/*.cpp & 9 | $CLANG_FORMAT -i samples/*/*.{h,cpp} & 10 | $CLANG_FORMAT -i test/*.{h,cpp} & 11 | $CLANG_FORMAT -i bindings/*/*.cpp & 12 | $CLANG_FORMAT -i bindings/c/include/manifold/*.h & 13 | $CLANG_FORMAT -i bindings/wasm/*.{js,ts} & 14 | $CLANG_FORMAT -i bindings/wasm/examples/*.{js,ts,html} & 15 | $CLANG_FORMAT -i bindings/wasm/examples/public/*.{js,ts} & 16 | $CLANG_FORMAT -i src/*.{h,cpp} & 17 | $CLANG_FORMAT -i src/*/*.cpp & 18 | $CLANG_FORMAT -i include/manifold/*.h & 19 | 20 | black --quiet bindings/python/examples/*.py & 21 | 22 | for pattern in 'CMakeLists.txt' '*.cmake*'; do 23 | for f in $(find -name ${pattern}); do 24 | # skip build directories 25 | if [[ $f != *build* && $f != *node_modules* ]]; then 26 | gersemi --no-warn-about-unknown-commands -i $f & 27 | fi 28 | done 29 | done 30 | 31 | wait 32 | -------------------------------------------------------------------------------- /scripts/gersemi-check.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | FAILED=0 4 | 5 | function check() { 6 | if [[ $1 != *build* ]]; then 7 | gersemi -c $1 2> /dev/null 8 | if [ $? -ne 0 ]; then 9 | gersemi --diff $1 2> /dev/null 10 | FAILED=1 11 | fi 12 | fi 13 | } 14 | 15 | for f in $(find -name CMakeLists.txt); do 16 | check $f 17 | done 18 | 19 | for f in $(find -name '*.cmake.in'); do 20 | check $f 21 | done 22 | 23 | if [[ $FAILED -ne 0 ]]; then 24 | exit 1 25 | fi 26 | -------------------------------------------------------------------------------- /scripts/minimizer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Modified from https://github.com/google/fuzztest/blob/main/tools/minimizer.sh 4 | # If you want to use a specific reproducer, pass it in FUZZTEST_MINIMIZE_REPRODUCER 5 | # environment variable. 6 | # Otherwise, just run it as 7 | # $ ./minimizer.sh ./build/test/polygon_fuzz --fuzz=PolygonFuzz.TriangulationNoCrash 8 | # If you want to kill it, try SIGKILL, SIGTERM is not very useful 9 | # If you want to end the loop early, rename $(pwd)/reproducers into something 10 | # else... 11 | 12 | if [ -z "${FUZZTEST_MINIMIZE_REPRODUCER}"]; then 13 | mkdir -p $(pwd)/reproducers 14 | FUZZTEST_REPRODUCERS_OUT_DIR=$(pwd)/reproducers "$@" 15 | wildcard="$(pwd)/reproducers/*" 16 | reproducers=($wildcard) 17 | FUZZTEST_MINIMIZE_REPRODUCER="${reproducers[0]}" 18 | fi 19 | readonly ORIGINAL_REPRODUCER="${FUZZTEST_MINIMIZE_REPRODUCER}" 20 | 21 | for i in {0001..9999}; do 22 | echo 23 | echo "╔════════════════════════════════════════════════╗" 24 | echo "║ Minimization round: ${i} ║" 25 | echo "╚════════════════════════════════════════════════╝" 26 | echo "Note that to terminate early, simply move $ORIGINAL_REPRODUCER to somewhere else..." 27 | echo 28 | 29 | if [ ! -f $ORIGINAL_REPRODUCER ]; then 30 | echo "Terminated by the user" 31 | break 32 | fi 33 | 34 | TEMP_DIR=$(mktemp -d) 35 | FUZZTEST_REPRODUCERS_OUT_DIR="${TEMP_DIR}" \ 36 | FUZZTEST_MINIMIZE_REPRODUCER="${FUZZTEST_MINIMIZE_REPRODUCER}" \ 37 | "$@" 38 | 39 | if [ $? -eq 130 ]; then 40 | echo 41 | echo "╔═══════════════════════════════════════════════╗" 42 | echo "║ Minimization terminated. ║" 43 | echo "╚═══════════════════════════════════════════════╝" 44 | echo 45 | echo "Find the smallest reproducer at:" 46 | echo 47 | echo "${FUZZTEST_MINIMIZE_REPRODUCER}" 48 | 49 | rm -rf "${TEMP_DIR}" 50 | break 51 | fi 52 | 53 | SMALLER_REPRODUCER=$(find "${TEMP_DIR}" -type f) 54 | NEW_NAME="${ORIGINAL_REPRODUCER}-min-${i}" 55 | mv "${SMALLER_REPRODUCER}" "${NEW_NAME}" 56 | FUZZTEST_MINIMIZE_REPRODUCER="${NEW_NAME}" 57 | 58 | rm -rf "${TEMP_DIR}" 59 | done 60 | -------------------------------------------------------------------------------- /scripts/test-cmake-subdir.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | mkdir cmake-consumer 3 | cd cmake-consumer 4 | 5 | cat < CMakeLists.txt 6 | cmake_minimum_required(VERSION 3.18) 7 | project(testing LANGUAGES CXX) 8 | set(MANIFOLD_PAR ON) 9 | add_subdirectory(manifold EXCLUDE_FROM_ALL) 10 | add_executable(testing test.cpp) 11 | target_link_libraries(testing PRIVATE manifold::manifold) 12 | EOT 13 | 14 | cat < test.cpp 15 | #include 16 | #include 17 | int main() { manifold::Manifold foo; return 0; } 18 | EOT 19 | 20 | cp -r ../manifold ./ 21 | mkdir build 22 | cd build 23 | cmake .. 24 | make 25 | ./testing 26 | 27 | -------------------------------------------------------------------------------- /scripts/test-cmake.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | mkdir cmake-consumer 3 | cd cmake-consumer 4 | 5 | cat < CMakeLists.txt 6 | cmake_minimum_required(VERSION 3.18) 7 | project(testing LANGUAGES CXX) 8 | find_package(manifold "3.1.1" REQUIRED) 9 | add_executable(testing test.cpp) 10 | target_link_libraries(testing PRIVATE manifold::manifold) 11 | EOT 12 | 13 | cat < test.cpp 14 | #include 15 | #include 16 | 17 | #if MANIFOLD_VERSION < MANIFOLD_VERSION_NUMBER(3, 1, 1) 18 | # error "Unexpected: minimum version number not available" 19 | #endif 20 | 21 | int main() { manifold::Manifold foo; return 0; } 22 | EOT 23 | 24 | mkdir build 25 | cd build 26 | cmake .. 27 | make 28 | ./testing 29 | -------------------------------------------------------------------------------- /scripts/test-pkgconfig.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | mkdir make-consumer 3 | cd make-consumer 4 | 5 | cat <<'EOT' > Makefile 6 | override CXXFLAGS += $(shell pkg-config --cflags manifold) 7 | override LDFLAGS += $(shell pkg-config --libs manifold) 8 | 9 | testing : testing.cpp 10 | EOT 11 | 12 | cat < testing.cpp 13 | #include 14 | #include 15 | 16 | #if MANIFOLD_VERSION < MANIFOLD_VERSION_NUMBER(2, 5, 1) 17 | # error "Unexpected: minimum version number not available" 18 | #endif 19 | 20 | int main() { manifold::Manifold foo; return 0; } 21 | EOT 22 | 23 | make 24 | ./testing 25 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Manifold Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | set( 16 | MANIFOLD_SRCS 17 | boolean3.cpp 18 | boolean_result.cpp 19 | constructors.cpp 20 | csg_tree.cpp 21 | edge_op.cpp 22 | face_op.cpp 23 | impl.cpp 24 | manifold.cpp 25 | polygon.cpp 26 | properties.cpp 27 | quickhull.cpp 28 | sdf.cpp 29 | smoothing.cpp 30 | sort.cpp 31 | subdivision.cpp 32 | tree2d.cpp 33 | # optional source files 34 | $<$:cross_section/cross_section.cpp> 35 | $<$:meshIO/meshIO.cpp> 36 | ) 37 | 38 | set( 39 | MANIFOLD_PRIVATE_HDRS 40 | boolean3.h 41 | collider.h 42 | csg_tree.h 43 | hashtable.h 44 | impl.h 45 | iters.h 46 | mesh_fixes.h 47 | parallel.h 48 | quickhull.h 49 | shared.h 50 | svd.h 51 | tree2d.h 52 | tri_dist.h 53 | utils.h 54 | vec.h 55 | ) 56 | 57 | # Include directories 58 | set(MANIFOLD_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include) 59 | 60 | add_library(manifold ${MANIFOLD_SRCS} ${MANIFOLD_PRIVATE_HDRS}) 61 | 62 | target_link_libraries(manifold PUBLIC $<$:TracyClient>) 63 | 64 | target_link_libraries( 65 | manifold 66 | PRIVATE 67 | # optional dependencies 68 | $<$:TBB::tbb> 69 | $<$:Clipper2::Clipper2> 70 | $<$:assimp::assimp> 71 | ) 72 | 73 | target_compile_options(manifold PRIVATE ${MANIFOLD_FLAGS}) 74 | # make sure users use appropriate c++ standard when including our headers 75 | target_compile_features(manifold PUBLIC cxx_std_17) 76 | set( 77 | OPTIONS 78 | MANIFOLD_DEBUG 79 | MANIFOLD_ASSERT 80 | MANIFOLD_CROSS_SECTION 81 | MANIFOLD_EXPORT 82 | TRACY_ENABLE 83 | TRACY_MEMORY_USAGE 84 | ) 85 | foreach(OPT IN LISTS OPTIONS) 86 | if(${${OPT}}) 87 | target_compile_options(manifold PUBLIC -D${OPT}) 88 | endif() 89 | endforeach() 90 | if(MANIFOLD_PAR) 91 | target_compile_options(manifold PUBLIC -DMANIFOLD_PAR=1) 92 | else() 93 | target_compile_options(manifold PUBLIC -DMANIFOLD_PAR=-1) 94 | endif() 95 | 96 | target_include_directories( 97 | manifold 98 | PUBLIC 99 | $ 100 | $ 101 | $ 102 | PRIVATE ${MANIFOLD_INCLUDE_DIRS} 103 | ) 104 | 105 | exportlib(manifold) 106 | 107 | install(TARGETS manifold EXPORT manifoldTargets) 108 | -------------------------------------------------------------------------------- /src/boolean3.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | #include "impl.h" 17 | 18 | #ifdef MANIFOLD_DEBUG 19 | #define PRINT(msg) \ 20 | if (ManifoldParams().verbose > 0) std::cout << msg << std::endl; 21 | #else 22 | #define PRINT(msg) 23 | #endif 24 | 25 | /** 26 | * The notation in these files is abbreviated due to the complexity of the 27 | * functions involved. The key is that the input manifolds are P and Q, while 28 | * the output is R, and these letters in both upper and lower case refer to 29 | * these objects. Operations are based on dimensionality: vert: 0, edge: 1, 30 | * face: 2, solid: 3. X denotes a winding-number type quantity from the source 31 | * paper of this algorithm, while S is closely related but includes only the 32 | * subset of X values which "shadow" (are on the correct side of). 33 | * 34 | * Nearly everything here are sparse arrays, where for instance each pair in 35 | * p2q1 refers to a face index of P interacting with a halfedge index of Q. 36 | * Adjacent arrays like x21 refer to the values of X corresponding to each 37 | * sparse index pair. 38 | * 39 | * Note many functions are designed to work symmetrically, for instance for both 40 | * p2q1 and p1q2. Inside of these functions P and Q are marked as though the 41 | * function is forwards, but it may include a Boolean "reverse" that indicates P 42 | * and Q have been swapped. 43 | */ 44 | 45 | namespace manifold { 46 | /** @ingroup Private */ 47 | class Boolean3 { 48 | public: 49 | Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ, OpType op); 50 | Manifold::Impl Result(OpType op) const; 51 | 52 | private: 53 | const Manifold::Impl &inP_, &inQ_; 54 | const double expandP_; 55 | Vec> p1q2_, p2q1_; 56 | Vec x12_, x21_, w03_, w30_; 57 | Vec v12_, v21_; 58 | bool valid = true; 59 | }; 60 | } // namespace manifold 61 | -------------------------------------------------------------------------------- /src/csg_tree.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | #include "manifold/manifold.h" 17 | #include "utils.h" 18 | 19 | namespace manifold { 20 | 21 | enum class CsgNodeType { Union, Intersection, Difference, Leaf }; 22 | 23 | class CsgLeafNode; 24 | 25 | class CsgNode : public std::enable_shared_from_this { 26 | public: 27 | virtual std::shared_ptr ToLeafNode() const = 0; 28 | virtual std::shared_ptr Transform(const mat3x4& m) const = 0; 29 | virtual CsgNodeType GetNodeType() const = 0; 30 | 31 | virtual std::shared_ptr Boolean( 32 | const std::shared_ptr& second, OpType op); 33 | 34 | std::shared_ptr Translate(const vec3& t) const; 35 | std::shared_ptr Scale(const vec3& s) const; 36 | std::shared_ptr Rotate(double xDegrees = 0, double yDegrees = 0, 37 | double zDegrees = 0) const; 38 | }; 39 | 40 | class CsgLeafNode final : public CsgNode { 41 | public: 42 | CsgLeafNode(); 43 | CsgLeafNode(std::shared_ptr pImpl_); 44 | CsgLeafNode(std::shared_ptr pImpl_, mat3x4 transform_); 45 | 46 | std::shared_ptr GetImpl() const; 47 | 48 | std::shared_ptr ToLeafNode() const override; 49 | 50 | std::shared_ptr Transform(const mat3x4& m) const override; 51 | 52 | CsgNodeType GetNodeType() const override; 53 | 54 | static std::shared_ptr Compose( 55 | const std::vector>& nodes); 56 | 57 | private: 58 | mutable std::shared_ptr pImpl_; 59 | mutable mat3x4 transform_ = la::identity; 60 | }; 61 | 62 | class CsgOpNode final : public CsgNode { 63 | public: 64 | CsgOpNode(); 65 | 66 | CsgOpNode(const std::vector>& children, OpType op); 67 | 68 | std::shared_ptr Boolean(const std::shared_ptr& second, 69 | OpType op) override; 70 | 71 | std::shared_ptr Transform(const mat3x4& m) const override; 72 | 73 | std::shared_ptr ToLeafNode() const override; 74 | 75 | CsgNodeType GetNodeType() const override; 76 | 77 | ~CsgOpNode(); 78 | 79 | private: 80 | mutable ConcurrentSharedPtr>> impl_ = 81 | ConcurrentSharedPtr>>({}); 82 | OpType op_; 83 | mat3x4 transform_ = la::identity; 84 | // the following fields are for lazy evaluation, so they are mutable 85 | mutable std::shared_ptr cache_ = nullptr; 86 | }; 87 | 88 | } // namespace manifold 89 | -------------------------------------------------------------------------------- /src/disjoint_sets.h: -------------------------------------------------------------------------------- 1 | // from https://github.com/wjakob/dset, changed to add connected component 2 | // computation 3 | // 4 | // Copyright (c) 2015 Wenzel Jakob 5 | // 6 | // This software is provided 'as-is', without any express or implied 7 | // warranty. In no event will the authors be held liable for any damages 8 | // arising from the use of this software. 9 | // 10 | // Permission is granted to anyone to use this software for any purpose, 11 | // including commercial applications, and to alter it and redistribute it 12 | // freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source distribution. 21 | // 22 | #pragma once 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | class DisjointSets { 29 | public: 30 | DisjointSets(uint32_t size) : mData(size) { 31 | for (uint32_t i = 0; i < size; ++i) mData[i] = (uint32_t)i; 32 | } 33 | 34 | uint32_t find(uint32_t id) const { 35 | while (id != parent(id)) { 36 | uint64_t value = mData[id]; 37 | uint32_t new_parent = parent((uint32_t)value); 38 | uint64_t new_value = (value & 0xFFFFFFFF00000000ULL) | new_parent; 39 | /* Try to update parent (may fail, that's ok) */ 40 | if (value != new_value) mData[id].compare_exchange_weak(value, new_value); 41 | id = new_parent; 42 | } 43 | return id; 44 | } 45 | 46 | bool same(uint32_t id1, uint32_t id2) const { 47 | for (;;) { 48 | id1 = find(id1); 49 | id2 = find(id2); 50 | if (id1 == id2) return true; 51 | if (parent(id1) == id1) return false; 52 | } 53 | } 54 | 55 | uint32_t unite(uint32_t id1, uint32_t id2) { 56 | for (;;) { 57 | id1 = find(id1); 58 | id2 = find(id2); 59 | 60 | if (id1 == id2) return id1; 61 | 62 | uint32_t r1 = rank(id1), r2 = rank(id2); 63 | 64 | if (r1 > r2 || (r1 == r2 && id1 < id2)) { 65 | std::swap(r1, r2); 66 | std::swap(id1, id2); 67 | } 68 | 69 | uint64_t oldEntry = ((uint64_t)r1 << 32) | id1; 70 | uint64_t newEntry = ((uint64_t)r1 << 32) | id2; 71 | 72 | if (!mData[id1].compare_exchange_strong(oldEntry, newEntry)) continue; 73 | 74 | if (r1 == r2) { 75 | oldEntry = ((uint64_t)r2 << 32) | id2; 76 | newEntry = ((uint64_t)(r2 + 1) << 32) | id2; 77 | /* Try to update the rank (may fail, retry if rank = 0) */ 78 | if (!mData[id2].compare_exchange_strong(oldEntry, newEntry) && r2 == 0) 79 | continue; 80 | } 81 | 82 | break; 83 | } 84 | return id2; 85 | } 86 | 87 | uint32_t size() const { return (uint32_t)mData.size(); } 88 | 89 | uint32_t rank(uint32_t id) const { 90 | return ((uint32_t)(mData[id] >> 32)) & 0x7FFFFFFFu; 91 | } 92 | 93 | uint32_t parent(uint32_t id) const { return (uint32_t)mData[id]; } 94 | 95 | int connectedComponents(std::vector& components) { 96 | components.resize(mData.size()); 97 | int lonelyNodes = 0; 98 | std::unordered_map toLabel; 99 | for (size_t i = 0; i < mData.size(); ++i) { 100 | // we optimize for connected component of size 1 101 | // no need to put them into the hashmap 102 | auto iParent = find(i); 103 | if (rank(iParent) == 0) { 104 | components[i] = static_cast(toLabel.size()) + lonelyNodes++; 105 | continue; 106 | } 107 | auto iter = toLabel.find(iParent); 108 | if (iter == toLabel.end()) { 109 | auto s = static_cast(toLabel.size()) + lonelyNodes; 110 | toLabel.insert(std::make_pair(iParent, s)); 111 | components[i] = s; 112 | } else { 113 | components[i] = iter->second; 114 | } 115 | } 116 | return toLabel.size() + lonelyNodes; 117 | } 118 | 119 | mutable std::vector> mData; 120 | }; 121 | -------------------------------------------------------------------------------- /src/mesh_fixes.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #pragma once 15 | #include "shared.h" 16 | 17 | namespace { 18 | using namespace manifold; 19 | 20 | inline int FlipHalfedge(int halfedge) { 21 | const int tri = halfedge / 3; 22 | const int vert = 2 - (halfedge - 3 * tri); 23 | return 3 * tri + vert; 24 | } 25 | 26 | struct TransformNormals { 27 | mat3 transform; 28 | 29 | vec3 operator()(vec3 normal) const { 30 | normal = la::normalize(transform * normal); 31 | if (std::isnan(normal.x)) normal = vec3(0.0); 32 | return normal; 33 | } 34 | }; 35 | 36 | struct TransformTangents { 37 | VecView tangent; 38 | const int edgeOffset; 39 | const mat3 transform; 40 | const bool invert; 41 | VecView oldTangents; 42 | VecView halfedge; 43 | 44 | void operator()(const int edgeOut) { 45 | const int edgeIn = 46 | invert ? halfedge[FlipHalfedge(edgeOut)].pairedHalfedge : edgeOut; 47 | tangent[edgeOut + edgeOffset] = 48 | vec4(transform * vec3(oldTangents[edgeIn]), oldTangents[edgeIn].w); 49 | } 50 | }; 51 | 52 | struct FlipTris { 53 | VecView halfedge; 54 | 55 | void operator()(const int tri) { 56 | std::swap(halfedge[3 * tri], halfedge[3 * tri + 2]); 57 | 58 | for (const int i : {0, 1, 2}) { 59 | std::swap(halfedge[3 * tri + i].startVert, halfedge[3 * tri + i].endVert); 60 | halfedge[3 * tri + i].pairedHalfedge = 61 | FlipHalfedge(halfedge[3 * tri + i].pairedHalfedge); 62 | } 63 | } 64 | }; 65 | } // namespace 66 | -------------------------------------------------------------------------------- /src/tree2d.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "tree2d.h" 16 | 17 | #include "parallel.h" 18 | 19 | #ifndef ZoneScoped 20 | #if __has_include() 21 | #include 22 | #else 23 | #define FrameMarkStart(x) 24 | #define FrameMarkEnd(x) 25 | // putting ZoneScoped in a function will instrument the function execution when 26 | // TRACY_ENABLE is set, which allows the profiler to record more accurate 27 | // timing. 28 | #define ZoneScoped 29 | #define ZoneScopedN(name) 30 | #endif 31 | #endif 32 | 33 | namespace manifold { 34 | 35 | // Not really a proper KD-tree, but a kd tree with k = 2 and alternating x/y 36 | // partition. 37 | // Recursive sorting is not the most efficient, but simple and guaranteed to 38 | // result in a balanced tree. 39 | void BuildTwoDTreeImpl(VecView points, bool sortX) { 40 | using CmpFn = std::function; 41 | CmpFn cmpx = [](const PolyVert& a, const PolyVert& b) { 42 | return a.pos.x < b.pos.x; 43 | }; 44 | CmpFn cmpy = [](const PolyVert& a, const PolyVert& b) { 45 | return a.pos.y < b.pos.y; 46 | }; 47 | manifold::stable_sort(points.begin(), points.end(), sortX ? cmpx : cmpy); 48 | if (points.size() < 2) return; 49 | BuildTwoDTreeImpl(points.view(0, points.size() / 2), !sortX); 50 | BuildTwoDTreeImpl(points.view(points.size() / 2 + 1), !sortX); 51 | } 52 | 53 | void BuildTwoDTree(VecView points) { 54 | ZoneScoped; 55 | // don't even bother... 56 | if (points.size() <= 8) return; 57 | BuildTwoDTreeImpl(points, true); 58 | } 59 | } // namespace manifold 60 | -------------------------------------------------------------------------------- /src/tree2d.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include "manifold/common.h" 18 | #include "manifold/optional_assert.h" 19 | #include "manifold/polygon.h" 20 | #include "manifold/vec_view.h" 21 | 22 | namespace manifold { 23 | 24 | void BuildTwoDTreeImpl(VecView points, bool sortX); 25 | 26 | void BuildTwoDTree(VecView points); 27 | 28 | template 29 | void QueryTwoDTree(VecView points, Rect r, F f) { 30 | if (points.size() <= 8) { 31 | for (const auto& p : points) 32 | if (r.Contains(p.pos)) f(p); 33 | return; 34 | } 35 | Rect current; 36 | current.min = vec2(-std::numeric_limits::infinity()); 37 | current.max = vec2(std::numeric_limits::infinity()); 38 | 39 | int level = 0; 40 | VecView currentView = points; 41 | std::array rectStack; 42 | std::array, 64> viewStack; 43 | std::array levelStack; 44 | int stackPointer = 0; 45 | 46 | while (1) { 47 | if (currentView.size() <= 2) { 48 | for (const auto& p : currentView) 49 | if (r.Contains(p.pos)) f(p); 50 | if (--stackPointer < 0) break; 51 | level = levelStack[stackPointer]; 52 | currentView = viewStack[stackPointer]; 53 | current = rectStack[stackPointer]; 54 | continue; 55 | } 56 | 57 | // these are conceptual left/right trees 58 | Rect left = current; 59 | Rect right = current; 60 | const PolyVert middle = currentView[currentView.size() / 2]; 61 | if (level % 2 == 0) 62 | left.max.x = right.min.x = middle.pos.x; 63 | else 64 | left.max.y = right.min.y = middle.pos.y; 65 | 66 | if (r.Contains(middle.pos)) f(middle); 67 | if (left.DoesOverlap(r)) { 68 | if (right.DoesOverlap(r)) { 69 | DEBUG_ASSERT(stackPointer < 64, logicErr, "Stack overflow"); 70 | rectStack[stackPointer] = right; 71 | viewStack[stackPointer] = currentView.view(currentView.size() / 2 + 1); 72 | levelStack[stackPointer] = level + 1; 73 | stackPointer++; 74 | } 75 | current = left; 76 | currentView = currentView.view(0, currentView.size() / 2); 77 | level++; 78 | } else { 79 | current = right; 80 | currentView = currentView.view(currentView.size() / 2 + 1); 81 | level++; 82 | } 83 | } 84 | } 85 | } // namespace manifold 86 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The Manifold Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | if(NOT MANIFOLD_TEST) 16 | return() 17 | endif() 18 | 19 | enable_testing() 20 | 21 | # put fast/simple tests files first to run earlier 22 | set( 23 | SOURCE_FILES 24 | test_main.cpp 25 | polygon_test.cpp 26 | properties_test.cpp 27 | manifold_test.cpp 28 | boolean_test.cpp 29 | sdf_test.cpp 30 | smooth_test.cpp 31 | hull_test.cpp 32 | samples_test.cpp 33 | boolean_complex_test.cpp 34 | $<$:cross_section_test.cpp> 35 | $<$:manifoldc_test.cpp> 36 | ) 37 | 38 | add_executable(manifold_test ${SOURCE_FILES}) 39 | target_link_libraries( 40 | manifold_test 41 | PRIVATE 42 | GTest::gtest_main 43 | manifold 44 | samples 45 | $<$:manifoldc> 46 | $<$:TBB::tbb> 47 | ) 48 | 49 | if(EMSCRIPTEN) 50 | target_link_options( 51 | manifold_test 52 | PRIVATE 53 | -sASSERTIONS=1 54 | --bind 55 | --preload-file 56 | ${CMAKE_CURRENT_SOURCE_DIR}/polygons@/polygons 57 | ) 58 | endif() 59 | 60 | target_compile_options(manifold_test PRIVATE ${MANIFOLD_FLAGS}) 61 | exportbin(manifold_test) 62 | 63 | add_test(NAME manifold_test COMMAND manifold_test) 64 | 65 | if(MANIFOLD_FUZZ) 66 | fuzztest_setup_fuzzing_flags() 67 | add_executable(polygon_fuzz polygon_fuzz.cpp) 68 | target_link_libraries(polygon_fuzz PUBLIC manifold) 69 | link_fuzztest(polygon_fuzz) 70 | gtest_discover_tests(polygon_fuzz) 71 | 72 | add_executable(manifold_fuzz manifold_fuzz.cpp) 73 | target_link_libraries(manifold_fuzz PUBLIC manifold) 74 | link_fuzztest(manifold_fuzz) 75 | gtest_discover_tests(manifold_fuzz) 76 | endif() 77 | -------------------------------------------------------------------------------- /test/manifold_fuzz.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include "fuzztest/fuzztest.h" 19 | #include "gtest/gtest.h" 20 | #include "manifold/manifold.h" 21 | 22 | using namespace fuzztest; 23 | using namespace manifold; 24 | 25 | enum class TransformType { Translate, Rotate, Scale }; 26 | struct Transform { 27 | TransformType ty; 28 | std::array vector; 29 | }; 30 | struct CubeOp { 31 | std::vector transforms; 32 | bool isUnion; 33 | }; 34 | 35 | // larger numbers may cause precision issues, prefer to test them later 36 | auto GoodNumbers = OneOf(InRange(0.1, 10.0), InRange(-10.0, -0.1)); 37 | auto Vec3Domain = ArrayOf<3>(GoodNumbers); 38 | auto TransformDomain = StructOf( 39 | ElementOf({TransformType::Translate, TransformType::Rotate, 40 | TransformType::Scale}), 41 | Vec3Domain); 42 | auto CsgDomain = 43 | VectorOf(StructOf(VectorOf(TransformDomain).WithMaxSize(20), 44 | ElementOf({false, true}))) 45 | .WithMaxSize(100); 46 | 47 | void SimpleCube(const std::vector& inputs) { 48 | ManifoldParams().intermediateChecks = true; 49 | ManifoldParams().processOverlaps = false; 50 | Manifold result; 51 | for (const auto& input : inputs) { 52 | auto cube = Manifold::Cube(); 53 | for (const auto& transform : input.transforms) { 54 | printf("transform: %d\n", static_cast(transform.ty)); 55 | switch (transform.ty) { 56 | case TransformType::Translate: 57 | cube = cube.Translate({std::get<0>(transform.vector), 58 | std::get<1>(transform.vector), 59 | std::get<2>(transform.vector)}); 60 | break; 61 | case TransformType::Rotate: 62 | cube = cube.Rotate(std::get<0>(transform.vector), 63 | std::get<1>(transform.vector), 64 | std::get<2>(transform.vector)); 65 | break; 66 | case TransformType::Scale: 67 | cube = cube.Scale({std::get<0>(transform.vector), 68 | std::get<1>(transform.vector), 69 | std::get<2>(transform.vector)}); 70 | break; 71 | } 72 | } 73 | 74 | printf("isUnion: %d\n", input.isUnion); 75 | std::atomic tid; 76 | std::atomic faulted(true); 77 | auto asyncFuture = std::async( 78 | std::launch::async, [&result, &faulted, &tid, &cube, &input]() { 79 | tid.store(gettid()); 80 | if (input.isUnion) { 81 | result += cube; 82 | } else { 83 | result -= cube; 84 | } 85 | EXPECT_EQ(result.Status(), Manifold::Error::NoError); 86 | faulted.store(false); 87 | }); 88 | if (asyncFuture.wait_for(std::chrono::milliseconds(10000)) == 89 | std::future_status::timeout) { 90 | printf("timeout after %dms...\n", 10000); 91 | pthread_cancel(tid.load()); 92 | } 93 | 94 | EXPECT_FALSE(faulted.load()); 95 | if (faulted.load()) break; 96 | } 97 | } 98 | 99 | FUZZ_TEST(ManifoldFuzz, SimpleCube).WithDomains(CsgDomain); 100 | -------------------------------------------------------------------------------- /test/models/Cray_left.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/Cray_left.glb -------------------------------------------------------------------------------- /test/models/Cray_right.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/Cray_right.glb -------------------------------------------------------------------------------- /test/models/Generic_Twin_7081.1.t0_left.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/Generic_Twin_7081.1.t0_left.glb -------------------------------------------------------------------------------- /test/models/Generic_Twin_7081.1.t0_right.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/Generic_Twin_7081.1.t0_right.glb -------------------------------------------------------------------------------- /test/models/Generic_Twin_7863.1.t0_left.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/Generic_Twin_7863.1.t0_left.glb -------------------------------------------------------------------------------- /test/models/Generic_Twin_7863.1.t0_right.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/Generic_Twin_7863.1.t0_right.glb -------------------------------------------------------------------------------- /test/models/Generic_Twin_91.1.t0.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/Generic_Twin_91.1.t0.glb -------------------------------------------------------------------------------- /test/models/Havocglass8_left.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/Havocglass8_left.glb -------------------------------------------------------------------------------- /test/models/Havocglass8_right.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/Havocglass8_right.glb -------------------------------------------------------------------------------- /test/models/Offset3.obj: -------------------------------------------------------------------------------- 1 | # ======= begin mesh ====== 2 | # tolerance = 5.5669873237609865e-11 3 | # epsilon = 5.5669873237609865e-11 4 | v -28.75 19.485572703317168 -8.6602537813123295 5 | v -26.24999999671385 23.815699471399075 -15.245190263195603 6 | v -30 21.650635607675078 -8.6602537813123295 7 | v -30 17.320508845284941 -4.3301270189221928 8 | v -28.75 19.485572703317168 -2.5653205604214691e-07 9 | v -32.5 21.650635607675078 -4.3301270189221928 10 | v -30 21.650635607675078 -2.5653205604214691e-07 11 | v -27.49999999671385 25.980762375756985 -15.245190263195603 12 | v -26.24999999671385 28.145825280114895 -15.245190263195603 13 | v -29.99999999671385 25.980762375756985 -10.915063500805466 14 | v -27.49999999671385 30.310889138147122 -10.915063500805466 15 | v -23.75 32.475952036813261 -8.6602537813123295 16 | v -25 34.641015894845488 -4.3301270189221928 17 | v -25 30.310889132455351 -2.5653205604214691e-07 18 | v -23.75 32.475952036813261 -2.5653205604214691e-07 19 | v 28.75 19.485572703317168 -8.6602537813123295 20 | v 26.24999999671385 23.815699471399075 -15.245190263195603 21 | v 30 21.650635607675078 -8.6602537813123295 22 | v 30 17.320508845284941 -4.3301270189221928 23 | v 28.75 19.485572703317168 -2.5653205604214691e-07 24 | v 32.5 21.650635607675078 -4.3301270189221928 25 | v 30 21.650635607675078 -2.5653205604214691e-07 26 | v 27.49999999671385 25.980762375756985 -15.245190263195603 27 | v 26.24999999671385 28.145825280114895 -15.245190263195603 28 | v 29.99999999671385 25.980762375756985 -10.915063500805466 29 | v 23.75 32.475952036813261 -8.6602537813123295 30 | v 27.49999999671385 30.310889138147122 -10.915063500805466 31 | v 25 34.641015894845488 -4.3301270189221928 32 | v 23.75 32.475952036813261 -2.5653205604214691e-07 33 | v 25 30.310889132455351 -2.5653205604214691e-07 34 | f 1 8 2 35 | f 2 16 1 36 | f 2 24 17 37 | f 3 10 8 38 | f 3 8 1 39 | f 4 3 1 40 | f 5 14 7 41 | f 5 4 20 42 | f 6 10 3 43 | f 6 3 4 44 | f 6 4 7 45 | f 6 14 13 46 | f 7 4 5 47 | f 7 14 6 48 | f 8 10 11 49 | f 8 24 2 50 | f 9 8 11 51 | f 9 11 12 52 | f 9 24 8 53 | f 11 10 13 54 | f 12 11 13 55 | f 12 24 9 56 | f 13 10 6 57 | f 13 14 15 58 | f 15 14 29 59 | f 16 4 1 60 | f 16 23 18 61 | f 16 18 19 62 | f 17 16 2 63 | f 17 23 16 64 | f 17 24 23 65 | f 18 25 21 66 | f 19 4 16 67 | f 19 18 21 68 | f 19 22 20 69 | f 20 14 5 70 | f 20 4 19 71 | f 21 22 19 72 | f 21 25 28 73 | f 22 14 20 74 | f 23 25 18 75 | f 23 24 27 76 | f 26 24 12 77 | f 26 12 28 78 | f 27 25 23 79 | f 27 24 28 80 | f 28 12 13 81 | f 28 25 27 82 | f 28 24 26 83 | f 28 13 29 84 | f 28 22 21 85 | f 29 13 15 86 | f 29 14 30 87 | f 30 14 22 88 | f 30 22 28 89 | f 30 28 29 90 | # ======== end mesh ======= 91 | 92 | -------------------------------------------------------------------------------- /test/models/Offset4.obj: -------------------------------------------------------------------------------- 1 | # ======= begin mesh ====== 2 | # tolerance = 5.5669873237609865e-11 3 | # epsilon = 5.5669873237609865e-11 4 | v -26.25 28.145826228097441 -4.3301267623901367 5 | v -27.5 25.980762370065214 0 6 | v -27.5 30.310889132455351 -4.3301267623901367 7 | v -30 30.310889132455351 0 8 | v -25 25.980762370065214 4.3301270189221928 9 | v -23.75 28.145826228097441 8.6602537813123295 10 | v -27.5 30.310889132455351 4.3301270189221928 11 | v -25 30.310889132455351 8.6602537813123295 12 | v -26.25 32.475952036813261 -4.3301267623901367 13 | v -27.5 34.641015894845488 0 14 | v -16.25 41.136207284864021 21.650634838078908 15 | v -21.541618525634803 44.54049658525895 14.650346463399867 16 | v -17.791618525634803 46.70555948961686 10.32021970100973 17 | v -19.041618525634803 48.870623347649087 14.650346463399867 18 | v -20 43.301270189221931 17.320508075688771 19 | v -19.041618525634803 44.54049658525895 18.980473225790004 20 | v -17.5 43.301270189221931 21.650634838078908 21 | v -16.25 45.466333093579841 21.650634838078908 22 | v -17.5 47.631396951612068 17.320508075688771 23 | v -17.791618525634803 46.70555948961686 18.980473225790004 24 | v 26.25 28.145826228097441 -4.3301267623901367 25 | v 27.5 25.980762370065214 0 26 | v 27.5 30.310889132455351 -4.3301267623901367 27 | v 30 30.310889132455351 0 28 | v 25 25.980762370065214 4.3301270189221928 29 | v 23.75 28.145826228097441 8.6602537813123295 30 | v 27.5 30.310889132455351 4.3301270189221928 31 | v 25 30.310889132455351 8.6602537813123295 32 | v 26.25 32.475952036813261 -4.3301267623901367 33 | v 27.5 34.641015894845488 0 34 | v 16.25 41.136207284864021 21.650634838078908 35 | v 21.541618525634803 44.54049658525895 14.650346463399867 36 | v 17.791618525634803 46.70555948961686 10.32021970100973 37 | v 19.041618525634803 48.870623347649087 14.650346463399867 38 | v 20 43.301270189221931 17.320508075688771 39 | v 17.5 43.301270189221931 21.650634838078908 40 | v 16.25 45.466333093579841 21.650634838078908 41 | v 19.041618525634803 44.54049658525895 18.980473225790004 42 | v 17.5 47.631396951612068 17.320508075688771 43 | v 17.791618525634803 46.70555948961686 18.980473225790004 44 | f 3 1 2 45 | f 2 5 4 46 | f 4 3 2 47 | f 8 5 6 48 | f 4 5 7 49 | f 7 5 8 50 | f 9 3 10 51 | f 10 3 4 52 | f 12 10 4 53 | f 4 7 15 54 | f 1 21 2 55 | f 3 23 1 56 | f 9 23 3 57 | f 2 25 5 58 | f 5 25 6 59 | f 15 7 8 60 | f 6 17 8 61 | f 6 31 11 62 | f 9 10 13 63 | f 13 10 14 64 | f 13 33 9 65 | f 4 15 12 66 | f 14 10 12 67 | f 11 17 6 68 | f 17 15 8 69 | f 12 15 16 70 | f 12 16 14 71 | f 16 15 17 72 | f 17 20 16 73 | f 18 20 17 74 | f 16 20 14 75 | f 20 19 14 76 | f 18 19 20 77 | f 11 36 17 78 | f 13 14 33 79 | f 17 36 18 80 | f 14 19 34 81 | f 18 37 19 82 | f 2 21 22 83 | f 22 25 2 84 | f 1 23 21 85 | f 6 25 26 86 | f 9 33 29 87 | f 29 23 9 88 | f 21 23 22 89 | f 24 25 22 90 | f 22 23 24 91 | f 26 25 28 92 | f 27 25 24 93 | f 28 25 27 94 | f 30 23 29 95 | f 24 23 30 96 | f 30 32 24 97 | f 32 27 24 98 | f 26 31 6 99 | f 28 36 26 100 | f 27 36 28 101 | f 33 30 29 102 | f 34 30 33 103 | f 31 36 11 104 | f 33 14 34 105 | f 18 36 37 106 | f 34 19 39 107 | f 19 37 39 108 | f 27 35 36 109 | f 32 35 27 110 | f 34 32 30 111 | f 26 36 31 112 | f 32 38 35 113 | f 34 38 32 114 | f 36 38 37 115 | f 37 38 40 116 | f 35 38 36 117 | f 40 38 34 118 | f 39 40 34 119 | f 37 40 39 120 | # ======== end mesh ======= 121 | -------------------------------------------------------------------------------- /test/models/hull-body.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/hull-body.glb -------------------------------------------------------------------------------- /test/models/hull-mask.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/hull-mask.glb -------------------------------------------------------------------------------- /test/models/self_intersectA.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/self_intersectA.glb -------------------------------------------------------------------------------- /test/models/self_intersectB.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elalish/manifold/90cab6e2bb91757f3d98051f33d8780527a9f3d6/test/models/self_intersectB.glb -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Manifold Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include "gtest/gtest.h" 18 | #include "manifold/common.h" 19 | #include "manifold/manifold.h" 20 | 21 | #ifdef MANIFOLD_EXPORT 22 | #include "manifold/meshIO.h" 23 | #endif 24 | 25 | using namespace manifold; 26 | 27 | struct Options { 28 | bool exportModels = false; 29 | manifold::ExecutionParams params = {}; 30 | }; 31 | 32 | extern Options options; 33 | 34 | struct MeshSize { 35 | int numVert, numTri; 36 | int numProp = 0; 37 | int numPropVert = numVert; 38 | }; 39 | 40 | Polygons SquareHole(double xOffset = 0.0); 41 | MeshGL Csaszar(); 42 | Manifold Gyroid(); 43 | MeshGL TetGL(); 44 | MeshGL CubeSTL(); 45 | MeshGL CubeUV(); 46 | Manifold WithPositionColors(const Manifold& in); 47 | float GetMaxProperty(const MeshGL& mesh, int channel); 48 | float GetMinProperty(const MeshGL& mesh, int channel); 49 | void CheckFinite(const MeshGL& mesh); 50 | void Identical(const MeshGL& mesh1, const MeshGL& mesh2); 51 | void RelatedGL(const Manifold& out, const std::vector& originals, 52 | bool checkNormals = false, bool updateNormals = false); 53 | void ExpectMeshes(const Manifold& manifold, 54 | const std::vector& meshSize); 55 | void CheckStrictly(const Manifold& manifold); 56 | void CheckGL(const Manifold& manifold, bool noMerge = true); 57 | void CheckGLEquiv(const MeshGL& mgl1, const MeshGL& mgl2); 58 | #ifdef MANIFOLD_EXPORT 59 | MeshGL ReadMesh(const std::string& filename); 60 | #endif 61 | #ifdef MANIFOLD_DEBUG 62 | Manifold ReadTestOBJ(const std::string& filename); 63 | #endif 64 | void RegisterPolygonTests(); 65 | --------------------------------------------------------------------------------