Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e2292bb24 | ||
|
|
e047ba6362 | ||
|
|
4f7c1e4024 | ||
|
|
ae57329b67 | ||
|
|
3124ad45e4 | ||
|
|
3a29e9566f | ||
|
|
7d5d78b957 | ||
|
|
bf93b97221 | ||
|
|
34afe21c31 | ||
|
|
b4e0dbb763 | ||
|
|
4b4eb9a8ec | ||
|
|
9457a4e889 | ||
|
|
f1962a723c | ||
|
|
56f2b6730b | ||
|
|
e4c8defdff | ||
|
|
dad5deeccc | ||
|
|
76208dc02b | ||
|
|
6754a3cf7c | ||
|
|
73f3afdfc2 | ||
|
|
39e3cd3f6e | ||
|
|
c18032c31e | ||
|
|
83ddd27063 | ||
|
|
8610651942 | ||
|
|
da4997bd29 | ||
|
|
52c5ef592e | ||
|
|
8c63b864d8 | ||
|
|
87b8167659 | ||
|
|
982c339abc | ||
|
|
3a83b83ca3 | ||
|
|
75dc3219fa | ||
|
|
90cab6e2bb | ||
|
|
186dc69f52 | ||
|
|
d013ddec32 | ||
|
|
182188b805 | ||
|
|
2f4741e0b1 | ||
|
|
023bb59be9 | ||
|
|
2984626cb2 | ||
|
|
551e1a12cf | ||
|
|
2cf3dfd0ec | ||
|
|
b252782489 | ||
|
|
39934bca55 | ||
|
|
1d18e60d74 | ||
|
|
061c5e7a48 | ||
|
|
e3ff2a0019 | ||
|
|
3027282259 | ||
|
|
1ec20369ca | ||
|
|
cd57073179 | ||
|
|
3ba3175f2c | ||
|
|
5145d3422b | ||
|
|
41d8d97d7c | ||
|
|
54baa62b4a | ||
|
|
f3a6a477da | ||
|
|
994814921e | ||
|
|
138815a127 | ||
|
|
3881cae9c5 | ||
|
|
85b0cd211c | ||
|
|
9756892b09 | ||
|
|
3015a81c07 | ||
|
|
272a22439f | ||
|
|
a863296cdd | ||
|
|
4ba67b8fcf | ||
|
|
09fb03cff2 | ||
|
|
5f94740be0 | ||
|
|
b8dd573a9c | ||
|
|
63a36e678a | ||
|
|
7c2b7b407b | ||
|
|
29d2b1dbe9 | ||
|
|
90f83a98be | ||
|
|
ca7d6c326c | ||
|
|
00a589dafe | ||
|
|
7c8fbe186a | ||
|
|
4fabfc327b | ||
|
|
75b592f38c | ||
|
|
444399393a | ||
|
|
2a4e9bae06 | ||
|
|
bdb0b880c9 | ||
|
|
33a69413c2 | ||
|
|
4edd442297 | ||
|
|
beb9ca6122 | ||
|
|
fab833e7e1 | ||
|
|
1f188036d1 | ||
|
|
808c261b33 | ||
|
|
bc70f58990 | ||
|
|
d4439bccec | ||
|
|
8e34f97194 | ||
|
|
954df798b2 | ||
|
|
0451671bd3 | ||
|
|
cba9e35bb1 | ||
|
|
5399bd4760 | ||
|
|
06b10e7149 | ||
|
|
f178cd1d73 | ||
|
|
5ae22f0d57 | ||
|
|
a97b150bb5 | ||
|
|
07885cb51b | ||
|
|
c16b521193 | ||
|
|
c149dd5088 | ||
|
|
a857f008dd | ||
|
|
f2da5eab66 | ||
|
|
447921cf97 | ||
|
|
ac82767927 | ||
|
|
56fec978c6 | ||
|
|
ae813ddecb | ||
|
|
1b3dbc29e2 | ||
|
|
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 | ||
|
|
cb30480865 | ||
|
|
6ade357e4b | ||
|
|
80a81b7641 | ||
|
|
e06765346e | ||
|
|
bd52d301ff | ||
|
|
370375ebf0 | ||
|
|
9d3fd48b01 | ||
|
|
451829f1d1 | ||
|
|
3febd48dcd | ||
|
|
658d3e037a | ||
|
|
cc1e97442a | ||
|
|
5719926db8 | ||
|
|
3c8d486e36 | ||
|
|
5a157a07fe | ||
|
|
acb4f8c5e4 | ||
|
|
5cab42f165 | ||
|
|
ea91395878 | ||
|
|
4d7613bd4f | ||
|
|
9f62dbb048 | ||
|
|
31d6da0973 | ||
|
|
359274d01b | ||
|
|
a24cca1f06 | ||
|
|
c1d96a6404 | ||
|
|
20a8f3d15d | ||
|
|
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 | ||
|
|
ce5d758f32 | ||
|
|
b817d07a9d | ||
|
|
85d71f5cef | ||
|
|
ab1a96a96a | ||
|
|
9a9f5a21d9 | ||
|
|
44a999a75e | ||
|
|
a97f6e94b0 | ||
|
|
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 | ||
|
|
edb4b25f64 | ||
|
|
5301542254 | ||
|
|
a122bde778 | ||
|
|
c9810fd517 | ||
|
|
4c57a56355 | ||
|
|
cdcbd329e6 | ||
|
|
c738f1eea1 | ||
|
|
a8b8bf71b1 | ||
|
|
c60df1b704 | ||
|
|
8d67ba3a3a | ||
|
|
3258152e2c | ||
|
|
78729599a5 | ||
|
|
32eefe9157 | ||
|
|
3e05284250 | ||
|
|
c9ec16969d | ||
|
|
9b1d255931 | ||
|
|
f9d363497b | ||
|
|
95f40d1d14 | ||
|
|
2001b183f8 | ||
|
|
03fbf27478 | ||
|
|
0d93550ac5 | ||
|
|
2e59ba5da6 | ||
|
|
095dc65159 | ||
|
|
9cec6cd2e4 | ||
|
|
cc153b21d3 | ||
|
|
557effe165 | ||
|
|
ebd8fa2357 | ||
|
|
defb89b7c3 | ||
|
|
7f0c586629 | ||
|
|
22d94d5688 | ||
|
|
e073141f14 | ||
|
|
f54e535c6a | ||
|
|
c6801c82ea | ||
|
|
a67aa13133 | ||
|
|
d43709715d | ||
|
|
2ddbd0a1a0 | ||
|
|
dc4afa950b | ||
|
|
c03a5ac3ad | ||
|
|
cae5a01af1 | ||
|
|
d59b9b93bd | ||
|
|
f3c5ada9f0 | ||
|
|
4395ab622c | ||
|
|
7cbb4fc517 | ||
|
|
994956d7c3 | ||
|
|
79f735c59a | ||
|
|
dd67e992bd | ||
|
|
5bdb8bc233 | ||
|
|
c9ae400647 | ||
|
|
9c1517e54e | ||
|
|
3b8282e1d5 | ||
|
|
1635864cbc | ||
|
|
65b2ef8198 | ||
|
|
a9055a8abf | ||
|
|
0b7ee87489 | ||
|
|
9690305b8d | ||
|
|
e7ca1e33d3 | ||
|
|
81464d30b1 | ||
|
|
aa88959899 | ||
|
|
dbb0ca76b4 | ||
|
|
9018b2ac45 | ||
|
|
f407e43e5b | ||
|
|
d7cbcb8c2e | ||
|
|
e27c9c100f | ||
|
|
8ad5f8e427 | ||
|
|
afc64f248a | ||
|
|
c0fb3766e4 | ||
|
|
07f860f6ad | ||
|
|
887a5021af | ||
|
|
e83b9bd158 | ||
|
|
2d0c705839 | ||
|
|
b36ec2ce0e | ||
|
|
b8d143dd72 | ||
|
|
be022a58e4 | ||
|
|
30e0af942a | ||
|
|
8c77a6aa05 | ||
|
|
54fa822408 | ||
|
|
1d1ef6393a | ||
|
|
7d564cfbc1 | ||
|
|
bc0bded294 | ||
|
|
d52decc4f1 | ||
|
|
8060376e12 | ||
|
|
19eccdad39 | ||
|
|
d8c6509876 | ||
|
|
07ef876ac2 | ||
|
|
412b5cd937 | ||
|
|
c8a792a7a0 | ||
|
|
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 | ||
|
|
dbff2eb012 | ||
|
|
4b5ead381e | ||
|
|
8550f0546c | ||
|
|
59ab7c443d | ||
|
|
0856539bfc | ||
|
|
621835683f | ||
|
|
bec8164120 | ||
|
|
062399c1c7 | ||
|
|
d51ee709c2 | ||
|
|
206871ac11 | ||
|
|
2055b9c412 | ||
|
|
5e103931f1 | ||
|
|
77051ead35 | ||
|
|
83b78f8356 | ||
|
|
6c6193c25b | ||
|
|
e79d343bec | ||
|
|
1d6a89ac87 | ||
|
|
8651bf8bd5 | ||
|
|
1514c15184 | ||
|
|
cc83e1b7aa | ||
|
|
111350f1ef | ||
|
|
b4f583fcae | ||
|
|
8011fb6072 | ||
|
|
9290d420b0 | ||
|
|
fca66380f5 | ||
|
|
8497e44915 | ||
|
|
a7eac92087 | ||
|
|
15399474fa | ||
|
|
2d513446f7 | ||
|
|
abff7e8ed3 | ||
|
|
7c454dc7a4 | ||
|
|
ffdd97d4d3 | ||
|
|
7facd0ecc0 | ||
|
|
59760b3369 | ||
|
|
5f9b484b6c | ||
|
|
18edefc6c4 | ||
|
|
93e06a2de8 | ||
|
|
047159e2c4 | ||
|
|
a8626f907c | ||
|
|
2f22b92352 | ||
|
|
a3d7991563 | ||
|
|
2aef6e4728 | ||
|
|
c167a48d36 | ||
|
|
da3f323113 | ||
|
|
8b7bf4231b | ||
|
|
ac2cfd857f | ||
|
|
285bb0754b | ||
|
|
ba5fead6d5 | ||
|
|
96c57c0591 | ||
|
|
4b65ff3afe | ||
|
|
709021d9d2 | ||
|
|
6cec951aed | ||
|
|
dd3445ddb1 | ||
|
|
37db0d0182 | ||
|
|
1b9243df2a | ||
|
|
8b4c79c485 | ||
|
|
7a197af83d | ||
|
|
a65fedb2f9 | ||
|
|
6ae9dee640 | ||
|
|
79daba1200 | ||
|
|
2f8e3174f7 | ||
|
|
cce35aa5a0 | ||
|
|
e727b115c0 | ||
|
|
f6431112a8 | ||
|
|
2643c291ed | ||
|
|
e024edf103 | ||
|
|
cbfeed1672 | ||
|
|
9dac47a76a | ||
|
|
eb41b9d36a | ||
|
|
72935213e8 | ||
|
|
e1ebc66425 | ||
|
|
ef52b0cce2 | ||
|
|
812ca5c30f | ||
|
|
f064b2a5fd | ||
|
|
18abd3ffcf | ||
|
|
1615b72e83 | ||
|
|
58fb7e3300 | ||
|
|
1959a30560 | ||
|
|
4bdb761d5e | ||
|
|
ba042ee8db | ||
|
|
97317888bc | ||
|
|
a9bf836574 | ||
|
|
29dc753513 | ||
|
|
3a517a490e | ||
|
|
aeba26760b | ||
|
|
85d74b4ccf | ||
|
|
a5f92e2522 | ||
|
|
fbe4cc6a86 | ||
|
|
f25d9601cb | ||
|
|
07513bd951 | ||
|
|
9a795162f4 | ||
|
|
cdce9e7618 | ||
|
|
628edc8e26 | ||
|
|
d209ead1c2 | ||
|
|
112f9ba501 | ||
|
|
2a63971983 | ||
|
|
4962ebf879 | ||
|
|
d713986f62 | ||
|
|
3b72852b0f | ||
|
|
e17eb038bf | ||
|
|
7b0758eddc | ||
|
|
20178cb209 | ||
|
|
61d87c503c | ||
|
|
7c2007771e | ||
|
|
d82cf061f8 | ||
|
|
dca9cce529 | ||
|
|
754a4811a5 | ||
|
|
83131c672e | ||
|
|
bd8da6ce1e | ||
|
|
3c736f9030 | ||
|
|
bb24367220 | ||
|
|
a97c666358 | ||
|
|
7c8ce3c935 | ||
|
|
4d434e4f8c | ||
|
|
9e9a22d66b | ||
|
|
893a4f8ac3 | ||
|
|
78c03f2611 | ||
|
|
2915f7aa90 | ||
|
|
95daa54591 | ||
|
|
2c597d1eef | ||
|
|
e2a8e94906 | ||
|
|
eae9653745 | ||
|
|
feb7fb6edd | ||
|
|
32f887d321 | ||
|
|
3a962cfa53 | ||
|
|
36ca7acf6c | ||
|
|
0704f62eaa | ||
|
|
5041c38424 | ||
|
|
c4bf392f66 | ||
|
|
3f42e5e6d7 | ||
|
|
376a7c676f | ||
|
|
e6bb87b2bb | ||
|
|
9477deaa9c | ||
|
|
02affd5247 | ||
|
|
171fe5466e | ||
|
|
5331b68485 | ||
|
|
20d7ef96fb | ||
|
|
3560bbf679 | ||
|
|
4415cfce46 | ||
|
|
bbb1fabcac | ||
|
|
f43dded234 | ||
|
|
05f9ceb999 | ||
|
|
422c7f26d9 | ||
|
|
a701d2be8e | ||
|
|
5b5f56674a | ||
|
|
1b7b365c3a | ||
|
|
812b056179 | ||
|
|
bd46bfd1cf | ||
|
|
a7b457db74 | ||
|
|
87059508e7 |
@@ -1 +1,4 @@
|
||||
BasedOnStyle: Google
|
||||
PointerAlignment: Left
|
||||
ReferenceAlignment: Left
|
||||
DerivePointerAlignment: false
|
||||
@@ -0,0 +1,2 @@
|
||||
line_length: 80
|
||||
indent: 2
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -0,0 +1,66 @@
|
||||
name: build_wheels
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_wheels:
|
||||
name: Build wheels on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
# macos-13: x86-64
|
||||
# macos-14: arm64
|
||||
os: [ubuntu-22.04, ubuntu-22.04-arm, windows-2025, macos-14, macos-13]
|
||||
steps:
|
||||
- run: |
|
||||
git config --global submodule.fetchJobs 8
|
||||
git config --global core.longpaths true
|
||||
- if: matrix.os == 'macos-14'
|
||||
run: echo "CIBW_ARCHS=arm64 universal2" >> "$GITHUB_ENV"
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v2.23.0
|
||||
- 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: |
|
||||
# FIXME: why doesn't scikit_build_core automatically download ninja?
|
||||
sudo apt-get update
|
||||
sudo apt-get install ninja-build
|
||||
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
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: python-*
|
||||
merge-multiple: true
|
||||
path: dist
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
@@ -8,14 +8,26 @@ on:
|
||||
|
||||
jobs:
|
||||
check_format:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: DoozyX/clang-format-lint-action@v0.14
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DoozyX/clang-format-lint-action@v0.20
|
||||
with:
|
||||
source: '.'
|
||||
exclude: '*/third_party'
|
||||
extensions: 'h,cpp'
|
||||
clangFormatVersion: 12
|
||||
extensions: 'h,cpp,js,ts,html'
|
||||
clangFormatVersion: 20
|
||||
- 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,35 +1,47 @@
|
||||
name: Deploy documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_run:
|
||||
workflows: [CI]
|
||||
types:
|
||||
- completed
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
deploy_github_pages:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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: |
|
||||
mkdir public
|
||||
mv ./samples ./public
|
||||
mv ./docs ./public
|
||||
mv ./bindings/wasm/examples/* ./public
|
||||
|
||||
- name: Download WASM files
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
- name: Download built examples
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
pr: ${{github.event.pull_request.number}}
|
||||
workflow_conclusion: completed
|
||||
# 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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
@@ -10,185 +10,341 @@ 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:
|
||||
cuda_support: [ON, OFF]
|
||||
parallel_backend: [NONE, OMP, TBB]
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event.pull_request.draft == false
|
||||
container:
|
||||
image: docker://nvidia/cuda:11.6.0-devel-ubuntu20.04
|
||||
cross_section: [ON]
|
||||
parallelization: [OFF, ON]
|
||||
os: [ubuntu-24.04]
|
||||
gcc: [13, 14]
|
||||
include:
|
||||
- cross_section: OFF
|
||||
parallelization: ON
|
||||
os: ubuntu-24.04
|
||||
gcc: 14
|
||||
- cross_section: ON
|
||||
parallelization: ON
|
||||
os: ubuntu-22.04
|
||||
gcc: 11
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CC: gcc-${{ matrix.gcc }}
|
||||
CXX: g++-${{ matrix.gcc }}
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
|
||||
pip install trimesh
|
||||
- uses: actions/checkout@v3
|
||||
sudo apt-get update
|
||||
sudo apt-get install 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
|
||||
with:
|
||||
submodules: true
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build ${{matrix.backend}}
|
||||
cmake-version: 4.0.2
|
||||
- name: Build ${{matrix.parallelization}}
|
||||
if: matrix.parallelization != 'OFF'
|
||||
run: |
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
# note that the test for CUDA backend does not really test CUDA, as we
|
||||
# don't have CUDA GPU on GitHub Action
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_ASSERT=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 ../../
|
||||
python3 bindings/python/examples/run_all.py
|
||||
- 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' && matrix.cuda_support == 'OFF'
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_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' && matrix.cuda_support == 'OFF'
|
||||
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}}-${{matrix.cuda_support}}
|
||||
name: ${{matrix.parallelization}}
|
||||
token: ${{secrets.CODECOV_TOKEN}}
|
||||
verbose: true
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd build
|
||||
sudo cmake --install .
|
||||
cd ..
|
||||
./scripts/test-cmake.sh
|
||||
|
||||
build_wasm:
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event.pull_request.draft == false
|
||||
name: WASM (TBB:${{matrix.parallelization}}, DEBUG:${{matrix.debug}})
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
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
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- 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
|
||||
git apply thrust.diff
|
||||
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}} -DMANIFOLD_ASSERT=${{matrix.debug}} .. && emmake make
|
||||
- name: Test WASM
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
cd build/test
|
||||
node ./manifold_test.js
|
||||
cd ../bindings/wasm
|
||||
npm install
|
||||
- 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: |
|
||||
build/bindings/wasm/manifold.js
|
||||
build/bindings/wasm/manifold.wasm
|
||||
build/bindings/wasm/manifold.d.ts
|
||||
retention-days: 7
|
||||
path: bindings/wasm/examples/dist/
|
||||
retention-days: 90
|
||||
overwrite: true
|
||||
|
||||
build_windows:
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE]
|
||||
cuda_support: [OFF]
|
||||
max-parallel: 1
|
||||
runs-on: windows-2019
|
||||
if: github.event.pull_request.draft == false
|
||||
build_fuzzer:
|
||||
name: fuzzer
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Install CUDA
|
||||
if: matrix.backend == 'CUDA'
|
||||
env:
|
||||
cuda: "11.3.0"
|
||||
visual_studio: "Visual Studio 16 2019"
|
||||
shell: powershell
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
Invoke-RestMethod https://raw.githubusercontent.com/ptheywood/cuda-cmake-github-actions/21811d232fc775fa8d44dddf7f8c5ce2cf8d2b81/scripts/actions/install_cuda_windows.ps1 -OutFile install_cuda_windows.ps1
|
||||
.\install_cuda_windows.ps1
|
||||
- name: nvcc check
|
||||
if: matrix.backend == 'CUDA'
|
||||
shell: powershell
|
||||
run: |
|
||||
nvcc -V
|
||||
ls $env:CUDA_PATH
|
||||
ls $env:CUDA_PATH\bin
|
||||
ls $env:CUDA_PATH\include
|
||||
- name: Build ${{matrix.backend}}
|
||||
shell: powershell
|
||||
run: |
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/test
|
||||
./Release/manifold_test.exe
|
||||
|
||||
build_mac:
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
runs-on: macos-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install OpenMP
|
||||
if: matrix.parallel_backend == 'OMP'
|
||||
run: brew install libomp
|
||||
- name: Install TBB
|
||||
if: matrix.parallel_backend == 'TBB'
|
||||
run: brew install tbb
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y libgtest-dev clang
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: Build
|
||||
run: |
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMANIFOLD_DEBUG=ON -DMANIFOLD_FUZZ=ON -DMANIFOLD_PYBIND=OFF -DCMAKE_CXX_COMPILER=clang++ ..
|
||||
make
|
||||
|
||||
build_fuzzer_mac:
|
||||
name: fuzzer mac
|
||||
timeout-minutes: 30
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install googletest
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: 3.31.6
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMANIFOLD_DEBUG=ON -DMANIFOLD_FUZZ=ON -DMANIFOLD_PYBIND=OFF -DCMAKE_CXX_COMPILER=clang++ -DMANIFOLD_PAR=OFF ..
|
||||
ASAN_OPTIONS=detect_container_overflow=0 make
|
||||
|
||||
build_windows:
|
||||
name: Windows (TBB:${{matrix.parallelization}}, SHARED:${{matrix.shared}})
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallelization: [OFF, ON]
|
||||
shared: [OFF, ON]
|
||||
runs-on: windows-2025
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: 4.0.2
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Build ${{matrix.parallelization}}
|
||||
shell: powershell
|
||||
run: |
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{matrix.shared}} -DMANIFOLD_DEBUG=ON -DMANIFOLD_ASSERT=ON -DMANIFOLD_PAR=${{matrix.parallelization}} -A x64 -B build
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test dll location
|
||||
if: matrix.shared == 'ON'
|
||||
shell: bash
|
||||
run: |
|
||||
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:
|
||||
parallelization: [OFF, ON]
|
||||
runs-on: ubuntu-latest
|
||||
container: openscad/mxe-x86_64-gui:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
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'}}, ${{matrix.os}})
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallelization: [OFF, ON]
|
||||
# macos-13: intel
|
||||
# macos-latest: arm
|
||||
os: [macos-13, macos-latest]
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install googletest assimp
|
||||
pip install trimesh pytest
|
||||
- name: Install TBB
|
||||
if: matrix.parallelization == 'ON'
|
||||
run: brew install tbb
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: 4.0.2
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_ASSERT=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
|
||||
with:
|
||||
cmake-version: 4.0.2
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_ASSERT=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
|
||||
with:
|
||||
cmake-version: 4.0.2
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd ..
|
||||
./manifold/scripts/test-cmake-subdir.sh
|
||||
|
||||
build_nix:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, 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
|
||||
- uses: cachix/install-nix-action@v15
|
||||
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- run: nix build -L '.?submodules=1#${{matrix.variant}}'
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -109,8 +108,30 @@ build
|
||||
buildWASM
|
||||
docs/html
|
||||
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
|
||||
.cache
|
||||
CMakeUserPresets.json
|
||||
CMakeSettings.json
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
|
||||
# Temporary files
|
||||
*~
|
||||
\#*\#
|
||||
.\#*
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-v][a-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Benchmark stl files
|
||||
extras/ember_tests/testfiles/raw_meshes
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
[submodule "third_party/thrust"]
|
||||
path = src/third_party/thrust
|
||||
url = https://github.com/NVIDIA/thrust
|
||||
[submodule "third_party/glm"]
|
||||
path = src/third_party/glm
|
||||
url = https://github.com/g-truc/glm
|
||||
[submodule "third_party/google_test"]
|
||||
path = test/third_party/google_test
|
||||
url = https://github.com/google/googletest.git
|
||||
[submodule "third_party/pybind11"]
|
||||
path = bindings/python/third_party/pybind11
|
||||
url = https://github.com/pybind/pybind11
|
||||
branch = stable
|
||||
@@ -1,26 +1,28 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "manifold test",
|
||||
"type": "lldb-dap",
|
||||
"MIMode": "lldb",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/test/manifold_test",
|
||||
"args": [
|
||||
"--gtest_catch_exceptions=0",
|
||||
"--gtest_filter=Manifold.Simplify"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/test",
|
||||
"environment": [
|
||||
{
|
||||
"name": "manifold test",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/test/manifold_test",
|
||||
"args": [
|
||||
"--gtest_break_on_failure",
|
||||
"--gtest_filter=Manifold.GetMesh"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/test",
|
||||
"environment": [
|
||||
{
|
||||
"name": "MALLOC_CHECK_",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
"name": "MALLOC_CHECK_",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
],
|
||||
"externalConsole": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -98,16 +98,39 @@
|
||||
"concepts": "cpp",
|
||||
"queue": "cpp",
|
||||
"stack": "cpp",
|
||||
"__hash_table": "cpp"
|
||||
"__hash_table": "cpp",
|
||||
"__std_stream": "cpp",
|
||||
"__verbose_abort": "cpp",
|
||||
"any": "cpp",
|
||||
"barrier": "cpp",
|
||||
"charconv": "cpp",
|
||||
"coroutine": "cpp",
|
||||
"latch": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"span": "cpp",
|
||||
"__sso_allocator": "cpp",
|
||||
"source_location": "cpp",
|
||||
"format": "cpp",
|
||||
"numbers": "cpp",
|
||||
"ranges": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"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_EXPORT=OFF",
|
||||
"-DMANIFOLD_CBIND=ON",
|
||||
"-DMANIFOLD_PYBIND=ON",
|
||||
"-DMANIFOLD_EXPORT=ON",
|
||||
"-DMANIFOLD_DEBUG=ON",
|
||||
"-DMANIFOLD_USE_CUDA=OFF",
|
||||
"-DMANIFOLD_PAR=NONE",
|
||||
"-DMANIFOLD_ASSERT=ON",
|
||||
"-DMANIFOLD_PAR=ON",
|
||||
"-DMANIFOLD_FUZZ=OFF",
|
||||
"-DCODE_COVERAGE=OFF",
|
||||
"-DCMAKE_CXX_FLAGS=''" //'-fsanitize=address,undefined'"
|
||||
],
|
||||
@@ -118,4 +141,26 @@
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"cSpell.words": [
|
||||
"Bary",
|
||||
"Coplanarity",
|
||||
"csaszar",
|
||||
"Gyroid",
|
||||
"halfedge",
|
||||
"halfedges",
|
||||
"Tris",
|
||||
"Verts",
|
||||
"Voxel",
|
||||
"voxels"
|
||||
],
|
||||
"javascript.format.semicolons": "insert",
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
},
|
||||
"python.formatting.provider": "none",
|
||||
"clang-format.executable": "clang-format",
|
||||
"clangd.arguments": [
|
||||
"--header-insertion=never"
|
||||
],
|
||||
"editor.defaultFormatter": "xaver.clang-format",
|
||||
}
|
||||
@@ -6,4 +6,5 @@
|
||||
# source control.
|
||||
Emmett Lalish <elalish>
|
||||
Chun Kit LAM <pca006132>
|
||||
Google LLC
|
||||
Geoff deRosenroll <geoffder>
|
||||
Google LLC
|
||||
|
||||
@@ -15,61 +15,311 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(manifold LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
# Use C++17
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
option(MANIFOLD_EXPORT off)
|
||||
option(MANIFOLD_DEBUG off)
|
||||
option(MANIFOLD_USE_CUDA off)
|
||||
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
include(GNUInstallDirs)
|
||||
include(CMakeDependentOption)
|
||||
|
||||
# Define Manifold version
|
||||
set(MANIFOLD_VERSION_MAJOR 3)
|
||||
set(MANIFOLD_VERSION_MINOR 2)
|
||||
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_ASSERT "Enable assertions - requires MANIFOLD_DEBUG" OFF)
|
||||
option(MANIFOLD_STRICT "Treat compile warnings as fatal build errors" ON)
|
||||
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)
|
||||
|
||||
# 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")
|
||||
|
||||
# Development options
|
||||
option(TRACY_ENABLE "Use tracy profiling" OFF)
|
||||
option(TRACY_MEMORY_USAGE "Track memory allocation with tracy (expensive)" 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)
|
||||
# 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)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
message("Building for Emscripten")
|
||||
set(MANIFOLD_FLAGS -fexceptions)
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
|
||||
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()
|
||||
|
||||
option(PYBIND11_FINDPYTHON on)
|
||||
|
||||
option(BUILD_TEST_CGAL off)
|
||||
|
||||
option(BUILD_SHARED_LIBS off)
|
||||
set(GLM_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/glm)
|
||||
set(PYBIND11_DIR ${PROJECT_SOURCE_DIR}/bindings/python/third_party/pybind11)
|
||||
set(THRUST_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/thrust)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
enable_language(CUDA)
|
||||
find_package(CUDA REQUIRED)
|
||||
|
||||
# we cannot set THRUST_INC_DIR when building with CUDA, otherwise the
|
||||
# compiler will not use the system CUDA headers which causes incompatibility
|
||||
# clear THRUST_INC_DIR, we use the one from nvcc
|
||||
set(THRUST_INC_DIR "")
|
||||
set(MANIFOLD_NVCC_RELEASE_FLAGS -O3 -lineinfo)
|
||||
set(MANIFOLD_NVCC_DEBUG_FLAGS -G)
|
||||
set(MANIFOLD_NVCC_FLAGS -Xcudafe --diag_suppress=esa_on_defaulted_function_ignored --extended-lambda --expt-relaxed-constexpr
|
||||
"$<$<CONFIG:RELEASE>:${MANIFOLD_NVCC_RELEASE_FLAGS}>"
|
||||
"$<$<CONFIG:DEBUG>:${MANIFOLD_NVCC_DEBUG_FLAGS}>")
|
||||
if(CMAKE_EXPORT_COMPILE_COMMANDS AND NOT EMSCRIPTEN)
|
||||
# for nixos
|
||||
set(
|
||||
CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
|
||||
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT MSVC)
|
||||
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused)
|
||||
add_compile_options(
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${WARNING_FLAGS}>"
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=${WARNING_FLAGS}>")
|
||||
if(MSVC)
|
||||
list(APPEND MANIFOLD_FLAGS /DNOMINMAX /bigobj)
|
||||
else()
|
||||
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 and
|
||||
# MANIFOLD_STRICT is on
|
||||
if(MANIFOLD_STRICT)
|
||||
list(APPEND WARNING_FLAGS -Werror)
|
||||
endif()
|
||||
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(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
# disable fp math optimizations, e.g. FMA for supported architectures, as
|
||||
# this changes floating-point results
|
||||
# clang defaults to -ffp-contract=off so we don't have to set that
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag(-ffp-contract=off HAS_FP_CONTRACT)
|
||||
check_cxx_compiler_flag(-fexcess-precision=standard HAS_FP_PRECISION)
|
||||
if(HAS_FP_CONTRACT)
|
||||
list(APPEND MANIFOLD_FLAGS -ffp-contract=off)
|
||||
endif()
|
||||
if(HAS_FP_PRECISION)
|
||||
list(APPEND MANIFOLD_FLAGS -fexcess-precision=standard)
|
||||
endif()
|
||||
endif()
|
||||
if(CODE_COVERAGE)
|
||||
list(
|
||||
APPEND
|
||||
MANIFOLD_FLAGS
|
||||
-coverage
|
||||
-fno-inline-small-functions
|
||||
-fkeep-inline-functions
|
||||
-fkeep-static-functions
|
||||
)
|
||||
add_link_options(-coverage)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CODE_COVERAGE AND NOT MSVC)
|
||||
set(COVERAGE_FLAGS -coverage -fno-inline-small-functions -fkeep-inline-functions -fkeep-static-functions)
|
||||
add_compile_options(
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${COVERAGE_FLAGS}>"
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=-coverage>")
|
||||
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(${PROJECT_SOURCE_DIR}/cmake/manifoldDeps.cmake)
|
||||
include(${PROJECT_SOURCE_DIR}/cmake/configHelper.cmake)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(extras)
|
||||
add_subdirectory(bindings)
|
||||
add_subdirectory(bindings)
|
||||
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,55 +1,151 @@
|
||||
# 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)
|
||||
|
||||
## [ManifoldCAD.org](https://manifoldcad.org)
|
||||
[**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)
|
||||
|
||||
If you like OpenSCAD / OpenJSCAD, you might also like ManifoldCAD - our own solid modelling web app. The operations are not GPU-accelerated, but it's still pretty fast and a good way to test out our Manifold library.
|
||||
[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
|
||||
|
||||
# Manifold
|
||||
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.
|
||||
|
||||
[**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)
|
||||
| | | |
|
||||
| --- | --- | --- |
|
||||
| [OpenSCAD](https://openscad.org/) | [Blender](https://www.blender.org/) | [IFCjs](https://ifcjs.github.io/info/) |
|
||||
| [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) | [PythonOpenSCAD](https://github.com/owebeeone/pythonopenscad) |
|
||||
| [Conversation](https://james-bern.github.io/conversation.html) | [AnchorSCAD](https://github.com/owebeeone/anchorscad-core) | [Dactyl Web Configurator](https://github.com/rianadon/dactyl-configurator) |
|
||||
| [Arcol](https://arcol.io) | [Bento3D](https://bento3d.design) | [Cadova](https://github.com/tomasf/Cadova) |
|
||||
|
||||
[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).
|
||||
### Bindings & Packages
|
||||
|
||||
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm) and Python to make this library more portable and easy to use. We have only three dependencies (all built-in header-only libraries): graphlite provides a connected components algorithm, GLM is a compact vector library, and Thrust is Nvidia's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms). We make use of submodules to ensure compatibility.
|
||||
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).
|
||||
|
||||
## What's here
|
||||
| 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 |
|
||||
| Swift | SPM | [Manifold-Swift](https://github.com/tomasf/manifold-swift) | external |
|
||||
|
||||
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.
|
||||
## Frontend Sandboxes
|
||||
|
||||
### [ManifoldCAD.org](https://manifoldcad.org)
|
||||
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app where you script in JS/TS. This uses our npm package, [manifold-3d](https://www.npmjs.com/package/manifold-3d), built via WASM. It's not quite as fast as our raw C++, but it's hard to beat for interoperability.
|
||||
|
||||
### [Python Colab Example](https://colab.research.google.com/drive/1VxrFYHPSHZgUbl9TeWzCeovlpXrPQ5J5?usp=sharing)
|
||||
|
||||
If you prefer Python to JS/TS, make your own copy of the example notebook above. It demonstrates interop between our [`manifold3d`](https://pypi.org/project/manifold3d/) PyPI library and the popular [`trimesh`](https://pypi.org/project/trimesh/) library, including showing the interactive model right in the notebook and saving 3D model output.
|
||||
|
||||

|
||||
|
||||
## Manifold Library
|
||||
|
||||
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as a level set function for evaluating signed-distance functions (SDF) that improves significantly over Marching Cubes. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. We provide a [`Merge`](https://manifoldcad.org/docs/html/structmanifold_1_1_mesh_g_l_p.html) function to fix slightly non-manifold meshes, but in general you may need one of the automated repair tools that exist mostly for 3D printing.
|
||||
|
||||
The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
|
||||
|
||||
To aid in speed, this library makes extensive use of parallelization, generally through Nvidia's Thrust library. You can switch between the CUDA, OMP and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled for CUDA, the code will still run without a GPU, falling back to the serial version of the algorithms. The WASM build is serial-only for now, but still fast.
|
||||
Manifold has full support for arbitrary vertex properties, and also has IDs that make it easy to keep track of materials and what surfaces belong to what input objects or faces. See our [web example](https://manifoldcad.org/model-viewer.html) for a simple demonstration of combining objects with unique textures.
|
||||
|
||||
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.
|
||||
Only CMake, a C++ compiler, and Python are required to be installed and set up to build this library (it has been tested with GCC, LLVM, MSVC). However, a variety of optional dependencies can bring in more functionality, see below.
|
||||
|
||||
Build and test (Ubuntu or similar):
|
||||
```
|
||||
git clone --recurse-submodules https://github.com/elalish/manifold.git
|
||||
cd manifold
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. && make
|
||||
test/manifold_test
|
||||
make test
|
||||
```
|
||||
|
||||
CMake flags (usage e.g. `-DMANIFOLD_USE_CUDA=ON`):
|
||||
- `MANIFOLD_USE_CUDA=[<OFF>, ON]`: Provides Nvidia GPU parallelization, requires CUDA Toolkit and its nvcc compiler.
|
||||
- `MANIFOLD_PAR=[<NONE>, OMP, TBB]`: Provides multi-thread parallelization, requires `libomp-dev` or `libtbb-dev`.
|
||||
- `MANIFOLD_EXPORT=[<OFF>, ON]`: Enables GLB export of 3D models from the tests, requires `libassimp-dev`.
|
||||
- `MANIFOLD_DEBUG=[<OFF>, ON]`: Enables internal assertions and exceptions.
|
||||
- `BUILD_TEST_CGAL=[<OFF>, ON]`: Builds a CGAL-based performance [comparison](https://github.com/elalish/manifold/tree/master/extras), requires `libcgal-dev`.
|
||||
CMake flags (usage e.g. `-DMANIFOLD_DEBUG=ON`):
|
||||
- `MANIFOLD_JSBIND=[OFF, <ON>]`: Build js binding (when using the emscripten toolchain).
|
||||
- `MANIFOLD_CBIND=[<OFF>, ON]`: Build C FFI binding.
|
||||
- `MANIFOLD_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 exceptions, timing, verbosity, OBJ test dumps. Has almost no effect on its own, but enables further runtime parameters to dump various outputs.
|
||||
- `MANIFOLD_ASSERT=[<OFF>, ON]`: Enables internal assertions. This incurs around 20% runtime overhead. Requires MANIFOLD_DEBUG to work.
|
||||
- `MANIFOLD_TEST=[OFF, <ON>]`: Build unit tests, requires `GTest`.
|
||||
- `TRACY_ENABLE=[<OFF>, ON]`: Enable integration with tracy profiler.
|
||||
See profiling section below.
|
||||
|
||||
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):
|
||||
@@ -71,19 +167,20 @@ Then build:
|
||||
cd manifold
|
||||
mkdir buildWASM
|
||||
cd buildWASM
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
|
||||
node test/manifold_test.js
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel .. && emmake make
|
||||
cd test
|
||||
node ./manifold_test.js
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
The CMake script will build the python binding `pymanifold` automatically. To
|
||||
use the extension, please add `$BUILD_DIR/tools` to your `PYTHONPATH`, where
|
||||
The CMake script will build the python binding `manifold3d` automatically. To
|
||||
use the extension, please add `$BUILD_DIR/bindings/python` to your `PYTHONPATH`, where
|
||||
`$BUILD_DIR` is the build directory for CMake. Examples using the python binding
|
||||
can be found in `bindings/python/examples`. To see exported samples, run:
|
||||
```
|
||||
sudo apt install pkg-config libpython3-dev python3 python3-distutils python3-pip
|
||||
pip install trimesh
|
||||
pip install trimesh pytest
|
||||
python3 run_all.py -e
|
||||
```
|
||||
|
||||
@@ -91,16 +188,52 @@ Run the following code in the interpreter for
|
||||
python binding documentation:
|
||||
|
||||
```
|
||||
>>> import pymanifold
|
||||
>>> help(pymanifold)
|
||||
>>> import manifold3d
|
||||
>>> help(manifold3d)
|
||||
```
|
||||
|
||||
For more detailed documentation, please refer to the C++ API.
|
||||
|
||||
### Windows Shenanigans
|
||||
|
||||
Windows users should build with `-DBUILD_SHARED_LIBS=OFF`, as enabling shared
|
||||
libraries in general makes things very complicated.
|
||||
|
||||
The DLL file for manifoldc (C FFI bindings) when built with msvc is in `${CMAKE_BINARY_DIR}/bin/${BUILD_TYPE}/manifoldc.dll`.
|
||||
For example, for the following command, the path relative to the project root directory is `build/bin/Release/manifoldc.dll`.
|
||||
```sh
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -A x64 -B build
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! A lower barrier contribution is to simply make a PR that adds a test, especially if it repros an issue you've found. Simply name it prepended with DISABLED_, so that it passes the CI. That will be a very strong signal to me to fix your issue. However, if you know how to fix it yourself, then including the fix in your PR would be much appreciated!
|
||||
|
||||
### Formatting
|
||||
|
||||
There is a formatting script `format.sh` that automatically formats everything.
|
||||
It requires clang-format, black formatter for python and [gersemi](https://github.com/BlankSpruce/gersemi) for formatting cmake files.
|
||||
|
||||
Note that our script can run with clang-format older than 18, but the GitHub
|
||||
action check may fail due to slight differences between different versions of
|
||||
clang-format. In that case, either update your clang-format version or apply the
|
||||
patch from the GitHub action log.
|
||||
|
||||
### Profiling
|
||||
|
||||
There is now basic support for the [Tracy profiler](https://github.com/wolfpld/tracy) for our tests.
|
||||
To enable tracing, compile with `-DTRACY_ENABLE=on` cmake option, and run the test with Tracy server running.
|
||||
To enable memory profiling in addition to tracing, compile with `-DTRACY_MEMORY_USAGE=ON` in addition to `-DTRACY_ENABLE=ON`.
|
||||
|
||||
### Fuzzing
|
||||
|
||||
To build with fuzzing support, you should set the following with CMake:
|
||||
|
||||
- Enable fuzzing by setting `-DMANIFOLD_FUZZ=ON`
|
||||
- Disable python bindings by setting `-DMANIFOLD_PYBIND=OFF`
|
||||
- Use `clang` for compiling by setting `-DCMAKE_CXX_COMPILER=clang++`
|
||||
- You may need to disable parallelization by setting `-DMANIFOLD_PAR=OFF`, and set `ASAN_OPTIONS=detect_container_overflow=0` when building the binary on MacOS.
|
||||
|
||||
## About the author
|
||||
|
||||
This library was started by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee and this is my 20% project, not an official Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
|
||||
This library was started by [Emmett Lalish](https://elalish.blogspot.com/), currently a senior rendering engineer at Wētā FX. This was my 20% project when I was a Google employee, though my day job was maintaining [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
|
||||
|
||||
@@ -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, manually trigger PyPI CI, merge.
|
||||
1. On Github, draft a new release, make a new tag with the version number, add release notes, and publish.
|
||||
1. Check the Actions and verify that both PyPI and npm publishing actions ran successfully.
|
||||
1. Verify the npm [package](https://www.npmjs.com/package/manifold-3d?activeTab=code) looks good - unpacked size should be close to 1MB.
|
||||
1. Verify PyPI [package](https://pypi.org/project/manifold3d/#files) looks good - a bunch of built distributions ranging from ~600kB to ~1.1MB.
|
||||
1. If there's a problem with release deployment, the release workflows can be triggered separately, manually for any branch, under the Actions tab.
|
||||
@@ -12,10 +12,18 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
add_subdirectory(python)
|
||||
if(NOT MANIFOLD_CROSS_SECTION)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
add_subdirectory(wasm)
|
||||
if(MANIFOLD_CBIND)
|
||||
add_subdirectory(c)
|
||||
endif()
|
||||
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
|
||||
add_subdirectory(python)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN AND MANIFOLD_JSBIND)
|
||||
add_subdirectory(wasm)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# 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_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../include
|
||||
)
|
||||
|
||||
add_library(
|
||||
manifoldc
|
||||
manifoldc.cpp
|
||||
conv.cpp
|
||||
box.cpp
|
||||
cross.cpp
|
||||
rect.cpp
|
||||
)
|
||||
exportlib(manifoldc)
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
target_sources(manifoldc PRIVATE meshIOc.cpp)
|
||||
target_compile_options(manifoldc PUBLIC -DMANIFOLD_EXPORT)
|
||||
endif()
|
||||
|
||||
target_link_libraries(manifoldc PRIVATE manifold)
|
||||
|
||||
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(
|
||||
FILES include/manifold/manifoldc.h include/manifold/types.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/manifold
|
||||
)
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox* b) { return to_c((*from_c(b)).min); }
|
||||
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox* b) { return to_c((*from_c(b)).max); }
|
||||
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox* b) {
|
||||
auto v = from_c(b)->Size();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox* b) {
|
||||
auto v = from_c(b)->Center();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
double manifold_box_scale(ManifoldBox* b) { return from_c(b)->Scale(); }
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int manifold_box_contains_box(ManifoldBox* a, ManifoldBox* b) {
|
||||
auto outer = *from_c(a);
|
||||
auto inner = *from_c(b);
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_box_include_pt(ManifoldBox* b, double x, double y, double z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = vec3(x, y, z);
|
||||
box.Union(p);
|
||||
}
|
||||
|
||||
ManifoldBox* manifold_box_union(void* mem, ManifoldBox* a, ManifoldBox* b) {
|
||||
auto box = from_c(a)->Union(*from_c(b));
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
ManifoldBox* manifold_box_transform(void* mem, ManifoldBox* b, 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, 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, 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, double x, double y, double z) {
|
||||
auto p = vec3(x, y, z);
|
||||
return from_c(b)->DoesOverlap(p);
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_box(ManifoldBox* a, ManifoldBox* b) {
|
||||
return from_c(a)->DoesOverlap(*from_c(b));
|
||||
}
|
||||
|
||||
int manifold_box_is_finite(ManifoldBox* b) { return from_c(b)->IsFinite(); }
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,259 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "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);
|
||||
}
|
||||
|
||||
ManifoldManifoldVec* to_c(ManifoldVec* ms) {
|
||||
return reinterpret_cast<ManifoldManifoldVec*>(ms);
|
||||
}
|
||||
|
||||
ManifoldCrossSection* to_c(manifold::CrossSection* cs) {
|
||||
return reinterpret_cast<ManifoldCrossSection*>(cs);
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec* to_c(CrossSectionVec* csv) {
|
||||
return reinterpret_cast<ManifoldCrossSectionVec*>(csv);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon* to_c(manifold::SimplePolygon* m) {
|
||||
return reinterpret_cast<ManifoldSimplePolygon*>(m);
|
||||
}
|
||||
|
||||
ManifoldPolygons* to_c(manifold::Polygons* m) {
|
||||
return reinterpret_cast<ManifoldPolygons*>(m);
|
||||
}
|
||||
|
||||
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) {
|
||||
case manifold::OpType::Add:
|
||||
break;
|
||||
case manifold::OpType::Subtract:
|
||||
op = MANIFOLD_SUBTRACT;
|
||||
break;
|
||||
case manifold::OpType::Intersect:
|
||||
op = MANIFOLD_INTERSECT;
|
||||
break;
|
||||
};
|
||||
return op;
|
||||
}
|
||||
|
||||
ManifoldError to_c(manifold::Manifold::Error error) {
|
||||
ManifoldError e = MANIFOLD_NO_ERROR;
|
||||
switch (error) {
|
||||
case Manifold::Error::NoError:
|
||||
break;
|
||||
case Manifold::Error::NonFiniteVertex:
|
||||
e = MANIFOLD_NON_FINITE_VERTEX;
|
||||
break;
|
||||
case Manifold::Error::NotManifold:
|
||||
e = MANIFOLD_NOT_MANIFOLD;
|
||||
break;
|
||||
case Manifold::Error::VertexOutOfBounds:
|
||||
e = MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::PropertiesWrongLength:
|
||||
e = MANIFOLD_PROPERTIES_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::MissingPositionProperties:
|
||||
e = MANIFOLD_MISSING_POSITION_PROPERTIES;
|
||||
break;
|
||||
case Manifold::Error::MergeVectorsDifferentLengths:
|
||||
e = MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS;
|
||||
break;
|
||||
case Manifold::Error::MergeIndexOutOfBounds:
|
||||
e = MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::TransformWrongLength:
|
||||
e = MANIFOLD_TRANSFORM_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::RunIndexWrongLength:
|
||||
e = MANIFOLD_RUN_INDEX_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::FaceIDWrongLength:
|
||||
e = MANIFOLD_FACE_ID_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::InvalidConstruction:
|
||||
e = MANIFOLD_INVALID_CONSTRUCTION;
|
||||
break;
|
||||
case Manifold::Error::ResultTooLarge:
|
||||
e = MANIFOLD_RESULT_TOO_LARGE;
|
||||
break;
|
||||
};
|
||||
return e;
|
||||
}
|
||||
|
||||
ManifoldBox* to_c(manifold::Box* m) {
|
||||
return reinterpret_cast<ManifoldBox*>(m);
|
||||
}
|
||||
|
||||
ManifoldRect* to_c(manifold::Rect* m) {
|
||||
return reinterpret_cast<ManifoldRect*>(m);
|
||||
}
|
||||
|
||||
ManifoldVec2 to_c(vec2 v) { return {v.x, v.y}; }
|
||||
|
||||
ManifoldVec3 to_c(vec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldIVec3 to_c(ivec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldTriangulation* to_c(std::vector<ivec3>* m) {
|
||||
return reinterpret_cast<ManifoldTriangulation*>(m);
|
||||
}
|
||||
|
||||
const manifold::Manifold* from_c(ManifoldManifold* m) {
|
||||
return reinterpret_cast<manifold::Manifold const*>(m);
|
||||
}
|
||||
|
||||
ManifoldVec* from_c(ManifoldManifoldVec* ms) {
|
||||
return reinterpret_cast<ManifoldVec*>(ms);
|
||||
}
|
||||
|
||||
const manifold::CrossSection* from_c(ManifoldCrossSection* cs) {
|
||||
return reinterpret_cast<manifold::CrossSection*>(cs);
|
||||
}
|
||||
|
||||
CrossSectionVec* from_c(ManifoldCrossSectionVec* csv) {
|
||||
return reinterpret_cast<CrossSectionVec*>(csv);
|
||||
}
|
||||
|
||||
const manifold::SimplePolygon* from_c(ManifoldSimplePolygon* m) {
|
||||
return reinterpret_cast<manifold::SimplePolygon const*>(m);
|
||||
}
|
||||
|
||||
const manifold::Polygons* from_c(ManifoldPolygons* m) {
|
||||
return reinterpret_cast<manifold::Polygons const*>(m);
|
||||
}
|
||||
|
||||
const manifold::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) {
|
||||
case MANIFOLD_ADD:
|
||||
break;
|
||||
case MANIFOLD_SUBTRACT:
|
||||
op = OpType::Subtract;
|
||||
break;
|
||||
case MANIFOLD_INTERSECT:
|
||||
op = OpType::Intersect;
|
||||
break;
|
||||
};
|
||||
return op;
|
||||
}
|
||||
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule) {
|
||||
auto fr = CrossSection::FillRule::EvenOdd;
|
||||
switch (fillrule) {
|
||||
case MANIFOLD_FILL_RULE_EVEN_ODD:
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_NON_ZERO:
|
||||
fr = CrossSection::FillRule::NonZero;
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_POSITIVE:
|
||||
fr = CrossSection::FillRule::Positive;
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_NEGATIVE:
|
||||
fr = CrossSection::FillRule::Negative;
|
||||
break;
|
||||
};
|
||||
return fr;
|
||||
}
|
||||
|
||||
CrossSection::JoinType from_c(ManifoldJoinType join_type) {
|
||||
auto jt = CrossSection::JoinType::Square;
|
||||
switch (join_type) {
|
||||
case MANIFOLD_JOIN_TYPE_SQUARE:
|
||||
break;
|
||||
case MANIFOLD_JOIN_TYPE_ROUND:
|
||||
jt = CrossSection::JoinType::Round;
|
||||
break;
|
||||
case MANIFOLD_JOIN_TYPE_MITER:
|
||||
jt = CrossSection::JoinType::Miter;
|
||||
break;
|
||||
case MANIFOLD_JOIN_TYPE_BEVEL:
|
||||
jt = CrossSection::JoinType::Bevel;
|
||||
break;
|
||||
};
|
||||
return jt;
|
||||
}
|
||||
|
||||
const manifold::Box* from_c(ManifoldBox* m) {
|
||||
return reinterpret_cast<manifold::Box const*>(m);
|
||||
}
|
||||
|
||||
const manifold::Rect* from_c(ManifoldRect* m) {
|
||||
return reinterpret_cast<manifold::Rect const*>(m);
|
||||
}
|
||||
|
||||
vec2 from_c(ManifoldVec2 v) { return vec2(v.x, v.y); }
|
||||
|
||||
vec3 from_c(ManifoldVec3 v) { return vec3(v.x, v.y, v.z); }
|
||||
|
||||
ivec3 from_c(ManifoldIVec3 v) { return ivec3(v.x, v.y, v.z); }
|
||||
|
||||
vec4 from_c(ManifoldVec4 v) { return vec4(v.x, v.y, v.z, v.w); }
|
||||
|
||||
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<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<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;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/cross_section.h"
|
||||
#include "manifold/manifold.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
using namespace manifold;
|
||||
using ManifoldVec = std::vector<Manifold>;
|
||||
using CrossSectionVec = std::vector<CrossSection>;
|
||||
|
||||
ManifoldManifold* to_c(manifold::Manifold* m);
|
||||
ManifoldManifoldVec* to_c(ManifoldVec* ms);
|
||||
ManifoldCrossSection* to_c(manifold::CrossSection* cs);
|
||||
ManifoldCrossSectionVec* to_c(CrossSectionVec* csv);
|
||||
ManifoldSimplePolygon* to_c(manifold::SimplePolygon* p);
|
||||
ManifoldPolygons* to_c(manifold::Polygons* ps);
|
||||
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(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);
|
||||
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::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);
|
||||
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<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 (size_t i = 0; i < length; ++i) {
|
||||
vec.push_back(ts[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* copy_data(void* mem, std::vector<T> v) {
|
||||
T* ts = reinterpret_cast<T*>(mem);
|
||||
memcpy(ts, v.data(), sizeof(T) * v.size());
|
||||
return ts;
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
#include "manifold/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());
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_copy(void* mem,
|
||||
ManifoldCrossSection* cs) {
|
||||
auto cross = *from_c(cs);
|
||||
return to_c(new (mem) CrossSection(cross));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_of_simple_polygon(
|
||||
void* mem, ManifoldSimplePolygon* p, ManifoldFillRule fr) {
|
||||
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_of_polygons(void* mem,
|
||||
ManifoldPolygons* p,
|
||||
ManifoldFillRule fr) {
|
||||
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec* manifold_cross_section_empty_vec(void* mem) {
|
||||
return to_c(new (mem) CrossSectionVec());
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec* manifold_cross_section_vec(void* mem, size_t sz) {
|
||||
return to_c(new (mem) CrossSectionVec(sz));
|
||||
}
|
||||
|
||||
void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec* csv,
|
||||
size_t sz) {
|
||||
from_c(csv)->reserve(sz);
|
||||
}
|
||||
|
||||
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec* csv) {
|
||||
return from_c(csv)->size();
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_vec_get(
|
||||
void* mem, ManifoldCrossSectionVec* csv, size_t idx) {
|
||||
auto cs = (*from_c(csv))[idx];
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
void manifold_cross_section_vec_set(ManifoldCrossSectionVec* csv, size_t idx,
|
||||
ManifoldCrossSection* cs) {
|
||||
(*from_c(csv))[idx] = *from_c(cs);
|
||||
}
|
||||
|
||||
void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec* csv,
|
||||
ManifoldCrossSection* cs) {
|
||||
return from_c(csv)->push_back(*from_c(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_square(void* mem, 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, double radius,
|
||||
int circular_segments) {
|
||||
auto cs = CrossSection::Circle(radius, circular_segments);
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_boolean(void* mem,
|
||||
ManifoldCrossSection* a,
|
||||
ManifoldCrossSection* b,
|
||||
ManifoldOpType op) {
|
||||
auto cs = from_c(a)->Boolean(*from_c(b), from_c(op));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_batch_boolean(
|
||||
void* mem, ManifoldCrossSectionVec* csv, ManifoldOpType op) {
|
||||
auto cs = CrossSection::BatchBoolean(*from_c(csv), from_c(op));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_union(void* mem,
|
||||
ManifoldCrossSection* a,
|
||||
ManifoldCrossSection* b) {
|
||||
auto cs = (*from_c(a)) + (*from_c(b));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_difference(
|
||||
void* mem, ManifoldCrossSection* a, ManifoldCrossSection* b) {
|
||||
auto cs = (*from_c(a)) - (*from_c(b));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_intersection(
|
||||
void* mem, ManifoldCrossSection* a, ManifoldCrossSection* b) {
|
||||
auto cs = (*from_c(a)) ^ (*from_c(b));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_hull(void* mem,
|
||||
ManifoldCrossSection* cs) {
|
||||
auto hulled = from_c(cs)->Hull();
|
||||
return to_c(new (mem) CrossSection(hulled));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_batch_hull(
|
||||
void* mem, ManifoldCrossSectionVec* css) {
|
||||
auto hulled = CrossSection::Hull(*from_c(css));
|
||||
return to_c(new (mem) CrossSection(hulled));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_hull_simple_polygon(
|
||||
void* mem, ManifoldSimplePolygon* ps) {
|
||||
auto hulled = CrossSection::Hull(*from_c(ps));
|
||||
return to_c(new (mem) CrossSection(hulled));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_hull_polygons(
|
||||
void* mem, ManifoldPolygons* ps) {
|
||||
auto hulled = CrossSection::Hull(*from_c(ps));
|
||||
return to_c(new (mem) CrossSection(hulled));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_translate(void* mem,
|
||||
ManifoldCrossSection* cs,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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)(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));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_simplify(void* mem,
|
||||
ManifoldCrossSection* cs,
|
||||
double epsilon) {
|
||||
auto simplified = from_c(cs)->Simplify(epsilon);
|
||||
return to_c(new (mem) CrossSection(simplified));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_offset(
|
||||
void* mem, ManifoldCrossSection* cs, double delta, ManifoldJoinType jt,
|
||||
double miter_limit, int circular_segments) {
|
||||
auto offset =
|
||||
from_c(cs)->Offset(delta, from_c(jt), miter_limit, circular_segments);
|
||||
return to_c(new (mem) CrossSection(offset));
|
||||
}
|
||||
|
||||
double manifold_cross_section_area(ManifoldCrossSection* cs) {
|
||||
return from_c(cs)->Area();
|
||||
}
|
||||
|
||||
size_t manifold_cross_section_num_vert(ManifoldCrossSection* cs) {
|
||||
return from_c(cs)->NumVert();
|
||||
}
|
||||
|
||||
size_t manifold_cross_section_num_contour(ManifoldCrossSection* cs) {
|
||||
return from_c(cs)->NumContour();
|
||||
}
|
||||
|
||||
int manifold_cross_section_is_empty(ManifoldCrossSection* cs) {
|
||||
return from_c(cs)->IsEmpty();
|
||||
}
|
||||
|
||||
ManifoldRect* manifold_cross_section_bounds(void* mem,
|
||||
ManifoldCrossSection* cs) {
|
||||
auto rect = from_c(cs)->Bounds();
|
||||
return to_c(new (mem) Rect(rect));
|
||||
}
|
||||
|
||||
ManifoldPolygons* manifold_cross_section_to_polygons(void* mem,
|
||||
ManifoldCrossSection* cs) {
|
||||
auto ps = from_c(cs)->ToPolygons();
|
||||
return to_c(new (mem) Polygons(ps));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_compose(
|
||||
void* mem, ManifoldCrossSectionVec* csv) {
|
||||
auto cs = CrossSection::Compose(*from_c(csv));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec* manifold_cross_section_decompose(
|
||||
void* mem, ManifoldCrossSection* cs) {
|
||||
auto comps = from_c(cs)->Decompose();
|
||||
return to_c(new (mem) CrossSectionVec(comps));
|
||||
}
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,514 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "manifold/types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdint>
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Polygons
|
||||
|
||||
ManifoldSimplePolygon* manifold_simple_polygon(void* mem, ManifoldVec2* ps,
|
||||
size_t length);
|
||||
ManifoldPolygons* manifold_polygons(void* mem, ManifoldSimplePolygon** ps,
|
||||
size_t length);
|
||||
size_t manifold_simple_polygon_length(ManifoldSimplePolygon* p);
|
||||
size_t manifold_polygons_length(ManifoldPolygons* ps);
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons* ps, size_t idx);
|
||||
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon* p,
|
||||
size_t idx);
|
||||
ManifoldSimplePolygon* manifold_polygons_get_simple(void* mem,
|
||||
ManifoldPolygons* ps,
|
||||
size_t idx);
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons* ps,
|
||||
size_t simple_idx, size_t pt_idx);
|
||||
|
||||
// Mesh Construction
|
||||
|
||||
ManifoldMeshGL* manifold_meshgl(void* mem, float* vert_props, size_t n_verts,
|
||||
size_t n_props, uint32_t* tri_verts,
|
||||
size_t n_tris);
|
||||
|
||||
ManifoldMeshGL* manifold_meshgl_w_tangents(void* mem, float* vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t* tri_verts, size_t n_tris,
|
||||
float* halfedge_tangent);
|
||||
ManifoldMeshGL* manifold_get_meshgl(void* mem, ManifoldManifold* m);
|
||||
ManifoldMeshGL* manifold_meshgl_copy(void* mem, ManifoldMeshGL* m);
|
||||
ManifoldMeshGL* manifold_meshgl_merge(void* mem, ManifoldMeshGL* m);
|
||||
|
||||
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
|
||||
// manifold_level_set will be chosen automatically depending on the size of the
|
||||
// job and whether Manifold has been compiled with a PAR backend. If you are
|
||||
// using these bindings from a language that has a runtime lock preventing the
|
||||
// parallel execution of closures, then you should use manifold_level_set_seq to
|
||||
// force sequential execution.
|
||||
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
|
||||
|
||||
ManifoldManifoldVec* manifold_manifold_empty_vec(void* mem);
|
||||
ManifoldManifoldVec* manifold_manifold_vec(void* mem, size_t sz);
|
||||
void manifold_manifold_vec_reserve(ManifoldManifoldVec* ms, size_t sz);
|
||||
size_t manifold_manifold_vec_length(ManifoldManifoldVec* ms);
|
||||
ManifoldManifold* manifold_manifold_vec_get(void* mem, ManifoldManifoldVec* ms,
|
||||
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);
|
||||
|
||||
// Manifold Booleans
|
||||
|
||||
ManifoldManifold* manifold_boolean(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b, ManifoldOpType op);
|
||||
ManifoldManifold* manifold_batch_boolean(void* mem, ManifoldManifoldVec* ms,
|
||||
ManifoldOpType op);
|
||||
ManifoldManifold* manifold_union(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b);
|
||||
ManifoldManifold* manifold_difference(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b);
|
||||
ManifoldManifold* manifold_intersection(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b);
|
||||
ManifoldManifoldPair manifold_split(void* mem_first, void* mem_second,
|
||||
ManifoldManifold* a, ManifoldManifold* b);
|
||||
ManifoldManifoldPair manifold_split_by_plane(void* mem_first, void* mem_second,
|
||||
ManifoldManifold* m,
|
||||
double normal_x, double normal_y,
|
||||
double normal_z, double offset);
|
||||
ManifoldManifold* manifold_trim_by_plane(void* mem, ManifoldManifold* m,
|
||||
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
|
||||
|
||||
ManifoldManifold* manifold_hull(void* mem, ManifoldManifold* m);
|
||||
ManifoldManifold* manifold_batch_hull(void* mem, ManifoldManifoldVec* ms);
|
||||
ManifoldManifold* manifold_hull_pts(void* mem, ManifoldVec3* ps, size_t length);
|
||||
|
||||
// Manifold Transformations
|
||||
|
||||
ManifoldManifold* manifold_translate(void* mem, ManifoldManifold* m, 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)(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, double x, double y, double z,
|
||||
int center);
|
||||
ManifoldManifold* manifold_cylinder(void* mem, double height, double radius_low,
|
||||
double radius_high, int circular_segments,
|
||||
int center);
|
||||
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,
|
||||
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);
|
||||
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);
|
||||
double manifold_epsilon(ManifoldManifold* m);
|
||||
int manifold_genus(ManifoldManifold* m);
|
||||
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)(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);
|
||||
ManifoldCrossSection* manifold_cross_section_of_simple_polygon(
|
||||
void* mem, ManifoldSimplePolygon* p, ManifoldFillRule fr);
|
||||
ManifoldCrossSection* manifold_cross_section_of_polygons(void* mem,
|
||||
ManifoldPolygons* p,
|
||||
ManifoldFillRule fr);
|
||||
ManifoldCrossSection* manifold_cross_section_square(void* mem, 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);
|
||||
ManifoldCrossSectionVec* manifold_cross_section_decompose(
|
||||
void* mem, ManifoldCrossSection* cs);
|
||||
|
||||
// CrossSection Vectors
|
||||
|
||||
ManifoldCrossSectionVec* manifold_cross_section_empty_vec(void* mem);
|
||||
ManifoldCrossSectionVec* manifold_cross_section_vec(void* mem, size_t sz);
|
||||
void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec* csv,
|
||||
size_t sz);
|
||||
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec* csv);
|
||||
ManifoldCrossSection* manifold_cross_section_vec_get(
|
||||
void* mem, ManifoldCrossSectionVec* csv, 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);
|
||||
|
||||
// CrossSection Booleans
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_boolean(void* mem,
|
||||
ManifoldCrossSection* a,
|
||||
ManifoldCrossSection* b,
|
||||
ManifoldOpType op);
|
||||
ManifoldCrossSection* manifold_cross_section_batch_boolean(
|
||||
void* mem, ManifoldCrossSectionVec* csv, ManifoldOpType op);
|
||||
ManifoldCrossSection* manifold_cross_section_union(void* mem,
|
||||
ManifoldCrossSection* a,
|
||||
ManifoldCrossSection* b);
|
||||
ManifoldCrossSection* manifold_cross_section_difference(
|
||||
void* mem, ManifoldCrossSection* a, ManifoldCrossSection* b);
|
||||
ManifoldCrossSection* manifold_cross_section_intersection(
|
||||
void* mem, ManifoldCrossSection* a, ManifoldCrossSection* b);
|
||||
|
||||
// CrossSection Convex Hulls
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_hull(void* mem,
|
||||
ManifoldCrossSection* cs);
|
||||
ManifoldCrossSection* manifold_cross_section_batch_hull(
|
||||
void* mem, ManifoldCrossSectionVec* css);
|
||||
ManifoldCrossSection* manifold_cross_section_hull_simple_polygon(
|
||||
void* mem, ManifoldSimplePolygon* ps);
|
||||
ManifoldCrossSection* manifold_cross_section_hull_polygons(
|
||||
void* mem, ManifoldPolygons* ps);
|
||||
|
||||
// CrossSection Transformation
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_translate(void* mem,
|
||||
ManifoldCrossSection* cs,
|
||||
double x, double y);
|
||||
ManifoldCrossSection* manifold_cross_section_rotate(void* mem,
|
||||
ManifoldCrossSection* cs,
|
||||
double deg);
|
||||
ManifoldCrossSection* manifold_cross_section_scale(void* mem,
|
||||
ManifoldCrossSection* cs,
|
||||
double x, double y);
|
||||
ManifoldCrossSection* manifold_cross_section_mirror(void* mem,
|
||||
ManifoldCrossSection* cs,
|
||||
double ax_x, double ax_y);
|
||||
ManifoldCrossSection* manifold_cross_section_transform(void* mem,
|
||||
ManifoldCrossSection* cs,
|
||||
double x1, double y1,
|
||||
double x2, double y2,
|
||||
double x3, double y3);
|
||||
ManifoldCrossSection* manifold_cross_section_warp(
|
||||
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);
|
||||
ManifoldCrossSection* manifold_cross_section_offset(
|
||||
void* mem, ManifoldCrossSection* cs, double delta, ManifoldJoinType jt,
|
||||
double miter_limit, int circular_segments);
|
||||
|
||||
// CrossSection Info
|
||||
|
||||
double manifold_cross_section_area(ManifoldCrossSection* cs);
|
||||
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);
|
||||
ManifoldPolygons* manifold_cross_section_to_polygons(void* mem,
|
||||
ManifoldCrossSection* cs);
|
||||
|
||||
// Rectangle
|
||||
|
||||
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);
|
||||
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, double x, double y);
|
||||
ManifoldRect* manifold_rect_union(void* mem, ManifoldRect* a, ManifoldRect* b);
|
||||
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, 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);
|
||||
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, double x, double y, double z);
|
||||
ManifoldBox* manifold_box_union(void* mem, ManifoldBox* a, ManifoldBox* b);
|
||||
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(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
|
||||
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL* m);
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL* m);
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_run_index_length(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_face_id_length(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_tangent_length(ManifoldMeshGL* m);
|
||||
float* manifold_meshgl_vert_properties(void* mem, ManifoldMeshGL* m);
|
||||
uint32_t* manifold_meshgl_tri_verts(void* mem, ManifoldMeshGL* m);
|
||||
uint32_t* manifold_meshgl_merge_from_vert(void* mem, ManifoldMeshGL* m);
|
||||
uint32_t* manifold_meshgl_merge_to_vert(void* mem, ManifoldMeshGL* m);
|
||||
uint32_t* manifold_meshgl_run_index(void* mem, ManifoldMeshGL* m);
|
||||
uint32_t* manifold_meshgl_run_original_id(void* mem, ManifoldMeshGL* m);
|
||||
float* manifold_meshgl_run_transform(void* mem, ManifoldMeshGL* m);
|
||||
uint32_t* manifold_meshgl_face_id(void* mem, ManifoldMeshGL* m);
|
||||
float* manifold_meshgl_halfedge_tangent(void* mem, ManifoldMeshGL* m);
|
||||
|
||||
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();
|
||||
size_t manifold_manifold_vec_size();
|
||||
size_t manifold_cross_section_size();
|
||||
size_t manifold_cross_section_vec_size();
|
||||
size_t manifold_simple_polygon_size();
|
||||
size_t manifold_polygons_size();
|
||||
size_t manifold_manifold_pair_size();
|
||||
size_t manifold_meshgl_size();
|
||||
size_t manifold_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
|
||||
|
||||
void manifold_destruct_manifold(ManifoldManifold* m);
|
||||
void manifold_destruct_manifold_vec(ManifoldManifoldVec* ms);
|
||||
void manifold_destruct_cross_section(ManifoldCrossSection* m);
|
||||
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec* csv);
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon* p);
|
||||
void manifold_destruct_polygons(ManifoldPolygons* p);
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL* m);
|
||||
void manifold_destruct_meshgl64(ManifoldMeshGL64* m);
|
||||
void manifold_destruct_box(ManifoldBox* b);
|
||||
void manifold_destruct_rect(ManifoldRect* b);
|
||||
void manifold_destruct_triangulation(ManifoldTriangulation* M);
|
||||
|
||||
// pointer free + destruction
|
||||
|
||||
void manifold_delete_manifold(ManifoldManifold* m);
|
||||
void manifold_delete_manifold_vec(ManifoldManifoldVec* ms);
|
||||
void manifold_delete_cross_section(ManifoldCrossSection* cs);
|
||||
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec* csv);
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon* p);
|
||||
void manifold_delete_polygons(ManifoldPolygons* p);
|
||||
void manifold_delete_meshgl(ManifoldMeshGL* m);
|
||||
void manifold_delete_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, 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);
|
||||
void manifold_export_options_set_material(ManifoldExportOptions* options,
|
||||
ManifoldMaterial* mat);
|
||||
void manifold_export_meshgl(const char* filename, ManifoldMeshGL* mesh,
|
||||
ManifoldExportOptions* options);
|
||||
ManifoldMeshGL* manifold_import_meshgl(void* mem, const char* filename,
|
||||
int force_cleanup);
|
||||
|
||||
size_t manifold_material_size();
|
||||
size_t manifold_export_options_size();
|
||||
|
||||
void manifold_destruct_material(ManifoldMaterial* m);
|
||||
void manifold_destruct_export_options(ManifoldExportOptions* options);
|
||||
|
||||
void manifold_delete_material(ManifoldMaterial* m);
|
||||
void manifold_delete_export_options(ManifoldExportOptions* options);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
// opaque pointers
|
||||
|
||||
typedef struct ManifoldManifold ManifoldManifold;
|
||||
typedef struct ManifoldManifoldVec ManifoldManifoldVec;
|
||||
typedef struct ManifoldCrossSection ManifoldCrossSection;
|
||||
typedef struct ManifoldCrossSectionVec ManifoldCrossSectionVec;
|
||||
typedef struct ManifoldSimplePolygon ManifoldSimplePolygon;
|
||||
typedef struct ManifoldPolygons ManifoldPolygons;
|
||||
typedef struct 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;
|
||||
typedef struct ManifoldExportOptions ManifoldExportOptions;
|
||||
#endif
|
||||
|
||||
// structs
|
||||
|
||||
typedef struct ManifoldManifoldPair {
|
||||
ManifoldManifold* first;
|
||||
ManifoldManifold* second;
|
||||
} ManifoldManifoldPair;
|
||||
|
||||
typedef struct ManifoldVec2 {
|
||||
double x;
|
||||
double y;
|
||||
} ManifoldVec2;
|
||||
|
||||
typedef struct ManifoldVec3 {
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
} ManifoldVec3;
|
||||
|
||||
typedef struct ManifoldIVec3 {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
} ManifoldIVec3;
|
||||
|
||||
typedef struct ManifoldVec4 {
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
double w;
|
||||
} ManifoldVec4;
|
||||
|
||||
typedef struct ManifoldProperties {
|
||||
double surface_area;
|
||||
double volume;
|
||||
} ManifoldProperties;
|
||||
|
||||
// enums
|
||||
|
||||
typedef enum ManifoldOpType {
|
||||
MANIFOLD_ADD,
|
||||
MANIFOLD_SUBTRACT,
|
||||
MANIFOLD_INTERSECT
|
||||
} ManifoldOpType;
|
||||
|
||||
typedef enum ManifoldError {
|
||||
MANIFOLD_NO_ERROR,
|
||||
MANIFOLD_NON_FINITE_VERTEX,
|
||||
MANIFOLD_NOT_MANIFOLD,
|
||||
MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS,
|
||||
MANIFOLD_PROPERTIES_WRONG_LENGTH,
|
||||
MANIFOLD_MISSING_POSITION_PROPERTIES,
|
||||
MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS,
|
||||
MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS,
|
||||
MANIFOLD_TRANSFORM_WRONG_LENGTH,
|
||||
MANIFOLD_RUN_INDEX_WRONG_LENGTH,
|
||||
MANIFOLD_FACE_ID_WRONG_LENGTH,
|
||||
MANIFOLD_INVALID_CONSTRUCTION,
|
||||
MANIFOLD_RESULT_TOO_LARGE,
|
||||
} ManifoldError;
|
||||
|
||||
typedef enum ManifoldFillRule {
|
||||
MANIFOLD_FILL_RULE_EVEN_ODD,
|
||||
MANIFOLD_FILL_RULE_NON_ZERO,
|
||||
MANIFOLD_FILL_RULE_POSITIVE,
|
||||
MANIFOLD_FILL_RULE_NEGATIVE
|
||||
} ManifoldFillRule;
|
||||
|
||||
typedef enum ManifoldJoinType {
|
||||
MANIFOLD_JOIN_TYPE_SQUARE,
|
||||
MANIFOLD_JOIN_TYPE_ROUND,
|
||||
MANIFOLD_JOIN_TYPE_MITER,
|
||||
MANIFOLD_JOIN_TYPE_BEVEL,
|
||||
} ManifoldJoinType;
|
||||
|
||||
// function pointer
|
||||
typedef double (*ManifoldSdf)(double, double, double, void*);
|
||||
@@ -0,0 +1,813 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "manifold/manifoldc.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#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 {
|
||||
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<double(double, double, double)> sdf =
|
||||
std::bind(sdf_context, _1, _2, _3, ctx);
|
||||
std::function<double(vec3)> fun = [sdf](vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
return to_c(new (mem) Manifold(Manifold::LevelSet(
|
||||
fun, *from_c(bounds), edge_length, level, tolerance, !seq)));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
ManifoldSimplePolygon* manifold_simple_polygon(void* mem, ManifoldVec2* ps,
|
||||
size_t length) {
|
||||
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);
|
||||
}
|
||||
|
||||
ManifoldPolygons* manifold_polygons(void* mem, ManifoldSimplePolygon** ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<SimplePolygon>;
|
||||
auto polys = reinterpret_cast<SimplePolygon**>(ps);
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
vec->push_back(*polys[i]);
|
||||
}
|
||||
return to_c(vec);
|
||||
}
|
||||
|
||||
size_t manifold_simple_polygon_length(ManifoldSimplePolygon* p) {
|
||||
return from_c(p)->size();
|
||||
}
|
||||
|
||||
size_t manifold_polygons_length(ManifoldPolygons* ps) {
|
||||
return from_c(ps)->size();
|
||||
}
|
||||
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons* ps, size_t idx) {
|
||||
return (*from_c(ps))[idx].size();
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon* p,
|
||||
size_t idx) {
|
||||
return to_c((*from_c(p))[idx]);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon* manifold_polygons_get_simple(void* mem,
|
||||
ManifoldPolygons* ps,
|
||||
size_t idx) {
|
||||
auto sp = (*from_c(ps))[idx];
|
||||
return to_c(new (mem) SimplePolygon(sp));
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
ManifoldManifoldVec* manifold_manifold_empty_vec(void* mem) {
|
||||
return to_c(new (mem) ManifoldVec());
|
||||
}
|
||||
|
||||
ManifoldManifoldVec* manifold_manifold_vec(void* mem, size_t sz) {
|
||||
return to_c(new (mem) ManifoldVec(sz));
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_reserve(ManifoldManifoldVec* ms, size_t sz) {
|
||||
from_c(ms)->reserve(sz);
|
||||
}
|
||||
|
||||
size_t manifold_manifold_vec_length(ManifoldManifoldVec* ms) {
|
||||
return from_c(ms)->size();
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_manifold_vec_get(void* mem, ManifoldManifoldVec* ms,
|
||||
size_t idx) {
|
||||
auto m = (*from_c(ms))[idx];
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_set(ManifoldManifoldVec* ms, size_t idx,
|
||||
ManifoldManifold* m) {
|
||||
(*from_c(ms))[idx] = *from_c(m);
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_push_back(ManifoldManifoldVec* ms,
|
||||
ManifoldManifold* m) {
|
||||
return from_c(ms)->push_back(*from_c(m));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_boolean(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b, ManifoldOpType op) {
|
||||
auto m = from_c(a)->Boolean(*from_c(b), from_c(op));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_batch_boolean(void* mem, ManifoldManifoldVec* ms,
|
||||
ManifoldOpType op) {
|
||||
auto m = Manifold::BatchBoolean(*from_c(ms), from_c(op));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_union(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b) {
|
||||
auto m = (*from_c(a)) + (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_difference(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b) {
|
||||
auto m = (*from_c(a)) - (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_intersection(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b) {
|
||||
auto m = (*from_c(a)) ^ (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifoldPair manifold_split(void* mem_first, void* mem_second,
|
||||
ManifoldManifold* a, ManifoldManifold* b) {
|
||||
auto pair = from_c(a)->Split(*from_c(b));
|
||||
auto first = new (mem_first) Manifold(pair.first);
|
||||
auto second = new (mem_second) Manifold(pair.second);
|
||||
return {to_c(first), to_c(second)};
|
||||
}
|
||||
|
||||
ManifoldManifoldPair manifold_split_by_plane(void* mem_first, void* mem_second,
|
||||
ManifoldManifold* m,
|
||||
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);
|
||||
return {to_c(first), to_c(second)};
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_trim_by_plane(void* mem, ManifoldManifold* m,
|
||||
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));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_batch_hull(void* mem, ManifoldManifoldVec* ms) {
|
||||
auto hulled = Manifold::Hull(*from_c(ms));
|
||||
return to_c(new (mem) Manifold(hulled));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_hull_pts(void* mem, ManifoldVec3* ps,
|
||||
size_t length) {
|
||||
std::vector<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, 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, 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, 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, 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, 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)(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));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_smooth_by_normals(void* mem, ManifoldManifold* m,
|
||||
int normalIdx) {
|
||||
auto smoothed = from_c(m)->SmoothByNormals(normalIdx);
|
||||
return to_c(new (mem) Manifold(smoothed));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_smooth_out(void* mem, ManifoldManifold* m,
|
||||
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) {
|
||||
auto refined = from_c(m)->Refine(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());
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_copy(void* mem, ManifoldManifold* m) {
|
||||
return to_c(new (mem) Manifold(*from_c(m)));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_tetrahedron(void* mem) {
|
||||
auto m = Manifold::Tetrahedron();
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_cube(void* mem, double x, double y, double z,
|
||||
int center) {
|
||||
auto size = vec3(x, y, z);
|
||||
auto m = Manifold::Cube(size, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
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, double radius,
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Sphere(radius, circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_meshgl(void* mem, float* vert_props, size_t n_verts,
|
||||
size_t n_props, uint32_t* tri_verts,
|
||||
size_t n_tris) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_meshgl_w_tangents(void* mem, float* vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t* tri_verts, size_t n_tris,
|
||||
float* halfedge_tangent) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
mesh->halfedgeTangent = vector_of_array(halfedge_tangent, n_tris * 3 * 4);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
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,
|
||||
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);
|
||||
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);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_of_meshgl(void* mem, ManifoldMeshGL* mesh) {
|
||||
auto m = Manifold(*from_c(mesh));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_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, 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));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_compose(void* mem, ManifoldManifoldVec* ms) {
|
||||
auto composed = Manifold::Compose(*from_c(ms));
|
||||
return to_c(new (mem) Manifold(composed));
|
||||
}
|
||||
|
||||
ManifoldManifoldVec* manifold_decompose(void* mem, ManifoldManifold* m) {
|
||||
auto comps = from_c(m)->Decompose();
|
||||
return to_c(new (mem) std::vector<Manifold>(comps));
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_get_meshgl(void* mem, ManifoldManifold* m) {
|
||||
auto mesh = from_c(m)->GetMeshGL();
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_meshgl_copy(void* mem, ManifoldMeshGL* m) {
|
||||
return to_c(new (mem) MeshGL(*from_c(m)));
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_meshgl_merge(void* mem, ManifoldMeshGL* m) {
|
||||
auto duplicate = new (mem) MeshGL(*from_c(m));
|
||||
if (duplicate->Merge()) {
|
||||
return to_c(duplicate);
|
||||
}
|
||||
duplicate->~MeshGL();
|
||||
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);
|
||||
}
|
||||
duplicate->~MeshGL64();
|
||||
return m;
|
||||
}
|
||||
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL* m) { return from_c(m)->numProp; }
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL* m) { return from_c(m)->NumVert(); }
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL* m) { return from_c(m)->NumTri(); }
|
||||
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL* m) {
|
||||
return from_c(m)->vertProperties.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL* m) {
|
||||
return from_c(m)->triVerts.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL* m) {
|
||||
return from_c(m)->mergeFromVert.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_index_length(ManifoldMeshGL* m) {
|
||||
return from_c(m)->runIndex.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL* m) {
|
||||
return from_c(m)->runOriginalID.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL* m) {
|
||||
return from_c(m)->runTransform.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_face_id_length(ManifoldMeshGL* m) {
|
||||
return from_c(m)->faceID.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_tangent_length(ManifoldMeshGL* m) {
|
||||
return from_c(m)->halfedgeTangent.size();
|
||||
}
|
||||
|
||||
float* manifold_meshgl_vert_properties(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->vertProperties);
|
||||
}
|
||||
|
||||
uint32_t* manifold_meshgl_tri_verts(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->triVerts);
|
||||
}
|
||||
|
||||
uint32_t* manifold_meshgl_merge_from_vert(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->mergeFromVert);
|
||||
}
|
||||
|
||||
uint32_t* manifold_meshgl_merge_to_vert(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->mergeToVert);
|
||||
}
|
||||
|
||||
uint32_t* manifold_meshgl_run_index(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->runIndex);
|
||||
}
|
||||
|
||||
uint32_t* manifold_meshgl_run_original_id(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->runOriginalID);
|
||||
}
|
||||
|
||||
float* manifold_meshgl_run_transform(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->runTransform);
|
||||
}
|
||||
|
||||
uint32_t* manifold_meshgl_face_id(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->faceID);
|
||||
}
|
||||
|
||||
float* manifold_meshgl_halfedge_tangent(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->halfedgeTangent);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
int manifold_original_id(ManifoldManifold* m) {
|
||||
return from_c(m)->OriginalID();
|
||||
}
|
||||
|
||||
int manifold_is_empty(ManifoldManifold* m) { return from_c(m)->IsEmpty(); }
|
||||
|
||||
ManifoldError manifold_status(ManifoldManifold* m) {
|
||||
auto error = from_c(m)->Status();
|
||||
return to_c(error);
|
||||
}
|
||||
|
||||
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(); }
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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)(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));
|
||||
};
|
||||
|
||||
ManifoldManifold* manifold_calculate_curvature(void* mem, ManifoldManifold* m,
|
||||
int gaussian_idx, int mean_idx) {
|
||||
auto man = from_c(m)->CalculateCurvature(gaussian_idx, mean_idx);
|
||||
return to_c(new (mem) Manifold(man));
|
||||
}
|
||||
|
||||
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(double degrees) {
|
||||
Quality::SetMinCircularAngle(degrees);
|
||||
}
|
||||
|
||||
void manifold_set_min_circular_edge_length(double length) {
|
||||
Quality::SetMinCircularEdgeLength(length);
|
||||
}
|
||||
|
||||
void manifold_set_circular_segments(int number) {
|
||||
Quality::SetCircularSegments(number);
|
||||
}
|
||||
|
||||
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() {
|
||||
return sizeof(std::vector<CrossSection>);
|
||||
}
|
||||
size_t manifold_simple_polygon_size() { return sizeof(SimplePolygon); }
|
||||
size_t manifold_polygons_size() { return sizeof(Polygons); }
|
||||
size_t manifold_manifold_size() { return sizeof(Manifold); }
|
||||
size_t manifold_manifold_vec_size() { return sizeof(std::vector<Manifold>); }
|
||||
size_t manifold_manifold_pair_size() { return sizeof(ManifoldManifoldPair); }
|
||||
size_t manifold_meshgl_size() { return sizeof(MeshGL); }
|
||||
size_t manifold_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) {
|
||||
delete from_c(c);
|
||||
}
|
||||
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec* csv) {
|
||||
delete from_c(csv);
|
||||
}
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon* p) {
|
||||
delete from_c(p);
|
||||
}
|
||||
void manifold_delete_polygons(ManifoldPolygons* p) { delete from_c(p); }
|
||||
void manifold_delete_manifold(ManifoldManifold* m) { delete from_c(m); }
|
||||
void manifold_delete_manifold_vec(ManifoldManifoldVec* ms) {
|
||||
delete from_c(ms);
|
||||
}
|
||||
void manifold_delete_meshgl(ManifoldMeshGL* m) { delete from_c(m); }
|
||||
void manifold_delete_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) {
|
||||
from_c(cs)->~CrossSection();
|
||||
}
|
||||
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec* csv) {
|
||||
from_c(csv)->~CrossSectionVec();
|
||||
}
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon* p) {
|
||||
from_c(p)->~SimplePolygon();
|
||||
}
|
||||
void manifold_destruct_polygons(ManifoldPolygons* p) { from_c(p)->~Polygons(); }
|
||||
void manifold_destruct_manifold(ManifoldManifold* m) { from_c(m)->~Manifold(); }
|
||||
void manifold_destruct_manifold_vec(ManifoldManifoldVec* ms) {
|
||||
from_c(ms)->~ManifoldVec();
|
||||
}
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL* m) { from_c(m)->~MeshGL(); }
|
||||
void manifold_destruct_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
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
#include "manifold/meshIO.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
// C <-> C++ conversions
|
||||
|
||||
ManifoldMaterial* to_c(manifold::Material* m) {
|
||||
return reinterpret_cast<ManifoldMaterial*>(m);
|
||||
}
|
||||
|
||||
ManifoldExportOptions* to_c(manifold::ExportOptions* m) {
|
||||
return reinterpret_cast<ManifoldExportOptions*>(m);
|
||||
}
|
||||
|
||||
manifold::Material* from_c(ManifoldMaterial* mat) {
|
||||
return reinterpret_cast<manifold::Material*>(mat);
|
||||
}
|
||||
|
||||
manifold::ExportOptions* from_c(ManifoldExportOptions* options) {
|
||||
return reinterpret_cast<manifold::ExportOptions*>(options);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// material
|
||||
|
||||
ManifoldMaterial* manifold_material(void* mem) {
|
||||
return to_c(new (mem) manifold::Material());
|
||||
}
|
||||
|
||||
void manifold_material_set_roughness(ManifoldMaterial* mat, double roughness) {
|
||||
from_c(mat)->roughness = roughness;
|
||||
}
|
||||
|
||||
void manifold_material_set_metalness(ManifoldMaterial* mat, double metalness) {
|
||||
from_c(mat)->metalness = metalness;
|
||||
}
|
||||
|
||||
void manifold_material_set_color(ManifoldMaterial* mat, ManifoldVec3 color) {
|
||||
from_c(mat)->color = from_c(color);
|
||||
}
|
||||
|
||||
// export options
|
||||
|
||||
ManifoldExportOptions* manifold_export_options(void* mem) {
|
||||
return to_c(new (mem) manifold::ExportOptions());
|
||||
}
|
||||
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions* options,
|
||||
int faceted) {
|
||||
from_c(options)->faceted = faceted;
|
||||
}
|
||||
|
||||
void manifold_export_options_set_material(ManifoldExportOptions* options,
|
||||
ManifoldMaterial* mat) {
|
||||
from_c(options)->mat = *from_c(mat);
|
||||
}
|
||||
|
||||
// mesh IO
|
||||
|
||||
void manifold_export_meshgl(const char* filename, ManifoldMeshGL* mesh,
|
||||
ManifoldExportOptions* options) {
|
||||
manifold::ExportMesh(std::string(filename), *from_c(mesh), *from_c(options));
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_import_meshgl(void* mem, const char* filename,
|
||||
int force_cleanup) {
|
||||
auto m = manifold::ImportMesh(std::string(filename), force_cleanup);
|
||||
return to_c(new (mem) MeshGL(m));
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_material_size() { return sizeof(manifold::Material); }
|
||||
|
||||
size_t manifold_export_options_size() {
|
||||
return sizeof(manifold::ExportOptions);
|
||||
}
|
||||
|
||||
// memory free + destruction
|
||||
void manifold_delete_material(ManifoldMaterial* m) { delete (from_c(m)); }
|
||||
|
||||
void manifold_delete_export_options(ManifoldExportOptions* m) {
|
||||
delete (from_c(m));
|
||||
}
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_material(ManifoldMaterial* m) { from_c(m)->~Material(); }
|
||||
|
||||
void manifold_destruct_export_options(ManifoldExportOptions* m) {
|
||||
from_c(m)->~ExportOptions();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_min(ManifoldRect* r) {
|
||||
return to_c((*from_c(r)).min);
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_max(ManifoldRect* r) {
|
||||
return to_c((*from_c(r)).max);
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_dimensions(ManifoldRect* r) {
|
||||
auto v = from_c(r)->Size();
|
||||
return {v.x, v.y};
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_center(ManifoldRect* r) {
|
||||
auto v = from_c(r)->Center();
|
||||
return {v.x, v.y};
|
||||
}
|
||||
|
||||
double manifold_rect_scale(ManifoldRect* r) { return from_c(r)->Scale(); }
|
||||
|
||||
int manifold_rect_contains_pt(ManifoldRect* r, double x, double y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = vec2(x, y);
|
||||
return rect.Contains(p);
|
||||
}
|
||||
|
||||
int manifold_rect_contains_rect(ManifoldRect* a, ManifoldRect* b) {
|
||||
auto outer = *from_c(a);
|
||||
auto inner = *from_c(b);
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_rect_include_pt(ManifoldRect* r, double x, double y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = vec2(x, y);
|
||||
rect.Union(p);
|
||||
}
|
||||
|
||||
ManifoldRect* manifold_rect_union(void* mem, ManifoldRect* a, ManifoldRect* b) {
|
||||
auto rect = from_c(a)->Union(*from_c(b));
|
||||
return to_c(new (mem) Rect(rect));
|
||||
}
|
||||
|
||||
ManifoldRect* manifold_rect_transform(void* mem, ManifoldRect* r, 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, double x,
|
||||
double 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, double x,
|
||||
double y) {
|
||||
auto p = vec2(x, y);
|
||||
auto scaled = (*from_c(r)) * p;
|
||||
return to_c(new (mem) Rect(scaled));
|
||||
}
|
||||
|
||||
int manifold_rect_does_overlap_rect(ManifoldRect* a, ManifoldRect* r) {
|
||||
return from_c(a)->DoesOverlap(*from_c(r));
|
||||
}
|
||||
|
||||
int manifold_rect_is_empty(ManifoldRect* r) { return from_c(r)->IsEmpty(); }
|
||||
|
||||
int manifold_rect_is_finite(ManifoldRect* r) { return from_c(r)->IsFinite(); }
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -12,13 +12,75 @@
|
||||
# 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
|
||||
)
|
||||
|
||||
add_subdirectory(third_party)
|
||||
pybind11_add_module(pymanifold pymanifold.cpp)
|
||||
target_link_libraries(pymanifold PRIVATE manifold)
|
||||
target_compile_options(pymanifold PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(pymanifold PUBLIC cxx_std_14)
|
||||
target_include_directories(pymanifold
|
||||
PRIVATE ${PYBIND11_DIR}/include
|
||||
)
|
||||
message(Python_EXECUTABLE = ${Python_EXECUTABLE})
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
TARGET manifold3d_stub
|
||||
POST_BUILD
|
||||
COMMAND
|
||||
"${Python_EXECUTABLE}"
|
||||
"${PROJECT_SOURCE_DIR}/bindings/python/stub_postbuild.py"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/manifold3d.pyi"
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(manifold3d PRIVATE manifold)
|
||||
target_compile_options(
|
||||
manifold3d
|
||||
PRIVATE ${MANIFOLD_FLAGS} -DMODULE_NAME=manifold3d
|
||||
)
|
||||
set_target_properties(manifold3d PROPERTIES OUTPUT_NAME "manifold3d")
|
||||
|
||||
# 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)
|
||||
set(MANIFOLD_PYBIND_LIBDIR ${SKBUILD_PLATLIB_DIR})
|
||||
else()
|
||||
set(MANIFOLD_PYBIND_LIBDIR ${Python_SITEARCH})
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS manifold3d
|
||||
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,39 @@
|
||||
# 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 nanobind python module
|
||||
```
|
||||
pip install -m nanobind
|
||||
python -m nanobind.stubgen -m manifold3d -o manifold3d.pyi -i=. -p=bindings/python/stub_pattern.txt
|
||||
```
|
||||
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,133 @@
|
||||
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)
|
||||
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)
|
||||
m = m.simplify(0.001)
|
||||
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()
|
||||
@@ -1,79 +1,101 @@
|
||||
"""
|
||||
Copyright 2022 The Manifold Authors.
|
||||
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
|
||||
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
|
||||
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.
|
||||
"""
|
||||
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 pymanifold import Manifold
|
||||
from manifold3d import Manifold
|
||||
|
||||
# https://gist.github.com/deckar01/ef11def51de7e71d9f288c6e5819fdb7
|
||||
|
||||
INCHES = 25.4
|
||||
|
||||
brick_depth = (3 + 5/8) * INCHES
|
||||
brick_height = (2 + 1/4) * INCHES
|
||||
brick_length = (7 + 5/8) * INCHES
|
||||
brick_depth = (3 + 5 / 8) * INCHES
|
||||
brick_height = (2 + 1 / 4) * INCHES
|
||||
brick_length = (7 + 5 / 8) * INCHES
|
||||
|
||||
mortar_gap = (3/8) * INCHES
|
||||
mortar_gap = (3 / 8) * INCHES
|
||||
|
||||
|
||||
def brick():
|
||||
return Manifold.cube(brick_length, brick_depth, brick_height)
|
||||
return Manifold.cube([brick_length, brick_depth, brick_height])
|
||||
|
||||
|
||||
def halfbrick():
|
||||
return Manifold.cube((brick_length - mortar_gap)/2, brick_depth, brick_height)
|
||||
return Manifold.cube([(brick_length - mortar_gap) / 2, brick_depth, brick_height])
|
||||
|
||||
|
||||
def row(length):
|
||||
bricks = [brick().translate((brick_length + mortar_gap)*x, 0, 0)
|
||||
for x in range(length)]
|
||||
return Manifold(bricks)
|
||||
bricks = [
|
||||
brick().translate([(brick_length + mortar_gap) * x, 0, 0])
|
||||
for x in range(length)
|
||||
]
|
||||
return sum(bricks, Manifold())
|
||||
|
||||
|
||||
def wall(length, height, alternate=0):
|
||||
bricks = [row(length).translate(
|
||||
((z+alternate) % 2)*(brick_depth+mortar_gap), 0,
|
||||
(brick_height+mortar_gap)*z) for z in range(height)]
|
||||
return Manifold(bricks)
|
||||
bricks = [
|
||||
row(length).translate(
|
||||
[
|
||||
((z + alternate) % 2) * (brick_depth + mortar_gap),
|
||||
0,
|
||||
(brick_height + mortar_gap) * z,
|
||||
]
|
||||
)
|
||||
for z in range(height)
|
||||
]
|
||||
return sum(bricks, Manifold())
|
||||
|
||||
|
||||
def walls(length, width, height):
|
||||
return Manifold([
|
||||
wall(length, height),
|
||||
wall(width, height, 1).rotate(0, 0, 90).translate(brick_depth, 0, 0),
|
||||
wall(length, height, 1).translate(
|
||||
0, (width)*(brick_length+mortar_gap), 0),
|
||||
wall(width, height).rotate(0, 0, 90)
|
||||
.translate((length+0.5)*(brick_length+mortar_gap) - mortar_gap, 0, 0),
|
||||
])
|
||||
return sum(
|
||||
[
|
||||
wall(length, height),
|
||||
wall(width, height, 1).rotate([0, 0, 90]).translate([brick_depth, 0, 0]),
|
||||
wall(length, height, 1).translate(
|
||||
[0, (width) * (brick_length + mortar_gap), 0]
|
||||
),
|
||||
wall(width, height)
|
||||
.rotate([0, 0, 90])
|
||||
.translate(
|
||||
[(length + 0.5) * (brick_length + mortar_gap) - mortar_gap, 0, 0]
|
||||
),
|
||||
],
|
||||
Manifold(),
|
||||
)
|
||||
|
||||
|
||||
def floor(length, width):
|
||||
results = [walls(length, width, 1)]
|
||||
if length > 1 and width > 1:
|
||||
results.append(floor(length-1, width-1).translate(brick_depth +
|
||||
mortar_gap, brick_depth+mortar_gap, 0))
|
||||
results.append(
|
||||
floor(length - 1, width - 1).translate(
|
||||
[brick_depth + mortar_gap, brick_depth + mortar_gap, 0]
|
||||
)
|
||||
)
|
||||
if length == 1 and width > 1:
|
||||
results.append(row(width-1).rotate(0, 0, 90))
|
||||
results.append(row(width - 1).rotate((0, 0, 90)))
|
||||
if width == 1 and length > 1:
|
||||
results.append(row(length-1).translate(2*(brick_depth +
|
||||
mortar_gap), brick_depth+mortar_gap, 0))
|
||||
results.append(halfbrick().translate(
|
||||
brick_depth+mortar_gap, brick_depth+mortar_gap, 0))
|
||||
return Manifold(results)
|
||||
results.append(
|
||||
row(length - 1).translate(
|
||||
[2 * (brick_depth + mortar_gap), brick_depth + mortar_gap, 0]
|
||||
)
|
||||
)
|
||||
results.append(
|
||||
halfbrick().translate([brick_depth + mortar_gap, brick_depth + mortar_gap, 0])
|
||||
)
|
||||
return sum(results, Manifold())
|
||||
|
||||
|
||||
def run(width=10, length=10, height=10):
|
||||
return walls(length, width, height) + floor(length, width)
|
||||
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
"""
|
||||
Copyright 2022 The Manifold Authors.
|
||||
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
|
||||
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
|
||||
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 pymanifold import Manifold
|
||||
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
|
||||
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())
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from manifold3d import CrossSection, Manifold
|
||||
|
||||
|
||||
def run():
|
||||
# create a polygon
|
||||
polygon_points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
|
||||
polygons_points = [polygon_points]
|
||||
|
||||
# create a cross-section
|
||||
cross_section = CrossSection(polygons_points)
|
||||
polygons = cross_section.to_polygons()
|
||||
polygon = polygons[0]
|
||||
if set([tuple(p) for p in polygon]) != set(polygon_points):
|
||||
raise Exception(
|
||||
f"polygon={polygon} differs from polygon_points={polygon_points}"
|
||||
)
|
||||
|
||||
# extrude a polygon to create a manifold
|
||||
extruded_polygon = Manifold.extrude(cross_section, 10.0)
|
||||
eps = 0.001
|
||||
observed_volume = extruded_polygon.volume()
|
||||
expected_volume = 10.0
|
||||
if abs(observed_volume - expected_volume) > eps:
|
||||
raise Exception(
|
||||
f"observed_volume={observed_volume} differs from expected_volume={expected_volume}"
|
||||
)
|
||||
observed_surface_area = extruded_polygon.surface_area()
|
||||
expected_surface_area = 42.0
|
||||
if abs(observed_surface_area - expected_surface_area) > eps:
|
||||
raise Exception(
|
||||
f"observed_surface_area={observed_surface_area} differs from expected_surface_area={expected_surface_area}"
|
||||
)
|
||||
|
||||
# get bounding box from manifold
|
||||
observed_bbox = extruded_polygon.bounding_box()
|
||||
expected_bbox = (0.0, 0.0, 0.0, 1.0, 1.0, 10.0)
|
||||
if observed_bbox != expected_bbox:
|
||||
raise Exception(
|
||||
f"observed_bbox={observed_bbox} differs from expected_bbox={expected_bbox}"
|
||||
)
|
||||
|
||||
return extruded_polygon
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -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)
|
||||
@@ -1,32 +1,32 @@
|
||||
"""
|
||||
Copyright 2022 The Manifold Authors.
|
||||
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
|
||||
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
|
||||
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.
|
||||
"""
|
||||
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 pathlib
|
||||
import sys
|
||||
import importlib
|
||||
import trimesh
|
||||
import numpy as np
|
||||
from time import time
|
||||
|
||||
if __name__ == "__main__":
|
||||
current_file = pathlib.Path(__file__)
|
||||
current_dir = current_file.parent
|
||||
files = [f.parts[-1][:-3]
|
||||
for f in current_dir.glob('*.py') if f != current_file]
|
||||
files = [f.parts[-1][:-3] for f in current_dir.glob("*.py") if f != current_file]
|
||||
|
||||
export_models = len(sys.argv) == 2 and sys.argv[-1] == '-e'
|
||||
export_models = len(sys.argv) == 2 and sys.argv[-1] == "-e"
|
||||
|
||||
for f in files:
|
||||
module = importlib.import_module(f)
|
||||
@@ -34,9 +34,16 @@ if __name__ == "__main__":
|
||||
model = module.run()
|
||||
mesh = model.to_mesh()
|
||||
if export_models:
|
||||
if mesh.vert_properties.shape[1] > 3:
|
||||
vertices = mesh.vert_properties[:, :3]
|
||||
colors = (mesh.vert_properties[:, 3:] * 255).astype(np.uint8)
|
||||
else:
|
||||
vertices = mesh.vert_properties
|
||||
colors = None
|
||||
meshOut = trimesh.Trimesh(
|
||||
vertices=mesh.vert_pos, faces=mesh.tri_verts)
|
||||
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
|
||||
print(f'Exported model to {f}.glb')
|
||||
vertices=vertices, faces=mesh.tri_verts, vertex_colors=colors
|
||||
)
|
||||
trimesh.exchange.export.export_mesh(meshOut, f"{f}.glb", "glb")
|
||||
print(f"Exported model to {f}.glb")
|
||||
t1 = time()
|
||||
print(f'Took {(t1-t0)*1000:.1f}ms for {f}')
|
||||
print(f"Took {(t1-t0)*1000:.1f}ms for {f}")
|
||||
|
||||
@@ -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]
|
||||
@@ -0,0 +1,45 @@
|
||||
from manifold3d import Manifold
|
||||
import numpy as np
|
||||
|
||||
|
||||
def fractal(holes, hole, w, position, depth, maxDepth):
|
||||
w /= 3
|
||||
holes.append(hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]))
|
||||
if depth == maxDepth:
|
||||
return
|
||||
offsets = np.array(
|
||||
[
|
||||
[-w, -w],
|
||||
[-w, 0.0],
|
||||
[-w, w],
|
||||
[0.0, w],
|
||||
[w, w],
|
||||
[w, 0.0],
|
||||
[w, -w],
|
||||
[0.0, -w],
|
||||
]
|
||||
)
|
||||
for offset in offsets:
|
||||
fractal(holes, hole, w, position + offset, depth + 1, maxDepth)
|
||||
|
||||
|
||||
def posColors(pos, _):
|
||||
return [-p + 0.5 for p in pos] + [1.0]
|
||||
|
||||
|
||||
def run(n=1):
|
||||
result = Manifold.cube([1, 1, 1], True)
|
||||
holes = []
|
||||
fractal(holes, result, 1.0, np.array([0.0, 0.0]), 1, n)
|
||||
|
||||
hole = Manifold.compose(holes)
|
||||
|
||||
result -= hole
|
||||
result -= hole.rotate([90, 0, 0])
|
||||
result -= hole.rotate([0, 90, 0])
|
||||
|
||||
return (
|
||||
result.trim_by_plane([1, 1, 1], 0)
|
||||
.set_properties(4, posColors)
|
||||
.scale([100, 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()
|
||||
@@ -0,0 +1,8 @@
|
||||
from manifold3d import Manifold
|
||||
|
||||
|
||||
def run():
|
||||
# for some reason this causes collider error
|
||||
obj = Manifold.cube()
|
||||
obj += Manifold.cube().rotate([0, 0, 45 + 180])
|
||||
return obj
|
||||
@@ -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,883 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "autogen_docstrings.inl" // generated in build folder
|
||||
#include "manifold/cross_section.h"
|
||||
#include "manifold/manifold.h"
|
||||
#include "manifold/polygon.h"
|
||||
#include "nanobind/nanobind.h"
|
||||
#include "nanobind/ndarray.h"
|
||||
#include "nanobind/operators.h"
|
||||
#include "nanobind/stl/function.h"
|
||||
#include "nanobind/stl/optional.h"
|
||||
#include "nanobind/stl/pair.h"
|
||||
#include "nanobind/stl/tuple.h"
|
||||
#include "nanobind/stl/vector.h"
|
||||
|
||||
namespace nb = nanobind;
|
||||
using namespace manifold;
|
||||
|
||||
template <class T>
|
||||
struct la_name {};
|
||||
template <>
|
||||
struct la_name<vec3> {
|
||||
static constexpr char const name[] = "Doublex3";
|
||||
static constexpr char const multi_name[] = "DoubleNx3";
|
||||
};
|
||||
template <>
|
||||
struct la_name<vec2> {
|
||||
static constexpr char const name[] = "Doublex2";
|
||||
static constexpr char const multi_name[] = "DoubleNx2";
|
||||
};
|
||||
template <>
|
||||
struct la_name<ivec3> {
|
||||
static constexpr char const name[] = "Intx3";
|
||||
static constexpr char const multi_name[] = "IntNx3";
|
||||
};
|
||||
template <>
|
||||
struct la_name<mat3x4> {
|
||||
static constexpr char const name[] = "Double3x4";
|
||||
};
|
||||
template <>
|
||||
struct la_name<mat2x3> {
|
||||
static constexpr char const name[] = "Double2x3";
|
||||
};
|
||||
|
||||
// handle la::vecN
|
||||
template <class T, int N>
|
||||
struct nb::detail::type_caster<la::vec<T, N>> {
|
||||
using la_type = la::vec<T, N>;
|
||||
NB_TYPE_CASTER(la_type, const_name(la_name<la_type>::name));
|
||||
|
||||
bool from_python(handle src, uint8_t flags, cleanup_list* cleanup) noexcept {
|
||||
int size = PyObject_Size(src.ptr()); // negative on failure
|
||||
if (size != N) return false;
|
||||
make_caster<T> t_cast;
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (!t_cast.from_python(src[i], flags, cleanup)) return false;
|
||||
value[i] = t_cast.value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static handle from_cpp(la_type vec, rv_policy policy,
|
||||
cleanup_list* cleanup) noexcept {
|
||||
nb::list out;
|
||||
for (int i = 0; i < N; i++) out.append(vec[i]);
|
||||
return out.release();
|
||||
}
|
||||
};
|
||||
|
||||
// handle la::matMxN
|
||||
template <class T, int C, int R>
|
||||
struct nb::detail::type_caster<la::mat<T, R, C>> {
|
||||
using la_type = la::mat<T, R, C>;
|
||||
using numpy_type = nb::ndarray<nb::numpy, T, nb::shape<R, C>>;
|
||||
NB_TYPE_CASTER(la_type, const_name(la_name<la_type>::name));
|
||||
|
||||
bool from_python(handle src, uint8_t flags, cleanup_list* cleanup) noexcept {
|
||||
int rows = PyObject_Size(src.ptr()); // negative on failure
|
||||
if (rows != R) return false;
|
||||
for (int i = 0; i < R; i++) {
|
||||
const nb::object& slice = src[i];
|
||||
int cols = PyObject_Size(slice.ptr()); // negative on failure
|
||||
if (cols != C) return false;
|
||||
for (int j = 0; j < C; j++) {
|
||||
make_caster<T> t_cast;
|
||||
if (!t_cast.from_python(slice[j], flags, cleanup)) return false;
|
||||
value[j][i] = t_cast.value;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static handle from_cpp(la_type mat, rv_policy policy,
|
||||
cleanup_list* cleanup) noexcept {
|
||||
std::array<T, R * C> buffer;
|
||||
for (int i = 0; i < R; i++) {
|
||||
for (int j = 0; j < C; j++) {
|
||||
// py is (Rows, Cols), la is (Cols, Rows)
|
||||
buffer[i * C + j] = mat[j][i];
|
||||
}
|
||||
}
|
||||
numpy_type arr{buffer, {R, C}, nb::handle()};
|
||||
// we must copy the underlying data
|
||||
return make_caster<numpy_type>::from_cpp(arr, rv_policy::copy, cleanup);
|
||||
}
|
||||
};
|
||||
|
||||
// handle std::vector<la::vecN>
|
||||
template <class T, int N>
|
||||
struct nb::detail::type_caster<std::vector<la::vec<T, N>>> {
|
||||
using la_type = la::vec<T, N>;
|
||||
using numpy_type = nb::ndarray<nb::numpy, T, nb::shape<-1, N>>;
|
||||
NB_TYPE_CASTER(std::vector<la_type>,
|
||||
const_name(la_name<la_type>::multi_name));
|
||||
|
||||
bool from_python(handle src, uint8_t flags, cleanup_list* cleanup) noexcept {
|
||||
make_caster<numpy_type> arr_cast;
|
||||
if (arr_cast.from_python(src, flags, cleanup)) {
|
||||
size_t num_vec = arr_cast.value.shape(0);
|
||||
value.resize(num_vec);
|
||||
for (size_t i = 0; i < num_vec; i++) {
|
||||
for (int j = 0; j < N; j++) {
|
||||
value[i][j] = arr_cast.value(i, j);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
size_t num_vec = PyObject_Size(src.ptr()); // negative on failure
|
||||
if (num_vec == static_cast<size_t>(-1)) return false;
|
||||
value.resize(num_vec);
|
||||
for (size_t i = 0; i < num_vec; i++) {
|
||||
make_caster<la_type> vec_cast;
|
||||
if (!vec_cast.from_python(src[i], flags, cleanup)) return false;
|
||||
value[i] = vec_cast.value;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static handle from_cpp(Value vec, rv_policy policy,
|
||||
cleanup_list* cleanup) noexcept {
|
||||
size_t num_vec = vec.size();
|
||||
T* buffer = new T[num_vec * N];
|
||||
nb::capsule mem_mgr(buffer, [](void* p) noexcept { delete[] (T*)p; });
|
||||
for (size_t i = 0; i < num_vec; i++) {
|
||||
for (int j = 0; j < N; j++) {
|
||||
buffer[i * N + j] = vec[i][j];
|
||||
}
|
||||
}
|
||||
numpy_type arr{buffer, {num_vec, N}, std::move(mem_mgr)};
|
||||
// we can just do a move because we already did the copying
|
||||
return make_caster<numpy_type>::from_cpp(arr, rv_policy::move, cleanup);
|
||||
}
|
||||
};
|
||||
|
||||
// handle VecView<la::vecN>
|
||||
template <class T, int N>
|
||||
struct nb::detail::type_caster<manifold::VecView<la::vec<T, N>>> {
|
||||
using la_type = la::vec<T, N>;
|
||||
using numpy_type = nb::ndarray<nb::numpy, T, nb::shape<-1, N>>;
|
||||
NB_TYPE_CASTER(manifold::VecView<la_type>,
|
||||
const_name(la_name<la_type>::multi_name));
|
||||
|
||||
static handle from_cpp(Value vec, rv_policy policy,
|
||||
cleanup_list* cleanup) noexcept {
|
||||
size_t num_vec = vec.size();
|
||||
// assume packed struct
|
||||
static_assert(alignof(la::vec<T, N>) <= (N * sizeof(T)),
|
||||
"VecView -> numpy requires packed structs");
|
||||
static_assert(sizeof(la::vec<T, N>) == (N * sizeof(T)),
|
||||
"VecView -> numpy requires packed structs");
|
||||
numpy_type arr{vec.data(), {num_vec, N}, nb::handle()};
|
||||
// we must copy the underlying data
|
||||
return make_caster<numpy_type>::from_cpp(arr, rv_policy::copy, cleanup);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> toVector(const T* arr, size_t size) {
|
||||
return std::vector<T>(arr, arr + size);
|
||||
}
|
||||
|
||||
using namespace manifold_docstrings;
|
||||
|
||||
// strip original :params: and replace with ours
|
||||
const std::string manifold__rotate_xyz =
|
||||
manifold__rotate__x_degrees__y_degrees__z_degrees;
|
||||
const std::string manifold__rotate__v =
|
||||
manifold__rotate_xyz.substr(0, manifold__rotate_xyz.find(":param")) +
|
||||
":param v: [X, Y, Z] rotation in degrees.";
|
||||
|
||||
NB_MODULE(manifold3d, m) {
|
||||
m.doc() = "Python binding for the Manifold library.";
|
||||
|
||||
m.def("set_min_circular_angle", Quality::SetMinCircularAngle,
|
||||
nb::arg("angle"), quality__set_min_circular_angle__angle);
|
||||
|
||||
m.def("set_min_circular_edge_length", Quality::SetMinCircularEdgeLength,
|
||||
nb::arg("length"), quality__set_min_circular_edge_length__length);
|
||||
|
||||
m.def("set_circular_segments", Quality::SetCircularSegments,
|
||||
nb::arg("number"), quality__set_circular_segments__number);
|
||||
|
||||
m.def("get_circular_segments", Quality::GetCircularSegments,
|
||||
nb::arg("radius"), quality__get_circular_segments__radius);
|
||||
|
||||
m.def("triangulate", &Triangulate, nb::arg("polygons"),
|
||||
nb::arg("epsilon") = -1, nb::arg("allow_convex") = true,
|
||||
triangulate__polygons__epsilon__allow_convex);
|
||||
|
||||
nb::class_<Manifold>(m, "Manifold")
|
||||
.def(nb::init<>(), manifold__manifold)
|
||||
.def(nb::init<const MeshGL&>(), nb::arg("mesh"),
|
||||
manifold__manifold__mesh_gl)
|
||||
.def(nb::init<const MeshGL64&>(), nb::arg("mesh"),
|
||||
manifold__manifold__mesh_gl64)
|
||||
.def(nb::self + nb::self, manifold__operator_plus__q)
|
||||
.def(nb::self - nb::self, manifold__operator_minus__q)
|
||||
.def(nb::self ^ nb::self, manifold__operator_xor__q)
|
||||
.def(
|
||||
"hull", [](const Manifold& self) { return self.Hull(); },
|
||||
manifold__hull)
|
||||
.def_static(
|
||||
"batch_hull",
|
||||
[](std::vector<Manifold> ms) { return Manifold::Hull(ms); },
|
||||
nb::arg("manifolds"), manifold__hull__manifolds)
|
||||
.def_static(
|
||||
"hull_points",
|
||||
[](std::vector<vec3> pts) { return Manifold::Hull(pts); },
|
||||
nb::arg("pts"), manifold__hull__pts)
|
||||
.def("transform", &Manifold::Transform, nb::arg("m"),
|
||||
manifold__transform__m)
|
||||
.def("translate", &Manifold::Translate, nb::arg("t"),
|
||||
manifold__translate__v)
|
||||
.def("scale", &Manifold::Scale, nb::arg("v"), manifold__scale__v)
|
||||
.def(
|
||||
"scale",
|
||||
[](const Manifold& self, double s) { self.Scale({s, s, s}); },
|
||||
nb::arg("s"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily.\n\n"
|
||||
":param s: The scalar to multiply every vertex by component.")
|
||||
.def("mirror", &Manifold::Mirror, nb::arg("v"), manifold__mirror__normal)
|
||||
.def(
|
||||
"rotate",
|
||||
[](const Manifold& self, vec3 v) {
|
||||
return self.Rotate(v.x, v.y, v.z);
|
||||
},
|
||||
nb::arg("v"), manifold__rotate__v.c_str())
|
||||
.def(
|
||||
"warp",
|
||||
[](const Manifold& self, std::function<vec3(vec3)> warp_func) {
|
||||
// need a wrapper because python cant modify a reference in-place
|
||||
return self.Warp([&warp_func](vec3& v) { v = warp_func(v); });
|
||||
},
|
||||
nb::arg("warp_func"), manifold__warp__warp_func)
|
||||
.def(
|
||||
"warp_batch",
|
||||
[](const Manifold& self,
|
||||
std::function<nb::object(VecView<vec3>)> warp_func) {
|
||||
// need a wrapper because python cant modify a reference in-place
|
||||
return self.WarpBatch([&warp_func](VecView<vec3> v) {
|
||||
auto tmp = warp_func(v);
|
||||
nb::ndarray<double, nb::shape<-1, 3>, nanobind::c_contig> tmpnd;
|
||||
if (!nb::try_cast(tmp, tmpnd) || tmpnd.ndim() != 2)
|
||||
throw std::runtime_error(
|
||||
"Invalid vector shape, expected (:, 3)");
|
||||
std::copy(tmpnd.data(), tmpnd.data() + v.size() * 3,
|
||||
&v.data()->x);
|
||||
});
|
||||
},
|
||||
nb::arg("warp_func"), manifold__warp_batch__warp_func)
|
||||
.def(
|
||||
"set_properties",
|
||||
[](const Manifold& self, int newNumProp,
|
||||
const std::function<nb::object(
|
||||
vec3, const nb::ndarray<nb::numpy, const double,
|
||||
nb::c_contig>&)>& f) {
|
||||
const int oldNumProp = self.NumProp();
|
||||
return self.SetProperties(newNumProp, [newNumProp, oldNumProp, &f](
|
||||
double* newProps, vec3 v,
|
||||
const double* oldProps) {
|
||||
auto result =
|
||||
f(v, nb::ndarray<nb::numpy, const double, nb::c_contig>(
|
||||
oldProps, {static_cast<unsigned long>(oldNumProp)},
|
||||
nb::handle()));
|
||||
nb::ndarray<double, nb::shape<-1>> array;
|
||||
std::vector<double> vec;
|
||||
if (nb::try_cast(result, array)) {
|
||||
if (array.ndim() != 1 ||
|
||||
array.shape(0) != static_cast<size_t>(newNumProp))
|
||||
throw std::runtime_error("Invalid vector shape, expected (" +
|
||||
std::to_string(newNumProp) + ")");
|
||||
for (int i = 0; i < newNumProp; i++) newProps[i] = array(i);
|
||||
} else if (nb::try_cast(result, vec)) {
|
||||
for (int i = 0; i < newNumProp; i++) newProps[i] = vec[i];
|
||||
} else {
|
||||
throw std::runtime_error(
|
||||
"Callback in set_properties should return an array");
|
||||
}
|
||||
});
|
||||
},
|
||||
nb::arg("new_num_prop"), nb::arg("f"),
|
||||
manifold__set_properties__num_prop__prop_func)
|
||||
.def("calculate_curvature", &Manifold::CalculateCurvature,
|
||||
nb::arg("gaussian_idx"), nb::arg("mean_idx"),
|
||||
manifold__calculate_curvature__gaussian_idx__mean_idx)
|
||||
.def("min_gap", &Manifold::MinGap, nb::arg("other"),
|
||||
nb::arg("search_length"),
|
||||
"Returns the minimum gap between two manifolds."
|
||||
"Returns a double between 0 and searchLength.")
|
||||
.def("calculate_normals", &Manifold::CalculateNormals,
|
||||
nb::arg("normal_idx"), nb::arg("min_sharp_angle") = 60,
|
||||
manifold__calculate_normals__normal_idx__min_sharp_angle)
|
||||
.def("smooth_by_normals", &Manifold::SmoothByNormals,
|
||||
nb::arg("normal_idx"), manifold__smooth_by_normals__normal_idx)
|
||||
.def("smooth_out", &Manifold::SmoothOut, nb::arg("min_sharp_angle") = 60,
|
||||
nb::arg("min_smoothness") = 0,
|
||||
manifold__smooth_out__min_sharp_angle__min_smoothness)
|
||||
.def("refine", &Manifold::Refine, nb::arg("n"), manifold__refine__n)
|
||||
.def("refine_to_length", &Manifold::RefineToLength, nb::arg("length"),
|
||||
manifold__refine_to_length__length)
|
||||
.def("refine_to_tolerance", &Manifold::RefineToTolerance,
|
||||
nb::arg("tolerance"), manifold__refine_to_tolerance__tolerance)
|
||||
.def("to_mesh", &Manifold::GetMeshGL, nb::arg("normal_idx") = -1,
|
||||
manifold__get_mesh_gl__normal_idx)
|
||||
.def("to_mesh64", &Manifold::GetMeshGL64, nb::arg("normal_idx") = -1,
|
||||
manifold__get_mesh_gl64__normal_idx)
|
||||
.def("num_vert", &Manifold::NumVert, manifold__num_vert)
|
||||
.def("num_edge", &Manifold::NumEdge, manifold__num_edge)
|
||||
.def("num_tri", &Manifold::NumTri, manifold__num_tri)
|
||||
.def("num_prop", &Manifold::NumProp, manifold__num_prop)
|
||||
.def("num_prop_vert", &Manifold::NumPropVert, manifold__num_prop_vert)
|
||||
.def("genus", &Manifold::Genus, manifold__genus)
|
||||
.def(
|
||||
"volume", [](const Manifold& self) { return self.Volume(); },
|
||||
"Get the volume of the manifold\n This is clamped to zero for a "
|
||||
"given face if they are within the Epsilon().")
|
||||
.def(
|
||||
"surface_area",
|
||||
[](const Manifold& self) { return self.SurfaceArea(); },
|
||||
"Get the surface area of the manifold\n This is clamped to zero for "
|
||||
"a given face if they are within the Epsilon().")
|
||||
.def("original_id", &Manifold::OriginalID, manifold__original_id)
|
||||
.def("get_tolerance", &Manifold::GetTolerance, manifold__get_tolerance)
|
||||
.def("set_tolerance", &Manifold::SetTolerance,
|
||||
manifold__set_tolerance__tolerance)
|
||||
.def("simplify", &Manifold::Simplify, manifold__simplify__tolerance)
|
||||
.def("as_original", &Manifold::AsOriginal, manifold__as_original)
|
||||
.def("is_empty", &Manifold::IsEmpty, manifold__is_empty)
|
||||
.def("decompose", &Manifold::Decompose, manifold__decompose)
|
||||
.def("split", &Manifold::Split, nb::arg("cutter"),
|
||||
manifold__split__cutter)
|
||||
.def("split_by_plane", &Manifold::SplitByPlane, nb::arg("normal"),
|
||||
nb::arg("origin_offset"),
|
||||
manifold__split_by_plane__normal__origin_offset)
|
||||
.def("trim_by_plane", &Manifold::TrimByPlane, nb::arg("normal"),
|
||||
nb::arg("origin_offset"),
|
||||
manifold__trim_by_plane__normal__origin_offset)
|
||||
.def(
|
||||
"slice",
|
||||
[](const Manifold& self, double height) {
|
||||
return CrossSection(self.Slice(height));
|
||||
},
|
||||
nb::arg("height"), manifold__slice__height)
|
||||
.def(
|
||||
"project",
|
||||
[](const Manifold& self) {
|
||||
return CrossSection(self.Project()).Simplify(self.GetEpsilon());
|
||||
},
|
||||
manifold__project)
|
||||
.def("status", &Manifold::Status, manifold__status)
|
||||
.def(
|
||||
"bounding_box",
|
||||
[](const Manifold& self) {
|
||||
Box b = self.BoundingBox();
|
||||
return nb::make_tuple(b.min[0], b.min[1], b.min[2], b.max[0],
|
||||
b.max[1], b.max[2]);
|
||||
},
|
||||
"Gets the manifold bounding box as a tuple "
|
||||
"(xmin, ymin, zmin, xmax, ymax, zmax).")
|
||||
.def_static(
|
||||
"smooth",
|
||||
[](const MeshGL& mesh, std::vector<size_t> sharpened_edges,
|
||||
std::vector<double> edge_smoothness) {
|
||||
if (sharpened_edges.size() != edge_smoothness.size()) {
|
||||
throw std::runtime_error(
|
||||
"sharpened_edges.size() != edge_smoothness.size()");
|
||||
}
|
||||
std::vector<Smoothness> vec(sharpened_edges.size());
|
||||
for (size_t i = 0; i < vec.size(); i++) {
|
||||
vec[i] = {sharpened_edges[i], edge_smoothness[i]};
|
||||
}
|
||||
return Manifold::Smooth(mesh, vec);
|
||||
},
|
||||
nb::arg("mesh"), nb::arg("sharpened_edges") = nb::list(),
|
||||
nb::arg("edge_smoothness") = nb::list(),
|
||||
manifold__smooth__mesh_gl__sharpened_edges)
|
||||
.def_static(
|
||||
"smooth",
|
||||
[](const MeshGL64& mesh, std::vector<size_t> sharpened_edges,
|
||||
std::vector<double> edge_smoothness) {
|
||||
if (sharpened_edges.size() != edge_smoothness.size()) {
|
||||
throw std::runtime_error(
|
||||
"sharpened_edges.size() != edge_smoothness.size()");
|
||||
}
|
||||
std::vector<Smoothness> vec(sharpened_edges.size());
|
||||
for (size_t i = 0; i < vec.size(); i++) {
|
||||
vec[i] = {sharpened_edges[i], edge_smoothness[i]};
|
||||
}
|
||||
return Manifold::Smooth(mesh, vec);
|
||||
},
|
||||
nb::arg("mesh"), nb::arg("sharpened_edges") = nb::list(),
|
||||
nb::arg("edge_smoothness") = nb::list(),
|
||||
// note: this is not a typo, the documentation is essentially the same
|
||||
// so we just use the 32 byte variant to avoid duplicating docstring
|
||||
// override...
|
||||
manifold__smooth__mesh_gl__sharpened_edges)
|
||||
.def_static("batch_boolean", &Manifold::BatchBoolean,
|
||||
nb::arg("manifolds"), nb::arg("op"),
|
||||
manifold__batch_boolean__manifolds__op)
|
||||
.def_static("compose", &Manifold::Compose, nb::arg("manifolds"),
|
||||
manifold__compose__manifolds)
|
||||
.def_static("tetrahedron", &Manifold::Tetrahedron, manifold__tetrahedron)
|
||||
.def_static("cube", &Manifold::Cube,
|
||||
nb::arg("size") = std::make_tuple(1.0, 1.0, 1.0),
|
||||
nb::arg("center") = false, manifold__cube__size__center)
|
||||
.def_static(
|
||||
"extrude",
|
||||
[](const CrossSection& crossSection, double height, int nDivisions,
|
||||
double twistDegrees, vec2 scaleTop) {
|
||||
return Manifold::Extrude(crossSection.ToPolygons(), height,
|
||||
nDivisions, twistDegrees, scaleTop);
|
||||
},
|
||||
nb::arg("crossSection"), nb::arg("height"),
|
||||
nb::arg("n_divisions") = 0, nb::arg("twist_degrees") = 0.0f,
|
||||
nb::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
|
||||
manifold__extrude__cross_section__height__n_divisions__twist_degrees__scale_top)
|
||||
.def_static(
|
||||
"revolve",
|
||||
[](const CrossSection& crossSection, int circularSegments,
|
||||
double revolveDegrees) {
|
||||
return Manifold::Revolve(crossSection.ToPolygons(),
|
||||
circularSegments, revolveDegrees);
|
||||
},
|
||||
nb::arg("crossSection"), nb::arg("circular_segments") = 0,
|
||||
nb::arg("revolve_degrees") = 360.0,
|
||||
manifold__revolve__cross_section__circular_segments__revolve_degrees)
|
||||
.def_static(
|
||||
"level_set",
|
||||
[](const std::function<double(double, double, double)>& f,
|
||||
std::vector<double> bounds, double edgeLength, double level = 0.0,
|
||||
double tolerance = -1) {
|
||||
// Same format as Manifold.bounding_box
|
||||
Box bound = {vec3(bounds[0], bounds[1], bounds[2]),
|
||||
vec3(bounds[3], bounds[4], bounds[5])};
|
||||
|
||||
std::function<double(vec3)> cppToPython = [&f](vec3 v) {
|
||||
return f(v.x, v.y, v.z);
|
||||
};
|
||||
return Manifold::LevelSet(cppToPython, bound, edgeLength, level,
|
||||
tolerance, false);
|
||||
},
|
||||
nb::arg("f"), nb::arg("bounds"), nb::arg("edgeLength"),
|
||||
nb::arg("level") = 0.0, nb::arg("tolerance") = -1,
|
||||
manifold__level_set__sdf__bounds__edge_length__level__tolerance__can_parallel)
|
||||
.def_static(
|
||||
"cylinder", &Manifold::Cylinder, nb::arg("height"),
|
||||
nb::arg("radius_low"), nb::arg("radius_high") = -1.0f,
|
||||
nb::arg("circular_segments") = 0, nb::arg("center") = false,
|
||||
manifold__cylinder__height__radius_low__radius_high__circular_segments__center)
|
||||
.def_static("sphere", &Manifold::Sphere, nb::arg("radius"),
|
||||
nb::arg("circular_segments") = 0,
|
||||
manifold__sphere__radius__circular_segments)
|
||||
.def_static("reserve_ids", Manifold::ReserveIDs, nb::arg("n"),
|
||||
manifold__reserve_ids__n);
|
||||
|
||||
nb::class_<MeshGL>(m, "Mesh")
|
||||
.def(
|
||||
// note that reshape requires mutable ndarray, but this will not
|
||||
// affect the original array passed into the function
|
||||
"__init__",
|
||||
[](MeshGL* self,
|
||||
nb::ndarray<float, nb::shape<-1, -1>, nb::c_contig>& vertProp,
|
||||
nb::ndarray<uint32_t, nb::shape<-1, 3>, nb::c_contig>& triVerts,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<-1>,
|
||||
nb::c_contig>>& mergeFromVert,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<-1>,
|
||||
nb::c_contig>>& mergeToVert,
|
||||
const std::optional<
|
||||
nb::ndarray<uint32_t, nb::shape<-1>, nb::c_contig>>& runIndex,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<-1>,
|
||||
nb::c_contig>>& runOriginalID,
|
||||
std::optional<nb::ndarray<float, nb::shape<-1, 4, 3>,
|
||||
nb::c_contig>>& runTransform,
|
||||
const std::optional<
|
||||
nb::ndarray<uint32_t, nb::shape<-1>, nb::c_contig>>& faceID,
|
||||
const std::optional<nb::ndarray<float, nb::shape<-1, 3, 4>,
|
||||
nb::c_contig>>& halfedgeTangent,
|
||||
float tolerance) {
|
||||
new (self) MeshGL();
|
||||
MeshGL& out = *self;
|
||||
out.numProp = vertProp.shape(1);
|
||||
out.vertProperties = toVector(vertProp.data(), vertProp.size());
|
||||
|
||||
out.triVerts = toVector(triVerts.data(), triVerts.size());
|
||||
|
||||
if (mergeFromVert.has_value())
|
||||
out.mergeFromVert =
|
||||
toVector(mergeFromVert->data(), mergeFromVert->size());
|
||||
|
||||
if (mergeToVert.has_value())
|
||||
out.mergeToVert =
|
||||
toVector(mergeToVert->data(), mergeToVert->size());
|
||||
|
||||
if (runIndex.has_value())
|
||||
out.runIndex = toVector(runIndex->data(), runIndex->size());
|
||||
|
||||
if (runOriginalID.has_value())
|
||||
out.runOriginalID =
|
||||
toVector(runOriginalID->data(), runOriginalID->size());
|
||||
|
||||
if (runTransform.has_value()) {
|
||||
out.runTransform =
|
||||
toVector(runTransform->data(), runTransform->size());
|
||||
}
|
||||
|
||||
if (faceID.has_value())
|
||||
out.faceID = toVector(faceID->data(), faceID->size());
|
||||
|
||||
if (halfedgeTangent.has_value()) {
|
||||
out.halfedgeTangent =
|
||||
toVector(halfedgeTangent->data(), halfedgeTangent->size());
|
||||
}
|
||||
},
|
||||
nb::arg("vert_properties"), nb::arg("tri_verts"),
|
||||
nb::arg("merge_from_vert") = nb::none(),
|
||||
nb::arg("merge_to_vert") = nb::none(),
|
||||
nb::arg("run_index") = nb::none(),
|
||||
nb::arg("run_original_id") = nb::none(),
|
||||
nb::arg("run_transform") = nb::none(),
|
||||
nb::arg("face_id") = nb::none(),
|
||||
nb::arg("halfedge_tangent") = nb::none(), nb::arg("tolerance") = 0)
|
||||
.def_prop_ro(
|
||||
"vert_properties",
|
||||
[](const MeshGL& self) {
|
||||
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
self.vertProperties.data(),
|
||||
{self.vertProperties.size() / self.numProp, self.numProp},
|
||||
nb::handle());
|
||||
},
|
||||
nb::rv_policy::reference_internal)
|
||||
.def_prop_ro(
|
||||
"tri_verts",
|
||||
[](const MeshGL& self) {
|
||||
return nb::ndarray<nb::numpy, const int, nb::c_contig>(
|
||||
self.triVerts.data(), {self.triVerts.size() / 3, 3},
|
||||
nb::handle());
|
||||
},
|
||||
nb::rv_policy::reference_internal)
|
||||
.def_prop_ro(
|
||||
"run_transform",
|
||||
[](const MeshGL& self) {
|
||||
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
self.runTransform.data(), {self.runTransform.size() / 12, 4, 3},
|
||||
nb::handle());
|
||||
},
|
||||
nb::rv_policy::reference_internal)
|
||||
.def_prop_ro(
|
||||
"halfedge_tangent",
|
||||
[](const MeshGL& self) {
|
||||
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
self.halfedgeTangent.data(),
|
||||
{self.halfedgeTangent.size() / 12, 3, 4}, nb::handle());
|
||||
},
|
||||
nb::rv_policy::reference_internal)
|
||||
.def_ro("merge_from_vert", &MeshGL::mergeFromVert)
|
||||
.def_ro("merge_to_vert", &MeshGL::mergeToVert)
|
||||
.def_ro("run_index", &MeshGL::runIndex)
|
||||
.def_ro("run_original_id", &MeshGL::runOriginalID)
|
||||
.def_ro("face_id", &MeshGL::faceID)
|
||||
.def("merge", &MeshGL::Merge, mesh_gl__merge);
|
||||
|
||||
nb::class_<MeshGL64>(m, "Mesh64")
|
||||
.def(
|
||||
// note that reshape requires mutable ndarray, but this will not
|
||||
// affect the original array passed into the function
|
||||
"__init__",
|
||||
[](MeshGL64* self,
|
||||
nb::ndarray<double, nb::shape<-1, -1>, nb::c_contig>& vertProp,
|
||||
nb::ndarray<uint64_t, nb::shape<-1, 3>, nb::c_contig>& triVerts,
|
||||
const std::optional<nb::ndarray<uint64_t, nb::shape<-1>,
|
||||
nb::c_contig>>& mergeFromVert,
|
||||
const std::optional<nb::ndarray<uint64_t, nb::shape<-1>,
|
||||
nb::c_contig>>& mergeToVert,
|
||||
const std::optional<
|
||||
nb::ndarray<uint64_t, nb::shape<-1>, nb::c_contig>>& runIndex,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<-1>,
|
||||
nb::c_contig>>& runOriginalID,
|
||||
std::optional<nb::ndarray<double, nb::shape<-1, 4, 3>,
|
||||
nb::c_contig>>& runTransform,
|
||||
const std::optional<
|
||||
nb::ndarray<uint64_t, nb::shape<-1>, nb::c_contig>>& faceID,
|
||||
const std::optional<nb::ndarray<double, nb::shape<-1, 3, 4>,
|
||||
nb::c_contig>>& halfedgeTangent,
|
||||
float tolerance) {
|
||||
new (self) MeshGL64();
|
||||
MeshGL64& out = *self;
|
||||
out.numProp = vertProp.shape(1);
|
||||
out.vertProperties = toVector(vertProp.data(), vertProp.size());
|
||||
|
||||
out.triVerts = toVector(triVerts.data(), triVerts.size());
|
||||
|
||||
if (mergeFromVert.has_value())
|
||||
out.mergeFromVert =
|
||||
toVector(mergeFromVert->data(), mergeFromVert->size());
|
||||
|
||||
if (mergeToVert.has_value())
|
||||
out.mergeToVert =
|
||||
toVector(mergeToVert->data(), mergeToVert->size());
|
||||
|
||||
if (runIndex.has_value())
|
||||
out.runIndex = toVector(runIndex->data(), runIndex->size());
|
||||
|
||||
if (runOriginalID.has_value())
|
||||
out.runOriginalID =
|
||||
toVector(runOriginalID->data(), runOriginalID->size());
|
||||
|
||||
if (runTransform.has_value()) {
|
||||
out.runTransform =
|
||||
toVector(runTransform->data(), runTransform->size());
|
||||
}
|
||||
|
||||
if (faceID.has_value())
|
||||
out.faceID = toVector(faceID->data(), faceID->size());
|
||||
|
||||
if (halfedgeTangent.has_value()) {
|
||||
out.halfedgeTangent =
|
||||
toVector(halfedgeTangent->data(), halfedgeTangent->size());
|
||||
}
|
||||
},
|
||||
nb::arg("vert_properties"), nb::arg("tri_verts"),
|
||||
nb::arg("merge_from_vert") = nb::none(),
|
||||
nb::arg("merge_to_vert") = nb::none(),
|
||||
nb::arg("run_index") = nb::none(),
|
||||
nb::arg("run_original_id") = nb::none(),
|
||||
nb::arg("run_transform") = nb::none(),
|
||||
nb::arg("face_id") = nb::none(),
|
||||
nb::arg("halfedge_tangent") = nb::none(), nb::arg("tolerance") = 0)
|
||||
.def_prop_ro(
|
||||
"vert_properties",
|
||||
[](const MeshGL64& self) {
|
||||
return nb::ndarray<nb::numpy, const double, nb::c_contig>(
|
||||
self.vertProperties.data(),
|
||||
{self.vertProperties.size() / self.numProp, self.numProp},
|
||||
nb::handle());
|
||||
},
|
||||
nb::rv_policy::reference_internal)
|
||||
.def_prop_ro(
|
||||
"tri_verts",
|
||||
[](const MeshGL64& self) {
|
||||
return nb::ndarray<nb::numpy, const uint64_t, nb::c_contig>(
|
||||
self.triVerts.data(), {self.triVerts.size() / 3, 3},
|
||||
nb::handle());
|
||||
},
|
||||
nb::rv_policy::reference_internal)
|
||||
.def_prop_ro(
|
||||
"run_transform",
|
||||
[](const MeshGL64& self) {
|
||||
return nb::ndarray<nb::numpy, const double, nb::c_contig>(
|
||||
self.runTransform.data(), {self.runTransform.size() / 12, 4, 3},
|
||||
nb::handle());
|
||||
},
|
||||
nb::rv_policy::reference_internal)
|
||||
.def_prop_ro(
|
||||
"halfedge_tangent",
|
||||
[](const MeshGL64& self) {
|
||||
return nb::ndarray<nb::numpy, const double, nb::c_contig>(
|
||||
self.halfedgeTangent.data(),
|
||||
{self.halfedgeTangent.size() / 12, 3, 4}, nb::handle());
|
||||
},
|
||||
nb::rv_policy::reference_internal)
|
||||
.def_ro("merge_from_vert", &MeshGL64::mergeFromVert)
|
||||
.def_ro("merge_to_vert", &MeshGL64::mergeToVert)
|
||||
.def_ro("run_index", &MeshGL64::runIndex)
|
||||
.def_ro("run_original_id", &MeshGL64::runOriginalID)
|
||||
.def_ro("face_id", &MeshGL64::faceID)
|
||||
.def("merge", &MeshGL64::Merge, mesh_gl__merge);
|
||||
|
||||
nb::enum_<Manifold::Error>(m, "Error")
|
||||
.value("NoError", Manifold::Error::NoError)
|
||||
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
|
||||
.value("NotManifold", Manifold::Error::NotManifold)
|
||||
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
|
||||
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
|
||||
.value("MissingPositionProperties",
|
||||
Manifold::Error::MissingPositionProperties)
|
||||
.value("MergeVectorsDifferentLengths",
|
||||
Manifold::Error::MergeVectorsDifferentLengths)
|
||||
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
|
||||
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
|
||||
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
|
||||
|
||||
nb::enum_<CrossSection::FillRule>(m, "FillRule")
|
||||
.value("EvenOdd", CrossSection::FillRule::EvenOdd,
|
||||
"Only odd numbered sub-regions are filled.")
|
||||
.value("NonZero", CrossSection::FillRule::NonZero,
|
||||
"Only non-zero sub-regions are filled.")
|
||||
.value("Positive", CrossSection::FillRule::Positive,
|
||||
"Only sub-regions with winding counts > 0 are filled.")
|
||||
.value("Negative", CrossSection::FillRule::Negative,
|
||||
"Only sub-regions with winding counts < 0 are filled.");
|
||||
|
||||
nb::enum_<CrossSection::JoinType>(m, "JoinType")
|
||||
.value("Square", CrossSection::JoinType::Square,
|
||||
"Squaring is applied uniformly at all joins where the internal "
|
||||
"join angle is less that 90 degrees. The squared edge will be at "
|
||||
"exactly the offset distance from the join vertex.")
|
||||
.value("Round", CrossSection::JoinType::Round,
|
||||
"Rounding is applied to all joins that have convex external "
|
||||
"angles, and it maintains the exact offset distance from the join "
|
||||
"vertex.")
|
||||
.value(
|
||||
"Miter", CrossSection::JoinType::Miter,
|
||||
"There's a necessary limit to mitered joins (to avoid narrow "
|
||||
"angled joins producing excessively long and narrow "
|
||||
"[spikes](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/"
|
||||
"Classes/ClipperOffset/Properties/MiterLimit.htm)). So where "
|
||||
"mitered joins would exceed a given maximum miter distance "
|
||||
"(relative to the offset distance), these are 'squared' instead.")
|
||||
.value("Bevel", CrossSection::JoinType::Bevel,
|
||||
"Bevelled joins are similar to 'squared' joins except that "
|
||||
"squaring won't occur at a fixed distance. While bevelled joins "
|
||||
"may not be as pretty as squared joins, bevelling is much easier "
|
||||
"(ie faster) than squaring. And perhaps this is why bevelling "
|
||||
"rather than squaring is preferred in numerous graphics display "
|
||||
"formats (including [SVG](https://developer.mozilla.org/en-US/"
|
||||
"docs/Web/SVG/Attribute/stroke-linejoin) and [PDF]"
|
||||
"(https://helpx.adobe.com/indesign/using/"
|
||||
"applying-line-stroke-settings.html) document formats).");
|
||||
|
||||
nb::enum_<OpType>(m, "OpType", "Operation types for batch_boolean")
|
||||
.value("Add", OpType::Add)
|
||||
.value("Subtract", OpType::Subtract)
|
||||
.value("Intersect", OpType::Intersect);
|
||||
|
||||
nb::class_<CrossSection>(
|
||||
m, "CrossSection",
|
||||
"Two-dimensional cross sections guaranteed to be without "
|
||||
"self-intersections, or overlaps between polygons (from construction "
|
||||
"onwards). This class makes use of the "
|
||||
"[Clipper2](http://www.angusj.com/clipper2/Docs/Overview.htm) library "
|
||||
"for polygon clipping (boolean) and offsetting operations.")
|
||||
.def(nb::init<>(), cross_section__cross_section)
|
||||
.def(nb::init<std::vector<std::vector<vec2>>, CrossSection::FillRule>(),
|
||||
nb::arg("contours"),
|
||||
nb::arg("fillrule") = CrossSection::FillRule::Positive,
|
||||
cross_section__cross_section__contours__fillrule)
|
||||
.def("area", &CrossSection::Area, cross_section__area)
|
||||
.def("num_vert", &CrossSection::NumVert, cross_section__num_vert)
|
||||
.def("num_contour", &CrossSection::NumContour, cross_section__num_contour)
|
||||
.def("is_empty", &CrossSection::IsEmpty, cross_section__is_empty)
|
||||
.def(
|
||||
"bounds",
|
||||
[](const CrossSection& self) {
|
||||
Rect r = self.Bounds();
|
||||
return nb::make_tuple(r.min[0], r.min[1], r.max[0], r.max[1]);
|
||||
},
|
||||
"Return bounding box of CrossSection as tuple("
|
||||
"min_x, min_y, max_x, max_y)")
|
||||
.def("translate", &CrossSection::Translate, nb::arg("v"),
|
||||
cross_section__translate__v)
|
||||
.def("rotate", &CrossSection::Rotate, nb::arg("degrees"),
|
||||
cross_section__rotate__degrees)
|
||||
.def("scale", &CrossSection::Scale, nb::arg("scale"),
|
||||
cross_section__scale__scale)
|
||||
.def(
|
||||
"scale",
|
||||
[](const CrossSection& self, double s) { self.Scale({s, s}); },
|
||||
nb::arg("s"),
|
||||
"Scale this CrossSection in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param s: The scalar to multiply every vertex by per component.")
|
||||
.def("mirror", &CrossSection::Mirror, nb::arg("ax"),
|
||||
cross_section__mirror__ax)
|
||||
.def("transform", &CrossSection::Transform, nb::arg("m"),
|
||||
cross_section__transform__m)
|
||||
.def(
|
||||
"warp",
|
||||
[](const CrossSection& self, std::function<vec2(vec2)> warp_func) {
|
||||
// need a wrapper because python cant modify a reference in-place
|
||||
return self.Warp([&warp_func](vec2& v) { v = warp_func(v); });
|
||||
},
|
||||
nb::arg("warp_func"), cross_section__warp__warp_func)
|
||||
.def("warp_batch", &CrossSection::WarpBatch, nb::arg("warp_func"),
|
||||
cross_section__warp_batch__warp_func)
|
||||
|
||||
.def(
|
||||
"warp_batch",
|
||||
[](const CrossSection& self,
|
||||
std::function<nb::object(VecView<vec2>)> warp_func) {
|
||||
// need a wrapper because python cant modify a reference in-place
|
||||
return self.WarpBatch([&warp_func](VecView<vec2> v) {
|
||||
auto tmp = warp_func(v);
|
||||
nb::ndarray<double, nb::shape<-1, 2>, nanobind::c_contig> tmpnd;
|
||||
if (!nb::try_cast(tmp, tmpnd) || tmpnd.ndim() != 2)
|
||||
throw std::runtime_error(
|
||||
"Invalid vector shape, expected (:, 2)");
|
||||
std::copy(tmpnd.data(), tmpnd.data() + v.size() * 2,
|
||||
&v.data()->x);
|
||||
});
|
||||
},
|
||||
nb::arg("warp_func"), cross_section__warp_batch__warp_func)
|
||||
.def("simplify", &CrossSection::Simplify, nb::arg("epsilon") = 1e-6,
|
||||
cross_section__simplify__epsilon)
|
||||
.def(
|
||||
"offset", &CrossSection::Offset, nb::arg("delta"),
|
||||
nb::arg("join_type") = CrossSection::JoinType::Round,
|
||||
nb::arg("miter_limit") = 2.0, nb::arg("circular_segments") = 0,
|
||||
cross_section__offset__delta__jointype__miter_limit__circular_segments)
|
||||
.def(nb::self + nb::self, cross_section__operator_plus__q)
|
||||
.def(nb::self - nb::self, cross_section__operator_minus__q)
|
||||
.def(nb::self ^ nb::self, cross_section__operator_xor__q)
|
||||
.def(
|
||||
"hull", [](const CrossSection& self) { return self.Hull(); },
|
||||
cross_section__hull)
|
||||
.def_static(
|
||||
"batch_hull",
|
||||
[](std::vector<CrossSection> cs) { return CrossSection::Hull(cs); },
|
||||
nb::arg("cross_sections"), cross_section__hull__cross_sections)
|
||||
.def_static(
|
||||
"hull_points",
|
||||
[](std::vector<vec2> pts) { return CrossSection::Hull(pts); },
|
||||
nb::arg("pts"), cross_section__hull__pts)
|
||||
.def("decompose", &CrossSection::Decompose, cross_section__decompose)
|
||||
.def_static("batch_boolean", &CrossSection::BatchBoolean,
|
||||
nb::arg("cross_sections"), nb::arg("op"),
|
||||
cross_section__batch_boolean__cross_sections__op)
|
||||
.def_static("compose", &CrossSection::Compose, nb::arg("cross_sections"),
|
||||
cross_section__compose__cross_sections)
|
||||
.def("to_polygons", &CrossSection::ToPolygons, cross_section__to_polygons)
|
||||
.def(
|
||||
"extrude",
|
||||
[](const CrossSection& self, double height, int nDivisions,
|
||||
double twistDegrees, vec2 scaleTop) {
|
||||
return Manifold::Extrude(self.ToPolygons(), height, nDivisions,
|
||||
twistDegrees, scaleTop);
|
||||
},
|
||||
nb::arg("height"), nb::arg("n_divisions") = 0,
|
||||
nb::arg("twist_degrees") = 0.0f,
|
||||
nb::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
|
||||
manifold__extrude__cross_section__height__n_divisions__twist_degrees__scale_top)
|
||||
.def(
|
||||
"revolve",
|
||||
[](const CrossSection& self, int circularSegments,
|
||||
double revolveDegrees) {
|
||||
return Manifold::Revolve(self.ToPolygons(), circularSegments,
|
||||
revolveDegrees);
|
||||
},
|
||||
nb::arg("circular_segments") = 0, nb::arg("revolve_degrees") = 360.0,
|
||||
manifold__revolve__cross_section__circular_segments__revolve_degrees)
|
||||
|
||||
.def_static("square", &CrossSection::Square, nb::arg("size"),
|
||||
nb::arg("center") = false,
|
||||
cross_section__square__size__center)
|
||||
.def_static("circle", &CrossSection::Circle, nb::arg("radius"),
|
||||
nb::arg("circular_segments") = 0,
|
||||
cross_section__circle__radius__circular_segments);
|
||||
}
|
||||
@@ -1,504 +0,0 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "manifold.h"
|
||||
#include "pybind11/functional.h"
|
||||
#include "pybind11/numpy.h"
|
||||
#include "pybind11/operators.h"
|
||||
#include "pybind11/pybind11.h"
|
||||
#include "pybind11/stl.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
typedef std::tuple<float, float> Float2;
|
||||
typedef std::tuple<float, float, float> Float3;
|
||||
|
||||
struct PolygonsWrapper {
|
||||
std::unique_ptr<Polygons> polygons;
|
||||
};
|
||||
|
||||
PYBIND11_MODULE(pymanifold, m) {
|
||||
m.doc() =
|
||||
"Python binding for the manifold library. Please check the C++ "
|
||||
"documentation for APIs.\n"
|
||||
"This binding will perform copying to make the API more familiar to "
|
||||
"OpenSCAD users.";
|
||||
py::class_<Manifold>(m, "Manifold")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::vector<Manifold> &manifolds) {
|
||||
Manifold result;
|
||||
for (Manifold &manifold : manifolds) result += manifold;
|
||||
return result;
|
||||
}),
|
||||
"Construct manifold as the union of a set of manifolds.")
|
||||
.def(py::self + py::self, "Boolean union.")
|
||||
.def(py::self - py::self, "Boolean difference.")
|
||||
.def(py::self ^ py::self, "Boolean intersection.")
|
||||
.def(
|
||||
"transform",
|
||||
[](Manifold self, py::array_t<float> &mat) {
|
||||
auto mat_view = mat.unchecked<2>();
|
||||
if (mat_view.shape(0) != 3 || mat_view.shape(1) != 4)
|
||||
throw std::runtime_error("Invalid matrix shape");
|
||||
glm::mat4x3 mat_glm;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
mat_glm[j][i] = mat_view(i, j);
|
||||
}
|
||||
}
|
||||
return self.Transform(mat_glm);
|
||||
},
|
||||
py::arg("m"),
|
||||
"Transform this Manifold in space. The first three columns form a "
|
||||
"3x3 matrix\n"
|
||||
"transform and the last is a translation vector. This operation can "
|
||||
"be\n"
|
||||
"chained. Transforms are combined and applied lazily.\n"
|
||||
"\n"
|
||||
":param m: The affine transform matrix to apply to all the vertices.")
|
||||
.def(
|
||||
"translate",
|
||||
[](Manifold self, float x = 0.0f, float y = 0.0f, float z = 0.0f) {
|
||||
return self.Translate(glm::vec3(x, y, z));
|
||||
},
|
||||
py::arg("x") = 0.0f, py::arg("y") = 0.0f, py::arg("z") = 0.0f,
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
":param x: X axis translation. (default 0.0).\n"
|
||||
":param y: Y axis translation. (default 0.0).\n"
|
||||
":param z: Z axis translation. (default 0.0).")
|
||||
.def(
|
||||
"translate",
|
||||
[](Manifold self, py::array_t<float> &t) {
|
||||
auto t_view = t.unchecked<1>();
|
||||
if (t.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return self.Translate(glm::vec3(t_view(0), t_view(1), t_view(2)));
|
||||
},
|
||||
py::arg("t"),
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
":param v: The vector to add to every vertex.")
|
||||
.def("scale",
|
||||
static_cast<Manifold (*)(Manifold, float)>(
|
||||
[](Manifold self, float scale) {
|
||||
return self.Scale(glm::vec3(scale));
|
||||
}),
|
||||
py::arg("scale"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
":param scale: The scalar multiplier for each component of every "
|
||||
"vertices.")
|
||||
.def(
|
||||
"scale",
|
||||
[](Manifold self, py::array_t<float> &scale) {
|
||||
auto scale_view = scale.unchecked<1>();
|
||||
if (scale_view.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
glm::vec3 v(scale_view(0), scale_view(1), scale_view(2));
|
||||
return self.Scale(v);
|
||||
},
|
||||
py::arg("v"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
":param v: The vector to multiply every vertex by component.")
|
||||
.def(
|
||||
"rotate",
|
||||
[](Manifold self, py::array_t<float> &v) {
|
||||
auto v_view = v.unchecked<1>();
|
||||
if (v_view.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return self.Rotate(v_view(0), v_view(1), v_view(2));
|
||||
},
|
||||
py::arg("v"),
|
||||
"Applies an Euler angle rotation to the manifold, first about the X "
|
||||
"axis, then\n"
|
||||
"Y, then Z, in degrees. We use degrees so that we can minimize "
|
||||
"rounding error,\n"
|
||||
"and eliminate it completely for any multiples of 90 degrees. "
|
||||
"Additionally, more\n"
|
||||
"efficient code paths are used to update the manifold when the "
|
||||
"transforms only\n"
|
||||
"rotate by multiples of 90 degrees. This operation can be chained. "
|
||||
"Transforms\n"
|
||||
"are combined and applied lazily.\n"
|
||||
"\n"
|
||||
":param v: [X, Y, Z] rotation in degrees.")
|
||||
.def(
|
||||
"rotate",
|
||||
[](Manifold self, float xDegrees = 0.0f, float yDegrees = 0.0f,
|
||||
float zDegrees = 0.0f) {
|
||||
return self.Rotate(xDegrees, yDegrees, zDegrees);
|
||||
},
|
||||
py::arg("x_degrees") = 0.0f, py::arg("y_degrees") = 0.0f,
|
||||
py::arg("z_degrees") = 0.0f,
|
||||
"Applies an Euler angle rotation to the manifold, first about the X "
|
||||
"axis, then\n"
|
||||
"Y, then Z, in degrees. We use degrees so that we can minimize "
|
||||
"rounding error,\n"
|
||||
"and eliminate it completely for any multiples of 90 degrees. "
|
||||
"Additionally, more\n"
|
||||
"efficient code paths are used to update the manifold when the "
|
||||
"transforms only\n"
|
||||
"rotate by multiples of 90 degrees. This operation can be chained. "
|
||||
"Transforms\n"
|
||||
"are combined and applied lazily.\n"
|
||||
"\n"
|
||||
":param x: X rotation in degrees. (default 0.0).\n"
|
||||
":param y: Y rotation in degrees. (default 0.0).\n"
|
||||
":param z: Z rotation in degrees. (default 0.0).")
|
||||
.def(
|
||||
"warp",
|
||||
[](Manifold self, const std::function<Float3(Float3)> &f) {
|
||||
return self.Warp([&f](glm::vec3 &v) {
|
||||
Float3 fv = f(std::make_tuple(v.x, v.y, v.z));
|
||||
v.x = std::get<0>(fv);
|
||||
v.y = std::get<1>(fv);
|
||||
v.z = std::get<2>(fv);
|
||||
});
|
||||
},
|
||||
py::arg("f"))
|
||||
.def(
|
||||
"refine", [](Manifold self, int n) { return self.Refine(n); },
|
||||
py::arg("n"),
|
||||
"Increase the density of the mesh by splitting every edge into n "
|
||||
"pieces. For\n"
|
||||
"instance, with n = 2, each triangle will be split into 4 triangles. "
|
||||
"These\n"
|
||||
"will all be coplanar (and will not be immediately collapsed) unless "
|
||||
"the\n"
|
||||
"Mesh/Manifold has halfedgeTangents specified (e.g. from the "
|
||||
"Smooth()\n"
|
||||
"constructor), in which case the new vertices will be moved to the\n"
|
||||
"interpolated surface according to their barycentric coordinates.\n"
|
||||
"\n"
|
||||
":param n: The number of pieces to split every edge into. Must be > "
|
||||
"1.")
|
||||
.def("to_mesh", &Manifold::GetMesh)
|
||||
.def_static("smooth", Manifold::Smooth,
|
||||
|
||||
"Constructs a smooth version of the input mesh by creating "
|
||||
"tangents; this\n"
|
||||
"method will throw if you have supplied tangents with your "
|
||||
"mesh already. The\n"
|
||||
"actual triangle resolution is unchanged; use the Refine() "
|
||||
"method to\n"
|
||||
"interpolate to a higher-resolution curve.\n"
|
||||
"\n"
|
||||
"By default, every edge is calculated for maximum smoothness "
|
||||
"(very much\n"
|
||||
"approximately), attempting to minimize the maximum mean "
|
||||
"Curvature magnitude.\n"
|
||||
"No higher-order derivatives are considered, as the "
|
||||
"interpolation is\n"
|
||||
"independent per triangle, only sharing constraints on their "
|
||||
"boundaries.\n"
|
||||
"\n"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector "
|
||||
"of sharpened\n"
|
||||
"halfedges, which should in general be a small subset of all "
|
||||
"halfedges. Order\n"
|
||||
"of entries doesn't matter, as each one specifies the "
|
||||
"desired smoothness\n"
|
||||
"(between zero and one, with one the default for all "
|
||||
"unspecified halfedges)\n"
|
||||
"and the halfedge index (3 * triangle index + [0,1,2] where "
|
||||
"0 is the edge\n"
|
||||
"between triVert 0 and 1, etc).\n"
|
||||
"\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is\n"
|
||||
"interpolated along each edge, so the specified value should "
|
||||
"be thought of as\n"
|
||||
"an average. Where exactly two sharpened edges meet at a "
|
||||
"vertex, their\n"
|
||||
"tangents are rotated to be colinear so that the sharpened "
|
||||
"edge can be\n"
|
||||
"continuous. Vertices with only one sharpened edge are "
|
||||
"completely smooth,\n"
|
||||
"allowing sharpened edges to smoothly vanish at termination. "
|
||||
"A single vertex\n"
|
||||
"can be sharpened by sharping all edges that are incident on "
|
||||
"it, allowing\n"
|
||||
"cones to be formed.")
|
||||
.def_static(
|
||||
"from_mesh", [](const Mesh &mesh) { return Manifold(mesh); },
|
||||
py::arg("mesh"))
|
||||
.def_static(
|
||||
"tetrahedron", []() { return Manifold::Tetrahedron(); },
|
||||
"Constructs a tetrahedron centered at the origin with one vertex at "
|
||||
"(1,1,1)\n"
|
||||
"and the rest at similarly symmetric points.")
|
||||
.def_static(
|
||||
"cube",
|
||||
[](Float3 size, bool center = false) {
|
||||
return Manifold::Cube(
|
||||
glm::vec3(std::get<0>(size), std::get<1>(size),
|
||||
std::get<2>(size)),
|
||||
center);
|
||||
},
|
||||
py::arg("size") = std::make_tuple(1.0f, 1.0f, 1.0f),
|
||||
py::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first\n"
|
||||
"octant, touching the origin.\n"
|
||||
"\n"
|
||||
":param size: The X, Y, and Z dimensions of the box.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"cube",
|
||||
[](py::array_t<float> &size, bool center = false) {
|
||||
auto size_view = size.unchecked<1>();
|
||||
if (size_view.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return Manifold::Cube(
|
||||
glm::vec3(size_view(0), size_view(1), size_view(2)), center);
|
||||
},
|
||||
py::arg("size"), py::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first\n"
|
||||
"octant, touching the origin.\n"
|
||||
"\n"
|
||||
":param size: The X, Y, and Z dimensions of the box.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"cube",
|
||||
[](float x, float y, float z, bool center = false) {
|
||||
return Manifold::Cube(glm::vec3(x, y, z), center);
|
||||
},
|
||||
py::arg("x"), py::arg("y"), py::arg("z"), py::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first\n"
|
||||
"octant, touching the origin.\n"
|
||||
"\n"
|
||||
":param x: The X dimensions of the box.\n"
|
||||
":param y: The Y dimensions of the box.\n"
|
||||
":param z: The Z dimensions of the box.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"cylinder",
|
||||
[](float height, float radiusLow, float radiusHigh = -1.0f,
|
||||
int circularSegments = 0) {
|
||||
return Manifold::Cylinder(height, radiusLow, radiusHigh,
|
||||
circularSegments);
|
||||
},
|
||||
py::arg("height"), py::arg("radius_low"),
|
||||
py::arg("radius_high") = -1.0f, py::arg("circular_segments") = 0,
|
||||
"A convenience constructor for the common case of extruding a "
|
||||
"circle. Can also\n"
|
||||
"form cones if both radii are specified.\n"
|
||||
"\n"
|
||||
":param height: Z-extent\n"
|
||||
":param radiusLow: Radius of bottom circle. Must be positive.\n"
|
||||
":param radiusHigh: Radius of top circle. Can equal zero. Default "
|
||||
"(-1) is equal to\n"
|
||||
"radiusLow.\n"
|
||||
":param circularSegments: How many line segments to use around the "
|
||||
"circle.\n"
|
||||
"Default (-1) is calculated by the static Defaults.\n"
|
||||
":param center: Set to true to shift the center to the origin. "
|
||||
"Default is\n"
|
||||
"origin at the bottom.")
|
||||
.def_static(
|
||||
"sphere",
|
||||
[](float radius, int circularSegments = 0) {
|
||||
return Manifold::Sphere(radius, circularSegments);
|
||||
},
|
||||
py::arg("radius"), py::arg("circular_segments") = 0,
|
||||
"Constructs a geodesic sphere of a given radius.\n"
|
||||
"\n"
|
||||
":param radius: Radius of the sphere. Must be positive.\n"
|
||||
":param circularSegments: Number of segments along its\n"
|
||||
"diameter. This number will always be rounded up to the nearest "
|
||||
"factor of\n"
|
||||
"four, as this sphere is constructed by refining an octahedron. This "
|
||||
"means\n"
|
||||
"there are a circle of vertices on all three of the axis planes. "
|
||||
"Default is\n"
|
||||
"calculated by the static Defaults.");
|
||||
|
||||
py::class_<PolygonsWrapper>(m, "Polygons")
|
||||
.def(py::init([](std::vector<std::vector<Float2>> &polygons) {
|
||||
std::vector<SimplePolygon> simplePolygons(polygons.size());
|
||||
for (int i = 0; i < polygons.size(); i++) {
|
||||
std::vector<PolyVert> vertices(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
vertices[j] = {
|
||||
{std::get<0>(polygons[i][j]), std::get<1>(polygons[i][j])},
|
||||
j};
|
||||
}
|
||||
simplePolygons[i] = {vertices};
|
||||
}
|
||||
return PolygonsWrapper{std::make_unique<Polygons>(simplePolygons)};
|
||||
}),
|
||||
py::arg("polygons"),
|
||||
"Construct the Polygons object from a list of simple polygon, "
|
||||
"where each simple polygon is a list of points (pair of floats).")
|
||||
.def(
|
||||
"extrude",
|
||||
[](PolygonsWrapper &self, float height, int nDivisions = 0,
|
||||
float twistDegrees = 0.0f,
|
||||
Float2 scaleTop = std::make_tuple(1.0f, 1.0f)) {
|
||||
glm::vec2 scaleTopVec(std::get<0>(scaleTop), std::get<1>(scaleTop));
|
||||
return Manifold::Extrude(*self.polygons, height, nDivisions,
|
||||
twistDegrees, scaleTopVec);
|
||||
},
|
||||
py::arg("height"), py::arg("n_divisions") = 0,
|
||||
py::arg("twist_degrees") = 0.0f,
|
||||
py::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
|
||||
"Constructs a manifold from the set of polygons by extruding them "
|
||||
"along the\n"
|
||||
"Z-axis.\n"
|
||||
"\n"
|
||||
":param height: Z-extent of extrusion.\n"
|
||||
":param nDivisions: Number of extra copies of the crossSection to "
|
||||
"insert into\n"
|
||||
"the shape vertically; especially useful in combination with "
|
||||
"twistDegrees to\n"
|
||||
"avoid interpolation artifacts. Default is none.\n"
|
||||
":param twistDegrees: Amount to twist the top crossSection relative "
|
||||
"to the\n"
|
||||
"bottom, interpolated linearly for the divisions in between.\n"
|
||||
":param scaleTop: Amount to scale the top (independently in X and "
|
||||
"Y). If the\n"
|
||||
"scale is (0, 0), a pure cone is formed with only a single vertex at "
|
||||
"the top.\n"
|
||||
"Default (1, 1).")
|
||||
.def(
|
||||
"revolve",
|
||||
[](PolygonsWrapper &self, int circularSegments = 0) {
|
||||
return Manifold::Revolve(*self.polygons, circularSegments);
|
||||
},
|
||||
py::arg("circular_segments") = 0,
|
||||
"Constructs a manifold from the set of polygons by revolving this "
|
||||
"cross-section\n"
|
||||
"around its Y-axis and then setting this as the Z-axis of the "
|
||||
"resulting\n"
|
||||
"manifold. If the polygons cross the Y-axis, only the part on the "
|
||||
"positive X\n"
|
||||
"side is used. Geometrically valid input will result in "
|
||||
"geometrically valid\n"
|
||||
"output.\n"
|
||||
"\n"
|
||||
":param circularSegments: Number of segments along its diameter. "
|
||||
"Default is\n"
|
||||
"calculated by the static Defaults.");
|
||||
|
||||
py::class_<Mesh>(m, "Mesh")
|
||||
.def(py::init([](py::array_t<float> &vertPos, py::array_t<int> &triVerts,
|
||||
py::array_t<float> &vertNormal,
|
||||
py::array_t<float> &halfedgeTangent) {
|
||||
auto vertPos_view = vertPos.unchecked<2>();
|
||||
auto triVerts_view = triVerts.unchecked<2>();
|
||||
auto vertNormal_view = vertNormal.unchecked<2>();
|
||||
auto halfedgeTangent_view = halfedgeTangent.unchecked<2>();
|
||||
if (vertPos_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid vert_pos shape");
|
||||
if (triVerts_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid tri_verts shape");
|
||||
if (vertNormal_view.shape(0) != 0) {
|
||||
if (vertNormal_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid vert_normal shape");
|
||||
if (vertNormal_view.shape(0) != vertPos_view.shape(0))
|
||||
throw std::runtime_error(
|
||||
"vert_normal must have the same length as vert_pos");
|
||||
}
|
||||
if (halfedgeTangent_view.shape(0) != 0) {
|
||||
if (halfedgeTangent_view.shape(1) != 4)
|
||||
throw std::runtime_error("Invalid halfedge_tangent shape");
|
||||
if (halfedgeTangent_view.shape(0) != triVerts_view.shape(0) * 3)
|
||||
throw std::runtime_error(
|
||||
"halfedge_tangent must be three times as long as "
|
||||
"tri_verts");
|
||||
}
|
||||
std::vector<glm::vec3> vertPos_vec(vertPos_view.shape(0));
|
||||
std::vector<glm::ivec3> triVerts_vec(triVerts_view.shape(0));
|
||||
std::vector<glm::vec3> vertNormal_vec(vertNormal_view.shape(0));
|
||||
std::vector<glm::vec4> halfedgeTangent_vec(
|
||||
halfedgeTangent_view.shape(0));
|
||||
for (int i = 0; i < vertPos_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2})
|
||||
vertPos_vec[i][j] = vertPos_view(i, j);
|
||||
for (int i = 0; i < triVerts_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2})
|
||||
triVerts_vec[i][j] = triVerts_view(i, j);
|
||||
for (int i = 0; i < vertNormal_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2})
|
||||
vertNormal_vec[i][j] = vertNormal_view(i, j);
|
||||
for (int i = 0; i < halfedgeTangent_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2, 3})
|
||||
halfedgeTangent_vec[i][j] = halfedgeTangent_view(i, j);
|
||||
return Mesh({vertPos_vec, triVerts_vec, vertNormal_vec,
|
||||
halfedgeTangent_vec});
|
||||
}),
|
||||
py::arg("vert_pos"), py::arg("tri_verts"), py::arg("vert_normal"),
|
||||
py::arg("halfedge_tangent"))
|
||||
.def_property_readonly("vert_pos",
|
||||
[](Mesh &self) {
|
||||
const int numVert = self.vertPos.size();
|
||||
py::array_t<float> vert_pos({numVert, 3});
|
||||
auto vert_pos_view =
|
||||
vert_pos.mutable_unchecked<2>();
|
||||
|
||||
for (int i = 0; i < numVert; ++i)
|
||||
for (const int j : {0, 1, 2})
|
||||
vert_pos_view(i, j) = self.vertPos[i][j];
|
||||
return vert_pos;
|
||||
})
|
||||
.def_property_readonly("tri_verts",
|
||||
[](Mesh &self) {
|
||||
const int numTri = self.triVerts.size();
|
||||
py::array_t<int> tri_verts({numTri, 3});
|
||||
auto tri_verts_view =
|
||||
tri_verts.mutable_unchecked<2>();
|
||||
|
||||
for (int i = 0; i < numTri; ++i)
|
||||
for (const int j : {0, 1, 2})
|
||||
tri_verts_view(i, j) = self.triVerts[i][j];
|
||||
return tri_verts;
|
||||
})
|
||||
.def_property_readonly(
|
||||
"vert_normal",
|
||||
[](Mesh &self) {
|
||||
const int numVert = self.vertNormal.size();
|
||||
py::array_t<float> vert_normal({numVert, 3});
|
||||
auto vert_normal_view = vert_normal.mutable_unchecked<2>();
|
||||
|
||||
for (int i = 0; i < numVert; ++i)
|
||||
for (const int j : {0, 1, 2})
|
||||
vert_normal_view(i, j) = self.vertNormal[i][j];
|
||||
return vert_normal;
|
||||
})
|
||||
.def_property_readonly("halfedge_tangent", [](Mesh &self) {
|
||||
const int numEdge = self.halfedgeTangent.size();
|
||||
py::array_t<float> halfedge_tangent({numEdge, 4});
|
||||
auto halfedge_tangent_view = halfedge_tangent.mutable_unchecked<2>();
|
||||
|
||||
for (int i = 0; i < numEdge; ++i)
|
||||
for (const int j : {0, 1, 2, 3})
|
||||
halfedge_tangent_view(i, j) = self.halfedgeTangent[i][j];
|
||||
return halfedge_tangent;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
manifold3d.__prefix__:
|
||||
import numpy as np
|
||||
from typing import Literal, TypeVar, Union, Any
|
||||
|
||||
N = TypeVar('N', bound=int)
|
||||
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]]
|
||||
@@ -0,0 +1,13 @@
|
||||
import re, sys;
|
||||
|
||||
with open(sys.argv[1], 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
content = re.sub(r"Annotated\[ArrayLike, dict\(dtype='([^']*)'.*\)\]", r"np.ndarray[Any, np.dtype[np.\1]]", content)
|
||||
|
||||
# Remove ArrayLike and Annotated import
|
||||
content = re.sub(r"\s*from numpy.typing import ArrayLike\s", r"", content)
|
||||
content = re.sub(r"from typing import Annotated, overload", r"from typing import overload", content)
|
||||
|
||||
with open(sys.argv[1], 'w') as f:
|
||||
f.write(content)
|
||||
@@ -1 +0,0 @@
|
||||
add_subdirectory(pybind11)
|
||||
@@ -0,0 +1,2 @@
|
||||
manifold.js
|
||||
manifold.wasm
|
||||
@@ -12,27 +12,53 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
project(wasm)
|
||||
|
||||
add_executable(manifoldjs bindings.cpp)
|
||||
|
||||
# make sure that we recompile the wasm when bindings.js is being modified
|
||||
set_source_files_properties(bindings.cpp OBJECT_DEPENDS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
|
||||
target_link_libraries(manifoldjs manifold sdf)
|
||||
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS} -fexceptions)
|
||||
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)
|
||||
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_compile_features(manifoldjs PUBLIC cxx_std_14)
|
||||
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
|
||||
|
||||
file(COPY examples;test;. DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS examples;test)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/built)
|
||||
|
||||
# ensure that interface files are copied over when modified
|
||||
add_custom_target(
|
||||
js_deps
|
||||
ALL
|
||||
DEPENDS manifoldjs ${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
TARGET manifoldjs POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_BINARY_DIR}/examples/)
|
||||
|
||||
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,27 +1,43 @@
|
||||
# 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 https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287.
|
||||
Since Manifold is a WASM module, it does not automatically garbage-collect like regular JavaScript. You must manually `delete()` each object constructed by your scripts (both `Manifold` and `CrossSection`), see [discussion](https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287).
|
||||
|
||||
## Examples
|
||||
|
||||
Please see our usage [examples](https://github.com/elalish/manifold/tree/master/bindings/wasm/examples) to see how to interface this library with `three.js`, `<model-viewer>`, and `glTF`. Of particular note are the included libraries for lossless roundtrip of manifold meshes through glTF files, via a new extension: [EXT_mesh_manifold](https://github.com/KhronosGroup/glTF/pull/2286).
|
||||
|
||||
## 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
|
||||
|
||||
This library was started by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee and this is my 20% project, not an official Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
|
||||
This library was started by [Emmett Lalish](https://elalish.blogspot.com/), currently a senior rendering engineer at Wētā FX. This was my 20% project when I was a Google employee, though my day job was maintaining [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
|
||||
|
||||
@@ -15,235 +15,209 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include "helpers.cpp"
|
||||
#include "manifold/cross_section.h"
|
||||
#include "manifold/manifold.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;
|
||||
|
||||
#include <manifold.h>
|
||||
#include <sdf.h>
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
Manifold Union(const Manifold& a, const Manifold& b) { return a + b; }
|
||||
|
||||
Manifold Difference(const Manifold& a, const Manifold& b) { return a - b; }
|
||||
|
||||
Manifold Intersection(const Manifold& a, const Manifold& b) { return a ^ b; }
|
||||
|
||||
Manifold UnionN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::ADD);
|
||||
;
|
||||
}
|
||||
|
||||
Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::SUBTRACT);
|
||||
;
|
||||
}
|
||||
|
||||
Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::INTERSECT);
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<SimplePolygon> ToPolygon(
|
||||
std::vector<std::vector<glm::vec2>>& polygons) {
|
||||
std::vector<SimplePolygon> simplePolygons(polygons.size());
|
||||
for (int i = 0; i < polygons.size(); i++) {
|
||||
std::vector<PolyVert> vertices(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
vertices[j] = {polygons[i][j], j};
|
||||
}
|
||||
simplePolygons[i] = {vertices};
|
||||
}
|
||||
return simplePolygons;
|
||||
}
|
||||
|
||||
val GetMeshJS(const Manifold& manifold) {
|
||||
MeshGL mesh = manifold.GetMeshGL();
|
||||
val meshJS = val::object();
|
||||
|
||||
meshJS.set("triVerts",
|
||||
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertPos",
|
||||
val(typed_memory_view(mesh.vertPos.size(), mesh.vertPos.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertNormal", val(typed_memory_view(mesh.vertNormal.size(),
|
||||
mesh.vertNormal.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("halfedgeTangent",
|
||||
val(typed_memory_view(mesh.halfedgeTangent.size(),
|
||||
mesh.halfedgeTangent.data()))
|
||||
.call<val>("slice"));
|
||||
|
||||
return meshJS;
|
||||
}
|
||||
|
||||
MeshGL MeshJS2GL(const val& mesh) {
|
||||
MeshGL out;
|
||||
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
|
||||
out.vertPos = convertJSArrayToNumberVector<float>(mesh["vertPos"]);
|
||||
if (mesh["vertNormal"] != val::undefined()) {
|
||||
out.vertNormal = convertJSArrayToNumberVector<float>(mesh["vertNormal"]);
|
||||
}
|
||||
if (mesh["halfedgeTangent"] != val::undefined()) {
|
||||
out.halfedgeTangent =
|
||||
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Manifold FromMeshJS(const val& mesh) { return Manifold(MeshJS2GL(mesh)); }
|
||||
|
||||
Manifold Smooth(const val& mesh,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {}) {
|
||||
return Manifold::Smooth(MeshJS2GL(mesh), sharpenedEdges);
|
||||
}
|
||||
|
||||
Manifold Extrude(std::vector<std::vector<glm::vec2>>& polygons, float height,
|
||||
int nDivisions, float twistDegrees, glm::vec2 scaleTop) {
|
||||
return Manifold::Extrude(ToPolygon(polygons), height, nDivisions,
|
||||
twistDegrees, scaleTop);
|
||||
}
|
||||
|
||||
Manifold Revolve(std::vector<std::vector<glm::vec2>>& polygons,
|
||||
int circularSegments) {
|
||||
return Manifold::Revolve(ToPolygon(polygons), circularSegments);
|
||||
}
|
||||
|
||||
Manifold Transform(Manifold& manifold, std::vector<float>& mat) {
|
||||
glm::mat4x3 matrix;
|
||||
for (int i = 0; i < 4; i++)
|
||||
for (int j = 0; j < 3; j++) matrix[i][j] = mat[i * 3 + j];
|
||||
return manifold.Transform(matrix);
|
||||
}
|
||||
|
||||
Manifold Warp(Manifold& manifold, uintptr_t funcPtr) {
|
||||
void (*f)(glm::vec3&) = reinterpret_cast<void (*)(glm::vec3&)>(funcPtr);
|
||||
return manifold.Warp(f);
|
||||
}
|
||||
|
||||
Manifold LevelSetJs(uintptr_t funcPtr, Box bounds, float edgeLength,
|
||||
float level) {
|
||||
float (*f)(const glm::vec3&) =
|
||||
reinterpret_cast<float (*)(const glm::vec3&)>(funcPtr);
|
||||
Mesh m = LevelSet(f, bounds, edgeLength, level);
|
||||
return Manifold(m);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(whatever) {
|
||||
value_object<glm::vec2>("vec2")
|
||||
.field("x", &glm::vec2::x)
|
||||
.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("NO_ERROR", Manifold::Error::NO_ERROR)
|
||||
.value("NON_FINITE_VERTEX", Manifold::Error::NON_FINITE_VERTEX)
|
||||
.value("NOT_MANIFOLD", Manifold::Error::NOT_MANIFOLD)
|
||||
.value("VERTEX_INDEX_OUT_OF_BOUNDS",
|
||||
Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS)
|
||||
.value("PROPERTIES_WRONG_LENGTH",
|
||||
Manifold::Error::PROPERTIES_WRONG_LENGTH)
|
||||
.value("TRI_PROPERTIES_WRONG_LENGTH",
|
||||
Manifold::Error::TRI_PROPERTIES_WRONG_LENGTH)
|
||||
.value("TRI_PROPERTIES_OUT_OF_BOUNDS",
|
||||
Manifold::Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
|
||||
.value("NoError", Manifold::Error::NoError)
|
||||
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
|
||||
.value("NotManifold", Manifold::Error::NotManifold)
|
||||
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
|
||||
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
|
||||
.value("MissingPositionProperties",
|
||||
Manifold::Error::MissingPositionProperties)
|
||||
.value("MergeVectorsDifferentLengths",
|
||||
Manifold::Error::MergeVectorsDifferentLengths)
|
||||
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
|
||||
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
|
||||
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
|
||||
|
||||
enum_<CrossSection::FillRule>("fillrule")
|
||||
.value("EvenOdd", CrossSection::FillRule::EvenOdd)
|
||||
.value("NonZero", CrossSection::FillRule::NonZero)
|
||||
.value("Positive", CrossSection::FillRule::Positive)
|
||||
.value("Negative", CrossSection::FillRule::Negative);
|
||||
|
||||
enum_<CrossSection::JoinType>("jointype")
|
||||
.value("Square", CrossSection::JoinType::Square)
|
||||
.value("Round", CrossSection::JoinType::Round)
|
||||
.value("Miter", CrossSection::JoinType::Miter)
|
||||
.value("Bevel", CrossSection::JoinType::Bevel);
|
||||
|
||||
value_object<Rect>("rect").field("min", &Rect::min).field("max", &Rect::max);
|
||||
value_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
|
||||
|
||||
value_object<Smoothness>("smoothness")
|
||||
.field("halfedge", &Smoothness::halfedge)
|
||||
.field("smoothness", &Smoothness::smoothness);
|
||||
|
||||
value_object<Properties>("properties")
|
||||
.field("surfaceArea", &Properties::surfaceArea)
|
||||
.field("volume", &Properties::volume);
|
||||
|
||||
value_object<BaryRef>("baryRef")
|
||||
.field("meshID", &BaryRef::meshID)
|
||||
.field("originalID", &BaryRef::originalID)
|
||||
.field("tri", &BaryRef::tri)
|
||||
.field("vertBary", &BaryRef::vertBary);
|
||||
|
||||
value_object<MeshRelation>("meshRelation")
|
||||
.field("barycentric", &MeshRelation::barycentric)
|
||||
.field("triBary", &MeshRelation::triBary);
|
||||
|
||||
value_object<Curvature>("curvature")
|
||||
.field("maxMeanCurvature", &Curvature::maxMeanCurvature)
|
||||
.field("minMeanCurvature", &Curvature::minMeanCurvature)
|
||||
.field("maxGaussianCurvature", &Curvature::maxGaussianCurvature)
|
||||
.field("minGaussianCurvature", &Curvature::minGaussianCurvature)
|
||||
.field("vertMeanCurvature", &Curvature::vertMeanCurvature)
|
||||
.field("vertGaussianCurvature", &Curvature::vertGaussianCurvature);
|
||||
|
||||
register_vector<glm::ivec3>("Vector_ivec3");
|
||||
register_vector<glm::vec3>("Vector_vec3");
|
||||
register_vector<glm::vec2>("Vector_vec2");
|
||||
register_vector<std::vector<glm::vec2>>("Vector2_vec2");
|
||||
register_vector<float>("Vector_f32");
|
||||
register_vector<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<BaryRef>("Vector_baryRef");
|
||||
register_vector<glm::vec4>("Vector_vec4");
|
||||
register_vector<vec4>("Vector_vec4");
|
||||
|
||||
class_<CrossSection>("CrossSection")
|
||||
.constructor(&cross_js::OfPolygons)
|
||||
.function("_add", &cross_js::Union)
|
||||
.function("_subtract", &cross_js::Difference)
|
||||
.function("_intersect", &cross_js::Intersection)
|
||||
.function("_Warp", &cross_js::Warp)
|
||||
.function("transform", &cross_js::Transform)
|
||||
.function("_Translate", &CrossSection::Translate)
|
||||
.function("rotate", &CrossSection::Rotate)
|
||||
.function("_Scale", &CrossSection::Scale)
|
||||
.function("_Mirror", &CrossSection::Mirror)
|
||||
.function("_Decompose", &CrossSection::Decompose)
|
||||
.function("isEmpty", &CrossSection::IsEmpty)
|
||||
.function("area", &CrossSection::Area)
|
||||
.function("numVert", &CrossSection::NumVert)
|
||||
.function("numContour", &CrossSection::NumContour)
|
||||
.function("_Bounds", &CrossSection::Bounds)
|
||||
.function("_Simplify", &CrossSection::Simplify)
|
||||
.function("_Offset", &cross_js::Offset)
|
||||
.function("_ToPolygons", &CrossSection::ToPolygons)
|
||||
.function("hull",
|
||||
select_overload<CrossSection() const>(&CrossSection::Hull));
|
||||
|
||||
// CrossSection Static Methods
|
||||
function("_Square", &CrossSection::Square);
|
||||
function("_Circle", &CrossSection::Circle);
|
||||
function("_crossSectionCompose", &CrossSection::Compose);
|
||||
function("_crossSectionUnionN", &cross_js::UnionN);
|
||||
function("_crossSectionDifferenceN", &cross_js::DifferenceN);
|
||||
function("_crossSectionIntersectionN", &cross_js::IntersectionN);
|
||||
function("_crossSectionCollectVertices", &cross_js::CollectVertices);
|
||||
function(
|
||||
"_crossSectionHullPoints",
|
||||
select_overload<CrossSection(std::vector<vec2>)>(&CrossSection::Hull));
|
||||
|
||||
class_<Manifold>("Manifold")
|
||||
.constructor(&FromMeshJS)
|
||||
.function("add", &Union)
|
||||
.function("subtract", &Difference)
|
||||
.function("intersect", &Intersection)
|
||||
.function("_GetMeshJS", &GetMeshJS)
|
||||
.constructor(&man_js::FromMeshJS)
|
||||
.function("add", &man_js::Union)
|
||||
.function("subtract", &man_js::Difference)
|
||||
.function("intersect", &man_js::Intersection)
|
||||
.function("_Split", &man_js::Split)
|
||||
.function("_SplitByPlane", &man_js::SplitByPlane)
|
||||
.function("_TrimByPlane", &Manifold::TrimByPlane)
|
||||
.function("_Slice", &Manifold::Slice)
|
||||
.function("_Project", &Manifold::Project)
|
||||
.function("hull", select_overload<Manifold() const>(&Manifold::Hull))
|
||||
.function("_GetMeshJS", &js::GetMeshJS)
|
||||
.function("refine", &Manifold::Refine)
|
||||
.function("_Warp", &Warp)
|
||||
.function("_Transform", &Transform)
|
||||
.function("refineToLength", &Manifold::RefineToLength)
|
||||
.function("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)
|
||||
.function("_Translate", &Manifold::Translate)
|
||||
.function("_Rotate", &Manifold::Rotate)
|
||||
.function("_Scale", &Manifold::Scale)
|
||||
.function("_Decompose", &Manifold::Decompose)
|
||||
.function("_Mirror", &Manifold::Mirror)
|
||||
.function("_Decompose", select_overload<std::vector<Manifold>() const>(
|
||||
&Manifold::Decompose))
|
||||
.function("isEmpty", &Manifold::IsEmpty)
|
||||
.function("status", &Manifold::Status)
|
||||
.function("status", &man_js::Status)
|
||||
.function("numVert", &Manifold::NumVert)
|
||||
.function("numEdge", &Manifold::NumEdge)
|
||||
.function("numTri", &Manifold::NumTri)
|
||||
.function("numProp", &Manifold::NumProp)
|
||||
.function("numPropVert", &Manifold::NumPropVert)
|
||||
.function("_boundingBox", &Manifold::BoundingBox)
|
||||
.function("precision", &Manifold::Precision)
|
||||
.function("tolerance", &Manifold::GetTolerance)
|
||||
.function("setTolerance", &Manifold::SetTolerance)
|
||||
.function("simplify", &Manifold::Simplify)
|
||||
.function("genus", &Manifold::Genus)
|
||||
.function("getProperties", &Manifold::GetProperties)
|
||||
.function("_getCurvature", &Manifold::GetCurvature)
|
||||
.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)
|
||||
.function("_getMeshRelation", &Manifold::GetMeshRelation);
|
||||
.function("asOriginal", &Manifold::AsOriginal);
|
||||
|
||||
// Manifold Static Methods
|
||||
function("_Cube", &Manifold::Cube);
|
||||
function("_Cylinder", &Manifold::Cylinder);
|
||||
function("_Sphere", &Manifold::Sphere);
|
||||
function("tetrahedron", &Manifold::Tetrahedron);
|
||||
function("_Smooth", &Smooth);
|
||||
function("_Extrude", &Extrude);
|
||||
function("_Revolve", &Revolve);
|
||||
function("_LevelSet", &LevelSetJs);
|
||||
function("_Tetrahedron", &Manifold::Tetrahedron);
|
||||
function("_Smooth", &js::Smooth);
|
||||
function("_Extrude", &Manifold::Extrude);
|
||||
function("_Triangulate", &Triangulate);
|
||||
function("_Revolve", &Manifold::Revolve);
|
||||
function("_LevelSet", &man_js::LevelSet);
|
||||
function("_Merge", &js::Merge);
|
||||
function("_ReserveIDs", &Manifold::ReserveIDs);
|
||||
function("_manifoldCompose", &Manifold::Compose);
|
||||
function("_manifoldUnionN", &man_js::UnionN);
|
||||
function("_manifoldDifferenceN", &man_js::DifferenceN);
|
||||
function("_manifoldIntersectionN", &man_js::IntersectionN);
|
||||
function("_manifoldCollectVertices", &man_js::CollectVertices);
|
||||
function(
|
||||
"_manifoldHullPoints",
|
||||
select_overload<Manifold(const std::vector<vec3>&)>(&Manifold::Hull));
|
||||
|
||||
function("_unionN", &UnionN);
|
||||
function("_differenceN", &DifferenceN);
|
||||
function("_intersectionN", &IntersectionN);
|
||||
function("_Compose", &Manifold::Compose);
|
||||
// Quality Globals
|
||||
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
|
||||
function("setMinCircularEdgeLength", &Quality::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Quality::SetCircularSegments);
|
||||
function("getCircularSegments", &Quality::GetCircularSegments);
|
||||
function("resetToCircularDefaults", &Quality::ResetToDefaults);
|
||||
|
||||
function("setMinCircularAngle", &Manifold::SetMinCircularAngle);
|
||||
function("setMinCircularEdgeLength", &Manifold::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Manifold::SetCircularSegments);
|
||||
function("getCircularSegments", &Manifold::GetCircularSegments);
|
||||
function("initTBB", &initTBB);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ 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) {
|
||||
if (list) {
|
||||
for (let x of list) {
|
||||
@@ -33,6 +38,21 @@ Module.setup = function() {
|
||||
return result;
|
||||
}
|
||||
|
||||
function vec2polygons(vec, f = x => x) {
|
||||
const result = [];
|
||||
const nPoly = vec.size();
|
||||
for (let i = 0; i < nPoly; i++) {
|
||||
const v = vec.get(i);
|
||||
const nPts = v.size();
|
||||
const poly = [];
|
||||
for (let j = 0; j < nPts; j++) {
|
||||
poly.push(f(v.get(j)));
|
||||
}
|
||||
result.push(poly);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function polygons2vec(polygons) {
|
||||
if (polygons[0].length < 3) {
|
||||
polygons = [polygons];
|
||||
@@ -50,7 +70,15 @@ Module.setup = function() {
|
||||
polygonsVec.delete();
|
||||
}
|
||||
|
||||
function vararg2vec(vec) {
|
||||
function vararg2vec2(vec) {
|
||||
if (vec[0] instanceof Array) return {x: vec[0][0], y: vec[0][1]};
|
||||
if (typeof (vec[0]) == 'number')
|
||||
// default to 0
|
||||
return {x: vec[0] || 0, y: vec[1] || 0};
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
function vararg2vec3(vec) {
|
||||
if (vec[0] instanceof Array)
|
||||
return {x: vec[0][0], y: vec[0][1], z: vec[0][2]};
|
||||
if (typeof (vec[0]) == 'number')
|
||||
@@ -59,130 +87,241 @@ Module.setup = function() {
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
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 vert = [x, y, z];
|
||||
function fillRuleToInt(fillRule) {
|
||||
return fillRule == 'EvenOdd' ? 0 :
|
||||
fillRule == 'NonZero' ? 1 :
|
||||
fillRule == 'Negative' ? 3 :
|
||||
/* Positive */ 2;
|
||||
}
|
||||
|
||||
function joinTypeToInt(joinType) {
|
||||
return joinType == 'Round' ? 1 : joinType == 'Miter' ? 2 : /* Square */ 0;
|
||||
}
|
||||
|
||||
// CrossSection methods
|
||||
|
||||
const CrossSectionCtor = Module.CrossSection;
|
||||
|
||||
function cross(polygons, fillRule = 'Positive') {
|
||||
if (polygons instanceof CrossSectionCtor) {
|
||||
return polygons;
|
||||
} else {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
|
||||
disposePolygons(polygonsVec);
|
||||
return cs;
|
||||
}
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec2(vec));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.scale = function(vec) {
|
||||
// if only one factor provided, scale both x and y with it
|
||||
if (typeof vec == 'number') {
|
||||
return this._Scale({x: vec, y: vec});
|
||||
}
|
||||
return this._Scale(vararg2vec2([vec]));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.mirror = function(vec) {
|
||||
return this._Mirror(vararg2vec2([vec]));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec2Ptr) {
|
||||
const x = getValue(vec2Ptr, 'double');
|
||||
const y = getValue(vec2Ptr + 8, 'double');
|
||||
const vert = [x, y];
|
||||
func(vert);
|
||||
setValue(vec3Ptr, vert[0], 'float');
|
||||
setValue(vec3Ptr + 4, vert[1], 'float');
|
||||
setValue(vec3Ptr + 8, vert[2], 'float');
|
||||
setValue(vec2Ptr, vert[0], 'double');
|
||||
setValue(vec2Ptr + 8, vert[1], 'double');
|
||||
}, 'vi');
|
||||
const out = this._Warp(wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
return out;
|
||||
};
|
||||
|
||||
// note that the matrix is using column major (same as glm)
|
||||
Module.Manifold.prototype.transform = function(mat) {
|
||||
console.assert(mat.length == 4, 'expects a 3x4 matrix');
|
||||
const vec = new Module.Vector_f32();
|
||||
for (let col of mat) {
|
||||
console.assert(col.length == 3, 'expects a 3x4 matrix');
|
||||
for (let x of col) mat.push_back(x);
|
||||
}
|
||||
const result = this._Transform(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec(vec));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.rotate = function(vec) {
|
||||
return this._Rotate(...vec);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.scale = function(vec) {
|
||||
if (typeof vec == 'number') {
|
||||
return this._Scale({x: vec, y: vec, z: vec});
|
||||
}
|
||||
return this._Scale(vararg2vec([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.decompose = function() {
|
||||
Module.CrossSection.prototype.decompose = function() {
|
||||
const vec = this._Decompose();
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.getCurvature = function() {
|
||||
const result = this._getCurvature();
|
||||
const oldMeanCurvature = result.vertMeanCurvature;
|
||||
const oldGaussianCurvature = result.vertGaussianCurvature;
|
||||
result.vertMeanCurvature = fromVec(oldMeanCurvature);
|
||||
result.vertGaussianCurvature = fromVec(oldGaussianCurvature);
|
||||
oldMeanCurvature.delete();
|
||||
oldGaussianCurvature.delete();
|
||||
Module.CrossSection.prototype.bounds = function() {
|
||||
const result = this._Bounds();
|
||||
return {
|
||||
min: ['x', 'y'].map(f => result.min[f]),
|
||||
max: ['x', 'y'].map(f => result.max[f]),
|
||||
};
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.offset = function(
|
||||
delta, joinType = 'Round', miterLimit = 2.0, circularSegments = 0) {
|
||||
return this._Offset(
|
||||
delta, joinTypeToInt(joinType), miterLimit, circularSegments);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.simplify = function(epsilon = 1e-6) {
|
||||
return this._Simplify(epsilon);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.extrude = function(
|
||||
height, nDivisions = 0, twistDegrees = 0.0, scaleTop = [1.0, 1.0],
|
||||
center = false) {
|
||||
scaleTop = vararg2vec2([scaleTop]);
|
||||
const man = Module._Extrude(
|
||||
this._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._ToPolygons(), circularSegments, revolveDegrees);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.add = function(other) {
|
||||
return this._add(cross(other));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.subtract = function(other) {
|
||||
return this._subtract(cross(other));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.intersect = function(other) {
|
||||
return this._intersect(cross(other));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.toPolygons = function() {
|
||||
const vec = this._ToPolygons();
|
||||
const result = vec2polygons(vec, v => [v.x, v.y]);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
class Mesh {
|
||||
constructor({
|
||||
triVerts = new Uint32Array(),
|
||||
vertPos = new Float32Array(),
|
||||
vertNormal,
|
||||
halfedgeTangent
|
||||
} = {}) {
|
||||
this.triVerts = triVerts;
|
||||
this.vertPos = vertPos;
|
||||
this.vertNormal = vertNormal;
|
||||
this.halfedgeTangent = halfedgeTangent;
|
||||
}
|
||||
// Manifold methods
|
||||
|
||||
get numTri() {
|
||||
return this.triVerts.length / 3;
|
||||
}
|
||||
|
||||
get numVert() {
|
||||
return this.vertPos.length / 3;
|
||||
}
|
||||
|
||||
verts(tri) {
|
||||
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
|
||||
}
|
||||
|
||||
position(vert) {
|
||||
return this.vertPos.subarray(3 * vert, 3 * (vert + 1));
|
||||
}
|
||||
|
||||
normal(vert) {
|
||||
return this.vertNormal.subarray(3 * vert, 3 * (vert + 1));
|
||||
}
|
||||
|
||||
tangent(halfedge) {
|
||||
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
|
||||
}
|
||||
}
|
||||
|
||||
Module.Mesh = Mesh;
|
||||
|
||||
Module.Manifold.prototype.getMesh = function() {
|
||||
return new Mesh(this._GetMeshJS());
|
||||
Module.Manifold.prototype.smoothOut = function(
|
||||
minSharpAngle = 60, minSmoothness = 0) {
|
||||
return this._SmoothOut(minSharpAngle, minSmoothness);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.getMeshRelation = function() {
|
||||
const result = this._getMeshRelation();
|
||||
const oldBarycentric = result.barycentric;
|
||||
const oldTriBary = result.triBary;
|
||||
const conversion1 = v => [v.x, v.y, v.z];
|
||||
const conversion2 = v => [v[0], v[1], v[2]];
|
||||
const conversion3 = v => {
|
||||
return {
|
||||
meshID: v.meshID,
|
||||
originalID: v.originalID,
|
||||
tri: v.tri,
|
||||
vertBary: conversion2(v.vertBary)
|
||||
};
|
||||
};
|
||||
result.barycentric = fromVec(oldBarycentric, conversion1);
|
||||
result.triBary = fromVec(oldTriBary, conversion3);
|
||||
oldBarycentric.delete();
|
||||
oldTriBary.delete();
|
||||
Module.Manifold.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
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], 'double');
|
||||
setValue(vec3Ptr + 8, vert[1], 'double');
|
||||
setValue(vec3Ptr + 16, vert[2], 'double');
|
||||
}, 'vi');
|
||||
const out = this._Warp(wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
|
||||
const status = out.status();
|
||||
if (status !== 'NoError') {
|
||||
throw new Module.ManifoldError(status);
|
||||
}
|
||||
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 + 8 * i, 'double');
|
||||
}
|
||||
const pos = [];
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
pos[i] = getValue(vec3Ptr + 8 * i, 'double');
|
||||
}
|
||||
const oldProp = [];
|
||||
for (let i = 0; i < oldNumProp; ++i) {
|
||||
oldProp[i] = getValue(oldPtr + 8 * i, 'double');
|
||||
}
|
||||
|
||||
func(newProp, pos, oldProp);
|
||||
|
||||
for (let i = 0; i < numProp; ++i) {
|
||||
setValue(newPtr + 8 * i, newProp[i], 'double');
|
||||
}
|
||||
}, 'viii');
|
||||
const out = this._SetProperties(numProp, wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
return out;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec3(vec));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.rotate = function(xOrVec, y, z) {
|
||||
if (Array.isArray(xOrVec)) {
|
||||
return this._Rotate(...xOrVec);
|
||||
} else {
|
||||
return this._Rotate(xOrVec, y || 0, z || 0);
|
||||
}
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.scale = function(vec) {
|
||||
// if only one factor provided, scale all three dimensions (xyz) with it
|
||||
if (typeof vec == 'number') {
|
||||
return this._Scale({x: vec, y: vec, z: vec});
|
||||
}
|
||||
return this._Scale(vararg2vec3([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.mirror = function(vec) {
|
||||
return this._Mirror(vararg2vec3([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.trimByPlane = function(normal, offset = 0.) {
|
||||
return this._TrimByPlane(vararg2vec3([normal]), offset);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.slice = function(height = 0.) {
|
||||
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 result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.splitByPlane = function(normal, offset = 0.) {
|
||||
const vec = this._SplitByPlane(vararg2vec3([normal]), offset);
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.decompose = function() {
|
||||
const vec = this._Decompose();
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -194,26 +333,119 @@ Module.setup = function() {
|
||||
};
|
||||
};
|
||||
|
||||
class Mesh {
|
||||
constructor({
|
||||
numProp = 3,
|
||||
triVerts = new Uint32Array(),
|
||||
vertProperties = new Float32Array(),
|
||||
mergeFromVert,
|
||||
mergeToVert,
|
||||
runIndex,
|
||||
runOriginalID,
|
||||
faceID,
|
||||
halfedgeTangent,
|
||||
runTransform
|
||||
} = {}) {
|
||||
this.numProp = numProp;
|
||||
this.triVerts = triVerts;
|
||||
this.vertProperties = vertProperties;
|
||||
this.mergeFromVert = mergeFromVert;
|
||||
this.mergeToVert = mergeToVert;
|
||||
this.runIndex = runIndex;
|
||||
this.runOriginalID = runOriginalID;
|
||||
this.faceID = faceID;
|
||||
this.halfedgeTangent = halfedgeTangent;
|
||||
this.runTransform = runTransform;
|
||||
}
|
||||
|
||||
get numTri() {
|
||||
return this.triVerts.length / 3;
|
||||
}
|
||||
|
||||
get numVert() {
|
||||
return this.vertProperties.length / this.numProp;
|
||||
}
|
||||
|
||||
get numRun() {
|
||||
return this.runOriginalID.length;
|
||||
}
|
||||
|
||||
merge() {
|
||||
const {changed, mesh} = Module._Merge(this);
|
||||
Object.assign(this, {...mesh});
|
||||
return changed;
|
||||
}
|
||||
|
||||
verts(tri) {
|
||||
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
|
||||
}
|
||||
|
||||
position(vert) {
|
||||
return this.vertProperties.subarray(
|
||||
this.numProp * vert, this.numProp * vert + 3);
|
||||
}
|
||||
|
||||
extras(vert) {
|
||||
return this.vertProperties.subarray(
|
||||
this.numProp * vert + 3, this.numProp * (vert + 1));
|
||||
}
|
||||
|
||||
tangent(halfedge) {
|
||||
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
|
||||
}
|
||||
|
||||
transform(run) {
|
||||
const mat4 = new Array(16);
|
||||
for (const col of [0, 1, 2, 3]) {
|
||||
for (const row of [0, 1, 2]) {
|
||||
mat4[4 * col + row] = this.runTransform[12 * run + 3 * col + row];
|
||||
}
|
||||
}
|
||||
mat4[15] = 1;
|
||||
return mat4;
|
||||
}
|
||||
}
|
||||
|
||||
Module.Mesh = Mesh;
|
||||
|
||||
Module.Manifold.prototype.getMesh = function(normalIdx = -1) {
|
||||
return new Mesh(this._GetMeshJS(normalIdx));
|
||||
};
|
||||
|
||||
Module.ManifoldError = function ManifoldError(code, ...args) {
|
||||
let message = 'Unknown error';
|
||||
switch (code) {
|
||||
case Module.status.NON_FINITE_VERTEX.value:
|
||||
case 'NonFiniteVertex':
|
||||
message = 'Non-finite vertex';
|
||||
break;
|
||||
case Module.status.NOT_MANIFOLD.value:
|
||||
case 'NotManifold':
|
||||
message = 'Not manifold';
|
||||
break;
|
||||
case Module.status.VERTEX_INDEX_OUT_OF_BOUNDS.value:
|
||||
case 'VertexOutOfBounds':
|
||||
message = 'Vertex index out of bounds';
|
||||
break;
|
||||
case Module.status.PROPERTIES_WRONG_LENGTH.value:
|
||||
case 'PropertiesWrongLength':
|
||||
message = 'Properties have wrong length';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
|
||||
message = 'Tri properties have wrong length';
|
||||
case 'MissingPositionProperties':
|
||||
message = 'Less than three properties';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_OUT_OF_BOUNDS.value:
|
||||
message = 'Tri properties out of bounds';
|
||||
case 'MergeVectorsDifferentLengths':
|
||||
message = 'Merge vectors have different lengths';
|
||||
break;
|
||||
case 'MergeIndexOutOfBounds':
|
||||
message = 'Merge index out of bounds';
|
||||
break;
|
||||
case 'TransformWrongLength':
|
||||
message = 'Transform vector has wrong length';
|
||||
break;
|
||||
case 'RunIndexWrongLength':
|
||||
message = 'Run index vector has wrong length';
|
||||
break;
|
||||
case 'FaceIDWrongLength':
|
||||
message = 'Face ID vector has wrong length';
|
||||
case 'InvalidConstruction':
|
||||
message = 'Manifold constructed with invalid parameters';
|
||||
}
|
||||
|
||||
const base = Error.apply(this, [message, ...args]);
|
||||
@@ -228,44 +460,139 @@ Module.setup = function() {
|
||||
{value: Module.ManifoldError, writable: true, configurable: true}
|
||||
});
|
||||
|
||||
// CrossSection Constructors
|
||||
|
||||
Module.CrossSection = function(polygons, fillRule = 'Positive') {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
|
||||
disposePolygons(polygonsVec);
|
||||
return cs;
|
||||
};
|
||||
|
||||
Module.CrossSection.ofPolygons = function(polygons, fillRule = 'Positive') {
|
||||
return new Module.CrossSection(polygons, fillRule);
|
||||
};
|
||||
|
||||
Module.CrossSection.square = function(...args) {
|
||||
let size = undefined;
|
||||
if (args.length == 0)
|
||||
size = {x: 1, y: 1};
|
||||
else if (typeof args[0] == 'number')
|
||||
size = {x: args[0], y: args[0]};
|
||||
else
|
||||
size = vararg2vec2(args);
|
||||
const center = args[1] || false;
|
||||
return Module._Square(size, center);
|
||||
};
|
||||
|
||||
Module.CrossSection.circle = function(radius, circularSegments = 0) {
|
||||
return Module._Circle(radius, circularSegments);
|
||||
};
|
||||
|
||||
// allows args to be either CrossSection or polygons (constructed with
|
||||
// Positive fill)
|
||||
function crossSectionBatchbool(name) {
|
||||
return function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
const v = new Module.Vector_crossSection();
|
||||
for (const cs of args) v.push_back(cross(cs));
|
||||
const result = Module['_crossSection' + name](v);
|
||||
v.delete();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Module.CrossSection.compose = crossSectionBatchbool('Compose');
|
||||
Module.CrossSection.union = crossSectionBatchbool('UnionN');
|
||||
Module.CrossSection.difference = crossSectionBatchbool('DifferenceN');
|
||||
Module.CrossSection.intersection = crossSectionBatchbool('IntersectionN');
|
||||
|
||||
function pushVec2(vec, ps) {
|
||||
toVec(vec, ps, p => {
|
||||
if (p instanceof Array) return {x: p[0], y: p[1]};
|
||||
return p;
|
||||
})
|
||||
}
|
||||
|
||||
Module.CrossSection.hull = function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
let pts = new Module.Vector_vec2();
|
||||
for (const cs of args) {
|
||||
if (cs instanceof CrossSectionCtor) {
|
||||
Module._crossSectionCollectVertices(pts, cs);
|
||||
} else if (
|
||||
cs instanceof Array && cs.length == 2 && typeof cs[0] == 'number') {
|
||||
pts.push_back({x: cs[0], y: cs[1]});
|
||||
} else if (cs.x) {
|
||||
pts.push_back(cs);
|
||||
} else {
|
||||
const wrap =
|
||||
((cs[0].length == 2 && typeof cs[0][0] == 'number') || cs[0].x);
|
||||
const polys = wrap ? [cs] : cs;
|
||||
for (const poly of polys) pushVec2(pts, poly);
|
||||
}
|
||||
}
|
||||
const result = Module._crossSectionHullPoints(pts);
|
||||
pts.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype = Object.create(CrossSectionCtor.prototype);
|
||||
|
||||
// Because the constructor and prototype are being replaced, instanceof will
|
||||
// not work as desired unless we refer back to the original like this
|
||||
Object.defineProperty(Module.CrossSection, Symbol.hasInstance, {
|
||||
get: () => (t) => {
|
||||
return (t instanceof CrossSectionCtor);
|
||||
}
|
||||
});
|
||||
|
||||
// Manifold Constructors
|
||||
|
||||
const ManifoldCtor = Module.Manifold;
|
||||
Module.Manifold = function(mesh) {
|
||||
const manifold = new ManifoldCtor(mesh);
|
||||
|
||||
const status = manifold.status();
|
||||
if (status.value !== 0) {
|
||||
throw new Module.ManifoldError(status.value);
|
||||
if (status !== 'NoError') {
|
||||
throw new Module.ManifoldError(status);
|
||||
}
|
||||
|
||||
return manifold;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
|
||||
Module.Manifold.ofMesh = function(mesh) {
|
||||
return new Module.Manifold(mesh);
|
||||
};
|
||||
|
||||
Module.cube = function(...args) {
|
||||
Module.Manifold.tetrahedron = function() {
|
||||
return Module._Tetrahedron();
|
||||
};
|
||||
|
||||
Module.Manifold.cube = function(...args) {
|
||||
let size = undefined;
|
||||
if (args.length == 0)
|
||||
size = {x: 1, y: 1, z: 1};
|
||||
else if (typeof args[0] == 'number')
|
||||
size = {x: args[0], y: args[0], z: args[0]};
|
||||
else
|
||||
size = vararg2vec(args);
|
||||
size = vararg2vec3(args);
|
||||
const center = args[1] || false;
|
||||
return Module._Cube(size, center);
|
||||
};
|
||||
|
||||
Module.cylinder = function(
|
||||
Module.Manifold.cylinder = function(
|
||||
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
|
||||
center = false) {
|
||||
return Module._Cylinder(
|
||||
height, radiusLow, radiusHigh, circularSegments, center);
|
||||
};
|
||||
|
||||
Module.sphere = function(radius, circularSegments = 0) {
|
||||
Module.Manifold.sphere = function(radius, circularSegments = 0) {
|
||||
return Module._Sphere(radius, circularSegments);
|
||||
};
|
||||
|
||||
Module.smooth = function(mesh, sharpenedEdges = []) {
|
||||
Module.Manifold.smooth = function(mesh, sharpenedEdges = []) {
|
||||
const sharp = new Module.Vector_smoothness();
|
||||
toVec(sharp, sharpenedEdges);
|
||||
const result = Module._Smooth(mesh, sharp);
|
||||
@@ -273,61 +600,114 @@ Module.setup = function() {
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.extrude = function(
|
||||
Module.Manifold.extrude = function(
|
||||
polygons, height, nDivisions = 0, twistDegrees = 0.0,
|
||||
scaleTop = [1.0, 1.0]) {
|
||||
if (scaleTop instanceof Array) scaleTop = {x: scaleTop[0], y: scaleTop[1]};
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = Module._Extrude(
|
||||
polygonsVec, height, nDivisions, twistDegrees, scaleTop);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
scaleTop = [1.0, 1.0], center = false) {
|
||||
const cs = (polygons instanceof CrossSectionCtor) ?
|
||||
polygons :
|
||||
Module.CrossSection(polygons, 'Positive');
|
||||
return cs.extrude(height, nDivisions, twistDegrees, scaleTop, center);
|
||||
};
|
||||
|
||||
Module.revolve = function(polygons, circularSegments = 0) {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = Module._Revolve(polygonsVec, circularSegments);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
Module.Manifold.revolve = function(
|
||||
polygons, circularSegments = 0, revolveDegrees = 360.0) {
|
||||
const cs = (polygons instanceof CrossSectionCtor) ?
|
||||
polygons :
|
||||
Module.CrossSection(polygons, 'Positive');
|
||||
return cs.revolve(circularSegments, revolveDegrees);
|
||||
};
|
||||
|
||||
Module.compose = function(manifolds) {
|
||||
Module.Manifold.reserveIDs = function(n) {
|
||||
return Module._ReserveIDs(n);
|
||||
};
|
||||
|
||||
Module.Manifold.compose = function(manifolds) {
|
||||
const vec = new Module.Vector_manifold();
|
||||
toVec(vec, manifolds);
|
||||
const result = Module._Compose(vec);
|
||||
const result = Module._manifoldCompose(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.levelSet = function(sdf, bounds, edgeLength, level = 0) {
|
||||
function manifoldBatchbool(name) {
|
||||
return function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
const v = new Module.Vector_manifold();
|
||||
for (const m of args) v.push_back(m);
|
||||
const result = Module['_manifold' + name + 'N'](v);
|
||||
v.delete();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Module.Manifold.union = manifoldBatchbool('Union');
|
||||
Module.Manifold.difference = manifoldBatchbool('Difference');
|
||||
Module.Manifold.intersection = manifoldBatchbool('Intersection');
|
||||
|
||||
Module.Manifold.levelSet = function(
|
||||
sdf, bounds, edgeLength, level = 0, 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;
|
||||
};
|
||||
|
||||
function batchbool(name) {
|
||||
return function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
const v = new Module.Vector_manifold();
|
||||
for (const m of args) v.push_back(m);
|
||||
const result = Module['_' + name + 'N'](v);
|
||||
v.delete();
|
||||
return result;
|
||||
}
|
||||
function pushVec3(vec, ps) {
|
||||
toVec(vec, ps, p => {
|
||||
if (p instanceof Array) return {x: p[0], y: p[1], z: p[2]};
|
||||
return p;
|
||||
})
|
||||
}
|
||||
|
||||
Module.union = batchbool('union');
|
||||
Module.difference = batchbool('difference');
|
||||
Module.intersection = batchbool('intersection');
|
||||
Module.Manifold.hull = function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
let pts = new Module.Vector_vec3();
|
||||
for (const m of args) {
|
||||
if (m instanceof ManifoldCtor) {
|
||||
Module._manifoldCollectVertices(pts, m);
|
||||
} else if (
|
||||
m instanceof Array && m.length == 3 && typeof m[0] == 'number') {
|
||||
pts.push_back({x: m[0], y: m[1], z: m[2]});
|
||||
} else if (m.x) {
|
||||
pts.push_back(m);
|
||||
} else {
|
||||
pushVec3(pts, m);
|
||||
}
|
||||
}
|
||||
const result = Module._manifoldHullPoints(pts);
|
||||
pts.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
|
||||
|
||||
// Because the constructor and prototype are being replaced, instanceof will
|
||||
// not work as desired unless we refer back to the original like this
|
||||
Object.defineProperty(Module.Manifold, Symbol.hasInstance, {
|
||||
get: () => (t) => {
|
||||
return (t instanceof ManifoldCtor);
|
||||
}
|
||||
});
|
||||
|
||||
// Top-level functions
|
||||
|
||||
Module.triangulate = function(polygons, epsilon = -1, allowConvex = true) {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = fromVec(
|
||||
Module._Triangulate(polygonsVec, epsilon, allowConvex),
|
||||
(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 @@
|
||||
manifold*
|
||||
@@ -0,0 +1 @@
|
||||
declare module '@jscadui/3mf-export';
|
||||
@@ -0,0 +1,45 @@
|
||||
# WASM/JS Examples
|
||||
|
||||
This is the home of our editor, [ManifoldCAD.org](https://manifoldcad.org/), as well as our other public examples of integrations with `three.js`, `<model-viewer>`, and `glTF`. Included are `manifold-gltf.js` and `gltf-io.js`, which are intended to be fairly general-purpose libraries for interfacing between manifolds and glTF. We should probably make them into their own npm package at some point.
|
||||
|
||||
## Local development
|
||||
|
||||
First, follow the directions in the root README to get your C++ build environment set up and working for WASM. From this directory (`bindings/wasm/examples/`) you can test the JS bindings by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
To develop the manifoldCAD.org editor as well as our other example pages, run
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
which will serve the pages, watch for changes, and automatically rebuild and refresh. This build step doesn't do TS type-checking, so to verify everything is correct (beyond VSCode's TS linting), run
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
See `package.json` for other useful scripts.
|
||||
|
||||
Note that the `emcmake` command automatically copies your WASM build into `built/`, (here, not just under the `buildWASM` directory) which is then packaged by Vite into `dist/assets/`.
|
||||
|
||||
To debug the WASM build directly in Chrome dev tools, simply build in debug mode:
|
||||
```
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Debug .. && emmake make
|
||||
```
|
||||
and install the [DWARF](goo.gle/wasm-debugging-extension) Chrome extension.
|
||||
|
||||
When testing [ManifoldCAD.org](https://manifoldcad.org/) (either locally or the
|
||||
deployed version) note that it uses a service worker for faster loading. This
|
||||
means you need to open the page twice to see updates (the first time loads the
|
||||
old version and caches the new one, the second time loads the new version from
|
||||
cache). To see changes on each reload, open Chrome dev tools, go to the
|
||||
Application tab and check "update on reload".
|
||||
|
||||
### Note for firefox users
|
||||
|
||||
To use the manifoldCAD.org editor (`npm run dev`), you'll likely have to set
|
||||
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
|
||||
discussion of the
|
||||
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
|
||||
of this repository.
|
||||
@@ -0,0 +1,34 @@
|
||||
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);
|
||||
});
|
||||
|
||||
test('simplify argument is defaulted', () => {
|
||||
const cs = manifoldModule.CrossSection.circle(1).simplify();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -128,20 +128,16 @@ model-viewer {
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
|
||||
.margin {
|
||||
.header .margin {
|
||||
font-size: 16px;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
#file {
|
||||
width: 250px;
|
||||
margin: 18px 8px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 250px;
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -150,6 +146,10 @@ model-viewer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.uparrow {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
@@ -168,28 +168,89 @@ model-viewer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.top-edit {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 36px;
|
||||
top: 18px;
|
||||
border: none;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.top-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 36px;
|
||||
background-size: 24px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.squish {
|
||||
margin-right: -12px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
background-color: transparent;
|
||||
position: absolute;
|
||||
width: 36px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
background-size: 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.undo {
|
||||
background-image: url(/icons/undo.png);
|
||||
}
|
||||
|
||||
.redo {
|
||||
background-image: url(/icons/redo.png);
|
||||
}
|
||||
|
||||
.star {
|
||||
background-image: url(/icons/star.png);
|
||||
}
|
||||
|
||||
.share {
|
||||
background-image: url(/icons/share.png);
|
||||
}
|
||||
|
||||
.cancel {
|
||||
background-image: url(icons/close.png);
|
||||
background-image: url(/icons/close.png);
|
||||
background-size: 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.edit {
|
||||
background-image: url(/icons/pencil.png);
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.trash {
|
||||
background-image: url(/icons/trash.png);
|
||||
}
|
||||
|
||||
.play {
|
||||
background-image: url(/icons/play.png);
|
||||
}
|
||||
|
||||
.pause {
|
||||
background-image: url(/icons/pause.png);
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.top-icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.blue {
|
||||
background-color: rgb(148, 221, 255);
|
||||
}
|
||||
@@ -202,7 +263,61 @@ model-viewer {
|
||||
background-color: rgb(255, 177, 177);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
#file-margin {
|
||||
position: relative;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
#save {
|
||||
width: 125px;
|
||||
}
|
||||
|
||||
#animation {
|
||||
display: none;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#play {
|
||||
position: relative;
|
||||
background-color: #ccc;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: relative;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
margin-right: 16px;
|
||||
background: #ffffff55;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider::-moz-range-thumb {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 820px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
@@ -210,5 +325,31 @@ model-viewer {
|
||||
|
||||
.col {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
#file-margin {
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: .2em 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header .margin {
|
||||
margin: 0.2em 16px;
|
||||
}
|
||||
|
||||
#button-container {
|
||||
flex-direction: row;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
width: calc(100% - 16px);
|
||||
}
|
||||
|
||||
#button-container .margin {
|
||||
width: calc(50% - 16px);
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
@@ -12,26 +12,68 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const exampleFunctions = examples.functionBodies;
|
||||
import ManifoldWorker from './worker-wrapper?worker';
|
||||
|
||||
const CODE_START = '<code>';
|
||||
// Loaded globally by examples.js
|
||||
const exampleFunctions = self.examples.functionBodies;
|
||||
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.register(
|
||||
'/service-worker.js', {scope: './index.html'});
|
||||
}
|
||||
|
||||
let editor = undefined;
|
||||
|
||||
// Edit UI ------------------------------------------------------------
|
||||
|
||||
const undoButton = document.querySelector('#undo');
|
||||
const redoButton = document.querySelector('#redo');
|
||||
const formatButton = document.querySelector('#format');
|
||||
const shareButton = document.querySelector('#share');
|
||||
|
||||
undoButton.onclick = () => editor.trigger('ignored', 'undo');
|
||||
redoButton.onclick = () => editor.trigger('ignored', 'redo');
|
||||
formatButton.onclick = () =>
|
||||
editor.trigger('ignored', 'editor.action.formatDocument');
|
||||
shareButton.onclick = () => {
|
||||
const url = new URL(window.location.toString());
|
||||
url.hash =
|
||||
'#' +
|
||||
encodeURIComponent(
|
||||
currentFileElement.textContent + CODE_START + editor.getValue());
|
||||
navigator.clipboard.writeText(url.toString());
|
||||
console.log('Shareable link copied to clipboard!');
|
||||
console.log('Consider shortening this URL using tinyURL.com');
|
||||
};
|
||||
|
||||
// File UI ------------------------------------------------------------
|
||||
const fileButton = document.querySelector('#file');
|
||||
const currentElement = document.querySelector('#current');
|
||||
const arrow = document.querySelector('.uparrow');
|
||||
const dropdown = document.querySelector('.dropdown');
|
||||
const currentFileElement = document.querySelector('#current');
|
||||
const fileArrow = document.querySelector('#file .uparrow');
|
||||
const fileDropdown = document.querySelector('#fileDropdown');
|
||||
const saveContainer = document.querySelector('#save');
|
||||
const saveDropdown = document.querySelector('#saveDropdown');
|
||||
const saveArrow = document.querySelector('#save .uparrow');
|
||||
|
||||
const hideDropdown = function() {
|
||||
dropdown.classList.remove('show');
|
||||
arrow.classList.remove('down');
|
||||
fileDropdown.classList.remove('show');
|
||||
saveDropdown.classList.remove('show');
|
||||
fileArrow.classList.remove('down');
|
||||
saveArrow.classList.remove('down');
|
||||
};
|
||||
const toggleDropdown = function(event) {
|
||||
const toggleFileDropdown = function(event) {
|
||||
event.stopPropagation();
|
||||
dropdown.classList.toggle('show');
|
||||
arrow.classList.toggle('down');
|
||||
fileDropdown.classList.toggle('show');
|
||||
fileArrow.classList.toggle('down');
|
||||
};
|
||||
fileButton.onclick = toggleDropdown;
|
||||
const toggleSaveDropdown = function(event) {
|
||||
event.stopPropagation();
|
||||
saveDropdown.classList.toggle('show');
|
||||
saveArrow.classList.toggle('down');
|
||||
};
|
||||
fileButton.onclick = toggleFileDropdown;
|
||||
saveArrow.parentElement.onclick = toggleSaveDropdown;
|
||||
document.body.onclick = hideDropdown;
|
||||
|
||||
const prefix = 'ManifoldCAD';
|
||||
@@ -54,7 +96,7 @@ function nthKey(n) {
|
||||
|
||||
function saveCurrent() {
|
||||
if (editor) {
|
||||
const currentName = currentElement.textContent;
|
||||
const currentName = currentFileElement.textContent;
|
||||
if (!exampleFunctions.get(currentName)) {
|
||||
setScript(currentName, editor.getValue());
|
||||
}
|
||||
@@ -69,11 +111,12 @@ let isExample = true;
|
||||
function switchTo(scriptName) {
|
||||
if (editor) {
|
||||
switching = true;
|
||||
currentElement.textContent = scriptName;
|
||||
currentFileElement.textContent = scriptName;
|
||||
setScript('currentName', scriptName);
|
||||
const code =
|
||||
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
|
||||
isExample = exampleFunctions.get(scriptName) != null;
|
||||
const code = isExample ? exampleFunctions.get(scriptName).substring(1) :
|
||||
getScript(scriptName) ?? '';
|
||||
window.location.hash = '#' + scriptName;
|
||||
editor.setValue(code);
|
||||
}
|
||||
}
|
||||
@@ -93,6 +136,10 @@ function createDropdownItem(name) {
|
||||
saveCurrent();
|
||||
switchTo(label.textContent);
|
||||
};
|
||||
// Stop text input spaces from triggering the button
|
||||
button.onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
};
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -115,8 +162,7 @@ function uniqueName(name) {
|
||||
function addEdit(button) {
|
||||
const label = button.firstChild;
|
||||
const edit = addIcon(button);
|
||||
edit.style.backgroundImage = 'url(icons/pencil.png)';
|
||||
edit.style.right = '30px';
|
||||
edit.classList.add('edit');
|
||||
|
||||
edit.onclick = function(event) {
|
||||
event.stopPropagation();
|
||||
@@ -124,6 +170,7 @@ function addEdit(button) {
|
||||
const code = getScript(oldName);
|
||||
const form = document.createElement('form');
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.classList.add('name');
|
||||
inputElement.value = oldName;
|
||||
label.textContent = '';
|
||||
button.appendChild(form);
|
||||
@@ -137,8 +184,8 @@ function addEdit(button) {
|
||||
if (!input) return;
|
||||
const newName = uniqueName(input);
|
||||
label.textContent = newName;
|
||||
if (currentElement.textContent == oldName) {
|
||||
currentElement.textContent = newName;
|
||||
if (currentFileElement.textContent == oldName) {
|
||||
currentFileElement.textContent = newName;
|
||||
}
|
||||
removeScript(oldName);
|
||||
setScript(newName, code);
|
||||
@@ -156,8 +203,7 @@ function addEdit(button) {
|
||||
};
|
||||
|
||||
const trash = addIcon(button);
|
||||
trash.style.backgroundImage = 'url(icons/trash.png)';
|
||||
trash.style.right = '0px';
|
||||
trash.classList.add('trash');
|
||||
let lastClick = 0;
|
||||
|
||||
trash.onclick = function(event) {
|
||||
@@ -172,7 +218,7 @@ function addEdit(button) {
|
||||
}, {once: true});
|
||||
} else if (performance.now() - lastClick > 500) {
|
||||
removeScript(label.textContent);
|
||||
if (currentElement.textContent == label.textContent) {
|
||||
if (currentFileElement.textContent == label.textContent) {
|
||||
switchTo('Intro');
|
||||
}
|
||||
const container = button.parentElement;
|
||||
@@ -182,16 +228,16 @@ function addEdit(button) {
|
||||
}
|
||||
|
||||
const newButton = document.querySelector('#new');
|
||||
function newItem(code) {
|
||||
const name = uniqueName('New Script');
|
||||
function newItem(code, scriptName = undefined) {
|
||||
const name = uniqueName(scriptName ?? 'New Script');
|
||||
setScript(name, code);
|
||||
const nextButton = createDropdownItem(name);
|
||||
newButton.insertAdjacentElement('afterend', nextButton.parentElement);
|
||||
addEdit(nextButton);
|
||||
nextButton.click();
|
||||
return {button: nextButton, name};
|
||||
};
|
||||
newButton.onclick = function() {
|
||||
newItem('');
|
||||
newItem('').button.click();
|
||||
};
|
||||
|
||||
const runButton = document.querySelector('#compile');
|
||||
@@ -204,20 +250,53 @@ function initializeRun() {
|
||||
if (autoExecute) {
|
||||
runButton.click();
|
||||
} else {
|
||||
poster.textContent = 'Auto-run disabled due to prior failure';
|
||||
poster.textContent = 'Auto-run disabled';
|
||||
}
|
||||
}
|
||||
|
||||
// Editor ------------------------------------------------------------
|
||||
let tsWorker = undefined;
|
||||
|
||||
async function getManifoldDTS() {
|
||||
const global = await fetch('/manifold-global-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
const encapsulated = await fetch('/manifold-encapsulated-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
return `
|
||||
${global.replaceAll('export', '')}
|
||||
${encapsulated.replace(/^import.*$/gm, '').replaceAll('export', 'declare')}
|
||||
declare interface ManifoldToplevel {
|
||||
CrossSection: typeof T.CrossSection;
|
||||
Manifold: typeof T.Manifold;
|
||||
Mesh: typeof T.Mesh;
|
||||
triangulate: typeof T.triangulate;
|
||||
setMinCircularAngle: typeof T.setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof T.setCircularSegments;
|
||||
getCircularSegments: typeof T.getCircularSegments;
|
||||
resetToCircularDefaults: typeof T.resetToCircularDefaults;
|
||||
setup: () => void;
|
||||
}
|
||||
declare const module: ManifoldToplevel;
|
||||
`;
|
||||
}
|
||||
|
||||
async function getEditorDTS() {
|
||||
const global = await fetch('/editor.d.ts').then(response => response.text());
|
||||
return `${global.replace(/^import.*$/gm, '')}`;
|
||||
}
|
||||
|
||||
require.config({
|
||||
paths:
|
||||
{vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs'}
|
||||
});
|
||||
require(['vs/editor/editor.main'], async function() {
|
||||
const content =
|
||||
await fetch('manifold.d.ts').then(response => response.text());
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(content);
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getManifoldDTS());
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getEditorDTS());
|
||||
editor = monaco.editor.create(
|
||||
document.getElementById('editor'),
|
||||
{language: 'typescript', automaticLayout: true});
|
||||
@@ -227,10 +306,10 @@ require(['vs/editor/editor.main'], async function() {
|
||||
|
||||
for (const [name] of exampleFunctions) {
|
||||
const button = createDropdownItem(name);
|
||||
dropdown.appendChild(button.parentElement);
|
||||
fileDropdown.appendChild(button.parentElement);
|
||||
}
|
||||
|
||||
let currentName = currentElement.textContent;
|
||||
let currentName = currentFileElement.textContent;
|
||||
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
const key = nthKey(i);
|
||||
@@ -245,7 +324,24 @@ require(['vs/editor/editor.main'], async function() {
|
||||
addEdit(button);
|
||||
}
|
||||
}
|
||||
switchTo(currentName);
|
||||
|
||||
if (window.location.hash.length > 0) {
|
||||
const fragment = decodeURIComponent(window.location.hash.substring(1));
|
||||
const codeIdx = fragment.indexOf(CODE_START);
|
||||
if (codeIdx != -1) {
|
||||
autoExecute = true;
|
||||
const name = fragment.substring(0, codeIdx);
|
||||
switchTo(
|
||||
newItem(fragment.substring(codeIdx + CODE_START.length), name).name);
|
||||
} else {
|
||||
if (fragment != currentName) {
|
||||
autoExecute = true;
|
||||
}
|
||||
switchTo(fragment);
|
||||
}
|
||||
} else {
|
||||
switchTo(currentName);
|
||||
}
|
||||
|
||||
if (manifoldInitialized) {
|
||||
initializeRun();
|
||||
@@ -255,11 +351,12 @@ require(['vs/editor/editor.main'], async function() {
|
||||
runButton.disabled = false;
|
||||
if (switching) {
|
||||
switching = false;
|
||||
editor.setScrollTop(0);
|
||||
return;
|
||||
}
|
||||
if (isExample) {
|
||||
const cursor = editor.getPosition();
|
||||
newItem(editor.getValue());
|
||||
newItem(editor.getValue()).button.click();
|
||||
editor.setPosition(cursor);
|
||||
}
|
||||
});
|
||||
@@ -269,6 +366,51 @@ require(['vs/editor/editor.main'], async function() {
|
||||
};
|
||||
});
|
||||
|
||||
// Animation ------------------------------------------------------------
|
||||
const mv = document.querySelector('model-viewer');
|
||||
const animationContainer = document.querySelector('#animation');
|
||||
const playButton = document.querySelector('#play');
|
||||
const scrubber = document.querySelector('#scrubber');
|
||||
let paused = false;
|
||||
|
||||
mv.addEventListener('load', () => {
|
||||
const hasAnimation = mv.availableAnimations.length > 0;
|
||||
animationContainer.style.display = hasAnimation ? 'flex' : 'none';
|
||||
if (hasAnimation) {
|
||||
play();
|
||||
}
|
||||
});
|
||||
|
||||
function play() {
|
||||
mv.play();
|
||||
playButton.classList.remove('play');
|
||||
playButton.classList.add('pause');
|
||||
paused = false;
|
||||
scrubber.classList.add('hide');
|
||||
}
|
||||
|
||||
function pause() {
|
||||
mv.pause();
|
||||
playButton.classList.remove('pause');
|
||||
playButton.classList.add('play');
|
||||
paused = true;
|
||||
scrubber.max = mv.duration;
|
||||
scrubber.value = mv.currentTime;
|
||||
scrubber.classList.remove('hide');
|
||||
}
|
||||
|
||||
playButton.onclick = function() {
|
||||
if (paused) {
|
||||
play();
|
||||
} else {
|
||||
pause();
|
||||
}
|
||||
};
|
||||
|
||||
scrubber.oninput = function() {
|
||||
mv.currentTime = scrubber.value;
|
||||
};
|
||||
|
||||
// Execution ------------------------------------------------------------
|
||||
const consoleElement = document.querySelector('#console');
|
||||
const oldLog = console.log;
|
||||
@@ -292,24 +434,21 @@ 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');
|
||||
let objectURL = null;
|
||||
const output = {
|
||||
glbURL: null,
|
||||
threeMFURL: null
|
||||
};
|
||||
let manifoldWorker = null;
|
||||
|
||||
function createWorker() {
|
||||
manifoldWorker = new Worker('worker.js');
|
||||
manifoldWorker = new ManifoldWorker();
|
||||
manifoldWorker.onmessage = function(e) {
|
||||
if (e.data == null) {
|
||||
if (tsWorker != null && !manifoldInitialized) {
|
||||
@@ -327,14 +466,22 @@ function createWorker() {
|
||||
|
||||
finishRun();
|
||||
runButton.disabled = true;
|
||||
setScript('safe', 'true');
|
||||
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = e.data.objectURL;
|
||||
mv.src = objectURL;
|
||||
if (objectURL == null) {
|
||||
if (output.threeMFURL != null) {
|
||||
URL.revokeObjectURL(output.threeMFURL);
|
||||
output.threeMFURL = null;
|
||||
}
|
||||
URL.revokeObjectURL(output.glbURL);
|
||||
output.glbURL = e.data.glbURL;
|
||||
output.threeMFURL = e.data.threeMFURL;
|
||||
threemfButton.disabled = output.threeMFURL == null;
|
||||
mv.src = output.glbURL;
|
||||
if (output.glbURL == null) {
|
||||
mv.showPoster();
|
||||
poster.textContent = 'Error';
|
||||
createWorker();
|
||||
} else {
|
||||
setScript('safe', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,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() {
|
||||
@@ -367,10 +513,24 @@ runButton.onclick = function() {
|
||||
}
|
||||
};
|
||||
|
||||
const downloadButton = document.querySelector('#download');
|
||||
downloadButton.onclick = function() {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = objectURL;
|
||||
link.click();
|
||||
};
|
||||
function clickSave(saveButton, filename, outputName) {
|
||||
const container = saveButton.parentElement;
|
||||
return () => {
|
||||
const oldSave = saveContainer.firstElementChild;
|
||||
if (oldSave !== container) {
|
||||
saveDropdown.insertBefore(oldSave, saveDropdown.firstElementChild);
|
||||
saveContainer.insertBefore(container, saveDropdown);
|
||||
container.appendChild(saveArrow.parentElement);
|
||||
}
|
||||
const link = document.createElement('a');
|
||||
link.download = filename;
|
||||
link.href = output[outputName];
|
||||
link.click();
|
||||
};
|
||||
}
|
||||
|
||||
const glbButton = document.querySelector('#glb');
|
||||
glbButton.onclick = clickSave(glbButton, 'manifold.glb', 'glbURL');
|
||||
|
||||
const threemfButton = document.querySelector('#threemf');
|
||||
threemfButton.onclick = clickSave(threemfButton, 'manifold.3mf', 'threeMFURL');
|
||||
|
||||
@@ -1,440 +0,0 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
(function(exports) {
|
||||
|
||||
exports.functions = {
|
||||
Intro: function() {
|
||||
// Write code in JavaScript or TypeScript and this editor will show the API
|
||||
// docs. Type e.g. "box." to see the Manifold API. Type "Module." to see the
|
||||
// static API - these functions can also be used bare. Use console.log() to
|
||||
// print output (lower-right). This editor defines Z as up and units of mm.
|
||||
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result".
|
||||
const result = box.subtract(ball);
|
||||
|
||||
// All changes are automatically saved and restored between sessions.
|
||||
// This app is purely local - there is no server communication.
|
||||
// This means it will work equally well offline once loaded.
|
||||
// To share your code with another browser/device, simply copy the text to a
|
||||
// file.
|
||||
|
||||
// See the script drop-down above ("Intro") for usage examples.
|
||||
return result;
|
||||
},
|
||||
|
||||
TetrahedronPuzzle: function() {
|
||||
// A tetrahedron cut into two identical halves that can screw together as a
|
||||
// puzzle. This only outputs one of the halves. This demonstrates how
|
||||
// redundant points along a polygon can be used to make twisted extrusions
|
||||
// smoother. Based on the screw puzzle by George Hart:
|
||||
// https://www.thingiverse.com/thing:186372
|
||||
|
||||
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
|
||||
const gap = 0.2; // Spacing between the two halves to allow sliding.
|
||||
const nDivisions =
|
||||
50; // Number of divisions (both ways) in the screw surface.
|
||||
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
|
||||
const tet = tetrahedron().scale(scale);
|
||||
|
||||
const box = [];
|
||||
box.push([2, -2], [2, 2]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
}
|
||||
|
||||
const screw = extrude(box, 2, nDivisions, 270)
|
||||
.rotate([0, 0, -45])
|
||||
.translate([0, 0, -1])
|
||||
.scale(scale);
|
||||
|
||||
const result = tet.intersect(screw);
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
let edge1 = union(corner, edge);
|
||||
edge1 = edge1.rotate([-90, 0, 0]).translate([
|
||||
-edgeLength / 2, -edgeLength / 2, 0
|
||||
]);
|
||||
|
||||
let edge2 = edge1.rotate([0, 0, 180]);
|
||||
edge2 = union(edge2, edge1);
|
||||
edge2 =
|
||||
union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
let edge4 = edge2.rotate([0, 0, 90]);
|
||||
edge4 = union(edge4, edge2);
|
||||
|
||||
let frame = edge4.translate([0, 0, -edgeLength / 2]);
|
||||
frame = union(frame, frame.rotate([180, 0, 0]));
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
return result;
|
||||
},
|
||||
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function. This
|
||||
// example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df = 6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
}
|
||||
// Update radius
|
||||
v[0] *= r;
|
||||
v[1] *= r;
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = sphere(1, 200);
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const result = heart.scale(100 / (box.max[0] - box.min[0]));
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function() {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with smooth()
|
||||
// and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const vertPos = [];
|
||||
const triVerts = [];
|
||||
vertPos.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
vertPos.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
|
||||
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
|
||||
let halfedge = triVerts.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triVerts.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triVerts.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triVerts.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
const scallop = new Mesh();
|
||||
scallop.triVerts = Uint32Array.from(triVerts);
|
||||
scallop.vertPos = Float32Array.from(vertPos);
|
||||
const result = smooth(scallop, sharpenedEdges).refine(n);
|
||||
return result;
|
||||
},
|
||||
|
||||
TorusKnot: function() {
|
||||
// Creates a classic torus knot, defined as a string wrapping periodically
|
||||
// around the surface of an imaginary donut. If p and q have a common factor
|
||||
// then you will get multiple separate, interwoven knots. This is an example
|
||||
// of using the warp() method, thus avoiding any direct handling of
|
||||
// triangles.
|
||||
|
||||
// @param p The number of times the thread passes through the donut hole.
|
||||
// @param q The number of times the thread circles the donut.
|
||||
// @param majorRadius Radius of the interior of the imaginary donut.
|
||||
// @param minorRadius Radius of the small cross-section of the imaginary
|
||||
// donut.
|
||||
// @param threadRadius Radius of the small cross-section of the actual
|
||||
// object.
|
||||
// @param circularSegments Number of linear segments making up the
|
||||
// threadRadius circle. Default is getCircularSegments(threadRadius).
|
||||
// @param linearSegments Number of segments along the length of the knot.
|
||||
// Default makes roughly square facets.
|
||||
|
||||
function torusKnot(
|
||||
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
|
||||
linearSegments = 0) {
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
}
|
||||
|
||||
const kLoops = gcd(p, q);
|
||||
p /= kLoops;
|
||||
q /= kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * q * majorRadius / threadRadius;
|
||||
|
||||
const circle = [];
|
||||
const dPhi = 2 * 3.14159 / n;
|
||||
const offset = 2;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
|
||||
}
|
||||
|
||||
const point = new THREE.Vector3();
|
||||
const axis = new THREE.Vector3();
|
||||
|
||||
const func =
|
||||
(v) => {
|
||||
const psi = q * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * p / q;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
point.set(Math.cos(phi), 0, Math.sin(phi))
|
||||
.multiplyScalar(threadRadius);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
point.applyAxisAngle(
|
||||
axis.set(1, 0, 0), -Math.atan2(p * minorRadius, q * r));
|
||||
point.x += minorRadius;
|
||||
point.applyAxisAngle(axis.set(0, 1, 0), theta);
|
||||
point.x += majorRadius;
|
||||
point.applyAxisAngle(axis.set(0, 0, 1), psi);
|
||||
v[0] = point.x;
|
||||
v[1] = point.y;
|
||||
v[2] = point.z;
|
||||
}
|
||||
|
||||
let knot = revolve(circle, m).warp(func);
|
||||
|
||||
if (kLoops > 1) {
|
||||
const knots = [];
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
|
||||
}
|
||||
knot = compose(knots);
|
||||
}
|
||||
|
||||
return knot;
|
||||
}
|
||||
|
||||
// This recreates Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const result = torusKnot(1, 3, 25, 10, 3.75);
|
||||
return result;
|
||||
},
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
|
||||
function vec2add(a, b) {
|
||||
return [a[0] + b[0], a[1] + b[1]];
|
||||
}
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
[-w, -w], [-w, 0.0], [-w, w], [0.0, w], [w, w], [w, 0.0], [w, -w],
|
||||
[0.0, -w]
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = compose(holes);
|
||||
|
||||
result = difference(result, hole);
|
||||
result = difference(result, hole.rotate([90, 0, 0]));
|
||||
result = difference(result, hole.rotate([0, 90, 0]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = mengerSponge(3).scale(100);
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
function rotate(v, theta) {
|
||||
return [
|
||||
v[0] * Math.cos(theta) - v[1] * Math.sin(theta),
|
||||
v[0] * Math.sin(theta) + v[1] * Math.cos(theta)
|
||||
];
|
||||
}
|
||||
|
||||
let b = cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
circle.push([
|
||||
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor =
|
||||
extrude(circle, width, nDivision, 180).scale([1, 0.5, 1]).translate([
|
||||
0, radius, 0
|
||||
]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
|
||||
const stretch = [];
|
||||
const dPhiRad = 2 * Math.PI / nCut;
|
||||
|
||||
const p0 = [outerRadius, 0];
|
||||
const p1 = [innerRadius, -cut];
|
||||
const p2 = [innerRadius, cut];
|
||||
for (let i = 0; i < nCut; ++i) {
|
||||
stretch.push(rotate(p0, dPhiRad * i));
|
||||
stretch.push(rotate(p1, dPhiRad * i));
|
||||
stretch.push(rotate(p2, dPhiRad * i));
|
||||
stretch.push(rotate(p0, dPhiRad * i));
|
||||
}
|
||||
b = intersection(extrude(stretch, width), b);
|
||||
return b;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
|
||||
nCut = 27, nDivision = 30) {
|
||||
const twistRadius = Math.PI * radius / nDecor;
|
||||
const decorRadius = twistRadius * 1.5;
|
||||
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
|
||||
const innerRadius = outerRadius - height;
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
cut - adjThickness, nCut, nDivision),
|
||||
base(
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
|
||||
GyroidModule: function() {
|
||||
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:25477. This sample demonstrates the use
|
||||
// of a Signed Distance Function (SDF) to create smooth, complex manifolds.
|
||||
|
||||
const size = 20;
|
||||
const n = 20;
|
||||
const pi = 3.14159;
|
||||
|
||||
function gyroid(p) {
|
||||
const x = p[0] - pi / 4;
|
||||
const y = p[1] - pi / 4;
|
||||
const z = p[2] - pi / 4;
|
||||
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: [-period, -period, -period],
|
||||
max: [period, period, period]
|
||||
};
|
||||
return levelSet(gyroid, box, period / n, level).scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const result =
|
||||
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
|
||||
return result.intersect(box.rotate([0, 0, 45]));
|
||||
}
|
||||
|
||||
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4))
|
||||
result = result.subtract(gyroidOffset(0.4));
|
||||
result = result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
exports.functionBodies = new Map();
|
||||
|
||||
for (const [func, code] of Object.entries(exports.functions)) {
|
||||
const whole = code.toString();
|
||||
const lines = whole.split('\n');
|
||||
lines.splice(0, 1); // remove first line
|
||||
lines.splice(-2, 2); // remove last two lines
|
||||
// remove first four leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(4)).join('\n');
|
||||
|
||||
const name =
|
||||
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
|
||||
exports.functionBodies.set(name, body);
|
||||
};
|
||||
})(typeof exports == 'undefined' ? this['examples'] = {} : exports);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -5,34 +5,81 @@
|
||||
<title>ManifoldCAD</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
<link type="text/css" href="editor.css" rel="stylesheet" />
|
||||
<meta name="description"
|
||||
content="Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.">
|
||||
<meta name="theme-color" content="#A288B3">
|
||||
<meta property="og:title" content="ManifoldCAD">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://manifoldcad.org/" />
|
||||
<meta property="og:image" content="https://elalish.github.io/manifold/samples/models/mengerSpongeSquare.png">
|
||||
<meta property="og:image:width" content="850">
|
||||
<meta property="og:image:height" content="850">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link type="text/css" href="editor.css" rel="stylesheet">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
"name": "ManifoldCAD",
|
||||
"applicationCategory": "DesignApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
self.ModelViewerElement = self.ModelViewerElement || {};
|
||||
self.ModelViewerElement.modelCacheSize = 0;
|
||||
</script>
|
||||
<script type="module" src="/examples.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="background-color: rgb(162 136 179);">
|
||||
<p style="flex: 2;"><a href="https://github.com/elalish/manifold">Manifold</a>CAD
|
||||
<div class="container header" style="background-color: #A288B3;">
|
||||
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD
|
||||
<span class="top-edit blue">
|
||||
<button type="button" id="undo" class="top-icon undo squish"></button>
|
||||
<button type="button" id="redo" class="top-icon redo squish"></button>
|
||||
<button type="button" id="format" class="top-icon star squish"></button>
|
||||
<button type="button" id="share" class="top-icon share"></button>
|
||||
</span>
|
||||
</p>
|
||||
<div class="margin" style="position: relative;">
|
||||
<button type="button" id="file" class="blue"><b class="uparrow"></b> <span
|
||||
<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">
|
||||
<div class="dropdown" id="fileDropdown">
|
||||
<button type="button" class="green item" id="new">New</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
|
||||
<button type="button" id="download" class="blue margin">Save GLB</button>
|
||||
<div class="container" id="button-container">
|
||||
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
|
||||
<div id="save" class="margin" style="position: relative; ">
|
||||
<div class="item">
|
||||
<button type="button" id="glb" class="blue item">Save GLB</button>
|
||||
<button type="button" class="icon"><b class="uparrow" style="right: 2px;"></b></button>
|
||||
</div>
|
||||
<div class="dropdown" id="saveDropdown">
|
||||
<div class="item">
|
||||
<button type="button" id="threemf" class="blue item">Save 3MF</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" style="flex: 2;">
|
||||
<div id="editor" class="col"></div>
|
||||
<div class="col container" style="flex-direction: column;">
|
||||
<model-viewer camera-controls min-camera-orbit="auto 0deg auto" max-camera-orbit="auto 180deg auto"
|
||||
shadow-intensity="1" interaction-prompt="none" alt="Editor 3D output">
|
||||
shadow-intensity="1" tone-mapping="neutral" interaction-prompt="none" alt="Editor 3D output">
|
||||
<div class="center" slot="poster">
|
||||
<p id="poster" style="text-align: center; line-height: 36px;">
|
||||
Loading...
|
||||
</p>
|
||||
<p id="poster" style="text-align: center; line-height: 36px;"> Loading... </p>
|
||||
</div>
|
||||
<div id="animation">
|
||||
<button type="button" id="play" class="icon pause"></button>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slider" id="scrubber">
|
||||
</div>
|
||||
</model-viewer>
|
||||
<div id="console"></div>
|
||||
@@ -40,17 +87,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"
|
||||
integrity="sha512-6bIYsGqvLpAiEBXPdRQeFf5cueeBECtAKJjIHer3BhBZNTV3WLcLA8Tm3pDfxUwTMIS+kAZwTUvJ1IrMdX8C5w=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css"
|
||||
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<script src="examples.js"></script>
|
||||
<script src="editor.js"></script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<script type="module" src="editor.js"></script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,65 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Make manifold glTF</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
<style>
|
||||
body {
|
||||
background-color: #f7f7f7;
|
||||
font-family: 'Rubik', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: rgba(0, 0, 0, .87);
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 700px;
|
||||
margin: 1em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
model-viewer {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vw;
|
||||
max-width: 600px;
|
||||
max-height: 600px;
|
||||
border: 3px solid black;
|
||||
border-radius: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#poster {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>Load a glTF/GLB model and our <a href="https://github.com/elalish/manifold">Manifold</a> library will attempt to
|
||||
merge it into a set of manifold objects. If the model is water-tight, you can download the new GLB which will have
|
||||
our <a href="https://github.com/KhronosGroup/glTF/pull/2286">EXT_mesh_manifold</a> extension, thus preserving the
|
||||
manifold data without losing any mesh properties.</p>
|
||||
<p> If the View Manifold GLB checkbox is enabled, then some meshes in the model have open edges and don't represent a
|
||||
solid object. Check this box to see only the manifold parts, which will also enable them to be downloaded. If the
|
||||
download button is still disabled, it means there were no manifold meshes found. </p>
|
||||
<input type="file" id="input">
|
||||
<button id="download" disabled>Download manifold GLB</button>
|
||||
<input type="checkbox" id="viewFinal" disabled>
|
||||
<label for="viewFinal">View Manifold GLB</label>
|
||||
<model-viewer camera-controls shadow-intensity="1" tone-mapping="neutral" alt="Loaded GLB model">
|
||||
<span id="poster" slot="poster">Drop a GLB here</span>
|
||||
</model-viewer>
|
||||
</body>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
||||
<script type="module" src="make-manifold.ts"></script>
|
||||
|
||||
</html>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Accessor, Extension, ExtensionProperty, GLTF, IProperty, PropertyType, ReaderContext, WriterContext} from '@gltf-transform/core';
|
||||
|
||||
const NAME = 'EXT_mesh_manifold';
|
||||
const MERGE = 'MERGE';
|
||||
|
||||
interface IManifoldPrimitive extends IProperty {
|
||||
mergeIndices: Accessor;
|
||||
mergeValues: Accessor;
|
||||
indices: Accessor;
|
||||
runIndex: number[]|Uint32Array;
|
||||
}
|
||||
|
||||
interface ManifoldDef {
|
||||
manifoldPrimitive: GLTF.IMeshPrimitive;
|
||||
mergeIndices: number;
|
||||
mergeValues: number;
|
||||
}
|
||||
|
||||
export class ManifoldPrimitive extends ExtensionProperty<IManifoldPrimitive> {
|
||||
static EXTENSION_NAME = NAME;
|
||||
public declare extensionName: typeof NAME;
|
||||
public declare propertyType: 'ManifoldPrimitive';
|
||||
public declare parentTypes: [PropertyType.MESH];
|
||||
|
||||
init() {
|
||||
ManifoldPrimitive.EXTENSION_NAME = NAME;
|
||||
this.propertyType = 'ManifoldPrimitive';
|
||||
this.parentTypes = [PropertyType.MESH];
|
||||
}
|
||||
|
||||
getDefaults() {
|
||||
return Object.assign(
|
||||
super.getDefaults(),
|
||||
{manifoldPrimitive: null, mergeIndices: null, mergeValues: null});
|
||||
}
|
||||
|
||||
getMergeIndices() {
|
||||
return this.getRef('mergeIndices');
|
||||
}
|
||||
|
||||
getMergeValues() {
|
||||
return this.getRef('mergeValues');
|
||||
}
|
||||
|
||||
setMerge(indicesAccessor: Accessor, valuesAccessor: Accessor) {
|
||||
if (indicesAccessor.getCount() !== valuesAccessor.getCount())
|
||||
throw new Error('merge vectors must be the same length.');
|
||||
this.setRef('mergeIndices', indicesAccessor);
|
||||
return this.setRef('mergeValues', valuesAccessor);
|
||||
}
|
||||
|
||||
getRunIndex() {
|
||||
return this.get('runIndex');
|
||||
}
|
||||
|
||||
setRunIndex(runIndex: number[]|Uint32Array) {
|
||||
return this.set('runIndex', runIndex);
|
||||
}
|
||||
|
||||
setIndices(indices: Accessor) {
|
||||
return this.setRef('indices', indices);
|
||||
}
|
||||
|
||||
getIndices() {
|
||||
return this.getRef('indices')!;
|
||||
}
|
||||
}
|
||||
|
||||
export class EXTManifold extends Extension {
|
||||
extensionName = NAME;
|
||||
prewriteTypes = [PropertyType.ACCESSOR];
|
||||
static EXTENSION_NAME = NAME;
|
||||
|
||||
createManifoldPrimitive() {
|
||||
return new ManifoldPrimitive(this.document.getGraph());
|
||||
}
|
||||
|
||||
read(context: ReaderContext) {
|
||||
const {json} = context.jsonDoc;
|
||||
const meshDefs = json.meshes || [];
|
||||
|
||||
meshDefs.forEach((meshDef, meshIndex) => {
|
||||
if (!meshDef.extensions || !meshDef.extensions[NAME]) return;
|
||||
|
||||
const mesh = context.meshes[meshIndex];
|
||||
const manifoldPrimitive = this.createManifoldPrimitive();
|
||||
mesh.setExtension(NAME, manifoldPrimitive);
|
||||
|
||||
const manifoldDef = meshDef.extensions[NAME] as ManifoldDef;
|
||||
|
||||
if (manifoldDef.manifoldPrimitive) {
|
||||
let count = 0;
|
||||
const runIndex = Array<number>();
|
||||
runIndex.push(count);
|
||||
for (const primitive of mesh.listPrimitives()) {
|
||||
const indices = primitive.getIndices();
|
||||
if (!indices) {
|
||||
console.log('Skipping non-indexed primitive ', primitive.getName());
|
||||
continue;
|
||||
}
|
||||
count += indices.getCount();
|
||||
runIndex.push(count);
|
||||
}
|
||||
manifoldPrimitive.setRunIndex(runIndex);
|
||||
manifoldPrimitive.setIndices(
|
||||
context.accessors[manifoldDef.manifoldPrimitive.indices!]);
|
||||
}
|
||||
|
||||
if (manifoldDef.mergeIndices != null && manifoldDef.mergeValues != null) {
|
||||
manifoldPrimitive.setMerge(
|
||||
context.accessors[manifoldDef.mergeIndices],
|
||||
context.accessors[manifoldDef.mergeValues]);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
prewrite(context: WriterContext) {
|
||||
this.document.getRoot().listMeshes().forEach((mesh) => {
|
||||
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
|
||||
if (!manifoldPrimitive) return;
|
||||
|
||||
const indices = manifoldPrimitive.getIndices();
|
||||
context.addAccessorToUsageGroup(
|
||||
indices, WriterContext.BufferViewUsage.ELEMENT_ARRAY_BUFFER);
|
||||
|
||||
const mergeFrom = manifoldPrimitive.getMergeIndices();
|
||||
const mergeTo = manifoldPrimitive.getMergeValues();
|
||||
if (!mergeFrom || !mergeTo) return;
|
||||
|
||||
context.addAccessorToUsageGroup(mergeFrom, MERGE);
|
||||
context.addAccessorToUsageGroup(mergeTo, MERGE);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
write(context: WriterContext) {
|
||||
const {json} = context.jsonDoc;
|
||||
|
||||
this.document.getRoot().listMeshes().forEach((mesh) => {
|
||||
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
|
||||
if (!manifoldPrimitive) return;
|
||||
|
||||
const meshIndex = context.meshIndexMap.get(mesh)!;
|
||||
const meshDef = json.meshes![meshIndex];
|
||||
|
||||
const runIndex = manifoldPrimitive.getRunIndex();
|
||||
const numPrimitive = runIndex.length - 1;
|
||||
|
||||
if (numPrimitive !== meshDef.primitives.length) {
|
||||
throw new Error(
|
||||
'The number of primitives must be exactly one less than the length of runIndex.');
|
||||
}
|
||||
|
||||
const mergeIndicesIndex =
|
||||
context.accessorIndexMap.get(manifoldPrimitive.getMergeIndices()!)!;
|
||||
const mergeValuesIndex =
|
||||
context.accessorIndexMap.get(manifoldPrimitive.getMergeValues()!)!;
|
||||
const mergeIndices = json.accessors![mergeIndicesIndex];
|
||||
const mergeValues = json.accessors![mergeValuesIndex];
|
||||
|
||||
const existingPrimitive = meshDef.primitives[0];
|
||||
const primitive = {
|
||||
indices: context.accessorIndexMap.get(manifoldPrimitive.getIndices())!,
|
||||
mode: existingPrimitive.mode,
|
||||
attributes: {'POSITION': existingPrimitive.attributes['POSITION']}
|
||||
};
|
||||
|
||||
const indices = json.accessors![primitive.indices];
|
||||
if (!indices) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mergeIndices && mergeValues) {
|
||||
indices.sparse = {
|
||||
count: mergeIndices.count,
|
||||
indices: {
|
||||
bufferView: mergeIndices.bufferView!,
|
||||
byteOffset: mergeIndices.byteOffset,
|
||||
componentType: mergeIndices.componentType
|
||||
},
|
||||
values: {
|
||||
bufferView: mergeValues.bufferView!,
|
||||
byteOffset: mergeValues.byteOffset,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (let i = 0; i < numPrimitive; ++i) {
|
||||
const accessor = json.accessors![meshDef.primitives[i].indices!];
|
||||
accessor.bufferView = indices.bufferView;
|
||||
accessor.byteOffset = indices.byteOffset! + 4 * runIndex[i];
|
||||
accessor.count = runIndex[i + 1] - runIndex[i];
|
||||
}
|
||||
|
||||
meshDef.extensions = meshDef.extensions || {};
|
||||
meshDef.extensions[NAME] = {
|
||||
manifoldPrimitive: primitive,
|
||||
mergeIndices: mergeIndicesIndex,
|
||||
mergeValues: mergeValuesIndex
|
||||
};
|
||||
|
||||
// Test the manifold primitive by replacing the material primitives
|
||||
// meshDef.primitives = [primitive];
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Document, WebIO} from '@gltf-transform/core';
|
||||
import {clearNodeTransform, flatten, prune} from '@gltf-transform/functions';
|
||||
|
||||
import Module, {Manifold, Mesh} from './built/manifold';
|
||||
import {disposeMesh, Properties, readMesh, setupIO, writeMesh} from './gltf-io';
|
||||
|
||||
// Set up gltf-transform
|
||||
const io = setupIO(new WebIO());
|
||||
const doc = new Document();
|
||||
|
||||
|
||||
// Set up Manifold WASM library
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
const {Manifold, Mesh} = wasm;
|
||||
|
||||
// Map of OriginalID to glTF material and attributes
|
||||
const id2properties = new Map<number, Properties>();
|
||||
|
||||
// Wrapper for gltf-io readMesh() that processes a whole glTF and stores the
|
||||
// properties in the above map. This function is simplified and intended only
|
||||
// for reading single-object glTFs, as it simply unions any extra meshes
|
||||
// together rather than returning a scene hierarchy.
|
||||
async function readGLB(url: string) {
|
||||
const manifolds = Array<Manifold>();
|
||||
const docIn = await io.read(url);
|
||||
await docIn.transform(flatten());
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
const ids = Array<number>();
|
||||
for (const node of nodes) {
|
||||
clearNodeTransform(node);
|
||||
const gltfMesh = node.getMesh();
|
||||
if (gltfMesh == null) continue;
|
||||
const tmp = readMesh(gltfMesh);
|
||||
if (tmp == null) continue;
|
||||
|
||||
const numID = tmp.runProperties.length;
|
||||
const firstID = Manifold.reserveIDs(numID);
|
||||
tmp.mesh.runOriginalID = new Uint32Array(numID);
|
||||
for (let i = 0; i < numID; ++i) {
|
||||
tmp.mesh.runOriginalID[i] = firstID + i;
|
||||
ids.push(firstID + i);
|
||||
id2properties.set(firstID + i, tmp.runProperties[i]);
|
||||
}
|
||||
manifolds.push(new Manifold(new Mesh(tmp.mesh)));
|
||||
}
|
||||
// pull in materials, TODO: replace with transfer() when available
|
||||
const startIdx = doc.getRoot().listMaterials().length;
|
||||
doc.merge(docIn);
|
||||
doc.getRoot().listScenes().forEach((s) => s.dispose());
|
||||
doc.getRoot().listBuffers().forEach((s) => s.dispose());
|
||||
doc.getRoot().listAccessors().forEach((s) => s.dispose());
|
||||
for (const [i, id] of ids.entries()) {
|
||||
const material = doc.getRoot().listMaterials()[startIdx + i];
|
||||
id2properties.get(id)!.material = material;
|
||||
}
|
||||
|
||||
return Manifold.union(manifolds);
|
||||
}
|
||||
|
||||
// Read static input glTFs
|
||||
const space = await readGLB('/models/space.glb');
|
||||
const moon = await readGLB('/models/moon.glb');
|
||||
|
||||
const node = doc.createNode();
|
||||
doc.createScene().addChild(node);
|
||||
|
||||
// Set up UI for operations
|
||||
type BooleanOp = 'union'|'difference'|'intersection';
|
||||
|
||||
function csg(operation: BooleanOp) {
|
||||
push2MV(Manifold[operation](space, moon));
|
||||
}
|
||||
|
||||
csg('difference');
|
||||
const selectElement = document.querySelector('select')!;
|
||||
selectElement.onchange = function() {
|
||||
csg(selectElement.value as BooleanOp);
|
||||
};
|
||||
|
||||
// The resulting glTF
|
||||
let objectURL = '';
|
||||
|
||||
// Set up download UI
|
||||
const downloadButton = document.querySelector('#download') as HTMLButtonElement;
|
||||
downloadButton.onclick = function() {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = objectURL;
|
||||
link.click();
|
||||
};
|
||||
|
||||
// <model-viewer> element for rendering resulting glTF.
|
||||
const mv = document.querySelector('model-viewer');
|
||||
|
||||
// Use gltf-io and gltf-transform to convert the resulting Manifold to a glTF
|
||||
// and display it with <model-viewer>.
|
||||
async function push2MV(manifold: Manifold) {
|
||||
disposeMesh(node.getMesh()!);
|
||||
const mesh = writeMesh(doc, manifold.getMesh(), id2properties);
|
||||
node.setMesh(mesh);
|
||||
await doc.transform(prune());
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
(mv as any).src = objectURL;
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
@@ -36,92 +35,24 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>This example demonstrates feeding Manifold's output into <a
|
||||
href="https://modelviewer.dev/"><model-viewer></a>
|
||||
via the three.js GLTFExporter. The most basic three.js example is <a href="three.html">here</a>.
|
||||
</p>
|
||||
<p>This example demonstrates reading and writing glTF models using <a
|
||||
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a> and using this to feed Manifold's
|
||||
output into <a href="https://modelviewer.dev/"><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>
|
||||
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
|
||||
<button id="download">Download manifold GLB</button>
|
||||
<model-viewer camera-controls shadow-intensity="1" tone-mapping="neutral"
|
||||
alt="Output of mesh Boolean operation"></model-viewer>
|
||||
</body>
|
||||
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/exporters/GLTFExporter.js"></script>
|
||||
<script src="manifold.js"></script>
|
||||
<script>
|
||||
let wasm;
|
||||
Module().then(function (tmp) {
|
||||
wasm = tmp;
|
||||
wasm.setup();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshStandardMaterial({
|
||||
color: 'yellow',
|
||||
metalness: 1,
|
||||
roughness: 0.2
|
||||
}));
|
||||
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
push2MV(mesh);
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
});
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
const exporter = new THREE.GLTFExporter();
|
||||
|
||||
function push2MV(mesh) {
|
||||
exporter.parse(
|
||||
mesh,
|
||||
(gltf) => {
|
||||
const blob = new Blob([gltf], { type: 'application/octet-stream' });
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
},
|
||||
() => console.log('GLTF export failed!'),
|
||||
{ binary: true }
|
||||
);
|
||||
}
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
// most of three.js geometries aren't manifolds, so...
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
||||
<script type="module" src="model-viewer-script.ts"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "manifold-examples",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:dev": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^3.8.0",
|
||||
"@gltf-transform/extensions": "^3.8.0",
|
||||
"@gltf-transform/functions": "^3.8.0",
|
||||
"@jscadui/3mf-export": "^0.5.0",
|
||||
"@types/three": "^0.164.0",
|
||||
"fflate": "^0.8.0",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"simple-dropzone": "0.8.3",
|
||||
"three": "0.164.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.2",
|
||||
"@vitest/ui": "^1.6.0",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "^4.5.0",
|
||||
"vitest": "^1.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/** @type {import("../../manifold-encapsulated-types")["Manifold"]} */
|
||||
const Manifold = globalThis.Manifold;
|
||||
|
||||
export const examples = {
|
||||
functions: {
|
||||
Intro: function() {
|
||||
// Write code in JavaScript or TypeScript and this editor will show the
|
||||
// API docs. Type e.g. "box." to see the Manifold API. Type
|
||||
// "CrossSection." or "Manifold." to list the 2D and 3D constructors,
|
||||
// respectively. Type "module." to see the static API - these functions
|
||||
// can also be used bare. Use console.log() to print output (lower-right).
|
||||
// This editor defines Z as up and units of mm.
|
||||
const {cube, sphere} = Manifold;
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result", or create at least one
|
||||
// GLTFNode - see Menger Sponge and Gyroid Module examples.
|
||||
const result = box.subtract(ball);
|
||||
|
||||
// For visual debug, wrap any shape with show() and it and all of its
|
||||
// copies will be shown in transparent red, akin to # in OpenSCAD. Or try
|
||||
// only() to ghost out everything else, akin to * in OpenSCAD.
|
||||
|
||||
// All changes are automatically saved and restored between sessions.
|
||||
// This PWA is purely local - there is no server communication.
|
||||
// This means it will work equally well offline once loaded.
|
||||
// Consider installing it (icon in the search bar) for easy access.
|
||||
|
||||
// See the script drop-down above ("Intro") for usage examples. The
|
||||
// gl-matrix package from npm is automatically imported for convenience -
|
||||
// its API is available in the top-level glMatrix object.
|
||||
|
||||
// Use GLTFNode for disjoint manifolds rather than compose(), as this will
|
||||
// keep them better organized in the GLB. This will also allow you to
|
||||
// specify material properties, and even vertex colors via
|
||||
// setProperties(). See Tetrahedron Puzzle example.
|
||||
return result;
|
||||
},
|
||||
|
||||
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 demonstrates how redundant points along a polygon can be
|
||||
// used to make twisted extrusions smoother. It also showcases animated
|
||||
// assemblies. Based on the screw puzzle by George Hart:
|
||||
// https://www.thingiverse.com/thing:186372
|
||||
|
||||
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
|
||||
const gap = 0.2; // Spacing between the two halves to allow sliding.
|
||||
const nDivisions = 50; // Divisions (both ways) in the screw surface.
|
||||
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
|
||||
const tet = Manifold.tetrahedron().intersect(
|
||||
Manifold.tetrahedron().rotate([0, 0, 90]).scale(2.5));
|
||||
|
||||
const box = [];
|
||||
box.push([2, -2], [2, 2]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
}
|
||||
|
||||
const cyan = [0, 1, 1];
|
||||
const magenta = [1, 0, 1];
|
||||
const fade = (color, pos) => {
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
const x = pos[2] / 2;
|
||||
color[i] = cyan[i] * x + magenta[i] * (1 - x);
|
||||
}
|
||||
};
|
||||
|
||||
// setProperties(3, fade) creates three channels of vertex properties
|
||||
// according to the above fade function. setMaterial assigns these
|
||||
// channels as colors, and sets the factor to white, since our default is
|
||||
// yellow.
|
||||
const screw = setMaterial(
|
||||
Manifold.extrude(box, 2, nDivisions, 270).setProperties(3, fade),
|
||||
{baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']});
|
||||
|
||||
const result =
|
||||
tet.intersect(screw.rotate([0, 0, -45]).translate([0, 0, -1]))
|
||||
.scale(scale);
|
||||
|
||||
// Assigned materials are only applied to a GLTFNode. Note that material
|
||||
// definitions cascade, applying recursively to all child surfaces, but
|
||||
// overridden by any materials defined lower down. The default material
|
||||
// properties, as well as animation parameters can be set via
|
||||
// globalDefaults.
|
||||
|
||||
const layFlat = new GLTFNode();
|
||||
layFlat.rotation =
|
||||
[45, -Math.atan(1 / Math.sqrt(2)) * 180 / Math.PI, 120];
|
||||
layFlat.translation = [0, 0, scale * Math.sqrt(3) / 3];
|
||||
|
||||
const fixed = new GLTFNode(layFlat);
|
||||
fixed.manifold = result;
|
||||
fixed.rotation = [0, 0, 180];
|
||||
|
||||
// For 3MF export, only top-level objects are independently arrangeable.
|
||||
const layFlat2 = layFlat.clone();
|
||||
|
||||
const moving = new GLTFNode(layFlat2);
|
||||
moving.manifold = result;
|
||||
// Use functions to create animation, which runs from t=0 to t=1.
|
||||
moving.translation = (t) => {
|
||||
const a = 1 - t;
|
||||
const x = a > 0.5 ? scale * 2 * (0.5 - a) : 0;
|
||||
return [x, x, -2 * x + scale * 4 * (0.5 - Math.abs(0.5 - a))];
|
||||
};
|
||||
moving.rotation = (t) => [0, 0, 270 * 2 * (1 - t)];
|
||||
|
||||
globalDefaults.animationLength = 10; // seconds
|
||||
globalDefaults.animationMode = 'ping-pong';
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
const {sphere, cylinder, union, cube} = Manifold;
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
const edge1 = union(corner, edge).rotate([-90, 0, 0]).translate([
|
||||
-edgeLength / 2, -edgeLength / 2, 0
|
||||
]);
|
||||
|
||||
const edge2 = union(
|
||||
union(edge1, edge1.rotate([0, 0, 180])),
|
||||
edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
const edge4 = union(edge2, edge2.rotate([0, 0, 90])).translate([
|
||||
0, 0, -edgeLength / 2
|
||||
]);
|
||||
|
||||
return union(edge4, edge4.rotate([180, 0, 0]));
|
||||
}
|
||||
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
// Demonstrate how you can use the .split method to perform
|
||||
// a subtraction and an intersection at once
|
||||
const [inside, outside] = result.split(cube(100, true));
|
||||
|
||||
const outsideNode = new GLTFNode();
|
||||
outsideNode.manifold = outside;
|
||||
|
||||
const insideNode = new GLTFNode();
|
||||
insideNode.manifold = inside;
|
||||
insideNode.material = {baseColorFactor: [0, 1, 1]};
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function.
|
||||
// This example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
// It also demonstrates the use of setMorph to animate a warping function.
|
||||
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df =
|
||||
6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
}
|
||||
// Update radius
|
||||
v[0] *= r;
|
||||
v[1] *= r;
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = Manifold.sphere(1, 200);
|
||||
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const scale = 100 / (box.max[0] - box.min[0]);
|
||||
|
||||
setMorphStart(ball, func);
|
||||
const node = new GLTFNode();
|
||||
node.manifold = ball;
|
||||
node.scale = [scale, scale, scale];
|
||||
|
||||
globalDefaults.animationLength = 5; // seconds
|
||||
globalDefaults.animationMode = 'ping-pong';
|
||||
|
||||
const result = heart.scale(scale);
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function() {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with
|
||||
// smooth() and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const positions = [];
|
||||
const triangles = [];
|
||||
positions.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
positions.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
|
||||
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
|
||||
let halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
const triVerts = Uint32Array.from(triangles);
|
||||
const vertProperties = Float32Array.from(positions);
|
||||
const scallop = new Mesh({numProp: 3, triVerts, vertProperties});
|
||||
|
||||
const colorCurvature = (color, pos, oldProp) => {
|
||||
const a = Math.max(0, Math.min(1, oldProp[0] / 3 + 0.5));
|
||||
const b = a * a * (3 - 2 * a);
|
||||
const red = [1, 0, 0];
|
||||
const blue = [0, 0, 1];
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
color[i] = (1 - b) * blue[i] + b * red[i];
|
||||
}
|
||||
};
|
||||
const result = Manifold.smooth(scallop, sharpenedEdges)
|
||||
.refine(n)
|
||||
.calculateCurvature(-1, 0)
|
||||
.setProperties(3, colorCurvature);
|
||||
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.material = {
|
||||
baseColorFactor: [1, 1, 1],
|
||||
metallic: 0,
|
||||
attributes: ['COLOR_0']
|
||||
};
|
||||
return result;
|
||||
},
|
||||
|
||||
TorusKnot: function() {
|
||||
// Creates a classic torus knot, defined as a string wrapping periodically
|
||||
// around the surface of an imaginary donut. If p and q have a common
|
||||
// factor then you will get multiple separate, interwoven knots. This is
|
||||
// an example of using the warp() method, thus avoiding any direct
|
||||
// handling of triangles.
|
||||
|
||||
// The number of times the thread passes through the donut hole.
|
||||
const p = 1;
|
||||
// The number of times the thread circles the donut.
|
||||
const q = 3;
|
||||
// Radius of the interior of the imaginary donut.
|
||||
const majorRadius = 25;
|
||||
// Radius of the small cross-section of the imaginary donut.
|
||||
const minorRadius = 10;
|
||||
// Radius of the small cross-section of the actual object.
|
||||
const threadRadius = 3.75;
|
||||
// Number of linear segments making up the threadRadius circle. Default is
|
||||
// getCircularSegments(threadRadius).
|
||||
const circularSegments = -1;
|
||||
// Number of segments along the length of the knot. Default makes roughly
|
||||
// square facets.
|
||||
const linearSegments = -1;
|
||||
|
||||
// These default values recreate Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
}
|
||||
|
||||
const kLoops = gcd(p, q);
|
||||
const pk = p / kLoops;
|
||||
const qk = q / kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * qk * majorRadius / threadRadius;
|
||||
|
||||
const offset = 2
|
||||
const circle = CrossSection.circle(1, n).translate([offset, 0]);
|
||||
|
||||
const func = (v) => {
|
||||
const psi = qk * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * pk / qk;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
vec3.set(
|
||||
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
|
||||
const center = vec3.fromValues(0, 0, 0);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
vec3.rotateX(v, v, center, -Math.atan2(pk * minorRadius, qk * r));
|
||||
v[0] += minorRadius;
|
||||
vec3.rotateY(v, v, center, theta);
|
||||
v[0] += majorRadius;
|
||||
vec3.rotateZ(v, v, center, psi);
|
||||
};
|
||||
|
||||
const result = Manifold.revolve(circle, m).warp(func);
|
||||
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.rotation = [0, 0, 360 * (k / kLoops) * (qk / pk)];
|
||||
node.translation = [0, 0, result.boundingBox().min[2]];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
vec2.fromValues(-w, -w), vec2.fromValues(-w, 0.0),
|
||||
vec2.fromValues(-w, w), vec2.fromValues(0.0, w),
|
||||
vec2.fromValues(w, w), vec2.fromValues(w, 0.0),
|
||||
vec2.fromValues(w, -w), vec2.fromValues(0.0, -w)
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(
|
||||
holes, hole, w, vec2.add(offset, position, offset), depth + 1,
|
||||
maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = Manifold.cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = Manifold.compose(holes);
|
||||
|
||||
result = Manifold.difference([
|
||||
result,
|
||||
hole,
|
||||
hole.rotate([90, 0, 0]),
|
||||
hole.rotate([0, 90, 0]),
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
const posColors = (newProp, pos) => {
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
newProp[i] = (1 - pos[i]) / 2;
|
||||
}
|
||||
};
|
||||
|
||||
const result = mengerSponge(3)
|
||||
.trimByPlane([1, 1, 1], 0)
|
||||
.setProperties(3, posColors)
|
||||
.scale(100);
|
||||
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.material = {baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']};
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
let b = Manifold.cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
circle.push([
|
||||
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor = Manifold.extrude(circle, width, nDivision, 180)
|
||||
.scale([1, 0.5, 1])
|
||||
.translate([0, radius, 0]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
|
||||
const stretch = [];
|
||||
const dPhiRad = 2 * Math.PI / nCut;
|
||||
|
||||
const o = vec2.fromValues(0, 0);
|
||||
const p0 = vec2.fromValues(outerRadius, 0);
|
||||
const p1 = vec2.fromValues(innerRadius, -cut);
|
||||
const p2 = vec2.fromValues(innerRadius, cut);
|
||||
for (let i = 0; i < nCut; ++i) {
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p1, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p2, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
}
|
||||
const result =
|
||||
Manifold.intersection(Manifold.extrude(stretch, width), b);
|
||||
return result;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
|
||||
nCut = 27, nDivision = 30) {
|
||||
const twistRadius = Math.PI * radius / nDecor;
|
||||
const decorRadius = twistRadius * 1.5;
|
||||
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
|
||||
const innerRadius = outerRadius - height;
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return Manifold.difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
cut - adjThickness, nCut, nDivision),
|
||||
base(
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
|
||||
GyroidModule: function() {
|
||||
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:25477. This sample demonstrates the
|
||||
// use of a Signed Distance Function (SDF) to create smooth, complex
|
||||
// manifolds.
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
// number of modules along pyramid edge (use 1 for print orientation)
|
||||
const m = 4;
|
||||
// module size
|
||||
const size = 20;
|
||||
// SDF resolution
|
||||
const n = 20;
|
||||
|
||||
const pi = 3.14159;
|
||||
|
||||
function gyroid(p) {
|
||||
const x = p[0] - pi / 4;
|
||||
const y = p[1] - pi / 4;
|
||||
const z = p[2] - pi / 4;
|
||||
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: vec3.fromValues(-period, -period, -period),
|
||||
max: vec3.fromValues(period, period, period)
|
||||
};
|
||||
return Manifold.levelSet(gyroid, box, period / n, level)
|
||||
.scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
const box = Manifold.cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const result =
|
||||
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
|
||||
return result.intersect(box.rotate([0, 0, 45]));
|
||||
}
|
||||
|
||||
const gyroidModule = rhombicDodecahedron()
|
||||
.intersect(gyroidOffset(-0.4))
|
||||
.subtract(gyroidOffset(0.4));
|
||||
|
||||
if (m > 1) {
|
||||
for (let i = 0; i < m; ++i) {
|
||||
for (let j = i; j < m; ++j) {
|
||||
for (let k = j; k < m; ++k) {
|
||||
const node = new GLTFNode();
|
||||
node.manifold = gyroidModule;
|
||||
node.translation =
|
||||
[(k + i - j) * size, (k - i) * size, (-j) * size];
|
||||
node.material = {
|
||||
baseColorFactor:
|
||||
[(k + i - j + 1) / m, (k - i + 1) / m, (j + 1) / m]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = gyroidModule.rotate([-45, 0, 90]).translate([
|
||||
0, 0, size / Math.sqrt(2)
|
||||
]);
|
||||
return result;
|
||||
},
|
||||
},
|
||||
|
||||
functionBodies: new Map()
|
||||
};
|
||||
|
||||
for (const [func, code] of Object.entries(examples.functions)) {
|
||||
const whole = code.toString();
|
||||
const lines = whole.split('\n');
|
||||
lines.splice(0, 1); // remove first line
|
||||
lines.splice(-2, 2); // remove last two lines
|
||||
// remove first six leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
|
||||
|
||||
const name =
|
||||
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
|
||||
examples.functionBodies.set(name, body);
|
||||
};
|
||||
|
||||
if (typeof self !== 'undefined') {
|
||||
self.examples = examples;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 539 B After Width: | Height: | Size: 539 B |
|
After Width: | Height: | Size: 704 KiB |
|
After Width: | Height: | Size: 952 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 375 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 |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 471 B |
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "ManifoldCAD",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/mengerSponge192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "/icons/mengerSponge512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"id": "/",
|
||||
"start_url": "/",
|
||||
"background_color": "#FFFFFF",
|
||||
"display": "standalone",
|
||||
"orientation": "landscape",
|
||||
"scope": "/",
|
||||
"theme_color": "#A288B3",
|
||||
"description": "Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.",
|
||||
"categories": [
|
||||
"design",
|
||||
"developer",
|
||||
"education",
|
||||
"graphics",
|
||||
"productivity"
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/icons/manifoldCAD.png",
|
||||
"type": "image/png",
|
||||
"sizes": "2906x1980"
|
||||
},
|
||||
{
|
||||
"src": "/icons/manifoldCADonly.png",
|
||||
"type": "image/png",
|
||||
"sizes": "2906x1980"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Increment version when updating CDN URLs to clean up cache.
|
||||
const cacheName = 'manifoldCAD-cache-v1';
|
||||
|
||||
self.addEventListener('activate', e => {
|
||||
e.waitUntil(caches.keys().then((keyList) => {
|
||||
return Promise.all(keyList.map((key) => {
|
||||
if (key !== cacheName) {
|
||||
return caches.delete(key);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
});
|
||||
|
||||
// Serves from cache, then updates the cache in the background from the network,
|
||||
// if available. Update available on refresh.
|
||||
self.addEventListener(
|
||||
'fetch',
|
||||
e => {e.respondWith(caches.match(e.request).then(cachedResponse => {
|
||||
const networkFetch = fetch(e.request).then(response => {
|
||||
const clone = response.clone();
|
||||
caches.open(cacheName).then(cache => {
|
||||
cache.put(e.request, clone);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
return cachedResponse || networkFetch;
|
||||
}))});
|
||||
@@ -0,0 +1 @@
|
||||
declare module 'simple-dropzone';
|
||||
@@ -1,119 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Manifold - three.js texture example</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #f7f7f7;
|
||||
font-family: 'Rubik', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: rgba(0, 0, 0, .87);
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 700px;
|
||||
margin: 1em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vw;
|
||||
max-width: 600px;
|
||||
max-height: 600px;
|
||||
}
|
||||
</style>
|
||||
</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>
|
||||
<input type="checkbox"/><br />
|
||||
<canvas id="output"></canvas>
|
||||
</body>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
|
||||
<script src="manifold.js"></script>
|
||||
<script>
|
||||
Module().then(function (wasm) {
|
||||
wasm.setup();
|
||||
|
||||
const cube = new wasm.Mesh();
|
||||
cube.vertPos = new Float32Array(24);
|
||||
cube.triVerts = new Uint32Array(36);
|
||||
cube.vertPos.set([
|
||||
-1, -1, -1,
|
||||
1, -1, -1,
|
||||
1, 1, -1,
|
||||
-1, 1, -1,
|
||||
-1, -1, 1,
|
||||
1, -1, 1,
|
||||
1, 1, 1,
|
||||
-1, 1, 1]);
|
||||
cube.triVerts.set([
|
||||
0, 2, 1, 0, 3, 2,
|
||||
4, 5, 6, 4, 6, 7,
|
||||
0, 1, 5, 0, 5, 4,
|
||||
1, 2, 6, 1, 6, 5,
|
||||
2, 3, 7, 2, 7, 6,
|
||||
3, 0, 4, 3, 4, 7]);
|
||||
|
||||
const manifold = wasm.Manifold(cube);
|
||||
|
||||
const result = manifold.add(manifold.rotate([90, 0, 90]).translate([1, 1, 0]));
|
||||
|
||||
// we have a manifold, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 1, 100);
|
||||
camera.position.z = 10;
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(mesh2geometry(result.getMesh()),
|
||||
new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
|
||||
const wireframe = new THREE.WireframeGeometry(mesh.geometry);
|
||||
const lines = new THREE.LineSegments(wireframe);
|
||||
|
||||
scene.add(mesh);
|
||||
scene.add(lines);
|
||||
lines.visible = false;
|
||||
|
||||
const checkbox = document.querySelector('input');
|
||||
checkbox.onchange = function (event) {
|
||||
mesh.visible = !checkbox.checked;
|
||||
lines.visible = checkbox.checked;
|
||||
};
|
||||
|
||||
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) {
|
||||
scene.rotation.x = time / 2000;
|
||||
scene.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
});
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
@@ -36,86 +35,21 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
|
||||
involving GLTF export and <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 src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
|
||||
<script src="manifold.js"></script>
|
||||
<script>
|
||||
let wasm;
|
||||
Module().then(function (tmp) {
|
||||
wasm = tmp;
|
||||
// we have manifold module, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
|
||||
wasm.setup();
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
scene.add(mesh);
|
||||
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
|
||||
const output = document.querySelector("#output");
|
||||
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
|
||||
const dim = output.getBoundingClientRect();
|
||||
renderer.setSize(dim.width, dim.height);
|
||||
renderer.setAnimationLoop(function (time) {
|
||||
mesh.rotation.x = time / 2000;
|
||||
mesh.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
});
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
// most of three.js geometries aren't manifolds, so...
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="three.ts"></script>
|
||||
|
||||
</html>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "es2022",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": [
|
||||
"es2022",
|
||||
"dom"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"include": [
|
||||
"./*.ts"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// vite.config.js
|
||||
import {resolve} from 'path'
|
||||
import {defineConfig} from 'vite'
|
||||
|
||||
import emscriptenStaticWorkerOptions from './vite-fixup-plugin.js'
|
||||
|
||||
export default defineConfig({
|
||||
test: {testTimeout: 15000},
|
||||
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'),
|
||||
makeManifold: resolve(__dirname, 'make-manifold.html'),
|
||||
modelViewer: resolve(__dirname, 'model-viewer.html'),
|
||||
three: resolve(__dirname, 'three.html'),
|
||||
},
|
||||
output: {
|
||||
entryFileNames: `assets/[name].js`,
|
||||
chunkFileNames: `assets/[name].js`,
|
||||
assetFileNames: `assets/[name].[ext]`
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||