Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98b8142519 | ||
|
|
b7a02755aa | ||
|
|
be1dec2ef0 | ||
|
|
3a0c251b0e | ||
|
|
209350fd1e | ||
|
|
1917ce0844 | ||
|
|
f48111c43c | ||
|
|
3db0969d1d | ||
|
|
c50b0f80ff | ||
|
|
25ce1b1d53 | ||
|
|
926d2e7a7d | ||
|
|
af5d95ec1b | ||
|
|
ec0bbca3d9 | ||
|
|
aacf37aa4d | ||
|
|
847016c069 | ||
|
|
517a224f44 | ||
|
|
a13ceab979 | ||
|
|
36035428bc | ||
|
|
f1a8693016 | ||
|
|
fb29e8586e | ||
|
|
8556840558 | ||
|
|
2162d8d91f | ||
|
|
11387fb871 | ||
|
|
ddc0a18d87 | ||
|
|
f8c9dc9c8b | ||
|
|
3ea3e7e5e5 | ||
|
|
bc08d41dc0 | ||
|
|
560e37e985 | ||
|
|
db08c23988 | ||
|
|
7d27a437ab | ||
|
|
b77abde57b | ||
|
|
70abe63963 | ||
|
|
75f42f9c53 | ||
|
|
978f84107f | ||
|
|
7d6c285b3e | ||
|
|
78915ae28d | ||
|
|
a557225c29 | ||
|
|
f22435f1e5 | ||
|
|
5d127e57fb | ||
|
|
125dbd41cd | ||
|
|
182adae00a | ||
|
|
7f298aa9db | ||
|
|
18101057b5 | ||
|
|
7de71d4c93 | ||
|
|
d9bc83a7ad | ||
|
|
ee32b3025d | ||
|
|
e7e0780114 | ||
|
|
0b957bf8df | ||
|
|
b26007e76c | ||
|
|
0f7cecaf1a | ||
|
|
a41a361123 | ||
|
|
74f340a8e7 | ||
|
|
12b43f50c4 | ||
|
|
66bca6e3fd | ||
|
|
55722a0ce3 | ||
|
|
2b29dca503 | ||
|
|
82140d3479 | ||
|
|
8e9bdb2614 | ||
|
|
4076b14dd6 | ||
|
|
f11ed8da6c | ||
|
|
a8a90c7d34 | ||
|
|
649b69a140 | ||
|
|
97cb8dbffa | ||
|
|
d1a8de2e00 | ||
|
|
a0d2c3ab25 | ||
|
|
80221a3511 | ||
|
|
5ba3c24112 | ||
|
|
85675f2723 | ||
|
|
e647093bbe | ||
|
|
54517b7546 | ||
|
|
e45df5dd3a | ||
|
|
97423a07f9 | ||
|
|
0a51ae2a0c | ||
|
|
e718288cbf | ||
|
|
005102c1a3 | ||
|
|
6ade357e4b | ||
|
|
e06765346e | ||
|
|
bd52d301ff | ||
|
|
370375ebf0 | ||
|
|
9d3fd48b01 | ||
|
|
451829f1d1 | ||
|
|
3febd48dcd | ||
|
|
658d3e037a | ||
|
|
cc1e97442a | ||
|
|
5719926db8 | ||
|
|
3c8d486e36 | ||
|
|
5a157a07fe | ||
|
|
acb4f8c5e4 | ||
|
|
5cab42f165 | ||
|
|
ea91395878 | ||
|
|
4d7613bd4f | ||
|
|
9f62dbb048 | ||
|
|
31d6da0973 | ||
|
|
359274d01b | ||
|
|
a24cca1f06 | ||
|
|
c1d96a6404 | ||
|
|
a8e533c790 | ||
|
|
fa6a632545 | ||
|
|
cff8d903e8 | ||
|
|
fcc707585b | ||
|
|
456b8ceb3b | ||
|
|
9257734c0f | ||
|
|
ced53e2109 | ||
|
|
a017f0e614 | ||
|
|
22c66051df | ||
|
|
4760ba3a33 | ||
|
|
c75f068a03 | ||
|
|
51ee91e356 | ||
|
|
2e41fef203 | ||
|
|
9124a68032 | ||
|
|
6f009ca13f | ||
|
|
5b2a95e6d2 | ||
|
|
e9c065bd5e | ||
|
|
b95b88f026 | ||
|
|
492c3a8b23 | ||
|
|
d0d5106add | ||
|
|
fbf07bf92a | ||
|
|
a0e4d1b558 | ||
|
|
db4470c3f3 | ||
|
|
fd485f62d5 | ||
|
|
74e15b1574 | ||
|
|
d4fb82d4d1 | ||
|
|
952546b761 | ||
|
|
e26e01a231 | ||
|
|
844334380a | ||
|
|
e01d31611e | ||
|
|
af2304d305 | ||
|
|
75c340f4f4 | ||
|
|
7ad7703964 | ||
|
|
a37d59ef5b | ||
|
|
3bf7f4b33d | ||
|
|
a93c0c6ca1 | ||
|
|
ccb682fc44 | ||
|
|
0617d344bc | ||
|
|
a6ee198b88 | ||
|
|
dad73e1bf4 | ||
|
|
610fd3a073 | ||
|
|
46f199c200 | ||
|
|
fa1a342e1c | ||
|
|
e46938b498 | ||
|
|
ddb3cbeb3a | ||
|
|
b817d07a9d | ||
|
|
85d71f5cef | ||
|
|
ab1a96a96a | ||
|
|
7b10590d89 | ||
|
|
5e7a576cea | ||
|
|
80a5f2e0c5 | ||
|
|
67c4647464 | ||
|
|
edd149e753 | ||
|
|
b5c175162a | ||
|
|
1df22bee48 | ||
|
|
d5c0418309 | ||
|
|
7a5d5421d4 | ||
|
|
69437096d0 | ||
|
|
3931b3296d | ||
|
|
62d0f25ce0 | ||
|
|
1bd16f4c02 | ||
|
|
3afe846570 | ||
|
|
12b9494fdd | ||
|
|
0577aabe7f | ||
|
|
bdae9dd11a | ||
|
|
5f4059ab1f | ||
|
|
9cb3969f70 | ||
|
|
4fb3520287 | ||
|
|
376d317f14 | ||
|
|
bf18c9f759 | ||
|
|
cdd1475fe1 | ||
|
|
55a0221f9b | ||
|
|
d78b1e2042 | ||
|
|
562ff02232 | ||
|
|
8e2b1dd485 | ||
|
|
5ef09daf45 | ||
|
|
6d932a2c7f | ||
|
|
fd81cd6172 | ||
|
|
6b4a4ed841 | ||
|
|
887f631b8c | ||
|
|
443863d09e | ||
|
|
5301542254 | ||
|
|
a122bde778 | ||
|
|
c9810fd517 | ||
|
|
4c57a56355 | ||
|
|
cdcbd329e6 | ||
|
|
c738f1eea1 | ||
|
|
a8b8bf71b1 | ||
|
|
c60df1b704 | ||
|
|
8d67ba3a3a | ||
|
|
3258152e2c | ||
|
|
78729599a5 | ||
|
|
32eefe9157 | ||
|
|
3e05284250 | ||
|
|
c9ec16969d | ||
|
|
9b1d255931 | ||
|
|
22d94d5688 | ||
|
|
e073141f14 | ||
|
|
f54e535c6a | ||
|
|
c6801c82ea | ||
|
|
a67aa13133 | ||
|
|
d43709715d | ||
|
|
2ddbd0a1a0 | ||
|
|
dc4afa950b | ||
|
|
c03a5ac3ad | ||
|
|
cae5a01af1 | ||
|
|
d59b9b93bd | ||
|
|
f3c5ada9f0 | ||
|
|
4395ab622c | ||
|
|
994956d7c3 | ||
|
|
79f735c59a | ||
|
|
dd67e992bd | ||
|
|
5bdb8bc233 | ||
|
|
c9ae400647 | ||
|
|
9c1517e54e | ||
|
|
3b8282e1d5 | ||
|
|
1635864cbc | ||
|
|
65b2ef8198 | ||
|
|
a9055a8abf | ||
|
|
0b7ee87489 | ||
|
|
9690305b8d | ||
|
|
aa88959899 | ||
|
|
dbb0ca76b4 | ||
|
|
9018b2ac45 | ||
|
|
f407e43e5b | ||
|
|
d7cbcb8c2e | ||
|
|
e27c9c100f | ||
|
|
8ad5f8e427 | ||
|
|
afc64f248a | ||
|
|
c0fb3766e4 | ||
|
|
887a5021af | ||
|
|
e83b9bd158 | ||
|
|
2d0c705839 | ||
|
|
b36ec2ce0e | ||
|
|
b8d143dd72 | ||
|
|
be022a58e4 | ||
|
|
30e0af942a | ||
|
|
8c77a6aa05 | ||
|
|
54fa822408 | ||
|
|
7d564cfbc1 | ||
|
|
bc0bded294 | ||
|
|
d52decc4f1 | ||
|
|
8060376e12 | ||
|
|
eba866d266 | ||
|
|
cc4966f062 | ||
|
|
3216a24be8 | ||
|
|
5e566c8c36 | ||
|
|
80edfee978 | ||
|
|
0e0d1c4f69 | ||
|
|
e15f4cf375 | ||
|
|
c9322ca63a | ||
|
|
45826d6c47 | ||
|
|
dd781cdbdf | ||
|
|
638badaf9b | ||
|
|
0149e482bc | ||
|
|
f683e8c9c0 | ||
|
|
1f8e8037f1 | ||
|
|
10a15c1dd4 | ||
|
|
6e1629ddce | ||
|
|
e273f6b09d | ||
|
|
67c468211d | ||
|
|
01c38e42ca | ||
|
|
1991232eb7 | ||
|
|
4551288f95 | ||
|
|
2e29784449 | ||
|
|
2a2cb6f88e | ||
|
|
1a91d8db2a | ||
|
|
82b98d9353 | ||
|
|
7273c9bf0f | ||
|
|
4ff3f7cf68 | ||
|
|
fafe4792da | ||
|
|
b439603eb9 | ||
|
|
b6eea01021 | ||
|
|
b9f7228064 | ||
|
|
0ef5f70434 | ||
|
|
bb6acf1f6e | ||
|
|
5930b5c09d | ||
|
|
a842de97c6 | ||
|
|
2942229b49 |
@@ -0,0 +1,2 @@
|
||||
line_length: 80
|
||||
indent: 2
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -1,6 +1,9 @@
|
||||
name: build_wheels
|
||||
|
||||
on: [release, workflow_dispatch]
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_wheels:
|
||||
@@ -8,17 +11,51 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, windows-latest, macos-latest]
|
||||
# macos-13: x86-64
|
||||
# macos-14: arm64
|
||||
os: [ubuntu-22.04, windows-2019, macos-14, macos-13]
|
||||
steps:
|
||||
- run: |
|
||||
git config --global submodule.fetchJobs 8
|
||||
git config --global core.longpaths true
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- if: matrix.os == 'macos-14'
|
||||
run: echo "CIBW_ARCHS=arm64 universal2" >> "$GITHUB_ENV"
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v2.15.0
|
||||
- uses: actions/upload-artifact@v3
|
||||
uses: pypa/cibuildwheel@v2.21.3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: python-${{ matrix.os }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
make_sdist:
|
||||
name: Make SDist
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build SDist
|
||||
run: pipx run build --sdist
|
||||
- name: Confirm SDist can be built
|
||||
run: pip wheel dist/*.tar.gz
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: python-sdist
|
||||
path: dist/*.tar.gz
|
||||
|
||||
pypi-publish:
|
||||
name: Upload release to PyPI
|
||||
needs: [build_wheels, make_sdist]
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/manifold3d
|
||||
permissions:
|
||||
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: python-*
|
||||
merge-multiple: true
|
||||
path: dist
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
|
||||
@@ -11,11 +11,23 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: DoozyX/clang-format-lint-action@v0.14
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DoozyX/clang-format-lint-action@v0.18
|
||||
with:
|
||||
source: '.'
|
||||
exclude: '*/third_party'
|
||||
extensions: 'h,cpp,js,ts,html'
|
||||
clangFormatVersion: 12
|
||||
clangFormatVersion: 18
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: 'pip'
|
||||
- uses: psf/black@stable
|
||||
with:
|
||||
options: "--check --verbose"
|
||||
src: "./bindings/python/examples"
|
||||
- name: "gersemi cmake check"
|
||||
run: |
|
||||
pip3 install gersemi
|
||||
./scripts/gersemi-check.sh
|
||||
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
name: Deploy documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_run:
|
||||
workflows: [CI]
|
||||
types:
|
||||
- completed
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
deploy_github_pages:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Doxygen Action
|
||||
uses: mattnotmitt/doxygen-action@v1
|
||||
uses: mattnotmitt/doxygen-action@edge
|
||||
|
||||
- name: Move deployables
|
||||
run: |
|
||||
@@ -22,14 +24,24 @@ jobs:
|
||||
mv ./docs ./public
|
||||
|
||||
- name: Download built examples
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
workflow_conclusion: completed
|
||||
pr: ${{github.event.pull_request.number}}
|
||||
# specific to the triggering workflow
|
||||
run_id: ${{github.event.workflow_run.id}}
|
||||
# do not download from old run
|
||||
check_artifacts: true
|
||||
name: wasm
|
||||
path: ./public
|
||||
|
||||
- name: Deploy Javascript Docs to Github Pages
|
||||
run: |
|
||||
cd bindings/wasm
|
||||
npm install
|
||||
npm run docs
|
||||
mv ./docs ../../public/jsdocs
|
||||
|
||||
- name: Deploy to Github Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
@@ -37,4 +49,4 @@ jobs:
|
||||
publish_branch: gh-pages
|
||||
force_orphan: true
|
||||
cname: manifoldcad.org
|
||||
publish_dir: ./public
|
||||
publish_dir: ./public
|
||||
|
||||
@@ -10,225 +10,286 @@ on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: GCC ${{matrix.gcc}} (CrossSection:${{matrix.cross_section}}, TBB:${{matrix.parallelization}})
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
cross_section: [ON]
|
||||
parallelization: [OFF, ON]
|
||||
gcc: [13, 14]
|
||||
include:
|
||||
- cross_section: OFF
|
||||
parallelization: ON
|
||||
gcc: 14
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
CC: gcc-${{ matrix.gcc }}
|
||||
CXX: g++-${{ matrix.gcc }}
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y libglm-dev libgtest-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
|
||||
pip install trimesh
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build ${{matrix.backend}}
|
||||
if: matrix.parallel_backend != 'NONE'
|
||||
sudo apt-get update
|
||||
sudo apt-get install libgtest-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev lcov
|
||||
python -m pip install -U trimesh pytest
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: Build ${{matrix.parallelization}}
|
||||
if: matrix.parallelization != 'OFF'
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test ${{matrix.parallel_backend}}
|
||||
if: matrix.parallel_backend != 'NONE'
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_FLAGS=-UNDEBUG -DMANIFOLD_CROSS_SECTION=${{matrix.cross_section}} -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallelization}} .. && make
|
||||
- name: Test ${{matrix.parallelization}}
|
||||
if: matrix.parallelization != 'OFF'
|
||||
run: |
|
||||
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
|
||||
cd build/test
|
||||
./manifold_test
|
||||
cd ../../
|
||||
- name: Test Python bindings ${{matrix.parallelization}}
|
||||
if: matrix.parallelization != 'OFF' && matrix.cross_section == 'ON'
|
||||
run: |
|
||||
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
|
||||
python3 bindings/python/examples/run_all.py -e
|
||||
python3 -m pytest
|
||||
- name: Coverage Report
|
||||
# only do code coverage for default sequential backend, it seems that TBB
|
||||
# backend will cause failure
|
||||
# perhaps issue related to invalid memory access?
|
||||
if: matrix.parallel_backend == 'NONE'
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_CROSS_SECTION=${{matrix.cross_section}} -DMANIFOLD_PAR=${{matrix.parallelization}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --gcov-tool gcov-${{ matrix.gcc }} --ignore-errors mismatch --initial --directory . --output-file ./code_coverage_init.info
|
||||
cd test
|
||||
./manifold_test
|
||||
cd ../
|
||||
lcov --capture --directory . --output-file ./code_coverage_test.info
|
||||
lcov --capture --gcov-tool gcov-${{ matrix.gcc }} --ignore-errors mismatch --directory . --output-file ./code_coverage_test.info
|
||||
lcov --add-tracefile ./code_coverage_init.info --add-tracefile ./code_coverage_test.info --output-file ./code_coverage_total.info
|
||||
lcov --remove ./code_coverage_total.info '/usr/*' '*/third_party/*' '*/test/*' '*/extras/*' '*/bindings/*' --output-file ./code_coverage.info
|
||||
- uses: codecov/codecov-action@v2
|
||||
if: matrix.parallel_backend == 'NONE'
|
||||
lcov --extract ./code_coverage_total.info "/*/manifold/src/*" --output-file ./code_coverage.info
|
||||
cd ../
|
||||
- uses: codecov/codecov-action@v5
|
||||
if: matrix.parallelization == 'OFF'
|
||||
with:
|
||||
disable_search: true
|
||||
files: build/code_coverage.info
|
||||
fail_ci_if_error: false
|
||||
name: ${{matrix.parallel_backend}}
|
||||
|
||||
build_cbind:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
name: ${{matrix.parallelization}}
|
||||
token: ${{secrets.CODECOV_TOKEN}}
|
||||
verbose: true
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y libgtest-dev libglm-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build C bindings with TBB
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_CBIND=ON -DMANIFOLD_PAR=TBB .. && make
|
||||
- name: Test ${{matrix.parallel_backend}}
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test --gtest_filter=CBIND.*
|
||||
sudo cmake --install .
|
||||
cd ..
|
||||
./scripts/test-cmake.sh
|
||||
|
||||
build_wasm:
|
||||
name: WASM (TBB:${{matrix.parallelization}}, DEBUG:${{matrix.debug}})
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
strategy:
|
||||
matrix:
|
||||
parallelization: [OFF, ON]
|
||||
debug: [OFF, ON]
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs libglm-dev
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup WASM
|
||||
run: |
|
||||
# setup emscripten
|
||||
git clone https://github.com/emscripten-core/emsdk.git
|
||||
cd emsdk
|
||||
./emsdk install latest
|
||||
./emsdk activate latest
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build WASM
|
||||
./emsdk install 3.1.64
|
||||
./emsdk activate 3.1.64
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: Build WASM ${{matrix.parallelization}}
|
||||
run: |
|
||||
source ./emsdk/emsdk_env.sh
|
||||
mkdir build
|
||||
cd build
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DMANIFOLD_PAR=${{matrix.parallelization}} -DMANIFOLD_DEBUG=${{matrix.debug}} .. && emmake make
|
||||
- name: Test WASM
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
cd build/test
|
||||
node ./manifold_test.js
|
||||
- name: Test examples
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
cd bindings/wasm/examples
|
||||
npm ci
|
||||
npm run build
|
||||
npm test
|
||||
cp ../manifold.* ./dist/
|
||||
- name: Upload WASM files
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: github.event_name == 'push' && matrix.parallelization == 'OFF' && matrix.debug == 'OFF'
|
||||
with:
|
||||
name: wasm
|
||||
path: bindings/wasm/examples/dist/
|
||||
retention-days: 90
|
||||
overwrite: true
|
||||
|
||||
build_windows:
|
||||
name: Windows (TBB:${{matrix.parallelization}}, SHARED:${{matrix.shared}})
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
max-parallel: 1
|
||||
parallelization: [OFF, ON]
|
||||
shared: [OFF, ON]
|
||||
runs-on: windows-2019
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Build ${{matrix.backend}}
|
||||
- name: Build ${{matrix.parallelization}}
|
||||
shell: powershell
|
||||
run: |
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DPYBIND11_FINDPYTHON=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -A x64
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{matrix.shared}} -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallelization}} -A x64 -B build
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test ${{matrix.parallel_backend}}
|
||||
- name: Test dll location
|
||||
if: matrix.shared == 'ON'
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/test
|
||||
./Release/manifold_test.exe
|
||||
ls ./build/lib/Release
|
||||
[ -f ./build/lib/Release/manifold.dll ]
|
||||
[ -f ./build/lib/Release/manifoldc.dll ]
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
cp ./build/lib/Release/* ./build/bin/Release
|
||||
cd build/bin/Release
|
||||
./manifold_test.exe
|
||||
cd ../../
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd build
|
||||
cmake --install .
|
||||
cd ..
|
||||
./scripts/test-cmake.sh
|
||||
|
||||
build_mxe:
|
||||
name: MXE (TBB:${{matrix.parallelization == 'ON'}})
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
max-parallel: 1
|
||||
parallelization: [OFF, ON]
|
||||
runs-on: ubuntu-latest
|
||||
container: openscad/mxe-x86_64-gui:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=OFF -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test
|
||||
export MXEDIR=/mxe
|
||||
export MXE_TARGETS=x86_64-w64-mingw32.static.posix
|
||||
export MXEDIR=$HOME/openscad_deps/mxe
|
||||
export MXETARGETDIR=$MXEDIR/usr/$MXE_TARGETS
|
||||
export PATH=/mxe/usr/bin:$PATH
|
||||
export CMAKE=$MXE_TARGETS-cmake
|
||||
$CMAKE -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DMANIFOLD_PYBIND=OFF -DMANIFOLD_CBIND=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallelization}} .. && make
|
||||
|
||||
build_mac:
|
||||
name: MacOS (TBB:${{matrix.parallelization == 'ON'}})
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
parallelization: [OFF, ON]
|
||||
runs-on: macos-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install pkg-config googletest assimp
|
||||
pip install trimesh
|
||||
brew install googletest assimp
|
||||
pip install trimesh pytest
|
||||
- name: Install TBB
|
||||
if: matrix.parallel_backend == 'TBB'
|
||||
if: matrix.parallelization == 'ON'
|
||||
run: brew install tbb
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallelization}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test
|
||||
cd ../../
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd build
|
||||
sudo cmake --install .
|
||||
cd ..
|
||||
./scripts/test-cmake.sh
|
||||
LDFLAGS=-Wl,-rpath,/usr/local/lib ./scripts/test-pkgconfig.sh
|
||||
|
||||
build_mac_builtin_tbb:
|
||||
timeout-minutes: 30
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install googletest
|
||||
pip install trimesh pytest
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=OFF -DMANIFOLD_PAR=ON .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test
|
||||
cd ../../
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd build
|
||||
sudo cmake --install .
|
||||
cd ..
|
||||
./scripts/test-cmake.sh
|
||||
LDFLAGS=-Wl,-rpath,/usr/local/lib ./scripts/test-pkgconfig.sh
|
||||
|
||||
build_mac_builtin_tbb_subdir:
|
||||
timeout-minutes: 30
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install googletest
|
||||
pip install trimesh pytest
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd ..
|
||||
./manifold/scripts/test-cmake-subdir.sh
|
||||
|
||||
build_nix:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [none, tbb, js]
|
||||
variant: [manifold-none, manifold-tbb, manifold-js, manifold-js-tbb, manifold3d]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- run: nix build -L '.?submodules=1#${{matrix.variant}}'
|
||||
|
||||
build_nix_python:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- run: nix build -L '.?submodules=1#manifold3d'
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
name: publish_npm
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish_npm:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Download built examples
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
workflow_conclusion: completed
|
||||
branch: master
|
||||
check_artifacts: true
|
||||
name: wasm
|
||||
path: ./bindings/wasm/
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
cd ./bindings/wasm/
|
||||
npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Release File
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
upload_archive:
|
||||
name: Upload archive
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: |
|
||||
ref=${{ github.ref_name }}
|
||||
echo "release_name=manifold-${ref#v}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{ env.release_name }}
|
||||
submodules: recursive
|
||||
- name: Build archive
|
||||
run: >
|
||||
tar --exclude=".git*" -cz ${{ env.release_name }} -f ${{ env.release_name }}.tar.gz
|
||||
- name: Log checksum
|
||||
run: >
|
||||
sha256sum ${{ env.release_name }}.tar.gz
|
||||
- name: Add file to release
|
||||
run: |
|
||||
cd ${{ env.release_name }} || exit 1
|
||||
gh release upload ${{ github.ref_name }} ../${{ env.release_name }}.tar.gz
|
||||
env:
|
||||
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
@@ -9,7 +9,6 @@
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
@@ -112,11 +111,24 @@ node_modules/
|
||||
bindings/wasm/examples/built
|
||||
bindings/wasm/examples/dist
|
||||
bindings/wasm/examples/public/manifold*
|
||||
bindings/wasm/docs
|
||||
__pycache__
|
||||
.vscode/c_cpp_properties.json
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.cache
|
||||
CMakeUserPresets.json
|
||||
CMakeSettings.json
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
.idea
|
||||
|
||||
# Temporary files
|
||||
*~
|
||||
\#*\#
|
||||
.\#*
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-v][a-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
[submodule "src/third_party/clipper2"]
|
||||
path = src/third_party/clipper2
|
||||
url = https://github.com/AngusJohnson/clipper2
|
||||
[submodule "src/third_party/quickhull"]
|
||||
path = src/third_party/quickhull
|
||||
url = https://github.com/akuukka/quickhull
|
||||
[submodule "bindings/python/third_party/nanobind"]
|
||||
path = bindings/python/third_party/nanobind
|
||||
url = https://github.com/wjakob/nanobind
|
||||
@@ -7,11 +7,12 @@
|
||||
{
|
||||
"name": "manifold test",
|
||||
"type": "cppdbg",
|
||||
"MIMode": "lldb",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/test/manifold_test",
|
||||
"args": [
|
||||
"--gtest_break_on_failure",
|
||||
"--gtest_filter=Manifold.GetMesh"
|
||||
"--gtest_catch_exceptions=0",
|
||||
"--gtest_filter=Manifold.MergeRefine"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/test",
|
||||
@@ -20,7 +21,8 @@
|
||||
"name": "MALLOC_CHECK_",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
],
|
||||
"externalConsole": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -108,17 +108,27 @@
|
||||
"latch": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"span": "cpp",
|
||||
"__sso_allocator": "cpp"
|
||||
"__sso_allocator": "cpp",
|
||||
"source_location": "cpp",
|
||||
"format": "cpp",
|
||||
"numbers": "cpp",
|
||||
"ranges": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"execution": "cpp",
|
||||
"csetjmp": "cpp",
|
||||
"cuchar": "cpp",
|
||||
"propagate_const": "cpp",
|
||||
"scoped_allocator": "cpp"
|
||||
},
|
||||
"C_Cpp.clang_format_fallbackStyle": "google",
|
||||
"editor.formatOnSave": true,
|
||||
"cmake.configureArgs": [
|
||||
"-DBUILD_SHARED_LIBS=ON",
|
||||
"-DMANIFOLD_CBIND=ON",
|
||||
"-DMANIFOLD_PYBIND=ON",
|
||||
"-DMANIFOLD_EXPORT=ON",
|
||||
"-DMANIFOLD_DEBUG=ON",
|
||||
"-DMANIFOLD_USE_CUDA=OFF",
|
||||
"-DMANIFOLD_PAR=TBB",
|
||||
"-DMANIFOLD_PAR=ON",
|
||||
"-DCODE_COVERAGE=OFF",
|
||||
"-DCMAKE_CXX_FLAGS=''" //'-fsanitize=address,undefined'"
|
||||
],
|
||||
@@ -137,7 +147,9 @@
|
||||
"halfedge",
|
||||
"halfedges",
|
||||
"Tris",
|
||||
"Verts"
|
||||
"Verts",
|
||||
"Voxel",
|
||||
"voxels"
|
||||
],
|
||||
"javascript.format.semicolons": "insert",
|
||||
"[python]": {
|
||||
|
||||
@@ -15,83 +15,293 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(manifold LANGUAGES CXX)
|
||||
|
||||
# Use C++17
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
|
||||
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"NONE\"")
|
||||
set(MANIFOLD_FLAGS "" CACHE STRING "Manifold compiler flags")
|
||||
option(MANIFOLD_EXPORT "Build mesh export (via assimp) utility library" OFF)
|
||||
option(MANIFOLD_TEST "Enable testing suite" ON)
|
||||
include(GNUInstallDirs)
|
||||
include(CMakeDependentOption)
|
||||
|
||||
# Define Manifold version
|
||||
set(MANIFOLD_VERSION_MAJOR 3)
|
||||
set(MANIFOLD_VERSION_MINOR 0)
|
||||
set(MANIFOLD_VERSION_PATCH 1)
|
||||
set(
|
||||
MANIFOLD_VERSION
|
||||
"${MANIFOLD_VERSION_MAJOR}.${MANIFOLD_VERSION_MINOR}.${MANIFOLD_VERSION_PATCH}"
|
||||
)
|
||||
|
||||
# Primary user facing options
|
||||
option(MANIFOLD_CROSS_SECTION "Build CrossSection for 2D support" ON)
|
||||
option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF)
|
||||
option(
|
||||
MANIFOLD_DOWNLOADS
|
||||
"Allow Manifold build to download missing dependencies"
|
||||
ON
|
||||
)
|
||||
option(MANIFOLD_EXPORT "Build mesh export (via assimp) utility library" OFF)
|
||||
option(MANIFOLD_PAR "Enable Parallel backend" OFF)
|
||||
option(
|
||||
MANIFOLD_OPTIMIZED
|
||||
"Force optimized build, even with debugging enabled"
|
||||
OFF
|
||||
)
|
||||
option(MANIFOLD_TEST "Enable testing suite" ON)
|
||||
option(MANIFOLD_PYBIND "Build python bindings" OFF)
|
||||
# MANIFOLD_CBIND is only available when MANIFOLD_CROSS_SECTION is enabled
|
||||
cmake_dependent_option(
|
||||
MANIFOLD_CBIND
|
||||
"Build C (FFI) bindings"
|
||||
ON
|
||||
"MANIFOLD_CROSS_SECTION"
|
||||
OFF
|
||||
)
|
||||
# MANIFOLD_JSBIND is only available when building with Emscripten
|
||||
cmake_dependent_option(
|
||||
MANIFOLD_JSBIND
|
||||
"Build js binding"
|
||||
ON
|
||||
"EMSCRIPTEN"
|
||||
OFF
|
||||
)
|
||||
# These three options can force the build to avoid using the system version of
|
||||
# the dependency
|
||||
# This will either use the provided source directory via
|
||||
# FETCHCONTENT_SOURCE_DIR_XXX, or fetch the source from GitHub.
|
||||
# Note that the dependency will be built as static dependency to avoid dynamic
|
||||
# library conflict.
|
||||
# When the system package is unavailable, the option will be automatically set
|
||||
# to true.
|
||||
option(MANIFOLD_USE_BUILTIN_TBB "Use builtin tbb" OFF)
|
||||
option(MANIFOLD_USE_BUILTIN_CLIPPER2 "Use builtin clipper2" OFF)
|
||||
option(MANIFOLD_USE_BUILTIN_NANOBIND "Use builtin nanobind" OFF)
|
||||
|
||||
option(MANIFOLD_PYBIND "Build python bindings" ON)
|
||||
option(MANIFOLD_CBIND "Build C (FFI) bindings" OFF)
|
||||
option(MANIFOLD_JSBIND "Build js binding" ${EMSCRIPTEN})
|
||||
# default to Release build
|
||||
option(CMAKE_BUILD_TYPE "Build type" Release)
|
||||
# default to building shared library
|
||||
option(BUILD_SHARED_LIBS "Build shared library" ON)
|
||||
# Set some option values in the CMake cache
|
||||
set(MANIFOLD_FLAGS "" CACHE STRING "Manifold compiler flags")
|
||||
|
||||
option(TRACY_ENABLE "Use tracy profiling" OFF)
|
||||
# Development options
|
||||
option(TRACY_ENABLE "Use tracy profiling" OFF)
|
||||
option(TRACY_MEMORY_USAGE "Track memory allocation with tracy (expensive)" OFF)
|
||||
option(BUILD_TEST_CGAL "Build CGAL performance comparisons" OFF)
|
||||
option(MANIFOLD_FUZZ "Enable fuzzing tests" OFF)
|
||||
mark_as_advanced(TRACY_ENABLE)
|
||||
mark_as_advanced(TRACY_MEMORY_USAGE)
|
||||
mark_as_advanced(MANIFOLD_FUZZ)
|
||||
|
||||
# Always build position independent code for relocatability
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
# Various Compiler Flags
|
||||
if(MANIFOLD_FUZZ)
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(FATAL_ERROR "fuzztest only supports clang")
|
||||
endif()
|
||||
# we should enable debug checks
|
||||
set(MANIFOLD_DEBUG ON)
|
||||
# enable fuzztest fuzzing mode
|
||||
set(FUZZTEST_FUZZING_MODE ON)
|
||||
# address sanitizer required
|
||||
add_compile_options(-fsanitize=address)
|
||||
add_link_options(-fsanitize=address)
|
||||
endif()
|
||||
|
||||
if(TRACY_ENABLE)
|
||||
option(CMAKE_BUILD_TYPE "Build type" RelWithDebInfo)
|
||||
if(TRACY_MEMORY_USAGE)
|
||||
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} -DTRACY_MEMORY_USAGE)
|
||||
endif()
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
|
||||
endif()
|
||||
else()
|
||||
option(CMAKE_BUILD_TYPE "Build type" Release)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
message("Building for Emscripten")
|
||||
set(MANIFOLD_FLAGS -fexceptions -D_LIBCUDACXX_HAS_THREAD_API_EXTERNAL -D_LIBCUDACXX_HAS_THREAD_API_CUDA)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1 -fexceptions -sDISABLE_EXCEPTION_CATCHING=0")
|
||||
set(MANIFOLD_PYBIND OFF)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_PYBIND)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
if(Python_VERSION VERSION_LESS 3.12)
|
||||
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
|
||||
else()
|
||||
find_package(Python COMPONENTS Interpreter Development.SABIModule REQUIRED)
|
||||
add_link_options(-sALLOW_MEMORY_GROWTH=1)
|
||||
add_link_options(-sMAXIMUM_MEMORY=4294967296)
|
||||
if(MANIFOLD_PAR)
|
||||
set(CMAKE_THREAD_LIBS_INIT "-pthread")
|
||||
add_compile_options(-pthread)
|
||||
# mimalloc is needed for good performance
|
||||
add_link_options(-sMALLOC=mimalloc)
|
||||
add_link_options(-sPTHREAD_POOL_SIZE=4)
|
||||
# The default stack size apparently causes problem when parallelization is
|
||||
# enabled.
|
||||
add_link_options(-sSTACK_SIZE=30MB)
|
||||
add_link_options(-sINITIAL_MEMORY=32MB)
|
||||
endif()
|
||||
if(MANIFOLD_DEBUG)
|
||||
list(APPEND MANIFOLD_FLAGS -fexceptions)
|
||||
add_link_options(-fexceptions)
|
||||
add_link_options(-sDISABLE_EXCEPTION_CATCHING=0)
|
||||
endif()
|
||||
set(MANIFOLD_PYBIND OFF)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
endif()
|
||||
|
||||
if(CMAKE_EXPORT_COMPILE_COMMANDS AND NOT EMSCRIPTEN)
|
||||
# for nixos
|
||||
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
|
||||
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
|
||||
set(
|
||||
CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
|
||||
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}
|
||||
)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} /DNOMINMAX)
|
||||
if(MSVC)
|
||||
list(APPEND MANIFOLD_FLAGS /DNOMINMAX /bigobj)
|
||||
else()
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused -Wno-array-bounds -Wno-stringop-overflow)
|
||||
else()
|
||||
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused)
|
||||
list(
|
||||
APPEND
|
||||
WARNING_FLAGS
|
||||
-Wall
|
||||
-Wno-unknown-warning-option
|
||||
-Wno-unused
|
||||
-Wno-shorten-64-to-32
|
||||
)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
list(APPEND WARNING_FLAGS -Wno-format)
|
||||
endif()
|
||||
elseif(PROJECT_IS_TOP_LEVEL)
|
||||
# only do -Werror if we are the top level project
|
||||
list(APPEND WARNING_FLAGS -Werror)
|
||||
endif()
|
||||
list(APPEND MANIFOLD_FLAGS ${WARNING_FLAGS})
|
||||
if(
|
||||
MANIFOLD_OPTIMIZED
|
||||
OR "${CMAKE_BUILD_TYPE}" STREQUAL "Release"
|
||||
OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo"
|
||||
)
|
||||
list(APPEND MANIFOLD_FLAGS -O3)
|
||||
endif()
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
|
||||
list(APPEND MANIFOLD_FLAGS -fno-omit-frame-pointer)
|
||||
endif()
|
||||
if(CODE_COVERAGE)
|
||||
list(
|
||||
APPEND
|
||||
MANIFOLD_FLAGS
|
||||
-coverage
|
||||
-fno-inline-small-functions
|
||||
-fkeep-inline-functions
|
||||
-fkeep-static-functions
|
||||
)
|
||||
add_link_options(-coverage)
|
||||
endif()
|
||||
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} ${WARNING_FLAGS} -O3)
|
||||
endif()
|
||||
|
||||
if(CODE_COVERAGE AND NOT MSVC)
|
||||
set(COVERAGE_FLAGS -coverage -fno-inline-small-functions -fkeep-inline-functions -fkeep-static-functions)
|
||||
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} ${COVERAGE_FLAGS})
|
||||
add_link_options("-coverage")
|
||||
endif()
|
||||
# RPath settings
|
||||
set(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
|
||||
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR})
|
||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
list(
|
||||
FIND
|
||||
CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES
|
||||
${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}
|
||||
isSystemDir
|
||||
)
|
||||
if("${isSystemDir}" STREQUAL "-1")
|
||||
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR})
|
||||
endif("${isSystemDir}" STREQUAL "-1")
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(${PROJECT_SOURCE_DIR}/cmake/manifoldDeps.cmake)
|
||||
include(${PROJECT_SOURCE_DIR}/cmake/configHelper.cmake)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(bindings)
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
add_subdirectory(meshIO)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_TEST)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(extras)
|
||||
endif()
|
||||
|
||||
include(${PROJECT_SOURCE_DIR}/cmake/info.cmake)
|
||||
|
||||
# note that the path ${CMAKE_CURRENT_BINARY_DIR}/include is included when we
|
||||
# build the manifold target (as ${PROJECT_SOURCE_DIR}/include), so users can
|
||||
# include manifold/version.h without installing our library.
|
||||
configure_file(
|
||||
cmake/version.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/manifold/version.h
|
||||
@ONLY
|
||||
)
|
||||
set_source_files_properties(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/manifold/version.h
|
||||
PROPERTIES GENERATED TRUE
|
||||
)
|
||||
|
||||
# If it's an EMSCRIPTEN build, we're done
|
||||
if(EMSCRIPTEN)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# CMake exports
|
||||
configure_file(
|
||||
cmake/manifoldConfig.cmake.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifoldConfig.cmake
|
||||
@ONLY
|
||||
)
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/cmake/manifoldConfigVersion.cmake
|
||||
VERSION ${MANIFOLD_VERSION}
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
|
||||
# Location of inputs for CMake find_package - see:
|
||||
# https://cmake.org/cmake/help/latest/command/find_package.html
|
||||
set(EXPORT_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake)
|
||||
|
||||
install(
|
||||
EXPORT manifoldTargets
|
||||
NAMESPACE manifold::
|
||||
DESTINATION ${EXPORT_INSTALL_DIR}/manifold
|
||||
)
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/cmake/manifoldConfigVersion.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifoldConfig.cmake
|
||||
DESTINATION ${EXPORT_INSTALL_DIR}/manifold
|
||||
)
|
||||
|
||||
# install public headers
|
||||
set(
|
||||
MANIFOLD_PUBLIC_HDRS
|
||||
common.h
|
||||
linalg.h
|
||||
manifold.h
|
||||
optional_assert.h
|
||||
polygon.h
|
||||
vec_view.h
|
||||
$<$<BOOL:${MANIFOLD_CROSS_SECTION}>:cross_section.h>
|
||||
$<$<BOOL:${MANIFOLD_EXPORT}>:meshIO.h>
|
||||
)
|
||||
list(TRANSFORM MANIFOLD_PUBLIC_HDRS PREPEND include/manifold/)
|
||||
list(
|
||||
APPEND
|
||||
MANIFOLD_PUBLIC_HDRS
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/manifold/version.h
|
||||
)
|
||||
|
||||
install(
|
||||
FILES ${MANIFOLD_PUBLIC_HDRS}
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/manifold
|
||||
)
|
||||
|
||||
# PkgConfig file
|
||||
if(MANIFOLD_PAR)
|
||||
set(TEMPLATE_OPTIONAL_TBB "tbb")
|
||||
endif()
|
||||
if(MANIFOLD_CROSS_SECTION)
|
||||
set(TEMPLATE_OPTIONAL_CLIPPER "Clipper2")
|
||||
endif()
|
||||
configure_file(
|
||||
cmake/manifold.pc.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.pc
|
||||
@ONLY
|
||||
)
|
||||
install(
|
||||
FILES ${CMAKE_CURRENT_BINARY_DIR}/manifold.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
|
||||
)
|
||||
|
||||
@@ -1,58 +1,89 @@
|
||||
# About Manifold
|
||||
|
||||
[](https://codecov.io/github/elalish/manifold)
|
||||
[](https://badge.fury.io/py/manifold3d)
|
||||
[](https://badge.fury.io/js/manifold-3d)
|
||||
[](https://twitter.com/intent/follow?screen_name=manifoldcad)
|
||||
|
||||
[**C++ Documentation**](https://manifoldcad.org/docs/html/classmanifold_1_1_manifold.html) | [**TS Documentation**](https://manifoldcad.org/jsdocs) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://manifoldcad.org/model-viewer.html)
|
||||
|
||||
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Manifold also supports arbitrary vertex properties and enables mapping of materials for rendering use-cases. Our primary goal is reliability: guaranteed manifold output without caveats or edge cases. Our secondary goal is performance: efficient algorithms that make extensive use of parallelization, or pipelining when only a single thread is available.
|
||||
|
||||
## Users
|
||||
|
||||
[OpenSCAD](https://openscad.org/), [IFCjs](https://ifcjs.github.io/info/), [Grid.Space](https://grid.space/), and [OCADml](https://github.com/OCADml/OManifold) have all integrated our Manifold geometry kernel! Why? Because its reliability is guaranteed and it's 1,000 times faster than other libraries. See our [usage](https://github.com/elalish/manifold/discussions/340) and [performance](https://github.com/elalish/manifold/discussions/383) discussions for all the latest and to add your own projects & analyses.
|
||||
Here is an incomplete list of our users, whose integrations may be anywhere from in-progress to released. Please feel free to send a PR to update this list with your own project - it's quite difficult for us to keep track.
|
||||
|
||||
For example, here is a log-log plot of Manifold's performance vs. earlier OpenSCAD geometry backends:
|
||||
| | | |
|
||||
| --- | --- | --- |
|
||||
| [OpenSCAD](https://openscad.org/) | [IFCjs](https://ifcjs.github.io/info/) | [Dactyl Web Configurator](https://github.com/rianadon/dactyl-configurator) |
|
||||
| [Nomad Sculpt](https://apps.apple.com/us/app/id1519508653?mt=8&platform=ipad) | [Grid.Space](https://grid.space/) | [badcad](https://github.com/wrongbad/badcad) |
|
||||
| [Godot Engine](https://godotengine.org/) | [OCADml](https://github.com/OCADml/OManifold) | [Flitter](https://flitter.readthedocs.io/en/latest/) |
|
||||
| [BRL-CAD](https://brlcad.org/) | [PolygonJS](https://polygonjs.com/) | [Spherene](https://spherene.ch/) |
|
||||
| [Babylon.js](https://doc.babylonjs.com/features/featuresDeepDive/mesh/mergeMeshes#merging-meshes-with-constructive-solid-geometry) | [trimesh](https://trimesh.org/) | [Gypsum](https://github.com/playkostudios/gypsum) |
|
||||
| [Valence 3D](https://apps.apple.com/us/app/valence-3d/id6450967410?mt=8&platform=ipad) | | [bitbybit.dev](https://bitbybit.dev) |
|
||||
|
||||
<img src="https://elalish.github.io/manifold/samples/models/perfSpheres.png" width="350px"/>
|
||||
### Bindings & Packages
|
||||
|
||||
## [ManifoldCAD.org](https://manifoldcad.org)
|
||||
Manifold has bindings to many other languages, some maintained in this repository, and others elsewhere. It can also be built in C++ via [vcpkg](https://github.com/microsoft/vcpkg.git).
|
||||
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app. Our WASM is not multithreaded yet, but it's still quite fast and a good way to test out our Manifold library.
|
||||
| Language | Packager | Name | Maintenance |
|
||||
| --- | --- | --- | --- |
|
||||
| C | N/A | N/A | internal |
|
||||
| C++ | vcpkg | [manifold](https://github.com/microsoft/vcpkg/tree/master/ports/manifold) | external |
|
||||
| TS/JS | npm | [manifold-3d](https://www.npmjs.com/package/manifold-3d) | internal |
|
||||
| Python | PyPI | [manifold3d](https://pypi.org/project/manifold3d/) | internal |
|
||||
| Java | N/A | [manifold](https://github.com/SovereignShop/manifold) | external |
|
||||
| Clojure | N/A | [clj-manifold3d](https://github.com/SovereignShop/clj-manifold3d) | external |
|
||||
| C# | NuGet | [ManifoldNET](https://www.nuget.org/packages/ManifoldNET) | external |
|
||||
| Julia | Packages | [ManifoldBindings.jl](https://juliapackages.com/p/manifoldbindings) | external |
|
||||
| OCaml | N/A | [OManifold](https://ocadml.github.io/OManifold/OManifold/index.html) | external |
|
||||
|
||||

|
||||
## Frontend Sandboxes
|
||||
|
||||
### Note for Firefox users
|
||||
### [ManifoldCAD.org](https://manifoldcad.org)
|
||||
|
||||
If you find the editor is stuck on **Loading...**, setting
|
||||
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
|
||||
discussion of the
|
||||
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
|
||||
of this repository may solve the problem.
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app where you script in JS/TS. This uses our npm package, [manifold-3d](https://www.npmjs.com/package/manifold-3d), built via WASM. It's not quite as fast as our raw C++, but it's hard to beat for interoperability.
|
||||
|
||||
# Manifold
|
||||
### [Python Colab Example](https://colab.research.google.com/drive/1VxrFYHPSHZgUbl9TeWzCeovlpXrPQ5J5?usp=sharing)
|
||||
|
||||
[**API Documentation**](https://elalish.github.io/manifold/docs/html/modules.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)
|
||||
If you prefer Python to JS/TS, make your own copy of the example notebook above. It demonstrates interop between our [`manifold3d`](https://pypi.org/project/manifold3d/) PyPI library and the popular [`trimesh`](https://pypi.org/project/trimesh/) library, including showing the interactive model right in the notebook and saving 3D model output.
|
||||
|
||||
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
|
||||

|
||||
|
||||
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm), Python, and C to make this library more portable and easy to use.
|
||||
## Manifold Library
|
||||
|
||||
System Dependencies (note that we will automatically download the dependency if there is no such package on the system):
|
||||
- [`GLM`](https://github.com/g-truc/glm/): A compact header-only vector library.
|
||||
- [`Thrust`](https://github.com/NVIDIA/thrust): NVIDIA's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms)
|
||||
- [`tbb`](https://github.com/oneapi-src/oneTBB/): Intel's thread building blocks library. (only when `MANIFOLD_PAR=TBB` is enabled)
|
||||
- [`gtest`](https://github.com/google/googletest/): Google test library (only when test is enabled, i.e. `MANIFOLD_TEST=ON`)
|
||||
|
||||
Other dependencies:
|
||||
- [`Clipper2`](https://github.com/AngusJohnson/Clipper2): provides our 2D subsystem
|
||||
- [`quickhull`](https://github.com/akuukka/quickhull): 3D convex hull algorithm.
|
||||
|
||||
## What's here
|
||||
|
||||
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
|
||||
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as a level set function for evaluating signed-distance functions (SDF) that improves significantly over Marching Cubes. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. We provide a [`Merge`](https://manifoldcad.org/docs/html/structmanifold_1_1_mesh_g_l_p.html) function to fix slightly non-manifold meshes, but in general you may need one of the automated repair tools that exist mostly for 3D printing.
|
||||
|
||||
The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
|
||||
|
||||
To aid in speed, this library makes extensive use of parallelization, generally through Nvidia's Thrust library. You can switch between the TBB, and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled with parallel backend, the code will still fall back to the serial version of the algorithms if the problem size is small. The WASM build is serial-only for now, but still fast.
|
||||
Manifold has full support for arbitrary vertex properties, and also has IDs that make it easy to keep track of materials and what surfaces belong to what input objects or faces. See our [web example](https://manifoldcad.org/model-viewer.html) for a simple demonstration of combining objects with unique textures.
|
||||
|
||||
> Note: OMP and CUDA backends are now removed
|
||||
Also included are a novel and powerful suite of refining functions for smooth mesh interpolation. They handle smoothing both triangles and quads, as well as keeping polygonal faces flat. You can easily create sharp or small-radius edges where desired, or even drive the curvature by normal vectors.
|
||||
|
||||
To aid in speed, this library makes extensive use of parallelization through TBB, if enabled. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled with parallel backend, the code will still fall back to the serial version of the algorithms if the problem size is small. The WASM build is serial-only for now, but still fast.
|
||||
|
||||
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible.
|
||||
|
||||
### Dependencies
|
||||
|
||||
Manifold no longer has **any** required dependencies! However, we do have several optional dependencies, of which the first two are strongly encouraged:
|
||||
| Name | CMake Flag | Provides |
|
||||
| --- | --- | --- |
|
||||
| [`TBB`](https://github.com/oneapi-src/oneTBB/) |`MANIFOLD_PAR=ON` | Parallel acceleration |
|
||||
| [`Clipper2`](https://github.com/AngusJohnson/Clipper2) | `MANIFOLD_CROSS_SECTION=ON` | 2D: [`CrossSection`](https://manifoldcad.org/docs/html/classmanifold_1_1_cross_section.html) |
|
||||
| [`Assimp`](https://github.com/assimp/assimp) | `MANIFOLD_EXPORT=ON` | Basic Mesh I/O |
|
||||
| [`Nanobind`](https://github.com/wjakob/nanobind) | `MANIFOLD_PYBIND=ON` | Python bindings |
|
||||
| [`Emscripten`](https://github.com/emscripten-core/emscripten) | `MANIFOLD_JSBIND=ON` | JS bindings via WASM |
|
||||
| [`GTest`](https://github.com/google/googletest/) | `MANIFOLD_TEST=ON` | Testing framework |
|
||||
|
||||
### 3D Formats
|
||||
|
||||
Please avoid saving to STL files! They are lossy and inefficient - when saving a manifold mesh to STL there is no guarantee that the re-imported mesh will still be manifold, as the topology is lost. Please consider using [3MF](https://3mf.io/) instead, as this format was designed from the beginning for manifold meshes representing solid objects.
|
||||
|
||||
If you use vertex properties for things like interpolated normals or texture UV coordinates, [glTF](https://www.khronos.org/Gltf) is recommended, specifically using the [`EXT_mesh_manifold`](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_manifold/README.md) extension. This allows for the lossless and efficient transmission of manifoldness even with property boundaries. Try our [make-manifold](https://manifoldcad.org/make-manifold) page to add this extension to your existing glTF/GLB models.
|
||||
|
||||
Manifold provides an optional [`MeshIO`](https://manifoldcad.org/docs/html/group___mesh_i_o.html) component based on [Assimp](https://assimp.org/), but it is limited in functionality and is primarily to aid in testing. If you are using our npm module, we have a much more capable [gltf-io.ts](https://github.com/elalish/manifold/blob/master/bindings/wasm/examples/gltf-io.ts) you can use instead. For other languages we strongly recommend using existing packages that focus on 3D file I/O, e.g. [trimesh](https://trimesh.org/) for Python, particularly when using vertex properties or materials.
|
||||
|
||||
## Building
|
||||
|
||||
Only CMake, a C++ compiler, and Python are required to be installed and set up to build this library (it has been tested with GCC, LLVM, MSVC). However, a variety of optional dependencies can bring in more functionality, see below.
|
||||
@@ -68,25 +99,49 @@ test/manifold_test
|
||||
```
|
||||
|
||||
CMake flags (usage e.g. `-DMANIFOLD_DEBUG=ON`):
|
||||
- `MANIFOLD_JSBIND=[OFF, <ON>]`: Build js binding when using emscripten.
|
||||
- `MANIFOLD_JSBIND=[OFF, <ON>]`: Build js binding (when using the emscripten toolchain).
|
||||
- `MANIFOLD_CBIND=[<OFF>, ON]`: Build C FFI binding.
|
||||
- `MANIFOLD_PYBIND=[OFF, <ON>]`: Build python binding.
|
||||
- `MANIFOLD_PAR=[<NONE>, TBB]`: Provides multi-thread parallelization, requires `libtbb-dev` if `TBB` backend is selected.
|
||||
- `MANIFOLD_EXPORT=[<OFF>, ON]`: Enables GLB export of 3D models from the tests, requires `libassimp-dev`.
|
||||
- `MANIFOLD_DEBUG=[<OFF>, ON]`: Enables internal assertions and exceptions.
|
||||
- `MANIFOLD_TEST=[OFF, <ON>]`: Build unittests.
|
||||
- `MANIFOLD_PYBIND=[OFF, <ON>]`: Build python binding, requires `nanobind`.
|
||||
- `MANIFOLD_PAR=[<OFF>, ON]`: Enables multi-thread parallelization, requires `tbb`.
|
||||
- `MANIFOLD_CROSS_SECTION=[OFF, <ON>]`: Build CrossSection for 2D support (needed by language bindings), requires `Clipper2`.
|
||||
- `MANIFOLD_EXPORT=[<OFF>, ON]`: Enables `MeshIO` and GLB export of 3D models from the tests, requires `assimp`.
|
||||
- `MANIFOLD_DEBUG=[<OFF>, ON]`: Enables internal assertions and exceptions. This incurs around 20% runtime overhead.
|
||||
- `MANIFOLD_TEST=[OFF, <ON>]`: Build unit tests, requires `GTest`.
|
||||
- `TRACY_ENABLE=[<OFF>, ON]`: Enable integration with tracy profiler.
|
||||
See profiling section below.
|
||||
- `BUILD_TEST_CGAL=[<OFF>, ON]`: Builds a CGAL-based performance [comparison](https://github.com/elalish/manifold/tree/master/extras), requires `libcgal-dev`.
|
||||
|
||||
Offline building:
|
||||
- `FETCHCONTENT_SOURCE_DIR_GLM`: path to glm source.
|
||||
- `FETCHCONTENT_SOURCE_DIR_GOOGLETEST`: path to googletest source.
|
||||
- `FETCHCONTENT_SOURCE_DIR_THRUST`: path to NVIDIA thrust source.
|
||||
Dependency version override:
|
||||
- `MANIFOLD_USE_BUILTIN_TBB=[<OFF>, ON]`: Use builtin version of tbb.
|
||||
- `MANIFOLD_USE_BUILTIN_CLIPPER2=[<OFF>, ON]`: Use builtin version of clipper2.
|
||||
- `MANIFOLD_USE_BUILTIN_NANOBIND=[<OFF>, ON]`: Use builtin version of nanobind.
|
||||
|
||||
> Note: These three options can force the build to avoid using the system
|
||||
> version of the dependency. This will either use the provided source directory
|
||||
> via `FETCHCONTENT_SOURCE_DIR_*` (see below), or fetch the source from GitHub.
|
||||
> Note that the dependency will be built as static dependency to avoid dynamic
|
||||
> library conflict. When the system package is unavailable, the option will be
|
||||
> automatically set to true.
|
||||
|
||||
Offline building (with missing dependencies/dependency version override):
|
||||
- `MANIFOLD_DOWNLOADS=[OFF, <ON>]`: Automatically download missing dependencies.
|
||||
Need to set `FETCHCONTENT_SOURCE_DIR_*` if the dependency `*` is missing.
|
||||
- `FETCHCONTENT_SOURCE_DIR_TBB`: path to tbb source (if `MANIFOLD_PAR` is enabled).
|
||||
- `FETCHCONTENT_SOURCE_DIR_CLIPPER2`: path to tbb source (if `MANIFOLD_CROSS_SECTION` is enabled).
|
||||
- `FETCHCONTENT_SOURCE_DIR_NANOBIND`: path to nanobind source (if `MANIFOLD_PYBIND` is enabled).
|
||||
- `FETCHCONTENT_SOURCE_DIR_GOOGLETEST`: path to googletest source (if `MANIFOLD_TEST` is enabled).
|
||||
|
||||
> Note: When `FETCHCONTENT_SOURCE_DIR_*` is set, CMake will use the provided
|
||||
> source directly without downloading regardless of the value of
|
||||
> `MANIFOLD_DOWNLOADS`.
|
||||
|
||||
The build instructions used by our CI are in [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml), which is a good source to check if something goes wrong and for instructions specific to other platforms, like Windows.
|
||||
|
||||
### WASM
|
||||
|
||||
> Note: While we support compiling with `MANIFOLD_PAR=ON` in recent emscripten
|
||||
> versions, this is not recommended as there can potentially be memory
|
||||
> corruption issues.
|
||||
|
||||
To build the JS WASM library, first install NodeJS and set up emscripten:
|
||||
|
||||
(on Mac):
|
||||
@@ -108,7 +163,7 @@ Then build:
|
||||
cd manifold
|
||||
mkdir buildWASM
|
||||
cd buildWASM
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel .. && emmake make
|
||||
node test/manifold_test.js
|
||||
```
|
||||
|
||||
@@ -120,7 +175,7 @@ use the extension, please add `$BUILD_DIR/bindings/python` to your `PYTHONPATH`,
|
||||
can be found in `bindings/python/examples`. To see exported samples, run:
|
||||
```
|
||||
sudo apt install pkg-config libpython3-dev python3 python3-distutils python3-pip
|
||||
pip install trimesh
|
||||
pip install trimesh pytest
|
||||
python3 run_all.py -e
|
||||
```
|
||||
|
||||
@@ -134,14 +189,36 @@ python binding documentation:
|
||||
|
||||
For more detailed documentation, please refer to the C++ API.
|
||||
|
||||
### Windows Shenanigans
|
||||
|
||||
Windows users should build with `-DBUILD_SHARED_LIBS=OFF`, as enabling shared
|
||||
libraries in general makes things very complicated.
|
||||
|
||||
The DLL file for manifoldc (C FFI bindings) when built with msvc is in `${CMAKE_BINARY_DIR}/bin/${BUILD_TYPE}/manifoldc.dll`.
|
||||
For example, for the following command, the path relative to the project root directory is `build/bin/Release/manifoldc.dll`.
|
||||
```sh
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -A x64 -B build
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! A lower barrier contribution is to simply make a PR that adds a test, especially if it repros an issue you've found. Simply name it prepended with DISABLED_, so that it passes the CI. That will be a very strong signal to me to fix your issue. However, if you know how to fix it yourself, then including the fix in your PR would be much appreciated!
|
||||
|
||||
## Profiling
|
||||
### Formatting
|
||||
|
||||
There is a formatting script `format.sh` that automatically formats everything.
|
||||
It requires clang-format, black formatter for python and [gersemi](https://github.com/BlankSpruce/gersemi) for formatting cmake files.
|
||||
|
||||
Note that our script can run with clang-format older than 18, but the GitHub
|
||||
action check may fail due to slight differences between different versions of
|
||||
clang-format. In that case, either update your clang-format version or apply the
|
||||
patch from the GitHub action log.
|
||||
|
||||
### Profiling
|
||||
|
||||
There is now basic support for the [Tracy profiler](https://github.com/wolfpld/tracy) for our tests.
|
||||
To enable tracing, compile with `-DTRACY_ENABLE=on` cmake option, and run the test with Tracy server running.
|
||||
To enable memory profiling in addition to tracing, compile with `-DTRACY_MEMORY_USAGE=ON` in addition to `-DTRACY_ENABLE=ON`.
|
||||
|
||||
## About the author
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
1. Go through Github issues to verify bugs have been fixed and closed.
|
||||
1. Verify [ManifoldCAD.org](https://manifoldcad.org) - check a few examples, run them, download a GLB and a 3MF.
|
||||
1. Verify our three.js [example](https://manifoldcad.org/three) is functional.
|
||||
1. Verify our model-viewer [example](https://manifoldcad.org/model-viewer) is functional - select Union and Intersection.
|
||||
1. Verify [make-manifold](https://manifoldcad.org/make-manifold) is functional. Try dropping [DragonAttenuation.glb](https://github.com/KhronosGroup/glTF-Sample-Assets/blob/main/Models/DragonAttenuation/glTF-Binary/DragonAttenuation.glb) in and verify you can select "View Manifold GLB" and that the dragon is still present while the backdrop is removed. Download the GLB.
|
||||
1. Make a new branch called the version, e.g. v2.3.0.
|
||||
1. Use VSCode to search and replace the old version with the new - so far in test-cmake.sh, flake.nix, pyproject.toml, and package.json.
|
||||
1. Also update CMakeLists.txt version by searching for "set(MANIFOLD_VERSION_".
|
||||
1. Commit, push, open a PR, verify tests pass, merge.
|
||||
1. On Github, draft a new release, make a new tag with the version number, add release notes, and publish.
|
||||
1. Check the Actions and verify that both PyPI and npm publishing actions ran successfully.
|
||||
1. Verify the npm [package](https://www.npmjs.com/package/manifold-3d?activeTab=code) looks good - unpacked size should be close to 1MB.
|
||||
1. Verify PyPI [package](https://pypi.org/project/manifold3d/#files) looks good - a bunch of built distributions ranging from ~600kB to ~1.1MB.
|
||||
1. If there's a problem with release deployment, the release workflows can be triggered separately, manually for any branch, under the Actions tab.
|
||||
@@ -12,14 +12,18 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_CBIND)
|
||||
add_subdirectory(c)
|
||||
if(NOT MANIFOLD_CROSS_SECTION)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
add_subdirectory(c)
|
||||
endif()
|
||||
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
|
||||
add_subdirectory(python)
|
||||
add_subdirectory(python)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN AND MANIFOLD_JSBIND)
|
||||
add_subdirectory(wasm)
|
||||
add_subdirectory(wasm)
|
||||
endif()
|
||||
|
||||
@@ -1,33 +1,50 @@
|
||||
project(manifoldc)
|
||||
# Copyright 2024 The Manifold Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
add_library(${PROJECT_NAME} manifoldc.cpp conv.cpp)
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../include
|
||||
)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(manifoldc.cpp conv.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
add_library(
|
||||
manifoldc
|
||||
manifoldc.cpp
|
||||
conv.cpp
|
||||
box.cpp
|
||||
cross.cpp
|
||||
rect.cpp
|
||||
)
|
||||
exportlib(manifoldc)
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE meshIO)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -DMANIFOLD_EXPORT)
|
||||
target_sources(manifoldc PRIVATE meshIOc.cpp)
|
||||
target_compile_options(manifoldc PUBLIC -DMANIFOLD_EXPORT)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE manifold sdf cross_section
|
||||
)
|
||||
target_link_libraries(manifoldc PRIVATE manifold)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
target_include_directories(
|
||||
manifoldc
|
||||
PUBLIC
|
||||
$<INSTALL_INTERFACE:include>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
)
|
||||
target_compile_options(manifoldc PRIVATE ${MANIFOLD_FLAGS})
|
||||
|
||||
install(TARGETS manifoldc EXPORT manifoldTargets)
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
COMPONENT bindings
|
||||
)
|
||||
install(
|
||||
FILES include/manifoldc.h include/types.h
|
||||
DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/manifold
|
||||
COMPONENT devel
|
||||
FILES include/manifold/manifoldc.h include/manifold/types.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/manifold
|
||||
)
|
||||
|
||||
@@ -12,15 +12,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
#include "./conv.h"
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2) {
|
||||
auto p1 = glm::vec3(x1, y1, z1);
|
||||
auto p2 = glm::vec3(x2, y2, z2);
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
ManifoldBox *manifold_box(void *mem, double x1, double y1, double z1, double x2,
|
||||
double y2, double z2) {
|
||||
auto p1 = vec3(x1, y1, z1);
|
||||
auto p2 = vec3(x2, y2, z2);
|
||||
auto box = new (mem) Box(p1, p2);
|
||||
return to_c(box);
|
||||
}
|
||||
@@ -39,10 +41,10 @@ ManifoldVec3 manifold_box_center(ManifoldBox *b) {
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
float manifold_box_scale(ManifoldBox *b) { return from_c(b)->Scale(); }
|
||||
double manifold_box_scale(ManifoldBox *b) { return from_c(b)->Scale(); }
|
||||
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
int manifold_box_contains_pt(ManifoldBox *b, double x, double y, double z) {
|
||||
auto p = vec3(x, y, z);
|
||||
return from_c(b)->Contains(p);
|
||||
}
|
||||
|
||||
@@ -52,9 +54,9 @@ int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
void manifold_box_include_pt(ManifoldBox *b, double x, double y, double z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto p = vec3(x, y, z);
|
||||
box.Union(p);
|
||||
}
|
||||
|
||||
@@ -63,31 +65,31 @@ ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b) {
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, double x1,
|
||||
double y1, double z1, double x2, double y2,
|
||||
double z2, double x3, double y3, double z3,
|
||||
double x4, double y4, double z4) {
|
||||
auto mat = mat3x4({x1, y1, z1}, {x2, y2, z2}, {x3, y3, z3}, {x4, y4, z4});
|
||||
auto transformed = from_c(b)->Transform(mat);
|
||||
return to_c(new (mem) Box(transformed));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, double x,
|
||||
double y, double z) {
|
||||
auto p = vec3(x, y, z);
|
||||
auto translated = (*from_c(b)) + p;
|
||||
return to_c(new (mem) Box(translated));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, double x, double y,
|
||||
double z) {
|
||||
auto p = vec3(x, y, z);
|
||||
auto scaled = (*from_c(b)) * p;
|
||||
return to_c(new (mem) Box(scaled));
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, double x, double y, double z) {
|
||||
auto p = vec3(x, y, z);
|
||||
return from_c(b)->DoesOverlap(p);
|
||||
}
|
||||
|
||||
@@ -96,3 +98,6 @@ int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
}
|
||||
|
||||
int manifold_box_is_finite(ManifoldBox *b) { return from_c(b)->IsFinite(); }
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -12,15 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
#include "./conv.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "sdf.h"
|
||||
#include "types.h"
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/cross_section.h"
|
||||
#include "manifold/manifold.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m) {
|
||||
return reinterpret_cast<ManifoldManifold *>(m);
|
||||
@@ -46,14 +45,14 @@ ManifoldPolygons *to_c(manifold::Polygons *m) {
|
||||
return reinterpret_cast<ManifoldPolygons *>(m);
|
||||
}
|
||||
|
||||
ManifoldMesh *to_c(manifold::Mesh *m) {
|
||||
return reinterpret_cast<ManifoldMesh *>(m);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m) {
|
||||
return reinterpret_cast<ManifoldMeshGL *>(m);
|
||||
}
|
||||
|
||||
ManifoldMeshGL64 *to_c(manifold::MeshGL64 *m) {
|
||||
return reinterpret_cast<ManifoldMeshGL64 *>(m);
|
||||
}
|
||||
|
||||
ManifoldOpType to_c(manifold::OpType optype) {
|
||||
ManifoldOpType op = MANIFOLD_ADD;
|
||||
switch (optype) {
|
||||
@@ -119,14 +118,14 @@ ManifoldRect *to_c(manifold::Rect *m) {
|
||||
return reinterpret_cast<ManifoldRect *>(m);
|
||||
}
|
||||
|
||||
ManifoldVec2 to_c(glm::vec2 v) { return {v.x, v.y}; }
|
||||
ManifoldVec2 to_c(vec2 v) { return {v.x, v.y}; }
|
||||
|
||||
ManifoldVec3 to_c(glm::vec3 v) { return {v.x, v.y, v.z}; }
|
||||
ManifoldVec3 to_c(vec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldIVec3 to_c(glm::ivec3 v) { return {v.x, v.y, v.z}; }
|
||||
ManifoldIVec3 to_c(ivec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldProperties to_c(manifold::Properties p) {
|
||||
return {p.surfaceArea, p.volume};
|
||||
ManifoldTriangulation *to_c(std::vector<ivec3> *m) {
|
||||
return reinterpret_cast<ManifoldTriangulation *>(m);
|
||||
}
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m) {
|
||||
@@ -153,14 +152,14 @@ const manifold::Polygons *from_c(ManifoldPolygons *m) {
|
||||
return reinterpret_cast<manifold::Polygons const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m) {
|
||||
return reinterpret_cast<manifold::Mesh const *>(m);
|
||||
}
|
||||
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m) {
|
||||
return reinterpret_cast<manifold::MeshGL const *>(m);
|
||||
}
|
||||
|
||||
const manifold::MeshGL64 *from_c(ManifoldMeshGL64 *m) {
|
||||
return reinterpret_cast<manifold::MeshGL64 const *>(m);
|
||||
}
|
||||
|
||||
OpType from_c(ManifoldOpType optype) {
|
||||
auto op = OpType::Add;
|
||||
switch (optype) {
|
||||
@@ -217,33 +216,37 @@ const manifold::Rect *from_c(ManifoldRect *m) {
|
||||
return reinterpret_cast<manifold::Rect const *>(m);
|
||||
}
|
||||
|
||||
glm::vec2 from_c(ManifoldVec2 v) { return glm::vec2(v.x, v.y); }
|
||||
vec2 from_c(ManifoldVec2 v) { return vec2(v.x, v.y); }
|
||||
|
||||
glm::vec3 from_c(ManifoldVec3 v) { return glm::vec3(v.x, v.y, v.z); }
|
||||
vec3 from_c(ManifoldVec3 v) { return vec3(v.x, v.y, v.z); }
|
||||
|
||||
glm::ivec3 from_c(ManifoldIVec3 v) { return glm::ivec3(v.x, v.y, v.z); }
|
||||
ivec3 from_c(ManifoldIVec3 v) { return ivec3(v.x, v.y, v.z); }
|
||||
|
||||
glm::vec4 from_c(ManifoldVec4 v) { return glm::vec4(v.x, v.y, v.z, v.w); }
|
||||
vec4 from_c(ManifoldVec4 v) { return vec4(v.x, v.y, v.z, v.w); }
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::vec3>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
const std::vector<ivec3> *from_c(ManifoldTriangulation *m) {
|
||||
return reinterpret_cast<std::vector<ivec3> const *>(m);
|
||||
}
|
||||
|
||||
std::vector<vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<vec3>();
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::ivec3>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
std::vector<ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<ivec3>();
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::vec4>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
std::vector<vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length) {
|
||||
auto vec = std::vector<vec4>();
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
|
||||
@@ -16,11 +16,10 @@
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "sdf.h"
|
||||
#include "types.h"
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/cross_section.h"
|
||||
#include "manifold/manifold.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
using namespace manifold;
|
||||
using ManifoldVec = std::vector<Manifold>;
|
||||
@@ -32,15 +31,15 @@ ManifoldCrossSection *to_c(manifold::CrossSection *cs);
|
||||
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv);
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *p);
|
||||
ManifoldPolygons *to_c(manifold::Polygons *ps);
|
||||
ManifoldMesh *to_c(manifold::Mesh *m);
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m);
|
||||
ManifoldMeshGL64 *to_c(manifold::MeshGL64 *m);
|
||||
ManifoldBox *to_c(manifold::Box *m);
|
||||
ManifoldRect *to_c(manifold::Rect *m);
|
||||
ManifoldError to_c(manifold::Manifold::Error error);
|
||||
ManifoldVec2 to_c(glm::vec2 v);
|
||||
ManifoldVec3 to_c(glm::vec3 v);
|
||||
ManifoldIVec3 to_c(glm::ivec3 v);
|
||||
ManifoldProperties to_c(manifold::Properties p);
|
||||
ManifoldVec2 to_c(vec2 v);
|
||||
ManifoldVec3 to_c(vec3 v);
|
||||
ManifoldIVec3 to_c(ivec3 v);
|
||||
ManifoldTriangulation *to_c(std::vector<ivec3> *m);
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m);
|
||||
ManifoldVec *from_c(ManifoldManifoldVec *ms);
|
||||
@@ -48,26 +47,27 @@ const manifold::CrossSection *from_c(ManifoldCrossSection *cs);
|
||||
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv);
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m);
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m);
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m);
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m);
|
||||
const manifold::MeshGL64 *from_c(ManifoldMeshGL64 *m);
|
||||
OpType from_c(ManifoldOpType op);
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule);
|
||||
CrossSection::JoinType from_c(ManifoldJoinType jt);
|
||||
const manifold::Box *from_c(ManifoldBox *m);
|
||||
const manifold::Rect *from_c(ManifoldRect *r);
|
||||
glm::vec2 from_c(ManifoldVec2 v);
|
||||
glm::vec3 from_c(ManifoldVec3 v);
|
||||
glm::ivec3 from_c(ManifoldIVec3 v);
|
||||
glm::vec4 from_c(ManifoldVec4 v);
|
||||
vec2 from_c(ManifoldVec2 v);
|
||||
vec3 from_c(ManifoldVec3 v);
|
||||
ivec3 from_c(ManifoldIVec3 v);
|
||||
vec4 from_c(ManifoldVec4 v);
|
||||
const std::vector<ivec3> *from_c(ManifoldTriangulation *m);
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length);
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length);
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length);
|
||||
std::vector<vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length);
|
||||
std::vector<ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length);
|
||||
std::vector<vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length);
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> vector_of_array(T *ts, size_t length) {
|
||||
auto vec = std::vector<T>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
vec.push_back(ts[i]);
|
||||
}
|
||||
return vec;
|
||||
@@ -12,14 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "conv.h"
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
#include "./conv.h"
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/cross_section.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
ManifoldCrossSection *manifold_cross_section_empty(void *mem) {
|
||||
return to_c(new (mem) CrossSection());
|
||||
}
|
||||
@@ -59,12 +59,12 @@ size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec *csv) {
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_vec_get(
|
||||
void *mem, ManifoldCrossSectionVec *csv, int idx) {
|
||||
void *mem, ManifoldCrossSectionVec *csv, size_t idx) {
|
||||
auto cs = (*from_c(csv))[idx];
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, int idx,
|
||||
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, size_t idx,
|
||||
ManifoldCrossSection *cs) {
|
||||
(*from_c(csv))[idx] = *from_c(cs);
|
||||
}
|
||||
@@ -74,13 +74,13 @@ void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec *csv,
|
||||
return from_c(csv)->push_back(*from_c(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_square(void *mem, float x, float y,
|
||||
int center) {
|
||||
auto cs = CrossSection::Square(glm::vec2(x, y), center);
|
||||
ManifoldCrossSection *manifold_cross_section_square(void *mem, double x,
|
||||
double y, int center) {
|
||||
auto cs = CrossSection::Square(vec2(x, y), center);
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_circle(void *mem, float radius,
|
||||
ManifoldCrossSection *manifold_cross_section_circle(void *mem, double radius,
|
||||
int circular_segments) {
|
||||
auto cs = CrossSection::Circle(radius, circular_segments);
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
@@ -145,46 +145,50 @@ ManifoldCrossSection *manifold_cross_section_hull_polygons(
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y) {
|
||||
auto translated = from_c(cs)->Translate(glm::vec2(x, y));
|
||||
double x, double y) {
|
||||
auto translated = from_c(cs)->Translate(vec2(x, y));
|
||||
return to_c(new (mem) CrossSection(translated));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_rotate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float deg) {
|
||||
double deg) {
|
||||
auto rotated = from_c(cs)->Rotate(deg);
|
||||
return to_c(new (mem) CrossSection(rotated));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_scale(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y) {
|
||||
auto scaled = from_c(cs)->Scale(glm::vec2(x, y));
|
||||
double x, double y) {
|
||||
auto scaled = from_c(cs)->Scale(vec2(x, y));
|
||||
return to_c(new (mem) CrossSection(scaled));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_mirror(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float ax_x, float ax_y) {
|
||||
auto mirrored = from_c(cs)->Mirror(glm::vec2(ax_x, ax_y));
|
||||
double ax_x, double ax_y) {
|
||||
auto mirrored = from_c(cs)->Mirror(vec2(ax_x, ax_y));
|
||||
return to_c(new (mem) CrossSection(mirrored));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_transform(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3) {
|
||||
auto mat = glm::mat3x2(x1, y1, x2, y2, x3, y3);
|
||||
double x1, double y1,
|
||||
double x2, double y2,
|
||||
double x3, double y3) {
|
||||
auto mat = mat2x3({x1, y1}, {x2, y2}, {x3, y3});
|
||||
auto transformed = from_c(cs)->Transform(mat);
|
||||
return to_c(new (mem) CrossSection(transformed));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_warp(
|
||||
void *mem, ManifoldCrossSection *cs, ManifoldVec2 (*fun)(float, float)) {
|
||||
std::function<void(glm::vec2 & v)> warp = [fun](glm::vec2 &v) {
|
||||
v = from_c(fun(v.x, v.y));
|
||||
void *mem, ManifoldCrossSection *cs,
|
||||
ManifoldVec2 (*fun)(double, double, void *), void *ctx) {
|
||||
// Bind function with context argument to one without
|
||||
using namespace std::placeholders;
|
||||
std::function<ManifoldVec2(double, double)> f2 = std::bind(fun, _1, _2, ctx);
|
||||
std::function<void(vec2 & v)> warp = [f2](vec2 &v) {
|
||||
v = from_c(f2(v.x, v.y));
|
||||
};
|
||||
auto warped = from_c(cs)->Warp(warp);
|
||||
return to_c(new (mem) CrossSection(warped));
|
||||
@@ -209,11 +213,11 @@ double manifold_cross_section_area(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->Area();
|
||||
}
|
||||
|
||||
int manifold_cross_section_num_vert(ManifoldCrossSection *cs) {
|
||||
size_t manifold_cross_section_num_vert(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->NumVert();
|
||||
}
|
||||
|
||||
int manifold_cross_section_num_contour(ManifoldCrossSection *cs) {
|
||||
size_t manifold_cross_section_num_contour(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->NumContour();
|
||||
}
|
||||
|
||||
@@ -244,3 +248,6 @@ ManifoldCrossSectionVec *manifold_cross_section_decompose(
|
||||
auto comps = from_c(cs)->Decompose();
|
||||
return to_c(new (mem) CrossSectionVec(comps));
|
||||
}
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdint>
|
||||
@@ -30,14 +30,14 @@ ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
size_t length);
|
||||
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p);
|
||||
size_t manifold_polygons_length(ManifoldPolygons *ps);
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx);
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, size_t idx);
|
||||
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
|
||||
int idx);
|
||||
size_t idx);
|
||||
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
|
||||
ManifoldPolygons *ps,
|
||||
int idx);
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
|
||||
int pt_idx);
|
||||
size_t idx);
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps,
|
||||
size_t simple_idx, size_t pt_idx);
|
||||
|
||||
// Mesh Construction
|
||||
|
||||
@@ -51,6 +51,20 @@ ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
|
||||
float *halfedge_tangent);
|
||||
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m);
|
||||
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m);
|
||||
ManifoldMeshGL *manifold_meshgl_merge(void *mem, ManifoldMeshGL *m);
|
||||
|
||||
ManifoldMeshGL64 *manifold_meshgl64(void *mem, double *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint64_t *tri_verts, size_t n_tris);
|
||||
|
||||
ManifoldMeshGL64 *manifold_meshgl64_w_tangents(void *mem, double *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint64_t *tri_verts,
|
||||
size_t n_tris,
|
||||
double *halfedge_tangent);
|
||||
ManifoldMeshGL64 *manifold_get_meshgl64(void *mem, ManifoldManifold *m);
|
||||
ManifoldMeshGL64 *manifold_meshgl64_copy(void *mem, ManifoldMeshGL64 *m);
|
||||
ManifoldMeshGL64 *manifold_meshgl64_merge(void *mem, ManifoldMeshGL64 *m);
|
||||
|
||||
// SDF
|
||||
// By default, the execution policy (sequential or parallel) of
|
||||
@@ -59,22 +73,13 @@ ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m);
|
||||
// using these bindings from a language that has a runtime lock preventing the
|
||||
// parallel execution of closures, then you should use manifold_level_set_seq to
|
||||
// force sequential execution.
|
||||
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
// The _context variants of manifold_level_set allow a pointer to be passed
|
||||
// back to each invocation of the sdf function pointer, for languages that
|
||||
// need additional data.
|
||||
ManifoldMeshGL *manifold_level_set_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx);
|
||||
ManifoldMeshGL *manifold_level_set_seq_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx);
|
||||
ManifoldManifold *manifold_level_set(void *mem, ManifoldSdf sdf,
|
||||
ManifoldBox *bounds, double edge_length,
|
||||
double level, double tolerance, void *ctx);
|
||||
ManifoldManifold *manifold_level_set_seq(void *mem, ManifoldSdf sdf,
|
||||
ManifoldBox *bounds,
|
||||
double edge_length, double level,
|
||||
double tolerance, void *ctx);
|
||||
|
||||
// Manifold Vectors
|
||||
|
||||
@@ -83,8 +88,8 @@ ManifoldManifoldVec *manifold_manifold_vec(void *mem, size_t sz);
|
||||
void manifold_manifold_vec_reserve(ManifoldManifoldVec *ms, size_t sz);
|
||||
size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms);
|
||||
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
|
||||
int idx);
|
||||
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
|
||||
size_t idx);
|
||||
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, size_t idx,
|
||||
ManifoldManifold *m);
|
||||
void manifold_manifold_vec_push_back(ManifoldManifoldVec *ms,
|
||||
ManifoldManifold *m);
|
||||
@@ -105,11 +110,16 @@ ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *a, ManifoldManifold *b);
|
||||
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset);
|
||||
double normal_x, double normal_y,
|
||||
double normal_z, double offset);
|
||||
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset);
|
||||
double normal_x, double normal_y,
|
||||
double normal_z, double offset);
|
||||
|
||||
// 3D to 2D
|
||||
|
||||
ManifoldPolygons *manifold_slice(void *mem, ManifoldManifold *m, double height);
|
||||
ManifoldPolygons *manifold_project(void *mem, ManifoldManifold *m);
|
||||
|
||||
// Convex Hulls
|
||||
|
||||
@@ -119,70 +129,95 @@ ManifoldManifold *manifold_hull_pts(void *mem, ManifoldVec3 *ps, size_t length);
|
||||
|
||||
// Manifold Transformations
|
||||
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4);
|
||||
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
|
||||
float ny, float nz);
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, double x,
|
||||
double y, double z);
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, double x,
|
||||
double y, double z);
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, double x,
|
||||
double y, double z);
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, double x1,
|
||||
double y1, double z1, double x2, double y2,
|
||||
double z2, double x3, double y3, double z3,
|
||||
double x4, double y4, double z4);
|
||||
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, double nx,
|
||||
double ny, double nz);
|
||||
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
|
||||
ManifoldVec3 (*fun)(float, float, float));
|
||||
ManifoldVec3 (*fun)(double, double, double,
|
||||
void *),
|
||||
void *ctx);
|
||||
ManifoldManifold *manifold_smooth_by_normals(void *mem, ManifoldManifold *m,
|
||||
int normalIdx);
|
||||
ManifoldManifold *manifold_smooth_out(void *mem, ManifoldManifold *m,
|
||||
double minSharpAngle,
|
||||
double minSmoothness);
|
||||
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine);
|
||||
ManifoldManifold *manifold_refine_to_length(void *mem, ManifoldManifold *m,
|
||||
double length);
|
||||
ManifoldManifold *manifold_refine_to_tolerance(void *mem, ManifoldManifold *m,
|
||||
double tolerance);
|
||||
|
||||
// Manifold Shapes / Constructors
|
||||
|
||||
ManifoldManifold *manifold_empty(void *mem);
|
||||
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m);
|
||||
ManifoldManifold *manifold_tetrahedron(void *mem);
|
||||
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
|
||||
ManifoldManifold *manifold_cube(void *mem, double x, double y, double z,
|
||||
int center);
|
||||
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
|
||||
float radius_high, int circular_segments,
|
||||
ManifoldManifold *manifold_cylinder(void *mem, double height, double radius_low,
|
||||
double radius_high, int circular_segments,
|
||||
int center);
|
||||
ManifoldManifold *manifold_sphere(void *mem, float radius,
|
||||
ManifoldManifold *manifold_sphere(void *mem, double radius,
|
||||
int circular_segments);
|
||||
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh);
|
||||
ManifoldManifold *manifold_of_meshgl64(void *mem, ManifoldMeshGL64 *mesh);
|
||||
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
|
||||
int *half_edges, float *smoothness,
|
||||
int n_idxs);
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
|
||||
float height, int slices,
|
||||
float twist_degrees, float scale_x,
|
||||
float scale_y);
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
|
||||
int circular_segments);
|
||||
size_t *half_edges, double *smoothness,
|
||||
size_t n_idxs);
|
||||
ManifoldManifold *manifold_smooth64(void *mem, ManifoldMeshGL64 *mesh,
|
||||
size_t *half_edges, double *smoothness,
|
||||
size_t n_idxs);
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldPolygons *cs,
|
||||
double height, int slices,
|
||||
double twist_degrees, double scale_x,
|
||||
double scale_y);
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldPolygons *cs,
|
||||
int circular_segments,
|
||||
double revolve_degrees);
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms);
|
||||
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m);
|
||||
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m);
|
||||
|
||||
// Manifold Info
|
||||
|
||||
int manifold_is_empty(ManifoldManifold *m);
|
||||
ManifoldError manifold_status(ManifoldManifold *m);
|
||||
int manifold_num_vert(ManifoldManifold *m);
|
||||
int manifold_num_edge(ManifoldManifold *m);
|
||||
int manifold_num_tri(ManifoldManifold *m);
|
||||
size_t manifold_num_vert(ManifoldManifold *m);
|
||||
size_t manifold_num_edge(ManifoldManifold *m);
|
||||
size_t manifold_num_tri(ManifoldManifold *m);
|
||||
size_t manifold_num_prop(ManifoldManifold *m);
|
||||
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m);
|
||||
float manifold_precision(ManifoldManifold *m);
|
||||
double manifold_epsilon(ManifoldManifold *m);
|
||||
int manifold_genus(ManifoldManifold *m);
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m);
|
||||
int manifold_get_circular_segments(float radius);
|
||||
double manifold_surface_area(ManifoldManifold *m);
|
||||
double manifold_volume(ManifoldManifold *m);
|
||||
int manifold_get_circular_segments(double radius);
|
||||
int manifold_original_id(ManifoldManifold *m);
|
||||
uint32_t manifold_reserve_ids(uint32_t n);
|
||||
ManifoldManifold *manifold_set_properties(
|
||||
void *mem, ManifoldManifold *m, int num_prop,
|
||||
void (*fun)(float *new_prop, ManifoldVec3 position, const float *old_prop));
|
||||
void (*fun)(double *new_prop, ManifoldVec3 position, const double *old_prop,
|
||||
void *ctx),
|
||||
void *ctx);
|
||||
ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
|
||||
int gaussian_idx, int mean_idx);
|
||||
double manifold_min_gap(ManifoldManifold *m, ManifoldManifold *other,
|
||||
double searchLength);
|
||||
ManifoldManifold *manifold_calculate_normals(void *mem, ManifoldManifold *m,
|
||||
int normal_idx,
|
||||
double min_sharp_angle);
|
||||
|
||||
// CrossSection Shapes/Constructors
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_empty(void *mem);
|
||||
ManifoldCrossSection *manifold_cross_section_copy(void *mem,
|
||||
ManifoldCrossSection *cs);
|
||||
@@ -191,9 +226,9 @@ ManifoldCrossSection *manifold_cross_section_of_simple_polygon(
|
||||
ManifoldCrossSection *manifold_cross_section_of_polygons(void *mem,
|
||||
ManifoldPolygons *p,
|
||||
ManifoldFillRule fr);
|
||||
ManifoldCrossSection *manifold_cross_section_square(void *mem, float x, float y,
|
||||
int center);
|
||||
ManifoldCrossSection *manifold_cross_section_circle(void *mem, float radius,
|
||||
ManifoldCrossSection *manifold_cross_section_square(void *mem, double x,
|
||||
double y, int center);
|
||||
ManifoldCrossSection *manifold_cross_section_circle(void *mem, double radius,
|
||||
int circular_segments);
|
||||
ManifoldCrossSection *manifold_cross_section_compose(
|
||||
void *mem, ManifoldCrossSectionVec *csv);
|
||||
@@ -208,8 +243,8 @@ void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec *csv,
|
||||
size_t sz);
|
||||
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec *csv);
|
||||
ManifoldCrossSection *manifold_cross_section_vec_get(
|
||||
void *mem, ManifoldCrossSectionVec *csv, int idx);
|
||||
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, int idx,
|
||||
void *mem, ManifoldCrossSectionVec *csv, size_t idx);
|
||||
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, size_t idx,
|
||||
ManifoldCrossSection *cs);
|
||||
void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec *csv,
|
||||
ManifoldCrossSection *cs);
|
||||
@@ -245,23 +280,26 @@ ManifoldCrossSection *manifold_cross_section_hull_polygons(
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y);
|
||||
double x, double y);
|
||||
ManifoldCrossSection *manifold_cross_section_rotate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float deg);
|
||||
double deg);
|
||||
ManifoldCrossSection *manifold_cross_section_scale(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y);
|
||||
double x, double y);
|
||||
ManifoldCrossSection *manifold_cross_section_mirror(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float ax_x, float ax_y);
|
||||
double ax_x, double ax_y);
|
||||
ManifoldCrossSection *manifold_cross_section_transform(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3);
|
||||
double x1, double y1,
|
||||
double x2, double y2,
|
||||
double x3, double y3);
|
||||
ManifoldCrossSection *manifold_cross_section_warp(
|
||||
void *mem, ManifoldCrossSection *cs, ManifoldVec2 (*fun)(float, float));
|
||||
void *mem, ManifoldCrossSection *cs, ManifoldVec2 (*fun)(double, double));
|
||||
ManifoldCrossSection *manifold_cross_section_warp_context(
|
||||
void *mem, ManifoldCrossSection *cs,
|
||||
ManifoldVec2 (*fun)(double, double, void *), void *ctx);
|
||||
ManifoldCrossSection *manifold_cross_section_simplify(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
double epsilon);
|
||||
@@ -272,8 +310,8 @@ ManifoldCrossSection *manifold_cross_section_offset(
|
||||
// CrossSection Info
|
||||
|
||||
double manifold_cross_section_area(ManifoldCrossSection *cs);
|
||||
int manifold_cross_section_num_vert(ManifoldCrossSection *cs);
|
||||
int manifold_cross_section_num_contour(ManifoldCrossSection *cs);
|
||||
size_t manifold_cross_section_num_vert(ManifoldCrossSection *cs);
|
||||
size_t manifold_cross_section_num_contour(ManifoldCrossSection *cs);
|
||||
int manifold_cross_section_is_empty(ManifoldCrossSection *cs);
|
||||
ManifoldRect *manifold_cross_section_bounds(void *mem,
|
||||
ManifoldCrossSection *cs);
|
||||
@@ -282,56 +320,58 @@ ManifoldPolygons *manifold_cross_section_to_polygons(void *mem,
|
||||
|
||||
// Rectangle
|
||||
|
||||
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2);
|
||||
ManifoldRect *manifold_rect(void *mem, double x1, double y1, double x2,
|
||||
double y2);
|
||||
ManifoldVec2 manifold_rect_min(ManifoldRect *r);
|
||||
ManifoldVec2 manifold_rect_max(ManifoldRect *r);
|
||||
ManifoldVec2 manifold_rect_dimensions(ManifoldRect *r);
|
||||
ManifoldVec2 manifold_rect_center(ManifoldRect *r);
|
||||
float manifold_rect_scale(ManifoldRect *r);
|
||||
int manifold_rect_contains_pt(ManifoldRect *r, float x, float y);
|
||||
double manifold_rect_scale(ManifoldRect *r);
|
||||
int manifold_rect_contains_pt(ManifoldRect *r, double x, double y);
|
||||
int manifold_rect_contains_rect(ManifoldRect *a, ManifoldRect *b);
|
||||
void manifold_rect_include_pt(ManifoldRect *r, float x, float y);
|
||||
void manifold_rect_include_pt(ManifoldRect *r, double x, double y);
|
||||
ManifoldRect *manifold_rect_union(void *mem, ManifoldRect *a, ManifoldRect *b);
|
||||
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, float x1,
|
||||
float y1, float x2, float y2, float x3,
|
||||
float y3);
|
||||
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, float x,
|
||||
float y);
|
||||
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y);
|
||||
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, double x1,
|
||||
double y1, double x2, double y2,
|
||||
double x3, double y3);
|
||||
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, double x,
|
||||
double y);
|
||||
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, double x, double y);
|
||||
int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r);
|
||||
int manifold_rect_is_empty(ManifoldRect *r);
|
||||
int manifold_rect_is_finite(ManifoldRect *r);
|
||||
|
||||
// Bounding Box
|
||||
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2);
|
||||
ManifoldBox *manifold_box(void *mem, double x1, double y1, double z1, double x2,
|
||||
double y2, double z2);
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b);
|
||||
float manifold_box_scale(ManifoldBox *b);
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z);
|
||||
double manifold_box_scale(ManifoldBox *b);
|
||||
int manifold_box_contains_pt(ManifoldBox *b, double x, double y, double z);
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b);
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z);
|
||||
void manifold_box_include_pt(ManifoldBox *b, double x, double y, double z);
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b);
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4);
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z);
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z);
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z);
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, double x1,
|
||||
double y1, double z1, double x2, double y2,
|
||||
double z2, double x3, double y3, double z3,
|
||||
double x4, double y4, double z4);
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, double x,
|
||||
double y, double z);
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, double x, double y,
|
||||
double z);
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, double x, double y, double z);
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b);
|
||||
int manifold_box_is_finite(ManifoldBox *b);
|
||||
|
||||
// Static Quality Globals
|
||||
|
||||
void manifold_set_min_circular_angle(float degrees);
|
||||
void manifold_set_min_circular_edge_length(float length);
|
||||
void manifold_set_min_circular_angle(double degrees);
|
||||
void manifold_set_min_circular_edge_length(double length);
|
||||
void manifold_set_circular_segments(int number);
|
||||
void manifold_reset_to_circular_defaults();
|
||||
|
||||
// Manifold Mesh Extraction
|
||||
|
||||
@@ -356,6 +396,34 @@ float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m);
|
||||
|
||||
size_t manifold_meshgl64_num_prop(ManifoldMeshGL64 *m);
|
||||
size_t manifold_meshgl64_num_vert(ManifoldMeshGL64 *m);
|
||||
size_t manifold_meshgl64_num_tri(ManifoldMeshGL64 *m);
|
||||
size_t manifold_meshgl64_vert_properties_length(ManifoldMeshGL64 *m);
|
||||
size_t manifold_meshgl64_tri_length(ManifoldMeshGL64 *m);
|
||||
size_t manifold_meshgl64_merge_length(ManifoldMeshGL64 *m);
|
||||
size_t manifold_meshgl64_run_index_length(ManifoldMeshGL64 *m);
|
||||
size_t manifold_meshgl64_run_original_id_length(ManifoldMeshGL64 *m);
|
||||
size_t manifold_meshgl64_run_transform_length(ManifoldMeshGL64 *m);
|
||||
size_t manifold_meshgl64_face_id_length(ManifoldMeshGL64 *m);
|
||||
size_t manifold_meshgl64_tangent_length(ManifoldMeshGL64 *m);
|
||||
double *manifold_meshgl64_vert_properties(void *mem, ManifoldMeshGL64 *m);
|
||||
uint64_t *manifold_meshgl64_tri_verts(void *mem, ManifoldMeshGL64 *m);
|
||||
uint64_t *manifold_meshgl64_merge_from_vert(void *mem, ManifoldMeshGL64 *m);
|
||||
uint64_t *manifold_meshgl64_merge_to_vert(void *mem, ManifoldMeshGL64 *m);
|
||||
uint64_t *manifold_meshgl64_run_index(void *mem, ManifoldMeshGL64 *m);
|
||||
uint32_t *manifold_meshgl64_run_original_id(void *mem, ManifoldMeshGL64 *m);
|
||||
double *manifold_meshgl64_run_transform(void *mem, ManifoldMeshGL64 *m);
|
||||
uint64_t *manifold_meshgl64_face_id(void *mem, ManifoldMeshGL64 *m);
|
||||
double *manifold_meshgl64_halfedge_tangent(void *mem, ManifoldMeshGL64 *m);
|
||||
|
||||
// Triangulation
|
||||
|
||||
ManifoldTriangulation *manifold_triangulate(void *mem, ManifoldPolygons *ps,
|
||||
double epsilon);
|
||||
size_t manifold_triangulation_num_tri(ManifoldTriangulation *m);
|
||||
int *manifold_triangulation_tri_verts(void *mem, ManifoldTriangulation *m);
|
||||
|
||||
// memory size
|
||||
|
||||
size_t manifold_manifold_size();
|
||||
@@ -366,9 +434,25 @@ size_t manifold_simple_polygon_size();
|
||||
size_t manifold_polygons_size();
|
||||
size_t manifold_manifold_pair_size();
|
||||
size_t manifold_meshgl_size();
|
||||
size_t manifold_meshgl64_size();
|
||||
size_t manifold_box_size();
|
||||
size_t manifold_rect_size();
|
||||
size_t manifold_curvature_size();
|
||||
size_t manifold_triangulation_size();
|
||||
|
||||
// allocation
|
||||
|
||||
ManifoldManifold *manifold_alloc_manifold();
|
||||
ManifoldManifoldVec *manifold_alloc_manifold_vec();
|
||||
ManifoldCrossSection *manifold_alloc_cross_section();
|
||||
ManifoldCrossSectionVec *manifold_alloc_cross_section_vec();
|
||||
ManifoldSimplePolygon *manifold_alloc_simple_polygon();
|
||||
ManifoldPolygons *manifold_alloc_polygons();
|
||||
ManifoldMeshGL *manifold_alloc_meshgl();
|
||||
ManifoldMeshGL64 *manifold_alloc_meshgl64();
|
||||
ManifoldBox *manifold_alloc_box();
|
||||
ManifoldRect *manifold_alloc_rect();
|
||||
ManifoldTriangulation *manifold_alloc_triangulation();
|
||||
|
||||
// destruction
|
||||
|
||||
@@ -379,8 +463,10 @@ void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv);
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p);
|
||||
void manifold_destruct_polygons(ManifoldPolygons *p);
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL *m);
|
||||
void manifold_destruct_meshgl64(ManifoldMeshGL64 *m);
|
||||
void manifold_destruct_box(ManifoldBox *b);
|
||||
void manifold_destruct_rect(ManifoldRect *b);
|
||||
void manifold_destruct_triangulation(ManifoldTriangulation *M);
|
||||
|
||||
// pointer free + destruction
|
||||
|
||||
@@ -391,18 +477,18 @@ void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv);
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p);
|
||||
void manifold_delete_polygons(ManifoldPolygons *p);
|
||||
void manifold_delete_meshgl(ManifoldMeshGL *m);
|
||||
void manifold_delete_meshgl64(ManifoldMeshGL64 *m);
|
||||
void manifold_delete_box(ManifoldBox *b);
|
||||
void manifold_delete_rect(ManifoldRect *b);
|
||||
void manifold_delete_triangulation(ManifoldTriangulation *m);
|
||||
|
||||
// MeshIO / Export
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ManifoldMaterial *manifold_material(void *mem);
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness);
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness);
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color);
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert);
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, double roughness);
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, double metalness);
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec3 color);
|
||||
ManifoldExportOptions *manifold_export_options(void *mem);
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
|
||||
int faceted);
|
||||
@@ -23,10 +23,11 @@ typedef struct ManifoldCrossSection ManifoldCrossSection;
|
||||
typedef struct ManifoldCrossSectionVec ManifoldCrossSectionVec;
|
||||
typedef struct ManifoldSimplePolygon ManifoldSimplePolygon;
|
||||
typedef struct ManifoldPolygons ManifoldPolygons;
|
||||
typedef struct ManifoldMesh ManifoldMesh;
|
||||
typedef struct ManifoldMeshGL ManifoldMeshGL;
|
||||
typedef struct ManifoldMeshGL64 ManifoldMeshGL64;
|
||||
typedef struct ManifoldBox ManifoldBox;
|
||||
typedef struct ManifoldRect ManifoldRect;
|
||||
typedef struct ManifoldTriangulation ManifoldTriangulation;
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
typedef struct ManifoldMaterial ManifoldMaterial;
|
||||
@@ -41,14 +42,14 @@ typedef struct ManifoldManifoldPair {
|
||||
} ManifoldManifoldPair;
|
||||
|
||||
typedef struct ManifoldVec2 {
|
||||
float x;
|
||||
float y;
|
||||
double x;
|
||||
double y;
|
||||
} ManifoldVec2;
|
||||
|
||||
typedef struct ManifoldVec3 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
} ManifoldVec3;
|
||||
|
||||
typedef struct ManifoldIVec3 {
|
||||
@@ -58,15 +59,15 @@ typedef struct ManifoldIVec3 {
|
||||
} ManifoldIVec3;
|
||||
|
||||
typedef struct ManifoldVec4 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
double w;
|
||||
} ManifoldVec4;
|
||||
|
||||
typedef struct ManifoldProperties {
|
||||
float surface_area;
|
||||
float volume;
|
||||
double surface_area;
|
||||
double volume;
|
||||
} ManifoldProperties;
|
||||
|
||||
// enums
|
||||
@@ -104,3 +105,6 @@ typedef enum ManifoldJoinType {
|
||||
MANIFOLD_JOIN_TYPE_ROUND,
|
||||
MANIFOLD_JOIN_TYPE_MITER,
|
||||
} ManifoldJoinType;
|
||||
|
||||
// function pointer
|
||||
typedef double (*ManifoldSdf)(double, double, double, void*);
|
||||
@@ -12,52 +12,33 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <conv.h>
|
||||
#include <cross_section.h>
|
||||
#include <manifold.h>
|
||||
#include <manifoldc.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
#include "manifold/manifoldc.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "box.cpp"
|
||||
#include "cross.cpp"
|
||||
#include "include/conv.h"
|
||||
#include "include/types.h"
|
||||
#include "rect.cpp"
|
||||
#include "types.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshio.cpp"
|
||||
#endif
|
||||
#include "./conv.h"
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/cross_section.h"
|
||||
#include "manifold/manifold.h"
|
||||
#include "manifold/polygon.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace {
|
||||
ManifoldMeshGL *level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length, float level,
|
||||
bool seq) {
|
||||
// typing with std::function rather than auto compiles when CUDA is on,
|
||||
// passing it into GPU (and crashing) is avoided dynamically in `sdf.h`
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, !seq);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
ManifoldMeshGL *level_set_context(
|
||||
void *mem, float (*sdf_context)(float, float, float, void *),
|
||||
ManifoldBox *bounds, float edge_length, float level, bool seq, void *ctx) {
|
||||
ManifoldManifold *level_set(
|
||||
void *mem, double (*sdf_context)(double, double, double, void *),
|
||||
ManifoldBox *bounds, double edge_length, double level, double tolerance,
|
||||
bool seq, void *ctx) {
|
||||
// Bind function with context argument to one without
|
||||
using namespace std::placeholders;
|
||||
std::function<float(float, float, float)> sdf =
|
||||
std::function<double(double, double, double)> sdf =
|
||||
std::bind(sdf_context, _1, _2, _3, ctx);
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
std::function<double(vec3)> fun = [sdf](vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, !seq);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
return to_c(new (mem) Manifold(Manifold::LevelSet(
|
||||
fun, *from_c(bounds), edge_length, level, tolerance, !seq)));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -67,8 +48,8 @@ extern "C" {
|
||||
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<glm::vec2>;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
auto vec = new (mem) std::vector<vec2>;
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
vec->push_back({ps[i].x, ps[i].y});
|
||||
}
|
||||
return to_c(vec);
|
||||
@@ -78,7 +59,7 @@ ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<SimplePolygon>;
|
||||
auto polys = reinterpret_cast<SimplePolygon **>(ps);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
vec->push_back(*polys[i]);
|
||||
}
|
||||
return to_c(vec);
|
||||
@@ -92,24 +73,24 @@ size_t manifold_polygons_length(ManifoldPolygons *ps) {
|
||||
return from_c(ps)->size();
|
||||
}
|
||||
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx) {
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, size_t idx) {
|
||||
return (*from_c(ps))[idx].size();
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
|
||||
int idx) {
|
||||
size_t idx) {
|
||||
return to_c((*from_c(p))[idx]);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
|
||||
ManifoldPolygons *ps,
|
||||
int idx) {
|
||||
size_t idx) {
|
||||
auto sp = (*from_c(ps))[idx];
|
||||
return to_c(new (mem) SimplePolygon(sp));
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
|
||||
int pt_idx) {
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps,
|
||||
size_t simple_idx, size_t pt_idx) {
|
||||
return to_c((*from_c(ps))[simple_idx][pt_idx]);
|
||||
}
|
||||
|
||||
@@ -130,12 +111,12 @@ size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms) {
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
|
||||
int idx) {
|
||||
size_t idx) {
|
||||
auto m = (*from_c(ms))[idx];
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
|
||||
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, size_t idx,
|
||||
ManifoldManifold *m) {
|
||||
(*from_c(ms))[idx] = *from_c(m);
|
||||
}
|
||||
@@ -185,9 +166,9 @@ ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
|
||||
|
||||
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset) {
|
||||
auto normal = glm::vec3(normal_x, normal_y, normal_z);
|
||||
double normal_x, double normal_y,
|
||||
double normal_z, double offset) {
|
||||
auto normal = vec3(normal_x, normal_y, normal_z);
|
||||
auto pair = from_c(m)->SplitByPlane(normal, offset);
|
||||
auto first = new (mem_first) Manifold(pair.first);
|
||||
auto second = new (mem_second) Manifold(pair.second);
|
||||
@@ -195,13 +176,24 @@ ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset) {
|
||||
auto normal = glm::vec3(normal_x, normal_y, normal_z);
|
||||
double normal_x, double normal_y,
|
||||
double normal_z, double offset) {
|
||||
auto normal = vec3(normal_x, normal_y, normal_z);
|
||||
auto trimmed = from_c(m)->TrimByPlane(normal, offset);
|
||||
return to_c(new (mem) Manifold(trimmed));
|
||||
}
|
||||
|
||||
ManifoldPolygons *manifold_slice(void *mem, ManifoldManifold *m,
|
||||
double height) {
|
||||
auto poly = from_c(m)->Slice(height);
|
||||
return to_c(new (mem) Polygons(poly));
|
||||
}
|
||||
|
||||
ManifoldPolygons *manifold_project(void *mem, ManifoldManifold *m) {
|
||||
auto poly = from_c(m)->Project();
|
||||
return to_c(new (mem) Polygons(poly));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_hull(void *mem, ManifoldManifold *m) {
|
||||
auto hulled = from_c(m)->Hull();
|
||||
return to_c(new (mem) Manifold(hulled));
|
||||
@@ -214,81 +206,89 @@ ManifoldManifold *manifold_batch_hull(void *mem, ManifoldManifoldVec *ms) {
|
||||
|
||||
ManifoldManifold *manifold_hull_pts(void *mem, ManifoldVec3 *ps,
|
||||
size_t length) {
|
||||
std::vector<glm::vec3> vec(length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
std::vector<vec3> vec(length);
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
vec[i] = {ps[i].x, ps[i].y, ps[i].z};
|
||||
}
|
||||
auto hulled = Manifold::Hull(vec);
|
||||
return to_c(new (mem) Manifold(hulled));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto v = glm::vec3(x, y, z);
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, double x,
|
||||
double y, double z) {
|
||||
auto v = vec3(x, y, z);
|
||||
auto translated = from_c(m)->Translate(v);
|
||||
return to_c(new (mem) Manifold(translated));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, double x,
|
||||
double y, double z) {
|
||||
auto rotated = from_c(m)->Rotate(x, y, z);
|
||||
return to_c(new (mem) Manifold(rotated));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto s = glm::vec3(x, y, z);
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, double x,
|
||||
double y, double z) {
|
||||
auto s = vec3(x, y, z);
|
||||
auto scaled = from_c(m)->Scale(s);
|
||||
return to_c(new (mem) Manifold(scaled));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, double x1,
|
||||
double y1, double z1, double x2, double y2,
|
||||
double z2, double x3, double y3, double z3,
|
||||
double x4, double y4, double z4) {
|
||||
auto mat = mat3x4({x1, y1, z1}, {x2, y2, z2}, {x3, y3, z3}, {x4, y4, z4});
|
||||
auto transformed = from_c(m)->Transform(mat);
|
||||
return to_c(new (mem) Manifold(transformed));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
|
||||
float ny, float nz) {
|
||||
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, double nx,
|
||||
double ny, double nz) {
|
||||
auto mirrored = from_c(m)->Mirror({nx, ny, nz});
|
||||
return to_c(new (mem) Manifold(mirrored));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
|
||||
ManifoldVec3 (*fun)(float, float, float)) {
|
||||
std::function<void(glm::vec3 & v)> warp = [fun](glm::vec3 &v) {
|
||||
v = from_c(fun(v.x, v.y, v.z));
|
||||
ManifoldVec3 (*fun)(double, double, double,
|
||||
void *),
|
||||
void *ctx) {
|
||||
// Bind function with context argument to one without
|
||||
using namespace std::placeholders;
|
||||
std::function<ManifoldVec3(double, double, double)> f3 =
|
||||
std::bind(fun, _1, _2, _3, ctx);
|
||||
std::function<void(vec3 & v)> warp = [f3](vec3 &v) {
|
||||
v = from_c(f3(v.x, v.y, v.z));
|
||||
};
|
||||
auto warped = from_c(m)->Warp(warp);
|
||||
return to_c(new (mem) Manifold(warped));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, false);
|
||||
ManifoldManifold *manifold_level_set(
|
||||
void *mem, double (*sdf)(double, double, double, void *),
|
||||
ManifoldBox *bounds, double edge_length, double level, double tolerance,
|
||||
void *ctx) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, tolerance, false, ctx);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, true);
|
||||
ManifoldManifold *manifold_level_set_seq(
|
||||
void *mem, double (*sdf)(double, double, double, void *),
|
||||
ManifoldBox *bounds, double edge_length, double level, double tolerance,
|
||||
void *ctx) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, tolerance, true, ctx);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx) {
|
||||
return level_set_context(mem, sdf, bounds, edge_length, level, false, ctx);
|
||||
ManifoldManifold *manifold_smooth_by_normals(void *mem, ManifoldManifold *m,
|
||||
int normalIdx) {
|
||||
auto smoothed = from_c(m)->SmoothByNormals(normalIdx);
|
||||
return to_c(new (mem) Manifold(smoothed));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_seq_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx) {
|
||||
return level_set_context(mem, sdf, bounds, edge_length, level, true, ctx);
|
||||
ManifoldManifold *manifold_smooth_out(void *mem, ManifoldManifold *m,
|
||||
double minSharpAngle,
|
||||
double minSmoothness) {
|
||||
auto smoothed = from_c(m)->SmoothOut(minSharpAngle, minSmoothness);
|
||||
return to_c(new (mem) Manifold(smoothed));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine) {
|
||||
@@ -296,6 +296,18 @@ ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine) {
|
||||
return to_c(new (mem) Manifold(refined));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_refine_to_length(void *mem, ManifoldManifold *m,
|
||||
double length) {
|
||||
auto refined = from_c(m)->RefineToLength(length);
|
||||
return to_c(new (mem) Manifold(refined));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_refine_to_tolerance(void *mem, ManifoldManifold *m,
|
||||
double tolerance) {
|
||||
auto refined = from_c(m)->RefineToTolerance(tolerance);
|
||||
return to_c(new (mem) Manifold(refined));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_empty(void *mem) {
|
||||
return to_c(new (mem) Manifold());
|
||||
}
|
||||
@@ -309,22 +321,22 @@ ManifoldManifold *manifold_tetrahedron(void *mem) {
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
|
||||
ManifoldManifold *manifold_cube(void *mem, double x, double y, double z,
|
||||
int center) {
|
||||
auto size = glm::vec3(x, y, z);
|
||||
auto size = vec3(x, y, z);
|
||||
auto m = Manifold::Cube(size, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
|
||||
float radius_high, int circular_segments,
|
||||
ManifoldManifold *manifold_cylinder(void *mem, double height, double radius_low,
|
||||
double radius_high, int circular_segments,
|
||||
int center) {
|
||||
auto m = Manifold::Cylinder(height, radius_low, radius_high,
|
||||
circular_segments, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_sphere(void *mem, float radius,
|
||||
ManifoldManifold *manifold_sphere(void *mem, double radius,
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Sphere(radius, circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
@@ -352,11 +364,45 @@ ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldMeshGL64 *manifold_meshgl64(void *mem, double *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint64_t *tri_verts, size_t n_tris) {
|
||||
auto mesh = new (mem) MeshGL64();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldMeshGL64 *manifold_meshgl64_w_tangents(void *mem, double *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint64_t *tri_verts,
|
||||
size_t n_tris,
|
||||
double *halfedge_tangent) {
|
||||
auto mesh = new (mem) MeshGL64();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
mesh->halfedgeTangent = vector_of_array(halfedge_tangent, n_tris * 3 * 4);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
|
||||
int *half_edges, float *smoothness,
|
||||
int n_edges) {
|
||||
size_t *half_edges, double *smoothness,
|
||||
size_t n_edges) {
|
||||
auto smooth = std::vector<Smoothness>();
|
||||
for (int i = 0; i < n_edges; ++i) {
|
||||
for (size_t i = 0; i < n_edges; ++i) {
|
||||
smooth.push_back({half_edges[i], smoothness[i]});
|
||||
}
|
||||
auto m = Manifold::Smooth(*from_c(mesh), smooth);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_smooth64(void *mem, ManifoldMeshGL64 *mesh,
|
||||
size_t *half_edges, double *smoothness,
|
||||
size_t n_edges) {
|
||||
auto smooth = std::vector<Smoothness>();
|
||||
for (size_t i = 0; i < n_edges; ++i) {
|
||||
smooth.push_back({half_edges[i], smoothness[i]});
|
||||
}
|
||||
auto m = Manifold::Smooth(*from_c(mesh), smooth);
|
||||
@@ -368,19 +414,24 @@ ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh) {
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
|
||||
float height, int slices,
|
||||
float twist_degrees, float scale_x,
|
||||
float scale_y) {
|
||||
auto scale = glm::vec2(scale_x, scale_y);
|
||||
ManifoldManifold *manifold_of_meshgl64(void *mem, ManifoldMeshGL64 *mesh) {
|
||||
auto m = Manifold(*from_c(mesh));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldPolygons *cs,
|
||||
double height, int slices,
|
||||
double twist_degrees, double scale_x,
|
||||
double scale_y) {
|
||||
auto scale = vec2(scale_x, scale_y);
|
||||
auto m = Manifold::Extrude(*from_c(cs), height, slices, twist_degrees, scale);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
|
||||
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Revolve(*from_c(cs), circular_segments);
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldPolygons *cs,
|
||||
int circular_segments,
|
||||
double revolve_degrees) {
|
||||
auto m = Manifold::Revolve(*from_c(cs), circular_segments, revolve_degrees);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
@@ -403,6 +454,33 @@ ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m) {
|
||||
return to_c(new (mem) MeshGL(*from_c(m)));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_merge(void *mem, ManifoldMeshGL *m) {
|
||||
auto duplicate = new (mem) MeshGL(*from_c(m));
|
||||
if (duplicate->Merge()) {
|
||||
return to_c(duplicate);
|
||||
}
|
||||
delete duplicate;
|
||||
return m;
|
||||
}
|
||||
|
||||
ManifoldMeshGL64 *manifold_get_meshgl64(void *mem, ManifoldManifold *m) {
|
||||
auto mesh = from_c(m)->GetMeshGL64();
|
||||
return to_c(new (mem) MeshGL64(mesh));
|
||||
}
|
||||
|
||||
ManifoldMeshGL64 *manifold_meshgl64_copy(void *mem, ManifoldMeshGL64 *m) {
|
||||
return to_c(new (mem) MeshGL64(*from_c(m)));
|
||||
}
|
||||
|
||||
ManifoldMeshGL64 *manifold_meshgl64_merge(void *mem, ManifoldMeshGL64 *m) {
|
||||
auto duplicate = new (mem) MeshGL64(*from_c(m));
|
||||
if (duplicate->Merge()) {
|
||||
return to_c(duplicate);
|
||||
}
|
||||
delete duplicate;
|
||||
return m;
|
||||
}
|
||||
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL *m) { return from_c(m)->numProp; }
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL *m) { return from_c(m)->NumVert(); }
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL *m) { return from_c(m)->NumTri(); }
|
||||
@@ -475,6 +553,67 @@ float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->halfedgeTangent);
|
||||
}
|
||||
|
||||
size_t manifold_meshgl64_num_prop(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->numProp;
|
||||
}
|
||||
size_t manifold_meshgl64_num_vert(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->NumVert();
|
||||
}
|
||||
size_t manifold_meshgl64_num_tri(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->NumTri();
|
||||
}
|
||||
size_t manifold_meshgl64_vert_properties_length(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->vertProperties.size();
|
||||
}
|
||||
size_t manifold_meshgl64_tri_length(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->triVerts.size();
|
||||
}
|
||||
size_t manifold_meshgl64_merge_length(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->mergeFromVert.size();
|
||||
}
|
||||
size_t manifold_meshgl64_run_index_length(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->runIndex.size();
|
||||
}
|
||||
size_t manifold_meshgl64_run_original_id_length(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->runOriginalID.size();
|
||||
}
|
||||
size_t manifold_meshgl64_run_transform_length(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->runTransform.size();
|
||||
}
|
||||
size_t manifold_meshgl64_face_id_length(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->faceID.size();
|
||||
}
|
||||
size_t manifold_meshgl64_tangent_length(ManifoldMeshGL64 *m) {
|
||||
return from_c(m)->halfedgeTangent.size();
|
||||
}
|
||||
double *manifold_meshgl64_vert_properties(void *mem, ManifoldMeshGL64 *m) {
|
||||
return copy_data(mem, from_c(m)->vertProperties);
|
||||
}
|
||||
uint64_t *manifold_meshgl64_tri_verts(void *mem, ManifoldMeshGL64 *m) {
|
||||
return copy_data(mem, from_c(m)->triVerts);
|
||||
}
|
||||
uint64_t *manifold_meshgl64_merge_from_vert(void *mem, ManifoldMeshGL64 *m) {
|
||||
return copy_data(mem, from_c(m)->mergeFromVert);
|
||||
}
|
||||
uint64_t *manifold_meshgl64_merge_to_vert(void *mem, ManifoldMeshGL64 *m) {
|
||||
return copy_data(mem, from_c(m)->mergeToVert);
|
||||
}
|
||||
uint64_t *manifold_meshgl64_run_index(void *mem, ManifoldMeshGL64 *m) {
|
||||
return copy_data(mem, from_c(m)->runIndex);
|
||||
}
|
||||
uint32_t *manifold_meshgl64_run_original_id(void *mem, ManifoldMeshGL64 *m) {
|
||||
return copy_data(mem, from_c(m)->runOriginalID);
|
||||
}
|
||||
double *manifold_meshgl64_run_transform(void *mem, ManifoldMeshGL64 *m) {
|
||||
return copy_data(mem, from_c(m)->runTransform);
|
||||
}
|
||||
uint64_t *manifold_meshgl64_face_id(void *mem, ManifoldMeshGL64 *m) {
|
||||
return copy_data(mem, from_c(m)->faceID);
|
||||
}
|
||||
double *manifold_meshgl64_halfedge_tangent(void *mem, ManifoldMeshGL64 *m) {
|
||||
return copy_data(mem, from_c(m)->halfedgeTangent);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m) {
|
||||
auto orig = from_c(m)->AsOriginal();
|
||||
return to_c(new (mem) Manifold(orig));
|
||||
@@ -491,32 +630,38 @@ ManifoldError manifold_status(ManifoldManifold *m) {
|
||||
return to_c(error);
|
||||
}
|
||||
|
||||
int manifold_num_vert(ManifoldManifold *m) { return from_c(m)->NumVert(); }
|
||||
int manifold_num_edge(ManifoldManifold *m) { return from_c(m)->NumEdge(); }
|
||||
int manifold_num_tri(ManifoldManifold *m) { return from_c(m)->NumTri(); }
|
||||
size_t manifold_num_vert(ManifoldManifold *m) { return from_c(m)->NumVert(); }
|
||||
size_t manifold_num_edge(ManifoldManifold *m) { return from_c(m)->NumEdge(); }
|
||||
size_t manifold_num_tri(ManifoldManifold *m) { return from_c(m)->NumTri(); }
|
||||
size_t manifold_num_prop(ManifoldManifold *m) { return from_c(m)->NumProp(); };
|
||||
int manifold_genus(ManifoldManifold *m) { return from_c(m)->Genus(); }
|
||||
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m) {
|
||||
return to_c(from_c(m)->GetProperties());
|
||||
double manifold_surface_area(ManifoldManifold *m) {
|
||||
return from_c(m)->SurfaceArea();
|
||||
}
|
||||
double manifold_volume(ManifoldManifold *m) { return from_c(m)->Volume(); }
|
||||
|
||||
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m) {
|
||||
auto box = from_c(m)->BoundingBox();
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
float manifold_precision(ManifoldManifold *m) { return from_c(m)->Precision(); }
|
||||
double manifold_epsilon(ManifoldManifold *m) { return from_c(m)->GetEpsilon(); }
|
||||
|
||||
uint32_t manifold_reserve_ids(uint32_t n) { return Manifold::ReserveIDs(n); }
|
||||
|
||||
ManifoldManifold *manifold_set_properties(void *mem, ManifoldManifold *m,
|
||||
int num_prop,
|
||||
void (*fun)(float *new_prop,
|
||||
ManifoldVec3 position,
|
||||
const float *old_prop)) {
|
||||
std::function<void(float *, glm::vec3, const float *)> f =
|
||||
[fun](float *new_prop, glm::vec3 v, const float *old_prop) {
|
||||
fun(new_prop, to_c(v), old_prop);
|
||||
ManifoldManifold *manifold_set_properties(
|
||||
void *mem, ManifoldManifold *m, int num_prop,
|
||||
void (*fun)(double *new_prop, ManifoldVec3 position, const double *old_prop,
|
||||
void *ctx),
|
||||
void *ctx) {
|
||||
// Bind function with context argument to one without
|
||||
using namespace std::placeholders;
|
||||
std::function<void(double *, ManifoldVec3, const double *)> f3 =
|
||||
std::bind(fun, _1, _2, _3, ctx);
|
||||
std::function<void(double *, vec3, const double *)> f =
|
||||
[f3](double *new_prop, vec3 v, const double *old_prop) {
|
||||
return (f3(new_prop, to_c(v), old_prop));
|
||||
};
|
||||
auto man = from_c(m)->SetProperties(num_prop, f);
|
||||
return to_c(new (mem) Manifold(man));
|
||||
@@ -528,13 +673,25 @@ ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
|
||||
return to_c(new (mem) Manifold(man));
|
||||
}
|
||||
|
||||
double manifold_min_gap(ManifoldManifold *m, ManifoldManifold *other,
|
||||
double searchLength) {
|
||||
return from_c(m)->MinGap(*from_c(other), searchLength);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_calculate_normals(void *mem, ManifoldManifold *m,
|
||||
int normal_idx,
|
||||
double min_sharp_angle) {
|
||||
auto man = from_c(m)->CalculateNormals(normal_idx, min_sharp_angle);
|
||||
return to_c(new (mem) Manifold(man));
|
||||
}
|
||||
|
||||
// Static Quality Globals
|
||||
|
||||
void manifold_set_min_circular_angle(float degrees) {
|
||||
void manifold_set_min_circular_angle(double degrees) {
|
||||
Quality::SetMinCircularAngle(degrees);
|
||||
}
|
||||
|
||||
void manifold_set_min_circular_edge_length(float length) {
|
||||
void manifold_set_min_circular_edge_length(double length) {
|
||||
Quality::SetMinCircularEdgeLength(length);
|
||||
}
|
||||
|
||||
@@ -542,10 +699,26 @@ void manifold_set_circular_segments(int number) {
|
||||
Quality::SetCircularSegments(number);
|
||||
}
|
||||
|
||||
int manifold_get_circular_segments(float radius) {
|
||||
int manifold_get_circular_segments(double radius) {
|
||||
return Quality::GetCircularSegments(radius);
|
||||
}
|
||||
|
||||
void manifold_reset_to_circular_defaults() { Quality::ResetToDefaults(); }
|
||||
|
||||
ManifoldTriangulation *manifold_triangulate(void *mem, ManifoldPolygons *ps,
|
||||
double epsilon) {
|
||||
auto triangulation = manifold::Triangulate(*from_c(ps), epsilon);
|
||||
return to_c(new (mem) std::vector<ivec3>(triangulation));
|
||||
}
|
||||
|
||||
size_t manifold_triangulation_num_tri(ManifoldTriangulation *m) {
|
||||
return from_c(m)->size();
|
||||
}
|
||||
|
||||
int *manifold_triangulation_tri_verts(void *mem, ManifoldTriangulation *m) {
|
||||
return reinterpret_cast<int *>(copy_data<ivec3>(mem, *from_c(m)));
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_cross_section_size() { return sizeof(CrossSection); }
|
||||
size_t manifold_cross_section_vec_size() {
|
||||
@@ -557,8 +730,37 @@ size_t manifold_manifold_size() { return sizeof(Manifold); }
|
||||
size_t manifold_manifold_vec_size() { return sizeof(std::vector<Manifold>); }
|
||||
size_t manifold_manifold_pair_size() { return sizeof(ManifoldManifoldPair); }
|
||||
size_t manifold_meshgl_size() { return sizeof(MeshGL); }
|
||||
size_t manifold_meshgl64_size() { return sizeof(MeshGL64); }
|
||||
size_t manifold_box_size() { return sizeof(Box); }
|
||||
size_t manifold_rect_size() { return sizeof(Rect); }
|
||||
size_t manifold_triangulation_size() { return sizeof(std::vector<ivec3>); }
|
||||
|
||||
// allocation
|
||||
ManifoldManifold *manifold_alloc_manifold() {
|
||||
return to_c(new manifold::Manifold);
|
||||
}
|
||||
ManifoldManifoldVec *manifold_alloc_manifold_vec() {
|
||||
return to_c(new std::vector<manifold::Manifold>);
|
||||
}
|
||||
ManifoldCrossSection *manifold_alloc_cross_section() {
|
||||
return to_c(new CrossSection);
|
||||
}
|
||||
ManifoldCrossSectionVec *manifold_alloc_cross_section_vec() {
|
||||
return to_c(new std::vector<CrossSection>);
|
||||
}
|
||||
ManifoldSimplePolygon *manifold_alloc_simple_polygon() {
|
||||
return to_c(new SimplePolygon);
|
||||
}
|
||||
ManifoldPolygons *manifold_alloc_polygons() {
|
||||
return to_c(new std::vector<SimplePolygon>);
|
||||
}
|
||||
ManifoldMeshGL *manifold_alloc_meshgl() { return to_c(new MeshGL); }
|
||||
ManifoldMeshGL64 *manifold_alloc_meshgl64() { return to_c(new MeshGL64); }
|
||||
ManifoldBox *manifold_alloc_box() { return to_c(new Box); }
|
||||
ManifoldRect *manifold_alloc_rect() { return to_c(new Rect); }
|
||||
ManifoldTriangulation *manifold_alloc_triangulation() {
|
||||
return to_c(new std::vector<ivec3>);
|
||||
}
|
||||
|
||||
// pointer free + destruction
|
||||
void manifold_delete_cross_section(ManifoldCrossSection *c) {
|
||||
@@ -576,8 +778,12 @@ void manifold_delete_manifold_vec(ManifoldManifoldVec *ms) {
|
||||
delete from_c(ms);
|
||||
}
|
||||
void manifold_delete_meshgl(ManifoldMeshGL *m) { delete from_c(m); }
|
||||
void manifold_delete_meshgl64(ManifoldMeshGL64 *m) { delete from_c(m); }
|
||||
void manifold_delete_box(ManifoldBox *b) { delete from_c(b); }
|
||||
void manifold_delete_rect(ManifoldRect *r) { delete from_c(r); }
|
||||
void manifold_delete_triangulation(ManifoldTriangulation *m) {
|
||||
delete from_c(m);
|
||||
}
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_cross_section(ManifoldCrossSection *cs) {
|
||||
@@ -595,8 +801,12 @@ void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms) {
|
||||
from_c(ms)->~ManifoldVec();
|
||||
}
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL *m) { from_c(m)->~MeshGL(); }
|
||||
void manifold_destruct_meshgl64(ManifoldMeshGL64 *m) { from_c(m)->~MeshGL64(); }
|
||||
void manifold_destruct_box(ManifoldBox *b) { from_c(b)->~Box(); }
|
||||
void manifold_destruct_rect(ManifoldRect *r) { from_c(r)->~Rect(); }
|
||||
void manifold_destruct_triangulation(ManifoldTriangulation *m) {
|
||||
from_c(m)->~vector<ivec3>();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "meshIO.h"
|
||||
|
||||
#include "conv.h"
|
||||
#include "types.h"
|
||||
#include "./conv.h"
|
||||
#include "manifold/meshIO.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
// C <-> C++ conversions
|
||||
|
||||
@@ -45,23 +44,18 @@ ManifoldMaterial *manifold_material(void *mem) {
|
||||
return to_c(new (mem) manifold::Material());
|
||||
}
|
||||
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness) {
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, double roughness) {
|
||||
from_c(mat)->roughness = roughness;
|
||||
}
|
||||
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness) {
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, double metalness) {
|
||||
from_c(mat)->metalness = metalness;
|
||||
}
|
||||
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color) {
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec3 color) {
|
||||
from_c(mat)->color = from_c(color);
|
||||
}
|
||||
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert) {
|
||||
from_c(mat)->vertColor = vector_of_vec_array(vert_color, n_vert);
|
||||
}
|
||||
|
||||
// export options
|
||||
|
||||
ManifoldExportOptions *manifold_export_options(void *mem) {
|
||||
@@ -88,7 +82,7 @@ void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldMeshGL *manifold_import_meshgl(void *mem, const char *filename,
|
||||
int force_cleanup) {
|
||||
auto m = manifold::ImportMesh(std::string(filename), force_cleanup);
|
||||
return to_c(new (mem) manifold::MeshGL(m));
|
||||
return to_c(new (mem) MeshGL(m));
|
||||
}
|
||||
|
||||
// memory size
|
||||
@@ -12,15 +12,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
#include "./conv.h"
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2) {
|
||||
auto p1 = glm::vec2(x1, y1);
|
||||
auto p2 = glm::vec2(x2, y2);
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
ManifoldRect *manifold_rect(void *mem, double x1, double y1, double x2,
|
||||
double y2) {
|
||||
auto p1 = vec2(x1, y1);
|
||||
auto p2 = vec2(x2, y2);
|
||||
auto rect = new (mem) Rect(p1, p2);
|
||||
return to_c(rect);
|
||||
}
|
||||
@@ -43,11 +45,11 @@ ManifoldVec2 manifold_rect_center(ManifoldRect *r) {
|
||||
return {v.x, v.y};
|
||||
}
|
||||
|
||||
float manifold_rect_scale(ManifoldRect *r) { return from_c(r)->Scale(); }
|
||||
double manifold_rect_scale(ManifoldRect *r) { return from_c(r)->Scale(); }
|
||||
|
||||
int manifold_rect_contains_pt(ManifoldRect *r, float x, float y) {
|
||||
int manifold_rect_contains_pt(ManifoldRect *r, double x, double y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
auto p = vec2(x, y);
|
||||
return rect.Contains(p);
|
||||
}
|
||||
|
||||
@@ -57,9 +59,9 @@ int manifold_rect_contains_rect(ManifoldRect *a, ManifoldRect *b) {
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_rect_include_pt(ManifoldRect *r, float x, float y) {
|
||||
void manifold_rect_include_pt(ManifoldRect *r, double x, double y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
auto p = vec2(x, y);
|
||||
rect.Union(p);
|
||||
}
|
||||
|
||||
@@ -68,25 +70,26 @@ ManifoldRect *manifold_rect_union(void *mem, ManifoldRect *a, ManifoldRect *b) {
|
||||
return to_c(new (mem) Rect(rect));
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, float x1,
|
||||
float y1, float x2, float y2, float x3,
|
||||
float y3) {
|
||||
auto mat = glm::mat3x2(x1, y1, x2, y2, x3, y3);
|
||||
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, double x1,
|
||||
double y1, double x2, double y2,
|
||||
double x3, double y3) {
|
||||
auto mat = mat2x3({x1, y1}, {x2, y2}, {x3, y3});
|
||||
auto transformed = from_c(r)->Transform(mat);
|
||||
return to_c(new (mem) Rect(transformed));
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, float x,
|
||||
float y) {
|
||||
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, double x,
|
||||
double y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
auto p = vec2(x, y);
|
||||
auto translated = (*from_c(r)) + p;
|
||||
return to_c(new (mem) Rect(translated));
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y) {
|
||||
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, double x,
|
||||
double y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
auto p = vec2(x, y);
|
||||
auto scaled = (*from_c(r)) * p;
|
||||
return to_c(new (mem) Rect(scaled));
|
||||
}
|
||||
@@ -98,3 +101,6 @@ int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r) {
|
||||
int manifold_rect_is_empty(ManifoldRect *r) { return from_c(r)->IsEmpty(); }
|
||||
|
||||
int manifold_rect_is_finite(ManifoldRect *r) { return from_c(r)->IsFinite(); }
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -12,28 +12,64 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
project(python)
|
||||
nanobind_add_module(manifold3d NB_STATIC STABLE_ABI LTO autogen_docstrings.inl
|
||||
manifold3d.cpp
|
||||
)
|
||||
|
||||
if(MANIFOLD_PYBIND_STUBGEN)
|
||||
nanobind_add_stub(
|
||||
manifold3d_stub
|
||||
MODULE manifold3d
|
||||
OUTPUT manifold3d.pyi
|
||||
PYTHON_PATH $<TARGET_FILE_DIR:manifold3d>
|
||||
DEPENDS manifold3d
|
||||
PATTERN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/stub_pattern.txt
|
||||
)
|
||||
endif()
|
||||
|
||||
add_subdirectory(third_party)
|
||||
nanobind_add_module(
|
||||
manifold3d
|
||||
NB_STATIC STABLE_ABI LTO
|
||||
manifold3d.cpp)
|
||||
target_link_libraries(manifold3d PRIVATE manifold)
|
||||
target_compile_options(manifold3d PRIVATE ${MANIFOLD_FLAGS} -DMODULE_NAME=manifold3d)
|
||||
target_compile_features(manifold3d PUBLIC cxx_std_17)
|
||||
target_compile_options(
|
||||
manifold3d
|
||||
PRIVATE ${MANIFOLD_FLAGS} -DMODULE_NAME=manifold3d
|
||||
)
|
||||
set_target_properties(manifold3d PROPERTIES OUTPUT_NAME "manifold3d")
|
||||
|
||||
message(Python_EXECUTABLE = ${Python_EXECUTABLE})
|
||||
# ideally we should generate a dependency file from python...
|
||||
set(
|
||||
DOCSTRING_DEPS
|
||||
${PROJECT_SOURCE_DIR}/src/manifold.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/constructors.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/sort.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/cross_section/cross_section.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/polygon.cpp
|
||||
${PROJECT_SOURCE_DIR}/include/manifold/common.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/gen_docs.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/docstring_override.txt
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT autogen_docstrings.inl
|
||||
DEPENDS ${DOCSTRING_DEPS}
|
||||
COMMAND ${Python_EXECUTABLE}
|
||||
ARGS ${CMAKE_CURRENT_SOURCE_DIR}/gen_docs.py
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
target_include_directories(manifold3d PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
if(SKBUILD)
|
||||
install(
|
||||
TARGETS manifold3d
|
||||
LIBRARY DESTINATION ${SKBUILD_PLATLIB_DIR}
|
||||
COMPONENT bindings
|
||||
)
|
||||
set(MANIFOLD_PYBIND_LIBDIR ${SKBUILD_PLATLIB_DIR})
|
||||
else()
|
||||
set(MANIFOLD_PYBIND_LIBDIR ${Python_SITEARCH})
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS manifold3d
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
COMPONENT bindings
|
||||
LIBRARY DESTINATION ${MANIFOLD_PYBIND_LIBDIR} COMPONENT bindings
|
||||
)
|
||||
if(MANIFOLD_PYBIND_STUBGEN)
|
||||
install(
|
||||
FILES ${CMAKE_CURRENT_BINARY_DIR}/manifold3d.pyi
|
||||
DESTINATION ${MANIFOLD_PYBIND_LIBDIR}
|
||||
COMPONENT bindings
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Python Bindings
|
||||
|
||||
## Autogenerated Doc-Strings
|
||||
|
||||
Doc-strings for the python API wrapper are generated by gen_docs.py
|
||||
The script is run automatically during the build to keep python
|
||||
doc strings fully up-to-date with c++ sources.
|
||||
|
||||
It scrapes documentation comments and c++ function signatures
|
||||
from the manifold c++ sources, in order to generate a c++ header file
|
||||
exposing the comments as string variables named by function names.
|
||||
This allows python bindings to re-use the c++ comments directly.
|
||||
|
||||
Some snake-casing of params is applied for python use case.
|
||||
|
||||
---
|
||||
|
||||
When modifying the Manifold C++ sources, you may need to update
|
||||
gen_docs.py. For example, top-level free functions are white-listed,
|
||||
so if you add a new one, you will need to add it in gen_docs.py.
|
||||
|
||||
Similarly, the list of source files to parse is also white listed,
|
||||
so if you define functions in new files that need python wrappers,
|
||||
you will also need to up gen_docs.py.
|
||||
|
||||
To verify that python docs are correct after changes, you can
|
||||
run the following commends from the manifold repo root:
|
||||
```
|
||||
pip install .
|
||||
python -c 'import manifold3d; help(manifold3d)'
|
||||
```
|
||||
|
||||
Alternatively you could generate stubs with roughly the same info
|
||||
```
|
||||
pip install nanobind-stubgen
|
||||
pip install .
|
||||
nanobind-stubgen manifold3d
|
||||
```
|
||||
It will emit some warnings and write a file `manifold3d.pyi`
|
||||
which will show all the function signatures and docstrings.
|
||||
@@ -0,0 +1,51 @@
|
||||
cross_section__warp__warp_func:
|
||||
Move the vertices of this CrossSection (creating a new one) according to
|
||||
any arbitrary input function, followed by a union operation (with a
|
||||
Positive fill rule) that ensures any introduced intersections are not
|
||||
included in the result.
|
||||
:param warp_func: A function that takes the original vertex position and
|
||||
return the new position.
|
||||
cross_section__warp_batch__warp_func:
|
||||
Same as CrossSection::warp but calls warpFunc with
|
||||
an ndarray[n, 2] instead of processing only one vertex at a time.
|
||||
:param warp_func: A function that takes multiple vertex positions as an
|
||||
ndarray[n, 2] and returns the new vertex positions.
|
||||
manifold__warp__warp_func:
|
||||
This function does not change the topology, but allows the vertices to be
|
||||
moved according to any arbitrary input function. It is easy to create a
|
||||
function that warps a geometrically valid object into one which overlaps, but
|
||||
that is not checked here, so it is up to the user to choose their function
|
||||
with discretion.
|
||||
:param warp_func: A function that takes the original vertex position and
|
||||
return the new position.
|
||||
manifold__warp_batch__warp_func:
|
||||
Same as Manifold::warp but calls warpFunc with with
|
||||
an ndarray[n, 3] instead of processing only one vertex at a time.
|
||||
:param warp_func: A function that takes multiple vertex positions as an
|
||||
ndarray[n, 3] and returns the new vertex positions. The result should have the
|
||||
same shape as the input.
|
||||
manifold__smooth__mesh_gl__sharpened_edges:
|
||||
Constructs a smooth version of the input mesh by creating tangents; this
|
||||
method will throw if you have supplied tangents with your mesh already. The
|
||||
actual triangle resolution is unchanged; use the Refine() method to
|
||||
interpolate to a higher-resolution curve.
|
||||
By default, every edge is calculated for maximum smoothness (very much
|
||||
approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
No higher-order derivatives are considered, as the interpolation is
|
||||
independent per triangle, only sharing constraints on their boundaries.
|
||||
:param mesh: input Mesh.
|
||||
:param sharpened_edges: If desired, you can supply a vector of sharpened
|
||||
halfedges, which should in general be a small subset of all halfedges. Order
|
||||
of entries doesn't matter, as each one specifies the desired smoothness
|
||||
(between zero and one, with one the default for all unspecified halfedges)
|
||||
and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
between triVert 0 and 1, etc).
|
||||
:param edge_smoothness: Smoothness values associated to each halfedge defined
|
||||
in sharpened_edges. At a smoothness value of zero, a sharp crease is made. The
|
||||
smoothness is interpolated along each edge, so the specified value should be
|
||||
thought of as an average. Where exactly two sharpened edges meet at a vertex,
|
||||
their tangents are rotated to be colinear so that the sharpened edge can be
|
||||
continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
can be sharpened by sharping all edges that are incident on it, allowing cones
|
||||
to be formed.
|
||||
@@ -0,0 +1,132 @@
|
||||
from manifold3d import *
|
||||
import numpy as np
|
||||
|
||||
|
||||
def all_root_level():
|
||||
set_min_circular_angle(10)
|
||||
set_min_circular_edge_length(1)
|
||||
set_circular_segments(22)
|
||||
n = get_circular_segments(1)
|
||||
assert n == 22
|
||||
poly = [[0, 0], [1, 0], [1, 1]]
|
||||
tris = triangulate([poly])
|
||||
tris = triangulate([np.array(poly)])
|
||||
|
||||
|
||||
def all_cross_section():
|
||||
poly = [[0, 0], [1, 0], [1, 1]]
|
||||
c = CrossSection([np.array(poly)])
|
||||
c = CrossSection([poly])
|
||||
c = CrossSection() + c
|
||||
a = c.area()
|
||||
c = CrossSection.batch_boolean(
|
||||
[
|
||||
CrossSection.circle(1),
|
||||
CrossSection.square((3, 3)),
|
||||
CrossSection.circle(3).translate((1, 1)),
|
||||
],
|
||||
OpType.Add,
|
||||
)
|
||||
c = CrossSection.batch_hull([c, c.translate((1, 0))])
|
||||
b = c.bounds()
|
||||
c = CrossSection.circle(1)
|
||||
c = CrossSection.compose([c, c.translate((1, 0))])
|
||||
cs = c.decompose()
|
||||
m = c.extrude(1)
|
||||
c = c.hull()
|
||||
c = CrossSection.hull_points(poly)
|
||||
c = CrossSection.hull_points(np.array(poly))
|
||||
e = c.is_empty()
|
||||
c = c.mirror((0, 1))
|
||||
n = c.num_contour()
|
||||
n = c.num_vert()
|
||||
c = c.offset(1, JoinType.Round)
|
||||
m = c.revolve()
|
||||
c = c.rotate(90)
|
||||
c = c.scale((2, 2))
|
||||
c = c.simplify()
|
||||
c = CrossSection.square((1, 1))
|
||||
p = c.to_polygons()
|
||||
c = c.transform([[1, 0, 0], [0, 1, 0]])
|
||||
c = c.translate((1, 1))
|
||||
c = c.warp(lambda p: (p[0] + 1, p[1] / 2))
|
||||
c = c.warp_batch(lambda ps: ps * [1, 0.5] + [1, 0])
|
||||
|
||||
|
||||
def all_manifold():
|
||||
mesh = Manifold.sphere(1).to_mesh()
|
||||
m = Manifold(mesh)
|
||||
m = Manifold() + m
|
||||
m = m.as_original()
|
||||
m = Manifold.batch_boolean(
|
||||
[
|
||||
Manifold.cylinder(4, 1),
|
||||
Manifold.cube((3, 2, 1)),
|
||||
Manifold.cylinder(5, 3).translate((1, 1, 1)),
|
||||
],
|
||||
OpType.Add,
|
||||
)
|
||||
m = Manifold.batch_hull([m, m.translate((0, 0, 1))])
|
||||
b = m.bounding_box()
|
||||
m = m.calculate_curvature(4, 5)
|
||||
m = m.calculate_normals(0)
|
||||
m = m.smooth_by_normals(0)
|
||||
m = Manifold.compose([m, m.translate((5, 0, 0))])
|
||||
m = Manifold.cube((1, 1, 1))
|
||||
m = Manifold.cylinder(1, 1)
|
||||
ms = m.decompose()
|
||||
m = Manifold.extrude(CrossSection.circle(1), 1)
|
||||
m = Manifold.revolve(CrossSection.circle(1))
|
||||
g = m.genus()
|
||||
a = m.surface_area()
|
||||
v = m.volume()
|
||||
m = m.hull()
|
||||
m = m.hull_points(mesh.vert_properties)
|
||||
e = m.is_empty()
|
||||
m = m.mirror((0, 0, 1))
|
||||
n = m.num_edge()
|
||||
n = m.num_prop()
|
||||
n = m.num_prop_vert()
|
||||
n = m.num_tri()
|
||||
n = m.num_vert()
|
||||
i = m.original_id()
|
||||
p = m.get_tolerance()
|
||||
pp = m.set_tolerance(0.0001)
|
||||
c = m.project()
|
||||
m = m.refine(2)
|
||||
m = m.refine_to_length(0.1)
|
||||
m = m.refine_to_tolerance(0.01)
|
||||
m = m.smooth_out()
|
||||
i = Manifold.reserve_ids(1)
|
||||
m = m.scale((1, 2, 3))
|
||||
m = m.set_properties(3, lambda pos, prop: pos)
|
||||
c = m.slice(0.5)
|
||||
m = Manifold.smooth(mesh, [0], [0.5])
|
||||
m = Manifold.sphere(1)
|
||||
m, n = m.split(m.translate((1, 0, 0)))
|
||||
m, n = m.split_by_plane((0, 0, 1), 0)
|
||||
e = m.status()
|
||||
m = Manifold.tetrahedron()
|
||||
mesh = m.to_mesh()
|
||||
ok = mesh.merge()
|
||||
m = m.transform([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]])
|
||||
m = m.translate((0, 0, 0))
|
||||
m = m.trim_by_plane((0, 0, 1), 0)
|
||||
m = m.warp(lambda p: (p[0] + 1, p[1] / 2, p[2] * 2))
|
||||
m = m.warp_batch(lambda ps: ps * 2 + [1, 0, 0])
|
||||
m = Manifold.cube()
|
||||
m2 = Manifold.cube().translate([2, 0, 0])
|
||||
d = m.min_gap(m2, 2)
|
||||
mesh2 = m.to_mesh64()
|
||||
ok = mesh.merge()
|
||||
|
||||
|
||||
def run():
|
||||
all_root_level()
|
||||
all_cross_section()
|
||||
all_manifold()
|
||||
return Manifold()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -20,60 +20,82 @@ from manifold3d import Manifold
|
||||
|
||||
INCHES = 25.4
|
||||
|
||||
brick_depth = (3 + 5/8) * INCHES
|
||||
brick_height = (2 + 1/4) * INCHES
|
||||
brick_length = (7 + 5/8) * INCHES
|
||||
brick_depth = (3 + 5 / 8) * INCHES
|
||||
brick_height = (2 + 1 / 4) * INCHES
|
||||
brick_length = (7 + 5 / 8) * INCHES
|
||||
|
||||
mortar_gap = (3/8) * INCHES
|
||||
mortar_gap = (3 / 8) * INCHES
|
||||
|
||||
|
||||
def brick():
|
||||
return Manifold.cube(brick_length, brick_depth, brick_height)
|
||||
return Manifold.cube([brick_length, brick_depth, brick_height])
|
||||
|
||||
|
||||
def halfbrick():
|
||||
return Manifold.cube((brick_length - mortar_gap)/2, brick_depth, brick_height)
|
||||
return Manifold.cube([(brick_length - mortar_gap) / 2, brick_depth, brick_height])
|
||||
|
||||
|
||||
def row(length):
|
||||
bricks = [brick().translate((brick_length + mortar_gap)*x, 0, 0)
|
||||
for x in range(length)]
|
||||
return Manifold(bricks)
|
||||
bricks = [
|
||||
brick().translate([(brick_length + mortar_gap) * x, 0, 0])
|
||||
for x in range(length)
|
||||
]
|
||||
return sum(bricks, Manifold())
|
||||
|
||||
|
||||
def wall(length, height, alternate=0):
|
||||
bricks = [row(length).translate(
|
||||
((z+alternate) % 2)*(brick_depth+mortar_gap), 0,
|
||||
(brick_height+mortar_gap)*z) for z in range(height)]
|
||||
return Manifold(bricks)
|
||||
bricks = [
|
||||
row(length).translate(
|
||||
[
|
||||
((z + alternate) % 2) * (brick_depth + mortar_gap),
|
||||
0,
|
||||
(brick_height + mortar_gap) * z,
|
||||
]
|
||||
)
|
||||
for z in range(height)
|
||||
]
|
||||
return sum(bricks, Manifold())
|
||||
|
||||
|
||||
def walls(length, width, height):
|
||||
return Manifold([
|
||||
wall(length, height),
|
||||
wall(width, height, 1).rotate(0, 0, 90).translate(brick_depth, 0, 0),
|
||||
wall(length, height, 1).translate(
|
||||
0, (width)*(brick_length+mortar_gap), 0),
|
||||
wall(width, height).rotate(0, 0, 90)
|
||||
.translate((length+0.5)*(brick_length+mortar_gap) - mortar_gap, 0, 0),
|
||||
])
|
||||
return sum(
|
||||
[
|
||||
wall(length, height),
|
||||
wall(width, height, 1).rotate([0, 0, 90]).translate([brick_depth, 0, 0]),
|
||||
wall(length, height, 1).translate(
|
||||
[0, (width) * (brick_length + mortar_gap), 0]
|
||||
),
|
||||
wall(width, height)
|
||||
.rotate([0, 0, 90])
|
||||
.translate(
|
||||
[(length + 0.5) * (brick_length + mortar_gap) - mortar_gap, 0, 0]
|
||||
),
|
||||
],
|
||||
Manifold(),
|
||||
)
|
||||
|
||||
|
||||
def floor(length, width):
|
||||
results = [walls(length, width, 1)]
|
||||
if length > 1 and width > 1:
|
||||
results.append(floor(length-1, width-1).translate(brick_depth +
|
||||
mortar_gap, brick_depth+mortar_gap, 0))
|
||||
results.append(
|
||||
floor(length - 1, width - 1).translate(
|
||||
[brick_depth + mortar_gap, brick_depth + mortar_gap, 0]
|
||||
)
|
||||
)
|
||||
if length == 1 and width > 1:
|
||||
results.append(row(width-1).rotate(0, 0, 90))
|
||||
results.append(row(width - 1).rotate((0, 0, 90)))
|
||||
if width == 1 and length > 1:
|
||||
results.append(row(length-1).translate(2*(brick_depth +
|
||||
mortar_gap), brick_depth+mortar_gap, 0))
|
||||
results.append(halfbrick().translate(
|
||||
brick_depth+mortar_gap, brick_depth+mortar_gap, 0))
|
||||
return Manifold(results)
|
||||
results.append(
|
||||
row(length - 1).translate(
|
||||
[2 * (brick_depth + mortar_gap), brick_depth + mortar_gap, 0]
|
||||
)
|
||||
)
|
||||
results.append(
|
||||
halfbrick().translate([brick_depth + mortar_gap, brick_depth + mortar_gap, 0])
|
||||
)
|
||||
return sum(results, Manifold())
|
||||
|
||||
|
||||
def run(width=10, length=10, height=10):
|
||||
return walls(length, width, height) + floor(length, width)
|
||||
|
||||
|
||||
@@ -13,18 +13,21 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
|
||||
from manifold3d import Manifold
|
||||
from functools import reduce
|
||||
|
||||
# https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355#file-cube-with-half-spheres-dents-scad
|
||||
|
||||
|
||||
def run(n=5, overlap=True):
|
||||
a = Manifold.cube(n, n, 0.5).translate(-0.5, -0.5, -0.5)
|
||||
a = Manifold.cube([n, n, 0.5]).translate([-0.5, -0.5, -0.5])
|
||||
|
||||
spheres = [Manifold.sphere(0.45 if overlap else 0.55, 50).translate(i, j, 0)
|
||||
for i in range(n) for j in range(n)]
|
||||
spheres = reduce(lambda a, b: a + b, spheres)
|
||||
|
||||
return a - spheres
|
||||
spheres = [
|
||||
Manifold.sphere(0.45 if overlap else 0.55, 50).translate([i, j, 0])
|
||||
for i in range(n)
|
||||
for j in range(n)
|
||||
]
|
||||
# spheres = reduce(lambda a, b: a + b, spheres)
|
||||
|
||||
return a - sum(spheres, Manifold())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from manifold3d import CrossSection
|
||||
from manifold3d import CrossSection, Manifold
|
||||
|
||||
|
||||
def run():
|
||||
@@ -10,26 +10,34 @@ def run():
|
||||
cross_section = CrossSection(polygons_points)
|
||||
polygons = cross_section.to_polygons()
|
||||
polygon = polygons[0]
|
||||
if set(polygon) != set(polygon_points):
|
||||
raise Exception(f"polygon={polygon} differs from polygon_points={polygon_points}")
|
||||
if set([tuple(p) for p in polygon]) != set(polygon_points):
|
||||
raise Exception(
|
||||
f"polygon={polygon} differs from polygon_points={polygon_points}"
|
||||
)
|
||||
|
||||
# extrude a polygon to create a manifold
|
||||
extruded_polygon = cross_section.extrude(10.0)
|
||||
extruded_polygon = Manifold.extrude(cross_section, 10.0)
|
||||
eps = 0.001
|
||||
observed_volume = extruded_polygon.get_volume()
|
||||
observed_volume = extruded_polygon.volume()
|
||||
expected_volume = 10.0
|
||||
if abs(observed_volume - expected_volume) > eps:
|
||||
raise Exception(f"observed_volume={observed_volume} differs from expected_volume={expected_volume}")
|
||||
observed_surface_area = extruded_polygon.get_surface_area()
|
||||
raise Exception(
|
||||
f"observed_volume={observed_volume} differs from expected_volume={expected_volume}"
|
||||
)
|
||||
observed_surface_area = extruded_polygon.surface_area()
|
||||
expected_surface_area = 42.0
|
||||
if abs(observed_surface_area - expected_surface_area) > eps:
|
||||
raise Exception(f"observed_surface_area={observed_surface_area} differs from expected_surface_area={expected_surface_area}")
|
||||
raise Exception(
|
||||
f"observed_surface_area={observed_surface_area} differs from expected_surface_area={expected_surface_area}"
|
||||
)
|
||||
|
||||
# get bounding box from manifold
|
||||
observed_bbox = extruded_polygon.bounding_box
|
||||
observed_bbox = extruded_polygon.bounding_box()
|
||||
expected_bbox = (0.0, 0.0, 0.0, 1.0, 1.0, 10.0)
|
||||
if observed_bbox != expected_bbox:
|
||||
raise Exception(f"observed_bbox={observed_bbox} differs from expected_bbox={expected_bbox}")
|
||||
raise Exception(
|
||||
f"observed_bbox={observed_bbox} differs from expected_bbox={expected_bbox}"
|
||||
)
|
||||
|
||||
return extruded_polygon
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
Copyright 2023 The Manifold Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
from manifold3d import Manifold
|
||||
|
||||
|
||||
def gyroid(x, y, z):
|
||||
xi = x - math.pi / 4.0
|
||||
yi = y - math.pi / 4.0
|
||||
zi = z - math.pi / 4.0
|
||||
return (
|
||||
math.cos(xi) * math.sin(yi)
|
||||
+ math.cos(yi) * math.sin(zi)
|
||||
+ math.cos(zi) * math.sin(xi)
|
||||
)
|
||||
|
||||
|
||||
def gyroid_levelset(level, period, size, n):
|
||||
return Manifold.level_set(
|
||||
gyroid,
|
||||
[-period, -period, -period, period, period, period],
|
||||
period / n,
|
||||
level,
|
||||
).scale([size / period] * 3)
|
||||
|
||||
|
||||
def rhombic_dodecahedron(size):
|
||||
box = Manifold.cube(size * math.sqrt(2.0) * np.array([1, 1, 2]), True)
|
||||
result = box.rotate([90, 45, 0]) ^ box.rotate([90, 45, 90])
|
||||
return result ^ box.rotate([0, 0, 45])
|
||||
|
||||
|
||||
def gyroid_module(size=20, n=15):
|
||||
period = math.pi * 2.0
|
||||
result = (
|
||||
gyroid_levelset(-0.4, period, size, n) ^ rhombic_dodecahedron(size)
|
||||
) - gyroid_levelset(0.4, period, size, n)
|
||||
return result.rotate([-45, 0, 90]).translate([0, 0, size / math.sqrt(2.0)])
|
||||
|
||||
|
||||
def run(size=20, n=15):
|
||||
return gyroid_module(size, n)
|
||||
@@ -24,10 +24,9 @@ from time import time
|
||||
if __name__ == "__main__":
|
||||
current_file = pathlib.Path(__file__)
|
||||
current_dir = current_file.parent
|
||||
files = [f.parts[-1][:-3]
|
||||
for f in current_dir.glob('*.py') if f != current_file]
|
||||
files = [f.parts[-1][:-3] for f in current_dir.glob("*.py") if f != current_file]
|
||||
|
||||
export_models = len(sys.argv) == 2 and sys.argv[-1] == '-e'
|
||||
export_models = len(sys.argv) == 2 and sys.argv[-1] == "-e"
|
||||
|
||||
for f in files:
|
||||
module = importlib.import_module(f)
|
||||
@@ -41,9 +40,10 @@ if __name__ == "__main__":
|
||||
else:
|
||||
vertices = mesh.vert_properties
|
||||
colors = None
|
||||
meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts,
|
||||
vertex_colors=colors)
|
||||
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
|
||||
print(f'Exported model to {f}.glb')
|
||||
meshOut = trimesh.Trimesh(
|
||||
vertices=vertices, faces=mesh.tri_verts, vertex_colors=colors
|
||||
)
|
||||
trimesh.exchange.export.export_mesh(meshOut, f"{f}.glb", "glb")
|
||||
print(f"Exported model to {f}.glb")
|
||||
t1 = time()
|
||||
print(f'Took {(t1-t0)*1000:.1f}ms for {f}')
|
||||
print(f"Took {(t1-t0)*1000:.1f}ms for {f}")
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
Copyright 2023 The Manifold Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
from manifold3d import Manifold, Mesh
|
||||
import numpy as np
|
||||
|
||||
|
||||
def run():
|
||||
# A smoothed manifold demonstrating selective edge sharpening with
|
||||
# smooth() and refine(), see more details at:
|
||||
# https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
height = 10
|
||||
radius = 30
|
||||
offset = 20
|
||||
wiggles = 12
|
||||
sharpness = 0.8
|
||||
n = 50
|
||||
|
||||
triangles = []
|
||||
positions = [[-offset, 0, height], [-offset, 0, -height]]
|
||||
sharpenedEdges = []
|
||||
|
||||
delta = np.pi / wiggles
|
||||
for i in range(2 * wiggles):
|
||||
theta = (i - wiggles) * delta
|
||||
amp = 0.5 * height * max(np.cos(0.8 * theta), 0)
|
||||
|
||||
positions.append(
|
||||
[
|
||||
radius * np.cos(theta),
|
||||
radius * np.sin(theta),
|
||||
amp * (1 if i % 2 == 0 else -1),
|
||||
]
|
||||
)
|
||||
|
||||
j = i + 1
|
||||
if j == 2 * wiggles:
|
||||
j = 0
|
||||
|
||||
smoothness = 1 - sharpness * np.cos((theta + delta / 2) / 2)
|
||||
halfedge = 3 * len(triangles) + 1
|
||||
sharpenedEdges.append((halfedge, smoothness))
|
||||
triangles.append([0, 2 + i, 2 + j])
|
||||
|
||||
halfedge = 3 * len(triangles) + 1
|
||||
sharpenedEdges.append((halfedge, smoothness))
|
||||
triangles.append([1, 2 + j, 2 + i])
|
||||
|
||||
scallop = Mesh(
|
||||
tri_verts=np.array(triangles, np.int32),
|
||||
vert_properties=np.array(positions, np.float32),
|
||||
)
|
||||
|
||||
def colorCurvature(_pos, oldProp):
|
||||
a = max(0, min(1, oldProp[0] / 3 + 0.5))
|
||||
b = a * a * (3 - 2 * a)
|
||||
red = [1, 0, 0]
|
||||
blue = [0, 0, 1]
|
||||
return [(1 - b) * blue[i] + b * red[i] for i in range(3)]
|
||||
|
||||
edges, smoothing = zip(*sharpenedEdges)
|
||||
return (
|
||||
Manifold.smooth(scallop, edges, smoothing)
|
||||
.refine(n)
|
||||
.calculate_curvature(-1, 0)
|
||||
.set_properties(3, colorCurvature)
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Copyright 2022 The Manifold Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
from manifold3d import Manifold
|
||||
|
||||
|
||||
def run():
|
||||
a = Manifold.cube([1.0, 1.0, 1.0])
|
||||
b = Manifold.cube([1.0, 1.0, 1.0]).rotate([45.0, 45.0, 45.0])
|
||||
return a.split(b)[0]
|
||||
@@ -4,26 +4,27 @@ import numpy as np
|
||||
|
||||
def fractal(holes, hole, w, position, depth, maxDepth):
|
||||
w /= 3
|
||||
holes.append(hole.scale([w, w, 1.0]).translate(
|
||||
[position[0], position[1], 0.0]))
|
||||
holes.append(hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]))
|
||||
if depth == maxDepth:
|
||||
return
|
||||
offsets = np.array([
|
||||
[-w, -w],
|
||||
[-w, 0.0],
|
||||
[-w, w],
|
||||
[0.0, w],
|
||||
[w, w],
|
||||
[w, 0.0],
|
||||
[w, -w],
|
||||
[0.0, -w],
|
||||
])
|
||||
offsets = np.array(
|
||||
[
|
||||
[-w, -w],
|
||||
[-w, 0.0],
|
||||
[-w, w],
|
||||
[0.0, w],
|
||||
[w, w],
|
||||
[w, 0.0],
|
||||
[w, -w],
|
||||
[0.0, -w],
|
||||
]
|
||||
)
|
||||
for offset in offsets:
|
||||
fractal(holes, hole, w, position + offset, depth + 1, maxDepth)
|
||||
|
||||
|
||||
def posColors(pos, _):
|
||||
return [1 - p / 2 for p in pos] + [1.0]
|
||||
return [-p + 0.5 for p in pos] + [1.0]
|
||||
|
||||
|
||||
def run(n=1):
|
||||
@@ -37,4 +38,8 @@ def run(n=1):
|
||||
result -= hole.rotate([90, 0, 0])
|
||||
result -= hole.rotate([0, 90, 0])
|
||||
|
||||
return result.trim_by_plane([1, 1, 1], 0).set_properties(4, posColors).scale(100)
|
||||
return (
|
||||
result.trim_by_plane([1, 1, 1], 0)
|
||||
.set_properties(4, posColors)
|
||||
.scale([100, 100, 100])
|
||||
)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
from manifold3d import *
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
# Creates a classic torus knot, defined as a string wrapping periodically
|
||||
# around the surface of an imaginary donut. If p and q have a common
|
||||
# factor then you will get multiple separate, interwoven knots. This is
|
||||
# an example of using the warp() method, thus avoiding any direct
|
||||
# handling of triangles.
|
||||
|
||||
|
||||
def run(warp_single=False):
|
||||
# The number of times the thread passes through the donut hole.
|
||||
p = 1
|
||||
# The number of times the thread circles the donut.
|
||||
q = 3
|
||||
# Radius of the interior of the imaginary donut.
|
||||
majorRadius = 25
|
||||
# Radius of the small cross-section of the imaginary donut.
|
||||
minorRadius = 10
|
||||
# Radius of the small cross-section of the actual object.
|
||||
threadRadius = 3.75
|
||||
# Number of linear segments making up the threadRadius circle. Default is
|
||||
# getCircularSegments(threadRadius).
|
||||
circularSegments = -1
|
||||
# Number of segments along the length of the knot. Default makes roughly
|
||||
# square facets.
|
||||
linearSegments = -1
|
||||
|
||||
# These default values recreate Matlab Knot by Emmett Lalish:
|
||||
# https://www.thingiverse.com/thing:7080
|
||||
|
||||
kLoops = np.gcd(p, q)
|
||||
pk = p / kLoops
|
||||
qk = q / kLoops
|
||||
n = (
|
||||
circularSegments
|
||||
if circularSegments > 2
|
||||
else get_circular_segments(threadRadius)
|
||||
)
|
||||
m = linearSegments if linearSegments > 2 else n * qk * majorRadius / threadRadius
|
||||
|
||||
offset = 2
|
||||
circle = CrossSection.circle(1, n).translate([offset, 0])
|
||||
|
||||
def ax_rotate(x, theta):
|
||||
a, b = (x + 1) % 3, (x + 2) % 3
|
||||
s, c = np.sin(theta), np.cos(theta)
|
||||
m = np.zeros((len(theta), 4, 4), dtype=np.float32)
|
||||
m[:, a, a], m[:, a, b] = c, s
|
||||
m[:, b, a], m[:, b, b] = -s, c
|
||||
m[:, x, x], m[:, 3, 3] = 1, 1
|
||||
return m
|
||||
|
||||
def func(pts):
|
||||
npts = pts.shape[0]
|
||||
x, y, z = pts[:, 0], pts[:, 1], pts[:, 2]
|
||||
psi = qk * np.arctan2(x, y)
|
||||
theta = psi * pk / qk
|
||||
x1 = np.sqrt(x * x + y * y)
|
||||
phi = np.arctan2(x1 - offset, z)
|
||||
|
||||
v = np.zeros((npts, 4), dtype=np.float32)
|
||||
v[:, 0] = threadRadius * np.cos(phi)
|
||||
v[:, 2] = threadRadius * np.sin(phi)
|
||||
v[:, 3] = 1
|
||||
r = majorRadius + minorRadius * np.cos(theta)
|
||||
|
||||
m1 = ax_rotate(0, -np.arctan2(pk * minorRadius, qk * r))
|
||||
m1[:, 3, 0] = minorRadius
|
||||
m2 = ax_rotate(1, theta)
|
||||
m2[:, 3, 0] = majorRadius
|
||||
m3 = ax_rotate(2, psi)
|
||||
|
||||
v = v[:, None, :] @ m1 @ m2 @ m3
|
||||
return v[:, 0, :3]
|
||||
|
||||
def func_single(v):
|
||||
pts = np.array([v])
|
||||
return func(pts)[0]
|
||||
|
||||
if warp_single:
|
||||
return Manifold.revolve(circle, int(m)).warp(func_single)
|
||||
else:
|
||||
return Manifold.revolve(circle, int(m)).warp_batch(func)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("warp_single", [True, False])
|
||||
def test_warp(warp_single):
|
||||
m = run(warp_single=warp_single)
|
||||
assert m.volume() == pytest.approx(20785.76)
|
||||
assert m.surface_area() == pytest.approx(11176.8)
|
||||
assert m.genus() == 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -1,7 +1,8 @@
|
||||
from manifold3d import Manifold
|
||||
|
||||
|
||||
def run():
|
||||
# for some reason this causes collider error
|
||||
obj = Manifold.cube()
|
||||
obj += Manifold([Manifold.cube()]).rotate(0, 0, 45+180)
|
||||
obj += Manifold.cube().rotate([0, 0, 45 + 180])
|
||||
return obj
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
from os.path import dirname
|
||||
import re
|
||||
|
||||
base = dirname(dirname(dirname(__file__)))
|
||||
|
||||
|
||||
def snake_case(name):
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
|
||||
|
||||
|
||||
def python_param_modifier(comment):
|
||||
# p = f":{snake_case(m[0][1:])}:"
|
||||
comment = re.sub(r"@(param \w+)", lambda m: f":{snake_case(m[1])}:", comment)
|
||||
# python API renames `MeshGL` to `Mesh`
|
||||
comment = re.sub("mesh_gl", "mesh", comment)
|
||||
comment = re.sub("MeshGL", "Mesh", comment)
|
||||
return comment
|
||||
|
||||
|
||||
def method_key(name):
|
||||
name = re.sub(r"\+", "_plus", name)
|
||||
name = re.sub(r"\-", "_minus", name)
|
||||
name = re.sub(r"\^", "_xor", name)
|
||||
name = re.sub(r"\=", "_eq", name)
|
||||
name = re.sub(r"\:", "_", name)
|
||||
name = re.sub(r"\~", "destroy_", name)
|
||||
return name
|
||||
|
||||
|
||||
parens_re = re.compile(r"[^(]+\(([^(]*(\(.*\))*[^(]*\))", flags=re.DOTALL)
|
||||
args_re = re.compile(
|
||||
r"^[^,^\(^\)]*(\(.*\))*[^,^\(^\)]*[\s\&\*]([0-9\w]+)\s*[,\)]", flags=re.DOTALL
|
||||
)
|
||||
|
||||
|
||||
def parse_args(s):
|
||||
par = parens_re.match(s)
|
||||
if not par:
|
||||
return None
|
||||
out = []
|
||||
arg_str = par[1]
|
||||
while m := re.search(args_re, arg_str):
|
||||
out += [snake_case(m[2])]
|
||||
arg_str = arg_str[m.span()[1] :]
|
||||
return out
|
||||
|
||||
|
||||
def collect(fname, matcher, param_modifier=python_param_modifier):
|
||||
comment = ""
|
||||
with open(fname) as f:
|
||||
for line in f:
|
||||
line = line.lstrip()
|
||||
if line.startswith("/**"):
|
||||
comment = ""
|
||||
elif line.startswith("*/"):
|
||||
pass
|
||||
elif line.startswith("*") and comment is not None:
|
||||
comment += line[1:].lstrip()
|
||||
elif comment and (m := matcher(line)):
|
||||
while (args := parse_args(line)) is None:
|
||||
line += next(f)
|
||||
if len(line) > 500:
|
||||
break
|
||||
|
||||
method = method_key(snake_case(m[1]))
|
||||
# comment = re.sub(param_re, param_modifier, comment)
|
||||
comment = param_modifier(comment)
|
||||
method = "__".join([method, *args])
|
||||
assert method not in comments
|
||||
comments[method] = comment
|
||||
comment = ""
|
||||
|
||||
|
||||
comments = {}
|
||||
|
||||
method_re = re.compile(r"(\w+::[\w\-\+\^\=\:]+)\(")
|
||||
function_re = re.compile(r"([\w\-\+\^\=\:]+)\(")
|
||||
|
||||
|
||||
# we don't handle inline functions in classes properly
|
||||
# so instead just white-list functions we want
|
||||
def select_functions(s):
|
||||
m = function_re.search(s)
|
||||
if m and "Triangulate" in m[0]:
|
||||
return m
|
||||
if m and "Circular" in m[0]:
|
||||
return m
|
||||
return None
|
||||
|
||||
|
||||
collect(f"{base}/src/manifold.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/constructors.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/sort.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/sdf.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/cross_section/cross_section.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/polygon.cpp", select_functions)
|
||||
collect(f"{base}/include/manifold/common.h", select_functions)
|
||||
|
||||
comments = dict(sorted(comments.items()))
|
||||
|
||||
with open(f"{base}/bindings/python/docstring_override.txt") as f:
|
||||
key = ""
|
||||
for l in f:
|
||||
if l.startswith(" "):
|
||||
comments[key] += l[2:]
|
||||
else:
|
||||
key = l[:-2]
|
||||
if key not in comments.keys():
|
||||
print(f"Error, unknown docstring override key {key}")
|
||||
exit(-1)
|
||||
comments[key] = ""
|
||||
|
||||
gen_h = "autogen_docstrings.inl"
|
||||
with open(gen_h, "w") as f:
|
||||
f.write("#pragma once\n\n")
|
||||
f.write("// --- AUTO GENERATED ---\n")
|
||||
f.write("// gen_docs.py is run by cmake build\n\n")
|
||||
f.write("namespace manifold_docstrings {\n")
|
||||
for key, doc in comments.items():
|
||||
f.write(f'const char* {key} = R"___({doc.strip()})___";\n')
|
||||
f.write("} // namespace manifold_docs")
|
||||
@@ -0,0 +1,13 @@
|
||||
manifold3d.__prefix__:
|
||||
import numpy as np
|
||||
from typing import Literal, TypeVar, Union
|
||||
|
||||
N = TypeVar('N', bound=np.generic)
|
||||
DoubleNx2 = np.ndarray[tuple[N, Literal[2]], np.dtype[np.double]]
|
||||
Doublex2 = Union[np.ndarray[tuple[Literal[2]], np.dtype[np.double]], tuple[float, float], list[float]]
|
||||
Doublex3 = Union[np.ndarray[tuple[Literal[3]], np.dtype[np.double]], tuple[float, float, float], list[float]]
|
||||
Double2x3 = np.ndarray[tuple[Literal[2], Literal[3]], np.dtype[np.double]]
|
||||
Double3x4 = np.ndarray[tuple[Literal[3], Literal[4]], np.dtype[np.double]]
|
||||
DoubleNx3 = np.ndarray[tuple[N, Literal[3]], np.dtype[np.double]]
|
||||
Intx3 = Union[np.ndarray[tuple[Literal[3]], np.dtype[np.integer]], tuple[int, int, int], list[int]]
|
||||
IntNx3 = np.ndarray[tuple[N, Literal[3]], np.dtype[np.integer]]
|
||||
@@ -1 +0,0 @@
|
||||
add_subdirectory(nanobind)
|
||||
@@ -0,0 +1,2 @@
|
||||
manifold.js
|
||||
manifold.wasm
|
||||
@@ -12,35 +12,53 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
project(wasm)
|
||||
|
||||
add_executable(manifoldjs bindings.cpp)
|
||||
|
||||
set_source_files_properties(bindings.cpp PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
|
||||
target_link_libraries(manifoldjs manifold sdf cross_section polygon)
|
||||
set_source_files_properties(
|
||||
bindings.cpp
|
||||
PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js
|
||||
)
|
||||
target_link_libraries(
|
||||
manifoldjs
|
||||
PRIVATE manifold $<$<BOOL:${MANIFOLD_PAR}>:TBB::tbb>
|
||||
)
|
||||
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1
|
||||
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1 -sEXPORT_ES6=1)
|
||||
target_link_options(
|
||||
manifoldjs
|
||||
PUBLIC
|
||||
--pre-js
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/bindings.js
|
||||
--bind
|
||||
-sALLOW_TABLE_GROWTH=1
|
||||
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction
|
||||
-sMODULARIZE=1
|
||||
-sEXPORT_ES6=1
|
||||
)
|
||||
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_17)
|
||||
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/built)
|
||||
|
||||
# ensure that interface files are copied over when modified
|
||||
add_custom_target(js_deps ALL
|
||||
DEPENDS manifoldjs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts)
|
||||
add_custom_target(
|
||||
js_deps
|
||||
ALL
|
||||
DEPENDS manifoldjs ${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
TARGET js_deps POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/)
|
||||
|
||||
add_custom_command(
|
||||
TARGET js_deps POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/public/)
|
||||
TARGET js_deps
|
||||
POST_BUILD
|
||||
# copy WASM build back here for publishing to npm
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
# copy WASM build and TS declarations for Vite to package into ManifoldCAD.org
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/manifold*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/
|
||||
# copy TS declarations to public so they can be accessed by our editor
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/public/
|
||||
)
|
||||
|
||||
@@ -1,30 +1,42 @@
|
||||
# About Manifold
|
||||
|
||||
[](https://codecov.io/github/elalish/manifold)
|
||||
[](https://badge.fury.io/js/manifold-3d)
|
||||
[](https://twitter.com/intent/follow?screen_name=manifoldcad)
|
||||
|
||||
[**TS Documentation**](https://manifoldcad.org/jsdocs) | [**C++ Documentation**](https://manifoldcad.org/docs/html/classmanifold_1_1_manifold.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://manifoldcad.org/model-viewer.html)
|
||||
|
||||
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Manifold also supports arbitrary vertex properties and enables mapping of materials for rendering use-cases. Our primary goal is reliability: guaranteed manifold output without caveats or edge cases. Our secondary goal is performance: efficient algorithms that make extensive use of parallelization, or pipelining when only a single thread is available.
|
||||
|
||||
## [ManifoldCAD.org](https://manifoldcad.org)
|
||||
If you like OpenSCAD / OpenJSCAD, you might also like ManifoldCAD - our own solid modelling web app based on this package.
|
||||
|
||||

|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app where you script in JS/TS and save a GLB or 3MF file. It contains several examples showing how to use our API to make interesting shapes. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, this library is dramatically faster and the code is more flexible.
|
||||
|
||||
# Manifold
|
||||

|
||||
|
||||
[**High-level Documentation**](https://elalish.blogspot.com/search/label/Manifold) | [**API Documentation**](https://elalish.github.io/manifold/docs/html/modules.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)
|
||||
## Manifold Library
|
||||
|
||||
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
|
||||
|
||||
## What's here
|
||||
|
||||
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
|
||||
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as a level set function for evaluating signed-distance functions (SDF) that improves significantly over Marching Cubes. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. We provide a [`Merge`](https://manifoldcad.org/docs/html/structmanifold_1_1_mesh_g_l_p.html) function to fix slightly non-manifold meshes, but in general you may need one of the automated repair tools that exist mostly for 3D printing.
|
||||
|
||||
The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
|
||||
|
||||
Manifold has full support for arbitrary vertex properties, and also has IDs that make it easy to keep track of materials and what surfaces belong to what input objects or faces. See our [web example](https://manifoldcad.org/model-viewer.html) for a simple demonstration of combining objects with unique textures.
|
||||
|
||||
Also included are a novel and powerful suite of refining functions for smooth mesh interpolation. They handle smoothing both triangles and quads, as well as keeping polygonal faces flat. You can easily create sharp or small-radius edges where desired, or even drive the curvature by normal vectors.
|
||||
|
||||
## Note on memory management
|
||||
|
||||
Since Manifold is a WASM module, it does not automatically garbage-collect like regular JavaScript. You must manually `delete()` each manifold object constructed by your scripts, see [discussion](https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287).
|
||||
|
||||
## Examples
|
||||
|
||||
Please see our usage [examples](https://github.com/elalish/manifold/tree/master/bindings/wasm/examples) to see how to interface this library with `three.js`, `<model-viewer>`, and `glTF`. Of particular note are the included libraries for lossless roundtrip of manifold meshes through glTF files, via a new extension: [EXT_manifold](https://github.com/KhronosGroup/glTF/pull/2286).
|
||||
Please see our usage [examples](https://github.com/elalish/manifold/tree/master/bindings/wasm/examples) to see how to interface this library with `three.js`, `<model-viewer>`, and `glTF`. Of particular note are the included libraries for lossless roundtrip of manifold meshes through glTF files, via a new extension: [EXT_mesh_manifold](https://github.com/KhronosGroup/glTF/pull/2286).
|
||||
|
||||
## 3D Formats
|
||||
|
||||
Please avoid saving to STL files! They are lossy and inefficient - when saving a manifold mesh to STL there is no guarantee that the re-imported mesh will still be manifold, as the topology is lost. Please consider using [3MF](https://3mf.io/) instead, as this format was designed from the beginning for manifold meshes representing solid objects.
|
||||
|
||||
If you use vertex properties for things like interpolated normals or texture UV coordinates, [glTF](https://www.khronos.org/Gltf) is recommended, specifically using the [`EXT_mesh_manifold`](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_manifold/README.md) extension. This allows for the lossless and efficient transmission of manifoldness even with property boundaries. Try our [make-manifold](https://manifoldcad.org/make-manifold) page to add this extension to your existing glTF/GLB models.
|
||||
|
||||
## About the author
|
||||
|
||||
|
||||
@@ -17,35 +17,56 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "helpers.cpp"
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
#include "manifold/cross_section.h"
|
||||
#include "manifold/manifold.h"
|
||||
#include "manifold/polygon.h"
|
||||
|
||||
#if (MANIFOLD_PAR == 1)
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include <atomic>
|
||||
#endif
|
||||
|
||||
// https://github.com/oneapi-src/oneTBB/blob/master/WASM_Support.md#limitations
|
||||
void initTBB() {
|
||||
#if (MANIFOLD_PAR == 1)
|
||||
int num_threads = tbb::this_task_arena::max_concurrency();
|
||||
std::atomic<int> barrier{num_threads};
|
||||
tbb::parallel_for(
|
||||
0, num_threads,
|
||||
[&barrier](int) {
|
||||
barrier--;
|
||||
while (barrier > 0) {
|
||||
// Send browser thread to event loop
|
||||
std::this_thread::yield();
|
||||
}
|
||||
},
|
||||
tbb::static_partitioner{});
|
||||
#endif
|
||||
}
|
||||
|
||||
using namespace emscripten;
|
||||
using namespace manifold;
|
||||
|
||||
EMSCRIPTEN_BINDINGS(whatever) {
|
||||
value_object<glm::vec2>("vec2")
|
||||
.field("x", &glm::vec2::x)
|
||||
.field("y", &glm::vec2::y);
|
||||
value_object<vec2>("vec2").field("x", &vec2::x).field("y", &vec2::y);
|
||||
|
||||
value_object<glm::ivec3>("ivec3")
|
||||
.field("0", &glm::ivec3::x)
|
||||
.field("1", &glm::ivec3::y)
|
||||
.field("2", &glm::ivec3::z);
|
||||
value_object<ivec3>("ivec3")
|
||||
.field("0", &ivec3::x)
|
||||
.field("1", &ivec3::y)
|
||||
.field("2", &ivec3::z);
|
||||
|
||||
value_object<glm::vec3>("vec3")
|
||||
.field("x", &glm::vec3::x)
|
||||
.field("y", &glm::vec3::y)
|
||||
.field("z", &glm::vec3::z);
|
||||
value_object<vec3>("vec3")
|
||||
.field("x", &vec3::x)
|
||||
.field("y", &vec3::y)
|
||||
.field("z", &vec3::z);
|
||||
|
||||
value_object<glm::vec4>("vec4")
|
||||
.field("x", &glm::vec4::x)
|
||||
.field("y", &glm::vec4::y)
|
||||
.field("z", &glm::vec4::z)
|
||||
.field("w", &glm::vec4::w);
|
||||
value_object<vec4>("vec4")
|
||||
.field("x", &vec4::x)
|
||||
.field("y", &vec4::y)
|
||||
.field("z", &vec4::z)
|
||||
.field("w", &vec4::w);
|
||||
|
||||
enum_<Manifold::Error>("status")
|
||||
.value("NoError", Manifold::Error::NoError)
|
||||
@@ -81,19 +102,15 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.field("halfedge", &Smoothness::halfedge)
|
||||
.field("smoothness", &Smoothness::smoothness);
|
||||
|
||||
value_object<Properties>("properties")
|
||||
.field("surfaceArea", &Properties::surfaceArea)
|
||||
.field("volume", &Properties::volume);
|
||||
|
||||
register_vector<glm::ivec3>("Vector_ivec3");
|
||||
register_vector<glm::vec3>("Vector_vec3");
|
||||
register_vector<glm::vec2>("Vector_vec2");
|
||||
register_vector<std::vector<glm::vec2>>("Vector2_vec2");
|
||||
register_vector<float>("Vector_f32");
|
||||
register_vector<ivec3>("Vector_ivec3");
|
||||
register_vector<vec3>("Vector_vec3");
|
||||
register_vector<vec2>("Vector_vec2");
|
||||
register_vector<std::vector<vec2>>("Vector2_vec2");
|
||||
register_vector<double>("Vector_f64");
|
||||
register_vector<CrossSection>("Vector_crossSection");
|
||||
register_vector<Manifold>("Vector_manifold");
|
||||
register_vector<Smoothness>("Vector_smoothness");
|
||||
register_vector<glm::vec4>("Vector_vec4");
|
||||
register_vector<vec4>("Vector_vec4");
|
||||
|
||||
class_<CrossSection>("CrossSection")
|
||||
.constructor(&cross_js::OfPolygons)
|
||||
@@ -126,9 +143,9 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
function("_crossSectionDifferenceN", &cross_js::DifferenceN);
|
||||
function("_crossSectionIntersectionN", &cross_js::IntersectionN);
|
||||
function("_crossSectionCollectVertices", &cross_js::CollectVertices);
|
||||
function("_crossSectionHullPoints",
|
||||
select_overload<CrossSection(std::vector<glm::vec2>)>(
|
||||
&CrossSection::Hull));
|
||||
function(
|
||||
"_crossSectionHullPoints",
|
||||
select_overload<CrossSection(std::vector<vec2>)>(&CrossSection::Hull));
|
||||
|
||||
class_<Manifold>("Manifold")
|
||||
.constructor(&man_js::FromMeshJS)
|
||||
@@ -138,9 +155,15 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("_Split", &man_js::Split)
|
||||
.function("_SplitByPlane", &man_js::SplitByPlane)
|
||||
.function("_TrimByPlane", &Manifold::TrimByPlane)
|
||||
.function("_Slice", &Manifold::Slice)
|
||||
.function("_Project", &Manifold::Project)
|
||||
.function("hull", select_overload<Manifold() const>(&Manifold::Hull))
|
||||
.function("_GetMeshJS", &js::GetMeshJS)
|
||||
.function("refine", &Manifold::Refine)
|
||||
.function("refineToLength", &Manifold::RefineToLength)
|
||||
.function("refineToTolerance", &Manifold::RefineToTolerance)
|
||||
.function("smoothByNormals", &Manifold::SmoothByNormals)
|
||||
.function("_SmoothOut", &Manifold::SmoothOut)
|
||||
.function("_Warp", &man_js::Warp)
|
||||
.function("_SetProperties", &man_js::SetProperties)
|
||||
.function("transform", &man_js::Transform)
|
||||
@@ -158,10 +181,14 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("numProp", &Manifold::NumProp)
|
||||
.function("numPropVert", &Manifold::NumPropVert)
|
||||
.function("_boundingBox", &Manifold::BoundingBox)
|
||||
.function("precision", &Manifold::Precision)
|
||||
.function("tolerance", &Manifold::GetTolerance)
|
||||
.function("setTolerance", &Manifold::SetTolerance)
|
||||
.function("genus", &Manifold::Genus)
|
||||
.function("getProperties", &Manifold::GetProperties)
|
||||
.function("volume", &Manifold::Volume)
|
||||
.function("surfaceArea", &Manifold::SurfaceArea)
|
||||
.function("minGap", &Manifold::MinGap)
|
||||
.function("calculateCurvature", &Manifold::CalculateCurvature)
|
||||
.function("_CalculateNormals", &Manifold::CalculateNormals)
|
||||
.function("originalID", &Manifold::OriginalID)
|
||||
.function("asOriginal", &Manifold::AsOriginal);
|
||||
|
||||
@@ -182,13 +209,16 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
function("_manifoldDifferenceN", &man_js::DifferenceN);
|
||||
function("_manifoldIntersectionN", &man_js::IntersectionN);
|
||||
function("_manifoldCollectVertices", &man_js::CollectVertices);
|
||||
function("_manifoldHullPoints",
|
||||
select_overload<Manifold(const std::vector<glm::vec3>&)>(
|
||||
&Manifold::Hull));
|
||||
function(
|
||||
"_manifoldHullPoints",
|
||||
select_overload<Manifold(const std::vector<vec3>&)>(&Manifold::Hull));
|
||||
|
||||
// Quality Globals
|
||||
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
|
||||
function("setMinCircularEdgeLength", &Quality::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Quality::SetCircularSegments);
|
||||
function("getCircularSegments", &Quality::GetCircularSegments);
|
||||
function("resetToCircularDefaults", &Quality::ResetToDefaults);
|
||||
|
||||
function("initTBB", &initTBB);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ Module.setup = function() {
|
||||
if (_ManifoldInitialized) return;
|
||||
_ManifoldInitialized = true;
|
||||
|
||||
// warmup tbb for emscripten, according to
|
||||
// https://github.com/oneapi-src/oneTBB/blob/master/WASM_Support.md#limitations
|
||||
Module.initTBB();
|
||||
// conversion utilities
|
||||
|
||||
function toVec(vec, list, f = x => x) {
|
||||
@@ -128,12 +131,12 @@ Module.setup = function() {
|
||||
|
||||
Module.CrossSection.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec2Ptr) {
|
||||
const x = getValue(vec2Ptr, 'float');
|
||||
const y = getValue(vec2Ptr + 4, 'float');
|
||||
const x = getValue(vec2Ptr, 'double');
|
||||
const y = getValue(vec2Ptr + 8, 'double');
|
||||
const vert = [x, y];
|
||||
func(vert);
|
||||
setValue(vec2Ptr, vert[0], 'float');
|
||||
setValue(vec2Ptr + 4, vert[1], 'float');
|
||||
setValue(vec2Ptr, vert[0], 'double');
|
||||
setValue(vec2Ptr + 8, vert[1], 'double');
|
||||
}, 'vi');
|
||||
const out = this._Warp(wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
@@ -165,14 +168,15 @@ Module.setup = function() {
|
||||
height, nDivisions = 0, twistDegrees = 0.0, scaleTop = [1.0, 1.0],
|
||||
center = false) {
|
||||
scaleTop = vararg2vec2([scaleTop]);
|
||||
const man =
|
||||
Module._Extrude(this, height, nDivisions, twistDegrees, scaleTop);
|
||||
const man = Module._Extrude(
|
||||
this._ToPolygons(), height, nDivisions, twistDegrees, scaleTop);
|
||||
return (center ? man.translate([0., 0., -height / 2.]) : man);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.revolve = function(
|
||||
circularSegments = 0, revolveDegrees = 360.0) {
|
||||
return Module._Revolve(this, circularSegments, revolveDegrees);
|
||||
return Module._Revolve(
|
||||
this._ToPolygons(), circularSegments, revolveDegrees);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.add = function(other) {
|
||||
@@ -189,23 +193,28 @@ Module.setup = function() {
|
||||
|
||||
Module.CrossSection.prototype.toPolygons = function() {
|
||||
const vec = this._ToPolygons();
|
||||
const result = vec2polygons(vec);
|
||||
const result = vec2polygons(vec, v => [v.x, v.y]);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
// Manifold methods
|
||||
|
||||
Module.Manifold.prototype.smoothOut = function(
|
||||
minSharpAngle = 60, minSmoothness = 0) {
|
||||
return this._SmoothOut(minSharpAngle, minSmoothness);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
const x = getValue(vec3Ptr, 'float');
|
||||
const y = getValue(vec3Ptr + 4, 'float');
|
||||
const z = getValue(vec3Ptr + 8, 'float');
|
||||
const x = getValue(vec3Ptr, 'double');
|
||||
const y = getValue(vec3Ptr + 8, 'double');
|
||||
const z = getValue(vec3Ptr + 16, 'double');
|
||||
const vert = [x, y, z];
|
||||
func(vert);
|
||||
setValue(vec3Ptr, vert[0], 'float');
|
||||
setValue(vec3Ptr + 4, vert[1], 'float');
|
||||
setValue(vec3Ptr + 8, vert[2], 'float');
|
||||
setValue(vec3Ptr, vert[0], 'double');
|
||||
setValue(vec3Ptr + 8, vert[1], 'double');
|
||||
setValue(vec3Ptr + 16, vert[2], 'double');
|
||||
}, 'vi');
|
||||
const out = this._Warp(wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
@@ -217,26 +226,31 @@ Module.setup = function() {
|
||||
return out;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.calculateNormals = function(
|
||||
normalIdx, minSharpAngle = 60) {
|
||||
return this._CalculateNormals(normalIdx, minSharpAngle);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.setProperties = function(numProp, func) {
|
||||
const oldNumProp = this.numProp();
|
||||
const wasmFuncPtr = addFunction(function(newPtr, vec3Ptr, oldPtr) {
|
||||
const newProp = [];
|
||||
for (let i = 0; i < numProp; ++i) {
|
||||
newProp[i] = getValue(newPtr + 4 * i, 'float');
|
||||
newProp[i] = getValue(newPtr + 8 * i, 'double');
|
||||
}
|
||||
const pos = [];
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
pos[i] = getValue(vec3Ptr + 4 * i, 'float');
|
||||
pos[i] = getValue(vec3Ptr + 8 * i, 'double');
|
||||
}
|
||||
const oldProp = [];
|
||||
for (let i = 0; i < oldNumProp; ++i) {
|
||||
oldProp[i] = getValue(oldPtr + 4 * i, 'float');
|
||||
oldProp[i] = getValue(oldPtr + 8 * i, 'double');
|
||||
}
|
||||
|
||||
func(newProp, pos, oldProp);
|
||||
|
||||
for (let i = 0; i < numProp; ++i) {
|
||||
setValue(newPtr + 4 * i, newProp[i], 'float');
|
||||
setValue(newPtr + 8 * i, newProp[i], 'double');
|
||||
}
|
||||
}, 'viii');
|
||||
const out = this._SetProperties(numProp, wasmFuncPtr);
|
||||
@@ -248,8 +262,12 @@ Module.setup = function() {
|
||||
return this._Translate(vararg2vec3(vec));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.rotate = function(vec) {
|
||||
return this._Rotate(...vec);
|
||||
Module.Manifold.prototype.rotate = function(xOrVec, y, z) {
|
||||
if (Array.isArray(xOrVec)) {
|
||||
return this._Rotate(...xOrVec);
|
||||
} else {
|
||||
return this._Rotate(xOrVec, y || 0, z || 0);
|
||||
}
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.scale = function(vec) {
|
||||
@@ -268,15 +286,29 @@ Module.setup = function() {
|
||||
return this._TrimByPlane(vararg2vec3([normal]), offset);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.slice = function(height = 0.) {
|
||||
const polygonsVec = this._Slice(height);
|
||||
const result = new CrossSectionCtor(polygonsVec, fillRuleToInt('Positive'));
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.project = function() {
|
||||
const polygonsVec = this._Project();
|
||||
const result = new CrossSectionCtor(polygonsVec, fillRuleToInt('Positive'));
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.split = function(manifold) {
|
||||
const vec = this._split(manifold);
|
||||
const vec = this._Split(manifold);
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.splitByPlane = function(normal, offset = 0.) {
|
||||
const vec = this._splitByPlane(vararg2vec3([normal]), offset);
|
||||
const vec = this._SplitByPlane(vararg2vec3([normal]), offset);
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
@@ -345,12 +377,13 @@ Module.setup = function() {
|
||||
}
|
||||
|
||||
position(vert) {
|
||||
return this.vertProperties.subarray(numProp * vert, numProp * vert + 3);
|
||||
return this.vertProperties.subarray(
|
||||
this.numProp * vert, this.numProp * vert + 3);
|
||||
}
|
||||
|
||||
extras(vert) {
|
||||
return this.vertProperties.subarray(
|
||||
numProp * vert + 3, numProp * (vert + 1));
|
||||
this.numProp * vert + 3, this.numProp * (vert + 1));
|
||||
}
|
||||
|
||||
tangent(halfedge) {
|
||||
@@ -371,9 +404,7 @@ Module.setup = function() {
|
||||
|
||||
Module.Mesh = Mesh;
|
||||
|
||||
Module.Manifold.prototype.getMesh = function(normalIdx = [0, 0, 0]) {
|
||||
if (normalIdx instanceof Array)
|
||||
normalIdx = {0: normalIdx[0], 1: normalIdx[1], 2: normalIdx[2]};
|
||||
Module.Manifold.prototype.getMesh = function(normalIdx = -1) {
|
||||
return new Mesh(this._GetMeshJS(normalIdx));
|
||||
};
|
||||
|
||||
@@ -609,19 +640,21 @@ Module.setup = function() {
|
||||
Module.Manifold.difference = manifoldBatchbool('Difference');
|
||||
Module.Manifold.intersection = manifoldBatchbool('Intersection');
|
||||
|
||||
Module.Manifold.levelSet = function(sdf, bounds, edgeLength, level = 0) {
|
||||
Module.Manifold.levelSet = function(
|
||||
sdf, bounds, edgeLength, level = 0, tolerance = -1) {
|
||||
const bounds2 = {
|
||||
min: {x: bounds.min[0], y: bounds.min[1], z: bounds.min[2]},
|
||||
max: {x: bounds.max[0], y: bounds.max[1], z: bounds.max[2]},
|
||||
};
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
const x = getValue(vec3Ptr, 'float');
|
||||
const y = getValue(vec3Ptr + 4, 'float');
|
||||
const z = getValue(vec3Ptr + 8, 'float');
|
||||
const x = getValue(vec3Ptr, 'double');
|
||||
const y = getValue(vec3Ptr + 8, 'double');
|
||||
const z = getValue(vec3Ptr + 16, 'double');
|
||||
const vert = [x, y, z];
|
||||
return sdf(vert);
|
||||
}, 'fi');
|
||||
const out = Module._LevelSet(wasmFuncPtr, bounds2, edgeLength, level);
|
||||
}, 'di');
|
||||
const out =
|
||||
Module._LevelSet(wasmFuncPtr, bounds2, edgeLength, level, tolerance);
|
||||
removeFunction(wasmFuncPtr);
|
||||
return out;
|
||||
};
|
||||
@@ -665,10 +698,10 @@ Module.setup = function() {
|
||||
|
||||
// Top-level functions
|
||||
|
||||
Module.triangulate = function(polygons, precision = -1) {
|
||||
Module.triangulate = function(polygons, epsilon = -1) {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = fromVec(
|
||||
Module._Triangulate(polygonsVec, precision), (x) => [x[0], x[1], x[2]]);
|
||||
Module._Triangulate(polygonsVec, epsilon), (x) => [x[0], x[1], x[2]]);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Get Started
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
In your project root folder run:
|
||||
|
||||
```bash
|
||||
npm i manifold-3d
|
||||
```
|
||||
|
||||
To start using Manifold, import it and initialize it:
|
||||
|
||||
```js
|
||||
import Module from 'manifold-3d';
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
const { Manifold } = wasm;
|
||||
```
|
||||
|
||||
Intro example
|
||||
|
||||
```js
|
||||
const {cube, sphere} = Manifold;
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
const result = box.subtract(ball);
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,28 @@
|
||||
import {beforeAll, expect, suite, test} from 'vitest';
|
||||
|
||||
import Module, {type ManifoldToplevel} from './built/manifold'
|
||||
|
||||
let manifoldModule: ManifoldToplevel;
|
||||
|
||||
beforeAll(async () => {
|
||||
manifoldModule = await Module();
|
||||
manifoldModule.setup();
|
||||
});
|
||||
|
||||
suite('CrossSection Bindings', () => {
|
||||
test('ToPolygons return correct shape', () => {
|
||||
const polygons = manifoldModule.CrossSection.square().toPolygons();
|
||||
expect(polygons).toHaveLength(1);
|
||||
expect(polygons[0]).toHaveLength(4);
|
||||
expect(polygons[0]).toContainEqual([0, 0]);
|
||||
expect(polygons[0]).toContainEqual([0, 1]);
|
||||
expect(polygons[0]).toContainEqual([1, 0]);
|
||||
expect(polygons[0]).toContainEqual([1, 1]);
|
||||
});
|
||||
|
||||
test('project creates a valid polygon', () => {
|
||||
const cs = manifoldModule.Manifold.sphere(1).project();
|
||||
expect(cs.numContour()).toEqual(1);
|
||||
expect(cs.area()).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -65,7 +65,7 @@ button:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
input {
|
||||
.name {
|
||||
border: none;
|
||||
height: 36px;
|
||||
background-color: transparent;
|
||||
@@ -77,12 +77,12 @@ input {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
.name:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em;
|
||||
margin: 18px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ model-viewer {
|
||||
|
||||
.header .margin {
|
||||
font-size: 16px;
|
||||
margin: 1em 0.5em;
|
||||
margin: 18px 8px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
@@ -168,6 +168,29 @@ model-viewer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.top-edit {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 36px;
|
||||
top: 18px;
|
||||
border: none;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.top-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 36px;
|
||||
background-size: 24px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.squish {
|
||||
margin-right: -12px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
background-color: transparent;
|
||||
@@ -180,6 +203,22 @@ model-viewer {
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.undo {
|
||||
background-image: url(/icons/undo.png);
|
||||
}
|
||||
|
||||
.redo {
|
||||
background-image: url(/icons/redo.png);
|
||||
}
|
||||
|
||||
.star {
|
||||
background-image: url(/icons/star.png);
|
||||
}
|
||||
|
||||
.share {
|
||||
background-image: url(/icons/share.png);
|
||||
}
|
||||
|
||||
.cancel {
|
||||
background-image: url(/icons/close.png);
|
||||
background-size: 20px;
|
||||
@@ -196,10 +235,22 @@ model-viewer {
|
||||
background-image: url(/icons/trash.png);
|
||||
}
|
||||
|
||||
.play {
|
||||
background-image: url(/icons/play.png);
|
||||
}
|
||||
|
||||
.pause {
|
||||
background-image: url(/icons/pause.png);
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.top-icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.blue {
|
||||
background-color: rgb(148, 221, 255);
|
||||
}
|
||||
@@ -221,7 +272,52 @@ model-viewer {
|
||||
width: 125px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
#animation {
|
||||
display: none;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#play {
|
||||
position: relative;
|
||||
background-color: #ccc;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: relative;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
margin-right: 16px;
|
||||
background: #ffffff55;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider::-moz-range-thumb {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 820px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
@@ -256,4 +352,4 @@ model-viewer {
|
||||
width: calc(50% - 16px);
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import ManifoldWorker from './worker?worker';
|
||||
import ManifoldWorker from './worker-wrapper?worker';
|
||||
|
||||
const CODE_START = '<code>';
|
||||
// Loaded globally by examples.js
|
||||
const exampleFunctions = self.examples.functionBodies;
|
||||
|
||||
@@ -24,6 +25,28 @@ if (navigator.serviceWorker) {
|
||||
|
||||
let editor = undefined;
|
||||
|
||||
// Edit UI ------------------------------------------------------------
|
||||
|
||||
const undoButton = document.querySelector('#undo');
|
||||
const redoButton = document.querySelector('#redo');
|
||||
const formatButton = document.querySelector('#format');
|
||||
const shareButton = document.querySelector('#share');
|
||||
|
||||
undoButton.onclick = () => editor.trigger('ignored', 'undo');
|
||||
redoButton.onclick = () => editor.trigger('ignored', 'redo');
|
||||
formatButton.onclick = () =>
|
||||
editor.trigger('ignored', 'editor.action.formatDocument');
|
||||
shareButton.onclick = () => {
|
||||
const url = new URL(window.location.toString());
|
||||
url.hash =
|
||||
'#' +
|
||||
encodeURIComponent(
|
||||
currentFileElement.textContent + CODE_START + editor.getValue());
|
||||
navigator.clipboard.writeText(url.toString());
|
||||
console.log('Shareable link copied to clipboard!');
|
||||
console.log('Consider shortening this URL using tinyURL.com');
|
||||
};
|
||||
|
||||
// File UI ------------------------------------------------------------
|
||||
const fileButton = document.querySelector('#file');
|
||||
const currentFileElement = document.querySelector('#current');
|
||||
@@ -90,9 +113,10 @@ function switchTo(scriptName) {
|
||||
switching = true;
|
||||
currentFileElement.textContent = scriptName;
|
||||
setScript('currentName', scriptName);
|
||||
const code =
|
||||
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
|
||||
isExample = exampleFunctions.get(scriptName) != null;
|
||||
const code = isExample ? exampleFunctions.get(scriptName).substring(1) :
|
||||
getScript(scriptName) ?? '';
|
||||
window.location.hash = '#' + scriptName;
|
||||
editor.setValue(code);
|
||||
}
|
||||
}
|
||||
@@ -146,6 +170,7 @@ function addEdit(button) {
|
||||
const code = getScript(oldName);
|
||||
const form = document.createElement('form');
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.classList.add('name');
|
||||
inputElement.value = oldName;
|
||||
label.textContent = '';
|
||||
button.appendChild(form);
|
||||
@@ -203,16 +228,16 @@ function addEdit(button) {
|
||||
}
|
||||
|
||||
const newButton = document.querySelector('#new');
|
||||
function newItem(code) {
|
||||
const name = uniqueName('New Script');
|
||||
function newItem(code, scriptName = undefined) {
|
||||
const name = uniqueName(scriptName ?? 'New Script');
|
||||
setScript(name, code);
|
||||
const nextButton = createDropdownItem(name);
|
||||
newButton.insertAdjacentElement('afterend', nextButton.parentElement);
|
||||
addEdit(nextButton);
|
||||
nextButton.click();
|
||||
return {button: nextButton, name};
|
||||
};
|
||||
newButton.onclick = function() {
|
||||
newItem('');
|
||||
newItem('').button.click();
|
||||
};
|
||||
|
||||
const runButton = document.querySelector('#compile');
|
||||
@@ -225,7 +250,7 @@ function initializeRun() {
|
||||
if (autoExecute) {
|
||||
runButton.click();
|
||||
} else {
|
||||
poster.textContent = 'Auto-run disabled due to prior failure';
|
||||
poster.textContent = 'Auto-run disabled';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +276,7 @@ declare interface ManifoldToplevel {
|
||||
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof T.setCircularSegments;
|
||||
getCircularSegments: typeof T.getCircularSegments;
|
||||
resetToCircularDefaults: typeof T.resetToCircularDefaults;
|
||||
setup: () => void;
|
||||
}
|
||||
declare const module: ManifoldToplevel;
|
||||
@@ -298,7 +324,24 @@ require(['vs/editor/editor.main'], async function() {
|
||||
addEdit(button);
|
||||
}
|
||||
}
|
||||
switchTo(currentName);
|
||||
|
||||
if (window.location.hash.length > 0) {
|
||||
const fragment = decodeURIComponent(window.location.hash.substring(1));
|
||||
const codeIdx = fragment.indexOf(CODE_START);
|
||||
if (codeIdx != -1) {
|
||||
autoExecute = true;
|
||||
const name = fragment.substring(0, codeIdx);
|
||||
switchTo(
|
||||
newItem(fragment.substring(codeIdx + CODE_START.length), name).name);
|
||||
} else {
|
||||
if (fragment != currentName) {
|
||||
autoExecute = true;
|
||||
}
|
||||
switchTo(fragment);
|
||||
}
|
||||
} else {
|
||||
switchTo(currentName);
|
||||
}
|
||||
|
||||
if (manifoldInitialized) {
|
||||
initializeRun();
|
||||
@@ -308,11 +351,12 @@ require(['vs/editor/editor.main'], async function() {
|
||||
runButton.disabled = false;
|
||||
if (switching) {
|
||||
switching = false;
|
||||
editor.setScrollTop(0);
|
||||
return;
|
||||
}
|
||||
if (isExample) {
|
||||
const cursor = editor.getPosition();
|
||||
newItem(editor.getValue());
|
||||
newItem(editor.getValue()).button.click();
|
||||
editor.setPosition(cursor);
|
||||
}
|
||||
});
|
||||
@@ -322,6 +366,51 @@ require(['vs/editor/editor.main'], async function() {
|
||||
};
|
||||
});
|
||||
|
||||
// Animation ------------------------------------------------------------
|
||||
const mv = document.querySelector('model-viewer');
|
||||
const animationContainer = document.querySelector('#animation');
|
||||
const playButton = document.querySelector('#play');
|
||||
const scrubber = document.querySelector('#scrubber');
|
||||
let paused = false;
|
||||
|
||||
mv.addEventListener('load', () => {
|
||||
const hasAnimation = mv.availableAnimations.length > 0;
|
||||
animationContainer.style.display = hasAnimation ? 'flex' : 'none';
|
||||
if (hasAnimation) {
|
||||
play();
|
||||
}
|
||||
});
|
||||
|
||||
function play() {
|
||||
mv.play();
|
||||
playButton.classList.remove('play');
|
||||
playButton.classList.add('pause');
|
||||
paused = false;
|
||||
scrubber.classList.add('hide');
|
||||
}
|
||||
|
||||
function pause() {
|
||||
mv.pause();
|
||||
playButton.classList.remove('pause');
|
||||
playButton.classList.add('play');
|
||||
paused = true;
|
||||
scrubber.max = mv.duration;
|
||||
scrubber.value = mv.currentTime;
|
||||
scrubber.classList.remove('hide');
|
||||
}
|
||||
|
||||
playButton.onclick = function() {
|
||||
if (paused) {
|
||||
play();
|
||||
} else {
|
||||
pause();
|
||||
}
|
||||
};
|
||||
|
||||
scrubber.oninput = function() {
|
||||
mv.currentTime = scrubber.value;
|
||||
};
|
||||
|
||||
// Execution ------------------------------------------------------------
|
||||
const consoleElement = document.querySelector('#console');
|
||||
const oldLog = console.log;
|
||||
@@ -345,19 +434,13 @@ function disableCancel() {
|
||||
runButton.classList.remove('red', 'cancel');
|
||||
}
|
||||
|
||||
let t0 = performance.now();
|
||||
|
||||
function finishRun() {
|
||||
disableCancel();
|
||||
const t1 = performance.now();
|
||||
const log = consoleElement.textContent;
|
||||
// Remove "Running..."
|
||||
consoleElement.textContent = log.substring(log.indexOf('\n') + 1);
|
||||
console.log(
|
||||
`Took ${(Math.round((t1 - t0) / 10) / 100).toLocaleString()} seconds`);
|
||||
}
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
const output = {
|
||||
glbURL: null,
|
||||
threeMFURL: null
|
||||
@@ -413,7 +496,6 @@ async function run() {
|
||||
console.log('Running...');
|
||||
const output = await tsWorker.getEmitOutput(editor.getModel().uri.toString());
|
||||
manifoldWorker.postMessage(output.outputFiles[0].text);
|
||||
t0 = performance.now();
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Accessor} from '@gltf-transform/core';
|
||||
|
||||
import {EXTManifold} from './manifold-gltf.js';
|
||||
|
||||
export const attributeDefs = {
|
||||
'POSITION': {type: Accessor.Type.VEC3, components: 3},
|
||||
'NORMAL': {type: Accessor.Type.VEC3, components: 3},
|
||||
'TANGENT': {type: Accessor.Type.VEC4, components: 4},
|
||||
'TEXCOORD_0': {type: Accessor.Type.VEC2, components: 2},
|
||||
'TEXCOORD_1': {type: Accessor.Type.VEC2, components: 2},
|
||||
'COLOR_0': {type: Accessor.Type.VEC3, components: 3},
|
||||
'JOINTS_0': {type: Accessor.Type.VEC4, components: 4},
|
||||
'WEIGHTS_0': {type: Accessor.Type.VEC4, components: 4},
|
||||
'SKIP_1': {type: null, components: 1},
|
||||
'SKIP_2': {type: null, components: 2},
|
||||
'SKIP_3': {type: null, components: 3},
|
||||
'SKIP_4': {type: null, components: 4},
|
||||
};
|
||||
|
||||
export function setupIO(io) {
|
||||
return io.registerExtensions([EXTManifold]);
|
||||
}
|
||||
|
||||
function readPrimitive(primitive, numProp, attributes) {
|
||||
const position = primitive.getAttribute('POSITION');
|
||||
const numVert = position.getCount();
|
||||
const vertProperties = [];
|
||||
let offset = 0;
|
||||
for (const attribute of attributes) {
|
||||
const size = attributeDefs[attribute].components;
|
||||
if (attribute === 'SKIP') {
|
||||
offset += size;
|
||||
continue;
|
||||
}
|
||||
const accessor = primitive.getAttribute(attribute);
|
||||
if (accessor) {
|
||||
const array = accessor.getArray();
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
for (let j = 0; j < size; ++j) {
|
||||
vertProperties[numProp * i + offset + j] = array[i * size + j];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
for (let j = 0; j < size; ++j) {
|
||||
vertProperties[numProp * i + offset + j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
return vertProperties;
|
||||
}
|
||||
|
||||
export function readMesh(mesh, attributes, materials) {
|
||||
const primitives = mesh.listPrimitives();
|
||||
if (primitives.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (attributes.length === 0) {
|
||||
const attributeSet = new Set();
|
||||
for (const primitive of primitives) {
|
||||
const semantics = primitive.listSemantics();
|
||||
for (const semantic of semantics) {
|
||||
attributeSet.add(semantic);
|
||||
}
|
||||
}
|
||||
for (const semantic in attributeDefs) {
|
||||
if (attributeSet.has(semantic)) {
|
||||
attributes.push(semantic);
|
||||
attributeSet.delete(semantic);
|
||||
}
|
||||
}
|
||||
for (const semantic of attributeSet.keys()) {
|
||||
attributes.push(semantic);
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes.length < 1 || attributes[0] !== 'POSITION')
|
||||
throw new Error('First attribute must be "POSITION".');
|
||||
|
||||
const numProp = attributes.map((def) => attributeDefs[def].components)
|
||||
.reduce((a, b) => a + b);
|
||||
|
||||
const manifoldPrimitive = mesh.getExtension('EXT_manifold');
|
||||
|
||||
let vertPropArray = [];
|
||||
let triVertArray = [];
|
||||
const runIndexArray = [0];
|
||||
const mergeFromVert = [];
|
||||
const mergeToVert = [];
|
||||
if (manifoldPrimitive != null) {
|
||||
vertPropArray = readPrimitive(primitives[0], numProp, attributes);
|
||||
for (const primitive of primitives) {
|
||||
triVertArray = [...triVertArray, ...primitive.getIndices().getArray()];
|
||||
runIndexArray.push(triVertArray.length);
|
||||
materials.push(primitive.getMaterial());
|
||||
}
|
||||
const mergeTriVert = manifoldPrimitive.getMergeIndices()?.getArray() ?? [];
|
||||
const mergeTo = manifoldPrimitive.getMergeValues()?.getArray() ?? [];
|
||||
const vert2merge = new Map();
|
||||
for (const [i, idx] of mergeTriVert.entries()) {
|
||||
vert2merge.set(triVertArray[idx], mergeTo[i]);
|
||||
}
|
||||
for (const [from, to] of vert2merge.entries()) {
|
||||
mergeFromVert.push(from);
|
||||
mergeToVert.push(to);
|
||||
}
|
||||
} else {
|
||||
for (const primitive of primitives) {
|
||||
const numVert = vertPropArray.length / numProp;
|
||||
vertPropArray =
|
||||
[...vertPropArray, ...readPrimitive(primitive, numProp, attributes)];
|
||||
triVertArray = [
|
||||
...triVertArray,
|
||||
...primitive.getIndices().getArray().map((i) => i + numVert)
|
||||
];
|
||||
runIndexArray.push(triVertArray.length);
|
||||
materials.push(primitive.getMaterial());
|
||||
}
|
||||
}
|
||||
const vertProperties = new Float32Array(vertPropArray);
|
||||
const triVerts = new Uint32Array(triVertArray);
|
||||
const runIndex = new Uint32Array(runIndexArray);
|
||||
|
||||
return {
|
||||
numProp,
|
||||
triVerts,
|
||||
vertProperties,
|
||||
runIndex,
|
||||
mergeFromVert,
|
||||
mergeToVert
|
||||
};
|
||||
}
|
||||
|
||||
export function writeMesh(doc, manifoldMesh, attributes, materials) {
|
||||
if (doc.getRoot().listBuffers().length === 0) {
|
||||
doc.createBuffer();
|
||||
}
|
||||
const buffer = doc.getRoot().listBuffers()[0];
|
||||
const manifoldExtension = doc.createExtension(EXTManifold);
|
||||
|
||||
const attributeUnion = [];
|
||||
for (const matAttributes of attributes) {
|
||||
matAttributes.forEach((attribute, i) => {
|
||||
if (i >= attributeUnion.length) {
|
||||
attributeUnion.push(attribute);
|
||||
} else {
|
||||
const size = attributeDefs[attribute].components;
|
||||
const unionSize = attributeDefs[attributeUnion[i]].components;
|
||||
if (size != unionSize) {
|
||||
throw new Error(
|
||||
'Attribute sizes do not correspond: ', attribute, ' and ',
|
||||
attributeUnion[i]);
|
||||
}
|
||||
if (attributeDefs[attributeUnion[i]].type == null) {
|
||||
attributeUnion[i] = attribute;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (attributeUnion.length < 1 || attributeUnion[0] !== 'POSITION')
|
||||
throw new Error('First attribute must be "POSITION".');
|
||||
|
||||
const mesh = doc.createMesh();
|
||||
const numPrimitive = manifoldMesh.runIndex.length - 1;
|
||||
for (let run = 0; run < numPrimitive; ++run) {
|
||||
const indices = doc.createAccessor('indices')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array());
|
||||
const primitive = doc.createPrimitive().setIndices(indices);
|
||||
const material = materials[run];
|
||||
if (material) {
|
||||
primitive.setMaterial(material);
|
||||
}
|
||||
mesh.addPrimitive(primitive);
|
||||
}
|
||||
|
||||
const numVert = manifoldMesh.numVert;
|
||||
const numProp = manifoldMesh.numProp;
|
||||
let offset = 0;
|
||||
attributeUnion.forEach((attribute, aIdx) => {
|
||||
if (attribute === 'SKIP') {
|
||||
++offset;
|
||||
return;
|
||||
}
|
||||
const def = attributeDefs[attribute];
|
||||
if (def == null)
|
||||
throw new Error(attribute + ' is not a recognized attribute.');
|
||||
|
||||
const n = def.components;
|
||||
if (offset + n > numProp) throw new Error('Too many attribute channels.');
|
||||
|
||||
const array = new Float32Array(n * numVert);
|
||||
for (let v = 0; v < numVert; ++v) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
array[n * v + i] =
|
||||
manifoldMesh.vertProperties[numProp * v + offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
const accessor =
|
||||
doc.createAccessor().setBuffer(buffer).setType(def.type).setArray(
|
||||
array);
|
||||
|
||||
mesh.listPrimitives().forEach((primitive, pIdx) => {
|
||||
if (attributes[pIdx].length > aIdx &&
|
||||
attributeDefs[attributes[pIdx][aIdx]].type != null) {
|
||||
primitive.setAttribute(attribute, accessor);
|
||||
}
|
||||
});
|
||||
offset += n;
|
||||
});
|
||||
|
||||
const manifoldPrimitive = manifoldExtension.createManifoldPrimitive();
|
||||
mesh.setExtension('EXT_manifold', manifoldPrimitive);
|
||||
|
||||
const indices = doc.createAccessor('indices')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(manifoldMesh.triVerts);
|
||||
manifoldPrimitive.setIndices(indices);
|
||||
manifoldPrimitive.setRunIndex(manifoldMesh.runIndex);
|
||||
|
||||
const vert2merge = [...Array(manifoldMesh.numVert).keys()];
|
||||
for (const [i, from] of manifoldMesh.mergeFromVert.entries()) {
|
||||
vert2merge[from] = manifoldMesh.mergeToVert[i];
|
||||
}
|
||||
const ind = [];
|
||||
const val = [];
|
||||
for (const [i, vert] of manifoldMesh.triVerts.entries()) {
|
||||
const newVert = vert2merge[vert];
|
||||
if (vert !== newVert) {
|
||||
ind.push(i);
|
||||
val.push(newVert);
|
||||
}
|
||||
}
|
||||
const indicesAccessor = doc.createAccessor()
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(ind));
|
||||
const valuesAccessor = doc.createAccessor()
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(val));
|
||||
manifoldPrimitive.setMerge(indicesAccessor, valuesAccessor);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
export function disposeMesh(mesh) {
|
||||
if (!mesh) return;
|
||||
const primitives = mesh.listPrimitives();
|
||||
for (const primitive of primitives) {
|
||||
primitive.getIndices()?.dispose();
|
||||
for (const accessor of primitive.listAttributes()) {
|
||||
accessor.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
mesh.dispose();
|
||||
}
|
||||
|
||||
export async function loadTexture(texture, uri) {
|
||||
const response = await fetch(uri);
|
||||
const blob = await response.blob();
|
||||
texture.setMimeType(blob.type);
|
||||
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Accessor, Document, Material, Mesh, Primitive, Texture, WebIO} from '@gltf-transform/core';
|
||||
|
||||
import {EXTManifold, ManifoldPrimitive} from './manifold-gltf';
|
||||
import {Mesh as ManifoldMesh, MeshOptions} from './public/manifold-encapsulated-types';
|
||||
|
||||
export const attributeDefs = {
|
||||
'POSITION': {type: Accessor.Type.VEC3, components: 3},
|
||||
'NORMAL': {type: Accessor.Type.VEC3, components: 3},
|
||||
'TANGENT': {type: Accessor.Type.VEC4, components: 4},
|
||||
'TEXCOORD_0': {type: Accessor.Type.VEC2, components: 2},
|
||||
'TEXCOORD_1': {type: Accessor.Type.VEC2, components: 2},
|
||||
'COLOR_0': {type: Accessor.Type.VEC3, components: 3},
|
||||
'JOINTS_0': {type: Accessor.Type.VEC4, components: 4},
|
||||
'WEIGHTS_0': {type: Accessor.Type.VEC4, components: 4},
|
||||
'SKIP_1': {type: null, components: 1},
|
||||
'SKIP_2': {type: null, components: 2},
|
||||
'SKIP_3': {type: null, components: 3},
|
||||
'SKIP_4': {type: null, components: 4},
|
||||
};
|
||||
|
||||
export type Attribute = keyof(typeof attributeDefs);
|
||||
|
||||
export interface Properties {
|
||||
material: Material;
|
||||
attributes: Attribute[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this first to register the manifold extension so that readMesh and
|
||||
* writeMesh will work.
|
||||
*/
|
||||
export function setupIO(io: WebIO) {
|
||||
return io.registerExtensions([EXTManifold]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an input mesh into Manifold-compatible data structures, whether it
|
||||
* contains the EXT_mesh_manifold extension or not.
|
||||
*
|
||||
* @param mesh The Mesh to read.
|
||||
* @param attributes An array of attributes representing the order of desired
|
||||
* properties returned in the vertProperties array of the output mesh. If
|
||||
* omitted, this will be populated with the union of all attributes defined
|
||||
* in the primitives of the input mesh. If present, the first entry must be
|
||||
* 'POSITION', and any attributes in the primitives that are not included in
|
||||
* this list will be ignored, while those in the list but not defined in a
|
||||
* primitive will be populated with zeros.
|
||||
* @returns The returned mesh is suitable for initializing a Manifold or Mesh of
|
||||
* the Manifold library if desired. See Manifold documentation if you prefer
|
||||
* to use these GL arrays in a different library. The runProperties array
|
||||
* gives the Material and attributes list associated with each triangle run,
|
||||
* which in turn corresponds to a primitive of the input mesh. These
|
||||
* attributes are the intersection of the attributes present on the
|
||||
* primitive and those requested in the attributes input.
|
||||
*/
|
||||
export function readMesh(mesh: Mesh, attributes: Attribute[] = []):
|
||||
{mesh: MeshOptions, runProperties: Properties[]}|null {
|
||||
const primitives = mesh.listPrimitives();
|
||||
if (primitives.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (attributes.length === 0) {
|
||||
const attributeSet = new Set<Attribute>();
|
||||
for (const primitive of primitives) {
|
||||
const semantics = primitive.listSemantics() as Attribute[];
|
||||
for (const semantic of semantics) {
|
||||
attributeSet.add(semantic);
|
||||
}
|
||||
}
|
||||
let semantic: Attribute;
|
||||
for (semantic in attributeDefs) {
|
||||
if (attributeSet.has(semantic)) {
|
||||
attributes.push(semantic);
|
||||
attributeSet.delete(semantic);
|
||||
}
|
||||
}
|
||||
for (const semantic of attributeSet.keys()) {
|
||||
attributes.push(semantic);
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes.length < 1 || attributes[0] !== 'POSITION')
|
||||
throw new Error('First attribute must be "POSITION".');
|
||||
|
||||
let numProp = 0;
|
||||
const attributeOffsets = attributes.map((numProp = 0, def => {
|
||||
const last = numProp;
|
||||
numProp += attributeDefs[def].components;
|
||||
return last;
|
||||
}));
|
||||
|
||||
const manifoldPrimitive =
|
||||
mesh.getExtension('EXT_mesh_manifold') as ManifoldPrimitive;
|
||||
|
||||
let vertPropArray = Array<number>();
|
||||
let triVertArray = Array<number>();
|
||||
const runIndexArray = [0];
|
||||
const mergeFromVertArray = Array<number>();
|
||||
const mergeToVertArray = Array<number>();
|
||||
const runProperties = Array<Properties>();
|
||||
if (manifoldPrimitive != null) {
|
||||
const numVert = primitives[0].getAttribute('POSITION')!.getCount();
|
||||
const foundAttribute = attributes.map((a) => attributeDefs[a].type == null);
|
||||
vertPropArray = new Array<number>(numProp * numVert);
|
||||
|
||||
for (const primitive of primitives) {
|
||||
const indices = primitive.getIndices();
|
||||
if (!indices) {
|
||||
console.log('Skipping non-indexed primitive ', primitive.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
const attributesIn = primitive.listSemantics() as Attribute[];
|
||||
|
||||
attributes.forEach((attributeOut, idx) => {
|
||||
if (foundAttribute[idx]) {
|
||||
return;
|
||||
}
|
||||
for (const attributeIn of attributesIn) {
|
||||
if (attributeIn === attributeOut) {
|
||||
foundAttribute[idx] = true;
|
||||
const accessor = primitive.getAttribute(attributeIn)!;
|
||||
writeProperties(
|
||||
vertPropArray, accessor, numProp, attributeOffsets[idx]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
triVertArray = [...triVertArray, ...indices.getArray()!];
|
||||
runIndexArray.push(triVertArray.length);
|
||||
runProperties.push({
|
||||
material: primitive.getMaterial()!,
|
||||
attributes: attributesIn.filter(b => attributes.some(a => a == b))
|
||||
});
|
||||
}
|
||||
const mergeTriVert = manifoldPrimitive.getMergeIndices()?.getArray() ?? [];
|
||||
const mergeTo = manifoldPrimitive.getMergeValues()?.getArray() ?? [];
|
||||
const vert2merge = new Map<number, number>();
|
||||
for (const [i, idx] of mergeTriVert.entries()) {
|
||||
vert2merge.set(triVertArray[idx], mergeTo[i]);
|
||||
}
|
||||
for (const [from, to] of vert2merge.entries()) {
|
||||
mergeFromVertArray.push(from);
|
||||
mergeToVertArray.push(to);
|
||||
}
|
||||
} else {
|
||||
for (const primitive of primitives) {
|
||||
const indices = primitive.getIndices();
|
||||
if (!indices) {
|
||||
console.log('Skipping non-indexed primitive ', primitive.getName());
|
||||
continue;
|
||||
}
|
||||
const numVert = vertPropArray.length / numProp;
|
||||
vertPropArray =
|
||||
[...vertPropArray, ...readPrimitive(primitive, numProp, attributes)];
|
||||
triVertArray =
|
||||
[...triVertArray, ...indices.getArray()!.map((i) => i + numVert)];
|
||||
runIndexArray.push(triVertArray.length);
|
||||
|
||||
const attributesIn = primitive.listSemantics() as Attribute[];
|
||||
runProperties.push({
|
||||
material: primitive.getMaterial()!,
|
||||
attributes: attributesIn.filter(b => attributes.some(a => a == b))
|
||||
});
|
||||
}
|
||||
}
|
||||
const vertProperties = new Float32Array(vertPropArray);
|
||||
const triVerts = new Uint32Array(triVertArray);
|
||||
const runIndex = new Uint32Array(runIndexArray);
|
||||
const mergeFromVert = new Uint32Array(mergeFromVertArray);
|
||||
const mergeToVert = new Uint32Array(mergeToVertArray);
|
||||
|
||||
const meshOut =
|
||||
{numProp, triVerts, vertProperties, runIndex, mergeFromVert, mergeToVert};
|
||||
|
||||
return {mesh: meshOut, runProperties};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a Manifold Mesh into a glTF Mesh object, using the EXT_mesh_manifold
|
||||
* extension to allow for lossless roundtrip of the manifold mesh through the
|
||||
* glTF file.
|
||||
*
|
||||
* @param doc The glTF Document to which this Mesh will be added.
|
||||
* @param manifoldMesh The Manifold Mesh to convert to glTF.
|
||||
* @param id2properties A map from originalID to Properties that include the
|
||||
* glTF Material and the set of attributes to output. All triangle runs with
|
||||
* the same originalID will be combined into a single output primitive. Any
|
||||
* originalIDs not found in the map will have the glTF default material and
|
||||
* no attributes beyond 'POSITION'. Each attributes array must correspond to
|
||||
* the manifoldMesh vertProperties, thus the first attribute must always be
|
||||
* 'POSITION'. Any properties that should not be output for a given
|
||||
* primitive must use the 'SKIP_*' attributes.
|
||||
* @returns The glTF Mesh to add to the Document.
|
||||
*/
|
||||
export function writeMesh(
|
||||
doc: Document, manifoldMesh: ManifoldMesh,
|
||||
id2properties: Map<number, Properties>): Mesh {
|
||||
if (doc.getRoot().listBuffers().length === 0) {
|
||||
doc.createBuffer();
|
||||
}
|
||||
const buffer = doc.getRoot().listBuffers()[0];
|
||||
const manifoldExtension = doc.createExtension(EXTManifold);
|
||||
|
||||
const mesh = doc.createMesh();
|
||||
const runIndex = Array<number>();
|
||||
const attributeUnion = Array<Attribute>();
|
||||
const primitive2attributes = new Map<Primitive, Attribute[]>();
|
||||
const numRun = manifoldMesh.runIndex.length - 1;
|
||||
let lastID = -1;
|
||||
for (let run = 0; run < numRun; ++run) {
|
||||
const id = manifoldMesh.runOriginalID[run];
|
||||
if (id == lastID) {
|
||||
continue;
|
||||
}
|
||||
lastID = id;
|
||||
runIndex.push(manifoldMesh.runIndex[run]);
|
||||
|
||||
const indices = doc.createAccessor('primitive indices of ID ' + id)
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(1));
|
||||
const primitive = doc.createPrimitive().setIndices(indices);
|
||||
|
||||
const properties = id2properties.get(id);
|
||||
if (properties) {
|
||||
const {material, attributes} = properties;
|
||||
if (attributes.length < 1 || attributes[0] !== 'POSITION')
|
||||
throw new Error('First attribute must be "POSITION".');
|
||||
|
||||
primitive.setMaterial(material);
|
||||
primitive2attributes.set(primitive, attributes);
|
||||
|
||||
properties.attributes.forEach((attribute, i) => {
|
||||
if (i >= attributeUnion.length) {
|
||||
attributeUnion.push(attribute);
|
||||
} else {
|
||||
const size = attributeDefs[attribute].components;
|
||||
const unionSize = attributeDefs[attributeUnion[i]].components;
|
||||
if (size != unionSize) {
|
||||
throw new Error(
|
||||
'Attribute sizes do not correspond: ' + attribute + ' and ' +
|
||||
attributeUnion[i]);
|
||||
}
|
||||
if (attributeDefs[attributeUnion[i]].type == null) {
|
||||
attributeUnion[i] = attribute;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
primitive2attributes.set(primitive, ['POSITION']);
|
||||
}
|
||||
|
||||
mesh.addPrimitive(primitive);
|
||||
}
|
||||
runIndex.push(manifoldMesh.runIndex[numRun]);
|
||||
|
||||
const numVert = manifoldMesh.numVert;
|
||||
const numProp = manifoldMesh.numProp;
|
||||
let offset = 0;
|
||||
attributeUnion.forEach((attribute, aIdx) => {
|
||||
const def = attributeDefs[attribute];
|
||||
if (def == null)
|
||||
throw new Error(attribute + ' is not a recognized attribute.');
|
||||
|
||||
if (def.type == null) {
|
||||
++offset;
|
||||
return;
|
||||
}
|
||||
|
||||
const n = def.components;
|
||||
if (offset + n > numProp) throw new Error('Too many attribute channels.');
|
||||
|
||||
const array = new Float32Array(n * numVert);
|
||||
for (let v = 0; v < numVert; ++v) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
let x = manifoldMesh.vertProperties[numProp * v + offset + i];
|
||||
if (attribute == 'COLOR_0') {
|
||||
x = Math.max(0, Math.min(1, x));
|
||||
}
|
||||
array[n * v + i] = x;
|
||||
}
|
||||
}
|
||||
|
||||
const accessor = doc.createAccessor(attribute)
|
||||
.setBuffer(buffer)
|
||||
.setType(def.type)
|
||||
.setArray(array);
|
||||
|
||||
for (const primitive of mesh.listPrimitives()) {
|
||||
const attributes = primitive2attributes.get(primitive)!;
|
||||
if (attributes.length > aIdx &&
|
||||
attributeDefs[attributes[aIdx]].type != null) {
|
||||
primitive.setAttribute(attribute, accessor);
|
||||
}
|
||||
}
|
||||
offset += n;
|
||||
});
|
||||
|
||||
const manifoldPrimitive = manifoldExtension.createManifoldPrimitive();
|
||||
mesh.setExtension('EXT_mesh_manifold', manifoldPrimitive);
|
||||
|
||||
const indices = doc.createAccessor('manifold indices')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(manifoldMesh.triVerts);
|
||||
manifoldPrimitive.setIndices(indices);
|
||||
manifoldPrimitive.setRunIndex(runIndex);
|
||||
|
||||
const vert2merge = [...Array(manifoldMesh.numVert).keys()];
|
||||
const ind = Array<number>();
|
||||
const val = Array<number>();
|
||||
if (manifoldMesh.mergeFromVert && manifoldMesh.mergeToVert) {
|
||||
for (const [i, from] of manifoldMesh.mergeFromVert.entries()) {
|
||||
vert2merge[from] = manifoldMesh.mergeToVert[i];
|
||||
}
|
||||
|
||||
for (const [i, vert] of manifoldMesh.triVerts.entries()) {
|
||||
const newVert = vert2merge[vert];
|
||||
if (vert !== newVert) {
|
||||
ind.push(i);
|
||||
val.push(newVert);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ind.length > 0) {
|
||||
const indicesAccessor = doc.createAccessor('merge from')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(ind));
|
||||
const valuesAccessor = doc.createAccessor('merge to')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(val));
|
||||
manifoldPrimitive.setMerge(indicesAccessor, valuesAccessor);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to dispose of a Mesh, useful when replacing an existing Mesh
|
||||
* with one from writeMesh.
|
||||
*/
|
||||
export function disposeMesh(mesh: Mesh) {
|
||||
if (!mesh) return;
|
||||
const primitives = mesh.listPrimitives();
|
||||
for (const primitive of primitives) {
|
||||
primitive.getIndices()?.dispose();
|
||||
for (const accessor of primitive.listAttributes()) {
|
||||
accessor.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
const manifoldPrimitive =
|
||||
mesh.getExtension('EXT_mesh_manifold') as ManifoldPrimitive;
|
||||
if (manifoldPrimitive) {
|
||||
manifoldPrimitive.getIndices()?.dispose();
|
||||
manifoldPrimitive.getMergeIndices()?.dispose();
|
||||
manifoldPrimitive.getMergeValues()?.dispose();
|
||||
}
|
||||
|
||||
mesh.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to download an image and apply it to the given texture.
|
||||
*
|
||||
* @param texture The texture to update
|
||||
* @param uri The location of the image to download
|
||||
*/
|
||||
export async function loadTexture(texture: Texture, uri: string) {
|
||||
const response = await fetch(uri);
|
||||
const blob = await response.blob();
|
||||
texture.setMimeType(blob.type);
|
||||
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
|
||||
}
|
||||
|
||||
function writeProperties(
|
||||
vertProperties: number[], accessor: Accessor, numProp: number,
|
||||
offset: number) {
|
||||
const array = accessor.getArray()!;
|
||||
const size = accessor.getElementSize();
|
||||
const numVert = accessor.getCount();
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
for (let j = 0; j < size; ++j) {
|
||||
vertProperties[numProp * i + offset + j] = array[i * size + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readPrimitive(
|
||||
primitive: Primitive, numProp: number, attributes: Attribute[]) {
|
||||
const vertProperties: number[] = [];
|
||||
let offset = 0;
|
||||
for (const attribute of attributes) {
|
||||
const size = attributeDefs[attribute].components;
|
||||
if (attributeDefs[attribute].type == null) {
|
||||
offset += size;
|
||||
continue;
|
||||
}
|
||||
const accessor = primitive.getAttribute(attribute);
|
||||
if (accessor) {
|
||||
writeProperties(vertProperties, accessor, numProp, offset);
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
return vertProperties;
|
||||
}
|
||||
@@ -39,8 +39,15 @@
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container header" style="background-color: #A288B3;">
|
||||
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD </p>
|
||||
<div class="margin" id="file-margin" >
|
||||
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD
|
||||
<span class="top-edit blue">
|
||||
<button type="button" id="undo" class="top-icon undo squish"></button>
|
||||
<button type="button" id="redo" class="top-icon redo squish"></button>
|
||||
<button type="button" id="format" class="top-icon star squish"></button>
|
||||
<button type="button" id="share" class="top-icon share"></button>
|
||||
</span>
|
||||
</p>
|
||||
<div class="margin" id="file-margin">
|
||||
<button type="button" id="file" class="blue item"><b class="uparrow"></b> <span
|
||||
id="current">Intro</span></button>
|
||||
<div class="dropdown" id="fileDropdown">
|
||||
@@ -66,10 +73,14 @@
|
||||
<div id="editor" class="col"></div>
|
||||
<div class="col container" style="flex-direction: column;">
|
||||
<model-viewer camera-controls min-camera-orbit="auto 0deg auto" max-camera-orbit="auto 180deg auto"
|
||||
shadow-intensity="1" interaction-prompt="none" alt="Editor 3D output">
|
||||
shadow-intensity="1" tone-mapping="neutral" interaction-prompt="none" alt="Editor 3D output">
|
||||
<div class="center" slot="poster">
|
||||
<p id="poster" style="text-align: center; line-height: 36px;"> Loading... </p>
|
||||
</div>
|
||||
<div id="animation">
|
||||
<button type="button" id="play" class="icon pause"></button>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slider" id="scrubber">
|
||||
</div>
|
||||
</model-viewer>
|
||||
<div id="console"></div>
|
||||
</div>
|
||||
@@ -84,6 +95,6 @@
|
||||
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<script type="module" src="editor.js"></script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.1.1/model-viewer.min.js"></script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -46,7 +46,7 @@
|
||||
<body>
|
||||
<p>Load a glTF/GLB model and our <a href="https://github.com/elalish/manifold">Manifold</a> library will attempt to
|
||||
merge it into a set of manifold objects. If the model is water-tight, you can download the new GLB which will have
|
||||
our <a href="https://github.com/KhronosGroup/glTF/pull/2286">EXT_manifold</a> extension, thus preserving the
|
||||
our <a href="https://github.com/KhronosGroup/glTF/pull/2286">EXT_mesh_manifold</a> extension, thus preserving the
|
||||
manifold data without losing any mesh properties.</p>
|
||||
<p> If the View Manifold GLB checkbox is enabled, then some meshes in the model have open edges and don't represent a
|
||||
solid object. Check this box to see only the manifold parts, which will also enable them to be downloaded. If the
|
||||
@@ -55,135 +55,11 @@
|
||||
<button id="download" disabled>Download manifold GLB</button>
|
||||
<input type="checkbox" id="viewFinal" disabled>
|
||||
<label for="viewFinal">View Manifold GLB</label>
|
||||
<model-viewer camera-controls shadow-intensity="1" alt="Loaded GLB model">
|
||||
<model-viewer camera-controls shadow-intensity="1" tone-mapping="neutral" alt="Loaded GLB model">
|
||||
<span id="poster" slot="poster">Drop a GLB here</span>
|
||||
</model-viewer>
|
||||
</body>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
<script type="module">
|
||||
import { WebIO } from '@gltf-transform/core';
|
||||
import { prune } from '@gltf-transform/functions';
|
||||
import { KHRONOS_EXTENSIONS } from '@gltf-transform/extensions';
|
||||
import { SimpleDropzone } from 'simple-dropzone';
|
||||
import { readMesh, writeMesh, setupIO } from './gltf-io.js';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
io.registerExtensions(KHRONOS_EXTENSIONS);
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
const inputEl = document.querySelector('#input');
|
||||
const downloadButton = document.querySelector('#download');
|
||||
const checkbox = document.querySelector('#viewFinal');
|
||||
const dropCtrl = new SimpleDropzone(mv, inputEl);
|
||||
|
||||
let inputGLBurl = null;
|
||||
let outputGLBurl = null;
|
||||
let allManifold = true;
|
||||
let anyManifold = false;
|
||||
|
||||
dropCtrl.on('drop', async ({ files }) => {
|
||||
for (const [path, file] of files) {
|
||||
const filename = file.name.toLowerCase();
|
||||
if (filename.match(/\.(gltf|glb)$/)) {
|
||||
URL.revokeObjectURL(inputGLBurl);
|
||||
inputGLBurl = URL.createObjectURL(file);
|
||||
await writeGLB(await readGLB(inputGLBurl));
|
||||
updateUI();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function updateUI() {
|
||||
if (allManifold) {
|
||||
checkbox.checked = true;
|
||||
checkbox.disabled = true;
|
||||
} else if (anyManifold) {
|
||||
checkbox.checked = false;
|
||||
checkbox.disabled = false;
|
||||
} else {
|
||||
checkbox.checked = false;
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
onClick();
|
||||
}
|
||||
|
||||
checkbox.onclick = onClick;
|
||||
|
||||
function onClick() {
|
||||
mv.src = checkbox.checked ? outputGLBurl : inputGLBurl;
|
||||
downloadButton.disabled = !checkbox.checked;
|
||||
};
|
||||
|
||||
downloadButton.onclick = function () {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = outputGLBurl;
|
||||
link.click();
|
||||
};
|
||||
|
||||
async function writeGLB(doc) {
|
||||
URL.revokeObjectURL(outputGLBurl);
|
||||
if (!anyManifold) {
|
||||
return;
|
||||
}
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], { type: 'application/octet-stream' });
|
||||
outputGLBurl = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
async function readGLB(url) {
|
||||
allManifold = false;
|
||||
anyManifold = false;
|
||||
updateUI();
|
||||
allManifold = true;
|
||||
const docIn = await io.read(url);
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
for (const node of nodes) {
|
||||
const attributes = [];
|
||||
const mesh = node.getMesh();
|
||||
if (!mesh) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const materials = [];
|
||||
const tmpMesh = readMesh(mesh, attributes, materials);
|
||||
tmpMesh.runOriginalID = [];
|
||||
const firstID = Manifold.reserveIDs(materials.length);
|
||||
for (let i = 0; i < materials.length; ++i) {
|
||||
tmpMesh.runOriginalID.push(firstID + i);
|
||||
}
|
||||
const manifoldMesh = new Mesh(tmpMesh);
|
||||
mesh.dispose();
|
||||
|
||||
const matAttributes = [];
|
||||
materials.forEach(() => matAttributes.push(attributes));
|
||||
|
||||
manifoldMesh.merge();
|
||||
|
||||
try {
|
||||
// Test manifoldness - will throw if not.
|
||||
const manifold = Manifold(manifoldMesh);
|
||||
node.setMesh(writeMesh(docIn, manifold.getMesh(), matAttributes, materials));
|
||||
manifold.delete();
|
||||
anyManifold = true;
|
||||
} catch (e) {
|
||||
console.log(mesh.getName(), e);
|
||||
allManifold = false;
|
||||
}
|
||||
}
|
||||
|
||||
await docIn.transform(prune());
|
||||
|
||||
return docIn;
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
||||
<script type="module" src="make-manifold.ts"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Document, WebIO} from '@gltf-transform/core';
|
||||
import {KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
|
||||
import {prune} from '@gltf-transform/functions';
|
||||
import {SimpleDropzone} from 'simple-dropzone';
|
||||
|
||||
import Module from './built/manifold.js';
|
||||
import {disposeMesh, Properties, readMesh, setupIO, writeMesh} from './gltf-io';
|
||||
|
||||
// Set up gltf-transform
|
||||
const io = setupIO(new WebIO());
|
||||
io.registerExtensions(KHRONOS_EXTENSIONS);
|
||||
|
||||
// Set up Manifold WASM library
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
const {Manifold, Mesh} = wasm;
|
||||
|
||||
// UX elements
|
||||
const mv = document.querySelector('model-viewer');
|
||||
const inputEl = document.querySelector('#input') as HTMLInputElement;
|
||||
const downloadButton = document.querySelector('#download') as HTMLButtonElement;
|
||||
const checkbox = document.querySelector('#viewFinal') as HTMLInputElement;
|
||||
const dropCtrl = new SimpleDropzone(mv, inputEl) as any;
|
||||
|
||||
// glTF objects in memory
|
||||
let inputGLBurl = '';
|
||||
let outputGLBurl = '';
|
||||
// Status of manifoldness of all meshes
|
||||
let allManifold = true;
|
||||
let anyManifold = false;
|
||||
|
||||
// The processing is run when a glTF is drag-and-dropped onto this element.
|
||||
dropCtrl.on('drop', async ({files}: {files: Map<string, File>}) => {
|
||||
for (const [_path, file] of files) {
|
||||
const filename = file.name.toLowerCase();
|
||||
if (filename.match(/\.(gltf|glb)$/)) {
|
||||
URL.revokeObjectURL(inputGLBurl);
|
||||
inputGLBurl = URL.createObjectURL(file);
|
||||
await writeGLB(await readGLB(inputGLBurl));
|
||||
updateUI();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// UI functions
|
||||
|
||||
function updateUI() {
|
||||
if (allManifold) {
|
||||
checkbox.checked = true;
|
||||
checkbox.disabled = true;
|
||||
} else if (anyManifold) {
|
||||
checkbox.checked = false;
|
||||
checkbox.disabled = false;
|
||||
} else {
|
||||
checkbox.checked = false;
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
onClick();
|
||||
}
|
||||
|
||||
checkbox.onclick = onClick;
|
||||
|
||||
function onClick() {
|
||||
(mv as any).src = checkbox.checked ? outputGLBurl : inputGLBurl;
|
||||
downloadButton.disabled = !checkbox.checked;
|
||||
};
|
||||
|
||||
downloadButton.onclick = () => {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = outputGLBurl;
|
||||
link.click();
|
||||
};
|
||||
|
||||
// Write output glTF using gltf-transform, which contains only the meshes that
|
||||
// are manifold, and using the EXT_mesh_manifold extension.
|
||||
async function writeGLB(doc: Document): Promise<void> {
|
||||
URL.revokeObjectURL(outputGLBurl);
|
||||
if (!anyManifold) {
|
||||
return;
|
||||
}
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
outputGLBurl = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
// Read the glTF ObjectURL and return a gltf-transform document with all the
|
||||
// non-manifold meshes stripped out.
|
||||
async function readGLB(url: string): Promise<Document> {
|
||||
allManifold = false;
|
||||
anyManifold = false;
|
||||
updateUI();
|
||||
allManifold = true;
|
||||
const docIn = await io.read(url);
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
for (const node of nodes) {
|
||||
const mesh = node.getMesh();
|
||||
if (!mesh) continue;
|
||||
|
||||
const tmp = readMesh(mesh);
|
||||
if (!tmp) continue;
|
||||
|
||||
const id2properties = new Map<number, Properties>();
|
||||
const numID = tmp.runProperties.length;
|
||||
const firstID = Manifold.reserveIDs(numID);
|
||||
tmp.mesh.runOriginalID = new Uint32Array(numID);
|
||||
for (let i = 0; i < numID; ++i) {
|
||||
tmp.mesh.runOriginalID[i] = firstID + i;
|
||||
id2properties.set(firstID + i, tmp.runProperties[i]);
|
||||
}
|
||||
const manifoldMesh = new Mesh(tmp.mesh);
|
||||
disposeMesh(mesh);
|
||||
// Make the mesh manifold if it's close.
|
||||
manifoldMesh.merge();
|
||||
|
||||
try {
|
||||
// Test manifoldness - will throw if not.
|
||||
const manifold = new Manifold(manifoldMesh);
|
||||
// Replace the mesh with a manifold version
|
||||
node.setMesh(writeMesh(docIn, manifold.getMesh(), id2properties));
|
||||
manifold.delete();
|
||||
anyManifold = true;
|
||||
} catch (e) {
|
||||
console.log(mesh.getName(), e);
|
||||
allManifold = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prune the leftovers after non-manifold mesh removal.
|
||||
await docIn.transform(prune());
|
||||
|
||||
return docIn;
|
||||
}
|
||||
@@ -12,15 +12,32 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Extension, ExtensionProperty, PropertyType} from '@gltf-transform/core';
|
||||
import {Accessor, Extension, ExtensionProperty, GLTF, IProperty, PropertyType, ReaderContext, WriterContext} from '@gltf-transform/core';
|
||||
|
||||
const NAME = 'EXT_manifold';
|
||||
const NAME = 'EXT_mesh_manifold';
|
||||
const MERGE = 'MERGE';
|
||||
|
||||
export class ManifoldPrimitive extends ExtensionProperty {
|
||||
interface IManifoldPrimitive extends IProperty {
|
||||
mergeIndices: Accessor;
|
||||
mergeValues: Accessor;
|
||||
indices: Accessor;
|
||||
runIndex: number[]|Uint32Array;
|
||||
}
|
||||
|
||||
interface ManifoldDef {
|
||||
manifoldPrimitive: GLTF.IMeshPrimitive;
|
||||
mergeIndices: number;
|
||||
mergeValues: number;
|
||||
}
|
||||
|
||||
export class ManifoldPrimitive extends ExtensionProperty<IManifoldPrimitive> {
|
||||
static EXTENSION_NAME = NAME;
|
||||
public declare extensionName: typeof NAME;
|
||||
public declare propertyType: 'ManifoldPrimitive';
|
||||
public declare parentTypes: [PropertyType.MESH];
|
||||
|
||||
init() {
|
||||
this.EXTENSION_NAME = NAME;
|
||||
ManifoldPrimitive.EXTENSION_NAME = NAME;
|
||||
this.propertyType = 'ManifoldPrimitive';
|
||||
this.parentTypes = [PropertyType.MESH];
|
||||
}
|
||||
@@ -39,7 +56,7 @@ export class ManifoldPrimitive extends ExtensionProperty {
|
||||
return this.getRef('mergeValues');
|
||||
}
|
||||
|
||||
setMerge(indicesAccessor, valuesAccessor) {
|
||||
setMerge(indicesAccessor: Accessor, valuesAccessor: Accessor) {
|
||||
if (indicesAccessor.getCount() !== valuesAccessor.getCount())
|
||||
throw new Error('merge vectors must be the same length.');
|
||||
this.setRef('mergeIndices', indicesAccessor);
|
||||
@@ -50,28 +67,29 @@ export class ManifoldPrimitive extends ExtensionProperty {
|
||||
return this.get('runIndex');
|
||||
}
|
||||
|
||||
setRunIndex(runIndex) {
|
||||
setRunIndex(runIndex: number[]|Uint32Array) {
|
||||
return this.set('runIndex', runIndex);
|
||||
}
|
||||
|
||||
setIndices(indices) {
|
||||
setIndices(indices: Accessor) {
|
||||
return this.setRef('indices', indices);
|
||||
}
|
||||
|
||||
getIndices() {
|
||||
return this.getRef('indices');
|
||||
return this.getRef('indices')!;
|
||||
}
|
||||
}
|
||||
|
||||
export class EXTManifold extends Extension {
|
||||
extensionName = NAME;
|
||||
prewriteTypes = [PropertyType.ACCESSOR];
|
||||
static EXTENSION_NAME = NAME;
|
||||
|
||||
createManifoldPrimitive() {
|
||||
return new ManifoldPrimitive(this.document.getGraph());
|
||||
}
|
||||
|
||||
read(context) {
|
||||
read(context: ReaderContext) {
|
||||
const {json} = context.jsonDoc;
|
||||
const meshDefs = json.meshes || [];
|
||||
|
||||
@@ -82,20 +100,27 @@ export class EXTManifold extends Extension {
|
||||
const manifoldPrimitive = this.createManifoldPrimitive();
|
||||
mesh.setExtension(NAME, manifoldPrimitive);
|
||||
|
||||
const manifoldDef = meshDef.extensions[NAME];
|
||||
const manifoldDef = meshDef.extensions[NAME] as ManifoldDef;
|
||||
|
||||
if (manifoldDef.manifoldPrimitive) {
|
||||
let count = 0;
|
||||
const runIndex = [];
|
||||
const runIndex = Array<number>();
|
||||
runIndex.push(count);
|
||||
for (const primitive of mesh.listPrimitives()) {
|
||||
count += primitive.getIndices().getCount();
|
||||
const indices = primitive.getIndices();
|
||||
if (!indices) {
|
||||
console.log('Skipping non-indexed primitive ', primitive.getName());
|
||||
continue;
|
||||
}
|
||||
count += indices.getCount();
|
||||
runIndex.push(count);
|
||||
}
|
||||
manifoldPrimitive.setRunIndex(runIndex);
|
||||
manifoldPrimitive.setIndices(
|
||||
context.accessors[manifoldDef.manifoldPrimitive.indices!]);
|
||||
}
|
||||
|
||||
if (manifoldDef.mergeIndices && manifoldDef.mergeValues) {
|
||||
if (manifoldDef.mergeIndices != null && manifoldDef.mergeValues != null) {
|
||||
manifoldPrimitive.setMerge(
|
||||
context.accessors[manifoldDef.mergeIndices],
|
||||
context.accessors[manifoldDef.mergeValues]);
|
||||
@@ -105,15 +130,34 @@ export class EXTManifold extends Extension {
|
||||
return this;
|
||||
}
|
||||
|
||||
write(context) {
|
||||
prewrite(context: WriterContext) {
|
||||
this.document.getRoot().listMeshes().forEach((mesh) => {
|
||||
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
|
||||
if (!manifoldPrimitive) return;
|
||||
|
||||
const indices = manifoldPrimitive.getIndices();
|
||||
context.addAccessorToUsageGroup(
|
||||
indices, WriterContext.BufferViewUsage.ELEMENT_ARRAY_BUFFER);
|
||||
|
||||
const mergeFrom = manifoldPrimitive.getMergeIndices();
|
||||
const mergeTo = manifoldPrimitive.getMergeValues();
|
||||
if (!mergeFrom || !mergeTo) return;
|
||||
|
||||
context.addAccessorToUsageGroup(mergeFrom, MERGE);
|
||||
context.addAccessorToUsageGroup(mergeTo, MERGE);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
write(context: WriterContext) {
|
||||
const {json} = context.jsonDoc;
|
||||
|
||||
this.document.getRoot().listMeshes().forEach((mesh) => {
|
||||
const manifoldPrimitive = mesh.getExtension(NAME);
|
||||
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
|
||||
if (!manifoldPrimitive) return;
|
||||
|
||||
const meshIndex = context.meshIndexMap.get(mesh);
|
||||
const meshDef = json.meshes[meshIndex];
|
||||
const meshIndex = context.meshIndexMap.get(mesh)!;
|
||||
const meshDef = json.meshes![meshIndex];
|
||||
|
||||
const runIndex = manifoldPrimitive.getRunIndex();
|
||||
const numPrimitive = runIndex.length - 1;
|
||||
@@ -124,20 +168,20 @@ export class EXTManifold extends Extension {
|
||||
}
|
||||
|
||||
const mergeIndicesIndex =
|
||||
context.accessorIndexMap.get(manifoldPrimitive.getMergeIndices());
|
||||
context.accessorIndexMap.get(manifoldPrimitive.getMergeIndices()!)!;
|
||||
const mergeValuesIndex =
|
||||
context.accessorIndexMap.get(manifoldPrimitive.getMergeValues());
|
||||
const mergeIndices = json.accessors[mergeIndicesIndex];
|
||||
const mergeValues = json.accessors[mergeValuesIndex];
|
||||
context.accessorIndexMap.get(manifoldPrimitive.getMergeValues()!)!;
|
||||
const mergeIndices = json.accessors![mergeIndicesIndex];
|
||||
const mergeValues = json.accessors![mergeValuesIndex];
|
||||
|
||||
const existingPrimitive = meshDef.primitives[0];
|
||||
const primitive = {
|
||||
indices: context.accessorIndexMap.get(manifoldPrimitive.getIndices()),
|
||||
indices: context.accessorIndexMap.get(manifoldPrimitive.getIndices())!,
|
||||
mode: existingPrimitive.mode,
|
||||
attributes: {'POSITION': existingPrimitive.attributes['POSITION']}
|
||||
};
|
||||
|
||||
const indices = json.accessors[primitive.indices];
|
||||
const indices = json.accessors![primitive.indices];
|
||||
if (!indices) {
|
||||
return;
|
||||
}
|
||||
@@ -146,21 +190,21 @@ export class EXTManifold extends Extension {
|
||||
indices.sparse = {
|
||||
count: mergeIndices.count,
|
||||
indices: {
|
||||
bufferView: mergeIndices.bufferView,
|
||||
bufferView: mergeIndices.bufferView!,
|
||||
byteOffset: mergeIndices.byteOffset,
|
||||
componentType: mergeIndices.componentType
|
||||
},
|
||||
values: {
|
||||
bufferView: mergeValues.bufferView,
|
||||
bufferView: mergeValues.bufferView!,
|
||||
byteOffset: mergeValues.byteOffset,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (let i = 0; i < numPrimitive; ++i) {
|
||||
const accessor = json.accessors[meshDef.primitives[i].indices];
|
||||
const accessor = json.accessors![meshDef.primitives[i].indices!];
|
||||
accessor.bufferView = indices.bufferView;
|
||||
accessor.byteOffset = indices.byteOffset + 4 * runIndex[i];
|
||||
accessor.byteOffset = indices.byteOffset! + 4 * runIndex[i];
|
||||
accessor.count = runIndex[i + 1] - runIndex[i];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Document, WebIO} from '@gltf-transform/core';
|
||||
import {clearNodeTransform, flatten, prune} from '@gltf-transform/functions';
|
||||
|
||||
import Module, {Manifold, Mesh} from './built/manifold';
|
||||
import {disposeMesh, Properties, readMesh, setupIO, writeMesh} from './gltf-io';
|
||||
|
||||
// Set up gltf-transform
|
||||
const io = setupIO(new WebIO());
|
||||
const doc = new Document();
|
||||
|
||||
|
||||
// Set up Manifold WASM library
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
const {Manifold, Mesh} = wasm;
|
||||
|
||||
// Map of OriginalID to glTF material and attributes
|
||||
const id2properties = new Map<number, Properties>();
|
||||
|
||||
// Wrapper for gltf-io readMesh() that processes a whole glTF and stores the
|
||||
// properties in the above map. This function is simplified and intended only
|
||||
// for reading single-object glTFs, as it simply unions any extra meshes
|
||||
// together rather than returning a scene hierarchy.
|
||||
async function readGLB(url: string) {
|
||||
const manifolds = Array<Manifold>();
|
||||
const docIn = await io.read(url);
|
||||
await docIn.transform(flatten());
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
const ids = Array<number>();
|
||||
for (const node of nodes) {
|
||||
clearNodeTransform(node);
|
||||
const gltfMesh = node.getMesh();
|
||||
if (gltfMesh == null) continue;
|
||||
const tmp = readMesh(gltfMesh);
|
||||
if (tmp == null) continue;
|
||||
|
||||
const numID = tmp.runProperties.length;
|
||||
const firstID = Manifold.reserveIDs(numID);
|
||||
tmp.mesh.runOriginalID = new Uint32Array(numID);
|
||||
for (let i = 0; i < numID; ++i) {
|
||||
tmp.mesh.runOriginalID[i] = firstID + i;
|
||||
ids.push(firstID + i);
|
||||
id2properties.set(firstID + i, tmp.runProperties[i]);
|
||||
}
|
||||
manifolds.push(new Manifold(new Mesh(tmp.mesh)));
|
||||
}
|
||||
// pull in materials, TODO: replace with transfer() when available
|
||||
const startIdx = doc.getRoot().listMaterials().length;
|
||||
doc.merge(docIn);
|
||||
doc.getRoot().listScenes().forEach((s) => s.dispose());
|
||||
doc.getRoot().listBuffers().forEach((s) => s.dispose());
|
||||
doc.getRoot().listAccessors().forEach((s) => s.dispose());
|
||||
for (const [i, id] of ids.entries()) {
|
||||
const material = doc.getRoot().listMaterials()[startIdx + i];
|
||||
id2properties.get(id)!.material = material;
|
||||
}
|
||||
|
||||
return Manifold.union(manifolds);
|
||||
}
|
||||
|
||||
// Read static input glTFs
|
||||
const space = await readGLB('/models/space.glb');
|
||||
const moon = await readGLB('/models/moon.glb');
|
||||
|
||||
const node = doc.createNode();
|
||||
doc.createScene().addChild(node);
|
||||
|
||||
// Set up UI for operations
|
||||
type BooleanOp = 'union'|'difference'|'intersection';
|
||||
|
||||
function csg(operation: BooleanOp) {
|
||||
push2MV(Manifold[operation](space, moon));
|
||||
}
|
||||
|
||||
csg('difference');
|
||||
const selectElement = document.querySelector('select')!;
|
||||
selectElement.onchange = function() {
|
||||
csg(selectElement.value as BooleanOp);
|
||||
};
|
||||
|
||||
// The resulting glTF
|
||||
let objectURL = '';
|
||||
|
||||
// Set up download UI
|
||||
const downloadButton = document.querySelector('#download') as HTMLButtonElement;
|
||||
downloadButton.onclick = function() {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = objectURL;
|
||||
link.click();
|
||||
};
|
||||
|
||||
// <model-viewer> element for rendering resulting glTF.
|
||||
const mv = document.querySelector('model-viewer');
|
||||
|
||||
// Use gltf-io and gltf-transform to convert the resulting Manifold to a glTF
|
||||
// and display it with <model-viewer>.
|
||||
async function push2MV(manifold: Manifold) {
|
||||
disposeMesh(node.getMesh()!);
|
||||
const mesh = writeMesh(doc, manifold.getMesh(), id2properties);
|
||||
node.setMesh(mesh);
|
||||
await doc.transform(prune());
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
(mv as any).src = objectURL;
|
||||
}
|
||||
@@ -35,117 +35,24 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>This example demonstrates feeding Manifold's output into <a
|
||||
href="https://modelviewer.dev/"><model-viewer></a> via <a
|
||||
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a>. It also shows how to pass mesh
|
||||
properties like UV coordinates through Manifold and how to re-associate textures after operations. A more basic
|
||||
three.js example is <a href="three.html">here</a>. </p>
|
||||
<p>This example demonstrates reading and writing glTF models using <a
|
||||
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a> and using this to feed Manifold's
|
||||
output into <a href="https://modelviewer.dev/"><model-viewer></a>. It also shows how to pass mesh properties
|
||||
like UV coordinates through Manifold and how to re-associate textures and materials after operations, made simple
|
||||
using our `gltf-io.ts` library. The resulting glTF includes the EXT_mesh_manifold extension, using our
|
||||
`manifold-gltf.ts` extension to gltf-transform, which ensures lossless data transfer.</p>
|
||||
<p>Please open dev tools to inspect our source code - everything is source mapped back to our TS files and commented
|
||||
thoroughly. A more basic three.js example is <a href="three.html">here</a>. </p>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
<option value="intersection">Intersection</option>
|
||||
<option value="union">Union</option>
|
||||
</select>
|
||||
<button id="download">Download manifold GLB</button>
|
||||
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
|
||||
<model-viewer camera-controls shadow-intensity="1" tone-mapping="neutral"
|
||||
alt="Output of mesh Boolean operation"></model-viewer>
|
||||
</body>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
<script type="module">
|
||||
import { Document, WebIO } from '@gltf-transform/core';
|
||||
import { clearNodeTransform, flatten, prune } from '@gltf-transform/functions';
|
||||
import { loadTexture, readMesh, writeMesh, disposeMesh, setupIO } from './gltf-io.js';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
const doc = new Document();
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
const attributes = ['POSITION', 'TEXCOORD_0'];
|
||||
const materialMap = new Map();
|
||||
|
||||
async function readGLB(url, attributes, materialMap) {
|
||||
const manifolds = [];
|
||||
const docIn = await io.read(url);
|
||||
await docIn.transform(flatten());
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
const ids = [];
|
||||
for (const node of nodes) {
|
||||
clearNodeTransform(node);
|
||||
const materials = [];
|
||||
const manifoldMesh = readMesh(node.getMesh(), attributes, materials);
|
||||
manifoldMesh.runOriginalID = [];
|
||||
const firstID = Manifold.reserveIDs(materials.length);
|
||||
for (let i = 0; i < materials.length; ++i) {
|
||||
manifoldMesh.runOriginalID.push(firstID + i);
|
||||
ids.push(firstID + i);
|
||||
}
|
||||
manifolds.push(Manifold(new Mesh(manifoldMesh)));
|
||||
}
|
||||
// pull in materials, TODO: replace with transfer() when available
|
||||
const startIdx = doc.getRoot().listMaterials().length;
|
||||
doc.merge(docIn);
|
||||
doc.getRoot().listScenes().forEach((s) => s.dispose());
|
||||
doc.getRoot().listBuffers().forEach((s) => s.dispose());
|
||||
for (const [i, id] of ids.entries()) {
|
||||
const material = doc.getRoot().listMaterials()[startIdx + i];
|
||||
materialMap.set(id, material);
|
||||
}
|
||||
|
||||
return Manifold.union(manifolds);
|
||||
}
|
||||
|
||||
const space = await readGLB('/models/space.glb', attributes, materialMap);
|
||||
const moon = await readGLB('/models/moon.glb', attributes, materialMap);
|
||||
|
||||
const node = doc.createNode();
|
||||
doc.createScene().addChild(node);
|
||||
|
||||
function csg(operation) {
|
||||
push2MV(Manifold[operation](space, moon));
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
|
||||
const downloadButton = document.querySelector('#download');
|
||||
downloadButton.onclick = function () {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = objectURL;
|
||||
link.click();
|
||||
};
|
||||
|
||||
async function push2MV(manifold) {
|
||||
// From Z-up to Y-up (glTF)
|
||||
const manifoldMesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
disposeMesh(node.getMesh());
|
||||
const materials = [];
|
||||
const matAttributes = [];
|
||||
for (const id of manifoldMesh.runOriginalID) {
|
||||
materials.push(materialMap.get(id));
|
||||
matAttributes.push(attributes);
|
||||
}
|
||||
const mesh = writeMesh(doc, manifoldMesh, matAttributes, materials);
|
||||
node.setMesh(mesh);
|
||||
await doc.transform(prune());
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], { type: 'application/octet-stream' });
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
||||
<script type="module" src="model-viewer-script.ts"></script>
|
||||
|
||||
</html>
|
||||
@@ -8,25 +8,26 @@
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest",
|
||||
"test": "vitest run",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:run": "vitest run"
|
||||
"test:dev": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^3.2.1",
|
||||
"@gltf-transform/extensions": "^3.2.1",
|
||||
"@gltf-transform/functions": "^3.2.1",
|
||||
"@jscadui/3mf-export": "^0.4.2",
|
||||
"@gltf-transform/core": "^3.8.0",
|
||||
"@gltf-transform/extensions": "^3.8.0",
|
||||
"@gltf-transform/functions": "^3.8.0",
|
||||
"@jscadui/3mf-export": "^0.5.0",
|
||||
"@types/three": "^0.164.0",
|
||||
"fflate": "^0.8.0",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"simple-dropzone": "0.8.3",
|
||||
"three": "0.151.2"
|
||||
"three": "0.164.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^0.31.1",
|
||||
"@vitest/web-worker": "^0.31.1",
|
||||
"typescript": "5.0.4",
|
||||
"vite": "^4.3.2",
|
||||
"vitest": "^0.31.1"
|
||||
"@types/node": "^22.10.2",
|
||||
"@vitest/ui": "^1.6.0",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "^4.5.0",
|
||||
"vitest": "^1.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,11 @@ declare class GLTFNode {
|
||||
clone(parent?: GLTFNode): GLTFNode;
|
||||
}
|
||||
|
||||
type Attribute = 'POSITION'|'NORMAL'|'TANGENT'|'TEXCOORD_0'|'TEXCOORD_1'|
|
||||
'COLOR_0'|'JOINTS_0'|'WEIGHTS_0'|'SKIP_1'|'SKIP_2'|'SKIP_3'|'SKIP_4';
|
||||
|
||||
declare class GLTFMaterial {
|
||||
attributes?: string[];
|
||||
attributes?: Attribute[];
|
||||
roughness?: number;
|
||||
metallic?: number;
|
||||
baseColorFactor?: [number, number, number];
|
||||
@@ -35,15 +38,48 @@ declare class GLTFMaterial {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
declare const globalDefaults: {
|
||||
roughness: number,
|
||||
metallic: number,
|
||||
baseColorFactor: [number, number, number],
|
||||
alpha: number,
|
||||
unlit: boolean,
|
||||
animationLength: number,
|
||||
animationMode: 'loop'|'ping-pong';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set material properties on the input manifold. They will be carried along
|
||||
* through operations.
|
||||
* Returns a shallow copy of the input manifold with the given material
|
||||
* properties applied. They will be carried along through operations.
|
||||
*
|
||||
* @param manifold The object to add properties to - returned for chaining.
|
||||
* @param manifold The input object.
|
||||
* @param material A set of material properties to apply to this manifold.
|
||||
*/
|
||||
declare function setMaterial(
|
||||
manifold: Manifold, material: GLTFMaterial): Manifold;
|
||||
declare function setMaterial(manifold: Manifold, material: GLTFMaterial):
|
||||
Manifold;
|
||||
|
||||
/**
|
||||
* Apply a morphing animation to the input manifold. Specify the start
|
||||
* function which will be applied to the vertex positions of the first frame and
|
||||
* linearly interpolated across the length of the overall animation. This
|
||||
* animation will only be shown if this manifold is used directly on a GLTFNode.
|
||||
*
|
||||
* @param manifold The object to add morphing animation to.
|
||||
* @param func A warping function to apply to the first animation frame.
|
||||
*/
|
||||
declare function setMorphStart(
|
||||
manifold: Manifold, func: (v: Vec3) => void): void;
|
||||
|
||||
/**
|
||||
* Apply a morphing animation to the input manifold. Specify the end
|
||||
* function which will be applied to the vertex positions of the last frame and
|
||||
* linearly interpolated across the length of the overall animation. This
|
||||
* animation will only be shown if this manifold is used directly on a GLTFNode.
|
||||
*
|
||||
* @param manifold The object to add morphing animation to.
|
||||
* @param func A warping function to apply to the last animation frame.
|
||||
*/
|
||||
declare function setMorphEnd(manifold: Manifold, func: (v: Vec3) => void): void;
|
||||
|
||||
/**
|
||||
* Wrap any shape object with this method to display it and any copies in
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/** @type {import("../../manifold-encapsulated-types")["Manifold"]} */
|
||||
const Manifold = globalThis.Manifold;
|
||||
|
||||
export const examples = {
|
||||
functions: {
|
||||
Intro: function() {
|
||||
@@ -48,11 +51,39 @@ export const examples = {
|
||||
return result;
|
||||
},
|
||||
|
||||
Auger: function() {
|
||||
const outerRadius = 20;
|
||||
const beadRadius = 2;
|
||||
const height = 40;
|
||||
const twist = 90;
|
||||
|
||||
const {revolve, sphere, union, extrude} = Manifold;
|
||||
const {circle} = CrossSection;
|
||||
setMinCircularEdgeLength(0.1);
|
||||
|
||||
const bead1 =
|
||||
revolve(circle(beadRadius).translate([outerRadius, 0]), 50, 90)
|
||||
.add(sphere(beadRadius).translate([outerRadius, 0, 0]))
|
||||
.translate([0, -outerRadius, 0]);
|
||||
|
||||
const beads = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
beads.push(bead1.rotate(0, 0, 120 * i));
|
||||
}
|
||||
const bead = union(beads);
|
||||
|
||||
const auger = extrude(bead.slice(0), height, 50, twist);
|
||||
|
||||
const result =
|
||||
auger.add(bead).add(bead.translate(0, 0, height).rotate(0, 0, twist));
|
||||
return result;
|
||||
},
|
||||
|
||||
TetrahedronPuzzle: function() {
|
||||
// A tetrahedron cut into two identical halves that can screw together as
|
||||
// a puzzle. This only outputs one of the halves. This demonstrates how
|
||||
// redundant points along a polygon can be used to make twisted extrusions
|
||||
// smoother. Based on the screw puzzle by George Hart:
|
||||
// a puzzle. This demonstrates how redundant points along a polygon can be
|
||||
// used to make twisted extrusions smoother. It also showcases animated
|
||||
// assemblies. Based on the screw puzzle by George Hart:
|
||||
// https://www.thingiverse.com/thing:186372
|
||||
|
||||
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
|
||||
@@ -61,19 +92,21 @@ export const examples = {
|
||||
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
|
||||
const tet = Manifold.tetrahedron().scale(scale);
|
||||
const tet = Manifold.tetrahedron().intersect(
|
||||
Manifold.tetrahedron().rotate([0, 0, 90]).scale(2.5));
|
||||
|
||||
const box = [];
|
||||
box.push([1, -1], [1, 1]);
|
||||
box.push([2, -2], [2, 2]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (4 * scale), 1 - i * 2 / nDivisions]);
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
}
|
||||
|
||||
const cyan = [0, 1, 1];
|
||||
const magenta = [1, 0, 1];
|
||||
const fade = (color, pos) => {
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
color[i] = cyan[i] * pos[2] + magenta[i] * (1 - pos[2]);
|
||||
const x = pos[2] / 2;
|
||||
color[i] = cyan[i] * x + magenta[i] * (1 - x);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -82,25 +115,43 @@ export const examples = {
|
||||
// channels as colors, and sets the factor to white, since our default is
|
||||
// yellow.
|
||||
const screw = setMaterial(
|
||||
Manifold.extrude(box, 1, nDivisions, 270).setProperties(3, fade),
|
||||
Manifold.extrude(box, 2, nDivisions, 270).setProperties(3, fade),
|
||||
{baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']});
|
||||
|
||||
const result = tet.intersect(
|
||||
screw.rotate([0, 0, -45]).translate([0, 0, -0.5]).scale(2 * scale));
|
||||
const result =
|
||||
tet.intersect(screw.rotate([0, 0, -45]).translate([0, 0, -1]))
|
||||
.scale(scale);
|
||||
|
||||
// Assigned materials are only applied to a GLTFNode. Note that material
|
||||
// definitions cascade, applying recursively to all child surfaces, but
|
||||
// overridden by any materials defined lower down. Our default material:
|
||||
// {
|
||||
// roughness = 0.2,
|
||||
// metallic = 1,
|
||||
// baseColorFactor = [1, 1, 0],
|
||||
// alpha = 1,
|
||||
// unlit = false,
|
||||
// name = ''
|
||||
// }
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
// overridden by any materials defined lower down. The default material
|
||||
// properties, as well as animation parameters can be set via
|
||||
// globalDefaults.
|
||||
|
||||
const layFlat = new GLTFNode();
|
||||
layFlat.rotation =
|
||||
[45, -Math.atan(1 / Math.sqrt(2)) * 180 / Math.PI, 120];
|
||||
layFlat.translation = [0, 0, scale * Math.sqrt(3) / 3];
|
||||
|
||||
const fixed = new GLTFNode(layFlat);
|
||||
fixed.manifold = result;
|
||||
fixed.rotation = [0, 0, 180];
|
||||
|
||||
// For 3MF export, only top-level objects are independently arrangeable.
|
||||
const layFlat2 = layFlat.clone();
|
||||
|
||||
const moving = new GLTFNode(layFlat2);
|
||||
moving.manifold = result;
|
||||
// Use functions to create animation, which runs from t=0 to t=1.
|
||||
moving.translation = (t) => {
|
||||
const a = 1 - t;
|
||||
const x = a > 0.5 ? scale * 2 * (0.5 - a) : 0;
|
||||
return [x, x, -2 * x + scale * 4 * (0.5 - Math.abs(0.5 - a))];
|
||||
};
|
||||
moving.rotation = (t) => [0, 0, 270 * 2 * (1 - t)];
|
||||
|
||||
globalDefaults.animationLength = 10; // seconds
|
||||
globalDefaults.animationMode = 'ping-pong';
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -108,7 +159,7 @@ export const examples = {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
const {sphere, cylinder, union} = Manifold;
|
||||
const {sphere, cylinder, union, cube} = Manifold;
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
@@ -132,6 +183,17 @@ export const examples = {
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
// Demonstrate how you can use the .split method to perform
|
||||
// a subtraction and an intersection at once
|
||||
const [inside, outside] = result.split(cube(100, true));
|
||||
|
||||
const outsideNode = new GLTFNode();
|
||||
outsideNode.manifold = outside;
|
||||
|
||||
const insideNode = new GLTFNode();
|
||||
insideNode.manifold = inside;
|
||||
insideNode.material = {baseColorFactor: [0, 1, 1]};
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -139,6 +201,7 @@ export const examples = {
|
||||
// Smooth, complex manifolds can be created using the warp() function.
|
||||
// This example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
// It also demonstrates the use of setMorph to animate a warping function.
|
||||
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
@@ -174,9 +237,20 @@ export const examples = {
|
||||
};
|
||||
|
||||
const ball = Manifold.sphere(1, 200);
|
||||
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const result = heart.scale(100 / (box.max[0] - box.min[0]));
|
||||
const scale = 100 / (box.max[0] - box.min[0]);
|
||||
|
||||
setMorphStart(ball, func);
|
||||
const node = new GLTFNode();
|
||||
node.manifold = ball;
|
||||
node.scale = [scale, scale, scale];
|
||||
|
||||
globalDefaults.animationLength = 5; // seconds
|
||||
globalDefaults.animationMode = 'ping-pong';
|
||||
|
||||
const result = heart.scale(scale);
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -253,71 +327,68 @@ export const examples = {
|
||||
// an example of using the warp() method, thus avoiding any direct
|
||||
// handling of triangles.
|
||||
|
||||
// @param p The number of times the thread passes through the donut hole.
|
||||
// @param q The number of times the thread circles the donut.
|
||||
// @param majorRadius Radius of the interior of the imaginary donut.
|
||||
// @param minorRadius Radius of the small cross-section of the imaginary
|
||||
// donut.
|
||||
// @param threadRadius Radius of the small cross-section of the actual
|
||||
// object.
|
||||
// @param circularSegments Number of linear segments making up the
|
||||
// threadRadius circle. Default is getCircularSegments(threadRadius).
|
||||
// @param linearSegments Number of segments along the length of the knot.
|
||||
// Default makes roughly square facets.
|
||||
// The number of times the thread passes through the donut hole.
|
||||
const p = 1;
|
||||
// The number of times the thread circles the donut.
|
||||
const q = 3;
|
||||
// Radius of the interior of the imaginary donut.
|
||||
const majorRadius = 25;
|
||||
// Radius of the small cross-section of the imaginary donut.
|
||||
const minorRadius = 10;
|
||||
// Radius of the small cross-section of the actual object.
|
||||
const threadRadius = 3.75;
|
||||
// Number of linear segments making up the threadRadius circle. Default is
|
||||
// getCircularSegments(threadRadius).
|
||||
const circularSegments = -1;
|
||||
// Number of segments along the length of the knot. Default makes roughly
|
||||
// square facets.
|
||||
const linearSegments = -1;
|
||||
|
||||
function torusKnot(
|
||||
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
|
||||
linearSegments = 0) {
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
}
|
||||
|
||||
const kLoops = gcd(p, q);
|
||||
p /= kLoops;
|
||||
q /= kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * q * majorRadius / threadRadius;
|
||||
|
||||
const offset = 2
|
||||
const circle = CrossSection.circle(1, n).translate([offset, 0]);
|
||||
|
||||
const func = (v) => {
|
||||
const psi = q * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * p / q;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
vec3.set(
|
||||
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
|
||||
const center = vec3.fromValues(0, 0, 0);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
vec3.rotateX(v, v, center, -Math.atan2(p * minorRadius, q * r));
|
||||
v[0] += minorRadius;
|
||||
vec3.rotateY(v, v, center, theta);
|
||||
v[0] += majorRadius;
|
||||
vec3.rotateZ(v, v, center, psi);
|
||||
};
|
||||
|
||||
let knot = Manifold.revolve(circle, m).warp(func);
|
||||
|
||||
if (kLoops > 1) {
|
||||
const knots = [];
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
|
||||
}
|
||||
knot = Manifold.compose(knots);
|
||||
}
|
||||
|
||||
return knot;
|
||||
}
|
||||
|
||||
// This recreates Matlab Knot by Emmett Lalish:
|
||||
// These default values recreate Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const result = torusKnot(1, 3, 25, 10, 3.75);
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
}
|
||||
|
||||
const kLoops = gcd(p, q);
|
||||
const pk = p / kLoops;
|
||||
const qk = q / kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * qk * majorRadius / threadRadius;
|
||||
|
||||
const offset = 2
|
||||
const circle = CrossSection.circle(1, n).translate([offset, 0]);
|
||||
|
||||
const func = (v) => {
|
||||
const psi = qk * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * pk / qk;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
vec3.set(
|
||||
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
|
||||
const center = vec3.fromValues(0, 0, 0);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
vec3.rotateX(v, v, center, -Math.atan2(pk * minorRadius, qk * r));
|
||||
v[0] += minorRadius;
|
||||
vec3.rotateY(v, v, center, theta);
|
||||
v[0] += majorRadius;
|
||||
vec3.rotateZ(v, v, center, psi);
|
||||
};
|
||||
|
||||
const result = Manifold.revolve(circle, m).warp(func);
|
||||
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.rotation = [0, 0, 360 * (k / kLoops) * (qk / pk)];
|
||||
node.translation = [0, 0, result.boundingBox().min[2]];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -350,12 +421,12 @@ export const examples = {
|
||||
|
||||
const hole = Manifold.compose(holes);
|
||||
|
||||
result = Manifold.difference(
|
||||
result,
|
||||
hole,
|
||||
hole.rotate([90, 0, 0]),
|
||||
hole.rotate([0, 90, 0]),
|
||||
);
|
||||
result = Manifold.difference([
|
||||
result,
|
||||
hole,
|
||||
hole.rotate([90, 0, 0]),
|
||||
hole.rotate([0, 90, 0]),
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -507,7 +578,7 @@ export const examples = {
|
||||
0, 0, size / Math.sqrt(2)
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
functionBodies: new Map()
|
||||
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 240 B |
|
After Width: | Height: | Size: 326 B |
|
After Width: | Height: | Size: 492 B |
|
After Width: | Height: | Size: 619 B |
|
After Width: | Height: | Size: 675 B |
|
After Width: | Height: | Size: 471 B |
@@ -0,0 +1 @@
|
||||
declare module 'simple-dropzone';
|
||||
@@ -35,83 +35,21 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
|
||||
involving GLTF export and <model-viewer> can be found <a href="model-viewer.html">here</a>. </p>
|
||||
<p>This example demonstrates interop between Manifold and Three.js with minimal code - please open dev tools to
|
||||
inspect the source. Here we generate two multi-material Three.js meshes and convert them to Manifolds while building
|
||||
a mapping from material to Manifold ID. Then Boolean operations are performed on the Manifolds and the result is
|
||||
converted back to a Three.js mesh using the material mapping. The input cube has half red faces and half
|
||||
normal-shaded, while the icosahedron has half blue faces and half normal-shaded. The resulting mesh has three
|
||||
materials, since one (normal-shaded) was common between the input models.</p>
|
||||
<p>A slightly more complex example involving our library for GLTF import/export and <model-viewer> can be found
|
||||
<a href="model-viewer.html">here</a>. </p>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
<option value="union" selected>Union</option>
|
||||
<option value="difference">Difference</option>
|
||||
<option value="intersection">Intersection</option>
|
||||
<option value="union">Union</option>
|
||||
</select><br />
|
||||
<canvas id="output"></canvas>
|
||||
</body>
|
||||
<script type="module">
|
||||
import * as THREE from 'three';
|
||||
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
// we have manifold module, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
scene.add(mesh);
|
||||
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
|
||||
const manifold_1 = Manifold.cube([0.2, 0.2, 0.2], true);
|
||||
const manifold_2 = new Manifold(geometry2mesh(icosahedron));
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(Manifold[operation](manifold_1, manifold_2).getMesh());
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
|
||||
const output = document.querySelector("#output");
|
||||
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
|
||||
const dim = output.getBoundingClientRect();
|
||||
renderer.setSize(dim.width, dim.height);
|
||||
renderer.setAnimationLoop(function (time) {
|
||||
mesh.rotation.x = time / 2000;
|
||||
mesh.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertProperties = geometry.attributes.position.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new Mesh({ numProp: 3, vertProperties, triVerts });
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
// most of three.js geometries aren't manifolds, so...
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="three.ts"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {BoxGeometry, BufferAttribute, BufferGeometry, IcosahedronGeometry, Mesh as ThreeMesh, MeshLambertMaterial, MeshNormalMaterial, PerspectiveCamera, PointLight, Scene, WebGLRenderer} from 'three';
|
||||
|
||||
import Module, {Mesh} from './built/manifold';
|
||||
|
||||
// Load Manifold WASM library
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
const {Manifold, Mesh} = wasm;
|
||||
|
||||
// Define our set of materials
|
||||
const materials = [
|
||||
new MeshNormalMaterial({flatShading: true}),
|
||||
new MeshLambertMaterial({color: 'red', flatShading: true}),
|
||||
new MeshLambertMaterial({color: 'blue', flatShading: true})
|
||||
];
|
||||
const result = new ThreeMesh(undefined, materials);
|
||||
|
||||
// Set up Manifold IDs corresponding to materials
|
||||
const firstID = Manifold.reserveIDs(materials.length);
|
||||
// ids vector is parallel to materials vector - same indexing
|
||||
const ids = [...Array<number>(materials.length)].map((_, idx) => firstID + idx);
|
||||
// Build a mapping to get back from ID to material index
|
||||
const id2matIndex = new Map();
|
||||
ids.forEach((id, idx) => id2matIndex.set(id, idx));
|
||||
|
||||
// Set up Three.js scene
|
||||
const scene = new Scene();
|
||||
const camera = new PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
camera.add(new PointLight(0xffffff, 1));
|
||||
scene.add(camera);
|
||||
scene.add(result);
|
||||
|
||||
// Set up Three.js renderer
|
||||
const output = document.querySelector('#output')!;
|
||||
const renderer = new WebGLRenderer({canvas: output, antialias: true});
|
||||
const dim = output.getBoundingClientRect();
|
||||
renderer.setSize(dim.width, dim.height);
|
||||
renderer.setAnimationLoop(function(time: number) {
|
||||
result.rotation.x = time / 2000;
|
||||
result.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
||||
// Create input meshes in Three.js
|
||||
const cube = new BoxGeometry(0.2, 0.2, 0.2);
|
||||
cube.clearGroups();
|
||||
cube.addGroup(0, 18, 0); // First 6 faces colored by normal
|
||||
cube.addGroup(18, Infinity, 1); // Rest of faces are red
|
||||
|
||||
const icosahedron = new IcosahedronGeometry(0.16);
|
||||
icosahedron.clearGroups();
|
||||
icosahedron.addGroup(30, Infinity, 2); // Last faces are blue
|
||||
icosahedron.addGroup(0, 30, 0); // First 10 faces colored by normal
|
||||
// The above groups are in reversed order to demonstrate the need for sorting.
|
||||
|
||||
// Convert Three.js input meshes to Manifolds
|
||||
const manifoldCube = new Manifold(geometry2mesh(cube));
|
||||
const manifoldIcosahedron = new Manifold(geometry2mesh(icosahedron));
|
||||
|
||||
// Set up UI for operations
|
||||
type BooleanOp = 'union'|'difference'|'intersection';
|
||||
|
||||
function csg(operation: BooleanOp) {
|
||||
result.geometry?.dispose();
|
||||
result.geometry = mesh2geometry(
|
||||
Manifold[operation](manifoldCube, manifoldIcosahedron).getMesh());
|
||||
}
|
||||
|
||||
csg('union');
|
||||
const selectElement = document.querySelector('select')!;
|
||||
selectElement.onchange = function() {
|
||||
csg(selectElement.value as BooleanOp);
|
||||
};
|
||||
|
||||
// Convert Three.js BufferGeometry to Manifold Mesh
|
||||
function geometry2mesh(geometry: BufferGeometry) {
|
||||
// Only using position in this sample for simplicity. Can interleave any other
|
||||
// desired attributes here such as UV, normal, etc.
|
||||
const vertProperties = geometry.attributes.position.array as Float32Array;
|
||||
// Manifold only uses indexed geometry, so generate an index if necessary.
|
||||
const triVerts = geometry.index != null ?
|
||||
geometry.index.array as Uint32Array :
|
||||
new Uint32Array(vertProperties.length / 3).map((_, idx) => idx);
|
||||
// Create a triangle run for each group (material) - akin to a draw call.
|
||||
const starts = [...Array(geometry.groups.length)].map(
|
||||
(_, idx) => geometry.groups[idx].start);
|
||||
// Map the materials to ID.
|
||||
const originalIDs = [...Array(geometry.groups.length)].map(
|
||||
(_, idx) => ids[geometry.groups[idx].materialIndex!]);
|
||||
// List the runs in sequence.
|
||||
const indices = Array.from(starts.keys())
|
||||
indices.sort((a, b) => starts[a] - starts[b])
|
||||
const runIndex = new Uint32Array(indices.map(i => starts[i]));
|
||||
const runOriginalID = new Uint32Array(indices.map(i => originalIDs[i]));
|
||||
// Create the MeshGL for I/O with Manifold library.
|
||||
const mesh =
|
||||
new Mesh({numProp: 3, vertProperties, triVerts, runIndex, runOriginalID});
|
||||
// Automatically merge vertices with nearly identical positions to create a
|
||||
// Manifold. This only fills in the mergeFromVert and mergeToVert vectors -
|
||||
// these are automatically filled in for any mesh returned by Manifold. These
|
||||
// are necessary because GL drivers require duplicate verts when any
|
||||
// properties change, e.g. a UV boundary or sharp corner.
|
||||
mesh.merge();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// Convert Manifold Mesh to Three.js BufferGeometry
|
||||
function mesh2geometry(mesh: Mesh) {
|
||||
const geometry = new BufferGeometry();
|
||||
// Assign buffers
|
||||
geometry.setAttribute(
|
||||
'position', new BufferAttribute(mesh.vertProperties, 3));
|
||||
geometry.setIndex(new BufferAttribute(mesh.triVerts, 1));
|
||||
// Create a group (material) for each ID. Note that there may be multiple
|
||||
// triangle runs returned with the same ID, though these will always be
|
||||
// sequential since they are sorted by ID. In this example there are two runs
|
||||
// for the MeshNormalMaterial, one corresponding to each input mesh that had
|
||||
// this ID. This allows runTransform to return the total transformation matrix
|
||||
// applied to each triangle run from its input mesh - even after many
|
||||
// consecutive operations.
|
||||
let id = mesh.runOriginalID[0];
|
||||
let start = mesh.runIndex[0];
|
||||
for (let run = 0; run < mesh.numRun; ++run) {
|
||||
const nextID = mesh.runOriginalID[run + 1];
|
||||
if (nextID !== id) {
|
||||
const end = mesh.runIndex[run + 1];
|
||||
geometry.addGroup(start, end - start, id2matIndex.get(id));
|
||||
id = nextID;
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
return geometry;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "es2022",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": [
|
||||
"es2022",
|
||||
"dom"
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// https://gist.github.com/jamsinclair/6ad148d0590291077a4ce389c2b274ea
|
||||
import {createFilter} from 'vite';
|
||||
|
||||
function isEmscriptenFile(code) {
|
||||
return /var\s+Module\s*=|WebAssembly\.instantiate/.test(code) &&
|
||||
/var\s+workerOptions\s*=/.test(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vite plugin that replaces Emscripten workerOptions with static object literal
|
||||
* to fix error with Vite See project issue:
|
||||
* https://github.com/emscripten-core/emscripten/issues/22394
|
||||
*
|
||||
* Defaults to running for all .js and .ts files. If there are any issues you
|
||||
* can use the include/exclude options.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @property {string[]} [include] - Glob patterns to include
|
||||
* @property {string[]} [exclude] - Glob patterns to exclude
|
||||
* @returns {import('vite').Plugin}
|
||||
*/
|
||||
export default function emscriptenStaticWorkerOptions(options = {}) {
|
||||
const filter = createFilter(options.include || /\.[jt]s$/, options.exclude);
|
||||
|
||||
return {
|
||||
name: 'emscripten-static-worker-options',
|
||||
enforce: 'pre',
|
||||
transform(code, id) {
|
||||
if (!filter(id)) return null;
|
||||
|
||||
if (!isEmscriptenFile(code)) return null;
|
||||
|
||||
const workerOptionsMatch =
|
||||
code.match(/var\s+workerOptions\s*=\s*({[^}]+})/);
|
||||
if (!workerOptionsMatch) return null;
|
||||
|
||||
const optionsObjectStr = workerOptionsMatch[1];
|
||||
const optionsDeclarationStr = workerOptionsMatch[0];
|
||||
|
||||
const modifiedCode =
|
||||
code.replace(optionsDeclarationStr, '')
|
||||
.replace(
|
||||
new RegExp('workerOptions(?![\\w$])', 'g'), optionsObjectStr);
|
||||
|
||||
return {code: modifiedCode, map: null};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,13 +2,20 @@
|
||||
import {resolve} from 'path'
|
||||
import {defineConfig} from 'vite'
|
||||
|
||||
import emscriptenStaticWorkerOptions from './vite-fixup-plugin.js'
|
||||
|
||||
export default defineConfig({
|
||||
test: {testTimeout: 15000},
|
||||
worker: {
|
||||
format: 'es',
|
||||
worker: {format: 'es', plugins: [emscriptenStaticWorkerOptions]},
|
||||
server: {
|
||||
headers: {
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
},
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
manifoldCAD: resolve(__dirname, 'index.html'),
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {cleanup, evaluateCADToModel} from './worker';
|
||||
|
||||
// Setup complete
|
||||
self.postMessage(null);
|
||||
|
||||
if (self.console) {
|
||||
const oldLog = self.console.log;
|
||||
self.console.log = function(...args) {
|
||||
let message = '';
|
||||
for (const arg of args) {
|
||||
if (arg == null) {
|
||||
message += 'undefined';
|
||||
} else if (typeof arg == 'object') {
|
||||
message += JSON.stringify(arg, null, 4);
|
||||
} else {
|
||||
message += arg.toString();
|
||||
}
|
||||
}
|
||||
self.postMessage({log: message});
|
||||
oldLog(...args);
|
||||
};
|
||||
}
|
||||
|
||||
self.onmessage = async (e) => {
|
||||
try {
|
||||
const result = await evaluateCADToModel(e.data);
|
||||
self.postMessage(result);
|
||||
} catch (error: any) {
|
||||
console.log(error.toString());
|
||||
self.postMessage({objectURL: null});
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
};
|
||||
@@ -1,152 +0,0 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import '@vitest/web-worker';
|
||||
|
||||
import {WebIO} from '@gltf-transform/core';
|
||||
import {expect, suite, test} from 'vitest';
|
||||
|
||||
import Module from './built/manifold.js';
|
||||
import {readMesh, setupIO} from './gltf-io.js';
|
||||
import {examples} from './public/examples.js';
|
||||
import ManifoldWorker from './worker?worker';
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
function initialized(worker) {
|
||||
return new Promise((resolve) => {
|
||||
worker.onmessage = function(e) {
|
||||
if (e.data == null) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let glbURL = null;
|
||||
|
||||
async function runExample(name) {
|
||||
const worker = new ManifoldWorker();
|
||||
await initialized(worker);
|
||||
return new Promise((resolve, reject) => {
|
||||
worker.onmessageerror = function({data}) {
|
||||
reject(data);
|
||||
};
|
||||
|
||||
worker.onerror = function(e) {
|
||||
reject(e);
|
||||
};
|
||||
|
||||
worker.onmessage = async function(e) {
|
||||
try {
|
||||
URL.revokeObjectURL(glbURL);
|
||||
glbURL = e.data.glbURL;
|
||||
if (glbURL == null) {
|
||||
reject('no glbURL)');
|
||||
}
|
||||
const docIn = await io.read(glbURL);
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
for (const node of nodes) {
|
||||
const mesh = node.getMesh();
|
||||
if (!mesh) {
|
||||
continue;
|
||||
}
|
||||
const attributes = [];
|
||||
const materials = [];
|
||||
const manifoldMesh = readMesh(mesh, attributes, materials);
|
||||
const manifold = wasm.Manifold(manifoldMesh);
|
||||
const prop = manifold.getProperties();
|
||||
const genus = manifold.genus();
|
||||
console.log(genus);
|
||||
manifold.delete();
|
||||
resolve({...prop, genus});
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
worker.postMessage(examples.functionBodies.get(name));
|
||||
});
|
||||
}
|
||||
|
||||
suite('Examples', () => {
|
||||
test('Intro', async () => {
|
||||
const result = await runExample('Intro');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Tetrahedron Puzzle', async () => {
|
||||
const result = await runExample('Tetrahedron Puzzle');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Rounded Frame', async () => {
|
||||
const result = await runExample('Rounded Frame');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(353706, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Heart', async () => {
|
||||
const result = await runExample('Heart');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(282743, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Scallop', async () => {
|
||||
const result = await runExample('Scallop');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Torus Knot', async () => {
|
||||
const result = await runExample('Torus Knot');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(20786, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(11177, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Menger Sponge', async () => {
|
||||
const result = await runExample('Menger Sponge');
|
||||
expect(result.genus).to.equal(729, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(203222, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(130475, 10, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Stretchy Bracelet', async () => {
|
||||
const result = await runExample('Stretchy Bracelet');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Gyroid Module', async () => {
|
||||
const result = await runExample('Gyroid Module');
|
||||
expect(result.genus).to.equal(15, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {WebIO} from '@gltf-transform/core';
|
||||
import {strict as assert} from 'assert';
|
||||
import {afterEach, expect, suite, test} from 'vitest';
|
||||
|
||||
import {readMesh, setupIO} from './gltf-io';
|
||||
// @ts-ignore
|
||||
import {examples} from './public/examples.js';
|
||||
import {Mesh} from './public/manifold';
|
||||
import {cleanup, evaluateCADToModel, module} from './worker';
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
|
||||
async function runExample(name: string) {
|
||||
const code = examples.functionBodies.get(name);
|
||||
const result = await evaluateCADToModel(code);
|
||||
cleanup();
|
||||
assert.ok(result?.glbURL);
|
||||
const docIn = await io.read(result?.glbURL);
|
||||
URL.revokeObjectURL(result?.glbURL);
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
for (const node of nodes) {
|
||||
const docMesh = node.getMesh();
|
||||
if (!docMesh) {
|
||||
continue;
|
||||
}
|
||||
const {mesh} = readMesh(docMesh)!;
|
||||
const manifold = new module.Manifold(mesh as Mesh);
|
||||
const volume = manifold.volume();
|
||||
const surfaceArea = manifold.surfaceArea();
|
||||
const genus = manifold.genus();
|
||||
manifold.delete();
|
||||
return {volume, surfaceArea, genus};
|
||||
}
|
||||
assert.ok(false);
|
||||
}
|
||||
|
||||
// allow vitest to report progress after each test
|
||||
// before going into heavy computation which blocks main thread
|
||||
afterEach(async () => {await new Promise(resolve => setTimeout(resolve, 500))})
|
||||
|
||||
suite('Examples', () => {
|
||||
test('Intro', async () => {
|
||||
const result = await runExample('Intro');
|
||||
expect(result?.genus).to.equal(5, 'Genus');
|
||||
expect(result?.volume).to.be.closeTo(203164, 1, 'Volume');
|
||||
expect(result?.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Auger', async () => {
|
||||
const result = await runExample('Auger');
|
||||
expect(result?.genus).to.equal(0, 'Genus');
|
||||
expect(result?.volume).to.be.closeTo(16842, 1, 'Volume');
|
||||
expect(result?.surfaceArea).to.be.closeTo(10519, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Tetrahedron Puzzle', async () => {
|
||||
const result = await runExample('Tetrahedron Puzzle');
|
||||
expect(result?.genus).to.equal(0, 'Genus');
|
||||
expect(result?.volume).to.be.closeTo(7240, 1, 'Volume');
|
||||
expect(result?.surfaceArea).to.be.closeTo(3235, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Rounded Frame', async () => {
|
||||
const result = await runExample('Rounded Frame');
|
||||
expect(result?.genus).to.equal(5, 'Genus');
|
||||
expect(result?.volume).to.be.closeTo(270807, 10, 'Volume');
|
||||
expect(result?.surfaceArea).to.be.closeTo(74599, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Heart', async () => {
|
||||
const result = await runExample('Heart');
|
||||
expect(result?.genus).to.equal(0, 'Genus');
|
||||
expect(result?.volume).to.be.closeTo(3.342, 0.001, 'Volume');
|
||||
expect(result?.surfaceArea).to.be.closeTo(11.51, 0.01, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Scallop', async () => {
|
||||
const result = await runExample('Scallop');
|
||||
expect(result?.genus).to.equal(0, 'Genus');
|
||||
expect(result?.volume).to.be.closeTo(39900, 100, 'Volume');
|
||||
expect(result?.surfaceArea).to.be.closeTo(7930, 10, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Torus Knot', async () => {
|
||||
const result = await runExample('Torus Knot');
|
||||
expect(result?.genus).to.equal(1, 'Genus');
|
||||
expect(result?.volume).to.be.closeTo(20786, 1, 'Volume');
|
||||
expect(result?.surfaceArea).to.be.closeTo(11177, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Menger Sponge', async () => {
|
||||
const result = await runExample('Menger Sponge');
|
||||
expect(result?.genus).to.equal(729, 'Genus');
|
||||
expect(result?.volume).to.be.closeTo(203222, 10, 'Volume');
|
||||
expect(result?.surfaceArea).to.be.closeTo(130475, 10, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Stretchy Bracelet', async () => {
|
||||
const result = await runExample('Stretchy Bracelet');
|
||||
expect(result?.genus).to.equal(1, 'Genus');
|
||||
expect(result?.volume).to.be.closeTo(3992, 1, 'Volume');
|
||||
expect(result?.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Gyroid Module', async () => {
|
||||
const result = await runExample('Gyroid Module');
|
||||
expect(result?.genus).to.equal(15, 'Genus');
|
||||
expect(result?.volume).to.be.closeTo(4175, 1, 'Volume');
|
||||
expect(result?.surfaceArea).to.be.closeTo(5645, 1, 'Surface Area');
|
||||
});
|
||||
});
|
||||
@@ -12,27 +12,37 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Document, mat4, Material, Node, WebIO} from '@gltf-transform/core';
|
||||
import {Accessor, Animation, AnimationSampler, Document, mat4, Material, Mesh as GLTFMesh, Node, WebIO} from '@gltf-transform/core';
|
||||
import {KHRMaterialsUnlit, KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
|
||||
import {fileForContentTypes, to3dmodel} from '@jscadui/3mf-export';
|
||||
import {fileForContentTypes, FileForRelThumbnail, to3dmodel} from '@jscadui/3mf-export';
|
||||
import {strToU8, Zippable, zipSync} from 'fflate'
|
||||
import * as glMatrix from 'gl-matrix';
|
||||
|
||||
import Module from './built/manifold';
|
||||
//@ts-ignore
|
||||
import {setupIO, writeMesh} from './gltf-io';
|
||||
import type {GLTFMaterial, Quat} from './public/editor';
|
||||
import type {CrossSection, Manifold, ManifoldToplevel, Mesh, Vec3} from './public/manifold';
|
||||
import Module, {CrossSection, Manifold, ManifoldToplevel, Mesh, Vec3} from './built/manifold';
|
||||
import {Properties, setupIO, writeMesh} from './gltf-io';
|
||||
import {GLTFMaterial, Quat} from './public/editor';
|
||||
|
||||
interface GlobalDefaults {
|
||||
roughness: number;
|
||||
metallic: number;
|
||||
baseColorFactor: [number, number, number];
|
||||
alpha: number;
|
||||
unlit: boolean;
|
||||
animationLength: number;
|
||||
animationMode: 'loop'|'ping-pong';
|
||||
}
|
||||
|
||||
interface WorkerStatic extends ManifoldToplevel {
|
||||
GLTFNode: typeof GLTFNode;
|
||||
show(manifold: Manifold): Manifold;
|
||||
only(manifold: Manifold): Manifold;
|
||||
setMaterial(manifold: Manifold, material: GLTFMaterial): void;
|
||||
setMaterial(manifold: Manifold, material: GLTFMaterial): Manifold;
|
||||
setMorphStart(manifold: Manifold, func: (v: Vec3) => void): void;
|
||||
setMorphEnd(manifold: Manifold, func: (v: Vec3) => void): void;
|
||||
cleanup(): void;
|
||||
}
|
||||
|
||||
const module = await Module() as unknown as WorkerStatic;
|
||||
export const module = await Module() as WorkerStatic;
|
||||
module.setup();
|
||||
|
||||
// Faster on modern browsers than Float32Array
|
||||
@@ -48,9 +58,32 @@ const manifoldStaticFunctions = [
|
||||
];
|
||||
// manifold member functions (that return a new manifold)
|
||||
const manifoldMemberFunctions = [
|
||||
'add', 'subtract', 'intersect', 'decompose', 'warp', 'transform', 'translate',
|
||||
'rotate', 'scale', 'mirror', 'refine', 'setProperties', 'asOriginal',
|
||||
'trimByPlane', 'split', 'splitByPlane', 'hull'
|
||||
'add',
|
||||
'subtract',
|
||||
'intersect',
|
||||
'decompose',
|
||||
'warp',
|
||||
'transform',
|
||||
'translate',
|
||||
'rotate',
|
||||
'scale',
|
||||
'mirror',
|
||||
'calculateCurvature',
|
||||
'calculateNormals',
|
||||
'smoothByNormals',
|
||||
'smoothOut',
|
||||
'refine',
|
||||
'refineToLength',
|
||||
'refineToTolerance',
|
||||
'setProperties',
|
||||
'setTolerance',
|
||||
'asOriginal',
|
||||
'trimByPlane',
|
||||
'split',
|
||||
'splitByPlane',
|
||||
'slice',
|
||||
'project',
|
||||
'hull'
|
||||
];
|
||||
// CrossSection static methods (that return a new cross-section)
|
||||
const crossSectionStaticFunctions = [
|
||||
@@ -66,7 +99,8 @@ const crossSectionMemberFunctions = [
|
||||
const toplevelConstructors = ['show', 'only', 'setMaterial'];
|
||||
const toplevel = [
|
||||
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
|
||||
'getCircularSegments', 'Mesh', 'GLTFNode', 'Manifold', 'CrossSection'
|
||||
'getCircularSegments', 'resetToCircularDefaults', 'Mesh', 'GLTFNode',
|
||||
'Manifold', 'CrossSection', 'setMorphStart', 'setMorphEnd'
|
||||
];
|
||||
const exposedFunctions = toplevelConstructors.concat(toplevel);
|
||||
|
||||
@@ -83,15 +117,13 @@ function addMembers(
|
||||
const cls = module[className];
|
||||
const obj = areStatic ? cls : cls.prototype;
|
||||
for (const name of methodNames) {
|
||||
if (name != 'cylinder') {
|
||||
const originalFn = obj[name];
|
||||
obj[name] = function(...args: any) {
|
||||
//@ts-ignore
|
||||
const result = originalFn(...args);
|
||||
memoryRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
const originalFn = obj[name];
|
||||
obj[name] = function(...args: any) {
|
||||
//@ts-ignore
|
||||
const result = originalFn(...args);
|
||||
memoryRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,22 +143,23 @@ for (const name of toplevelConstructors) {
|
||||
};
|
||||
}
|
||||
|
||||
module.cleanup = function() {
|
||||
for (const obj of memoryRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
memoryRegistry.length = 0;
|
||||
};
|
||||
|
||||
// Debug setup to show source meshes
|
||||
let ghost = false;
|
||||
const shown = new Map<number, Mesh>();
|
||||
const singles = new Map<number, Mesh>();
|
||||
|
||||
const FPS = 30;
|
||||
|
||||
const GLOBAL_DEFAULTS = {
|
||||
roughness: 0.2,
|
||||
metallic: 1,
|
||||
baseColorFactor: [1, 1, 0] as [number, number, number],
|
||||
alpha: 1,
|
||||
unlit: false,
|
||||
animationLength: 1,
|
||||
animationMode: 'loop'
|
||||
};
|
||||
|
||||
const SHOW = {
|
||||
baseColorFactor: [1, 0, 0],
|
||||
alpha: 0.25,
|
||||
@@ -144,10 +177,27 @@ const GHOST = {
|
||||
const nodes = new Array<GLTFNode>();
|
||||
const id2material = new Map<number, GLTFMaterial>();
|
||||
const materialCache = new Map<GLTFMaterial, Material>();
|
||||
let nextGlobalID = 0;
|
||||
const object2globalID = new Map<GLTFNode|Manifold, number>();
|
||||
const manifold2morph = new Map<Manifold, Morph>();
|
||||
// lib3mf doesn't like objectid=0
|
||||
let nextGlobalID = 1;
|
||||
let animation: Animation;
|
||||
let timesAccessor: Accessor;
|
||||
let weightsAccessor: Accessor;
|
||||
let weightsSampler: AnimationSampler;
|
||||
let hasAnimation: boolean;
|
||||
let t0 = 0;
|
||||
|
||||
export function cleanup() {
|
||||
for (const obj of memoryRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
memoryRegistry.length = 0;
|
||||
|
||||
function cleanup() {
|
||||
ghost = false;
|
||||
shown.clear();
|
||||
singles.clear();
|
||||
@@ -155,7 +205,13 @@ function cleanup() {
|
||||
id2material.clear();
|
||||
materialCache.clear();
|
||||
object2globalID.clear();
|
||||
nextGlobalID = 0;
|
||||
manifold2morph.clear();
|
||||
nextGlobalID = 1;
|
||||
}
|
||||
|
||||
interface Morph {
|
||||
start?: (v: Vec3) => void;
|
||||
end?: (v: Vec3) => void;
|
||||
}
|
||||
|
||||
interface Mesh3MF {
|
||||
@@ -198,9 +254,9 @@ interface To3MF {
|
||||
class GLTFNode {
|
||||
private _parent?: GLTFNode;
|
||||
manifold?: Manifold;
|
||||
translation?: Vec3;
|
||||
rotation?: Vec3;
|
||||
scale?: Vec3;
|
||||
translation?: Vec3|((t: number) => Vec3);
|
||||
rotation?: Vec3|((t: number) => Vec3);
|
||||
scale?: Vec3|((t: number) => Vec3);
|
||||
material?: GLTFMaterial;
|
||||
name?: string;
|
||||
|
||||
@@ -221,12 +277,32 @@ class GLTFNode {
|
||||
|
||||
module.GLTFNode = GLTFNode;
|
||||
|
||||
const globalDefaults = {...GLOBAL_DEFAULTS};
|
||||
|
||||
module.setMaterial = (manifold: Manifold, material: GLTFMaterial): Manifold => {
|
||||
const out = manifold.asOriginal();
|
||||
id2material.set(out.originalID(), material);
|
||||
return out;
|
||||
};
|
||||
|
||||
module.setMorphStart = (manifold: Manifold, func: (v: Vec3) => void): void => {
|
||||
const morph = manifold2morph.get(manifold);
|
||||
if (morph != null) {
|
||||
morph.start = func;
|
||||
} else {
|
||||
manifold2morph.set(manifold, {start: func});
|
||||
}
|
||||
};
|
||||
|
||||
module.setMorphEnd = (manifold: Manifold, func: (v: Vec3) => void): void => {
|
||||
const morph = manifold2morph.get(manifold);
|
||||
if (morph != null) {
|
||||
morph.end = func;
|
||||
} else {
|
||||
manifold2morph.set(manifold, {end: func});
|
||||
}
|
||||
};
|
||||
|
||||
function debug(manifold: Manifold, map: Map<number, Mesh>) {
|
||||
let result = manifold.asOriginal();
|
||||
map.set(result.originalID(), result.getMesh());
|
||||
@@ -242,69 +318,153 @@ module.only = (manifold) => {
|
||||
return debug(manifold, singles);
|
||||
};
|
||||
|
||||
// Setup complete
|
||||
self.postMessage(null);
|
||||
|
||||
if (self.console) {
|
||||
const oldLog = self.console.log;
|
||||
self.console.log = function(...args) {
|
||||
let message = '';
|
||||
for (const arg of args) {
|
||||
if (arg == null) {
|
||||
message += 'undefined';
|
||||
} else if (typeof arg == 'object') {
|
||||
message += JSON.stringify(arg, null, 4);
|
||||
} else {
|
||||
message += arg.toString();
|
||||
}
|
||||
}
|
||||
self.postMessage({log: message});
|
||||
oldLog(...args);
|
||||
};
|
||||
}
|
||||
|
||||
// Swallow informational logs in testing framework
|
||||
function log(...args: any[]) {
|
||||
if (self.console) {
|
||||
if (typeof self !== 'undefined' && self.console) {
|
||||
self.console.log(...args);
|
||||
}
|
||||
}
|
||||
|
||||
self.onmessage = async (e) => {
|
||||
const content = e.data +
|
||||
'\nreturn exportModels(typeof result === "undefined" ? undefined : result);\n';
|
||||
try {
|
||||
const f = new Function(
|
||||
'exportModels', 'glMatrix', 'module', ...exposedFunctions, content);
|
||||
await f(
|
||||
exportModels, glMatrix, module, //@ts-ignore
|
||||
...exposedFunctions.map(name => module[name]));
|
||||
} catch (error: any) {
|
||||
console.log(error.toString());
|
||||
self.postMessage({objectURL: null});
|
||||
} finally {
|
||||
module.cleanup();
|
||||
cleanup();
|
||||
}
|
||||
};
|
||||
function euler2quat(rotation: Vec3): Quat {
|
||||
const {quat} = glMatrix;
|
||||
const deg2rad = Math.PI / 180;
|
||||
const q = quat.create() as Quat;
|
||||
quat.rotateZ(q, q, deg2rad * rotation[2]);
|
||||
quat.rotateY(q, q, deg2rad * rotation[1]);
|
||||
quat.rotateX(q, q, deg2rad * rotation[0]);
|
||||
return q;
|
||||
}
|
||||
|
||||
function createGLTFnode(doc: Document, node: GLTFNode) {
|
||||
function addMotion(
|
||||
doc: Document, type: 'translation'|'rotation'|'scale', node: GLTFNode,
|
||||
out: Node): Vec3|null {
|
||||
const motion = node[type];
|
||||
if (motion == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof motion !== 'function') {
|
||||
return motion;
|
||||
}
|
||||
|
||||
const nFrames = timesAccessor.getCount();
|
||||
const nEl = type == 'rotation' ? 4 : 3;
|
||||
const frames = new Float32Array(nEl * nFrames);
|
||||
for (let i = 0; i < nFrames; ++i) {
|
||||
const x = i / (nFrames - 1);
|
||||
const m = motion(
|
||||
globalDefaults.animationMode !== 'ping-pong' ?
|
||||
x :
|
||||
(1 - Math.cos(x * 2 * Math.PI)) / 2);
|
||||
frames.set(nEl === 4 ? euler2quat(m) : m, nEl * i);
|
||||
}
|
||||
|
||||
const framesAccessor =
|
||||
doc.createAccessor(node.name + ' ' + type + ' frames')
|
||||
.setBuffer(doc.getRoot().listBuffers()[0])
|
||||
.setArray(frames)
|
||||
.setType(nEl === 4 ? Accessor.Type.VEC4 : Accessor.Type.VEC3);
|
||||
const sampler = doc.createAnimationSampler()
|
||||
.setInput(timesAccessor)
|
||||
.setOutput(framesAccessor)
|
||||
.setInterpolation('LINEAR');
|
||||
const channel = doc.createAnimationChannel()
|
||||
.setTargetPath(type)
|
||||
.setTargetNode(out)
|
||||
.setSampler(sampler);
|
||||
animation.addSampler(sampler);
|
||||
animation.addChannel(channel);
|
||||
hasAnimation = true;
|
||||
return motion(0);
|
||||
}
|
||||
|
||||
function setMorph(doc: Document, node: Node, manifold: Manifold) {
|
||||
if (manifold2morph.has(manifold)) {
|
||||
const channel = doc.createAnimationChannel()
|
||||
.setTargetPath('weights')
|
||||
.setTargetNode(node)
|
||||
.setSampler(weightsSampler);
|
||||
animation.addChannel(channel);
|
||||
hasAnimation = true;
|
||||
}
|
||||
}
|
||||
|
||||
function morphStart(manifoldMesh: Mesh, morph?: Morph): number[] {
|
||||
const inputPositions: number[] = [];
|
||||
if (morph == null) {
|
||||
return inputPositions;
|
||||
}
|
||||
|
||||
for (let i = 0; i < manifoldMesh.numVert; ++i) {
|
||||
for (let j = 0; j < 3; ++j)
|
||||
inputPositions[i * 3 + j] =
|
||||
manifoldMesh.vertProperties[i * manifoldMesh.numProp + j];
|
||||
}
|
||||
if (morph.start) {
|
||||
for (let i = 0; i < manifoldMesh.numVert; ++i) {
|
||||
const vertProp = manifoldMesh.vertProperties;
|
||||
const offset = i * manifoldMesh.numProp;
|
||||
const pos = inputPositions.slice(offset, offset + 3) as Vec3;
|
||||
morph.start(pos);
|
||||
for (let j = 0; j < 3; ++j) vertProp[offset + j] = pos[j];
|
||||
}
|
||||
}
|
||||
return inputPositions;
|
||||
}
|
||||
|
||||
function morphEnd(
|
||||
doc: Document, manifoldMesh: Mesh, mesh: GLTFMesh, inputPositions: number[],
|
||||
morph?: Morph) {
|
||||
if (morph == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mesh.setWeights([0]);
|
||||
|
||||
mesh.listPrimitives().forEach((primitive, i) => {
|
||||
if (morph.end) {
|
||||
for (let i = 0; i < manifoldMesh.numVert; ++i) {
|
||||
const pos = inputPositions.slice(3 * i, 3 * (i + 1)) as Vec3;
|
||||
morph.end(pos);
|
||||
inputPositions.splice(3 * i, 3, ...pos);
|
||||
}
|
||||
}
|
||||
|
||||
const startPosition = primitive.getAttribute('POSITION')!.getArray()!;
|
||||
const array = new Float32Array(startPosition.length);
|
||||
|
||||
const offset = manifoldMesh.runIndex[i];
|
||||
for (let j = 0; j < array.length; ++j) {
|
||||
array[j] = inputPositions[offset + j] - startPosition[j];
|
||||
}
|
||||
|
||||
const morphAccessor = doc.createAccessor(mesh.getName() + ' morph target')
|
||||
.setBuffer(doc.getRoot().listBuffers()[0])
|
||||
.setArray(array)
|
||||
.setType(Accessor.Type.VEC3);
|
||||
const morphTarget =
|
||||
doc.createPrimitiveTarget().setAttribute('POSITION', morphAccessor);
|
||||
primitive.addTarget(morphTarget);
|
||||
});
|
||||
}
|
||||
|
||||
function createGLTFnode(doc: Document, node: GLTFNode): Node {
|
||||
const out = doc.createNode(node.name);
|
||||
if (node.translation) {
|
||||
out.setTranslation(node.translation);
|
||||
|
||||
const pos = addMotion(doc, 'translation', node, out);
|
||||
if (pos != null) {
|
||||
out.setTranslation(pos);
|
||||
}
|
||||
if (node.rotation) {
|
||||
const {quat} = glMatrix;
|
||||
const deg2rad = Math.PI / 180;
|
||||
const q = quat.create() as Quat;
|
||||
quat.rotateX(q, q, deg2rad * node.rotation[0]);
|
||||
quat.rotateY(q, q, deg2rad * node.rotation[1]);
|
||||
quat.rotateZ(q, q, deg2rad * node.rotation[2]);
|
||||
out.setRotation(q);
|
||||
|
||||
const rot = addMotion(doc, 'rotation', node, out);
|
||||
if (rot != null) {
|
||||
out.setRotation(euler2quat(rot));
|
||||
}
|
||||
if (node.scale) {
|
||||
out.setScale(node.scale);
|
||||
|
||||
const scale = addMotion(doc, 'scale', node, out);
|
||||
if (scale != null) {
|
||||
out.setScale(scale);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -318,15 +478,13 @@ function getBackupMaterial(node?: GLTFNode): GLTFMaterial {
|
||||
return node.material;
|
||||
}
|
||||
|
||||
function makeDefaultedMaterial(doc: Document, {
|
||||
roughness = 0.2,
|
||||
metallic = 1,
|
||||
baseColorFactor = [1, 1, 0],
|
||||
alpha = 1,
|
||||
unlit = false,
|
||||
name = ''
|
||||
}: GLTFMaterial = {}): Material {
|
||||
const material = doc.createMaterial(name);
|
||||
function makeDefaultedMaterial(
|
||||
doc: Document, matIn: GLTFMaterial = {}): Material {
|
||||
const defaults = {...globalDefaults};
|
||||
Object.assign(defaults, matIn);
|
||||
const {roughness, metallic, baseColorFactor, alpha, unlit} = defaults;
|
||||
|
||||
const material = doc.createMaterial(matIn.name ?? '');
|
||||
|
||||
if (unlit) {
|
||||
const unlit = doc.createExtension(KHRMaterialsUnlit).createUnlit();
|
||||
@@ -366,26 +524,30 @@ function addMesh(
|
||||
}
|
||||
log(`Bounding Box: X = ${size[0].toLocaleString()} mm, Y = ${
|
||||
size[1].toLocaleString()} mm, Z = ${size[2].toLocaleString()} mm`);
|
||||
const volume = Math.round(manifold.getProperties().volume / 10);
|
||||
const volume = Math.round(manifold.volume() / 10);
|
||||
log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
|
||||
(volume / 100).toLocaleString()} cm^3`);
|
||||
|
||||
// From Z-up to Y-up (glTF)
|
||||
const manifoldMesh = manifold.getMesh();
|
||||
|
||||
const materials = new Array<GLTFMaterial>();
|
||||
const attributes = new Array<Array<string>>();
|
||||
const id2properties = new Map<number, Properties>();
|
||||
for (const id of manifoldMesh.runOriginalID!) {
|
||||
const material = id2material.get(id) || backupMaterial;
|
||||
materials.push(material);
|
||||
attributes.push(['POSITION', ...material.attributes ?? []]);
|
||||
id2properties.set(id, {
|
||||
material: getCachedMaterial(doc, ghost ? GHOST : material),
|
||||
attributes: ['POSITION', ...material.attributes ?? []]
|
||||
});
|
||||
}
|
||||
|
||||
const gltfMaterials = materials.map((matDef) => {
|
||||
return getCachedMaterial(doc, ghost ? GHOST : matDef);
|
||||
});
|
||||
const morph = manifold2morph.get(manifold);
|
||||
const inputPositions = morphStart(manifoldMesh, morph);
|
||||
|
||||
node.setMesh(writeMesh(doc, manifoldMesh, attributes, gltfMaterials));
|
||||
const mesh = writeMesh(doc, manifoldMesh, id2properties);
|
||||
mesh.setName(node.getName());
|
||||
node.setMesh(mesh);
|
||||
|
||||
morphEnd(doc, manifoldMesh, mesh, inputPositions, morph);
|
||||
|
||||
const vertices = manifoldMesh.numProp === 3 ?
|
||||
manifoldMesh.vertProperties :
|
||||
@@ -402,21 +564,18 @@ function addMesh(
|
||||
{vertices, indices: manifoldMesh.triVerts, id: `${nextGlobalID++}`});
|
||||
|
||||
for (const [run, id] of manifoldMesh.runOriginalID!.entries()) {
|
||||
let inMesh = shown.get(id);
|
||||
let single = false;
|
||||
if (inMesh == null) {
|
||||
single = true;
|
||||
inMesh = singles.get(id);
|
||||
}
|
||||
const show = shown.has(id);
|
||||
const inMesh = show ? shown.get(id) : singles.get(id);
|
||||
if (inMesh == null) {
|
||||
continue;
|
||||
}
|
||||
const mat = single ? materials[run] : SHOW;
|
||||
const debugNode =
|
||||
doc.createNode('debug')
|
||||
.setMesh(writeMesh(
|
||||
doc, inMesh, attributes, [getCachedMaterial(doc, mat)]))
|
||||
.setMatrix(manifoldMesh.transform(run));
|
||||
|
||||
id2properties.get(id)!.material = getCachedMaterial(
|
||||
doc, show ? SHOW : (id2material.get(id) || backupMaterial));
|
||||
|
||||
const debugNode = doc.createNode('debug')
|
||||
.setMesh(writeMesh(doc, inMesh, id2properties))
|
||||
.setMatrix(manifoldMesh.transform(run));
|
||||
node.addChild(debugNode);
|
||||
}
|
||||
}
|
||||
@@ -450,6 +609,7 @@ function createNodeFromCache(
|
||||
const node = createGLTFnode(doc, nodeDef);
|
||||
const {manifold} = nodeDef;
|
||||
if (manifold != null) {
|
||||
setMorph(doc, node, manifold);
|
||||
const backupMaterial = getBackupMaterial(nodeDef);
|
||||
const cachedNodes = manifold2node.get(manifold);
|
||||
if (cachedNodes == null) {
|
||||
@@ -460,7 +620,8 @@ function createNodeFromCache(
|
||||
} else {
|
||||
const cachedNode = cachedNodes.get(backupMaterial);
|
||||
if (cachedNode == null) {
|
||||
const [oldBackupMaterial, oldNode] = cachedNodes.entries().next().value;
|
||||
const [oldBackupMaterial, oldNode] =
|
||||
cachedNodes.entries().next().value!;
|
||||
cloneNodeNewMaterial(
|
||||
doc, node, oldNode, getCachedMaterial(doc, backupMaterial),
|
||||
getCachedMaterial(doc, oldBackupMaterial));
|
||||
@@ -481,7 +642,10 @@ function createNodeFromCache(
|
||||
return node;
|
||||
}
|
||||
|
||||
async function exportModels(manifold?: Manifold) {
|
||||
async function exportModels(defaults: GlobalDefaults, manifold?: Manifold) {
|
||||
Object.assign(globalDefaults, GLOBAL_DEFAULTS);
|
||||
Object.assign(globalDefaults, defaults);
|
||||
|
||||
const doc = new Document();
|
||||
const halfRoot2 = Math.sqrt(2) / 2;
|
||||
const mm2m = 1 / 1000;
|
||||
@@ -490,6 +654,32 @@ async function exportModels(manifold?: Manifold) {
|
||||
.setScale([mm2m, mm2m, mm2m]);
|
||||
doc.createScene().addChild(wrapper);
|
||||
|
||||
animation = doc.createAnimation('');
|
||||
hasAnimation = false;
|
||||
const nFrames = Math.round(globalDefaults.animationLength * FPS) + 1;
|
||||
const times = new Float32Array(nFrames);
|
||||
const weights = new Float32Array(nFrames);
|
||||
for (let i = 0; i < nFrames; ++i) {
|
||||
const x = i / (nFrames - 1);
|
||||
times[i] = x * globalDefaults.animationLength;
|
||||
weights[i] = globalDefaults.animationMode !== 'ping-pong' ?
|
||||
x :
|
||||
(1 - Math.cos(x * 2 * Math.PI)) / 2;
|
||||
}
|
||||
timesAccessor = doc.createAccessor('animation times')
|
||||
.setBuffer(doc.createBuffer())
|
||||
.setArray(times)
|
||||
.setType(Accessor.Type.SCALAR);
|
||||
weightsAccessor = doc.createAccessor('animation weights')
|
||||
.setBuffer(doc.getRoot().listBuffers()[0])
|
||||
.setArray(weights)
|
||||
.setType(Accessor.Type.SCALAR);
|
||||
weightsSampler = doc.createAnimationSampler()
|
||||
.setInput(timesAccessor)
|
||||
.setOutput(weightsAccessor)
|
||||
.setInterpolation('LINEAR');
|
||||
animation.addSampler(weightsSampler);
|
||||
|
||||
const to3mf = {
|
||||
meshes: [],
|
||||
components: [],
|
||||
@@ -547,20 +737,65 @@ async function exportModels(manifold?: Manifold) {
|
||||
to3mf.items.push({objectID: `${object2globalID.get(manifold)}`});
|
||||
}
|
||||
|
||||
const t1 = performance.now();
|
||||
console.log(`Manifold took ${
|
||||
(Math.round((t1 - t0) / 10) / 100).toLocaleString()} seconds`);
|
||||
|
||||
if (!hasAnimation) {
|
||||
timesAccessor.dispose();
|
||||
weightsAccessor.dispose();
|
||||
weightsSampler.dispose();
|
||||
animation.dispose();
|
||||
}
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
const blobGLB = new Blob([glb], {type: 'application/octet-stream'});
|
||||
|
||||
const model = to3dmodel(to3mf);
|
||||
const fileForRelThumbnail = new FileForRelThumbnail();
|
||||
fileForRelThumbnail.add3dModel('3D/3dmodel.model')
|
||||
|
||||
const model = to3dmodel(to3mf as any);
|
||||
const files: Zippable = {};
|
||||
files['3D/3dmodel.model'] = strToU8(model);
|
||||
files[fileForContentTypes.name] = strToU8(fileForContentTypes.content);
|
||||
files[fileForRelThumbnail.name] = strToU8(fileForRelThumbnail.content);
|
||||
const zipFile = zipSync(files);
|
||||
const blob3MF = new Blob(
|
||||
[zipFile],
|
||||
{type: 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml'});
|
||||
|
||||
self.postMessage({
|
||||
const t2 = performance.now();
|
||||
console.log(`Exporting GLB & 3MF took ${
|
||||
(Math.round((t2 - t1) / 10) / 100).toLocaleString()} seconds`);
|
||||
|
||||
return ({
|
||||
glbURL: URL.createObjectURL(blobGLB),
|
||||
threeMFURL: URL.createObjectURL(blob3MF)
|
||||
});
|
||||
}
|
||||
|
||||
function evaluateCADToManifold(code: string) {
|
||||
const globalDefaults = {} as GlobalDefaults;
|
||||
const context = {
|
||||
globalDefaults,
|
||||
exportModels,
|
||||
glMatrix,
|
||||
module,
|
||||
...Object.fromEntries(
|
||||
exposedFunctions.map((name) => [name, (module as any)[name]]),
|
||||
),
|
||||
};
|
||||
const evalFn = new Function(
|
||||
...Object.keys(context),
|
||||
'resetToCircularDefaults();\n' + code +
|
||||
'\n return typeof result === "undefined" ? undefined : result;',
|
||||
);
|
||||
const manifold = evalFn(...Object.values(context));
|
||||
return {globalDefaults, manifold};
|
||||
}
|
||||
|
||||
export async function evaluateCADToModel(code: string) {
|
||||
t0 = performance.now();
|
||||
const {globalDefaults, manifold} = evaluateCADToManifold(code);
|
||||
return await exportModels(globalDefaults, manifold);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
#include "manifold/cross_section.h"
|
||||
#include "manifold/manifold.h"
|
||||
#include "manifold/polygon.h"
|
||||
|
||||
using namespace emscripten;
|
||||
using namespace manifold;
|
||||
@@ -84,7 +83,7 @@ MeshGL MeshJS2GL(const val& mesh) {
|
||||
return out;
|
||||
}
|
||||
|
||||
val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
|
||||
val GetMeshJS(const Manifold& manifold, int normalIdx) {
|
||||
MeshGL mesh = manifold.GetMeshGL(normalIdx);
|
||||
return MeshGL2JS(mesh);
|
||||
}
|
||||
@@ -106,7 +105,7 @@ Manifold Smooth(const val& mesh,
|
||||
} // namespace js
|
||||
|
||||
namespace cross_js {
|
||||
CrossSection OfPolygons(std::vector<std::vector<glm::vec2>> polygons,
|
||||
CrossSection OfPolygons(std::vector<std::vector<vec2>> polygons,
|
||||
int fill_rule) {
|
||||
auto fr = fill_rule == 0 ? CrossSection::FillRule::EvenOdd
|
||||
: fill_rule == 1 ? CrossSection::FillRule::NonZero
|
||||
@@ -140,15 +139,15 @@ CrossSection IntersectionN(const std::vector<CrossSection>& cross_sections) {
|
||||
}
|
||||
|
||||
CrossSection Transform(CrossSection& cross_section, const val& mat) {
|
||||
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
|
||||
glm::mat3x2 matrix;
|
||||
std::vector<double> array = convertJSArrayToNumberVector<double>(mat);
|
||||
mat2x3 matrix;
|
||||
for (const int col : {0, 1, 2})
|
||||
for (const int row : {0, 1}) matrix[col][row] = array[col * 3 + row];
|
||||
return cross_section.Transform(matrix);
|
||||
}
|
||||
|
||||
CrossSection Warp(CrossSection& cross_section, uintptr_t funcPtr) {
|
||||
void (*f)(glm::vec2&) = reinterpret_cast<void (*)(glm::vec2&)>(funcPtr);
|
||||
void (*f)(vec2&) = reinterpret_cast<void (*)(vec2&)>(funcPtr);
|
||||
return cross_section.Warp(f);
|
||||
}
|
||||
|
||||
@@ -160,7 +159,7 @@ CrossSection Offset(CrossSection& cross_section, double delta, int join_type,
|
||||
return cross_section.Offset(delta, jt, miter_limit, arc_tolerance);
|
||||
}
|
||||
|
||||
void CollectVertices(std::vector<glm::vec2>& verts, const CrossSection& cs) {
|
||||
void CollectVertices(std::vector<vec2>& verts, const CrossSection& cs) {
|
||||
auto polys = cs.ToPolygons();
|
||||
for (auto poly : polys) verts.insert(verts.end(), poly.begin(), poly.end());
|
||||
}
|
||||
@@ -188,30 +187,28 @@ Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
|
||||
}
|
||||
|
||||
Manifold Transform(Manifold& manifold, const val& mat) {
|
||||
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
|
||||
glm::mat4x3 matrix;
|
||||
std::vector<double> array = convertJSArrayToNumberVector<double>(mat);
|
||||
mat3x4 matrix;
|
||||
for (const int col : {0, 1, 2, 3})
|
||||
for (const int row : {0, 1, 2}) matrix[col][row] = array[col * 4 + row];
|
||||
return manifold.Transform(matrix);
|
||||
}
|
||||
|
||||
Manifold Warp(Manifold& manifold, uintptr_t funcPtr) {
|
||||
void (*f)(glm::vec3&) = reinterpret_cast<void (*)(glm::vec3&)>(funcPtr);
|
||||
void (*f)(vec3&) = reinterpret_cast<void (*)(vec3&)>(funcPtr);
|
||||
return manifold.Warp(f);
|
||||
}
|
||||
|
||||
Manifold SetProperties(Manifold& manifold, int numProp, uintptr_t funcPtr) {
|
||||
void (*f)(float*, glm::vec3, const float*) =
|
||||
reinterpret_cast<void (*)(float*, glm::vec3, const float*)>(funcPtr);
|
||||
void (*f)(double*, vec3, const double*) =
|
||||
reinterpret_cast<void (*)(double*, vec3, const double*)>(funcPtr);
|
||||
return manifold.SetProperties(numProp, f);
|
||||
}
|
||||
|
||||
Manifold LevelSet(uintptr_t funcPtr, Box bounds, float edgeLength,
|
||||
float level) {
|
||||
float (*f)(const glm::vec3&) =
|
||||
reinterpret_cast<float (*)(const glm::vec3&)>(funcPtr);
|
||||
Mesh m = LevelSet(f, bounds, edgeLength, level);
|
||||
return Manifold(m);
|
||||
Manifold LevelSet(uintptr_t funcPtr, Box bounds, double edgeLength,
|
||||
double level, double tolerance) {
|
||||
double (*f)(const vec3&) = reinterpret_cast<double (*)(const vec3&)>(funcPtr);
|
||||
return Manifold::LevelSet(f, bounds, edgeLength, level, tolerance, false);
|
||||
}
|
||||
|
||||
std::vector<Manifold> Split(Manifold& a, Manifold& b) {
|
||||
@@ -219,14 +216,17 @@ std::vector<Manifold> Split(Manifold& a, Manifold& b) {
|
||||
return {r1, r2};
|
||||
}
|
||||
|
||||
std::vector<Manifold> SplitByPlane(Manifold& m, glm::vec3 normal,
|
||||
float originOffset) {
|
||||
std::vector<Manifold> SplitByPlane(Manifold& m, vec3 normal,
|
||||
double originOffset) {
|
||||
auto [a, b] = m.SplitByPlane(normal, originOffset);
|
||||
return {a, b};
|
||||
}
|
||||
|
||||
void CollectVertices(std::vector<glm::vec3>& verts, const Manifold& manifold) {
|
||||
Mesh mesh = manifold.GetMesh();
|
||||
verts.insert(verts.end(), mesh.vertPos.begin(), mesh.vertPos.end());
|
||||
void CollectVertices(std::vector<vec3>& verts, const Manifold& manifold) {
|
||||
const MeshGL64 mesh = manifold.GetMeshGL64();
|
||||
const auto numVert = mesh.NumVert();
|
||||
for (size_t v = 0; v < numVert; ++v) {
|
||||
verts.push_back(mesh.GetVertPos(v));
|
||||
}
|
||||
}
|
||||
} // namespace man_js
|
||||
|
||||
@@ -12,32 +12,70 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Box, FillRule, JoinType, Mat4, Polygons, Properties, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
|
||||
import {Box, FillRule, JoinType, Mat3, Mat4, Polygons, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
|
||||
|
||||
/**
|
||||
* Triangulates a set of /epsilon-valid polygons.
|
||||
*
|
||||
* @param polygons The set of polygons, wound CCW and representing multiple
|
||||
* polygons and/or holes.
|
||||
* @param precision The value of epsilon, bounding the uncertainty of the input
|
||||
* @param epsilon The value of epsilon, bounding the uncertainty of the input
|
||||
* @return The triangles, referencing the original polygon points in order.
|
||||
*/
|
||||
export function triangulate(polygons: Polygons, precision?: number): Vec3[];
|
||||
export function triangulate(polygons: Polygons, epsilon?: number): Vec3[];
|
||||
|
||||
/**
|
||||
* @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
* Sets an angle constraint the default number of circular segments for the
|
||||
* {@link CrossSection.circle}, {@link Manifold.cylinder}, {@link
|
||||
* Manifold.sphere}, and
|
||||
* {@link Manifold.revolve} constructors. The number of segments will be rounded
|
||||
* up to the nearest factor of four.
|
||||
*
|
||||
* @param angle The minimum angle in degrees between consecutive segments. The
|
||||
* angle will increase if the the segments hit the minimum edge length.
|
||||
* Default is 10 degrees.
|
||||
*/
|
||||
///@{
|
||||
export function setMinCircularAngle(angle: number): void;
|
||||
|
||||
/**
|
||||
* Sets a length constraint the default number of circular segments for the
|
||||
* {@link CrossSection.circle}, {@link Manifold.cylinder}, {@link
|
||||
* Manifold.sphere}, and
|
||||
* {@link Manifold.revolve} constructors. The number of segments will be rounded
|
||||
* up to the nearest factor of four.
|
||||
*
|
||||
* @param length The minimum length of segments. The length will
|
||||
* increase if the the segments hit the minimum angle. Default is 1.0.
|
||||
*/
|
||||
export function setMinCircularEdgeLength(length: number): void;
|
||||
|
||||
/**
|
||||
* Sets the default number of circular segments for the
|
||||
* {@link CrossSection.circle}, {@link Manifold.cylinder}, {@link
|
||||
* Manifold.sphere}, and
|
||||
* {@link Manifold.revolve} constructors. Overrides the edge length and angle
|
||||
* constraints and sets the number of segments to exactly this value.
|
||||
*
|
||||
* @param segments Number of circular segments. Default is 0, meaning no
|
||||
* constraint is applied.
|
||||
*/
|
||||
export function setCircularSegments(segments: number): void;
|
||||
|
||||
/**
|
||||
* Determine the result of the {@link setMinCircularAngle},
|
||||
* {@link setMinCircularEdgeLength}, and {@link setCircularSegments} defaults.
|
||||
*
|
||||
* @param radius For a given radius of circle, determine how many default
|
||||
* segments there will be.
|
||||
*/
|
||||
export function getCircularSegments(radius: number): number;
|
||||
|
||||
/**
|
||||
* Resets the circular construction parameters to their defaults if
|
||||
* {@link setMinCircularAngle}, {@link setMinCircularEdgeLength}, or {@link
|
||||
* setCircularSegments} have been called.
|
||||
*/
|
||||
export function resetToCircularDefaults(): void;
|
||||
///@}
|
||||
|
||||
export class CrossSection {
|
||||
@@ -52,12 +90,27 @@ export class CrossSection {
|
||||
* @param fillRule The filling rule used to interpret polygon sub-regions in
|
||||
* contours.
|
||||
*/
|
||||
constructor(polygons: Polygons, fillRule?: FillRule);
|
||||
constructor(contours: Polygons, fillRule?: FillRule);
|
||||
|
||||
// Shapes
|
||||
|
||||
/**
|
||||
* Constructs a square with the given XY dimensions. By default it is
|
||||
* positioned in the first quadrant, touching the origin. If any dimensions in
|
||||
* size are negative, or if all are zero, an empty Manifold will be returned.
|
||||
*
|
||||
* @param size The X, and Y dimensions of the square.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
static square(size?: Vec2|number, center?: boolean): CrossSection;
|
||||
|
||||
/**
|
||||
* Constructs a circle of a given radius.
|
||||
*
|
||||
* @param radius Radius of the circle. Must be positive.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Quality defaults according to the radius.
|
||||
*/
|
||||
static circle(radius: number, circularSegments?: number): CrossSection;
|
||||
|
||||
// Extrusions (2d to 3d manifold)
|
||||
@@ -119,7 +172,7 @@ export class CrossSection {
|
||||
*
|
||||
* @param degrees degrees about the Z-axis to rotate.
|
||||
*/
|
||||
rotate(v: number): CrossSection;
|
||||
rotate(degrees: number): CrossSection;
|
||||
|
||||
/**
|
||||
* Scale this CrossSection in space. This operation can be chained. Transforms
|
||||
@@ -138,7 +191,7 @@ export class CrossSection {
|
||||
*
|
||||
* @param ax the axis to be mirrored over
|
||||
*/
|
||||
mirror(v: Vec2): CrossSection;
|
||||
mirror(ax: Vec2): CrossSection;
|
||||
|
||||
/**
|
||||
* Move the vertices of this CrossSection (creating a new one) according to
|
||||
@@ -282,7 +335,7 @@ export class CrossSection {
|
||||
* @param fillRule The filling rule used to interpret polygon sub-regions in
|
||||
* contours.
|
||||
*/
|
||||
static ofPolygons(polygons: Polygons, fillRule?: FillRule): CrossSection;
|
||||
static ofPolygons(contours: Polygons, fillRule?: FillRule): CrossSection;
|
||||
|
||||
/**
|
||||
* Return the contours of this CrossSection as a list of simple polygons.
|
||||
@@ -327,6 +380,30 @@ export class CrossSection {
|
||||
delete(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* This library's internal representation of an oriented, 2-manifold, triangle
|
||||
* mesh - a simple boundary-representation of a solid object. Use this class to
|
||||
* store and operate on solids, and use MeshGL for input and output, or
|
||||
* potentially Mesh if only basic geometry is required.
|
||||
*
|
||||
* In addition to storing geometric data, a Manifold can also store an arbitrary
|
||||
* number of vertex properties. These could be anything, e.g. normals, UV
|
||||
* coordinates, colors, etc, but this library is completely agnostic. All
|
||||
* properties are merely float values indexed by channel number. It is up to the
|
||||
* user to associate channel numbers with meaning.
|
||||
*
|
||||
* Manifold allows vertex properties to be shared for efficient storage, or to
|
||||
* have multiple property verts associated with a single geometric vertex,
|
||||
* allowing sudden property changes, e.g. at Boolean intersections, without
|
||||
* sacrificing manifoldness.
|
||||
*
|
||||
* Manifolds also keep track of their relationships to their inputs, via
|
||||
* OriginalIDs and the faceIDs and transforms accessible through MeshGL. This
|
||||
* allows object-level properties to be re-associated with the output after many
|
||||
* operations, particularly useful for materials. Since separate object's
|
||||
* properties are not mixed, there is no requirement that channels have
|
||||
* consistent meaning between different inputs.
|
||||
*/
|
||||
export class Manifold {
|
||||
/**
|
||||
* Convert a Mesh into a Manifold, retaining its properties and merging only
|
||||
@@ -492,10 +569,14 @@ export class Manifold {
|
||||
* performance.
|
||||
* @param level You can inset your Mesh by using a positive value, or outset
|
||||
* it with a negative value.
|
||||
* @param tolerance Ensure each vertex is within this distance of the true
|
||||
* surface. Defaults to -1, which will return the interpolated
|
||||
* crossing-point based on the two nearest grid points. Small positive values
|
||||
* will require more sdf evaluations per output vertex.
|
||||
*/
|
||||
static levelSet(
|
||||
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
|
||||
level?: number): Manifold;
|
||||
level?: number, tolerance?: number): Manifold;
|
||||
|
||||
// Transformations
|
||||
|
||||
@@ -546,7 +627,7 @@ export class Manifold {
|
||||
*
|
||||
* @param normal The normal vector of the plane to be mirrored over
|
||||
*/
|
||||
mirror(v: Vec3): Manifold;
|
||||
mirror(normal: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* This function does not change the topology, but allows the vertices to be
|
||||
@@ -559,6 +640,37 @@ export class Manifold {
|
||||
*/
|
||||
warp(warpFunc: (vert: Vec3) => void): Manifold;
|
||||
|
||||
/**
|
||||
* Smooths out the Manifold by filling in the halfedgeTangent vectors. The
|
||||
* geometry will remain unchanged until Refine or RefineToLength is called to
|
||||
* interpolate the surface. This version uses the supplied vertex normal
|
||||
* properties to define the tangent vectors.
|
||||
*
|
||||
* @param normalIdx The first property channel of the normals. NumProp must be
|
||||
* at least normalIdx + 3. Any vertex where multiple normals exist and don't
|
||||
* agree will result in a sharp edge.
|
||||
*/
|
||||
smoothByNormals(normalIdx: number): Manifold;
|
||||
|
||||
/**
|
||||
* Smooths out the Manifold by filling in the halfedgeTangent vectors. The
|
||||
* geometry will remain unchanged until Refine or RefineToLength is called to
|
||||
* interpolate the surface. This version uses the geometry of the triangles
|
||||
* and pseudo-normals to define the tangent vectors.
|
||||
*
|
||||
* @param minSharpAngle degrees, default 60. Any edges with angles greater
|
||||
* than this value will remain sharp. The rest will be smoothed to G1
|
||||
* continuity, with the caveat that flat faces of three or more triangles will
|
||||
* always remain flat. With a value of zero, the model is faceted, but in this
|
||||
* case there is no point in smoothing.
|
||||
*
|
||||
* @param minSmoothness range: 0 - 1, default 0. The smoothness applied to
|
||||
* sharp angles. The default gives a hard edge, while values > 0 will give a
|
||||
* small fillet on these sharp edges. A value of 1 is equivalent to a
|
||||
* minSharpAngle of 180 - all edges will be smooth.
|
||||
*/
|
||||
smoothOut(minSharpAngle?: number, minSmoothness?: number): Manifold;
|
||||
|
||||
/**
|
||||
* Increase the density of the mesh by splitting every edge into n pieces. For
|
||||
* instance, with n = 2, each triangle will be split into 4 triangles. These
|
||||
@@ -571,6 +683,32 @@ export class Manifold {
|
||||
*/
|
||||
refine(n: number): Manifold;
|
||||
|
||||
/**
|
||||
* Increase the density of the mesh by splitting each edge into pieces of
|
||||
* roughly the input length. Interior verts are added to keep the rest of the
|
||||
* triangulation edges also of roughly the same length. If halfedgeTangents
|
||||
* are present (e.g. from the Smooth() constructor), the new vertices will be
|
||||
* moved to the interpolated surface according to their barycentric
|
||||
* coordinates.
|
||||
*
|
||||
* @param length The length that edges will be broken down to.
|
||||
*/
|
||||
refineToLength(length: number): Manifold;
|
||||
|
||||
/**
|
||||
* Increase the density of the mesh by splitting each edge into pieces such
|
||||
* that any point on the resulting triangles is roughly within tolerance of
|
||||
* the smoothly curved surface defined by the tangent vectors. This means
|
||||
* tightly curving regions will be divided more finely than smoother regions.
|
||||
* If halfedgeTangents are not present, the result will simply be a copy of
|
||||
* the original. Quads will ignore their interior triangle bisector.
|
||||
*
|
||||
* @param tolerance The desired maximum distance between the faceted mesh
|
||||
* produced and the exact smoothly curving surface. All vertices are exactly
|
||||
* on the surface, within rounding error.
|
||||
*/
|
||||
refineToTolerance(tolerance: number): Manifold;
|
||||
|
||||
/**
|
||||
* Create a new copy of this manifold with updated vertex properties by
|
||||
* supplying a function that takes the existing position and properties as
|
||||
@@ -606,6 +744,24 @@ export class Manifold {
|
||||
*/
|
||||
calculateCurvature(gaussianIdx: number, meanIdx: number): Manifold;
|
||||
|
||||
/**
|
||||
* Fills in vertex properties for normal vectors, calculated from the mesh
|
||||
* geometry. Flat faces composed of three or more triangles will remain flat.
|
||||
*
|
||||
* @param normalIdx The property channel in which to store the X
|
||||
* values of the normals. The X, Y, and Z channels will be sequential. The
|
||||
* property set will be automatically expanded to include up through normalIdx
|
||||
* + 2.
|
||||
*
|
||||
* @param minSharpAngle Any edges with angles greater than this value will
|
||||
* remain sharp, getting different normal vector properties on each side of
|
||||
* the edge. By default, no edges are sharp and all normals are shared. With a
|
||||
* value of zero, the model is faceted and all normals match their triangle
|
||||
* normals, but in this case it would be better not to calculate normals at
|
||||
* all.
|
||||
*/
|
||||
calculateNormals(normalIdx: number, minSharpAngle: number): Manifold;
|
||||
|
||||
// Boolean Operations
|
||||
|
||||
/**
|
||||
@@ -685,6 +841,22 @@ export class Manifold {
|
||||
*/
|
||||
trimByPlane(normal: Vec3, originOffset: number): Manifold;
|
||||
|
||||
/**
|
||||
* Returns the cross section of this object parallel to the X-Y plane at the
|
||||
* specified height. Using a height equal to the bottom
|
||||
* of the bounding box will return the bottom faces, while using a height
|
||||
* equal to the top of the bounding box will return empty.
|
||||
*
|
||||
* @param height Z-level of slice.
|
||||
*/
|
||||
slice(height: number): CrossSection;
|
||||
|
||||
/**
|
||||
* Returns a cross section representing the projected outline of this object
|
||||
* onto the X-Y plane.
|
||||
*/
|
||||
project(): CrossSection;
|
||||
|
||||
// Convex Hulls
|
||||
|
||||
/**
|
||||
@@ -757,14 +929,20 @@ export class Manifold {
|
||||
boundingBox(): Box;
|
||||
|
||||
/**
|
||||
* Returns the precision of this Manifold's vertices, which tracks the
|
||||
* Returns the tolerance of this Manifold's vertices, which tracks the
|
||||
* approximate rounding error over all the transforms and operations that have
|
||||
* led to this state. Any triangles that are colinear within this precision
|
||||
* led to this state. Any triangles that are colinear within this tolerance
|
||||
* are considered degenerate and removed. This is the value of ε
|
||||
* defining
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
precision(): number;
|
||||
tolerance(): number;
|
||||
|
||||
/**
|
||||
* Return a copy of the manifold with the set tolerance value.
|
||||
* This performs mesh simplification when the tolerance value is increased.
|
||||
*/
|
||||
setTolerance(tolerance: number): Manifold;
|
||||
|
||||
/**
|
||||
* The genus is a topological property of the manifold, representing the
|
||||
@@ -774,12 +952,20 @@ export class Manifold {
|
||||
genus(): number;
|
||||
|
||||
/**
|
||||
* Returns the surface area and volume of the manifold. These properties are
|
||||
* clamped to zero for a given face if they are within the Precision(). This
|
||||
* means degenerate manifolds can by identified by testing these properties as
|
||||
* == 0.
|
||||
* Returns the surface area of the manifold.
|
||||
*/
|
||||
getProperties(): Properties;
|
||||
surfaceArea(): number;
|
||||
|
||||
/**
|
||||
* Returns the volume of the manifold.
|
||||
*/
|
||||
volume(): number;
|
||||
|
||||
/**
|
||||
* Returns the minimum gap between two manifolds. Returns a float between
|
||||
* 0 and searchLength.
|
||||
*/
|
||||
minGap(other: Manifold, searchLength: number): number;
|
||||
|
||||
// Export
|
||||
|
||||
@@ -789,14 +975,15 @@ export class Manifold {
|
||||
* includes relations to all the input meshes that form a part of this result
|
||||
* and the transforms applied to each.
|
||||
*
|
||||
* @param normalIdx If the original Mesh inputs that formed this manifold had
|
||||
* properties corresponding to normal vectors, you can specify which property
|
||||
* channels these are (x, y, z), which will cause this output Mesh to
|
||||
* automatically update these normals according to the applied transforms and
|
||||
* front/back side. Each channel must be >= 3 and < numProp, and all original
|
||||
* Meshes must use the same channels for their normals.
|
||||
* @param normalIdx If the original MeshGL inputs that formed this manifold
|
||||
* had properties corresponding to normal vectors, you can specify the first
|
||||
* of the three consecutive property channels forming the (x, y, z) normals,
|
||||
* which will cause this output MeshGL to automatically update these normals
|
||||
* according to the applied transforms and front/back side. normalIdx + 3 must
|
||||
* be <= numProp, and all original MeshGLs must use the same channels for
|
||||
* their normals.
|
||||
*/
|
||||
getMesh(normalIdx?: Vec3): Mesh;
|
||||
getMesh(normalIdx?: number): Mesh;
|
||||
|
||||
// ID Management
|
||||
|
||||
@@ -838,19 +1025,7 @@ export class Manifold {
|
||||
delete(): void;
|
||||
}
|
||||
|
||||
export class Mesh {
|
||||
constructor(options: {
|
||||
numProp: number,
|
||||
vertProperties: Float32Array,
|
||||
triVerts: Uint32Array,
|
||||
mergeFromVert?: Uint32Array,
|
||||
mergeToVert?: Uint32Array,
|
||||
runIndex?: Uint32Array,
|
||||
runOriginalID?: Uint32Array,
|
||||
runTransform?: Float32Array,
|
||||
faceID?: Uint32Array,
|
||||
halfedgeTangent?: Float32Array
|
||||
});
|
||||
export interface MeshOptions {
|
||||
numProp: number;
|
||||
vertProperties: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
@@ -861,13 +1036,161 @@ export class Mesh {
|
||||
runTransform?: Float32Array;
|
||||
faceID?: Uint32Array;
|
||||
halfedgeTangent?: Float32Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative to Mesh for output suitable for pushing into graphics
|
||||
* libraries directly. This may not be manifold since the verts are duplicated
|
||||
* along property boundaries that do not match. The additional merge vectors
|
||||
* store this missing information, allowing the manifold to be reconstructed.
|
||||
*/
|
||||
export class Mesh {
|
||||
constructor(options: MeshOptions);
|
||||
|
||||
/**
|
||||
* Number of properties per vertex, always >= 3.
|
||||
*/
|
||||
numProp: number;
|
||||
|
||||
/**
|
||||
* Flat, GL-style interleaved list of all vertex properties: propVal =
|
||||
* vertProperties[vert * numProp + propIdx]. The first three properties are
|
||||
* always the position x, y, z.
|
||||
*/
|
||||
vertProperties: Float32Array;
|
||||
|
||||
/**
|
||||
* The vertex indices of the three triangle corners in CCW (from the outside)
|
||||
* order, for each triangle.
|
||||
*/
|
||||
triVerts: Uint32Array;
|
||||
|
||||
/**
|
||||
* Optional: A list of only the vertex indicies that need to be merged to
|
||||
* reconstruct the manifold.
|
||||
*/
|
||||
mergeFromVert: Uint32Array;
|
||||
|
||||
/**
|
||||
* Optional: The same length as mergeFromVert, and the corresponding value
|
||||
* contains the vertex to merge with. It will have an identical position, but
|
||||
* the other properties may differ.
|
||||
*/
|
||||
mergeToVert: Uint32Array;
|
||||
|
||||
/**
|
||||
* Optional: Indicates runs of triangles that correspond to a particular
|
||||
* input mesh instance. The runs encompass all of triVerts and are sorted
|
||||
* by runOriginalID. Run i begins at triVerts[runIndex[i]] and ends at
|
||||
* triVerts[runIndex[i+1]]. All runIndex values are divisible by 3. Returned
|
||||
* runIndex will always be 1 longer than runOriginalID, but same length is
|
||||
* also allowed as input: triVerts.size() will be automatically appended in
|
||||
* this case.
|
||||
*/
|
||||
runIndex: Uint32Array;
|
||||
|
||||
/**
|
||||
* Optional: The OriginalID of the mesh this triangle run came from. This ID
|
||||
* is ideal for reapplying materials to the output mesh. Multiple runs may
|
||||
* have the same ID, e.g. representing different copies of the same input
|
||||
* mesh. If you create an input MeshGL that you want to be able to reference
|
||||
* as one or more originals, be sure to set unique values from ReserveIDs().
|
||||
*/
|
||||
runOriginalID: Uint32Array;
|
||||
|
||||
/**
|
||||
* Optional: For each run, a 3x4 transform is stored representing how the
|
||||
* corresponding original mesh was transformed to create this triangle run.
|
||||
* This matrix is stored in column-major order and the length of the overall
|
||||
* vector is 12 * runOriginalID.size().
|
||||
*/
|
||||
runTransform: Float32Array;
|
||||
|
||||
/**
|
||||
* Optional: Length NumTri, contains an ID of the source face this triangle
|
||||
* comes from. When auto-generated, this ID will be a triangle index into the
|
||||
* original mesh. All neighboring coplanar triangles from that input mesh
|
||||
* will refer to a single triangle of that group as the faceID. When
|
||||
* supplying faceIDs, ensure that triangles with the same ID are in fact
|
||||
* coplanar and have consistent properties (within some tolerance) or the
|
||||
* output will be surprising.
|
||||
*/
|
||||
faceID: Uint32Array;
|
||||
|
||||
/**
|
||||
* Optional: The X-Y-Z-W weighted tangent vectors for smooth Refine(). If
|
||||
* non-empty, must be exactly four times as long as Mesh.triVerts. Indexed
|
||||
* as 4 * (3 * tri + i) + j, i < 3, j < 4, representing the tangent value
|
||||
* Mesh.triVerts[tri][i] along the CCW edge. If empty, mesh is faceted.
|
||||
*/
|
||||
halfedgeTangent: Float32Array;
|
||||
|
||||
/**
|
||||
* Number of triangles
|
||||
*/
|
||||
get numTri(): number;
|
||||
|
||||
/**
|
||||
* Number of property vertices
|
||||
*/
|
||||
get numVert(): number;
|
||||
|
||||
/**
|
||||
* Number of triangle runs. Each triangle run is a set of consecutive
|
||||
* triangles that all come from the same instance of the same input mesh.
|
||||
*/
|
||||
get numRun(): number;
|
||||
|
||||
/**
|
||||
* Updates the mergeFromVert and mergeToVert vectors in order to create a
|
||||
* manifold solid. If the MeshGL is already manifold, no change will occur and
|
||||
* the function will return false. Otherwise, this will merge verts along open
|
||||
* edges within tolerance (the maximum of the MeshGL tolerance and the
|
||||
* baseline bounding-box tolerance), keeping any from the existing merge
|
||||
* vectors.
|
||||
*
|
||||
* There is no guarantee the result will be manifold - this is a best-effort
|
||||
* helper function designed primarily to aid in the case where a manifold
|
||||
* multi-material MeshGL was produced, but its merge vectors were lost due to
|
||||
* a round-trip through a file format. Constructing a Manifold from the result
|
||||
* will report a Status if it is not manifold.
|
||||
*/
|
||||
merge(): boolean;
|
||||
|
||||
/**
|
||||
* Gets the three vertex indices of this triangle in CCW order.
|
||||
*
|
||||
* @param tri triangle index.
|
||||
*/
|
||||
verts(tri: number): SealedUint32Array<3>;
|
||||
|
||||
/**
|
||||
* Gets the x, y, z position of this vertex.
|
||||
*
|
||||
* @param vert vertex index.
|
||||
*/
|
||||
position(vert: number): SealedFloat32Array<3>;
|
||||
|
||||
/**
|
||||
* Gets any other properties associated with this vertex.
|
||||
*
|
||||
* @param vert vertex index.
|
||||
*/
|
||||
extras(vert: number): Float32Array;
|
||||
|
||||
/**
|
||||
* Gets the tangent vector starting at verts(tri)[j] pointing to the next
|
||||
* Bezier point along the CCW edge. The fourth value is its weight.
|
||||
*
|
||||
* @param halfedge halfedge index: 3 * tri + j, where j is 0, 1, or 2.
|
||||
*/
|
||||
tangent(halfedge: number): SealedFloat32Array<4>;
|
||||
|
||||
/**
|
||||
* Gets the column-major 4x4 matrix transform from the original mesh to these
|
||||
* related triangles.
|
||||
*
|
||||
* @param run triangle run index.
|
||||
*/
|
||||
transform(run: number): Mat4;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,18 @@ export interface SealedFloat32Array<N extends number> extends Float32Array {
|
||||
|
||||
export type Vec2 = [number, number];
|
||||
export type Vec3 = [number, number, number];
|
||||
// 3x3 matrix stored in column-major order
|
||||
export type Mat3 = [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
];
|
||||
// 4x4 matrix stored in column-major order
|
||||
export type Mat4 = [
|
||||
number,
|
||||
@@ -55,9 +67,5 @@ export type Smoothness = {
|
||||
halfedge: number,
|
||||
smoothness: number
|
||||
};
|
||||
export type Properties = {
|
||||
surfaceArea: number,
|
||||
volume: number
|
||||
};
|
||||
export type FillRule = 'EvenOdd'|'NonZero'|'Positive'|'Negative'
|
||||
export type JoinType = 'Square'|'Round'|'Miter'
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface ManifoldToplevel {
|
||||
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof T.setCircularSegments;
|
||||
getCircularSegments: typeof T.getCircularSegments;
|
||||
resetToCircularDefaults: typeof T.resetToCircularDefaults;
|
||||
setup: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,344 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "2.1.0",
|
||||
"version": "2.5.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "manifold-3d",
|
||||
"version": "2.5.1",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"typedoc": "^0.26.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/core": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.15.2.tgz",
|
||||
"integrity": "sha512-hi6XZuwHYn6bU4wtXZxST8ynM55aiU2+rVU9aPIrSxqKmEKl4d65puwGsggwcZWTET+7zGXKe7AUj46iQ8Aq8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
||||
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/shiki": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-1.15.2.tgz",
|
||||
"integrity": "sha512-M+7QZQZiZw/cZeizrC/yryG3eeG8pTUhu7ZaHxVyzPNFIRIlN46YBciquoNPCiXiwLnx6JB62f3lSuSYQrus1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@shikijs/core": "1.15.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc": {
|
||||
"version": "0.26.6",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.6.tgz",
|
||||
"integrity": "sha512-SfEU3SH3wHNaxhFPjaZE2kNl/NFtLNW5c1oHsg7mti7GjmUj1Roq6osBQeMd+F4kL0BoRBBr8gQAuqBlfFu8LA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lunr": "^2.3.9",
|
||||
"markdown-it": "^14.1.0",
|
||||
"minimatch": "^9.0.5",
|
||||
"shiki": "^1.9.1",
|
||||
"yaml": "^2.4.5"
|
||||
},
|
||||
"bin": {
|
||||
"typedoc": "bin/typedoc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.5.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"license": "Apache-2.0"
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
|
||||
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@shikijs/core": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.15.2.tgz",
|
||||
"integrity": "sha512-hi6XZuwHYn6bU4wtXZxST8ynM55aiU2+rVU9aPIrSxqKmEKl4d65puwGsggwcZWTET+7zGXKe7AUj46iQ8Aq8w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/hast": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"@types/hast": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
||||
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"@types/unist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
||||
"dev": true
|
||||
},
|
||||
"argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true
|
||||
},
|
||||
"linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
|
||||
"dev": true
|
||||
},
|
||||
"markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"dev": true
|
||||
},
|
||||
"shiki": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-1.15.2.tgz",
|
||||
"integrity": "sha512-M+7QZQZiZw/cZeizrC/yryG3eeG8pTUhu7ZaHxVyzPNFIRIlN46YBciquoNPCiXiwLnx6JB62f3lSuSYQrus1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@shikijs/core": "1.15.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"typedoc": {
|
||||
"version": "0.26.6",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.6.tgz",
|
||||
"integrity": "sha512-SfEU3SH3wHNaxhFPjaZE2kNl/NFtLNW5c1oHsg7mti7GjmUj1Roq6osBQeMd+F4kL0BoRBBr8gQAuqBlfFu8LA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lunr": "^2.3.9",
|
||||
"markdown-it": "^14.1.0",
|
||||
"minimatch": "^9.0.5",
|
||||
"shiki": "^1.9.1",
|
||||
"yaml": "^2.4.5"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "5.5.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"dev": true
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
|
||||
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "2.1.0",
|
||||
"version": "3.0.1",
|
||||
"description": "Geometry library for topological robustness",
|
||||
"main": "manifold.js",
|
||||
"scripts": {
|
||||
"docs": "npx typedoc"
|
||||
},
|
||||
"files": [
|
||||
"manifold.js",
|
||||
"manifold.wasm",
|
||||
@@ -35,5 +38,8 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/elalish/manifold/issues"
|
||||
},
|
||||
"homepage": "https://github.com/elalish/manifold#readme"
|
||||
"homepage": "https://github.com/elalish/manifold#readme",
|
||||
"devDependencies": {
|
||||
"typedoc": "^0.26.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// typedoc.json
|
||||
{
|
||||
"entryPoints": [
|
||||
"manifold-encapsulated-types.d.ts",
|
||||
"manifold-global-types.d.ts"
|
||||
],
|
||||
"projectDocuments": ["documents/*.md"],
|
||||
"out": "docs"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Copyright 2024 The Manifold Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
function(exportbin TARGET)
|
||||
if(MSVC)
|
||||
set_target_properties(
|
||||
${TARGET}
|
||||
PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(exportlib TARGET)
|
||||
add_library(manifold::${TARGET} ALIAS ${TARGET})
|
||||
set_target_properties(
|
||||
${TARGET}
|
||||
PROPERTIES VERSION "${MANIFOLD_VERSION}" SOVERSION ${MANIFOLD_VERSION_MAJOR}
|
||||
)
|
||||
if(MSVC)
|
||||
set_target_properties(
|
||||
${TARGET}
|
||||
PROPERTIES
|
||||
WINDOWS_EXPORT_ALL_SYMBOLS ON
|
||||
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
|
||||
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,90 @@
|
||||
# Copyright 2024 The Manifold Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# configuration summary, idea from openscad
|
||||
# https://github.com/openscad/openscad/blob/master/cmake/Modules/info.cmake
|
||||
|
||||
get_target_property(MANIFOLD_COMPILE_OPTIONS manifold COMPILE_OPTIONS)
|
||||
get_target_property(MANIFOLD_LINK_OPTIONS manifold LINK_OPTIONS)
|
||||
if(MANIFOLD_LINK_OPTIONS STREQUAL "MANIFOLD_LINK_OPTIONS-NOTFOUND")
|
||||
set(MANIFOLD_LINK_OPTIONS "")
|
||||
endif()
|
||||
|
||||
message(STATUS "====================================")
|
||||
message(STATUS "Manifold Build Configuration Summary")
|
||||
message(STATUS "====================================")
|
||||
message(STATUS " ")
|
||||
if(MXECROSS)
|
||||
message(STATUS "Environment: MXE")
|
||||
elseif(APPLE)
|
||||
message(STATUS "Environment: macOS")
|
||||
elseif(WIN32)
|
||||
if(MINGW)
|
||||
message(STATUS "Environment: msys2")
|
||||
else()
|
||||
message(STATUS "Environment: Windows")
|
||||
endif()
|
||||
elseif(LINUX)
|
||||
message(STATUS "Environment: Linux")
|
||||
elseif(UNIX)
|
||||
message(STATUS "Environment: Unknown Unix")
|
||||
else()
|
||||
message(STATUS "Environment: Unknown")
|
||||
endif()
|
||||
message(STATUS " ")
|
||||
message(STATUS "CMAKE_VERSION: ${CMAKE_VERSION}")
|
||||
message(STATUS "CMAKE_TOOLCHAIN_FILE: ${CMAKE_TOOLCHAIN_FILE}")
|
||||
message(STATUS "CMAKE_GENERATOR: ${CMAKE_GENERATOR}")
|
||||
message(STATUS "CPACK_CMAKE_GENERATOR: ${CPACK_CMAKE_GENERATOR}")
|
||||
message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||
message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")
|
||||
message(STATUS "CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}")
|
||||
message(STATUS "CMAKE_CXX_COMPILER_VERSION: ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
if(APPLE)
|
||||
message(
|
||||
STATUS
|
||||
"CMAKE_OSX_DEPLOYMENT_TARGET: ${CMAKE_OSX_DEPLOYMENT_TARGET}"
|
||||
)
|
||||
message(STATUS "CMAKE_OSX_ARCHITECTURES: ${CMAKE_OSX_ARCHITECTURES}")
|
||||
endif()
|
||||
message(STATUS "BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}")
|
||||
message(STATUS " ")
|
||||
message(STATUS "MANIFOLD_VERSION: ${MANIFOLD_VERSION}")
|
||||
message(STATUS "MANIFOLD_PAR: ${MANIFOLD_PAR}")
|
||||
message(STATUS "MANIFOLD_CROSS_SECTION: ${MANIFOLD_CROSS_SECTION}")
|
||||
message(STATUS "MANIFOLD_EXPORT: ${MANIFOLD_EXPORT}")
|
||||
message(STATUS "MANIFOLD_TEST: ${MANIFOLD_TEST}")
|
||||
message(STATUS "MANIFOLD_DEBUG: ${MANIFOLD_DEBUG}")
|
||||
message(STATUS "MANIFOLD_CBIND: ${MANIFOLD_CBIND}")
|
||||
message(STATUS "MANIFOLD_PYBIND: ${MANIFOLD_PYBIND}")
|
||||
message(STATUS "MANIFOLD_JSBIND: ${MANIFOLD_JSBIND}")
|
||||
message(STATUS "MANIFOLD_FLAGS: ${MANIFOLD_FLAGS}")
|
||||
# these two include global flags added through add_compile_options and
|
||||
# add_link_options
|
||||
message(STATUS "MANIFOLD_COMPILE_OPTIONS: ${MANIFOLD_COMPILE_OPTIONS}")
|
||||
message(STATUS "MANIFOLD_LINK_OPTIONS: ${MANIFOLD_LINK_OPTIONS}")
|
||||
message(STATUS "MANIFOLD_DOWNLOADS: ${MANIFOLD_DOWNLOADS}")
|
||||
message(STATUS "MANIFOLD_USE_BUILTIN_TBB: ${MANIFOLD_USE_BUILTIN_TBB}")
|
||||
message(
|
||||
STATUS
|
||||
"MANIFOLD_USE_BUILTIN_CLIPPER2: ${MANIFOLD_USE_BUILTIN_CLIPPER2}"
|
||||
)
|
||||
message(
|
||||
STATUS
|
||||
"MANIFOLD_USE_BUILTIN_NANOBIND: ${MANIFOLD_USE_BUILTIN_NANOBIND}"
|
||||
)
|
||||
message(STATUS "MANIFOLD_FUZZ: ${MANIFOLD_FUZZ}")
|
||||
message(STATUS "TRACY_ENABLE: ${TRACY_ENABLE}")
|
||||
message(STATUS "TRACY_MEMORY_USAGE: ${TRACY_MEMORY_USAGE}")
|
||||
message(STATUS " ")
|
||||
@@ -0,0 +1,12 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
|
||||
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
||||
|
||||
Name: manifold@PCFILE_LIB_SUFFIX@
|
||||
Description: Geometry library for topological robustness
|
||||
Version: @MANIFOLD_VERSION@
|
||||
URL: https://github.com/elalish/manifold
|
||||
Requires-private: @TEMPLATE_OPTIONAL_TBB@ @TEMPLATE_OPTIONAL_CLIPPER@
|
||||
Libs: -L${libdir} -lmanifold@PCFILE_LIB_SUFFIX@
|
||||
Cflags: -I${includedir} -std=c++17
|
||||
@@ -0,0 +1,29 @@
|
||||
# Compute the installation prefix relative to this file, so we can have the
|
||||
# subsequent find_package calls first check for bundled dependencies.
|
||||
get_filename_component(_FIND_ROOT "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
# CMAKE_CURRENT_LIST_FILE path is several directories below the path we want
|
||||
# for _FIND_ROOT, so we need multiple get_filename_component calls to trim it
|
||||
# back to the the root path.
|
||||
get_filename_component(_FIND_ROOT "${_FIND_ROOT}" PATH)
|
||||
get_filename_component(_FIND_ROOT "${_FIND_ROOT}" PATH)
|
||||
get_filename_component(_FIND_ROOT "${_FIND_ROOT}" PATH)
|
||||
if(_FIND_ROOT STREQUAL "/")
|
||||
set(_FIND_ROOT "")
|
||||
endif()
|
||||
|
||||
set(MANIFOLD_CROSS_SECTION "@MANIFOLD_CROSS_SECTION@")
|
||||
set(MANIFOLD_USE_BUILTIN_CLIPPER2 "@MANIFOLD_USE_BUILTIN_CLIPPER2@")
|
||||
if(MANIFOLD_CROSS_SECTION AND NOT MANIFOLD_USE_BUILTIN_CLIPPER2)
|
||||
set(Clipper2_ROOT "${_FIND_ROOT}")
|
||||
find_package(Clipper2 REQUIRED)
|
||||
endif()
|
||||
set(MANIFOLD_PAR "@MANIFOLD_PAR@")
|
||||
set(MANIFOLD_USE_BUILTIN_TBB "@MANIFOLD_USE_BUILTIN_TBB@")
|
||||
if(MANIFOLD_PAR STREQUAL "ON" AND NOT MANIFOLD_USE_BUILTIN_TBB)
|
||||
find_package(TBB REQUIRED)
|
||||
endif()
|
||||
set(MANIFOLD_EXPORT "@MANIFOLD_EXPORT@")
|
||||
if(MANIFOLD_EXPORT)
|
||||
find_package(assimp REQUIRED)
|
||||
endif(MANIFOLD_EXPORT)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/manifoldTargets.cmake")
|
||||
@@ -0,0 +1,234 @@
|
||||
# Copyright 2024 The Manifold Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
include(FetchContent)
|
||||
|
||||
function(logmissingdep PKG)
|
||||
# if this is already in FETCHCONTENT_SOURCE_DIR_X, we don't have to
|
||||
# download...
|
||||
if(DEFINED FETCHCONTENT_SOURCE_DIR_${PKG})
|
||||
return()
|
||||
endif()
|
||||
if(NOT MANIFOLD_DOWNLOADS)
|
||||
if(ARGC EQUAL 3)
|
||||
set(MSG "${ARGV2} enabled, but ${PKG} was not found ")
|
||||
string(APPEND MSG "and dependency downloading is disabled. ")
|
||||
else()
|
||||
set(MSG "${PKG} not found, and dependency downloading disabled. ")
|
||||
endif()
|
||||
string(APPEND MSG "Please install ${PKG} and reconfigure.\n")
|
||||
message(FATAL_ERROR ${MSG})
|
||||
else()
|
||||
message(STATUS "${PKG} not found, downloading from source")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(OLD_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
|
||||
# we build fetched dependencies as static library
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
# If we're building parallel, we need tbb
|
||||
if(MANIFOLD_PAR)
|
||||
find_package(Threads REQUIRED)
|
||||
if(NOT MANIFOLD_USE_BUILTIN_TBB)
|
||||
find_package(TBB QUIET)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(NOT TBB_FOUND AND PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(TBB tbb)
|
||||
endif()
|
||||
endif()
|
||||
if(TBB_FOUND)
|
||||
if(NOT TARGET TBB::tbb)
|
||||
if(NOT TARGET tbb)
|
||||
add_library(TBB::tbb SHARED IMPORTED)
|
||||
set_property(
|
||||
TARGET TBB::tbb
|
||||
PROPERTY IMPORTED_LOCATION ${TBB_LINK_LIBRARIES}
|
||||
)
|
||||
target_include_directories(TBB::tbb INTERFACE ${TBB_INCLUDE_DIRS})
|
||||
else()
|
||||
add_library(TBB::tbb ALIAS tbb)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
logmissingdep("TBB" , "Parallel mode")
|
||||
set(MANIFOLD_USE_BUILTIN_TBB ON)
|
||||
set(TBB_TEST OFF CACHE INTERNAL "" FORCE)
|
||||
set(TBB_STRICT OFF CACHE INTERNAL "" FORCE)
|
||||
FetchContent_Declare(
|
||||
TBB
|
||||
GIT_REPOSITORY https://github.com/oneapi-src/oneTBB.git
|
||||
GIT_TAG v2022.0.0
|
||||
GIT_PROGRESS TRUE
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
FetchContent_MakeAvailable(TBB)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# If we're building cross_section, we need Clipper2
|
||||
if(MANIFOLD_CROSS_SECTION)
|
||||
if(NOT MANIFOLD_USE_BUILTIN_CLIPPER2 AND NOT Clipper2_FOUND)
|
||||
find_package(Clipper2 QUIET)
|
||||
if(NOT Clipper2_FOUND AND PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(Clipper2 Clipper2)
|
||||
if(Clipper2_FOUND)
|
||||
add_library(Clipper2 SHARED IMPORTED)
|
||||
set_property(
|
||||
TARGET Clipper2
|
||||
PROPERTY IMPORTED_LOCATION ${Clipper2_LINK_LIBRARIES}
|
||||
)
|
||||
if(WIN32)
|
||||
set_property(
|
||||
TARGET Clipper2
|
||||
PROPERTY IMPORTED_IMPLIB ${Clipper2_LINK_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
target_include_directories(Clipper2 INTERFACE ${Clipper2_INCLUDE_DIRS})
|
||||
endif(Clipper2_FOUND)
|
||||
endif(NOT Clipper2_FOUND AND PKG_CONFIG_FOUND)
|
||||
endif(NOT MANIFOLD_USE_BUILTIN_CLIPPER2 AND NOT Clipper2_FOUND)
|
||||
if(NOT Clipper2_FOUND)
|
||||
logmissingdep("Clipper2" , "cross_section")
|
||||
set(MANIFOLD_USE_BUILTIN_CLIPPER2 ON)
|
||||
set(CLIPPER2_UTILS OFF)
|
||||
set(CLIPPER2_EXAMPLES OFF)
|
||||
set(CLIPPER2_TESTS OFF)
|
||||
set(
|
||||
CLIPPER2_USINGZ
|
||||
OFF
|
||||
CACHE BOOL
|
||||
"Preempt cache default of USINGZ (we only use 2d)"
|
||||
)
|
||||
FetchContent_Declare(
|
||||
Clipper2
|
||||
GIT_REPOSITORY https://github.com/AngusJohnson/Clipper2.git
|
||||
# Nov 22, 2024
|
||||
GIT_TAG a8269cafe92cdbf92572bceda5e9fdacc4684b51
|
||||
GIT_PROGRESS TRUE
|
||||
SOURCE_SUBDIR CPP
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
FetchContent_MakeAvailable(Clipper2)
|
||||
endif()
|
||||
if(NOT TARGET Clipper2::Clipper2)
|
||||
add_library(Clipper2::Clipper2 ALIAS Clipper2)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(TRACY_ENABLE)
|
||||
logmissingdep("tracy" , "TRACY_ENABLE")
|
||||
FetchContent_Declare(
|
||||
tracy
|
||||
GIT_REPOSITORY https://github.com/wolfpld/tracy.git
|
||||
GIT_TAG v0.10
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
FetchContent_MakeAvailable(tracy)
|
||||
endif()
|
||||
|
||||
# If we're supporting mesh I/O, we need assimp
|
||||
if(MANIFOLD_EXPORT)
|
||||
find_package(assimp REQUIRED)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_PYBIND)
|
||||
if(Python_VERSION VERSION_LESS 3.12)
|
||||
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
|
||||
else()
|
||||
find_package(Python COMPONENTS Interpreter Development.SABIModule REQUIRED)
|
||||
endif()
|
||||
if(Python_VERSION VERSION_GREATER_EQUAL 3.11)
|
||||
set(MANIFOLD_PYBIND_STUBGEN ON)
|
||||
else()
|
||||
# stubgen does not support version less than 3.11
|
||||
set(MANIFOLD_PYBIND_STUBGEN OFF)
|
||||
message("Python version too old, stub will not be generated")
|
||||
endif()
|
||||
|
||||
if(NOT MANIFOLD_USE_BUILTIN_NANOBIND)
|
||||
execute_process(
|
||||
COMMAND "${Python_EXECUTABLE}" -m nanobind --version
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
OUTPUT_VARIABLE NB_VERSION
|
||||
)
|
||||
endif()
|
||||
# we are fine with 2.0.0
|
||||
if(NB_VERSION VERSION_GREATER_EQUAL 2.0.0)
|
||||
message("Found nanobind, version ${NB_VERSION}")
|
||||
execute_process(
|
||||
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
OUTPUT_VARIABLE nanobind_ROOT
|
||||
)
|
||||
find_package(nanobind CONFIG REQUIRED)
|
||||
else()
|
||||
logmissingdep("nanobind" , "MANIFOLD_PYBIND")
|
||||
set(MANIFOLD_USE_BUILTIN_NANOBIND ON)
|
||||
FetchContent_Declare(
|
||||
nanobind
|
||||
GIT_REPOSITORY https://github.com/wjakob/nanobind.git
|
||||
GIT_TAG
|
||||
784efa2a0358a4dc5432c74f5685ee026e20f2b6 # v2.2.0
|
||||
GIT_PROGRESS TRUE
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
FetchContent_MakeAvailable(nanobind)
|
||||
set(NB_VERSION 2.2.0)
|
||||
endif()
|
||||
|
||||
if(NB_VERSION VERSION_LESS 2.1.0)
|
||||
message("Nanobind version too old, stub will not be generated")
|
||||
set(MANIFOLD_PYBIND_STUBGEN OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_TEST)
|
||||
find_package(GTest QUIET)
|
||||
if(NOT GTest_FOUND)
|
||||
logmissingdep("GTest" , "MANIFOLD_TEST")
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
# Prevent installation of GTest with your project
|
||||
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
|
||||
set(INSTALL_GMOCK OFF CACHE BOOL "" FORCE)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG v1.14.0
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
FIND_PACKAGE_ARGS NAMES GTest gtest
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
endif()
|
||||
if(NOT TARGET GTest::gtest_main)
|
||||
add_library(GTest::gtest_main ALIAS gtest_main)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_FUZZ)
|
||||
logmissingdep("fuzztest" , "MANIFOLD_FUZZ")
|
||||
FetchContent_Declare(
|
||||
fuzztest
|
||||
GIT_REPOSITORY https://github.com/google/fuzztest.git
|
||||
GIT_TAG 7b107216fa6cd62659234366aee493d6f8832d46
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(fuzztest)
|
||||
endif()
|
||||
|
||||
set(BUILD_SHARED_LIBS ${OLD_BUILD_SHARED_LIBS})
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MANIFOLD_VERSION_MAJOR @MANIFOLD_VERSION_MAJOR@
|
||||
#define MANIFOLD_VERSION_MINOR @MANIFOLD_VERSION_MINOR@
|
||||
#define MANIFOLD_VERSION_PATCH @MANIFOLD_VERSION_PATCH@
|
||||
|
||||
// Comparable version numbers to check version in code or preprocessing.
|
||||
// Check if your minimum requirements are met with e.g.
|
||||
// MANIFOLD_VERSION > MANIFOLD_VERSION_NUMBER(2, 5, 1)
|
||||
#define MANIFOLD_VERSION_NUMBER(v_major, v_minor, v_patch) \
|
||||
(((v_major) * 1000000) + ((v_minor) * 1000) + (v_patch))
|
||||
|
||||
#define MANIFOLD_VERSION MANIFOLD_VERSION_NUMBER(MANIFOLD_VERSION_MAJOR, \
|
||||
MANIFOLD_VERSION_MINOR, \
|
||||
MANIFOLD_VERSION_PATCH)
|
||||