@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
|
@@ -9,13 +9,14 @@ import {
|
|
|
9
9
|
movingAlignmentAnchors,
|
|
10
10
|
type NodeEvent,
|
|
11
11
|
resolveAlignment,
|
|
12
|
+
resolveSupportSlabPatch,
|
|
12
13
|
StairNode,
|
|
13
14
|
StairSegmentNode,
|
|
14
15
|
syncAutoStairOpenings,
|
|
15
16
|
useScene,
|
|
16
17
|
} from '@pascal-app/core'
|
|
17
|
-
import { useAlignmentGuides } from '@pascal-app/editor'
|
|
18
18
|
import { useViewer } from '@pascal-app/viewer'
|
|
19
|
+
import { useThree } from '@react-three/fiber'
|
|
19
20
|
import { useEffect, useMemo, useRef } from 'react'
|
|
20
21
|
import * as THREE from 'three'
|
|
21
22
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
@@ -23,8 +24,20 @@ import {
|
|
|
23
24
|
resolveStairDestinationLevel,
|
|
24
25
|
resolveStairPlacementLevelId,
|
|
25
26
|
} from '../../../lib/stair-levels'
|
|
27
|
+
|
|
28
|
+
import useAlignmentGuides from '../../../store/use-alignment-guides'
|
|
29
|
+
import useEditor, {
|
|
30
|
+
isAlignmentGuideActive,
|
|
31
|
+
isGridSnapActive,
|
|
32
|
+
isMagneticSnapActive,
|
|
33
|
+
} from '../../../store/use-editor'
|
|
34
|
+
|
|
35
|
+
import useFacingPose from '../../../store/use-facing-pose'
|
|
36
|
+
import { useStairBuildPreview } from '../../../store/use-stair-build-preview'
|
|
26
37
|
import { CursorSphere } from '../shared/cursor-sphere'
|
|
27
38
|
import { getFloorStackPreviewPosition } from '../shared/floor-stack-preview'
|
|
39
|
+
import { resolvePointerSupportSurface } from '../shared/pointer-support-cap'
|
|
40
|
+
import { createStairCommitGate, swallowFollowUpBrowserClick } from './stair-click-guard'
|
|
28
41
|
import {
|
|
29
42
|
DEFAULT_CURVED_STAIR_INNER_RADIUS,
|
|
30
43
|
DEFAULT_CURVED_STAIR_SWEEP_ANGLE,
|
|
@@ -36,6 +49,7 @@ import {
|
|
|
36
49
|
DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
37
50
|
DEFAULT_STAIR_HEIGHT,
|
|
38
51
|
DEFAULT_STAIR_LENGTH,
|
|
52
|
+
DEFAULT_STAIR_OPENING_OFFSET,
|
|
39
53
|
DEFAULT_STAIR_RAILING_HEIGHT,
|
|
40
54
|
DEFAULT_STAIR_RAILING_MODE,
|
|
41
55
|
DEFAULT_STAIR_STEP_COUNT,
|
|
@@ -48,6 +62,7 @@ const GRID_OFFSET = 0.02
|
|
|
48
62
|
/** Figma-style alignment-snap threshold (meters), matching the move tools. */
|
|
49
63
|
const ALIGNMENT_THRESHOLD_M = 0.08
|
|
50
64
|
type ClickTriggerEvent = GridEvent | NodeEvent<AnyNode>
|
|
65
|
+
type MoveTriggerEvent = GridEvent | NodeEvent<AnyNode>
|
|
51
66
|
|
|
52
67
|
const CLICK_TRIGGER_KINDS = [
|
|
53
68
|
'shelf',
|
|
@@ -138,9 +153,8 @@ function createDefaultStairNode({
|
|
|
138
153
|
fromLevelId: levelId,
|
|
139
154
|
toLevelId: nextLevelId,
|
|
140
155
|
slabOpeningMode: 'destination',
|
|
141
|
-
openingOffset:
|
|
156
|
+
openingOffset: DEFAULT_STAIR_OPENING_OFFSET,
|
|
142
157
|
width: DEFAULT_STAIR_WIDTH,
|
|
143
|
-
totalRise: DEFAULT_STAIR_HEIGHT,
|
|
144
158
|
stepCount: DEFAULT_STAIR_STEP_COUNT,
|
|
145
159
|
thickness: DEFAULT_STAIR_THICKNESS,
|
|
146
160
|
fillToFloor: DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
@@ -163,6 +177,7 @@ function commitStairPlacement(
|
|
|
163
177
|
levelId: LevelNode['id'],
|
|
164
178
|
position: [number, number, number],
|
|
165
179
|
rotation: number,
|
|
180
|
+
supportElevationCap: number | null,
|
|
166
181
|
): void {
|
|
167
182
|
const { createNodes, nodes } = useScene.getState()
|
|
168
183
|
const placementLevelId = resolveStairPlacementLevelId(
|
|
@@ -183,13 +198,27 @@ function commitStairPlacement(
|
|
|
183
198
|
})
|
|
184
199
|
const nextLevelId = destinationPlan?.toLevel.id ?? placementLevelId
|
|
185
200
|
|
|
186
|
-
const stair =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
201
|
+
const stair = StairNode.parse({
|
|
202
|
+
...createDefaultStairNode({
|
|
203
|
+
name,
|
|
204
|
+
levelId: placementLevelId,
|
|
205
|
+
nextLevelId,
|
|
206
|
+
position,
|
|
207
|
+
rotation,
|
|
208
|
+
segmentId: segment.id,
|
|
209
|
+
}),
|
|
210
|
+
parentId: placementLevelId,
|
|
211
|
+
})
|
|
212
|
+
const prospectiveNodes = {
|
|
213
|
+
...nodes,
|
|
214
|
+
[stair.id]: stair,
|
|
215
|
+
[segment.id]: { ...segment, parentId: stair.id },
|
|
216
|
+
} as Record<string, AnyNode>
|
|
217
|
+
const committedStair = StairNode.parse({
|
|
218
|
+
...stair,
|
|
219
|
+
...resolveSupportSlabPatch(stair, prospectiveNodes, {
|
|
220
|
+
maxElevation: supportElevationCap,
|
|
221
|
+
}),
|
|
193
222
|
})
|
|
194
223
|
|
|
195
224
|
const createdLevel = destinationPlan?.createdLevel
|
|
@@ -200,17 +229,21 @@ function commitStairPlacement(
|
|
|
200
229
|
|
|
201
230
|
createNodes([
|
|
202
231
|
...levelCreateOps,
|
|
203
|
-
{ node:
|
|
204
|
-
{ node: segment, parentId:
|
|
232
|
+
{ node: committedStair, parentId: placementLevelId },
|
|
233
|
+
{ node: segment, parentId: committedStair.id },
|
|
205
234
|
])
|
|
206
235
|
|
|
207
236
|
sfxEmitter.emit('sfx:structure-build')
|
|
208
237
|
}
|
|
209
238
|
|
|
210
239
|
export const StairTool: React.FC = () => {
|
|
240
|
+
const camera = useThree((state) => state.camera)
|
|
241
|
+
const cameraRef = useRef(camera)
|
|
242
|
+
cameraRef.current = camera
|
|
211
243
|
const cursorRef = useRef<THREE.Group>(null)
|
|
212
244
|
const previewRef = useRef<THREE.Group>(null)
|
|
213
245
|
const rotationRef = useRef(0)
|
|
246
|
+
const supportCapRef = useRef<number | null>(null)
|
|
214
247
|
const previousGridPosRef = useRef<[number, number] | null>(null)
|
|
215
248
|
const lastCanonicalPositionRef = useRef<[number, number, number] | null>(null)
|
|
216
249
|
const currentLevelId = useViewer((state) => state.selection.levelId)
|
|
@@ -221,11 +254,16 @@ export const StairTool: React.FC = () => {
|
|
|
221
254
|
if (!currentLevelId) return
|
|
222
255
|
|
|
223
256
|
const openingPreview = createSurfaceOpeningPreviewController()
|
|
257
|
+
// Refuses the duplicate commit triggers a single physical click produces
|
|
258
|
+
// — see `stair-click-guard.ts`. Fresh per armed session.
|
|
259
|
+
const commitGate = createStairCommitGate()
|
|
224
260
|
|
|
225
261
|
// Reset rotation when tool activates
|
|
226
262
|
rotationRef.current = 0
|
|
263
|
+
useStairBuildPreview.getState().reset()
|
|
227
264
|
if (previewRef.current) previewRef.current.rotation.y = 0
|
|
228
265
|
lastCanonicalPositionRef.current = null
|
|
266
|
+
supportCapRef.current = null
|
|
229
267
|
|
|
230
268
|
const buildPreviewScene = (position: [number, number, number], rotation: number) => {
|
|
231
269
|
const nodes = useScene.getState().nodes
|
|
@@ -263,7 +301,24 @@ export const StairTool: React.FC = () => {
|
|
|
263
301
|
return { placementLevelId, previewNodes, stair }
|
|
264
302
|
}
|
|
265
303
|
|
|
266
|
-
|
|
304
|
+
// The preview rebuild (full-scene copy + destination-level resolution +
|
|
305
|
+
// auto-opening CSG) is expensive; `grid:move` fires it every pointer event
|
|
306
|
+
// but the placed position is grid-snapped, so within a cell every rebuild
|
|
307
|
+
// is identical. Dedupe on the snapped position + rotation so we rebuild
|
|
308
|
+
// only when the staircase would actually land somewhere new — this is the
|
|
309
|
+
// difference between a smooth and a stuttering stair tool (the elevator is
|
|
310
|
+
// cheap because it has no opening sync).
|
|
311
|
+
let lastPreviewKey: string | null = null
|
|
312
|
+
|
|
313
|
+
const applyDraftPreview = (
|
|
314
|
+
position: [number, number, number],
|
|
315
|
+
rotation: number,
|
|
316
|
+
supportElevationCap: number | null,
|
|
317
|
+
) => {
|
|
318
|
+
const key = `${position[0].toFixed(3)},${position[2].toFixed(3)},${rotation.toFixed(4)},${supportElevationCap?.toFixed(3) ?? 'none'}`
|
|
319
|
+
if (key === lastPreviewKey) return
|
|
320
|
+
lastPreviewKey = key
|
|
321
|
+
useStairBuildPreview.getState().setPreview([position[0], position[2]], rotation)
|
|
267
322
|
const preview = buildPreviewScene(position, rotation)
|
|
268
323
|
const visualPosition = preview
|
|
269
324
|
? getFloorStackPreviewPosition({
|
|
@@ -272,6 +327,7 @@ export const StairTool: React.FC = () => {
|
|
|
272
327
|
rotation,
|
|
273
328
|
levelId: preview.placementLevelId,
|
|
274
329
|
nodes: preview.previewNodes,
|
|
330
|
+
maxElevation: supportElevationCap,
|
|
275
331
|
})
|
|
276
332
|
: position
|
|
277
333
|
if (cursorRef.current) {
|
|
@@ -287,6 +343,19 @@ export const StairTool: React.FC = () => {
|
|
|
287
343
|
previewRef.current.rotation.y = rotation
|
|
288
344
|
}
|
|
289
345
|
|
|
346
|
+
// Forward-facing triangle (editor-side overlay). The run ascends along
|
|
347
|
+
// local +Z from the entry at z≈0; the stair's front is the -Z entry side,
|
|
348
|
+
// so `reversed` points the triangle out of the entry (where you approach
|
|
349
|
+
// from), sitting just before it — not inside the footprint or at the
|
|
350
|
+
// elevated far end. Centre is the footprint mid-run (origin is the entry).
|
|
351
|
+
useFacingPose.getState().set({
|
|
352
|
+
position: visualPosition,
|
|
353
|
+
rotationY: rotation,
|
|
354
|
+
depth: DEFAULT_STAIR_LENGTH,
|
|
355
|
+
center: [0, DEFAULT_STAIR_LENGTH / 2],
|
|
356
|
+
reversed: true,
|
|
357
|
+
})
|
|
358
|
+
|
|
290
359
|
if (!preview) {
|
|
291
360
|
openingPreview.clear()
|
|
292
361
|
return
|
|
@@ -319,13 +388,16 @@ export const StairTool: React.FC = () => {
|
|
|
319
388
|
// The probe is the RAW cursor, not the grid-snapped point: resolving
|
|
320
389
|
// against the grid point would only catch anchors that happen to sit near
|
|
321
390
|
// a grid line. Matched axes use the raw probe + snap delta; unmatched axes
|
|
322
|
-
// keep the normal grid snap.
|
|
391
|
+
// keep the normal grid snap. Guides are published in every snapping mode
|
|
392
|
+
// (including Off); the magnetic pull toward them (applySnap) applies only in
|
|
393
|
+
// 'lines' mode.
|
|
323
394
|
const alignPoint = (
|
|
324
395
|
gridX: number,
|
|
325
396
|
gridZ: number,
|
|
326
397
|
rawX: number,
|
|
327
398
|
rawZ: number,
|
|
328
399
|
bypass: boolean,
|
|
400
|
+
applySnap: boolean,
|
|
329
401
|
): [number, number] => {
|
|
330
402
|
if (bypass || alignmentCandidates.length === 0) {
|
|
331
403
|
useAlignmentGuides.getState().clear()
|
|
@@ -336,6 +408,10 @@ export const StairTool: React.FC = () => {
|
|
|
336
408
|
useAlignmentGuides.getState().clear()
|
|
337
409
|
return [gridX, gridZ]
|
|
338
410
|
}
|
|
411
|
+
if (!applySnap) {
|
|
412
|
+
useAlignmentGuides.getState().set(ar.guides)
|
|
413
|
+
return [gridX, gridZ]
|
|
414
|
+
}
|
|
339
415
|
let x = gridX
|
|
340
416
|
let z = gridZ
|
|
341
417
|
if (ar.snap) {
|
|
@@ -347,19 +423,37 @@ export const StairTool: React.FC = () => {
|
|
|
347
423
|
return [x, z]
|
|
348
424
|
}
|
|
349
425
|
|
|
350
|
-
const
|
|
426
|
+
const resolveStairPosition = (event: MoveTriggerEvent): [number, number, number] | null => {
|
|
427
|
+
const pointed = resolvePointerSupportSurface(cameraRef.current, event.position)
|
|
428
|
+
supportCapRef.current = pointed?.elevation ?? null
|
|
429
|
+
const fallbackPosition =
|
|
430
|
+
'node' in event ? lastCanonicalPositionRef.current : event.localPosition
|
|
431
|
+
if (!pointed?.localPoint && !fallbackPosition) return null
|
|
432
|
+
const rawX = pointed?.localPoint?.[0] ?? fallbackPosition![0]
|
|
433
|
+
const rawZ = pointed?.localPoint?.[2] ?? fallbackPosition![2]
|
|
434
|
+
// Grid snap follows the global mode (live step so the HUD chip is
|
|
435
|
+
// honest); Off keeps the raw cursor. Shift cycles the mode centrally.
|
|
436
|
+
const step = useEditor.getState().gridSnapStep
|
|
351
437
|
const [gridX, gridZ] = alignPoint(
|
|
352
|
-
Math.round(
|
|
353
|
-
Math.round(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
438
|
+
isGridSnapActive() ? Math.round(rawX / step) * step : rawX,
|
|
439
|
+
isGridSnapActive() ? Math.round(rawZ / step) * step : rawZ,
|
|
440
|
+
rawX,
|
|
441
|
+
rawZ,
|
|
442
|
+
!isAlignmentGuideActive(),
|
|
443
|
+
isMagneticSnapActive(),
|
|
357
444
|
)
|
|
358
|
-
|
|
445
|
+
return [gridX, 0, gridZ]
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const onPointerMove = (event: MoveTriggerEvent) => {
|
|
449
|
+
const position = resolveStairPosition(event)
|
|
450
|
+
if (!position) return
|
|
451
|
+
const [gridX, , gridZ] = position
|
|
359
452
|
lastCanonicalPositionRef.current = position
|
|
360
|
-
applyDraftPreview(position, rotationRef.current)
|
|
453
|
+
applyDraftPreview(position, rotationRef.current, supportCapRef.current)
|
|
361
454
|
|
|
362
455
|
if (
|
|
456
|
+
(isGridSnapActive() || isMagneticSnapActive()) &&
|
|
363
457
|
previousGridPosRef.current &&
|
|
364
458
|
(gridX !== previousGridPosRef.current[0] || gridZ !== previousGridPosRef.current[1])
|
|
365
459
|
) {
|
|
@@ -369,34 +463,49 @@ export const StairTool: React.FC = () => {
|
|
|
369
463
|
previousGridPosRef.current = [gridX, gridZ]
|
|
370
464
|
}
|
|
371
465
|
|
|
372
|
-
const getAlignedGridPosition = (event: GridEvent): [number, number, number] => {
|
|
373
|
-
const [gridX, gridZ] = alignPoint(
|
|
374
|
-
Math.round(event.localPosition[0] * 2) / 2,
|
|
375
|
-
Math.round(event.localPosition[2] * 2) / 2,
|
|
376
|
-
event.localPosition[0],
|
|
377
|
-
event.localPosition[2],
|
|
378
|
-
event.nativeEvent?.altKey === true,
|
|
379
|
-
)
|
|
380
|
-
return [gridX, 0, gridZ]
|
|
381
|
-
}
|
|
382
|
-
|
|
383
466
|
const commitAtCursor = (event: ClickTriggerEvent) => {
|
|
384
467
|
if (!currentLevelId) return
|
|
468
|
+
// One physical click can reach here twice (node click synthesized on
|
|
469
|
+
// pointerup + the native browser click driving `grid:click`) — see
|
|
470
|
+
// `stair-click-guard.ts`. The gate refuses anything after a single-
|
|
471
|
+
// continuation commit; the swallow below eats the same gesture's
|
|
472
|
+
// follow-up click while the tool stays armed (repeat continuation).
|
|
473
|
+
if (!commitGate.shouldCommit()) return
|
|
385
474
|
const nodeEvent = 'node' in event ? (event as NodeEvent<AnyNode>) : null
|
|
386
475
|
if (nodeEvent) {
|
|
387
476
|
nodeEvent.stopPropagation()
|
|
388
477
|
nodeEvent.nativeEvent.stopPropagation()
|
|
478
|
+
// The canvas-level `grid:click` listener is out of stopPropagation's
|
|
479
|
+
// reach — without this, the browser click that follows this
|
|
480
|
+
// pointerup-synthesized node click commits a second stair.
|
|
481
|
+
swallowFollowUpBrowserClick()
|
|
389
482
|
}
|
|
390
483
|
|
|
391
|
-
const position =
|
|
392
|
-
? lastCanonicalPositionRef.current
|
|
393
|
-
: getAlignedGridPosition(event as GridEvent)
|
|
484
|
+
const position = resolveStairPosition(event)
|
|
394
485
|
if (!position) return
|
|
395
486
|
|
|
396
|
-
commitStairPlacement(currentLevelId, position, rotationRef.current)
|
|
487
|
+
commitStairPlacement(currentLevelId, position, rotationRef.current, supportCapRef.current)
|
|
397
488
|
openingPreview.clear()
|
|
398
|
-
|
|
489
|
+
// Commit cleared the opening preview, so force the next hover (even on the
|
|
490
|
+
// same cell) to rebuild rather than dedupe against the just-placed key.
|
|
491
|
+
lastPreviewKey = null
|
|
399
492
|
useAlignmentGuides.getState().clear()
|
|
493
|
+
|
|
494
|
+
// Single by default; the C-toggle ('point' context, shared with every
|
|
495
|
+
// other placement tool) opts into placing more. On single, drop the tool
|
|
496
|
+
// and the facing triangle so we fall back to select after one stair.
|
|
497
|
+
if (useEditor.getState().getContinuation('point') === 'repeat') {
|
|
498
|
+
alignmentCandidates = collectAlignmentAnchors(useScene.getState().nodes, '', currentLevelId)
|
|
499
|
+
} else {
|
|
500
|
+
commitGate.markExited()
|
|
501
|
+
useFacingPose.getState().clear()
|
|
502
|
+
useEditor.getState().setTool(null)
|
|
503
|
+
// Return to select mode explicitly (matches the spawn tool's exit).
|
|
504
|
+
// The selection managers route node clicks only while
|
|
505
|
+
// `mode === 'select'`; exiting with `mode: 'build'` + a null tool
|
|
506
|
+
// left every click dead until the user pressed Escape.
|
|
507
|
+
useEditor.getState().setMode('select')
|
|
508
|
+
}
|
|
400
509
|
}
|
|
401
510
|
|
|
402
511
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
@@ -414,33 +523,44 @@ export const StairTool: React.FC = () => {
|
|
|
414
523
|
sfxEmitter.emit('sfx:item-rotate')
|
|
415
524
|
rotationRef.current += rotationDelta
|
|
416
525
|
if (lastCanonicalPositionRef.current) {
|
|
417
|
-
applyDraftPreview(
|
|
526
|
+
applyDraftPreview(
|
|
527
|
+
lastCanonicalPositionRef.current,
|
|
528
|
+
rotationRef.current,
|
|
529
|
+
supportCapRef.current,
|
|
530
|
+
)
|
|
418
531
|
} else if (previewRef.current) {
|
|
419
532
|
previewRef.current.rotation.y = rotationRef.current
|
|
420
533
|
}
|
|
421
534
|
}
|
|
422
535
|
}
|
|
423
536
|
|
|
424
|
-
emitter.on('grid:move',
|
|
537
|
+
emitter.on('grid:move', onPointerMove)
|
|
425
538
|
emitter.on('grid:click', commitAtCursor)
|
|
426
539
|
type SuffixedKey<K extends string> = `${K}:${EventSuffix}`
|
|
427
540
|
type ClickKey = SuffixedKey<(typeof CLICK_TRIGGER_KINDS)[number]>
|
|
541
|
+
type MoveKey = SuffixedKey<(typeof CLICK_TRIGGER_KINDS)[number]>
|
|
428
542
|
for (const kind of CLICK_TRIGGER_KINDS) {
|
|
429
543
|
const key = `${kind}:click` as ClickKey
|
|
430
544
|
emitter.on(key, commitAtCursor as never)
|
|
545
|
+
const moveKey = `${kind}:move` as MoveKey
|
|
546
|
+
emitter.on(moveKey, onPointerMove as never)
|
|
431
547
|
}
|
|
432
548
|
window.addEventListener('keydown', onKeyDown)
|
|
433
549
|
|
|
434
550
|
return () => {
|
|
435
|
-
emitter.off('grid:move',
|
|
551
|
+
emitter.off('grid:move', onPointerMove)
|
|
436
552
|
emitter.off('grid:click', commitAtCursor)
|
|
437
553
|
for (const kind of CLICK_TRIGGER_KINDS) {
|
|
438
554
|
const key = `${kind}:click` as ClickKey
|
|
439
555
|
emitter.off(key, commitAtCursor as never)
|
|
556
|
+
const moveKey = `${kind}:move` as MoveKey
|
|
557
|
+
emitter.off(moveKey, onPointerMove as never)
|
|
440
558
|
}
|
|
441
559
|
window.removeEventListener('keydown', onKeyDown)
|
|
442
560
|
useAlignmentGuides.getState().clear()
|
|
443
561
|
openingPreview.clear()
|
|
562
|
+
useFacingPose.getState().clear()
|
|
563
|
+
useStairBuildPreview.getState().reset()
|
|
444
564
|
}
|
|
445
565
|
}, [currentLevelId])
|
|
446
566
|
|
|
@@ -448,7 +568,9 @@ export const StairTool: React.FC = () => {
|
|
|
448
568
|
<group>
|
|
449
569
|
<CursorSphere ref={cursorRef} />
|
|
450
570
|
|
|
451
|
-
{/* 3D ghost preview — position/rotation updated imperatively
|
|
571
|
+
{/* 3D ghost preview — position/rotation updated imperatively. The
|
|
572
|
+
forward-facing triangle is drawn by the editor-side overlay from the
|
|
573
|
+
pose published in `applyDraftPreview`. */}
|
|
452
574
|
<group ref={previewRef}>
|
|
453
575
|
<mesh castShadow geometry={previewGeometry}>
|
|
454
576
|
<meshStandardMaterial color="#818cf8" depthWrite={false} opacity={0.35} transparent />
|