@pascal-app/editor 1.0.1 → 1.0.4
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 +16 -9
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +315 -4
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/custom-camera-controls.tsx +221 -128
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +37 -25
- package/src/components/editor/editor-layout-v2.tsx +131 -58
- package/src/components/editor/export-manager.tsx +34 -8
- package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
- package/src/components/editor/first-person/build-collider-world.ts +6 -2
- package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
- package/src/components/editor/first-person/mechanism-targets.ts +36 -0
- package/src/components/editor/first-person-controls.tsx +205 -2
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-panel.tsx +536 -487
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/grid.tsx +31 -7
- package/src/components/editor/group-actions.ts +59 -11
- package/src/components/editor/group-floating-action-menu.tsx +19 -9
- package/src/components/editor/group-move-3d.ts +4 -6
- package/src/components/editor/group-rotate-handle.tsx +81 -35
- package/src/components/editor/group-selection-box-3d.tsx +25 -17
- package/src/components/editor/group-transform-shared.test.ts +126 -1
- package/src/components/editor/group-transform-shared.ts +79 -73
- package/src/components/editor/handles/handle-arrow.tsx +72 -2
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +272 -131
- package/src/components/editor/index.tsx +316 -176
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +70 -70
- package/src/components/editor/node-arrow-handles.tsx +167 -14
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +132 -0
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +225 -0
- package/src/components/editor/room-labels.test.ts +357 -0
- package/src/components/editor/room-labels.tsx +990 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +451 -317
- package/src/components/editor/slab-hole-highlights.tsx +3 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
- package/src/components/editor/snapshot-capture.test.ts +52 -4
- package/src/components/editor/snapshot-capture.ts +30 -5
- package/src/components/editor/thumbnail-generator.test.ts +308 -0
- package/src/components/editor/thumbnail-generator.tsx +1035 -229
- package/src/components/editor/use-floorplan-background-placement.ts +31 -11
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +670 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.tsx +220 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +114 -163
- package/src/components/editor/wall-opening-highlights.tsx +2 -3
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +17 -24
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +33 -12
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +242 -126
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +166 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +87 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +355 -245
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +31 -66
- package/src/components/tools/__fixtures__/hosting.ts +10 -0
- package/src/components/tools/item/item-surface-placement.test.ts +3 -9
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +3 -9
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +63 -47
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +3 -3
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +207 -97
- package/src/components/tools/item/use-placement-coordinator.tsx +251 -111
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/item-surface-move.test.ts +10 -2
- package/src/components/tools/registry/item-surface-move.ts +46 -24
- package/src/components/tools/registry/item-surface-resolver.test.ts +2 -2
- package/src/components/tools/registry/move-registry-node-tool.tsx +26 -10
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/affordance-dispatch.test.ts +24 -0
- package/src/components/tools/shared/affordance-dispatch.ts +13 -0
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/shared/draft-measurement-label.tsx +38 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +27 -0
- package/src/components/tools/shared/pointer-support-cap.ts +17 -5
- package/src/components/tools/shared/surface-rejection.test.tsx +5 -4
- package/src/components/tools/shared/surface-rejection.tsx +14 -0
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/stair/stair-defaults.ts +0 -3
- package/src/components/tools/stair/stair-tool.tsx +90 -42
- package/src/components/tools/tool-manager.tsx +82 -23
- package/src/components/tools/wall/wall-drafting.test.ts +19 -4
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +3 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -1
- package/src/components/ui/action-menu/control-modes.tsx +2 -35
- package/src/components/ui/action-menu/measurement-control.tsx +3 -10
- package/src/components/ui/build-panel/build-panel.tsx +235 -0
- package/src/components/ui/command-palette/editor-commands.tsx +4 -30
- package/src/components/ui/command-palette/index.tsx +5 -24
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +101 -91
- package/src/components/ui/controls/material-picker.tsx +8 -67
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/controls/slider-control.tsx +76 -31
- package/src/components/ui/floating-level-selector.tsx +9 -14
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
- package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
- package/src/components/ui/helpers/helper-manager.tsx +348 -19
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +10 -33
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +5 -30
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-height-mode.tsx +27 -21
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +78 -36
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +7 -19
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +725 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/shortcut-token.tsx +4 -0
- package/src/components/ui/primitives/sidebar.tsx +2 -0
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +145 -0
- package/src/components/ui/sidebar/icon-rail.tsx +5 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +615 -372
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +7 -7
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
- package/src/components/ui/sidebar/tab-bar.tsx +10 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview-visibility.test.ts +35 -0
- package/src/components/viewer/floorplan-preview-visibility.ts +19 -0
- package/src/components/viewer/floorplan-preview.tsx +6 -18
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +175 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +9 -9
- package/src/hooks/use-auto-save.integration.test.tsx +311 -0
- package/src/hooks/use-auto-save.ts +55 -14
- package/src/hooks/use-ceiling-events.test.ts +1 -0
- package/src/hooks/use-ceiling-events.ts +2 -0
- package/src/hooks/use-grid-events.test.ts +2 -0
- package/src/hooks/use-grid-events.ts +16 -11
- package/src/hooks/use-keyboard.test.ts +182 -0
- package/src/hooks/use-keyboard.ts +122 -91
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +48 -0
- package/src/hooks/use-placement-notice.ts +42 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +306 -39
- package/src/lib/catalog-panel-model.ts +39 -0
- package/src/lib/catalog-selection.test.ts +27 -0
- package/src/lib/catalog-selection.ts +8 -0
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/contextual-help.test.ts +27 -1
- package/src/lib/contextual-help.ts +16 -9
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +48 -0
- package/src/lib/editor-views.ts +144 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +283 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +15 -9
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +48 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +100 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +232 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
- package/src/lib/floorplan/site-plan/geometry.ts +557 -0
- package/src/lib/floorplan/site-plan/index.ts +71 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/fresh-planar-placement.test.ts +36 -0
- package/src/lib/fresh-planar-placement.ts +191 -36
- package/src/lib/geometry-script/author.ts +152 -0
- package/src/lib/geometry-script/client.ts +152 -0
- package/src/lib/geometry-script/protocol.ts +12 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +544 -16
- package/src/lib/glb-export.ts +256 -125
- package/src/lib/grid-frame.test.ts +19 -0
- package/src/lib/grid-frame.ts +18 -0
- package/src/lib/history.test.ts +6 -0
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +27 -1
- package/src/lib/keyboard-pan.test.ts +184 -0
- package/src/lib/keyboard-pan.ts +238 -0
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +186 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +407 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +74 -0
- package/src/lib/material-paint-panel-model.ts +80 -0
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +165 -0
- package/src/lib/orbit-floor-clearance.ts +106 -0
- package/src/lib/overlay-transform.test.ts +28 -0
- package/src/lib/overlay-transform.ts +12 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +111 -0
- package/src/lib/paint-pass-through.ts +76 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +266 -25
- package/src/lib/panel-rows.ts +96 -0
- package/src/lib/panel-tool-options.test.ts +34 -0
- package/src/lib/panel-tool-options.ts +46 -0
- package/src/lib/parametric-node-update.ts +37 -0
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +50 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +266 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +973 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +364 -0
- package/src/lib/scene-clipboard.ts +229 -48
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +87 -0
- package/src/lib/scene.ts +39 -45
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +53 -74
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +17 -2
- package/src/lib/snapping-mode.ts +19 -7
- package/src/lib/spatial-drag-plane.test.ts +53 -0
- package/src/lib/spatial-drag-plane.ts +19 -0
- package/src/lib/spatial-pointer-input.test.ts +72 -0
- package/src/lib/spatial-pointer-input.ts +62 -0
- package/src/lib/spatial-snap.test.ts +47 -0
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/terrain-panel-model.ts +117 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +569 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/world-grid-snap.test.ts +48 -0
- package/src/lib/world-grid-snap.ts +9 -5
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/rotation-snapping.test.ts +53 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +442 -323
- package/src/store/use-floorplan-draft-preview.ts +23 -1
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-interaction-scope.ts +155 -6
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +13 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +175 -0
- package/src/store/view-layout.ts +196 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -3,12 +3,16 @@ import {
|
|
|
3
3
|
type AlignmentAnchor,
|
|
4
4
|
type AnyNode,
|
|
5
5
|
type AnyNodeId,
|
|
6
|
+
beginSceneHistoryPauseSession,
|
|
6
7
|
type CeilingEvent,
|
|
8
|
+
canHostSurfaceChild,
|
|
7
9
|
collectAlignmentAnchors,
|
|
8
10
|
emitter,
|
|
9
11
|
findLevelAncestorId,
|
|
10
12
|
type GridEvent,
|
|
11
13
|
getScaledDimensions,
|
|
14
|
+
getWallBodyCenterOffset,
|
|
15
|
+
getWallLocalFaceZ,
|
|
12
16
|
type ItemEvent,
|
|
13
17
|
movingFootprintAnchors,
|
|
14
18
|
type NodeEvent,
|
|
@@ -24,7 +28,6 @@ import {
|
|
|
24
28
|
useScene,
|
|
25
29
|
useSpatialQuery,
|
|
26
30
|
type WallEvent,
|
|
27
|
-
type WallNode,
|
|
28
31
|
} from '@pascal-app/core'
|
|
29
32
|
import { useViewer } from '@pascal-app/viewer'
|
|
30
33
|
import { Html } from '@react-three/drei'
|
|
@@ -48,12 +51,13 @@ import {
|
|
|
48
51
|
} from '../../../lib/active-placement-surface'
|
|
49
52
|
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
50
53
|
import { formatLinearMeasurement } from '../../../lib/measurements'
|
|
54
|
+
import { isFreshPlacementMetadata } from '../../../lib/placement-metadata'
|
|
51
55
|
import { createMovementSfxTick } from '../../../lib/sfx/movement-tick'
|
|
52
56
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
53
57
|
import {
|
|
54
58
|
surfaceAttachmentId,
|
|
55
59
|
surfaceFramePose,
|
|
56
|
-
updateSurfaceNode,
|
|
60
|
+
updateSurfaceNode as writeSurfaceNode,
|
|
57
61
|
} from '../../../lib/surface-attachment'
|
|
58
62
|
import {
|
|
59
63
|
projectAlignmentGuidesWorldToActiveBuildingLocal,
|
|
@@ -66,6 +70,7 @@ import useEditor, {
|
|
|
66
70
|
isMagneticSnapActive,
|
|
67
71
|
} from '../../../store/use-editor'
|
|
68
72
|
import useFacingPose from '../../../store/use-facing-pose'
|
|
73
|
+
import { getMovingNode } from '../../../store/use-interaction-scope'
|
|
69
74
|
import usePlacementPreview from '../../../store/use-placement-preview'
|
|
70
75
|
import {
|
|
71
76
|
createItemSurfaceGridDispatch,
|
|
@@ -80,11 +85,13 @@ import {
|
|
|
80
85
|
} from '../shared/placement-box-geometry'
|
|
81
86
|
import {
|
|
82
87
|
type PointerSupportSurface,
|
|
83
|
-
resolvePointerSupportElevation,
|
|
84
88
|
resolvePointerSupportSurface,
|
|
85
89
|
} from '../shared/pointer-support-cap'
|
|
86
90
|
import { createShelfStickiness } from '../shared/shelf-stickiness'
|
|
87
|
-
import {
|
|
91
|
+
import {
|
|
92
|
+
createSurfaceEventOwnership,
|
|
93
|
+
createSurfaceRejectionFeedback,
|
|
94
|
+
} from '../shared/surface-rejection'
|
|
88
95
|
import { shouldCreateFloorDraft } from './draft-creation'
|
|
89
96
|
import { commitFaceHostClick, resolveFaceHostPreviewCommit } from './face-host-commit'
|
|
90
97
|
import {
|
|
@@ -112,7 +119,7 @@ import {
|
|
|
112
119
|
} from './placement-strategies'
|
|
113
120
|
import { resolveItemPlacementSurfaceNormal } from './placement-surface'
|
|
114
121
|
import type { PlacementState, TransitionResult } from './placement-types'
|
|
115
|
-
import type
|
|
122
|
+
import { type DraftNodeHandle, type ItemCopy, pausedDraftWrite } from './use-draft-node'
|
|
116
123
|
|
|
117
124
|
const DEFAULT_DIMENSIONS: [number, number, number] = [1, 1, 1]
|
|
118
125
|
|
|
@@ -258,20 +265,20 @@ basePlaneMaterial.opacityNode = radialOpacity
|
|
|
258
265
|
|
|
259
266
|
export interface PlacementCoordinatorConfig {
|
|
260
267
|
asset: AssetInput | null
|
|
268
|
+
/** Scale to use when lazily creating a draft (e.g. for wall/ceiling duplicates). Defaults to [1,1,1]. */
|
|
269
|
+
defaultScale?: [number, number, number]
|
|
261
270
|
draftNode: DraftNodeHandle
|
|
262
271
|
initDraft: (gridPosition: Vector3) => void
|
|
263
|
-
onCommitted: () => boolean
|
|
264
|
-
onCancel?: () => void
|
|
265
272
|
initialState?: PlacementState
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
/** Painted slot overrides to seed onto a lazily-created draft (wall/ceiling
|
|
269
|
-
* duplicates) so the duplicate keeps its materials. */
|
|
270
|
-
slots?: ItemNode['slots']
|
|
273
|
+
onCancel?: () => void
|
|
274
|
+
onCommitted: () => boolean
|
|
271
275
|
/** Move-mode sessions keep the grabbed item offset from the first surface hit
|
|
272
276
|
* (floor / wall / ceiling / item-surface / shelf) instead of snapping the
|
|
273
277
|
* item's origin under the cursor. */
|
|
274
278
|
preserveDragOffset?: boolean
|
|
279
|
+
/** The item a duplicate copies, for a lazily-created draft (wall/ceiling
|
|
280
|
+
* duplicates) to keep its materials and collections. */
|
|
281
|
+
copied?: ItemCopy
|
|
275
282
|
}
|
|
276
283
|
|
|
277
284
|
export function usePlacementCoordinator(config: PlacementCoordinatorConfig): React.ReactNode {
|
|
@@ -524,7 +531,14 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
524
531
|
|
|
525
532
|
useEffect(() => {
|
|
526
533
|
if (!asset) return
|
|
527
|
-
|
|
534
|
+
// History is paused only around the placement's own writes, never for the whole carry:
|
|
535
|
+
// the draft stays out of history snapshots (core's history-drafts), so writes others make
|
|
536
|
+
// mid-carry record as their own steps, with space detection. In split view the 2D move
|
|
537
|
+
// overlay co-owns the gesture (keyed by the moving node), and the drop's commitStep lifts
|
|
538
|
+
// its pause too.
|
|
539
|
+
const movingNodeId = getMovingNode()?.id
|
|
540
|
+
const updateSurfaceNode: typeof writeSurfaceNode = (...args) =>
|
|
541
|
+
pausedDraftWrite(() => writeSurfaceNode(...args))
|
|
528
542
|
|
|
529
543
|
const validators = { canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling }
|
|
530
544
|
|
|
@@ -597,13 +611,13 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
597
611
|
wasAdopted: boolean,
|
|
598
612
|
repeat: () => void,
|
|
599
613
|
) => {
|
|
614
|
+
if (!committedId) return
|
|
600
615
|
if (configRef.current.onCommitted()) {
|
|
601
616
|
repeat()
|
|
602
617
|
return
|
|
603
618
|
}
|
|
604
619
|
|
|
605
620
|
useAlignmentGuides.getState().clear()
|
|
606
|
-
useScene.temporal.getState().resume()
|
|
607
621
|
if (committedId) {
|
|
608
622
|
useViewer.getState().setSelection({ selectedIds: [committedId as AnyNodeId] })
|
|
609
623
|
}
|
|
@@ -626,12 +640,27 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
626
640
|
) => {
|
|
627
641
|
const draftId = draftNode.current?.id ?? null
|
|
628
642
|
const wasAdopted = draftNode.isAdopted
|
|
629
|
-
const
|
|
630
|
-
|
|
643
|
+
const drop = beginSceneHistoryPauseSession(useScene, { gesture: movingNodeId })
|
|
644
|
+
let finalId: string | null
|
|
645
|
+
try {
|
|
646
|
+
finalId = drop.commitStep(() =>
|
|
647
|
+
draftNode.commit(nodeUpdate, {
|
|
648
|
+
...options,
|
|
649
|
+
onReject: (reason) => {
|
|
650
|
+
feedback.reject(reason)
|
|
651
|
+
edgeMaterial.color.setHex(0xef_44_44)
|
|
652
|
+
basePlaneMaterial.color.setHex(0xef_44_44)
|
|
653
|
+
},
|
|
654
|
+
}),
|
|
655
|
+
)
|
|
656
|
+
} finally {
|
|
657
|
+
drop.end()
|
|
658
|
+
}
|
|
659
|
+
if (finalId && draftId) {
|
|
631
660
|
useLiveTransforms.getState().clear(draftId)
|
|
632
661
|
useLiveNodeOverrides.getState().clearFields(draftId, faceHostClearFields(draftNode.current))
|
|
633
662
|
}
|
|
634
|
-
return { committedId: finalId
|
|
663
|
+
return { committedId: finalId, wasAdopted }
|
|
635
664
|
}
|
|
636
665
|
|
|
637
666
|
const faceHostClearFields = (draft: ItemNode | null | undefined): Array<keyof ItemNode> => {
|
|
@@ -651,6 +680,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
651
680
|
}
|
|
652
681
|
|
|
653
682
|
const surfacePointer = createItemSurfacePointerArbitration()
|
|
683
|
+
const surfaceOwnership = createSurfaceEventOwnership()
|
|
654
684
|
const revalidate = (): boolean => {
|
|
655
685
|
const fits = checkCanPlace(getContext(), validators)
|
|
656
686
|
if (
|
|
@@ -681,11 +711,22 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
681
711
|
return placeable
|
|
682
712
|
}
|
|
683
713
|
|
|
684
|
-
const surfaceContext = (event: NodeEvent<AnyNode
|
|
714
|
+
const surfaceContext = (event: NodeEvent<AnyNode>, generic = false) => {
|
|
685
715
|
lastSurfaceEvent = event
|
|
686
716
|
return {
|
|
687
717
|
...getContext(),
|
|
688
718
|
onSurfaceReject: (reason: SurfaceRejectReason) => {
|
|
719
|
+
// A rejected generic acquisition leaves the paired pointer's floor route in charge.
|
|
720
|
+
if (
|
|
721
|
+
generic &&
|
|
722
|
+
placementState.current.surfaceItemId !== event.node.id &&
|
|
723
|
+
reason !== 'surface-cutout' &&
|
|
724
|
+
reason !== 'surface-occupied'
|
|
725
|
+
) {
|
|
726
|
+
return
|
|
727
|
+
}
|
|
728
|
+
surfaceOwnership.claim(event.node.id, event.nativeEvent.nativeEvent ?? event.nativeEvent)
|
|
729
|
+
event.stopPropagation()
|
|
689
730
|
if (
|
|
690
731
|
(reason === 'footprint-outside-surface' ||
|
|
691
732
|
reason === 'footprint-exceeds-host' ||
|
|
@@ -695,7 +736,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
695
736
|
) {
|
|
696
737
|
feedback.clear()
|
|
697
738
|
detachItemSurfaceToFloor(event as ItemEvent)
|
|
698
|
-
|
|
739
|
+
// A generic host exit yields positioning to this pointer's paired floor event.
|
|
740
|
+
if (!generic)
|
|
741
|
+
surfacePointer.hit(event.node.id, event.nativeEvent.nativeEvent ?? event.nativeEvent)
|
|
699
742
|
return
|
|
700
743
|
}
|
|
701
744
|
feedback.reject(reason, event.nativeEvent.nativeEvent ?? event.nativeEvent)
|
|
@@ -746,6 +789,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
746
789
|
|
|
747
790
|
const applyTransition = (result: TransitionResult, event?: NodeEvent<AnyNode>) => {
|
|
748
791
|
if (event) {
|
|
792
|
+
surfaceOwnership.claim(event.node.id, event.nativeEvent.nativeEvent ?? event.nativeEvent)
|
|
749
793
|
surfacePointer.hit(event.node.id, event.nativeEvent.nativeEvent ?? event.nativeEvent)
|
|
750
794
|
tickSurfaceMovementSfx(result.cursorPosition)
|
|
751
795
|
}
|
|
@@ -805,16 +849,15 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
805
849
|
asset,
|
|
806
850
|
initRotation,
|
|
807
851
|
configRef.current.defaultScale,
|
|
808
|
-
configRef.current.
|
|
852
|
+
configRef.current.copied,
|
|
809
853
|
)
|
|
810
854
|
|
|
811
855
|
const draft = draftNode.current
|
|
812
856
|
if (draft) {
|
|
813
857
|
Object.assign(draft, result.nodeUpdate)
|
|
814
858
|
// One-time setup: put node in the right parent so it renders correctly
|
|
815
|
-
if (result.surfaceId
|
|
816
|
-
|
|
817
|
-
else updateSurfaceNode(draft.id, result.nodeUpdate)
|
|
859
|
+
if (result.surfaceId === undefined) updateSurfaceNode(draft.id, result.nodeUpdate)
|
|
860
|
+
else draftNode.updateSurface(result.nodeUpdate, result.surfaceId)
|
|
818
861
|
disableDraftRaycastNow()
|
|
819
862
|
}
|
|
820
863
|
|
|
@@ -1054,7 +1097,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1054
1097
|
// perspective-skewed along the ray whenever the plane sits on a
|
|
1055
1098
|
// different storey than the pointed surface (the skew is what made
|
|
1056
1099
|
// a drag over a deck-above-a-floor hop between the two surfaces).
|
|
1057
|
-
const pointed = resolvePointerSupportSurface(cameraRef.current, event.position
|
|
1100
|
+
const pointed = resolvePointerSupportSurface(cameraRef.current, event.position, {
|
|
1101
|
+
pointerRay: event.nativeEvent.ray,
|
|
1102
|
+
})
|
|
1058
1103
|
pointerSupportCapRef.current = pointed?.elevation ?? null
|
|
1059
1104
|
pointerSupportSurfaceRef.current = pointed
|
|
1060
1105
|
const surfaceEvent: GridEvent =
|
|
@@ -1247,7 +1292,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1247
1292
|
asset,
|
|
1248
1293
|
currentRotation,
|
|
1249
1294
|
configRef.current.defaultScale,
|
|
1250
|
-
configRef.current.
|
|
1295
|
+
configRef.current.copied,
|
|
1251
1296
|
)
|
|
1252
1297
|
const previewBounds = expandBoundsToGrid(
|
|
1253
1298
|
getFallbackPreviewBounds(draftNode.current, asset, asset.attachTo),
|
|
@@ -1398,12 +1443,13 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1398
1443
|
const rot = result.nodeUpdate?.rotation
|
|
1399
1444
|
if (rot) mesh.rotation.y = rot[1]
|
|
1400
1445
|
|
|
1401
|
-
|
|
1402
|
-
if (asset.attachTo === 'wall-side' && placementState.current.wallId) {
|
|
1446
|
+
if (placementState.current.wallId) {
|
|
1403
1447
|
const parentWall = useScene.getState().nodes[placementState.current.wallId as AnyNodeId]
|
|
1404
1448
|
if (parentWall?.type === 'wall') {
|
|
1405
|
-
|
|
1406
|
-
|
|
1449
|
+
mesh.position.z =
|
|
1450
|
+
asset.attachTo === 'wall-side'
|
|
1451
|
+
? getWallLocalFaceZ(parentWall, draft.side === 'front' ? 'a' : 'b')
|
|
1452
|
+
: draft.position[2] + getWallBodyCenterOffset(parentWall)
|
|
1407
1453
|
}
|
|
1408
1454
|
}
|
|
1409
1455
|
}
|
|
@@ -1571,7 +1617,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1571
1617
|
const segment =
|
|
1572
1618
|
useScene.getState().nodes[placementState.current.roofSegmentId as AnyNodeId]
|
|
1573
1619
|
if (segment?.type === 'roof-segment') {
|
|
1574
|
-
mesh.position.z = (segment.wallThickness ?? 0.1
|
|
1620
|
+
mesh.position.z = getWallLocalFaceZ({ thickness: segment.wallThickness ?? 0.1 }, 'a')
|
|
1575
1621
|
}
|
|
1576
1622
|
}
|
|
1577
1623
|
const rot = result.nodeUpdate?.rotation
|
|
@@ -1611,10 +1657,12 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1611
1657
|
applyTransition(result)
|
|
1612
1658
|
const draft = draftNode.current
|
|
1613
1659
|
if (draft) {
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1660
|
+
pausedDraftWrite(() =>
|
|
1661
|
+
useScene.getState().updateNode(draft.id, {
|
|
1662
|
+
parentId: result.nodeUpdate.parentId as string,
|
|
1663
|
+
roofSegmentId: undefined,
|
|
1664
|
+
}),
|
|
1665
|
+
)
|
|
1618
1666
|
}
|
|
1619
1667
|
} else {
|
|
1620
1668
|
// Create mode: destroy transient and reset state
|
|
@@ -1729,9 +1777,11 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1729
1777
|
}
|
|
1730
1778
|
if (draftNode.isAdopted) {
|
|
1731
1779
|
applyTransition(result)
|
|
1732
|
-
if (draft)
|
|
1780
|
+
if (draft)
|
|
1781
|
+
pausedDraftWrite(() => useScene.getState().updateNode(draft.id, result.nodeUpdate))
|
|
1733
1782
|
} else {
|
|
1734
1783
|
draftNode.destroy()
|
|
1784
|
+
gridPosition.current.set(...result.gridPosition)
|
|
1735
1785
|
Object.assign(placementState.current, result.stateUpdate)
|
|
1736
1786
|
}
|
|
1737
1787
|
}
|
|
@@ -1744,11 +1794,10 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1744
1794
|
// Landing back on the floor: refresh the pointer surface cap from
|
|
1745
1795
|
// this event's world hit so the first floor position already targets
|
|
1746
1796
|
// the aimed-at surface (not a deck above it).
|
|
1747
|
-
pointerSupportCapRef.current =
|
|
1748
|
-
event.position
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
])
|
|
1797
|
+
pointerSupportCapRef.current =
|
|
1798
|
+
resolvePointerSupportSurface(cameraRef.current, event.position, {
|
|
1799
|
+
pointerRay: event.nativeEvent.ray,
|
|
1800
|
+
})?.elevation ?? null
|
|
1752
1801
|
// Coming back from a host: forget the floor grab too, so the item
|
|
1753
1802
|
// centers under the cursor instead of restoring the pre-drag offset —
|
|
1754
1803
|
// and landing on the floor is "anchoring elsewhere", so a later return
|
|
@@ -1823,7 +1872,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1823
1872
|
revalidate()
|
|
1824
1873
|
}
|
|
1825
1874
|
|
|
1826
|
-
const onItemEnter = (event: NodeEvent<AnyNode
|
|
1875
|
+
const onItemEnter = (event: NodeEvent<AnyNode>, generic = false) => {
|
|
1827
1876
|
if (event.node.type === 'cabinet' && placementState.current.surfaceItemId === event.node.id) {
|
|
1828
1877
|
onItemMove(event)
|
|
1829
1878
|
return
|
|
@@ -1834,7 +1883,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1834
1883
|
}
|
|
1835
1884
|
if (event.node.id === draftNode.current?.id) return
|
|
1836
1885
|
has3DPointerDrivenMoveRef.current = true
|
|
1837
|
-
|
|
1886
|
+
// An authored object's underside takes ceiling items (a pendant under a vault).
|
|
1887
|
+
if (enterFaceHost(event)) return
|
|
1888
|
+
const result = itemSurfaceStrategy.enter(surfaceContext(event, generic), event)
|
|
1838
1889
|
if (!result) return
|
|
1839
1890
|
feedback.clear()
|
|
1840
1891
|
|
|
@@ -1849,12 +1900,23 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1849
1900
|
}
|
|
1850
1901
|
}
|
|
1851
1902
|
|
|
1852
|
-
const onItemMove = (event: NodeEvent<AnyNode
|
|
1903
|
+
const onItemMove = (event: NodeEvent<AnyNode>, generic = false) => {
|
|
1853
1904
|
if (event.node.id === draftNode.current?.id) return
|
|
1905
|
+
if (
|
|
1906
|
+
placementState.current.surface === 'block-face' &&
|
|
1907
|
+
placementState.current.blockId === event.node.id
|
|
1908
|
+
) {
|
|
1909
|
+
onFaceHostMove(event)
|
|
1910
|
+
return
|
|
1911
|
+
}
|
|
1912
|
+
if (placementState.current.surface !== 'block-face' && enterFaceHost(event)) {
|
|
1913
|
+
releaseCommit = () => onFaceHostClick(event)
|
|
1914
|
+
return
|
|
1915
|
+
}
|
|
1854
1916
|
releaseCommit = () => onItemClick(event)
|
|
1855
1917
|
has3DPointerDrivenMoveRef.current = true
|
|
1856
1918
|
if (!cursorGroupRef.current) return
|
|
1857
|
-
const ctx = surfaceContext(event)
|
|
1919
|
+
const ctx = surfaceContext(event, generic)
|
|
1858
1920
|
if (event.node.type === 'cabinet') {
|
|
1859
1921
|
lastRawPos.current.set(...event.position)
|
|
1860
1922
|
surfacePointer.hit(event.node.id, event.nativeEvent.nativeEvent ?? event.nativeEvent)
|
|
@@ -1879,6 +1941,8 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1879
1941
|
event,
|
|
1880
1942
|
)
|
|
1881
1943
|
|
|
1944
|
+
// An unclaimed generic mesh must let its owning ancestor receive the same hit.
|
|
1945
|
+
if (generic && !enterResult) return
|
|
1882
1946
|
event.stopPropagation()
|
|
1883
1947
|
if (enterResult) {
|
|
1884
1948
|
applyTransition(enterResult, event)
|
|
@@ -1892,7 +1956,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1892
1956
|
}
|
|
1893
1957
|
|
|
1894
1958
|
if (!draftNode.current) {
|
|
1895
|
-
const enterResult = itemSurfaceStrategy.enter(surfaceContext(event), event)
|
|
1959
|
+
const enterResult = itemSurfaceStrategy.enter(surfaceContext(event, generic), event)
|
|
1896
1960
|
if (!enterResult) return
|
|
1897
1961
|
event.stopPropagation()
|
|
1898
1962
|
ensureDraft(enterResult)
|
|
@@ -1900,9 +1964,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1900
1964
|
}
|
|
1901
1965
|
|
|
1902
1966
|
const surfaceWorld =
|
|
1903
|
-
ctx.state.surfaceItemId
|
|
1904
|
-
?
|
|
1905
|
-
:
|
|
1967
|
+
ctx.state.surfaceItemId === null
|
|
1968
|
+
? null
|
|
1969
|
+
: resolveHostSurfaceWorld(ctx.state.surfaceItemId, event.position)
|
|
1906
1970
|
const itemMoveEvent = surfaceWorld ? { ...event, position: surfaceWorld } : event
|
|
1907
1971
|
lastRawPos.current.set(
|
|
1908
1972
|
itemMoveEvent.position[0],
|
|
@@ -1949,6 +2013,13 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1949
2013
|
const onItemLeave = (event: NodeEvent<AnyNode>) => {
|
|
1950
2014
|
if (event.node.type === 'cabinet') return
|
|
1951
2015
|
if (event.node.id === draftNode.current?.id) return
|
|
2016
|
+
if (
|
|
2017
|
+
placementState.current.surface === 'block-face' &&
|
|
2018
|
+
placementState.current.blockId === event.node.id
|
|
2019
|
+
) {
|
|
2020
|
+
onFaceHostLeave(event)
|
|
2021
|
+
return
|
|
2022
|
+
}
|
|
1952
2023
|
if (placementState.current.surface !== 'item-surface') return
|
|
1953
2024
|
|
|
1954
2025
|
event.stopPropagation()
|
|
@@ -1964,6 +2035,13 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
1964
2035
|
const onItemClick = (event: NodeEvent<AnyNode>) => {
|
|
1965
2036
|
gridDispatch.flush()
|
|
1966
2037
|
if (feedback.reason) return
|
|
2038
|
+
if (
|
|
2039
|
+
placementState.current.surface === 'block-face' &&
|
|
2040
|
+
placementState.current.blockId === event.node.id
|
|
2041
|
+
) {
|
|
2042
|
+
onFaceHostClick(event)
|
|
2043
|
+
return
|
|
2044
|
+
}
|
|
1967
2045
|
// Click on the draft item itself. R3F dispatches click events to
|
|
1968
2046
|
// the closest intersected mesh only — when the draft is hovering
|
|
1969
2047
|
// on a host (shelf / table / etc.) the draft's mesh is *above*
|
|
@@ -2007,12 +2085,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2007
2085
|
// the host's own click event is blocked by the cursor preview.
|
|
2008
2086
|
if (ctx.state.surface === 'item-surface' && ctx.state.surfaceItemId) {
|
|
2009
2087
|
const hostNode = useScene.getState().nodes[ctx.state.surfaceItemId as AnyNodeId]
|
|
2010
|
-
if (
|
|
2011
|
-
hostNode &&
|
|
2012
|
-
(hostNode.type === 'item' ||
|
|
2013
|
-
hostNode.type === 'cabinet' ||
|
|
2014
|
-
hostNode.type === 'procedural-item')
|
|
2015
|
-
) {
|
|
2088
|
+
if (hostNode) {
|
|
2016
2089
|
const synthetic = { ...event, node: hostNode } as ItemEvent
|
|
2017
2090
|
const result = itemSurfaceStrategy.click(ctx, synthetic)
|
|
2018
2091
|
if (result) {
|
|
@@ -2283,9 +2356,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2283
2356
|
return
|
|
2284
2357
|
}
|
|
2285
2358
|
const shelfWorld =
|
|
2286
|
-
ctx.state.shelfId
|
|
2287
|
-
?
|
|
2288
|
-
:
|
|
2359
|
+
ctx.state.shelfId === null
|
|
2360
|
+
? null
|
|
2361
|
+
: resolveHostSurfaceWorld(ctx.state.shelfId, event.position)
|
|
2289
2362
|
const shelfMoveEvent = shelfWorld ? { ...event, position: shelfWorld } : event
|
|
2290
2363
|
const result = shelfSurfaceStrategy.move(ctx, shelfMoveEvent)
|
|
2291
2364
|
if (!result) return
|
|
@@ -2386,7 +2459,49 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2386
2459
|
const currentRotation = draft.rotation
|
|
2387
2460
|
// Round to the nearest 45° then step, matching the placed-item R/T.
|
|
2388
2461
|
const newRotationY = steppedRotation(currentRotation[1] ?? 0, rotationDir)
|
|
2389
|
-
|
|
2462
|
+
const nextRotation: [number, number, number] = [
|
|
2463
|
+
currentRotation[0],
|
|
2464
|
+
newRotationY,
|
|
2465
|
+
currentRotation[2],
|
|
2466
|
+
]
|
|
2467
|
+
const surface = placementState.current.surface
|
|
2468
|
+
const surfaceMesh =
|
|
2469
|
+
surface === 'item-surface' && placementState.current.surfaceItemId
|
|
2470
|
+
? sceneRegistry.nodes.get(placementState.current.surfaceItemId)
|
|
2471
|
+
: undefined
|
|
2472
|
+
let surfacePosition: [number, number, number] | undefined
|
|
2473
|
+
if (surfaceMesh) {
|
|
2474
|
+
const localPos = surfaceMesh.worldToLocal(lastRawPos.current.clone())
|
|
2475
|
+
const [dimX, , dimZ] = getScaledDimensions(draft)
|
|
2476
|
+
const swapDims = Math.abs(Math.sin(newRotationY)) > 0.9
|
|
2477
|
+
surfacePosition = [
|
|
2478
|
+
snapToGrid(localPos.x, swapDims ? dimZ : dimX),
|
|
2479
|
+
gridPosition.current.y,
|
|
2480
|
+
snapToGrid(localPos.z, swapDims ? dimX : dimZ),
|
|
2481
|
+
]
|
|
2482
|
+
}
|
|
2483
|
+
if (surfaceAttachmentId(draft)) {
|
|
2484
|
+
let occupied = false
|
|
2485
|
+
checkCanPlace(
|
|
2486
|
+
{
|
|
2487
|
+
...getContext(),
|
|
2488
|
+
draftItem: { ...draft, rotation: nextRotation },
|
|
2489
|
+
gridPosition: surfacePosition
|
|
2490
|
+
? new Vector3(...surfacePosition)
|
|
2491
|
+
: gridPosition.current,
|
|
2492
|
+
onSurfaceReject: (reason) => {
|
|
2493
|
+
occupied = reason === 'surface-occupied'
|
|
2494
|
+
},
|
|
2495
|
+
},
|
|
2496
|
+
validators,
|
|
2497
|
+
)
|
|
2498
|
+
if (occupied) {
|
|
2499
|
+
feedback.reject('surface-occupied')
|
|
2500
|
+
revalidate()
|
|
2501
|
+
return
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
draft.rotation = nextRotation
|
|
2390
2505
|
|
|
2391
2506
|
// Rotate the building-local cursor by the same delta as the host-local
|
|
2392
2507
|
// draft. This preserves the host's composed yaw for items resting on a
|
|
@@ -2398,7 +2513,6 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2398
2513
|
if (mesh) mesh.rotation.y = newRotationY
|
|
2399
2514
|
|
|
2400
2515
|
// Re-snap position immediately with updated rotation (dimX/dimZ may swap at 90°)
|
|
2401
|
-
const surface = placementState.current.surface
|
|
2402
2516
|
if (surface === 'floor' || surface === 'ceiling') {
|
|
2403
2517
|
const dims = getScaledDimensions(draft)
|
|
2404
2518
|
const [dimX, , dimZ] = dims
|
|
@@ -2428,15 +2542,8 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2428
2542
|
}
|
|
2429
2543
|
}
|
|
2430
2544
|
} else if (surface === 'item-surface' && placementState.current.surfaceItemId) {
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
const localPos = surfaceMesh.worldToLocal(lastRawPos.current.clone())
|
|
2434
|
-
const dims = getScaledDimensions(draft)
|
|
2435
|
-
const [dimX, , dimZ] = dims
|
|
2436
|
-
const swapDims = Math.abs(Math.sin(newRotationY)) > 0.9
|
|
2437
|
-
const x = snapToGrid(localPos.x, swapDims ? dimZ : dimX)
|
|
2438
|
-
const z = snapToGrid(localPos.z, swapDims ? dimX : dimZ)
|
|
2439
|
-
const y = gridPosition.current.y
|
|
2545
|
+
if (surfaceMesh && surfacePosition) {
|
|
2546
|
+
const [x, y, z] = surfacePosition
|
|
2440
2547
|
gridPosition.current.set(x, y, z)
|
|
2441
2548
|
draft.position = [x, y, z]
|
|
2442
2549
|
const worldSnapped = surfaceMesh.localToWorld(new Vector3(x, y, z))
|
|
@@ -2553,7 +2660,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2553
2660
|
if (event.button !== 2) return
|
|
2554
2661
|
const down = rightDown
|
|
2555
2662
|
rightDown = null
|
|
2556
|
-
if (!down
|
|
2663
|
+
if (!(down && configRef.current.onCancel)) return
|
|
2557
2664
|
const movedSq = (event.clientX - down.x) ** 2 + (event.clientY - down.y) ** 2
|
|
2558
2665
|
const elapsed = performance.now() - down.t
|
|
2559
2666
|
if (movedSq <= RIGHT_CLICK_CANCEL_MAX_MOVE_PX ** 2 && elapsed <= RIGHT_CLICK_CANCEL_MAX_MS) {
|
|
@@ -2594,7 +2701,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2594
2701
|
const unsubDraftWatch = useScene.subscribe((state) => {
|
|
2595
2702
|
if (tearingDown) return
|
|
2596
2703
|
const draft = draftNode.current
|
|
2597
|
-
if (draft === null) return
|
|
2704
|
+
if (draft === null || isFreshPlacementMetadata(draft.metadata)) return
|
|
2598
2705
|
if (draft.id in state.nodes) return
|
|
2599
2706
|
|
|
2600
2707
|
queueMicrotask(() => {
|
|
@@ -2602,48 +2709,79 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2602
2709
|
const draft = draftNode.current
|
|
2603
2710
|
if (draft === null) return
|
|
2604
2711
|
if (draft.id in useScene.getState().nodes) return
|
|
2605
|
-
|
|
2606
|
-
useScene.getState().createNode(draft, draft.parentId as AnyNodeId)
|
|
2712
|
+
pausedDraftWrite(() => useScene.getState().createNode(draft, draft.parentId as AnyNodeId))
|
|
2607
2713
|
})
|
|
2608
2714
|
})
|
|
2609
2715
|
|
|
2610
2716
|
// ---- Subscribe ----
|
|
2611
2717
|
|
|
2718
|
+
const specializedKinds = new Set<string>()
|
|
2719
|
+
const subscribeSpecialized = <E,>(key: string, handler: (event: E) => void) => {
|
|
2720
|
+
specializedKinds.add(key.slice(0, key.lastIndexOf(':')))
|
|
2721
|
+
emitter.on(key as never, handler as never)
|
|
2722
|
+
}
|
|
2723
|
+
const genericRoute = (phase: 'enter' | 'move' | 'click' | 'leave') => (event: NodeEvent) => {
|
|
2724
|
+
if (specializedKinds.has(event.node.type)) return
|
|
2725
|
+
if (
|
|
2726
|
+
!surfaceOwnership.allows(event.node.id, event.nativeEvent.nativeEvent ?? event.nativeEvent)
|
|
2727
|
+
)
|
|
2728
|
+
return
|
|
2729
|
+
if (nodeRegistry.get(event.node.type)?.capabilities.faceHost) {
|
|
2730
|
+
const handlers = {
|
|
2731
|
+
enter: onFaceHostEnter,
|
|
2732
|
+
move: onFaceHostMove,
|
|
2733
|
+
click: onFaceHostClick,
|
|
2734
|
+
leave: onFaceHostLeave,
|
|
2735
|
+
}
|
|
2736
|
+
handlers[phase](event)
|
|
2737
|
+
return
|
|
2738
|
+
}
|
|
2739
|
+
if (!canHostSurfaceChild(event.node, 'item', draftNode.current?.id)) return
|
|
2740
|
+
if (phase === 'leave' && placementState.current.surfaceItemId !== event.node.id) return
|
|
2741
|
+
if (phase === 'enter') onItemEnter(event, true)
|
|
2742
|
+
else if (phase === 'move') onItemMove(event, true)
|
|
2743
|
+
else if (phase === 'click') onItemClick(event)
|
|
2744
|
+
else onItemLeave(event)
|
|
2745
|
+
}
|
|
2746
|
+
const onNodeEnter = genericRoute('enter')
|
|
2747
|
+
const onNodeMove = genericRoute('move')
|
|
2748
|
+
const onNodeClick = genericRoute('click')
|
|
2749
|
+
const onNodeLeave = genericRoute('leave')
|
|
2612
2750
|
const gridDispatch = createItemSurfaceGridDispatch(onGridMove)
|
|
2613
2751
|
emitter.on('grid:move', gridDispatch.schedule)
|
|
2614
2752
|
emitter.on('grid:click', onGridClick)
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
emitter.on('node:enter',
|
|
2628
|
-
emitter.on('node:move',
|
|
2629
|
-
emitter.on('node:click',
|
|
2630
|
-
emitter.on('node:leave',
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2753
|
+
subscribeSpecialized('item:enter', onItemEnter)
|
|
2754
|
+
subscribeSpecialized('item:move', onItemMove)
|
|
2755
|
+
subscribeSpecialized('item:leave', onItemLeave)
|
|
2756
|
+
subscribeSpecialized('item:click', onItemClick)
|
|
2757
|
+
subscribeSpecialized('wall:enter', onWallEnter)
|
|
2758
|
+
subscribeSpecialized('wall:move', onWallMove)
|
|
2759
|
+
subscribeSpecialized('wall:click', onWallClick)
|
|
2760
|
+
subscribeSpecialized('wall:leave', onWallLeave)
|
|
2761
|
+
subscribeSpecialized('roof:enter', onRoofWallEnter)
|
|
2762
|
+
subscribeSpecialized('roof:move', onRoofWallMove)
|
|
2763
|
+
subscribeSpecialized('roof:click', onRoofWallClick)
|
|
2764
|
+
subscribeSpecialized('roof:leave', onRoofWallLeave)
|
|
2765
|
+
emitter.on('node:enter', onNodeEnter)
|
|
2766
|
+
emitter.on('node:move', onNodeMove)
|
|
2767
|
+
emitter.on('node:click', onNodeClick)
|
|
2768
|
+
emitter.on('node:leave', onNodeLeave)
|
|
2769
|
+
subscribeSpecialized('ceiling:enter', onCeilingEnter)
|
|
2770
|
+
subscribeSpecialized('ceiling:move', onCeilingMove)
|
|
2771
|
+
subscribeSpecialized('ceiling:click', onCeilingClick)
|
|
2772
|
+
subscribeSpecialized('ceiling:leave', onCeilingLeave)
|
|
2773
|
+
subscribeSpecialized('cabinet:enter', onItemEnter)
|
|
2774
|
+
subscribeSpecialized('procedural-item:enter', onItemEnter)
|
|
2775
|
+
subscribeSpecialized('cabinet:move', onItemMove)
|
|
2776
|
+
subscribeSpecialized('procedural-item:move', onItemMove)
|
|
2777
|
+
subscribeSpecialized('cabinet:click', onItemClick)
|
|
2778
|
+
subscribeSpecialized('procedural-item:click', onItemClick)
|
|
2779
|
+
subscribeSpecialized('cabinet:leave', onItemLeave)
|
|
2780
|
+
subscribeSpecialized('procedural-item:leave', onItemLeave)
|
|
2781
|
+
subscribeSpecialized('shelf:enter', onShelfEnter)
|
|
2782
|
+
subscribeSpecialized('shelf:move', onShelfMove)
|
|
2783
|
+
subscribeSpecialized('shelf:click', onShelfClick)
|
|
2784
|
+
subscribeSpecialized('shelf:leave', onShelfLeave)
|
|
2647
2785
|
|
|
2648
2786
|
// A floor placement commits at the tracked floor cursor (`gridPosition`),
|
|
2649
2787
|
// which keeps following the floor even when the click ray lands on a wall
|
|
@@ -2673,7 +2811,6 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2673
2811
|
useLiveNodeOverrides.getState().clearFields(draftNode.current.id, ['rotation'])
|
|
2674
2812
|
}
|
|
2675
2813
|
draftNode.destroy()
|
|
2676
|
-
useScene.temporal.getState().resume()
|
|
2677
2814
|
gridDispatch.cancel()
|
|
2678
2815
|
emitter.off('grid:move', gridDispatch.schedule)
|
|
2679
2816
|
emitter.off('grid:click', onGridClick)
|
|
@@ -2689,10 +2826,10 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2689
2826
|
emitter.off('roof:move', onRoofWallMove)
|
|
2690
2827
|
emitter.off('roof:click', onRoofWallClick)
|
|
2691
2828
|
emitter.off('roof:leave', onRoofWallLeave)
|
|
2692
|
-
emitter.off('node:enter',
|
|
2693
|
-
emitter.off('node:move',
|
|
2694
|
-
emitter.off('node:click',
|
|
2695
|
-
emitter.off('node:leave',
|
|
2829
|
+
emitter.off('node:enter', onNodeEnter)
|
|
2830
|
+
emitter.off('node:move', onNodeMove)
|
|
2831
|
+
emitter.off('node:click', onNodeClick)
|
|
2832
|
+
emitter.off('node:leave', onNodeLeave)
|
|
2696
2833
|
emitter.off('ceiling:enter', onCeilingEnter)
|
|
2697
2834
|
emitter.off('ceiling:move', onCeilingMove)
|
|
2698
2835
|
emitter.off('ceiling:click', onCeilingClick)
|
|
@@ -2764,12 +2901,15 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2764
2901
|
draftParent?.type === 'shelf' ||
|
|
2765
2902
|
draftParent?.type === 'cabinet' ||
|
|
2766
2903
|
draftParent?.type === 'procedural-item' ||
|
|
2904
|
+
(draftParent && canHostSurfaceChild(draftParent, draft.type, draft.id)) ||
|
|
2767
2905
|
(draftParent &&
|
|
2768
2906
|
nodeRegistry.get(draftParent.type)?.capabilities.faceHost?.currentFaceId(draft))
|
|
2769
2907
|
)
|
|
2770
2908
|
return
|
|
2771
2909
|
draft.parentId = viewerLevelId
|
|
2772
|
-
|
|
2910
|
+
pausedDraftWrite(() =>
|
|
2911
|
+
useScene.getState().updateNode(draft.id as AnyNodeId, { parentId: viewerLevelId }),
|
|
2912
|
+
)
|
|
2773
2913
|
}, [viewerLevelId, draftNode, asset])
|
|
2774
2914
|
|
|
2775
2915
|
// Restore the draft mesh's raycast when the coordinator unmounts (tool change).
|
|
@@ -2782,8 +2922,8 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
2782
2922
|
// instead of drawing an inline triangle. Only this coordinator publishes — a
|
|
2783
2923
|
// moving existing node has no draft here, so the grid reads that case straight
|
|
2784
2924
|
// off the node's mesh. Cleared when idle.
|
|
2785
|
-
const surfaceNormalRef = useRef(new Vector3(0, 1, 0))
|
|
2786
2925
|
const surfaceWorldPointRef = useRef(new Vector3())
|
|
2926
|
+
const surfaceNormalRef = useRef(new Vector3(0, 1, 0))
|
|
2787
2927
|
const facingForwardRef = useRef(new Vector3(0, 0, 1))
|
|
2788
2928
|
const facingQuatRef = useRef(new Quaternion())
|
|
2789
2929
|
const ghostSurfaceQuatRef = useRef(new Quaternion())
|