@pascal-app/editor 1.0.0-beta.5 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/LICENSE +21 -0
  2. package/bunfig.toml +4 -0
  3. package/package.json +15 -8
  4. package/scripts/generate-print-golden-house.ts +89 -0
  5. package/src/components/editor/bake-exporter.tsx +5 -0
  6. package/src/components/editor/bake-thumbnail.tsx +4 -1
  7. package/src/components/editor/camera-dragging-lifecycle.test.ts +41 -0
  8. package/src/components/editor/camera-dragging-lifecycle.ts +10 -2
  9. package/src/components/editor/capture-camera-rig.tsx +59 -0
  10. package/src/components/editor/custom-camera-controls.tsx +20 -11
  11. package/src/components/editor/editor-layout-mobile.tsx +6 -6
  12. package/src/components/editor/editor-layout-v2.tsx +9 -2
  13. package/src/components/editor/export-manager.tsx +175 -33
  14. package/src/components/editor/first-person/build-collider-world.test.ts +30 -0
  15. package/src/components/editor/first-person-controls.tsx +180 -12
  16. package/src/components/editor/floating-action-menu.tsx +55 -51
  17. package/src/components/editor/floorplan-panel.tsx +99 -15
  18. package/src/components/editor/grid.tsx +12 -7
  19. package/src/components/editor/group-actions.ts +34 -50
  20. package/src/components/editor/group-move-3d.ts +51 -21
  21. package/src/components/editor/group-transform-shared.test.ts +78 -0
  22. package/src/components/editor/group-transform-shared.ts +74 -7
  23. package/src/components/editor/handles/handle-arrow-raycast.test.ts +86 -0
  24. package/src/components/editor/handles/handle-arrow.tsx +109 -48
  25. package/src/components/editor/handles/handle-placement.test.ts +111 -0
  26. package/src/components/editor/handles/handle-placement.ts +69 -0
  27. package/src/components/editor/handles/linear-resize-drag.ts +78 -0
  28. package/src/components/editor/handles/resize-snap.test.ts +46 -0
  29. package/src/components/editor/handles/resize-snap.ts +10 -1
  30. package/src/components/editor/handles/use-handle-drag.ts +23 -3
  31. package/src/components/editor/index.tsx +151 -24
  32. package/src/components/editor/node-action-menu.tsx +14 -1
  33. package/src/components/editor/node-arrow-handles.tsx +67 -119
  34. package/src/components/editor/selection-manager.tsx +153 -42
  35. package/src/components/editor/snapshot-capture-overlay.tsx +298 -39
  36. package/src/components/editor/snapshot-capture.test.ts +422 -0
  37. package/src/components/editor/snapshot-capture.ts +145 -0
  38. package/src/components/editor/thumbnail-generator.tsx +295 -239
  39. package/src/components/editor/use-floorplan-background-placement.ts +53 -5
  40. package/src/components/editor/wall-move-side-handles.tsx +299 -7
  41. package/src/components/editor-2d/floorplan-group-move.tsx +67 -33
  42. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +3 -1
  43. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +12 -1
  44. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +129 -38
  45. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +14 -1
  46. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +16 -0
  47. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +2 -0
  48. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +52 -3
  49. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +196 -1
  50. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +226 -35
  51. package/src/components/procedural-items/inspector.tsx +223 -0
  52. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +402 -0
  53. package/src/components/systems/ceiling/ceiling-bracket-batch.ts +371 -0
  54. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +347 -0
  55. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +281 -305
  56. package/src/components/systems/ceiling/ceiling-system.tsx +3 -1
  57. package/src/components/systems/roof/roof-edit-system.test.tsx +120 -0
  58. package/src/components/systems/roof/roof-edit-system.tsx +10 -19
  59. package/src/components/systems/selection-affordance-manager.tsx +59 -9
  60. package/src/components/systems/selection-affordance-services.ts +19 -0
  61. package/src/components/systems/stair/stair-edit-system.tsx +5 -2
  62. package/src/components/tools/fence/fence-drafting.test.ts +100 -0
  63. package/src/components/tools/fence/fence-drafting.ts +6 -25
  64. package/src/components/tools/item/draft-creation.test.ts +12 -0
  65. package/src/components/tools/item/draft-creation.ts +10 -0
  66. package/src/components/tools/item/face-host-commit.test.ts +65 -0
  67. package/src/components/tools/item/face-host-commit.ts +56 -0
  68. package/src/components/tools/item/face-host-preview.test.ts +77 -0
  69. package/src/components/tools/item/face-host-preview.ts +82 -0
  70. package/src/components/tools/item/item-surface-placement.test.ts +175 -0
  71. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +210 -0
  72. package/src/components/tools/item/move-tool.tsx +4 -3
  73. package/src/components/tools/item/placement-math.test.ts +11 -1
  74. package/src/components/tools/item/placement-math.ts +0 -20
  75. package/src/components/tools/item/placement-strategies.test.ts +331 -4
  76. package/src/components/tools/item/placement-strategies.ts +304 -171
  77. package/src/components/tools/item/placement-surface.test.ts +61 -0
  78. package/src/components/tools/item/placement-surface.ts +24 -0
  79. package/src/components/tools/item/placement-types.ts +10 -0
  80. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +257 -0
  81. package/src/components/tools/item/test-face-host.ts +121 -0
  82. package/src/components/tools/item/use-draft-node.test.tsx +336 -0
  83. package/src/components/tools/item/use-draft-node.ts +116 -44
  84. package/src/components/tools/item/use-placement-coordinator.tsx +672 -256
  85. package/src/components/tools/registered-tool-install-lifecycle.test.tsx +256 -0
  86. package/src/components/tools/registry/item-surface-child-frame.test.ts +90 -0
  87. package/src/components/tools/registry/item-surface-move.test.ts +501 -0
  88. package/src/components/tools/registry/item-surface-move.ts +441 -0
  89. package/src/components/tools/registry/item-surface-resolver.test.ts +257 -0
  90. package/src/components/tools/registry/move-registry-node-tool.test.ts +10 -0
  91. package/src/components/tools/registry/move-registry-node-tool.tsx +505 -164
  92. package/src/components/tools/registry-tool-context.tsx +30 -0
  93. package/src/components/tools/select/box-select-tool.tsx +3 -1
  94. package/src/components/tools/select/marquee-footprint.test.ts +53 -0
  95. package/src/components/tools/select/marquee-footprint.ts +29 -0
  96. package/src/components/tools/shared/placement-box.tsx +57 -7
  97. package/src/components/tools/shared/placement-dimension-guides.tsx +121 -0
  98. package/src/components/tools/shared/pointer-support-cap.test.ts +319 -17
  99. package/src/components/tools/shared/pointer-support-cap.ts +56 -17
  100. package/src/components/tools/shared/shelf-stickiness.ts +54 -0
  101. package/src/components/tools/shared/surface-hit.test.ts +56 -0
  102. package/src/components/tools/shared/surface-hit.ts +23 -0
  103. package/src/components/tools/shared/surface-rejection.test.tsx +106 -0
  104. package/src/components/tools/shared/surface-rejection.tsx +29 -0
  105. package/src/components/tools/site/terrain-sculpt-grid.tsx +9 -5
  106. package/src/components/tools/site/terrain-sculpt-tool.tsx +1 -1
  107. package/src/components/tools/stair/stair-tool.tsx +127 -74
  108. package/src/components/tools/tool-manager.tsx +25 -8
  109. package/src/components/tools/wall/wall-drafting.test.ts +320 -2
  110. package/src/components/tools/wall/wall-drafting.ts +23 -114
  111. package/src/components/tools/zone/zone-tool.tsx +13 -4
  112. package/src/components/ui/action-menu/control-modes.tsx +9 -6
  113. package/src/components/ui/action-menu/index.tsx +11 -1
  114. package/src/components/ui/command-palette/editor-commands.tsx +63 -13
  115. package/src/components/ui/command-palette/index.tsx +7 -2
  116. package/src/components/ui/controls/material-paint-panel.tsx +6 -4
  117. package/src/components/ui/controls/material-picker.tsx +4 -4
  118. package/src/components/ui/controls/metric-control.tsx +29 -13
  119. package/src/components/ui/controls/scene-material-list.tsx +6 -5
  120. package/src/components/ui/controls/segmented-control.tsx +8 -1
  121. package/src/components/ui/controls/slider-control.tsx +44 -21
  122. package/src/components/ui/controls/toggle-control.tsx +18 -5
  123. package/src/components/ui/controls/tool-options-panel.tsx +126 -0
  124. package/src/components/ui/floating-level-selector.tsx +103 -10
  125. package/src/components/ui/helpers/contextual-helper-panel.tsx +9 -0
  126. package/src/components/ui/helpers/helper-manager.tsx +47 -11
  127. package/src/components/ui/helpers/item-helper.tsx +1 -1
  128. package/src/components/ui/helpers/registered-tool-helper.tsx +21 -0
  129. package/src/components/ui/panels/homogeneous-selection.test.ts +80 -0
  130. package/src/components/ui/panels/homogeneous-selection.ts +47 -0
  131. package/src/components/ui/panels/mobile-selection-bar.tsx +11 -5
  132. package/src/components/ui/panels/multi-field-value.test.ts +209 -0
  133. package/src/components/ui/panels/multi-field-value.ts +163 -0
  134. package/src/components/ui/panels/multi-height-mode.tsx +185 -0
  135. package/src/components/ui/panels/multi-parametric-inspector.tsx +266 -0
  136. package/src/components/ui/panels/multi-selection-panel.tsx +48 -37
  137. package/src/components/ui/panels/node-display.ts +4 -0
  138. package/src/components/ui/panels/panel-manager.tsx +77 -3
  139. package/src/components/ui/panels/parametric-field-control.tsx +173 -0
  140. package/src/components/ui/panels/parametric-field-utils.ts +18 -0
  141. package/src/components/ui/panels/parametric-inspector.tsx +35 -164
  142. package/src/components/ui/panels/reference-panel.tsx +34 -4
  143. package/src/components/ui/primitives/checkbox.tsx +34 -0
  144. package/src/components/ui/primitives/shortcut-token.tsx +23 -1
  145. package/src/components/ui/scene-loader.tsx +35 -0
  146. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +502 -31
  147. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +20 -23
  148. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +167 -0
  149. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +128 -0
  150. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +8 -1
  151. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +19 -5
  152. package/src/components/ui/sidebar/panels/site-panel/index.tsx +222 -50
  153. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +2 -7
  154. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +5 -2
  155. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +28 -11
  156. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +3 -8
  157. package/src/components/ui/sidebar/panels/site-panel/tree-node.test.ts +8 -0
  158. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +20 -7
  159. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +224 -0
  160. package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +22 -0
  161. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +24 -7
  162. package/src/components/ui/sidebar/tab-bar.tsx +2 -0
  163. package/src/components/ui/sidebar/use-plugin-panels.tsx +17 -2
  164. package/src/components/ui/snap-target-badge.tsx +2 -0
  165. package/src/components/viewer/viewer-scene-header.tsx +5 -1
  166. package/src/components/viewer/viewer-stage.tsx +4 -1
  167. package/src/components/viewer/viewer-units-panel.tsx +116 -0
  168. package/src/components/viewer-overlay.tsx +1 -22
  169. package/src/components/viewer-zone-system.tsx +6 -2
  170. package/src/components/walkthrough-hud.tsx +19 -10
  171. package/src/hooks/use-auto-save.test.ts +73 -1
  172. package/src/hooks/use-auto-save.ts +76 -5
  173. package/src/hooks/use-ceiling-events.test.ts +144 -0
  174. package/src/hooks/use-ceiling-events.ts +5 -1
  175. package/src/hooks/use-drag-action.ts +17 -3
  176. package/src/hooks/use-grid-events.test.ts +240 -0
  177. package/src/hooks/use-grid-events.ts +190 -11
  178. package/src/hooks/use-keyboard.test.ts +244 -0
  179. package/src/hooks/use-keyboard.ts +132 -63
  180. package/src/hooks/use-registered-tool-enabled.ts +20 -0
  181. package/src/hooks/use-save-shortcut.ts +33 -0
  182. package/src/index.tsx +87 -4
  183. package/src/lib/active-placement-surface.test.ts +16 -0
  184. package/src/lib/active-placement-surface.ts +14 -0
  185. package/src/lib/ceiling-surface-raycast.test.ts +64 -0
  186. package/src/lib/ceiling-surface-raycast.ts +12 -0
  187. package/src/lib/contextual-help-extension.ts +17 -0
  188. package/src/lib/continuation.test.ts +11 -0
  189. package/src/lib/continuation.ts +8 -1
  190. package/src/lib/direct-manipulation.test.ts +132 -0
  191. package/src/lib/direct-manipulation.ts +38 -1
  192. package/src/lib/editor-api.ts +6 -3
  193. package/src/lib/elevation-guides.test.ts +28 -1
  194. package/src/lib/elevation-guides.ts +22 -7
  195. package/src/lib/export-texture-utils.test.ts +167 -0
  196. package/src/lib/export-texture-utils.ts +121 -0
  197. package/src/lib/export-user-data.test.ts +48 -0
  198. package/src/lib/export-user-data.ts +69 -0
  199. package/src/lib/floating-menu-scale.test.ts +29 -0
  200. package/src/lib/floating-menu-scale.ts +14 -0
  201. package/src/lib/floorplan/apply-alignment.test.ts +47 -2
  202. package/src/lib/floorplan/apply-alignment.ts +10 -1
  203. package/src/lib/floorplan/floorplan-export.test.ts +536 -2
  204. package/src/lib/floorplan/floorplan-export.tsx +120 -19
  205. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +26 -0
  206. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +32 -24
  207. package/src/lib/floorplan/floorplan-readonly.ts +5 -0
  208. package/src/lib/floorplan-grid-event-point.test.ts +29 -0
  209. package/src/lib/floorplan-grid-event-point.ts +13 -0
  210. package/src/lib/fresh-planar-placement.ts +13 -5
  211. package/src/lib/glb-export-serialize.test.ts +280 -0
  212. package/src/lib/glb-export.test.ts +1375 -21
  213. package/src/lib/glb-export.ts +781 -106
  214. package/src/lib/grid-event-presentation.ts +16 -0
  215. package/src/lib/history.test.ts +528 -147
  216. package/src/lib/history.ts +81 -7
  217. package/src/lib/host-tree-children.test.ts +27 -0
  218. package/src/lib/host-tree-children.ts +79 -0
  219. package/src/lib/interaction/hot-set.test.ts +10 -1
  220. package/src/lib/interaction/hot-set.ts +3 -1
  221. package/src/lib/interaction/overlay-policy.test.ts +33 -1
  222. package/src/lib/interaction/overlay-policy.ts +16 -0
  223. package/src/lib/interaction/registered-drafting.ts +36 -0
  224. package/src/lib/interaction/scope.ts +31 -0
  225. package/src/lib/level-duplication.test.ts +73 -0
  226. package/src/lib/level-duplication.ts +32 -2
  227. package/src/lib/level-print-export.test.ts +484 -0
  228. package/src/lib/level-print-export.ts +526 -0
  229. package/src/lib/linear-display.test.ts +59 -0
  230. package/src/lib/linear-display.ts +39 -0
  231. package/src/lib/local-guide-image.test.ts +24 -0
  232. package/src/lib/local-guide-image.ts +34 -1
  233. package/src/lib/local-project-presentation-persistence.test.ts +302 -0
  234. package/src/lib/local-project-presentation-persistence.ts +335 -0
  235. package/src/lib/material-paint.ts +2 -0
  236. package/src/lib/model-export.ts +29 -0
  237. package/src/lib/node-action-movement.ts +21 -0
  238. package/src/lib/paint-preview-owner.ts +71 -0
  239. package/src/lib/planar-cursor-placement.test.ts +178 -1
  240. package/src/lib/planar-cursor-placement.ts +61 -5
  241. package/src/lib/plugin-panels.test.ts +92 -1
  242. package/src/lib/plugin-panels.ts +48 -0
  243. package/src/lib/portable-export.test.ts +455 -0
  244. package/src/lib/portable-export.ts +1042 -0
  245. package/src/lib/print-3mf.test.ts +86 -0
  246. package/src/lib/print-3mf.ts +161 -0
  247. package/src/lib/print-content-scope.test.ts +107 -0
  248. package/src/lib/print-content-scope.ts +90 -0
  249. package/src/lib/print-export.test.ts +294 -0
  250. package/src/lib/print-export.ts +735 -0
  251. package/src/lib/print-feature-thickness.test.ts +147 -0
  252. package/src/lib/print-feature-thickness.ts +180 -0
  253. package/src/lib/print-golden-house.test-fixture.ts +352 -0
  254. package/src/lib/print-golden-house.test.ts +249 -0
  255. package/src/lib/print-roof-solids.test.ts +133 -0
  256. package/src/lib/print-roof-solids.ts +630 -0
  257. package/src/lib/print-shell-compiler-baseline.test.ts +419 -0
  258. package/src/lib/print-shell-compiler-baseline.ts +225 -0
  259. package/src/lib/print-shell-compiler-manifold-core.ts +379 -0
  260. package/src/lib/print-shell-compiler-manifold-worker.ts +200 -0
  261. package/src/lib/print-shell-compiler-manifold.worker.ts +20 -0
  262. package/src/lib/print-shell-compiler-mesh-data.ts +32 -0
  263. package/src/lib/print-shell-compiler-protocol.ts +38 -0
  264. package/src/lib/print-shell-compiler.ts +273 -0
  265. package/src/lib/print-wall-solids.test.ts +194 -0
  266. package/src/lib/print-wall-solids.ts +328 -0
  267. package/src/lib/rigid-plan-svg-transform.test.ts +35 -0
  268. package/src/lib/rigid-plan-svg-transform.ts +24 -0
  269. package/src/lib/scene-clipboard.ts +1 -1
  270. package/src/lib/scene.ts +22 -8
  271. package/src/lib/selection-routing.test.ts +105 -1
  272. package/src/lib/selection-routing.ts +19 -0
  273. package/src/lib/send-to-app.test.ts +108 -0
  274. package/src/lib/send-to-app.ts +121 -0
  275. package/src/lib/sfx/movement-tick.ts +37 -0
  276. package/src/lib/sfx-bus.ts +4 -0
  277. package/src/lib/sfx-player.ts +8 -0
  278. package/src/lib/snapping-mode.test.ts +32 -4
  279. package/src/lib/snapping-mode.ts +11 -7
  280. package/src/lib/surface-attachment.ts +77 -0
  281. package/src/lib/units.ts +254 -0
  282. package/src/lib/usdz-export.ts +65 -0
  283. package/src/lib/use-linear-display.ts +8 -32
  284. package/src/lib/walkthrough-pointer-lock.ts +47 -0
  285. package/src/store/live-draft-preview-stores.test.ts +3 -0
  286. package/src/store/terrain-sculpt-mode.test.ts +3 -6
  287. package/src/store/tool-mode.test.ts +87 -0
  288. package/src/store/use-camera-hint-focus.ts +48 -0
  289. package/src/store/use-editor.tsx +299 -188
  290. package/src/store/use-floorplan-draft-preview.ts +9 -0
  291. package/src/store/use-handle-group.ts +4 -0
  292. package/src/store/use-interaction-scope.test.ts +23 -1
  293. package/src/store/use-interaction-scope.ts +63 -2
  294. package/src/store/use-placement-preview.ts +56 -3
  295. package/src/store/use-send-to-app.ts +21 -0
  296. package/src/components/tools/roof/roof-tool.tsx +0 -656
  297. package/src/components/ui/helpers/roof-helper.tsx +0 -14
