@pascal-app/editor 1.0.0-beta.5 → 1.0.0
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.
- package/LICENSE +21 -0
- package/bunfig.toml +4 -0
- package/package.json +13 -7
- package/scripts/generate-print-golden-house.ts +89 -0
- package/src/components/editor/bake-exporter.tsx +1 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +41 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +10 -2
- package/src/components/editor/capture-camera-rig.tsx +59 -0
- package/src/components/editor/custom-camera-controls.tsx +20 -11
- package/src/components/editor/editor-layout-mobile.tsx +6 -6
- package/src/components/editor/editor-layout-v2.tsx +9 -2
- package/src/components/editor/export-manager.tsx +175 -33
- package/src/components/editor/first-person/build-collider-world.test.ts +30 -0
- package/src/components/editor/first-person-controls.tsx +180 -12
- package/src/components/editor/floating-action-menu.tsx +42 -51
- package/src/components/editor/floorplan-panel.tsx +76 -12
- package/src/components/editor/grid.tsx +12 -7
- package/src/components/editor/group-actions.ts +34 -50
- package/src/components/editor/group-move-3d.ts +51 -21
- package/src/components/editor/group-transform-shared.test.ts +78 -0
- package/src/components/editor/group-transform-shared.ts +74 -7
- package/src/components/editor/handles/handle-arrow-raycast.test.ts +86 -0
- package/src/components/editor/handles/handle-arrow.tsx +109 -48
- package/src/components/editor/handles/linear-resize-drag.ts +67 -0
- package/src/components/editor/handles/resize-snap.test.ts +46 -0
- package/src/components/editor/handles/resize-snap.ts +10 -1
- package/src/components/editor/handles/use-handle-drag.ts +18 -1
- package/src/components/editor/index.tsx +140 -21
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +47 -58
- package/src/components/editor/selection-manager.tsx +104 -42
- package/src/components/editor/snapshot-capture-overlay.tsx +298 -39
- package/src/components/editor/snapshot-capture.test.ts +422 -0
- package/src/components/editor/snapshot-capture.ts +145 -0
- package/src/components/editor/thumbnail-generator.tsx +284 -237
- package/src/components/editor/use-floorplan-background-placement.ts +53 -5
- package/src/components/editor/wall-move-side-handles.tsx +299 -7
- package/src/components/editor-2d/floorplan-group-move.tsx +67 -33
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +3 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +129 -38
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +14 -1
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +16 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +2 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +52 -3
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +196 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +180 -32
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +402 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.ts +371 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +347 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +281 -305
- package/src/components/systems/roof/roof-edit-system.test.tsx +120 -0
- package/src/components/systems/roof/roof-edit-system.tsx +10 -19
- package/src/components/systems/selection-affordance-manager.tsx +59 -9
- package/src/components/systems/selection-affordance-services.ts +19 -0
- package/src/components/systems/stair/stair-edit-system.tsx +5 -2
- package/src/components/tools/fence/fence-drafting.test.ts +100 -0
- package/src/components/tools/fence/fence-drafting.ts +6 -25
- package/src/components/tools/item/draft-creation.test.ts +12 -0
- package/src/components/tools/item/draft-creation.ts +10 -0
- package/src/components/tools/item/face-host-commit.test.ts +65 -0
- package/src/components/tools/item/face-host-commit.ts +56 -0
- package/src/components/tools/item/face-host-preview.test.ts +77 -0
- package/src/components/tools/item/face-host-preview.ts +82 -0
- package/src/components/tools/item/move-tool.tsx +4 -3
- package/src/components/tools/item/placement-math.test.ts +11 -1
- package/src/components/tools/item/placement-math.ts +0 -20
- package/src/components/tools/item/placement-strategies.test.ts +331 -4
- package/src/components/tools/item/placement-strategies.ts +135 -2
- package/src/components/tools/item/placement-surface.test.ts +61 -0
- package/src/components/tools/item/placement-surface.ts +24 -0
- package/src/components/tools/item/placement-types.ts +6 -0
- package/src/components/tools/item/test-face-host.ts +121 -0
- package/src/components/tools/item/use-draft-node.test.tsx +336 -0
- package/src/components/tools/item/use-draft-node.ts +26 -4
- package/src/components/tools/item/use-placement-coordinator.tsx +428 -153
- package/src/components/tools/registered-tool-install-lifecycle.test.tsx +256 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +10 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +306 -110
- package/src/components/tools/registry-tool-context.tsx +30 -0
- package/src/components/tools/select/box-select-tool.tsx +3 -1
- package/src/components/tools/select/marquee-footprint.test.ts +53 -0
- package/src/components/tools/select/marquee-footprint.ts +29 -0
- package/src/components/tools/shared/placement-box.tsx +57 -7
- package/src/components/tools/shared/placement-dimension-guides.tsx +121 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +319 -17
- package/src/components/tools/shared/pointer-support-cap.ts +45 -8
- package/src/components/tools/site/terrain-sculpt-grid.tsx +9 -5
- package/src/components/tools/site/terrain-sculpt-tool.tsx +1 -1
- package/src/components/tools/stair/stair-tool.tsx +127 -74
- package/src/components/tools/tool-manager.tsx +25 -8
- package/src/components/tools/wall/wall-drafting.test.ts +320 -2
- package/src/components/tools/wall/wall-drafting.ts +23 -114
- package/src/components/ui/action-menu/control-modes.tsx +9 -6
- package/src/components/ui/action-menu/index.tsx +11 -1
- package/src/components/ui/command-palette/editor-commands.tsx +9 -12
- package/src/components/ui/command-palette/index.tsx +7 -2
- package/src/components/ui/controls/material-paint-panel.tsx +6 -4
- package/src/components/ui/controls/material-picker.tsx +4 -4
- package/src/components/ui/controls/scene-material-list.tsx +6 -5
- package/src/components/ui/controls/segmented-control.tsx +8 -1
- package/src/components/ui/controls/slider-control.tsx +19 -3
- package/src/components/ui/controls/toggle-control.tsx +18 -5
- package/src/components/ui/controls/tool-options-panel.tsx +126 -0
- package/src/components/ui/floating-level-selector.tsx +22 -5
- package/src/components/ui/helpers/contextual-helper-panel.tsx +9 -0
- package/src/components/ui/helpers/helper-manager.tsx +47 -11
- package/src/components/ui/helpers/item-helper.tsx +1 -1
- package/src/components/ui/helpers/registered-tool-helper.tsx +21 -0
- package/src/components/ui/panels/homogeneous-selection.test.ts +80 -0
- package/src/components/ui/panels/homogeneous-selection.ts +47 -0
- package/src/components/ui/panels/mobile-selection-bar.tsx +11 -5
- package/src/components/ui/panels/multi-field-value.test.ts +209 -0
- package/src/components/ui/panels/multi-field-value.ts +163 -0
- package/src/components/ui/panels/multi-height-mode.tsx +185 -0
- package/src/components/ui/panels/multi-parametric-inspector.tsx +263 -0
- package/src/components/ui/panels/multi-selection-panel.tsx +48 -37
- package/src/components/ui/panels/node-display.ts +4 -0
- package/src/components/ui/panels/panel-manager.tsx +77 -3
- package/src/components/ui/panels/parametric-field-control.tsx +173 -0
- package/src/components/ui/panels/parametric-field-utils.ts +18 -0
- package/src/components/ui/panels/parametric-inspector.tsx +29 -162
- package/src/components/ui/panels/reference-panel.tsx +31 -1
- package/src/components/ui/primitives/shortcut-token.tsx +23 -1
- package/src/components/ui/scene-loader.tsx +35 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +390 -36
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +20 -23
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +140 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +109 -0
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +19 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +116 -46
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +28 -11
- package/src/components/ui/sidebar/panels/site-panel/tree-node.test.ts +8 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +14 -6
- package/src/components/ui/sidebar/tab-bar.tsx +2 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +17 -2
- package/src/components/viewer/viewer-scene-header.tsx +2 -1
- package/src/components/viewer/viewer-stage.tsx +4 -1
- package/src/components/viewer-overlay.tsx +1 -22
- package/src/components/viewer-zone-system.tsx +6 -2
- package/src/components/walkthrough-hud.tsx +19 -10
- package/src/hooks/use-auto-save.test.ts +73 -1
- package/src/hooks/use-auto-save.ts +76 -5
- package/src/hooks/use-ceiling-events.test.ts +122 -0
- package/src/hooks/use-drag-action.ts +17 -3
- package/src/hooks/use-grid-events.test.ts +240 -0
- package/src/hooks/use-grid-events.ts +190 -11
- package/src/hooks/use-keyboard.test.ts +244 -0
- package/src/hooks/use-keyboard.ts +129 -62
- package/src/hooks/use-registered-tool-enabled.ts +20 -0
- package/src/hooks/use-save-shortcut.ts +33 -0
- package/src/index.tsx +82 -4
- package/src/lib/active-placement-surface.test.ts +16 -0
- package/src/lib/active-placement-surface.ts +14 -0
- package/src/lib/ceiling-surface-raycast.test.ts +64 -0
- package/src/lib/ceiling-surface-raycast.ts +12 -0
- package/src/lib/contextual-help-extension.ts +17 -0
- package/src/lib/continuation.test.ts +11 -0
- package/src/lib/continuation.ts +8 -1
- package/src/lib/direct-manipulation.test.ts +132 -0
- package/src/lib/direct-manipulation.ts +38 -1
- package/src/lib/editor-api.ts +6 -3
- package/src/lib/elevation-guides.test.ts +28 -1
- package/src/lib/elevation-guides.ts +22 -7
- package/src/lib/floating-menu-scale.test.ts +29 -0
- package/src/lib/floating-menu-scale.ts +14 -0
- package/src/lib/floorplan/apply-alignment.test.ts +47 -2
- package/src/lib/floorplan/apply-alignment.ts +10 -1
- package/src/lib/floorplan/floorplan-export.test.ts +397 -1
- package/src/lib/floorplan/floorplan-export.tsx +111 -16
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +26 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +32 -24
- package/src/lib/floorplan/floorplan-readonly.ts +1 -0
- package/src/lib/floorplan-grid-event-point.test.ts +29 -0
- package/src/lib/floorplan-grid-event-point.ts +13 -0
- package/src/lib/glb-export.test.ts +1169 -21
- package/src/lib/glb-export.ts +629 -101
- package/src/lib/grid-event-presentation.ts +16 -0
- package/src/lib/history.test.ts +528 -147
- package/src/lib/history.ts +81 -7
- package/src/lib/host-tree-children.test.ts +27 -0
- package/src/lib/host-tree-children.ts +79 -0
- package/src/lib/interaction/hot-set.test.ts +10 -1
- package/src/lib/interaction/hot-set.ts +3 -1
- package/src/lib/interaction/overlay-policy.test.ts +33 -1
- package/src/lib/interaction/overlay-policy.ts +16 -0
- package/src/lib/interaction/registered-drafting.ts +36 -0
- package/src/lib/interaction/scope.ts +31 -0
- package/src/lib/level-print-export.test.ts +484 -0
- package/src/lib/level-print-export.ts +526 -0
- package/src/lib/local-project-presentation-persistence.test.ts +302 -0
- package/src/lib/local-project-presentation-persistence.ts +335 -0
- package/src/lib/material-paint.ts +2 -0
- package/src/lib/model-export.ts +29 -0
- package/src/lib/paint-preview-owner.ts +71 -0
- package/src/lib/planar-cursor-placement.test.ts +178 -1
- package/src/lib/planar-cursor-placement.ts +61 -5
- package/src/lib/plugin-panels.test.ts +92 -1
- package/src/lib/plugin-panels.ts +48 -0
- package/src/lib/portable-export.test.ts +366 -0
- package/src/lib/portable-export.ts +969 -0
- package/src/lib/print-3mf.test.ts +86 -0
- package/src/lib/print-3mf.ts +161 -0
- package/src/lib/print-content-scope.test.ts +107 -0
- package/src/lib/print-content-scope.ts +90 -0
- package/src/lib/print-export.test.ts +294 -0
- package/src/lib/print-export.ts +735 -0
- package/src/lib/print-feature-thickness.test.ts +147 -0
- package/src/lib/print-feature-thickness.ts +180 -0
- package/src/lib/print-golden-house.test-fixture.ts +352 -0
- package/src/lib/print-golden-house.test.ts +249 -0
- package/src/lib/print-roof-solids.test.ts +133 -0
- package/src/lib/print-roof-solids.ts +630 -0
- package/src/lib/print-shell-compiler-baseline.test.ts +419 -0
- package/src/lib/print-shell-compiler-baseline.ts +225 -0
- package/src/lib/print-shell-compiler-manifold-core.ts +330 -0
- package/src/lib/print-shell-compiler-manifold-worker.ts +187 -0
- package/src/lib/print-shell-compiler-manifold.worker.ts +20 -0
- package/src/lib/print-shell-compiler-mesh-data.ts +32 -0
- package/src/lib/print-shell-compiler-protocol.ts +30 -0
- package/src/lib/print-shell-compiler.ts +273 -0
- package/src/lib/print-wall-solids.test.ts +194 -0
- package/src/lib/print-wall-solids.ts +328 -0
- package/src/lib/rigid-plan-svg-transform.test.ts +35 -0
- package/src/lib/rigid-plan-svg-transform.ts +24 -0
- package/src/lib/scene.ts +22 -8
- package/src/lib/selection-routing.test.ts +105 -1
- package/src/lib/selection-routing.ts +19 -0
- package/src/lib/sfx-bus.ts +4 -0
- package/src/lib/sfx-player.ts +8 -0
- package/src/lib/snapping-mode.test.ts +32 -4
- package/src/lib/snapping-mode.ts +11 -7
- package/src/lib/usdz-export.ts +38 -0
- package/src/lib/walkthrough-pointer-lock.ts +47 -0
- package/src/store/live-draft-preview-stores.test.ts +3 -0
- package/src/store/terrain-sculpt-mode.test.ts +3 -6
- package/src/store/tool-mode.test.ts +87 -0
- package/src/store/use-camera-hint-focus.ts +48 -0
- package/src/store/use-editor.tsx +299 -188
- package/src/store/use-floorplan-draft-preview.ts +9 -0
- package/src/store/use-interaction-scope.test.ts +23 -1
- package/src/store/use-interaction-scope.ts +63 -2
- package/src/store/use-placement-preview.ts +56 -3
- package/src/components/tools/roof/roof-tool.tsx +0 -656
- package/src/components/ui/helpers/roof-helper.tsx +0 -14
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import ManifoldModule, { type Manifold as ManifoldSolid, type ManifoldToplevel } from 'manifold-3d'
|
|
2
|
+
import type { PrintShellCompileDiagnostic } from './print-shell-compiler-baseline'
|
|
3
|
+
import type { ManifoldCompileOutput, ManifoldMeshData } from './print-shell-compiler-protocol'
|
|
4
|
+
|
|
5
|
+
let modulePromise: Promise<ManifoldToplevel> | null = null
|
|
6
|
+
const MANIFOLD_OUTPUT_WELD_EPSILON_METERS = 2e-5
|
|
7
|
+
const COLLINEAR_SEAM_CROSS_LENGTH_SQ = 1e-20
|
|
8
|
+
|
|
9
|
+
type Triangle = [number, number, number]
|
|
10
|
+
|
|
11
|
+
async function getManifoldModule(wasmUrl?: string): Promise<ManifoldToplevel> {
|
|
12
|
+
modulePromise ??= ManifoldModule(wasmUrl ? { locateFile: () => wasmUrl } : undefined).then(
|
|
13
|
+
(module) => {
|
|
14
|
+
module.setup()
|
|
15
|
+
return module
|
|
16
|
+
},
|
|
17
|
+
)
|
|
18
|
+
return modulePromise
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function manifoldMesh(
|
|
22
|
+
module: ManifoldToplevel,
|
|
23
|
+
mesh: ManifoldMeshData,
|
|
24
|
+
): InstanceType<ManifoldToplevel['Mesh']> {
|
|
25
|
+
return new module.Mesh({
|
|
26
|
+
numProp: 3,
|
|
27
|
+
vertProperties: mesh.positions,
|
|
28
|
+
triVerts: mesh.indices,
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function distanceSquared(positions: Float32Array, left: number, right: number): number {
|
|
33
|
+
const dx = positions[left * 3]! - positions[right * 3]!
|
|
34
|
+
const dy = positions[left * 3 + 1]! - positions[right * 3 + 1]!
|
|
35
|
+
const dz = positions[left * 3 + 2]! - positions[right * 3 + 2]!
|
|
36
|
+
return dx * dx + dy * dy + dz * dz
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function triangleCrossLengthSquared(positions: Float32Array, [a, b, c]: Triangle): number {
|
|
40
|
+
const abX = positions[b * 3]! - positions[a * 3]!
|
|
41
|
+
const abY = positions[b * 3 + 1]! - positions[a * 3 + 1]!
|
|
42
|
+
const abZ = positions[b * 3 + 2]! - positions[a * 3 + 2]!
|
|
43
|
+
const acX = positions[c * 3]! - positions[a * 3]!
|
|
44
|
+
const acY = positions[c * 3 + 1]! - positions[a * 3 + 1]!
|
|
45
|
+
const acZ = positions[c * 3 + 2]! - positions[a * 3 + 2]!
|
|
46
|
+
const crossX = abY * acZ - abZ * acY
|
|
47
|
+
const crossY = abZ * acX - abX * acZ
|
|
48
|
+
const crossZ = abX * acY - abY * acX
|
|
49
|
+
return crossX * crossX + crossY * crossY + crossZ * crossZ
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function collinearSeam(
|
|
53
|
+
positions: Float32Array,
|
|
54
|
+
triangle: Triangle,
|
|
55
|
+
): { start: number; middle: number; end: number } | null {
|
|
56
|
+
if (triangleCrossLengthSquared(positions, triangle) > COLLINEAR_SEAM_CROSS_LENGTH_SQ) {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const [a, b, c] = triangle
|
|
61
|
+
const edges = [
|
|
62
|
+
{ start: a, middle: c, end: b, lengthSquared: distanceSquared(positions, a, b) },
|
|
63
|
+
{ start: b, middle: a, end: c, lengthSquared: distanceSquared(positions, b, c) },
|
|
64
|
+
{ start: c, middle: b, end: a, lengthSquared: distanceSquared(positions, c, a) },
|
|
65
|
+
].sort((left, right) => right.lengthSquared - left.lengthSquared)
|
|
66
|
+
const longest = edges[0]!
|
|
67
|
+
if (longest.lengthSquared === 0) return null
|
|
68
|
+
|
|
69
|
+
const startOffset = longest.start * 3
|
|
70
|
+
const middleOffset = longest.middle * 3
|
|
71
|
+
const endOffset = longest.end * 3
|
|
72
|
+
const edgeX = positions[endOffset]! - positions[startOffset]!
|
|
73
|
+
const edgeY = positions[endOffset + 1]! - positions[startOffset + 1]!
|
|
74
|
+
const edgeZ = positions[endOffset + 2]! - positions[startOffset + 2]!
|
|
75
|
+
const middleX = positions[middleOffset]! - positions[startOffset]!
|
|
76
|
+
const middleY = positions[middleOffset + 1]! - positions[startOffset + 1]!
|
|
77
|
+
const middleZ = positions[middleOffset + 2]! - positions[startOffset + 2]!
|
|
78
|
+
const projection = middleX * edgeX + middleY * edgeY + middleZ * edgeZ
|
|
79
|
+
if (projection <= 0 || projection >= longest.lengthSquared) return null
|
|
80
|
+
|
|
81
|
+
return longest
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function stitchCollinearSeams(positions: Float32Array, input: Triangle[]): Triangle[] {
|
|
85
|
+
const triangles: Array<Triangle | null> = [...input]
|
|
86
|
+
|
|
87
|
+
// Manifold can encode a T-junction as one zero-area triangle: one surface owns the long
|
|
88
|
+
// edge while the other owns its two segments. Split the neighboring face at the middle
|
|
89
|
+
// vertex before removing the collapsed face so indexed edge incidence remains closed.
|
|
90
|
+
for (let triangleIndex = 0; triangleIndex < triangles.length; triangleIndex += 1) {
|
|
91
|
+
const triangle = triangles[triangleIndex]
|
|
92
|
+
if (!triangle) continue
|
|
93
|
+
const seam = collinearSeam(positions, triangle)
|
|
94
|
+
if (!seam) continue
|
|
95
|
+
|
|
96
|
+
const matches: Array<{ index: number; edgeOffset: number }> = []
|
|
97
|
+
for (let candidateIndex = 0; candidateIndex < triangles.length; candidateIndex += 1) {
|
|
98
|
+
if (candidateIndex === triangleIndex) continue
|
|
99
|
+
const candidate = triangles[candidateIndex]
|
|
100
|
+
if (
|
|
101
|
+
!candidate ||
|
|
102
|
+
triangleCrossLengthSquared(positions, candidate) <= COLLINEAR_SEAM_CROSS_LENGTH_SQ
|
|
103
|
+
) {
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
for (let edgeOffset = 0; edgeOffset < 3; edgeOffset += 1) {
|
|
107
|
+
const from = candidate[edgeOffset]!
|
|
108
|
+
const to = candidate[(edgeOffset + 1) % 3]!
|
|
109
|
+
if ((from === seam.start && to === seam.end) || (from === seam.end && to === seam.start)) {
|
|
110
|
+
matches.push({ index: candidateIndex, edgeOffset })
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (matches.length !== 1) continue
|
|
115
|
+
|
|
116
|
+
const match = matches[0]!
|
|
117
|
+
const neighbor = triangles[match.index]!
|
|
118
|
+
const from = neighbor[match.edgeOffset]!
|
|
119
|
+
const to = neighbor[(match.edgeOffset + 1) % 3]!
|
|
120
|
+
const opposite = neighbor[(match.edgeOffset + 2) % 3]!
|
|
121
|
+
triangles[match.index] = [from, seam.middle, opposite]
|
|
122
|
+
triangles.push([seam.middle, to, opposite])
|
|
123
|
+
triangles[triangleIndex] = null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return triangles.filter((triangle): triangle is Triangle => triangle !== null)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function manifoldOutput(solid: ManifoldSolid): { positions: Float32Array; indices: Uint32Array } {
|
|
130
|
+
const mesh = solid.getMesh()
|
|
131
|
+
const positions = new Float32Array(mesh.numVert * 3)
|
|
132
|
+
for (let index = 0; index < mesh.numVert; index += 1) {
|
|
133
|
+
const sourceOffset = index * mesh.numProp
|
|
134
|
+
positions[index * 3] = mesh.vertProperties[sourceOffset]!
|
|
135
|
+
positions[index * 3 + 1] = mesh.vertProperties[sourceOffset + 1]!
|
|
136
|
+
positions[index * 3 + 2] = mesh.vertProperties[sourceOffset + 2]!
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const parents = new Uint32Array(mesh.numVert)
|
|
140
|
+
for (let index = 0; index < parents.length; index += 1) parents[index] = index
|
|
141
|
+
const find = (index: number): number => {
|
|
142
|
+
let root = index
|
|
143
|
+
while (parents[root] !== root) root = parents[root]!
|
|
144
|
+
while (parents[index] !== index) {
|
|
145
|
+
const next = parents[index]!
|
|
146
|
+
parents[index] = root
|
|
147
|
+
index = next
|
|
148
|
+
}
|
|
149
|
+
return root
|
|
150
|
+
}
|
|
151
|
+
for (let index = 0; index < mesh.mergeFromVert.length; index += 1) {
|
|
152
|
+
parents[find(mesh.mergeFromVert[index]!)] = find(mesh.mergeToVert[index]!)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Float32 boolean output can leave seam vertices just over 10 microns apart. Dropping the
|
|
156
|
+
// resulting sliver triangle by area opens the shell; weld the vertices first so adjacent
|
|
157
|
+
// faces inherit one indexed edge, then remove only triangles collapsed by that topology.
|
|
158
|
+
const cellSize = MANIFOLD_OUTPUT_WELD_EPSILON_METERS
|
|
159
|
+
const cellRoots = new Map<string, number[]>()
|
|
160
|
+
const cellCoordinate = (value: number) => Math.floor(value / cellSize)
|
|
161
|
+
const cellKey = (x: number, y: number, z: number) => `${x},${y},${z}`
|
|
162
|
+
const weldDistanceSquared = cellSize * cellSize
|
|
163
|
+
for (let index = 0; index < mesh.numVert; index += 1) {
|
|
164
|
+
const root = find(index)
|
|
165
|
+
if (root !== index) continue
|
|
166
|
+
const cellX = cellCoordinate(positions[root * 3]!)
|
|
167
|
+
const cellY = cellCoordinate(positions[root * 3 + 1]!)
|
|
168
|
+
const cellZ = cellCoordinate(positions[root * 3 + 2]!)
|
|
169
|
+
let weldedTo: number | null = null
|
|
170
|
+
for (let xOffset = -1; xOffset <= 1 && weldedTo === null; xOffset += 1) {
|
|
171
|
+
for (let yOffset = -1; yOffset <= 1 && weldedTo === null; yOffset += 1) {
|
|
172
|
+
for (let zOffset = -1; zOffset <= 1 && weldedTo === null; zOffset += 1) {
|
|
173
|
+
const candidates = cellRoots.get(
|
|
174
|
+
cellKey(cellX + xOffset, cellY + yOffset, cellZ + zOffset),
|
|
175
|
+
)
|
|
176
|
+
for (const candidate of candidates ?? []) {
|
|
177
|
+
if (distanceSquared(positions, root, candidate) <= weldDistanceSquared) {
|
|
178
|
+
weldedTo = candidate
|
|
179
|
+
break
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (weldedTo === null) {
|
|
186
|
+
const key = cellKey(cellX, cellY, cellZ)
|
|
187
|
+
const roots = cellRoots.get(key) ?? []
|
|
188
|
+
roots.push(root)
|
|
189
|
+
cellRoots.set(key, roots)
|
|
190
|
+
} else {
|
|
191
|
+
parents[root] = find(weldedTo)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const triangles: Triangle[] = []
|
|
196
|
+
for (let index = 0; index + 2 < mesh.triVerts.length; index += 3) {
|
|
197
|
+
const a = find(mesh.triVerts[index]!)
|
|
198
|
+
const b = find(mesh.triVerts[index + 1]!)
|
|
199
|
+
const c = find(mesh.triVerts[index + 2]!)
|
|
200
|
+
if (a === b || b === c || c === a) continue
|
|
201
|
+
triangles.push([a, b, c])
|
|
202
|
+
}
|
|
203
|
+
const indices = stitchCollinearSeams(positions, triangles).flat()
|
|
204
|
+
|
|
205
|
+
return { positions, indices: new Uint32Array(indices) }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function elapsed(startedAt: number): number {
|
|
209
|
+
return Math.max(0, performance.now() - startedAt)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function compileManifoldMeshData(
|
|
213
|
+
meshes: ManifoldMeshData[],
|
|
214
|
+
wasmUrl?: string,
|
|
215
|
+
): Promise<ManifoldCompileOutput> {
|
|
216
|
+
const startedAt = performance.now()
|
|
217
|
+
const sourceNodeIds = Array.from(new Set(meshes.map((mesh) => mesh.nodeId))).sort()
|
|
218
|
+
const diagnostics: PrintShellCompileDiagnostic[] = []
|
|
219
|
+
const solids: ManifoldSolid[] = []
|
|
220
|
+
let union: ManifoldSolid | null = null
|
|
221
|
+
let result: ManifoldSolid | null = null
|
|
222
|
+
|
|
223
|
+
if (meshes.length === 0) {
|
|
224
|
+
return {
|
|
225
|
+
status: 'blocked',
|
|
226
|
+
positions: null,
|
|
227
|
+
indices: null,
|
|
228
|
+
diagnostics: [
|
|
229
|
+
{
|
|
230
|
+
severity: 'error',
|
|
231
|
+
code: 'no_shell_meshes',
|
|
232
|
+
message: 'No structural meshes are available for Manifold compilation.',
|
|
233
|
+
nodeIds: [],
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
durationMs: elapsed(startedAt),
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const module = await getManifoldModule(wasmUrl)
|
|
242
|
+
for (const mesh of meshes) {
|
|
243
|
+
try {
|
|
244
|
+
solids.push(new module.Manifold(manifoldMesh(module, mesh)))
|
|
245
|
+
} catch (error) {
|
|
246
|
+
diagnostics.push({
|
|
247
|
+
severity: 'error',
|
|
248
|
+
code: 'manifold_input_failed',
|
|
249
|
+
message: `Node ${mesh.nodeId}: ${
|
|
250
|
+
error instanceof Error ? error.message : 'Manifold rejected the shell input.'
|
|
251
|
+
}`,
|
|
252
|
+
nodeIds: [mesh.nodeId],
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (diagnostics.length > 0) {
|
|
257
|
+
return {
|
|
258
|
+
status: 'blocked',
|
|
259
|
+
positions: null,
|
|
260
|
+
indices: null,
|
|
261
|
+
diagnostics,
|
|
262
|
+
durationMs: elapsed(startedAt),
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
union = module.Manifold.union(solids)
|
|
267
|
+
result = union.asOriginal()
|
|
268
|
+
const status = result.status()
|
|
269
|
+
if (status !== 'NoError') {
|
|
270
|
+
return {
|
|
271
|
+
status: 'blocked',
|
|
272
|
+
positions: null,
|
|
273
|
+
indices: null,
|
|
274
|
+
diagnostics: [
|
|
275
|
+
{
|
|
276
|
+
severity: 'error',
|
|
277
|
+
code: 'manifold_union_failed',
|
|
278
|
+
message: `Manifold union failed with ${status}.`,
|
|
279
|
+
nodeIds: sourceNodeIds,
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
durationMs: elapsed(startedAt),
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const output = manifoldOutput(result)
|
|
287
|
+
if (output.indices.length === 0) {
|
|
288
|
+
return {
|
|
289
|
+
status: 'blocked',
|
|
290
|
+
positions: null,
|
|
291
|
+
indices: null,
|
|
292
|
+
diagnostics: [
|
|
293
|
+
{
|
|
294
|
+
severity: 'error',
|
|
295
|
+
code: 'manifold_union_failed',
|
|
296
|
+
message: 'Manifold produced no printable triangles.',
|
|
297
|
+
nodeIds: sourceNodeIds,
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
durationMs: elapsed(startedAt),
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
status: 'compiled',
|
|
305
|
+
positions: output.positions,
|
|
306
|
+
indices: output.indices,
|
|
307
|
+
diagnostics: [],
|
|
308
|
+
durationMs: elapsed(startedAt),
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
return {
|
|
312
|
+
status: 'blocked',
|
|
313
|
+
positions: null,
|
|
314
|
+
indices: null,
|
|
315
|
+
diagnostics: [
|
|
316
|
+
{
|
|
317
|
+
severity: 'error',
|
|
318
|
+
code: 'manifold_worker_failed',
|
|
319
|
+
message: error instanceof Error ? error.message : 'Manifold compilation failed.',
|
|
320
|
+
nodeIds: sourceNodeIds,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
durationMs: elapsed(startedAt),
|
|
324
|
+
}
|
|
325
|
+
} finally {
|
|
326
|
+
for (const solid of solids) solid.delete()
|
|
327
|
+
union?.delete()
|
|
328
|
+
result?.delete()
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
ManifoldWorkerRequest,
|
|
20
|
+
ManifoldWorkerResponse,
|
|
21
|
+
} from './print-shell-compiler-protocol'
|
|
22
|
+
|
|
23
|
+
const WORKER_TIMEOUT_MS = 60_000
|
|
24
|
+
|
|
25
|
+
export type ManifoldCompileRunner = (meshes: ManifoldMeshData[]) => Promise<ManifoldCompileOutput>
|
|
26
|
+
|
|
27
|
+
export type SemanticManifoldCompileOptions = SemanticPrintCompileOptions & {
|
|
28
|
+
runner?: ManifoldCompileRunner
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type PendingRequest = {
|
|
32
|
+
resolve: (output: ManifoldCompileOutput) => void
|
|
33
|
+
reject: (error: Error) => void
|
|
34
|
+
timeout: ReturnType<typeof setTimeout>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let worker: Worker | null = null
|
|
38
|
+
let nextRequestId = 1
|
|
39
|
+
const pendingRequests = new Map<number, PendingRequest>()
|
|
40
|
+
|
|
41
|
+
function resetWorker(error: Error) {
|
|
42
|
+
worker?.terminate()
|
|
43
|
+
worker = null
|
|
44
|
+
for (const pending of pendingRequests.values()) {
|
|
45
|
+
clearTimeout(pending.timeout)
|
|
46
|
+
pending.reject(error)
|
|
47
|
+
}
|
|
48
|
+
pendingRequests.clear()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getWorker(): Worker {
|
|
52
|
+
if (worker) return worker
|
|
53
|
+
if (typeof Worker === 'undefined') {
|
|
54
|
+
throw new Error('Web Workers are unavailable in this environment.')
|
|
55
|
+
}
|
|
56
|
+
worker = new Worker(new URL('./print-shell-compiler-manifold.worker.ts', import.meta.url), {
|
|
57
|
+
type: 'module',
|
|
58
|
+
})
|
|
59
|
+
worker.addEventListener('message', (event: MessageEvent<ManifoldWorkerResponse>) => {
|
|
60
|
+
const pending = pendingRequests.get(event.data.id)
|
|
61
|
+
if (!pending) return
|
|
62
|
+
pendingRequests.delete(event.data.id)
|
|
63
|
+
clearTimeout(pending.timeout)
|
|
64
|
+
pending.resolve(event.data)
|
|
65
|
+
})
|
|
66
|
+
worker.addEventListener('error', (event) => {
|
|
67
|
+
resetWorker(new Error(event.message || 'The Manifold worker failed.'))
|
|
68
|
+
})
|
|
69
|
+
worker.addEventListener('messageerror', () => {
|
|
70
|
+
resetWorker(new Error('The Manifold worker returned an unreadable response.'))
|
|
71
|
+
})
|
|
72
|
+
return worker
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const runManifoldWorker: ManifoldCompileRunner = (meshes) => {
|
|
76
|
+
const activeWorker = getWorker()
|
|
77
|
+
const id = nextRequestId
|
|
78
|
+
nextRequestId += 1
|
|
79
|
+
const request: ManifoldWorkerRequest = { id, meshes }
|
|
80
|
+
const transfer = meshes.flatMap((mesh) => [
|
|
81
|
+
mesh.positions.buffer as ArrayBuffer,
|
|
82
|
+
mesh.indices.buffer as ArrayBuffer,
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const timeout = setTimeout(() => {
|
|
87
|
+
resetWorker(new Error(`The Manifold worker exceeded ${WORKER_TIMEOUT_MS / 1000} seconds.`))
|
|
88
|
+
}, WORKER_TIMEOUT_MS)
|
|
89
|
+
pendingRequests.set(id, { resolve, reject, timeout })
|
|
90
|
+
try {
|
|
91
|
+
activeWorker.postMessage(request, transfer)
|
|
92
|
+
} catch (error) {
|
|
93
|
+
resetWorker(
|
|
94
|
+
error instanceof Error ? error : new Error('Failed to start the Manifold worker.'),
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function blockedResult(
|
|
101
|
+
inputMeshCount: number,
|
|
102
|
+
sourceNodeIds: Iterable<string>,
|
|
103
|
+
diagnostics: PrintShellCompileDiagnostic[],
|
|
104
|
+
): PrintShellCompileResult {
|
|
105
|
+
return {
|
|
106
|
+
backend: 'manifold-3d',
|
|
107
|
+
status: 'blocked',
|
|
108
|
+
scene: null,
|
|
109
|
+
inputMeshCount,
|
|
110
|
+
sourceNodeIds: Array.from(sourceNodeIds).sort(),
|
|
111
|
+
diagnostics,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function compileSemanticPrintShellWithManifold(
|
|
116
|
+
source: THREE.Object3D,
|
|
117
|
+
nodes: Record<string, AnyNode>,
|
|
118
|
+
options: SemanticManifoldCompileOptions = {},
|
|
119
|
+
): Promise<PrintShellCompileResult> {
|
|
120
|
+
const { runner = runManifoldWorker, ...semanticOptions } = options
|
|
121
|
+
const prepared = prepareSemanticPrintShellSource(source, nodes, {
|
|
122
|
+
...semanticOptions,
|
|
123
|
+
wallSolids: semanticOptions.wallSolids ?? true,
|
|
124
|
+
})
|
|
125
|
+
if (prepared.status === 'blocked') {
|
|
126
|
+
return blockedResult(prepared.inputMeshCount, prepared.sourceNodeIds, prepared.diagnostics)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const input = collectPrintShellInput(prepared.scene)
|
|
130
|
+
prepared.dispose()
|
|
131
|
+
if (input.diagnostics.some((diagnostic) => diagnostic.severity === 'error')) {
|
|
132
|
+
for (const geometry of input.geometries) geometry.dispose()
|
|
133
|
+
return blockedResult(input.inputMeshCount, input.sourceNodeIds, input.diagnostics)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const meshes = input.geometries.map((geometry, index) =>
|
|
137
|
+
geometryToManifoldMeshData(geometry, input.geometryNodeIds[index]!),
|
|
138
|
+
)
|
|
139
|
+
for (const geometry of input.geometries) geometry.dispose()
|
|
140
|
+
|
|
141
|
+
let output: ManifoldCompileOutput
|
|
142
|
+
try {
|
|
143
|
+
output = await runner(meshes)
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return blockedResult(input.inputMeshCount, input.sourceNodeIds, [
|
|
146
|
+
...input.diagnostics,
|
|
147
|
+
{
|
|
148
|
+
severity: 'error',
|
|
149
|
+
code: 'manifold_worker_failed',
|
|
150
|
+
message: error instanceof Error ? error.message : 'The Manifold worker failed.',
|
|
151
|
+
nodeIds: Array.from(input.sourceNodeIds).sort(),
|
|
152
|
+
},
|
|
153
|
+
])
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const diagnostics = [...input.diagnostics, ...output.diagnostics]
|
|
157
|
+
if (output.status === 'blocked') {
|
|
158
|
+
return blockedResult(input.inputMeshCount, input.sourceNodeIds, diagnostics)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const geometry = geometryFromManifoldMeshData(output.positions, output.indices)
|
|
162
|
+
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial())
|
|
163
|
+
mesh.name = 'print-shell-manifold'
|
|
164
|
+
mesh.userData = {
|
|
165
|
+
printCompiler: 'manifold-3d',
|
|
166
|
+
sourceNodeIds: Array.from(input.sourceNodeIds).sort(),
|
|
167
|
+
}
|
|
168
|
+
const scene = new THREE.Group()
|
|
169
|
+
scene.name = 'compiled-print-shell'
|
|
170
|
+
scene.add(mesh)
|
|
171
|
+
diagnostics.push({
|
|
172
|
+
severity: 'info',
|
|
173
|
+
code: runner === runManifoldWorker ? 'manifold_worker_compiler' : 'manifold_compiler_candidate',
|
|
174
|
+
message: `Compiled with Manifold in ${output.durationMs.toFixed(1)} ms${
|
|
175
|
+
runner === runManifoldWorker ? ' off the main thread' : ' through the in-process test runner'
|
|
176
|
+
}.`,
|
|
177
|
+
nodeIds: Array.from(input.sourceNodeIds).sort(),
|
|
178
|
+
})
|
|
179
|
+
return {
|
|
180
|
+
backend: 'manifold-3d',
|
|
181
|
+
status: 'compiled',
|
|
182
|
+
scene,
|
|
183
|
+
inputMeshCount: input.inputMeshCount,
|
|
184
|
+
sourceNodeIds: Array.from(input.sourceNodeIds).sort(),
|
|
185
|
+
diagnostics,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -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)
|
|
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,30 @@
|
|
|
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 ManifoldWorkerRequest = {
|
|
26
|
+
id: number
|
|
27
|
+
meshes: ManifoldMeshData[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ManifoldWorkerResponse = ManifoldCompileOutput & { id: number }
|