refactor: stl->3mf
This commit is contained in:
parent
7be206581d
commit
2476659fc6
|
|
@ -22,7 +22,8 @@
|
||||||
"mermaid": "^11.0.0",
|
"mermaid": "^11.0.0",
|
||||||
"solid-element": "^1.9.1",
|
"solid-element": "^1.9.1",
|
||||||
"solid-js": "^1.9.3",
|
"solid-js": "^1.9.3",
|
||||||
"three": "^0.183.2"
|
"three": "^0.183.2",
|
||||||
|
"three-3mf-exporter": "^45.1.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"ttrpg": "dist/cli/index.js"
|
"ttrpg": "dist/cli/index.js"
|
||||||
|
|
@ -4655,6 +4656,12 @@
|
||||||
"url": "https://opencollective.com/core-js"
|
"url": "https://opencollective.com/core-js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/core-util-is": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/cose-base": {
|
"node_modules/cose-base": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
|
||||||
|
|
@ -5757,6 +5764,41 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-xml-builder": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"path-expression-matcher": "^1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fast-xml-parser": {
|
||||||
|
"version": "5.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.5.tgz",
|
||||||
|
"integrity": "sha512-NLY+V5NNbdmiEszx9n14mZBseJTC50bRq1VHsaxOmR72JDuZt+5J1Co+dC/4JPnyq+WrIHNM69r0sqf7BMb3Mg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-xml-builder": "^1.1.3",
|
||||||
|
"path-expression-matcher": "^1.1.3",
|
||||||
|
"strnum": "^2.1.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"fxparser": "src/cli/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fb-watchman": {
|
"node_modules/fb-watchman": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
|
||||||
|
|
@ -6156,6 +6198,12 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immediate": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/import-local": {
|
"node_modules/import-local": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
|
||||||
|
|
@ -6202,7 +6250,6 @@
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/internmap": {
|
"node_modules/internmap": {
|
||||||
|
|
@ -6287,6 +6334,12 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/isarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/isexe": {
|
"node_modules/isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
|
|
@ -8133,6 +8186,18 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jszip": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
||||||
|
"license": "(MIT OR GPL-3.0-or-later)",
|
||||||
|
"dependencies": {
|
||||||
|
"lie": "~3.3.0",
|
||||||
|
"pako": "~1.0.2",
|
||||||
|
"readable-stream": "~2.3.6",
|
||||||
|
"setimmediate": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/katex": {
|
"node_modules/katex": {
|
||||||
"version": "0.16.33",
|
"version": "0.16.33",
|
||||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.33.tgz",
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.33.tgz",
|
||||||
|
|
@ -8206,6 +8271,15 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lie": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"immediate": "~3.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lightningcss": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.31.1",
|
"version": "1.31.1",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
|
||||||
|
|
@ -8946,6 +9020,12 @@
|
||||||
"integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
|
"integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pako": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||||
|
"license": "(MIT AND Zlib)"
|
||||||
|
},
|
||||||
"node_modules/parse-json": {
|
"node_modules/parse-json": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||||
|
|
@ -8994,6 +9074,21 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-expression-matcher": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-is-absolute": {
|
"node_modules/path-is-absolute": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
|
@ -9188,6 +9283,12 @@
|
||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/process-nextick-args": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/prompts": {
|
"node_modules/prompts": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||||
|
|
@ -9256,6 +9357,21 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/readable-stream": {
|
||||||
|
"version": "2.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||||
|
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"core-util-is": "~1.0.0",
|
||||||
|
"inherits": "~2.0.3",
|
||||||
|
"isarray": "~1.0.0",
|
||||||
|
"process-nextick-args": "~2.0.0",
|
||||||
|
"safe-buffer": "~5.1.1",
|
||||||
|
"string_decoder": "~1.1.1",
|
||||||
|
"util-deprecate": "~1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
||||||
|
|
@ -9417,6 +9533,12 @@
|
||||||
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
|
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/safer-buffer": {
|
"node_modules/safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
|
@ -9467,6 +9589,12 @@
|
||||||
"seroval": "^1.0"
|
"seroval": "^1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/setimmediate": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|
@ -9602,6 +9730,15 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string_decoder": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string-length": {
|
"node_modules/string-length": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
||||||
|
|
@ -9677,6 +9814,18 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/strnum": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/stylis": {
|
"node_modules/stylis": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
|
||||||
|
|
@ -9758,6 +9907,19 @@
|
||||||
"integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==",
|
"integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/three-3mf-exporter": {
|
||||||
|
"version": "45.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/three-3mf-exporter/-/three-3mf-exporter-45.1.0.tgz",
|
||||||
|
"integrity": "sha512-lNQSK+dHaHVBsviJsViMlniBjb/hRUKIHHf5Fjnppv/wqCtbJiseuzjpptJV5jiyY3zsdjLgnKTQ/g4COgMvmw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-xml-parser": "^5.0.9",
|
||||||
|
"jszip": "^3.10.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"three": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyexec": {
|
"node_modules/tinyexec": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
|
||||||
|
|
@ -10104,7 +10266,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@
|
||||||
"mermaid": "^11.0.0",
|
"mermaid": "^11.0.0",
|
||||||
"solid-element": "^1.9.1",
|
"solid-element": "^1.9.1",
|
||||||
"solid-js": "^1.9.3",
|
"solid-js": "^1.9.3",
|
||||||
"three": "^0.183.2"
|
"three": "^0.183.2",
|
||||||
|
"three-3mf-exporter": "^45.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@image-tracer-ts/core": "^1.0.2",
|
"@image-tracer-ts/core": "^1.0.2",
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
Show, createEffect,
|
Show, createEffect,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
|
import { ThreeMFLoader } from "three/addons/loaders/3MFLoader.js";
|
||||||
|
|
||||||
export interface TokenViewerProps {
|
export interface TokenViewerProps {
|
||||||
stlUrl: string | null;
|
stlUrl: string | null;
|
||||||
|
|
@ -21,49 +21,66 @@ export default function MdTokenViewer(props: TokenViewerProps) {
|
||||||
let camera: THREE.PerspectiveCamera | null = null;
|
let camera: THREE.PerspectiveCamera | null = null;
|
||||||
let renderer: THREE.WebGLRenderer | null = null;
|
let renderer: THREE.WebGLRenderer | null = null;
|
||||||
let mesh: THREE.Mesh | null = null;
|
let mesh: THREE.Mesh | null = null;
|
||||||
|
let group: THREE.Group | null = null;
|
||||||
let animationId: number | null = null;
|
let animationId: number | null = null;
|
||||||
let isDragging = false;
|
let isDragging = false;
|
||||||
let previousMousePosition = { x: 0, y: 0 };
|
let previousMousePosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
// 加载 STL 用于预览
|
// 加载 3MF 用于预览
|
||||||
const loadSTL = async (url: string) => {
|
const loadSTL = async (url: string) => {
|
||||||
const viewerEl = viewerRef();
|
const viewerEl = viewerRef();
|
||||||
if (!viewerEl) return;
|
if (!viewerEl) return;
|
||||||
|
|
||||||
// 清理旧的场景
|
// 清理旧的场景
|
||||||
if (mesh) {
|
if (group) {
|
||||||
scene?.remove(mesh);
|
scene?.remove(group);
|
||||||
mesh.geometry.dispose();
|
group.traverse((obj) => {
|
||||||
(mesh.material as THREE.Material).dispose();
|
if ((obj as THREE.Mesh).isMesh) {
|
||||||
|
const meshObj = obj as THREE.Mesh;
|
||||||
|
meshObj.geometry.dispose();
|
||||||
|
(meshObj.material as THREE.Material).dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
group = null;
|
||||||
mesh = null;
|
mesh = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const loader = new ThreeMFLoader();
|
||||||
const stlText = await response.text();
|
const object = await loader.loadAsync(url);
|
||||||
|
|
||||||
// 解析 STL
|
// 3MF 文件可能返回一个 Group,包含多个 Mesh
|
||||||
const loader = new STLLoader();
|
group = object instanceof THREE.Group ? object : new THREE.Group().add(object);
|
||||||
const geometry = loader.parse(stlText);
|
|
||||||
|
|
||||||
// 计算边界并居中
|
// 居中并调整方向
|
||||||
geometry.center();
|
group.center();
|
||||||
|
group.rotateX(Math.PI);
|
||||||
|
|
||||||
// 创建材质
|
// 为每个 mesh 启用原始颜色
|
||||||
const material = new THREE.MeshStandardMaterial({
|
group.traverse((child) => {
|
||||||
color: 0x808080,
|
if ((child as THREE.Mesh).isMesh) {
|
||||||
metalness: 0.3,
|
const childMesh = child as THREE.Mesh;
|
||||||
roughness: 0.7,
|
// 保留原始顶点颜色或材质颜色
|
||||||
side: THREE.DoubleSide,
|
if (childMesh.geometry.attributes.color) {
|
||||||
|
childMesh.material.vertexColors = true;
|
||||||
|
}
|
||||||
|
// 确保材质是标准材质以支持光照
|
||||||
|
if (!(childMesh.material instanceof THREE.MeshStandardMaterial)) {
|
||||||
|
childMesh.material = new THREE.MeshStandardMaterial({
|
||||||
|
color: childMesh.material.color,
|
||||||
|
metalness: 0.3,
|
||||||
|
roughness: 0.7,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mesh = new THREE.Mesh(geometry, material);
|
scene?.add(group);
|
||||||
mesh.rotateX(Math.PI);
|
mesh = group.children[0] as THREE.Mesh;
|
||||||
scene?.add(mesh);
|
|
||||||
|
|
||||||
// 调整相机
|
// 调整相机
|
||||||
if (camera && scene) {
|
if (camera && scene) {
|
||||||
const box = new THREE.Box3().setFromObject(mesh);
|
const box = new THREE.Box3().setFromObject(group);
|
||||||
const size = box.getSize(new THREE.Vector3());
|
const size = box.getSize(new THREE.Vector3());
|
||||||
const maxDim = Math.max(size.x, size.y, size.z);
|
const maxDim = Math.max(size.x, size.y, size.z);
|
||||||
|
|
||||||
|
|
@ -73,8 +90,8 @@ export default function MdTokenViewer(props: TokenViewerProps) {
|
||||||
|
|
||||||
setIsLoaded(true);
|
setIsLoaded(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("加载 STL 预览失败:", e);
|
console.error("加载 3MF 预览失败:", e);
|
||||||
setError(e instanceof Error ? e.message : "加载 STL 失败");
|
setError(e instanceof Error ? e.message : "加载模型失败");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -125,13 +142,13 @@ export default function MdTokenViewer(props: TokenViewerProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
if (!isDragging || !mesh) return;
|
if (!isDragging || !group) return;
|
||||||
|
|
||||||
const deltaX = e.clientX - previousMousePosition.x;
|
const deltaX = e.clientX - previousMousePosition.x;
|
||||||
const deltaY = e.clientY - previousMousePosition.y;
|
const deltaY = e.clientY - previousMousePosition.y;
|
||||||
|
|
||||||
mesh.rotation.y += deltaX * 0.01;
|
group.rotation.y += deltaX * 0.01;
|
||||||
mesh.rotation.x += deltaY * 0.01;
|
group.rotation.x += deltaY * 0.01;
|
||||||
|
|
||||||
previousMousePosition = { x: e.clientX, y: e.clientY };
|
previousMousePosition = { x: e.clientX, y: e.clientY };
|
||||||
};
|
};
|
||||||
|
|
@ -154,9 +171,9 @@ export default function MdTokenViewer(props: TokenViewerProps) {
|
||||||
|
|
||||||
// 动画循环
|
// 动画循环
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
if (scene && camera && renderer && mesh) {
|
if (scene && camera && renderer && group) {
|
||||||
if (!isDragging) {
|
if (!isDragging) {
|
||||||
mesh.rotation.y += 0.005; // 自动旋转
|
group.rotation.y += 0.005; // 自动旋转
|
||||||
}
|
}
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { resolvePath } from "./utils/path";
|
import { resolvePath } from "./utils/path";
|
||||||
import { traceImage, type TracedLayer } from "./utils/image-tracer";
|
import { traceImage, type TracedLayer } from "./utils/image-tracer";
|
||||||
import { generateSTL, type ExtrusionSettings } from "./utils/stl-generator";
|
import { generate3MF, type ExtrusionSettings } from "./utils/3mf-generator";
|
||||||
import MdTokenViewer from "./md-token-viewer";
|
import MdTokenViewer from "./md-token-viewer";
|
||||||
|
|
||||||
export interface TokenProps {
|
export interface TokenProps {
|
||||||
|
|
@ -30,7 +30,7 @@ customElement("md-token", { size: 50, defaultThickness: 2 }, (props, { element }
|
||||||
|
|
||||||
const [showEditor, setShowEditor] = createSignal(false);
|
const [showEditor, setShowEditor] = createSignal(false);
|
||||||
const [layers, setLayers] = createSignal<LayerSettings[]>([]);
|
const [layers, setLayers] = createSignal<LayerSettings[]>([]);
|
||||||
const [stlUrl, setStlUrl] = createSignal<string | null>(null);
|
const [modelUrl, setModelUrl] = createSignal<string | null>(null);
|
||||||
const [isGenerating, setIsGenerating] = createSignal(false);
|
const [isGenerating, setIsGenerating] = createSignal(false);
|
||||||
const [error, setError] = createSignal<string | null>(null);
|
const [error, setError] = createSignal<string | null>(null);
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ customElement("md-token", { size: 50, defaultThickness: 2 }, (props, { element }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 生成 STL 模型
|
// 生成 3MF 模型
|
||||||
const generateModel = async () => {
|
const generateModel = async () => {
|
||||||
if (!image()) return;
|
if (!image()) return;
|
||||||
|
|
||||||
|
|
@ -114,9 +114,9 @@ customElement("md-token", { size: 50, defaultThickness: 2 }, (props, { element }
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
const stlBlob = await generateSTL(image()!, traceResult()!, settings);
|
const modelBlob = await generate3MF(image()!, traceResult()!, settings);
|
||||||
const url = URL.createObjectURL(stlBlob);
|
const url = URL.createObjectURL(modelBlob);
|
||||||
setStlUrl(url);
|
setModelUrl(url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : "生成模型失败");
|
setError(e instanceof Error ? e.message : "生成模型失败");
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -138,20 +138,20 @@ customElement("md-token", { size: 50, defaultThickness: 2 }, (props, { element }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 下载 STL 文件
|
// 下载 3MF 文件
|
||||||
const downloadSTL = () => {
|
const downloadModel = () => {
|
||||||
const url = stlUrl();
|
const url = modelUrl();
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = `token-${Date.now()}.stl`;
|
a.download = `token-${Date.now()}.3mf`;
|
||||||
a.click();
|
a.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 清理 URL
|
// 清理 URL
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
const url = stlUrl();
|
const url = modelUrl();
|
||||||
if (url) {
|
if (url) {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
@ -177,13 +177,13 @@ customElement("md-token", { size: 50, defaultThickness: 2 }, (props, { element }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* STL 预览 (如果有) */}
|
{/* 3D 模型预览 */}
|
||||||
<Show when={stlUrl()}>
|
<Show when={modelUrl()}>
|
||||||
<div class="flex-1 min-w-[200px]">
|
<div class="flex-1 min-w-[200px]">
|
||||||
<h4 class="text-sm font-semibold mb-2 text-gray-700">
|
<h4 class="text-sm font-semibold mb-2 text-gray-700">
|
||||||
3D 模型预览
|
3D 模型预览
|
||||||
</h4>
|
</h4>
|
||||||
<MdTokenViewer stlUrl={stlUrl()}/>
|
<MdTokenViewer stlUrl={modelUrl()}/>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -286,12 +286,12 @@ customElement("md-token", { size: 50, defaultThickness: 2 }, (props, { element }
|
||||||
>
|
>
|
||||||
{isGenerating() ? "生成中..." : "🔄 生成 3D 模型"}
|
{isGenerating() ? "生成中..." : "🔄 生成 3D 模型"}
|
||||||
</button>
|
</button>
|
||||||
<Show when={stlUrl()}>
|
<Show when={modelUrl()}>
|
||||||
<button
|
<button
|
||||||
onClick={downloadSTL}
|
onClick={downloadModel}
|
||||||
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded font-medium transition-colors"
|
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded font-medium transition-colors"
|
||||||
>
|
>
|
||||||
📥 下载 STL
|
📥 下载 3MF
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { exportTo3MF } from "three-3mf-exporter";
|
||||||
|
import type { TraceResult, PathData } from "./image-tracer";
|
||||||
|
|
||||||
|
export interface ExtrusionSettings {
|
||||||
|
size: number; // 模型整体尺寸 (mm)
|
||||||
|
layers: Array<{
|
||||||
|
id: string;
|
||||||
|
thickness: number; // 图层厚度 (mm)
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LayerMesh {
|
||||||
|
id: string;
|
||||||
|
mesh: THREE.Mesh;
|
||||||
|
thickness: number;
|
||||||
|
color: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从图层索引生成颜色(使用黄金角确保颜色分散)
|
||||||
|
*/
|
||||||
|
function generateLayerColor(index: number): number {
|
||||||
|
const hue = (index * 137.508) % 360; // 黄金角
|
||||||
|
return new THREE.Color(`hsl(${hue}, 70%, 50%)`).getHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将矢量路径生成 3MF 文件
|
||||||
|
* @param image - 原始图片
|
||||||
|
* @param traceResult - 矢量追踪结果
|
||||||
|
* @param settings - 挤压设置
|
||||||
|
* @returns 3MF Blob
|
||||||
|
*/
|
||||||
|
export async function generate3MF(
|
||||||
|
image: HTMLImageElement,
|
||||||
|
traceResult: TraceResult,
|
||||||
|
settings: ExtrusionSettings
|
||||||
|
): Promise<Blob> {
|
||||||
|
// 创建 Three.js 场景
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
|
||||||
|
// 计算缩放比例,使模型适应指定尺寸
|
||||||
|
const maxDimension = Math.max(traceResult.width, traceResult.height);
|
||||||
|
const scale = settings.size / maxDimension;
|
||||||
|
|
||||||
|
// 中心偏移
|
||||||
|
const offsetX = -traceResult.width / 2;
|
||||||
|
const offsetY = -traceResult.height / 2;
|
||||||
|
|
||||||
|
// 为每个启用的图层创建网格
|
||||||
|
let currentHeight = 0;
|
||||||
|
const meshes: LayerMesh[] = [];
|
||||||
|
let layerIndex = 0;
|
||||||
|
|
||||||
|
for (const layerSetting of settings.layers) {
|
||||||
|
const layer = traceResult.layers.find((l) => l.id === layerSetting.id);
|
||||||
|
if (!layer) continue;
|
||||||
|
|
||||||
|
// 为该图层的所有路径创建形状
|
||||||
|
const shapes: THREE.Shape[] = [];
|
||||||
|
|
||||||
|
for (const path of layer.paths) {
|
||||||
|
if (path.points.length < 2) continue;
|
||||||
|
|
||||||
|
const shape = createShapeFromPath(path, scale, offsetX, offsetY);
|
||||||
|
if (shape) {
|
||||||
|
shapes.push(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shapes.length === 0) continue;
|
||||||
|
|
||||||
|
// 创建挤压几何体
|
||||||
|
const extrudeSettings: THREE.ExtrudeGeometryOptions = {
|
||||||
|
depth: layerSetting.thickness,
|
||||||
|
curveSegments: 36,
|
||||||
|
bevelEnabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果有多个形状,创建多个几何体并合并
|
||||||
|
const geometries: THREE.ExtrudeGeometry[] = [];
|
||||||
|
for (const shape of shapes) {
|
||||||
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||||
|
geometries.push(geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并同一图层的几何体
|
||||||
|
let combinedGeometry;
|
||||||
|
if (geometries.length === 1) {
|
||||||
|
combinedGeometry = geometries[0];
|
||||||
|
} else {
|
||||||
|
combinedGeometry = mergeGeometries(geometries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为该图层生成颜色
|
||||||
|
const color = generateLayerColor(layerIndex);
|
||||||
|
const material = new THREE.MeshStandardMaterial({
|
||||||
|
color,
|
||||||
|
metalness: 0.3,
|
||||||
|
roughness: 0.7,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mesh = new THREE.Mesh(combinedGeometry, material);
|
||||||
|
|
||||||
|
// 设置图层高度(堆叠)
|
||||||
|
mesh.position.y = currentHeight;
|
||||||
|
|
||||||
|
scene.add(mesh);
|
||||||
|
meshes.push({
|
||||||
|
id: layerSetting.id,
|
||||||
|
mesh,
|
||||||
|
thickness: layerSetting.thickness,
|
||||||
|
color,
|
||||||
|
});
|
||||||
|
|
||||||
|
currentHeight += layerSetting.thickness;
|
||||||
|
layerIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meshes.length === 0) {
|
||||||
|
throw new Error("没有可生成的图层");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出为 3MF
|
||||||
|
const data = await exportTo3MF(scene);
|
||||||
|
const blob = new Blob([data], { type: "model/3mf" });
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
scene.clear();
|
||||||
|
meshes.forEach(({ mesh }) => {
|
||||||
|
mesh.geometry.dispose();
|
||||||
|
(mesh.material as THREE.Material).dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从路径数据创建 Three.js 形状
|
||||||
|
*/
|
||||||
|
function createShapeFromPath(
|
||||||
|
path: PathData,
|
||||||
|
scale: number,
|
||||||
|
offsetX: number,
|
||||||
|
offsetY: number
|
||||||
|
): THREE.Shape | null {
|
||||||
|
if (path.points.length < 2) return null;
|
||||||
|
|
||||||
|
const shape = new THREE.Shape();
|
||||||
|
|
||||||
|
// 移动到起点
|
||||||
|
const startPoint = path.points[0];
|
||||||
|
shape.moveTo(
|
||||||
|
(startPoint.x + offsetX) * scale,
|
||||||
|
(startPoint.y + offsetY) * scale
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制线段到后续点
|
||||||
|
for (let i = 1; i < path.points.length; i++) {
|
||||||
|
const point = path.points[i];
|
||||||
|
shape.lineTo(
|
||||||
|
(point.x + offsetX) * scale,
|
||||||
|
(point.y + offsetY) * scale
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是闭合路径,闭合形状
|
||||||
|
if (path.isClosed) {
|
||||||
|
shape.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并多个几何体
|
||||||
|
*/
|
||||||
|
function mergeGeometries(
|
||||||
|
geometries: THREE.ExtrudeGeometry[]
|
||||||
|
): THREE.ExtrudeGeometry {
|
||||||
|
const mergedGeometry = geometries[0].clone();
|
||||||
|
|
||||||
|
for (let i = 1; i < geometries.length; i++) {
|
||||||
|
const geometry = geometries[i];
|
||||||
|
|
||||||
|
const positionAttribute = geometry.getAttribute("position");
|
||||||
|
const normalAttribute = geometry.getAttribute("normal");
|
||||||
|
const uvAttribute = geometry.getAttribute("uv");
|
||||||
|
|
||||||
|
if (positionAttribute) {
|
||||||
|
const positions = mergedGeometry.getAttribute("position");
|
||||||
|
const newPositions = new Float32Array(
|
||||||
|
positions.array.length + positionAttribute.array.length
|
||||||
|
);
|
||||||
|
newPositions.set(positions.array);
|
||||||
|
newPositions.set(positionAttribute.array, positions.array.length);
|
||||||
|
mergedGeometry.setAttribute(
|
||||||
|
"position",
|
||||||
|
new THREE.BufferAttribute(newPositions, 3)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalAttribute) {
|
||||||
|
const normals = mergedGeometry.getAttribute("normal");
|
||||||
|
const newNormals = new Float32Array(
|
||||||
|
normals.array.length + normalAttribute.array.length
|
||||||
|
);
|
||||||
|
newNormals.set(normals.array);
|
||||||
|
newNormals.set(normalAttribute.array, normals.array.length);
|
||||||
|
mergedGeometry.setAttribute(
|
||||||
|
"normal",
|
||||||
|
new THREE.BufferAttribute(newNormals, 3)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uvAttribute) {
|
||||||
|
const uvs = mergedGeometry.getAttribute("uv");
|
||||||
|
const newUvs = new Float32Array(
|
||||||
|
uvs.array.length + uvAttribute.array.length
|
||||||
|
);
|
||||||
|
newUvs.set(uvs.array);
|
||||||
|
newUvs.set(uvAttribute.array, uvs.array.length);
|
||||||
|
mergedGeometry.setAttribute(
|
||||||
|
"uv",
|
||||||
|
new THREE.BufferAttribute(newUvs, 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedGeometry.computeVertexNormals();
|
||||||
|
return mergedGeometry;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue