@pascal-app/editor 0.9.1 → 1.0.0-beta.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.
- package/package.json +14 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
- package/src/components/editor/bake-exporter.tsx +49 -0
- package/src/components/editor/bake-thumbnail.tsx +95 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
- package/src/components/editor/custom-camera-controls.tsx +332 -199
- package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
- package/src/components/editor/editor-layout-mobile.tsx +5 -0
- package/src/components/editor/editor-layout-v2.tsx +18 -0
- package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
- package/src/components/editor/export-manager.tsx +71 -63
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +102 -5
- package/src/components/editor/first-person/build-collider-world.ts +52 -5
- package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
- package/src/components/editor/first-person/terrain-collider.ts +212 -0
- package/src/components/editor/first-person-controls.tsx +410 -136
- package/src/components/editor/floating-action-menu.tsx +435 -64
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-background-selection.test.ts +72 -0
- package/src/components/editor/floorplan-background-selection.ts +7 -20
- package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
- package/src/components/editor/floorplan-camera-sync.ts +213 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
- package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
- package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
- package/src/components/editor/floorplan-panel.tsx +2517 -1492
- package/src/components/editor/grid.tsx +166 -39
- package/src/components/editor/group-actions.ts +597 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +102 -64
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +192 -1
- package/src/components/editor/group-transform-shared.ts +240 -7
- package/src/components/editor/handles/handle-arrow.tsx +81 -23
- package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
- package/src/components/editor/handles/handle-drag-history.ts +14 -0
- package/src/components/editor/handles/preview-overrides.test.ts +26 -0
- package/src/components/editor/handles/preview-overrides.ts +13 -0
- package/src/components/editor/handles/resize-snap.test.ts +52 -0
- package/src/components/editor/handles/resize-snap.ts +23 -0
- package/src/components/editor/handles/use-handle-drag.ts +49 -6
- package/src/components/editor/index.tsx +289 -35
- package/src/components/editor/measurement-pill.tsx +68 -23
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +409 -180
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +76 -0
- package/src/components/editor/quick-measurement-hud.tsx +34 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +814 -466
- package/src/components/editor/site-edge-labels.tsx +28 -6
- package/src/components/editor/slab-hole-highlights.tsx +13 -5
- package/src/components/editor/snapshot-capture-overlay.tsx +258 -115
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +118 -253
- package/src/components/editor/use-floorplan-background-placement.ts +113 -45
- package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
- package/src/components/editor/use-floorplan-scene-data.ts +5 -5
- package/src/components/editor/use-mesh-settle-epoch.ts +27 -0
- package/src/components/editor/wall-measurement-label.tsx +10 -28
- package/src/components/editor/wall-move-side-handles.tsx +229 -39
- package/src/components/editor/wall-opening-highlights.tsx +63 -32
- package/src/components/editor/wall-snap-beacon-layer.tsx +162 -6
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +40 -9
- package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +744 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1672 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +219 -20
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +327 -59
- package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +89 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +106 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +94 -26
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +587 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +2280 -578
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +425 -61
- package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
- package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-tool.tsx +34 -8
- package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
- package/src/components/tools/fence/fence-drafting.ts +107 -8
- package/src/components/tools/item/move-tool.tsx +17 -13
- package/src/components/tools/item/placement-math.test.ts +13 -1
- package/src/components/tools/item/placement-math.ts +28 -2
- package/src/components/tools/item/placement-strategies.ts +246 -5
- package/src/components/tools/item/placement-types.ts +13 -1
- package/src/components/tools/item/use-draft-node.ts +146 -57
- package/src/components/tools/item/use-placement-coordinator.tsx +813 -125
- package/src/components/tools/registry/move-registry-node-tool.tsx +636 -103
- package/src/components/tools/roof/roof-tool.tsx +336 -94
- package/src/components/tools/select/box-select-state.ts +18 -7
- package/src/components/tools/select/box-select-tool.tsx +176 -40
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
- package/src/components/tools/select/select-candidates.ts +1 -1
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/floor-stack-preview.ts +4 -0
- package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +183 -1
- package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
- package/src/components/tools/shared/pointer-support-cap.ts +273 -0
- package/src/components/tools/shared/polygon-editor.tsx +276 -35
- package/src/components/tools/site/site-boundary-editor.tsx +101 -42
- package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
- package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
- package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
- package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
- package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
- package/src/components/tools/stair/stair-click-guard.ts +72 -0
- package/src/components/tools/stair/stair-defaults.ts +1 -0
- package/src/components/tools/stair/stair-tool.tsx +164 -42
- package/src/components/tools/tool-manager.tsx +149 -30
- package/src/components/tools/wall/wall-drafting.test.ts +447 -0
- package/src/components/tools/wall/wall-drafting.ts +304 -108
- package/src/components/tools/wall/wall-snap-geometry.test.ts +52 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -9
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +98 -88
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +44 -94
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +2 -4
- package/src/components/ui/action-menu/measurement-control.tsx +267 -0
- package/src/components/ui/action-menu/structure-tools.tsx +22 -13
- package/src/components/ui/action-menu/view-toggles.tsx +42 -90
- package/src/components/ui/command-palette/editor-commands.tsx +16 -3
- package/src/components/ui/command-palette/index.tsx +4 -3
- package/src/components/ui/controls/material-paint-panel.tsx +93 -18
- package/src/components/ui/controls/material-picker.tsx +162 -152
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +118 -44
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
- package/src/components/ui/floating-level-selector.tsx +74 -20
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +273 -14
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
- package/src/components/ui/panels/node-display.ts +17 -17
- package/src/components/ui/panels/panel-manager.tsx +36 -14
- package/src/components/ui/panels/panel-wrapper.tsx +24 -3
- package/src/components/ui/panels/parametric-inspector.tsx +19 -4
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
- package/src/components/ui/panels/selection-breakdown.ts +20 -0
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +82 -41
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +76 -6
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -17
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +42 -28
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer/viewer-controls-bar.tsx +466 -0
- package/src/components/viewer/viewer-scene-header.tsx +235 -0
- package/src/components/viewer-overlay.tsx +36 -596
- package/src/components/viewer-zone-system.tsx +6 -1
- package/src/components/walkthrough-hud.tsx +111 -0
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +54 -7
- package/src/hooks/use-ceiling-events.ts +3 -2
- package/src/hooks/use-drag-action.ts +4 -1
- package/src/hooks/use-grid-events.ts +24 -0
- package/src/hooks/use-keyboard.ts +438 -70
- package/src/index.tsx +355 -9
- package/src/lib/active-placement-surface.test.ts +24 -0
- package/src/lib/active-placement-surface.ts +42 -0
- package/src/lib/camera-pose.test.ts +237 -0
- package/src/lib/camera-pose.ts +172 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +275 -0
- package/src/lib/direct-manipulation.ts +118 -0
- package/src/lib/door-interaction.ts +1 -1
- package/src/lib/editor-api.ts +23 -35
- package/src/lib/elevation-guides.test.ts +107 -0
- package/src/lib/elevation-guides.ts +236 -0
- package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
- package/src/lib/floorplan/annotation-visibility.ts +133 -0
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +18 -12
- package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
- package/src/lib/floorplan/drawing-coordination.ts +13 -0
- package/src/lib/floorplan/floorplan-export.test.ts +319 -0
- package/src/lib/floorplan/floorplan-export.tsx +978 -0
- package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
- package/src/lib/floorplan/floorplan-extension.ts +181 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
- package/src/lib/floorplan/floorplan-mode.ts +73 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
- package/src/lib/floorplan/geometry.ts +52 -0
- package/src/lib/floorplan/index.ts +3 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/fresh-planar-placement.test.ts +283 -3
- package/src/lib/fresh-planar-placement.ts +85 -1
- package/src/lib/glb-export.test.ts +556 -0
- package/src/lib/glb-export.ts +1071 -0
- package/src/lib/ground-surface.ts +95 -0
- package/src/lib/history.test.ts +96 -0
- package/src/lib/history.ts +31 -0
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +52 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +243 -0
- package/src/lib/level-duplication.test.ts +2 -1
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +36 -49
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +188 -0
- package/src/lib/measurements.ts +210 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/planar-cursor-placement.test.ts +57 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-action-feedback.ts +33 -0
- package/src/lib/quick-action-nodes.test.ts +94 -0
- package/src/lib/quick-action-nodes.ts +47 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +6 -6
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +325 -0
- package/src/lib/scene-clipboard.ts +310 -21
- package/src/lib/scene.ts +28 -5
- package/src/lib/selection-routing.test.ts +313 -0
- package/src/lib/selection-routing.ts +180 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +82 -14
- package/src/lib/sfx-player.test.ts +266 -0
- package/src/lib/sfx-player.ts +262 -69
- package/src/lib/site-boundary.test.ts +53 -0
- package/src/lib/site-boundary.ts +27 -0
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +165 -0
- package/src/lib/snapping-mode.ts +191 -0
- package/src/lib/stair-duplication.test.ts +103 -0
- package/src/lib/stair-duplication.ts +58 -86
- package/src/lib/stair-levels.ts +2 -0
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/terrain-sculpt.test.ts +429 -0
- package/src/lib/terrain-sculpt.ts +301 -0
- package/src/lib/terrain-verb-color.test.ts +51 -0
- package/src/lib/terrain-verb-color.ts +58 -0
- package/src/lib/touch-gesture-priority.test.ts +45 -0
- package/src/lib/touch-gesture-priority.ts +30 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/window-interaction.ts +1 -1
- package/src/lib/world-grid-snap.ts +37 -5
- package/src/store/camera-pose-store.test.ts +29 -0
- package/src/store/camera-pose-store.ts +27 -0
- package/src/store/live-draft-preview-stores.test.ts +136 -0
- package/src/store/navigation-sync-pose-store.test.ts +34 -0
- package/src/store/navigation-sync-pose-store.ts +27 -0
- package/src/store/terrain-sculpt-mode.test.ts +223 -0
- package/src/store/use-delete-confirmation.ts +27 -0
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-drawing-view.test.ts +23 -0
- package/src/store/use-drawing-view.ts +84 -0
- package/src/store/use-editor.tsx +665 -126
- package/src/store/use-elevation-guides.ts +40 -0
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +51 -0
- package/src/store/use-first-person-hud.ts +40 -0
- package/src/store/use-floorplan-annotation-visibility.ts +60 -0
- package/src/store/use-floorplan-draft-preview.ts +151 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-floorplan-mode.ts +110 -0
- package/src/store/use-interaction-scope.test.ts +206 -0
- package/src/store/use-interaction-scope.ts +140 -0
- package/src/store/use-measurement-draft.test.ts +559 -0
- package/src/store/use-measurement-draft.ts +570 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-path-draft-preview.ts +90 -0
- package/src/store/use-placement-preview.ts +11 -3
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +53 -0
- package/src/store/use-wall-snap-indicator.ts +2 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/group-move-handle.tsx +0 -316
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/lib/floorplan/selection-tool.ts +0 -271
- package/src/lib/level-name.ts +0 -11
|
@@ -2,7 +2,12 @@ import {
|
|
|
2
2
|
AnyNode,
|
|
3
3
|
type AnyNodeId,
|
|
4
4
|
generateId,
|
|
5
|
+
generateSceneMaterialId,
|
|
5
6
|
type LevelNode,
|
|
7
|
+
nodeRegistry,
|
|
8
|
+
remapMeasurementReferences,
|
|
9
|
+
SceneMaterial,
|
|
10
|
+
type SceneMaterialId,
|
|
6
11
|
type StairNode,
|
|
7
12
|
useScene,
|
|
8
13
|
} from '@pascal-app/core'
|
|
@@ -10,18 +15,25 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
10
15
|
|
|
11
16
|
type ClipboardPayload = {
|
|
12
17
|
copiedAt: number
|
|
18
|
+
materials: SceneMaterial[]
|
|
13
19
|
nodes: AnyNode[]
|
|
14
20
|
rootIds: AnyNodeId[]
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
type PasteResult = {
|
|
23
|
+
export type PasteResult = {
|
|
24
|
+
createdMaterialIds: SceneMaterialId[]
|
|
18
25
|
pastedIds: AnyNodeId[]
|
|
19
26
|
skippedIds: AnyNodeId[]
|
|
20
27
|
}
|
|
21
28
|
|
|
29
|
+
const SYSTEM_CLIPBOARD_KIND = 'pascal.scene-nodes'
|
|
30
|
+
const SYSTEM_CLIPBOARD_VERSION = 1
|
|
31
|
+
|
|
22
32
|
const COPYABLE_ROOT_TYPES = new Set<AnyNode['type']>([
|
|
23
33
|
'wall',
|
|
24
34
|
'fence',
|
|
35
|
+
'door',
|
|
36
|
+
'window',
|
|
25
37
|
'column',
|
|
26
38
|
'item',
|
|
27
39
|
'slab',
|
|
@@ -30,9 +42,13 @@ const COPYABLE_ROOT_TYPES = new Set<AnyNode['type']>([
|
|
|
30
42
|
'stair',
|
|
31
43
|
'spawn',
|
|
32
44
|
'zone',
|
|
45
|
+
'cabinet',
|
|
46
|
+
'cabinet-module',
|
|
47
|
+
'measurement',
|
|
33
48
|
])
|
|
34
49
|
|
|
35
50
|
let clipboardPayload: ClipboardPayload | null = null
|
|
51
|
+
let pendingSystemClipboardWrite: Promise<boolean> | null = null
|
|
36
52
|
const subscribers = new Set<() => void>()
|
|
37
53
|
|
|
38
54
|
function notifySubscribers() {
|
|
@@ -93,10 +109,53 @@ function hasSelectedAncestor(
|
|
|
93
109
|
return false
|
|
94
110
|
}
|
|
95
111
|
|
|
96
|
-
function
|
|
112
|
+
function isClipboardRoot(
|
|
113
|
+
nodes: Record<AnyNodeId, AnyNode>,
|
|
114
|
+
node: AnyNode,
|
|
115
|
+
allowHostedOpening: boolean,
|
|
116
|
+
) {
|
|
97
117
|
const parentId = node.parentId as AnyNodeId | null
|
|
98
118
|
if (!parentId) return true
|
|
99
|
-
|
|
119
|
+
const parent = nodes[parentId]
|
|
120
|
+
if (parent?.type === 'level') return true
|
|
121
|
+
if (
|
|
122
|
+
allowHostedOpening &&
|
|
123
|
+
(node.type === 'door' || node.type === 'window') &&
|
|
124
|
+
(parent?.type === 'wall' || parent?.type === 'roof-segment')
|
|
125
|
+
) {
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
return parent?.type === 'building' && nodeRegistry.get(node.type)?.floorplanScope === 'building'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isCopyableRootType(node: AnyNode) {
|
|
132
|
+
if (COPYABLE_ROOT_TYPES.has(node.type)) return true
|
|
133
|
+
const definition = nodeRegistry.get(node.type)
|
|
134
|
+
return !!definition && definition.capabilities?.duplicable !== false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getPromotedCabinetRunId(
|
|
138
|
+
nodes: Record<AnyNodeId, AnyNode>,
|
|
139
|
+
node: AnyNode,
|
|
140
|
+
selectedIds: Set<AnyNodeId>,
|
|
141
|
+
) {
|
|
142
|
+
if (node.type !== 'cabinet-module') return null
|
|
143
|
+
|
|
144
|
+
const parentId = node.parentId as AnyNodeId | null
|
|
145
|
+
const parent = parentId ? nodes[parentId] : null
|
|
146
|
+
if (parent?.type !== 'cabinet') return null
|
|
147
|
+
if (!parentId) return null
|
|
148
|
+
if (selectedIds.has(parentId)) return parentId
|
|
149
|
+
|
|
150
|
+
const siblingIds = Array.isArray(parent.children) ? (parent.children as AnyNodeId[]) : []
|
|
151
|
+
const hasOnlySelectedModules =
|
|
152
|
+
siblingIds.length > 0 &&
|
|
153
|
+
siblingIds.every((childId) => {
|
|
154
|
+
const child = nodes[childId]
|
|
155
|
+
return child?.type === 'cabinet-module' && selectedIds.has(childId)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
return hasOnlySelectedModules ? parentId : null
|
|
100
159
|
}
|
|
101
160
|
|
|
102
161
|
function getPasteTargetLevel(targetLevelId?: AnyNodeId) {
|
|
@@ -114,7 +173,7 @@ function getNextLevelId(level: LevelNode, nodes: Record<AnyNodeId, AnyNode>) {
|
|
|
114
173
|
if (!parentId) return null
|
|
115
174
|
|
|
116
175
|
const building = nodes[parentId]
|
|
117
|
-
if (
|
|
176
|
+
if (building?.type !== 'building') return null
|
|
118
177
|
|
|
119
178
|
const siblingLevels = building.children
|
|
120
179
|
.map((childId) => nodes[childId as AnyNodeId])
|
|
@@ -132,14 +191,28 @@ function remapNodeReferences(
|
|
|
132
191
|
oldId: AnyNodeId,
|
|
133
192
|
targetLevel: LevelNode,
|
|
134
193
|
idMap: Map<AnyNodeId, AnyNodeId>,
|
|
194
|
+
materialIdMap: Map<SceneMaterialId, SceneMaterialId>,
|
|
135
195
|
rootIds: Set<AnyNodeId>,
|
|
136
196
|
nodes: Record<AnyNodeId, AnyNode>,
|
|
137
197
|
) {
|
|
138
|
-
const clone =
|
|
198
|
+
const clone = remapSceneMaterialReferences(
|
|
199
|
+
JSON.parse(JSON.stringify(node)),
|
|
200
|
+
materialIdMap,
|
|
201
|
+
) as AnyNode
|
|
139
202
|
;(clone as Record<string, unknown>).id = idMap.get(oldId)
|
|
140
203
|
|
|
141
204
|
if (rootIds.has(oldId)) {
|
|
142
|
-
|
|
205
|
+
const buildingId = targetLevel.parentId as AnyNodeId | null
|
|
206
|
+
clone.parentId =
|
|
207
|
+
nodeRegistry.get(node.type)?.floorplanScope === 'building' &&
|
|
208
|
+
buildingId &&
|
|
209
|
+
nodes[buildingId]?.type === 'building'
|
|
210
|
+
? buildingId
|
|
211
|
+
: targetLevel.id
|
|
212
|
+
if (clone.type === 'door' || clone.type === 'window') {
|
|
213
|
+
delete clone.roofSegmentId
|
|
214
|
+
delete clone.roofFace
|
|
215
|
+
}
|
|
143
216
|
} else if (clone.parentId && typeof clone.parentId === 'string') {
|
|
144
217
|
clone.parentId = idMap.get(clone.parentId as AnyNodeId) ?? clone.parentId
|
|
145
218
|
}
|
|
@@ -165,6 +238,10 @@ function remapNodeReferences(
|
|
|
165
238
|
;(clone as StairNode).toLevelId = nextLevelId
|
|
166
239
|
}
|
|
167
240
|
|
|
241
|
+
if (clone.type === 'measurement') {
|
|
242
|
+
clone.measurement = remapMeasurementReferences(clone.measurement, idMap)
|
|
243
|
+
}
|
|
244
|
+
|
|
168
245
|
const metadata =
|
|
169
246
|
clone.metadata && typeof clone.metadata === 'object' && !Array.isArray(clone.metadata)
|
|
170
247
|
? { ...(clone.metadata as Record<string, unknown>) }
|
|
@@ -176,22 +253,63 @@ function remapNodeReferences(
|
|
|
176
253
|
return AnyNode.parse(clone)
|
|
177
254
|
}
|
|
178
255
|
|
|
179
|
-
|
|
256
|
+
function remapSceneMaterialReferences(
|
|
257
|
+
value: unknown,
|
|
258
|
+
materialIdMap: Map<SceneMaterialId, SceneMaterialId>,
|
|
259
|
+
): unknown {
|
|
260
|
+
if (typeof value === 'string' && value.startsWith('scene:')) {
|
|
261
|
+
const oldId = value.slice('scene:'.length) as SceneMaterialId
|
|
262
|
+
const nextId = materialIdMap.get(oldId)
|
|
263
|
+
return nextId ? `scene:${nextId}` : value
|
|
264
|
+
}
|
|
265
|
+
if (Array.isArray(value)) {
|
|
266
|
+
return value.map((entry) => remapSceneMaterialReferences(entry, materialIdMap))
|
|
267
|
+
}
|
|
268
|
+
if (value && typeof value === 'object') {
|
|
269
|
+
return Object.fromEntries(
|
|
270
|
+
Object.entries(value).map(([key, entry]) => [
|
|
271
|
+
key,
|
|
272
|
+
remapSceneMaterialReferences(entry, materialIdMap),
|
|
273
|
+
]),
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
return value
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function collectReferencedSceneMaterialIds(value: unknown, ids: Set<SceneMaterialId>) {
|
|
280
|
+
if (typeof value === 'string' && value.startsWith('scene:')) {
|
|
281
|
+
ids.add(value.slice('scene:'.length) as SceneMaterialId)
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
if (Array.isArray(value)) {
|
|
285
|
+
for (const entry of value) collectReferencedSceneMaterialIds(entry, ids)
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
if (value && typeof value === 'object') {
|
|
289
|
+
for (const entry of Object.values(value)) collectReferencedSceneMaterialIds(entry, ids)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function buildClipboardPayload(ids: AnyNodeId[]): ClipboardPayload | null {
|
|
180
294
|
const scene = useScene.getState()
|
|
181
|
-
const ids = selectedIds ?? (useViewer.getState().selection.selectedIds as AnyNodeId[])
|
|
182
295
|
const selectedIdSet = new Set(ids)
|
|
183
|
-
const
|
|
296
|
+
const allowHostedOpening = ids.length === 1
|
|
297
|
+
const promotedIds = ids.map((id) => {
|
|
298
|
+
const node = scene.nodes[id]
|
|
299
|
+
return node ? (getPromotedCabinetRunId(scene.nodes, node, selectedIdSet) ?? id) : id
|
|
300
|
+
})
|
|
301
|
+
const rootIds = Array.from(new Set(promotedIds)).filter((id) => {
|
|
184
302
|
const node = scene.nodes[id]
|
|
185
303
|
return (
|
|
186
304
|
node &&
|
|
187
|
-
|
|
188
|
-
|
|
305
|
+
isCopyableRootType(node) &&
|
|
306
|
+
isClipboardRoot(scene.nodes, node, allowHostedOpening) &&
|
|
189
307
|
!hasSelectedAncestor(scene.nodes, id, selectedIdSet)
|
|
190
308
|
)
|
|
191
309
|
})
|
|
192
310
|
|
|
193
311
|
if (rootIds.length === 0) {
|
|
194
|
-
return
|
|
312
|
+
return null
|
|
195
313
|
}
|
|
196
314
|
|
|
197
315
|
const subtreeIds = new Set<AnyNodeId>()
|
|
@@ -199,30 +317,189 @@ export function copySelectedNodesToEditorClipboard(selectedIds?: AnyNodeId[]) {
|
|
|
199
317
|
collectSubtreeIds(scene.nodes, rootId, subtreeIds)
|
|
200
318
|
}
|
|
201
319
|
|
|
202
|
-
|
|
320
|
+
const copiedNodes = [...subtreeIds]
|
|
321
|
+
.map((id) => scene.nodes[id])
|
|
322
|
+
.filter((node): node is AnyNode => !!node)
|
|
323
|
+
.map((node) => JSON.parse(JSON.stringify(node)) as AnyNode)
|
|
324
|
+
const materialIds = new Set<SceneMaterialId>()
|
|
325
|
+
collectReferencedSceneMaterialIds(copiedNodes, materialIds)
|
|
326
|
+
|
|
327
|
+
return {
|
|
203
328
|
copiedAt: Date.now(),
|
|
204
|
-
|
|
205
|
-
.map((id) => scene.
|
|
206
|
-
.filter((
|
|
207
|
-
.map((
|
|
329
|
+
materials: [...materialIds]
|
|
330
|
+
.map((id) => scene.materials[id])
|
|
331
|
+
.filter((material): material is SceneMaterial => !!material)
|
|
332
|
+
.map((material) => JSON.parse(JSON.stringify(material)) as SceneMaterial),
|
|
333
|
+
nodes: copiedNodes,
|
|
208
334
|
rootIds,
|
|
209
335
|
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function serializeClipboardPayload(payload: ClipboardPayload) {
|
|
339
|
+
return JSON.stringify({
|
|
340
|
+
kind: SYSTEM_CLIPBOARD_KIND,
|
|
341
|
+
version: SYSTEM_CLIPBOARD_VERSION,
|
|
342
|
+
payload,
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function parseClipboardPayload(text: string): ClipboardPayload | null {
|
|
347
|
+
try {
|
|
348
|
+
const envelope = JSON.parse(text) as {
|
|
349
|
+
kind?: unknown
|
|
350
|
+
payload?: unknown
|
|
351
|
+
version?: unknown
|
|
352
|
+
}
|
|
353
|
+
if (
|
|
354
|
+
envelope.kind !== SYSTEM_CLIPBOARD_KIND ||
|
|
355
|
+
envelope.version !== SYSTEM_CLIPBOARD_VERSION ||
|
|
356
|
+
!envelope.payload ||
|
|
357
|
+
typeof envelope.payload !== 'object'
|
|
358
|
+
) {
|
|
359
|
+
return null
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const candidate = envelope.payload as {
|
|
363
|
+
copiedAt?: unknown
|
|
364
|
+
materials?: unknown
|
|
365
|
+
nodes?: unknown
|
|
366
|
+
rootIds?: unknown
|
|
367
|
+
}
|
|
368
|
+
if (
|
|
369
|
+
typeof candidate.copiedAt !== 'number' ||
|
|
370
|
+
!Array.isArray(candidate.nodes) ||
|
|
371
|
+
!Array.isArray(candidate.rootIds) ||
|
|
372
|
+
!candidate.rootIds.every((id) => typeof id === 'string')
|
|
373
|
+
) {
|
|
374
|
+
return null
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const nodes = candidate.nodes.map((node) => AnyNode.safeParse(node))
|
|
378
|
+
if (nodes.some((result) => !result.success)) return null
|
|
379
|
+
const materials = Array.isArray(candidate.materials)
|
|
380
|
+
? candidate.materials.map((material) => SceneMaterial.safeParse(material))
|
|
381
|
+
: []
|
|
382
|
+
if (materials.some((result) => !result.success)) return null
|
|
383
|
+
|
|
384
|
+
const parsedNodes = nodes.filter((result) => result.success).map((result) => result.data)
|
|
385
|
+
const nodeIds = new Set(parsedNodes.map((node) => node.id))
|
|
386
|
+
const rootIds = candidate.rootIds as AnyNodeId[]
|
|
387
|
+
if (rootIds.length === 0 || rootIds.some((id) => !nodeIds.has(id))) return null
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
copiedAt: candidate.copiedAt,
|
|
391
|
+
materials: materials.filter((result) => result.success).map((result) => result.data),
|
|
392
|
+
nodes: parsedNodes,
|
|
393
|
+
rootIds,
|
|
394
|
+
}
|
|
395
|
+
} catch {
|
|
396
|
+
return null
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function writeSystemClipboard(payload: ClipboardPayload) {
|
|
401
|
+
if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {
|
|
402
|
+
pendingSystemClipboardWrite = null
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
pendingSystemClipboardWrite = navigator.clipboard
|
|
406
|
+
.writeText(serializeClipboardPayload(payload))
|
|
407
|
+
.then(
|
|
408
|
+
() => true,
|
|
409
|
+
() => false,
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function copySelectedNodesToEditorClipboard(selectedIds?: AnyNodeId[]) {
|
|
414
|
+
const ids = selectedIds ?? (useViewer.getState().selection.selectedIds as AnyNodeId[])
|
|
415
|
+
const payload = buildClipboardPayload(ids)
|
|
416
|
+
if (!payload) return false
|
|
417
|
+
|
|
418
|
+
clipboardPayload = payload
|
|
210
419
|
notifySubscribers()
|
|
420
|
+
writeSystemClipboard(payload)
|
|
211
421
|
|
|
212
422
|
return true
|
|
213
423
|
}
|
|
214
424
|
|
|
425
|
+
export async function readEditorClipboardFromSystem() {
|
|
426
|
+
const pendingWrite = pendingSystemClipboardWrite
|
|
427
|
+
pendingSystemClipboardWrite = null
|
|
428
|
+
if (pendingWrite && !(await pendingWrite)) {
|
|
429
|
+
return hasEditorClipboard()
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (typeof navigator === 'undefined' || !navigator.clipboard?.readText) {
|
|
433
|
+
return hasEditorClipboard()
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
let text: string
|
|
437
|
+
try {
|
|
438
|
+
text = await navigator.clipboard.readText()
|
|
439
|
+
} catch {
|
|
440
|
+
return hasEditorClipboard()
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const payload = parseClipboardPayload(text)
|
|
444
|
+
if (!payload) return false
|
|
445
|
+
clipboardPayload = payload
|
|
446
|
+
notifySubscribers()
|
|
447
|
+
return true
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Clone the given nodes (subtrees included, ids remapped) onto the target /
|
|
452
|
+
* active level in place — the same copy + paste pipeline in one step, WITHOUT
|
|
453
|
+
* touching the user's editor clipboard. Selects the clones. Used by the group
|
|
454
|
+
* action menu's Duplicate.
|
|
455
|
+
*/
|
|
456
|
+
export function duplicateNodesToLevel(
|
|
457
|
+
ids: AnyNodeId[],
|
|
458
|
+
targetLevelId?: AnyNodeId,
|
|
459
|
+
): PasteResult | null {
|
|
460
|
+
const payload = buildClipboardPayload(ids)
|
|
461
|
+
if (!payload) return null
|
|
462
|
+
return applyClipboardPayloadToLevel(payload, targetLevelId)
|
|
463
|
+
}
|
|
464
|
+
|
|
215
465
|
export function pasteEditorClipboardToLevel(targetLevelId?: AnyNodeId): PasteResult | null {
|
|
216
|
-
|
|
466
|
+
if (!clipboardPayload) return null
|
|
467
|
+
return applyClipboardPayloadToLevel(clipboardPayload, targetLevelId)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export async function pasteSystemEditorClipboardToLevel(
|
|
471
|
+
targetLevelId?: AnyNodeId,
|
|
472
|
+
): Promise<PasteResult | null> {
|
|
473
|
+
if (!(await readEditorClipboardFromSystem())) return null
|
|
474
|
+
return pasteEditorClipboardToLevel(targetLevelId)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function applyClipboardPayloadToLevel(
|
|
478
|
+
payload: ClipboardPayload,
|
|
479
|
+
targetLevelId?: AnyNodeId,
|
|
480
|
+
): PasteResult | null {
|
|
217
481
|
const targetLevel = getPasteTargetLevel(targetLevelId)
|
|
218
|
-
if (!
|
|
482
|
+
if (!targetLevel) return null
|
|
219
483
|
|
|
220
484
|
const scene = useScene.getState()
|
|
221
485
|
const idMap = new Map<AnyNodeId, AnyNodeId>()
|
|
486
|
+
const materialIdMap = new Map<SceneMaterialId, SceneMaterialId>()
|
|
487
|
+
const materialsToCreate: SceneMaterial[] = []
|
|
222
488
|
|
|
223
489
|
for (const node of payload.nodes) {
|
|
224
490
|
idMap.set(node.id as AnyNodeId, generateId(extractIdPrefix(node.id)) as AnyNodeId)
|
|
225
491
|
}
|
|
492
|
+
for (const material of payload.materials) {
|
|
493
|
+
const oldId = material.id as SceneMaterialId
|
|
494
|
+
const existing = scene.materials[oldId]
|
|
495
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(material)) {
|
|
496
|
+
materialIdMap.set(oldId, oldId)
|
|
497
|
+
continue
|
|
498
|
+
}
|
|
499
|
+
const nextId = existing ? generateSceneMaterialId() : oldId
|
|
500
|
+
materialIdMap.set(oldId, nextId)
|
|
501
|
+
materialsToCreate.push({ ...material, id: nextId })
|
|
502
|
+
}
|
|
226
503
|
|
|
227
504
|
const rootIdSet = new Set(payload.rootIds)
|
|
228
505
|
const pastedNodes: AnyNode[] = []
|
|
@@ -231,7 +508,15 @@ export function pasteEditorClipboardToLevel(targetLevelId?: AnyNodeId): PasteRes
|
|
|
231
508
|
for (const node of payload.nodes) {
|
|
232
509
|
try {
|
|
233
510
|
pastedNodes.push(
|
|
234
|
-
remapNodeReferences(
|
|
511
|
+
remapNodeReferences(
|
|
512
|
+
node,
|
|
513
|
+
node.id as AnyNodeId,
|
|
514
|
+
targetLevel,
|
|
515
|
+
idMap,
|
|
516
|
+
materialIdMap,
|
|
517
|
+
rootIdSet,
|
|
518
|
+
scene.nodes,
|
|
519
|
+
),
|
|
235
520
|
)
|
|
236
521
|
} catch (error) {
|
|
237
522
|
console.error('Failed to paste copied node', node.id, error)
|
|
@@ -240,9 +525,12 @@ export function pasteEditorClipboardToLevel(targetLevelId?: AnyNodeId): PasteRes
|
|
|
240
525
|
}
|
|
241
526
|
|
|
242
527
|
if (pastedNodes.length === 0) {
|
|
243
|
-
return { pastedIds: [], skippedIds }
|
|
528
|
+
return { createdMaterialIds: [], pastedIds: [], skippedIds }
|
|
244
529
|
}
|
|
245
530
|
|
|
531
|
+
for (const material of materialsToCreate) {
|
|
532
|
+
scene.addSceneMaterial(material)
|
|
533
|
+
}
|
|
246
534
|
scene.createNodes(
|
|
247
535
|
pastedNodes.map((node) => ({
|
|
248
536
|
node,
|
|
@@ -261,6 +549,7 @@ export function pasteEditorClipboardToLevel(targetLevelId?: AnyNodeId): PasteRes
|
|
|
261
549
|
})
|
|
262
550
|
|
|
263
551
|
return {
|
|
552
|
+
createdMaterialIds: materialsToCreate.map((material) => material.id as SceneMaterialId),
|
|
264
553
|
pastedIds: pastedRootIds,
|
|
265
554
|
skippedIds,
|
|
266
555
|
}
|
package/src/lib/scene.ts
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
clearSceneHistory,
|
|
5
|
+
nodeRegistry,
|
|
6
|
+
resolveLevelId,
|
|
7
|
+
sceneRegistry,
|
|
8
|
+
useScene,
|
|
9
|
+
} from '@pascal-app/core'
|
|
4
10
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
11
|
import useEditor, {
|
|
6
12
|
hasCustomPersistedEditorUiState,
|
|
7
13
|
normalizePersistedEditorUiState,
|
|
8
14
|
type PersistedEditorUiState,
|
|
9
15
|
} from '../store/use-editor'
|
|
16
|
+
import { editorHostPanelRegistry } from './plugin-panels'
|
|
10
17
|
|
|
11
18
|
export type SceneGraph = {
|
|
12
19
|
nodes: Record<string, unknown>
|
|
13
20
|
rootNodeIds: string[]
|
|
21
|
+
// Document-level scene state that travels with the graph. Optional so older
|
|
22
|
+
// payloads (and callers that only build nodes) stay valid.
|
|
23
|
+
collections?: Record<string, unknown>
|
|
24
|
+
materials?: Record<string, unknown>
|
|
25
|
+
installedPlugins?: string[]
|
|
14
26
|
}
|
|
15
27
|
|
|
16
28
|
type PersistedSelectionPath = {
|
|
@@ -355,10 +367,8 @@ function resetEditorInteractionState() {
|
|
|
355
367
|
structureLayer: 'elements',
|
|
356
368
|
catalogCategory: null,
|
|
357
369
|
selectedItem: null,
|
|
358
|
-
movingNode: null,
|
|
359
370
|
selectedReferenceId: null,
|
|
360
371
|
spaces: {},
|
|
361
|
-
editingHole: null,
|
|
362
372
|
hoveredHole: null,
|
|
363
373
|
isPreviewMode: false,
|
|
364
374
|
})
|
|
@@ -373,13 +383,26 @@ function hasUsableSceneGraph(sceneGraph?: SceneGraph | null): sceneGraph is Scen
|
|
|
373
383
|
}
|
|
374
384
|
|
|
375
385
|
export function applySceneGraphToEditor(sceneGraph?: SceneGraph | null) {
|
|
386
|
+
const defaultInstalledPlugins = editorHostPanelRegistry.getDefaultInstalledPluginIds()
|
|
376
387
|
if (hasUsableSceneGraph(sceneGraph)) {
|
|
377
|
-
const { nodes, rootNodeIds } = sceneGraph
|
|
378
|
-
useScene.getState().setScene(nodes as any, rootNodeIds as any
|
|
388
|
+
const { nodes, rootNodeIds, collections, materials, installedPlugins } = sceneGraph
|
|
389
|
+
useScene.getState().setScene(nodes as any, rootNodeIds as any, {
|
|
390
|
+
collections: collections as any,
|
|
391
|
+
materials: materials as any,
|
|
392
|
+
installedPlugins: installedPlugins ?? defaultInstalledPlugins,
|
|
393
|
+
hasExplicitPluginInstallState: installedPlugins !== undefined,
|
|
394
|
+
})
|
|
379
395
|
} else {
|
|
380
396
|
useScene.getState().clearScene()
|
|
397
|
+
useScene.getState().setInstalledPlugins(defaultInstalledPlugins, { explicit: false })
|
|
381
398
|
}
|
|
382
399
|
|
|
400
|
+
// The loaded scene is the undo floor. Loading records history entries of
|
|
401
|
+
// its own (`unloadScene` + `setScene`/`clearScene` are tracked writes), so
|
|
402
|
+
// without this reset a few Ctrl+Z presses could step past the load into the
|
|
403
|
+
// pre-load — often empty — state and wipe the whole project.
|
|
404
|
+
clearSceneHistory()
|
|
405
|
+
|
|
383
406
|
syncEditorSelectionFromCurrentScene()
|
|
384
407
|
}
|
|
385
408
|
|