@@ -0,0 +1,379 @@
1
+ import type { Manifold as ManifoldSolid, ManifoldToplevel } from 'manifold-3d'
2
+ import type { PrintShellCompileDiagnostic } from './print-shell-compiler-baseline'
3
+ import type {
4
+ ManifoldCompileOutput,
5
+ ManifoldMeshData,
6
+ ManifoldRuntimeOptions,
7
+ } from './print-shell-compiler-protocol'
8
+
9
+ let modulePromise: Promise<ManifoldToplevel> | null = null
10
+ const MANIFOLD_OUTPUT_WELD_EPSILON_METERS = 2e-5
11
+ const COLLINEAR_SEAM_CROSS_LENGTH_SQ = 1e-20
12
+
13
+ type Triangle = [number, number, number]
14
+
15
+ type ManifoldFactory = (config?: {
16
+ locateFile?: (path: string) => string
17
+ }) => Promise<ManifoldToplevel>
18
+
19
+ // manifold-3d's emscripten glue awaits import('node:module') behind a Node
20
+ // check. The branch never executes in a browser, but webpack refuses to
21
+ // *build* a graph that can reach it, so a static specifier here poisons every
22
+ // external bundler that compiles this package's source (#715). The factory is
23
+ // therefore loaded through an import() no bundler can trace: the bare
24
+ // specifier resolves wherever node-style resolution exists at runtime (bun
25
+ // tests, dev servers, bundlers that inline it anyway), and the version-pinned
26
+ // CDN copy covers bundled browser builds that left the specifier unresolved —
27
+ // emscripten then locates manifold.wasm relative to the glue's own URL. Hosts
28
+ // that can't reach the CDN (offline, CSP) pass their own URLs through
29
+ // configureManifoldRuntime.
30
+ const MANIFOLD_VERSION = '3.5.1'
31
+ const FALLBACK_MODULE_URL = `https://cdn.jsdelivr.net/npm/manifold-3d@${MANIFOLD_VERSION}/manifold.js`
32
+
33
+ function importUntraced(specifier: string): Promise<{ default: ManifoldFactory }> {
34
+ return import(/* webpackIgnore: true */ /* @vite-ignore */ specifier)
35
+ }
36
+
37
+ async function loadManifoldFactory(moduleUrl?: string): Promise<ManifoldFactory> {
38
+ const specifiers = moduleUrl ? [moduleUrl] : ['manifold-3d', FALLBACK_MODULE_URL]
39
+ let lastError: unknown
40
+ for (const specifier of specifiers) {
41
+ try {
42
+ return (await importUntraced(specifier)).default
43
+ } catch (error) {
44
+ lastError = error
45
+ }
46
+ }
47
+ throw lastError instanceof Error ? lastError : new Error('Failed to load manifold-3d.')
48
+ }
49
+
50
+ async function getManifoldModule(runtime?: ManifoldRuntimeOptions): Promise<ManifoldToplevel> {
51
+ modulePromise ??= loadManifoldFactory(runtime?.moduleUrl)
52
+ .then((factory) => {
53
+ const wasmUrl = runtime?.wasmUrl
54
+ return factory(wasmUrl ? { locateFile: () => wasmUrl } : undefined)
55
+ })
56
+ .then((module) => {
57
+ module.setup()
58
+ return module
59
+ })
60
+ try {
61
+ return await modulePromise
62
+ } catch (error) {
63
+ // A transient load failure (offline, blocked CDN) must not poison every
64
+ // later compile attempt with the cached rejection.
65
+ modulePromise = null
66
+ throw error
67
+ }
68
+ }
69
+
70
+ function manifoldMesh(
71
+ module: ManifoldToplevel,
72
+ mesh: ManifoldMeshData,
73
+ ): InstanceType<ManifoldToplevel['Mesh']> {
74
+ return new module.Mesh({
75
+ numProp: 3,
76
+ vertProperties: mesh.positions,
77
+ triVerts: mesh.indices,
78
+ })
79
+ }
80
+
81
+ function distanceSquared(positions: Float32Array, left: number, right: number): number {
82
+ const dx = positions[left * 3]! - positions[right * 3]!
83
+ const dy = positions[left * 3 + 1]! - positions[right * 3 + 1]!
84
+ const dz = positions[left * 3 + 2]! - positions[right * 3 + 2]!
85
+ return dx * dx + dy * dy + dz * dz
86
+ }
87
+
88
+ function triangleCrossLengthSquared(positions: Float32Array, [a, b, c]: Triangle): number {
89
+ const abX = positions[b * 3]! - positions[a * 3]!
90
+ const abY = positions[b * 3 + 1]! - positions[a * 3 + 1]!
91
+ const abZ = positions[b * 3 + 2]! - positions[a * 3 + 2]!
92
+ const acX = positions[c * 3]! - positions[a * 3]!
93
+ const acY = positions[c * 3 + 1]! - positions[a * 3 + 1]!
94
+ const acZ = positions[c * 3 + 2]! - positions[a * 3 + 2]!
95
+ const crossX = abY * acZ - abZ * acY
96
+ const crossY = abZ * acX - abX * acZ
97
+ const crossZ = abX * acY - abY * acX
98
+ return crossX * crossX + crossY * crossY + crossZ * crossZ
99
+ }
100
+
101
+ function collinearSeam(
102
+ positions: Float32Array,
103
+ triangle: Triangle,
104
+ ): { start: number; middle: number; end: number } | null {
105
+ if (triangleCrossLengthSquared(positions, triangle) > COLLINEAR_SEAM_CROSS_LENGTH_SQ) {
106
+ return null
107
+ }
108
+
109
+ const [a, b, c] = triangle
110
+ const edges = [
111
+ { start: a, middle: c, end: b, lengthSquared: distanceSquared(positions, a, b) },
112
+ { start: b, middle: a, end: c, lengthSquared: distanceSquared(positions, b, c) },
113
+ { start: c, middle: b, end: a, lengthSquared: distanceSquared(positions, c, a) },
114
+ ].sort((left, right) => right.lengthSquared - left.lengthSquared)
115
+ const longest = edges[0]!
116
+ if (longest.lengthSquared === 0) return null
117
+
118
+ const startOffset = longest.start * 3
119
+ const middleOffset = longest.middle * 3
120
+ const endOffset = longest.end * 3
121
+ const edgeX = positions[endOffset]! - positions[startOffset]!
122
+ const edgeY = positions[endOffset + 1]! - positions[startOffset + 1]!
123
+ const edgeZ = positions[endOffset + 2]! - positions[startOffset + 2]!
124
+ const middleX = positions[middleOffset]! - positions[startOffset]!
125
+ const middleY = positions[middleOffset + 1]! - positions[startOffset + 1]!
126
+ const middleZ = positions[middleOffset + 2]! - positions[startOffset + 2]!
127
+ const projection = middleX * edgeX + middleY * edgeY + middleZ * edgeZ
128
+ if (projection <= 0 || projection >= longest.lengthSquared) return null
129
+
130
+ return longest
131
+ }
132
+
133
+ function stitchCollinearSeams(positions: Float32Array, input: Triangle[]): Triangle[] {
134
+ const triangles: Array<Triangle | null> = [...input]
135
+
136
+ // Manifold can encode a T-junction as one zero-area triangle: one surface owns the long
137
+ // edge while the other owns its two segments. Split the neighboring face at the middle
138
+ // vertex before removing the collapsed face so indexed edge incidence remains closed.
139
+ for (let triangleIndex = 0; triangleIndex < triangles.length; triangleIndex += 1) {
140
+ const triangle = triangles[triangleIndex]
141
+ if (!triangle) continue
142
+ const seam = collinearSeam(positions, triangle)
143
+ if (!seam) continue
144
+
145
+ const matches: Array<{ index: number; edgeOffset: number }> = []
146
+ for (let candidateIndex = 0; candidateIndex < triangles.length; candidateIndex += 1) {
147
+ if (candidateIndex === triangleIndex) continue
148
+ const candidate = triangles[candidateIndex]
149
+ if (
150
+ !candidate ||
151
+ triangleCrossLengthSquared(positions, candidate) <= COLLINEAR_SEAM_CROSS_LENGTH_SQ
152
+ ) {
153
+ continue
154
+ }
155
+ for (let edgeOffset = 0; edgeOffset < 3; edgeOffset += 1) {
156
+ const from = candidate[edgeOffset]!
157
+ const to = candidate[(edgeOffset + 1) % 3]!
158
+ if ((from === seam.start && to === seam.end) || (from === seam.end && to === seam.start)) {
159
+ matches.push({ index: candidateIndex, edgeOffset })
160
+ }
161
+ }
162
+ }
163
+ if (matches.length !== 1) continue
164
+
165
+ const match = matches[0]!
166
+ const neighbor = triangles[match.index]!
167
+ const from = neighbor[match.edgeOffset]!
168
+ const to = neighbor[(match.edgeOffset + 1) % 3]!
169
+ const opposite = neighbor[(match.edgeOffset + 2) % 3]!
170
+ triangles[match.index] = [from, seam.middle, opposite]
171
+ triangles.push([seam.middle, to, opposite])
172
+ triangles[triangleIndex] = null
173
+ }
174
+
175
+ return triangles.filter((triangle): triangle is Triangle => triangle !== null)
176
+ }
177
+
178
+ function manifoldOutput(solid: ManifoldSolid): { positions: Float32Array; indices: Uint32Array } {
179
+ const mesh = solid.getMesh()
180
+ const positions = new Float32Array(mesh.numVert * 3)
181
+ for (let index = 0; index < mesh.numVert; index += 1) {
182
+ const sourceOffset = index * mesh.numProp
183
+ positions[index * 3] = mesh.vertProperties[sourceOffset]!
184
+ positions[index * 3 + 1] = mesh.vertProperties[sourceOffset + 1]!
185
+ positions[index * 3 + 2] = mesh.vertProperties[sourceOffset + 2]!
186
+ }
187
+
188
+ const parents = new Uint32Array(mesh.numVert)
189
+ for (let index = 0; index < parents.length; index += 1) parents[index] = index
190
+ const find = (index: number): number => {
191
+ let root = index
192
+ while (parents[root] !== root) root = parents[root]!
193
+ while (parents[index] !== index) {
194
+ const next = parents[index]!
195
+ parents[index] = root
196
+ index = next
197
+ }
198
+ return root
199
+ }
200
+ for (let index = 0; index < mesh.mergeFromVert.length; index += 1) {
201
+ parents[find(mesh.mergeFromVert[index]!)] = find(mesh.mergeToVert[index]!)
202
+ }
203
+
204
+ // Float32 boolean output can leave seam vertices just over 10 microns apart. Dropping the
205
+ // resulting sliver triangle by area opens the shell; weld the vertices first so adjacent
206
+ // faces inherit one indexed edge, then remove only triangles collapsed by that topology.
207
+ const cellSize = MANIFOLD_OUTPUT_WELD_EPSILON_METERS
208
+ const cellRoots = new Map<string, number[]>()
209
+ const cellCoordinate = (value: number) => Math.floor(value / cellSize)
210
+ const cellKey = (x: number, y: number, z: number) => `${x},${y},${z}`
211
+ const weldDistanceSquared = cellSize * cellSize
212
+ for (let index = 0; index < mesh.numVert; index += 1) {
213
+ const root = find(index)
214
+ if (root !== index) continue
215
+ const cellX = cellCoordinate(positions[root * 3]!)
216
+ const cellY = cellCoordinate(positions[root * 3 + 1]!)
217
+ const cellZ = cellCoordinate(positions[root * 3 + 2]!)
218
+ let weldedTo: number | null = null
219
+ for (let xOffset = -1; xOffset <= 1 && weldedTo === null; xOffset += 1) {
220
+ for (let yOffset = -1; yOffset <= 1 && weldedTo === null; yOffset += 1) {
221
+ for (let zOffset = -1; zOffset <= 1 && weldedTo === null; zOffset += 1) {
222
+ const candidates = cellRoots.get(
223
+ cellKey(cellX + xOffset, cellY + yOffset, cellZ + zOffset),
224
+ )
225
+ for (const candidate of candidates ?? []) {
226
+ if (distanceSquared(positions, root, candidate) <= weldDistanceSquared) {
227
+ weldedTo = candidate
228
+ break
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+ if (weldedTo === null) {
235
+ const key = cellKey(cellX, cellY, cellZ)
236
+ const roots = cellRoots.get(key) ?? []
237
+ roots.push(root)
238
+ cellRoots.set(key, roots)
239
+ } else {
240
+ parents[root] = find(weldedTo)
241
+ }
242
+ }
243
+
244
+ const triangles: Triangle[] = []
245
+ for (let index = 0; index + 2 < mesh.triVerts.length; index += 3) {
246
+ const a = find(mesh.triVerts[index]!)
247
+ const b = find(mesh.triVerts[index + 1]!)
248
+ const c = find(mesh.triVerts[index + 2]!)
249
+ if (a === b || b === c || c === a) continue
250
+ triangles.push([a, b, c])
251
+ }
252
+ const indices = stitchCollinearSeams(positions, triangles).flat()
253
+
254
+ return { positions, indices: new Uint32Array(indices) }
255
+ }
256
+
257
+ function elapsed(startedAt: number): number {
258
+ return Math.max(0, performance.now() - startedAt)
259
+ }
260
+
261
+ export async function compileManifoldMeshData(
262
+ meshes: ManifoldMeshData[],
263
+ runtime?: ManifoldRuntimeOptions,
264
+ ): Promise<ManifoldCompileOutput> {
265
+ const startedAt = performance.now()
266
+ const sourceNodeIds = Array.from(new Set(meshes.map((mesh) => mesh.nodeId))).sort()
267
+ const diagnostics: PrintShellCompileDiagnostic[] = []
268
+ const solids: ManifoldSolid[] = []
269
+ let union: ManifoldSolid | null = null
270
+ let result: ManifoldSolid | null = null
271
+
272
+ if (meshes.length === 0) {
273
+ return {
274
+ status: 'blocked',
275
+ positions: null,
276
+ indices: null,
277
+ diagnostics: [
278
+ {
279
+ severity: 'error',
280
+ code: 'no_shell_meshes',
281
+ message: 'No structural meshes are available for Manifold compilation.',
282
+ nodeIds: [],
283
+ },
284
+ ],
285
+ durationMs: elapsed(startedAt),
286
+ }
287
+ }
288
+
289
+ try {
290
+ const module = await getManifoldModule(runtime)
291
+ for (const mesh of meshes) {
292
+ try {
293
+ solids.push(new module.Manifold(manifoldMesh(module, mesh)))
294
+ } catch (error) {
295
+ diagnostics.push({
296
+ severity: 'error',
297
+ code: 'manifold_input_failed',
298
+ message: `Node ${mesh.nodeId}: ${
299
+ error instanceof Error ? error.message : 'Manifold rejected the shell input.'
300
+ }`,
301
+ nodeIds: [mesh.nodeId],
302
+ })
303
+ }
304
+ }
305
+ if (diagnostics.length > 0) {
306
+ return {
307
+ status: 'blocked',
308
+ positions: null,
309
+ indices: null,
310
+ diagnostics,
311
+ durationMs: elapsed(startedAt),
312
+ }
313
+ }
314
+
315
+ union = module.Manifold.union(solids)
316
+ result = union.asOriginal()
317
+ const status = result.status()
318
+ if (status !== 'NoError') {
319
+ return {
320
+ status: 'blocked',
321
+ positions: null,
322
+ indices: null,
323
+ diagnostics: [
324
+ {
325
+ severity: 'error',
326
+ code: 'manifold_union_failed',
327
+ message: `Manifold union failed with ${status}.`,
328
+ nodeIds: sourceNodeIds,
329
+ },
330
+ ],
331
+ durationMs: elapsed(startedAt),
332
+ }
333
+ }
334
+
335
+ const output = manifoldOutput(result)
336
+ if (output.indices.length === 0) {
337
+ return {
338
+ status: 'blocked',
339
+ positions: null,
340
+ indices: null,
341
+ diagnostics: [
342
+ {
343
+ severity: 'error',
344
+ code: 'manifold_union_failed',
345
+ message: 'Manifold produced no printable triangles.',
346
+ nodeIds: sourceNodeIds,
347
+ },
348
+ ],
349
+ durationMs: elapsed(startedAt),
350
+ }
351
+ }
352
+ return {
353
+ status: 'compiled',
354
+ positions: output.positions,
355
+ indices: output.indices,
356
+ diagnostics: [],
357
+ durationMs: elapsed(startedAt),
358
+ }
359
+ } catch (error) {
360
+ return {
361
+ status: 'blocked',
362
+ positions: null,
363
+ indices: null,
364
+ diagnostics: [
365
+ {
366
+ severity: 'error',
367
+ code: 'manifold_worker_failed',
368
+ message: error instanceof Error ? error.message : 'Manifold compilation failed.',
369
+ nodeIds: sourceNodeIds,
370
+ },
371
+ ],
372
+ durationMs: elapsed(startedAt),
373
+ }
374
+ } finally {
375
+ for (const solid of solids) solid.delete()
376
+ union?.delete()
377
+ result?.delete()
378
+ }
379
+ }
@@ -0,0 +1,200 @@
1
+ import type { AnyNode } from '@pascal-app/core'
2
+ import * as THREE from 'three'
3
+ import {
4
+ prepareSemanticPrintShellSource,
5
+ type SemanticPrintCompileOptions,
6
+ } from './print-shell-compiler'
7
+ import {
8
+ collectPrintShellInput,
9
+ type PrintShellCompileDiagnostic,
10
+ type PrintShellCompileResult,
11
+ } from './print-shell-compiler-baseline'
12
+ import {
13
+ geometryFromManifoldMeshData,
14
+ geometryToManifoldMeshData,
15
+ } from './print-shell-compiler-mesh-data'
16
+ import type {
17
+ ManifoldCompileOutput,
18
+ ManifoldMeshData,
19
+ ManifoldRuntimeOptions,
20
+ ManifoldWorkerRequest,
21
+ ManifoldWorkerResponse,
22
+ } from './print-shell-compiler-protocol'
23
+
24
+ const WORKER_TIMEOUT_MS = 60_000
25
+
26
+ let manifoldRuntime: ManifoldRuntimeOptions | undefined
27
+
28
+ /**
29
+ * Overrides where the print-export worker loads the manifold-3d module and
30
+ * wasm from. See the loader in print-shell-compiler-manifold-core.ts for the
31
+ * default resolution order; hosts with restrictive networks should call this
32
+ * with self-hosted asset URLs before the first print export.
33
+ */
34
+ export function configureManifoldRuntime(options: ManifoldRuntimeOptions | undefined): void {
35
+ manifoldRuntime = options
36
+ }
37
+
38
+ export type ManifoldCompileRunner = (meshes: ManifoldMeshData[]) => Promise<ManifoldCompileOutput>
39
+
40
+ export type SemanticManifoldCompileOptions = SemanticPrintCompileOptions & {
41
+ runner?: ManifoldCompileRunner
42
+ }
43
+
44
+ type PendingRequest = {
45
+ resolve: (output: ManifoldCompileOutput) => void
46
+ reject: (error: Error) => void
47
+ timeout: ReturnType<typeof setTimeout>
48
+ }
49
+
50
+ let worker: Worker | null = null
51
+ let nextRequestId = 1
52
+ const pendingRequests = new Map<number, PendingRequest>()
53
+
54
+ function resetWorker(error: Error) {
55
+ worker?.terminate()
56
+ worker = null
57
+ for (const pending of pendingRequests.values()) {
58
+ clearTimeout(pending.timeout)
59
+ pending.reject(error)
60
+ }
61
+ pendingRequests.clear()
62
+ }
63
+
64
+ function getWorker(): Worker {
65
+ if (worker) return worker
66
+ if (typeof Worker === 'undefined') {
67
+ throw new Error('Web Workers are unavailable in this environment.')
68
+ }
69
+ worker = new Worker(new URL('./print-shell-compiler-manifold.worker.ts', import.meta.url), {
70
+ type: 'module',
71
+ })
72
+ worker.addEventListener('message', (event: MessageEvent<ManifoldWorkerResponse>) => {
73
+ const pending = pendingRequests.get(event.data.id)
74
+ if (!pending) return
75
+ pendingRequests.delete(event.data.id)
76
+ clearTimeout(pending.timeout)
77
+ pending.resolve(event.data)
78
+ })
79
+ worker.addEventListener('error', (event) => {
80
+ resetWorker(new Error(event.message || 'The Manifold worker failed.'))
81
+ })
82
+ worker.addEventListener('messageerror', () => {
83
+ resetWorker(new Error('The Manifold worker returned an unreadable response.'))
84
+ })
85
+ return worker
86
+ }
87
+
88
+ export const runManifoldWorker: ManifoldCompileRunner = (meshes) => {
89
+ const activeWorker = getWorker()
90
+ const id = nextRequestId
91
+ nextRequestId += 1
92
+ const request: ManifoldWorkerRequest = { id, meshes, runtime: manifoldRuntime }
93
+ const transfer = meshes.flatMap((mesh) => [
94
+ mesh.positions.buffer as ArrayBuffer,
95
+ mesh.indices.buffer as ArrayBuffer,
96
+ ])
97
+
98
+ return new Promise((resolve, reject) => {
99
+ const timeout = setTimeout(() => {
100
+ resetWorker(new Error(`The Manifold worker exceeded ${WORKER_TIMEOUT_MS / 1000} seconds.`))
101
+ }, WORKER_TIMEOUT_MS)
102
+ pendingRequests.set(id, { resolve, reject, timeout })
103
+ try {
104
+ activeWorker.postMessage(request, transfer)
105
+ } catch (error) {
106
+ resetWorker(
107
+ error instanceof Error ? error : new Error('Failed to start the Manifold worker.'),
108
+ )
109
+ }
110
+ })
111
+ }
112
+
113
+ function blockedResult(
114
+ inputMeshCount: number,
115
+ sourceNodeIds: Iterable<string>,
116
+ diagnostics: PrintShellCompileDiagnostic[],
117
+ ): PrintShellCompileResult {
118
+ return {
119
+ backend: 'manifold-3d',
120
+ status: 'blocked',
121
+ scene: null,
122
+ inputMeshCount,
123
+ sourceNodeIds: Array.from(sourceNodeIds).sort(),
124
+ diagnostics,
125
+ }
126
+ }
127
+
128
+ export async function compileSemanticPrintShellWithManifold(
129
+ source: THREE.Object3D,
130
+ nodes: Record<string, AnyNode>,
131
+ options: SemanticManifoldCompileOptions = {},
132
+ ): Promise<PrintShellCompileResult> {
133
+ const { runner = runManifoldWorker, ...semanticOptions } = options
134
+ const prepared = prepareSemanticPrintShellSource(source, nodes, {
135
+ ...semanticOptions,
136
+ wallSolids: semanticOptions.wallSolids ?? true,
137
+ })
138
+ if (prepared.status === 'blocked') {
139
+ return blockedResult(prepared.inputMeshCount, prepared.sourceNodeIds, prepared.diagnostics)
140
+ }
141
+
142
+ const input = collectPrintShellInput(prepared.scene)
143
+ prepared.dispose()
144
+ if (input.diagnostics.some((diagnostic) => diagnostic.severity === 'error')) {
145
+ for (const geometry of input.geometries) geometry.dispose()
146
+ return blockedResult(input.inputMeshCount, input.sourceNodeIds, input.diagnostics)
147
+ }
148
+
149
+ const meshes = input.geometries.map((geometry, index) =>
150
+ geometryToManifoldMeshData(geometry, input.geometryNodeIds[index]!),
151
+ )
152
+ for (const geometry of input.geometries) geometry.dispose()
153
+
154
+ let output: ManifoldCompileOutput
155
+ try {
156
+ output = await runner(meshes)
157
+ } catch (error) {
158
+ return blockedResult(input.inputMeshCount, input.sourceNodeIds, [
159
+ ...input.diagnostics,
160
+ {
161
+ severity: 'error',
162
+ code: 'manifold_worker_failed',
163
+ message: error instanceof Error ? error.message : 'The Manifold worker failed.',
164
+ nodeIds: Array.from(input.sourceNodeIds).sort(),
165
+ },
166
+ ])
167
+ }
168
+
169
+ const diagnostics = [...input.diagnostics, ...output.diagnostics]
170
+ if (output.status === 'blocked') {
171
+ return blockedResult(input.inputMeshCount, input.sourceNodeIds, diagnostics)
172
+ }
173
+
174
+ const geometry = geometryFromManifoldMeshData(output.positions, output.indices)
175
+ const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial())
176
+ mesh.name = 'print-shell-manifold'
177
+ mesh.userData = {
178
+ printCompiler: 'manifold-3d',
179
+ sourceNodeIds: Array.from(input.sourceNodeIds).sort(),
180
+ }
181
+ const scene = new THREE.Group()
182
+ scene.name = 'compiled-print-shell'
183
+ scene.add(mesh)
184
+ diagnostics.push({
185
+ severity: 'info',
186
+ code: runner === runManifoldWorker ? 'manifold_worker_compiler' : 'manifold_compiler_candidate',
187
+ message: `Compiled with Manifold in ${output.durationMs.toFixed(1)} ms${
188
+ runner === runManifoldWorker ? ' off the main thread' : ' through the in-process test runner'
189
+ }.`,
190
+ nodeIds: Array.from(input.sourceNodeIds).sort(),
191
+ })
192
+ return {
193
+ backend: 'manifold-3d',
194
+ status: 'compiled',
195
+ scene,
196
+ inputMeshCount: input.inputMeshCount,
197
+ sourceNodeIds: Array.from(input.sourceNodeIds).sort(),
198
+ diagnostics,
199
+ }
200
+ }
@@ -0,0 +1,20 @@
1
+ import { compileManifoldMeshData } from './print-shell-compiler-manifold-core'
2
+ import type { ManifoldWorkerRequest, ManifoldWorkerResponse } from './print-shell-compiler-protocol'
3
+
4
+ const workerScope = self as unknown as {
5
+ addEventListener: (
6
+ type: 'message',
7
+ listener: (event: MessageEvent<ManifoldWorkerRequest>) => void,
8
+ ) => void
9
+ postMessage: (response: ManifoldWorkerResponse, transfer: Transferable[]) => void
10
+ }
11
+
12
+ workerScope.addEventListener('message', async (event) => {
13
+ const output = await compileManifoldMeshData(event.data.meshes, event.data.runtime)
14
+ const response: ManifoldWorkerResponse = { id: event.data.id, ...output }
15
+ const transfer: Transferable[] = []
16
+ if (response.status === 'compiled') {
17
+ transfer.push(response.positions.buffer as ArrayBuffer, response.indices.buffer as ArrayBuffer)
18
+ }
19
+ workerScope.postMessage(response, transfer)
20
+ })
@@ -0,0 +1,32 @@
1
+ import * as THREE from 'three'
2
+ import type { ManifoldMeshData } from './print-shell-compiler-protocol'
3
+
4
+ export function geometryToManifoldMeshData(
5
+ geometry: THREE.BufferGeometry,
6
+ nodeId: string,
7
+ ): ManifoldMeshData {
8
+ const position = geometry.getAttribute('position')
9
+ const positions = new Float32Array(position.count * 3)
10
+ for (let index = 0; index < position.count; index += 1) {
11
+ positions[index * 3] = position.getX(index)
12
+ positions[index * 3 + 1] = position.getY(index)
13
+ positions[index * 3 + 2] = position.getZ(index)
14
+ }
15
+ const geometryIndex = geometry.getIndex()
16
+ const indices = new Uint32Array(geometryIndex?.count ?? position.count)
17
+ for (let index = 0; index < indices.length; index += 1) {
18
+ indices[index] = geometryIndex?.getX(index) ?? index
19
+ }
20
+ return { nodeId, positions, indices }
21
+ }
22
+
23
+ export function geometryFromManifoldMeshData(
24
+ positions: Float32Array,
25
+ indices: Uint32Array,
26
+ ): THREE.BufferGeometry {
27
+ const geometry = new THREE.BufferGeometry()
28
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
29
+ geometry.setIndex(new THREE.BufferAttribute(indices, 1))
30
+ geometry.computeVertexNormals()
31
+ return geometry
32
+ }
@@ -0,0 +1,38 @@
1
+ import type { PrintShellCompileDiagnostic } from './print-shell-compiler-baseline'
2
+
3
+ export type ManifoldMeshData = {
4
+ nodeId: string
5
+ positions: Float32Array
6
+ indices: Uint32Array
7
+ }
8
+
9
+ export type ManifoldCompileOutput =
10
+ | {
11
+ status: 'compiled'
12
+ positions: Float32Array
13
+ indices: Uint32Array
14
+ diagnostics: PrintShellCompileDiagnostic[]
15
+ durationMs: number
16
+ }
17
+ | {
18
+ status: 'blocked'
19
+ positions: null
20
+ indices: null
21
+ diagnostics: PrintShellCompileDiagnostic[]
22
+ durationMs: number
23
+ }
24
+
25
+ export type ManifoldRuntimeOptions = {
26
+ /** URL of the manifold-3d emscripten glue module. Defaults to the pinned CDN copy. */
27
+ moduleUrl?: string
28
+ /** URL of manifold.wasm. Defaults to resolving relative to the glue module. */
29
+ wasmUrl?: string
30
+ }
31
+
32
+ export type ManifoldWorkerRequest = {
33
+ id: number
34
+ meshes: ManifoldMeshData[]
35
+ runtime?: ManifoldRuntimeOptions
36
+ }
37
+
38
+ export type ManifoldWorkerResponse = ManifoldCompileOutput & { id: number }