@pascal-app/editor 1.0.3 → 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 +11 -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 +202 -57
- 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 +36 -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 +344 -342
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +54 -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 +269 -197
- package/src/components/editor/index.tsx +294 -169
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- 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 +80 -1
- 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 +43 -142
- 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-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-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
- 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-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
- 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 +29 -68
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +8 -4
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
- 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/move-registry-node-tool.tsx +3 -1
- 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/cursor-sphere.tsx +36 -12
- 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 +46 -20
- 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 +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- 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/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 +325 -14
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- 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-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 +4 -4
- 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/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/index.tsx +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- 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.tsx +5 -6
- 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 +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +123 -9
- package/src/hooks/use-auto-save.ts +34 -8
- package/src/hooks/use-keyboard.test.ts +178 -3
- package/src/hooks/use-keyboard.ts +89 -71
- 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 +246 -39
- 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/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 +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -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 +59 -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 +485 -0
- package/src/lib/glb-export.ts +237 -119
- 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 +18 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- 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 +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- 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/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 +21 -0
- package/src/lib/parametric-node-update.ts +1 -1
- 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 +24 -36
- package/src/lib/scene.ts +33 -57
- 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 +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -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/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/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 +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- 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
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AnyNodeId,
|
|
3
3
|
type AssetInput,
|
|
4
|
+
beginSceneHistoryDraft,
|
|
4
5
|
ItemNode,
|
|
5
6
|
resolveSupportSlabPatch,
|
|
7
|
+
runSceneHistoryDraftWrite,
|
|
6
8
|
type SurfaceRejectReason,
|
|
9
|
+
sceneHistoryDraftRevertUpdates,
|
|
7
10
|
sceneRegistry,
|
|
8
11
|
useScene,
|
|
12
|
+
withSceneHistoryDraftSuspended,
|
|
9
13
|
} from '@pascal-app/core'
|
|
10
14
|
import { beginPerfAction, commitPerfAction, useViewer } from '@pascal-app/viewer'
|
|
11
15
|
import { useCallback, useMemo, useRef } from 'react'
|
|
@@ -24,6 +28,51 @@ import useInteractionScope, {
|
|
|
24
28
|
import usePlacementPreview from '../../../store/use-placement-preview'
|
|
25
29
|
import { stripTransient } from './placement-math'
|
|
26
30
|
|
|
31
|
+
function releaseHistoryDraft(end: { current: (() => void) | null }): void {
|
|
32
|
+
end.current?.()
|
|
33
|
+
end.current = null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A placement's own write: under a short pause, and the carried draft's fields it changes stay
|
|
38
|
+
* recorded as they were before the carry (core's `runSceneHistoryDraftWrite`).
|
|
39
|
+
*/
|
|
40
|
+
export const pausedDraftWrite = runSceneHistoryDraftWrite
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Puts back what the carry still holds on an adopted item (its own writes, core's
|
|
44
|
+
* `sceneHistoryDraftRevertUpdates`), as a carry write: a position, name or host a collaborator
|
|
45
|
+
* wrote meanwhile stays. A host deleted mid-carry is never a parent again (nor its face, roof
|
|
46
|
+
* or surface): the item stays on its live parent, else the level. Returns its parent after.
|
|
47
|
+
*/
|
|
48
|
+
function restoreOriginalState(
|
|
49
|
+
id: AnyNodeId,
|
|
50
|
+
original: OriginalState,
|
|
51
|
+
draftParentId: string | null | undefined,
|
|
52
|
+
): string | null {
|
|
53
|
+
const nodes = useScene.getState().nodes
|
|
54
|
+
const exists = (parentId: string | null | undefined): parentId is string =>
|
|
55
|
+
Boolean(parentId && nodes[parentId as AnyNodeId])
|
|
56
|
+
const hostGone = original.parentId != null && !exists(original.parentId)
|
|
57
|
+
const updates = sceneHistoryDraftRevertUpdates([id])
|
|
58
|
+
const own = updates.find((update) => update.id === id)
|
|
59
|
+
if (hostGone && own) {
|
|
60
|
+
for (const key of ['roofSegmentId', 'roofFace', 'blockFaceId']) delete own.data[key]
|
|
61
|
+
}
|
|
62
|
+
const liveParentId = nodes[id]?.parentId
|
|
63
|
+
if (!exists(liveParentId)) {
|
|
64
|
+
const fallback = [draftParentId, useViewer.getState().selection.levelId].find(exists)
|
|
65
|
+
if (fallback) {
|
|
66
|
+
if (own) own.data.parentId = fallback
|
|
67
|
+
else updates.push({ id, data: { parentId: fallback } })
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (updates.length > 0) {
|
|
71
|
+
pausedDraftWrite(() => useScene.getState().updateNodes(updates as never))
|
|
72
|
+
}
|
|
73
|
+
return useScene.getState().nodes[id]?.parentId ?? null
|
|
74
|
+
}
|
|
75
|
+
|
|
27
76
|
interface OriginalState {
|
|
28
77
|
surfaceId: string | null
|
|
29
78
|
position: [number, number, number]
|
|
@@ -38,6 +87,9 @@ interface OriginalState {
|
|
|
38
87
|
metadata: ItemNode['metadata']
|
|
39
88
|
}
|
|
40
89
|
|
|
90
|
+
/** What a duplicate keeps from the item it copies. */
|
|
91
|
+
export type ItemCopy = Pick<ItemNode, 'slots' | 'collectionIds'>
|
|
92
|
+
|
|
41
93
|
export interface DraftNodeHandle {
|
|
42
94
|
updateSurface: (data: Partial<ItemNode>, surfaceId: string | null) => void
|
|
43
95
|
/** Current draft item, or null */
|
|
@@ -45,17 +97,19 @@ export interface DraftNodeHandle {
|
|
|
45
97
|
/** Whether the current draft was adopted (move mode) vs created (create mode) */
|
|
46
98
|
readonly isAdopted: boolean
|
|
47
99
|
/** Create a new draft item at the given position. Returns the created node or null.
|
|
48
|
-
* `
|
|
100
|
+
* `copied` is the item a duplicate copies: it keeps its painted slots and collections. */
|
|
49
101
|
create: (
|
|
50
102
|
gridPosition: Vector3,
|
|
51
103
|
asset: AssetInput,
|
|
52
104
|
rotation?: [number, number, number],
|
|
53
105
|
scale?: [number, number, number],
|
|
54
|
-
|
|
106
|
+
copied?: ItemCopy,
|
|
55
107
|
) => ItemNode | null
|
|
56
108
|
/** Take ownership of an existing scene node as the draft (for move mode). */
|
|
57
109
|
adopt: (node: ItemNode) => void
|
|
58
110
|
/** Commit the current draft. Create mode: delete+recreate. Move mode: update in place.
|
|
111
|
+
* The final write is the one undo step, recorded only while no owner pauses history:
|
|
112
|
+
* a caller holding a pause lifts it around this call (`SceneHistoryPauseSession.commitStep`).
|
|
59
113
|
* `supportElevationCap` (floor commits) is the pointer-decided surface
|
|
60
114
|
* elevation — it caps the persisted `supportSlabId` election so the
|
|
61
115
|
* commit lands on the surface the cursor pointed at. */
|
|
@@ -74,7 +128,11 @@ export interface DraftNodeHandle {
|
|
|
74
128
|
|
|
75
129
|
/**
|
|
76
130
|
* Hook that manages the lifecycle of a transient (draft) item node.
|
|
77
|
-
*
|
|
131
|
+
* The draft is registered with core's history drafts from create/adopt until
|
|
132
|
+
* commit/destroy, so history records it as absent (created) or as it was
|
|
133
|
+
* (adopted) and no draft write becomes an undo step; the draft's own writes
|
|
134
|
+
* also run under a short balanced pause. An adopted `commit` suspends the registration for
|
|
135
|
+
* its tracked write and ends it only after success, so a rejected drop can still be retried.
|
|
78
136
|
*
|
|
79
137
|
* Supports two modes:
|
|
80
138
|
* - Create mode (via `create()`): draft is a new transient node. Commit = delete+recreate (undo removes node).
|
|
@@ -85,6 +143,7 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
85
143
|
const adoptedRef = useRef(false)
|
|
86
144
|
const ownsSubtreeRef = useRef(false)
|
|
87
145
|
const originalStateRef = useRef<OriginalState | null>(null)
|
|
146
|
+
const endHistoryDraftRef = useRef<(() => void) | null>(null)
|
|
88
147
|
|
|
89
148
|
const create = useCallback(
|
|
90
149
|
(
|
|
@@ -92,7 +151,7 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
92
151
|
asset: AssetInput,
|
|
93
152
|
rotation?: [number, number, number],
|
|
94
153
|
scale?: [number, number, number],
|
|
95
|
-
|
|
154
|
+
copied?: ItemCopy,
|
|
96
155
|
): ItemNode | null => {
|
|
97
156
|
const currentLevelId = useViewer.getState().selection.levelId
|
|
98
157
|
if (!currentLevelId) return null
|
|
@@ -105,10 +164,24 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
105
164
|
asset,
|
|
106
165
|
parentId: currentLevelId,
|
|
107
166
|
metadata: { isTransient: true },
|
|
108
|
-
...(slots ? { slots } : {}),
|
|
167
|
+
...(copied?.slots ? { slots: copied.slots } : {}),
|
|
168
|
+
...(copied?.collectionIds ? { collectionIds: copied.collectionIds } : {}),
|
|
109
169
|
})
|
|
110
170
|
|
|
111
|
-
|
|
171
|
+
releaseHistoryDraft(endHistoryDraftRef)
|
|
172
|
+
endHistoryDraftRef.current = beginSceneHistoryDraft(node.id, null)
|
|
173
|
+
try {
|
|
174
|
+
pausedDraftWrite(() => useScene.getState().createNode(node, currentLevelId))
|
|
175
|
+
} catch (error) {
|
|
176
|
+
try {
|
|
177
|
+
if (useScene.getState().nodes[node.id]) {
|
|
178
|
+
pausedDraftWrite(() => useScene.getState().deleteNode(node.id))
|
|
179
|
+
}
|
|
180
|
+
} finally {
|
|
181
|
+
releaseHistoryDraft(endHistoryDraftRef)
|
|
182
|
+
}
|
|
183
|
+
throw error
|
|
184
|
+
}
|
|
112
185
|
usePlacementPreview
|
|
113
186
|
.getState()
|
|
114
187
|
.set(node, useScene.getState().nodes[currentLevelId as AnyNodeId] ?? null)
|
|
@@ -122,6 +195,11 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
122
195
|
)
|
|
123
196
|
|
|
124
197
|
const adopt = useCallback((node: ItemNode): void => {
|
|
198
|
+
releaseHistoryDraft(endHistoryDraftRef)
|
|
199
|
+
endHistoryDraftRef.current = beginSceneHistoryDraft(
|
|
200
|
+
node.id,
|
|
201
|
+
isFreshPlacementMetadata(node.metadata) ? null : node,
|
|
202
|
+
)
|
|
125
203
|
ownsSubtreeRef.current =
|
|
126
204
|
useInteractionScope.getState().adoptSubtree(node.id) || isInteractionSubtreeDraft(node.id)
|
|
127
205
|
// Save original state so destroy() can restore it
|
|
@@ -149,9 +227,11 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
149
227
|
adoptedRef.current = true
|
|
150
228
|
|
|
151
229
|
// Mark as transient so it renders as a draft
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
230
|
+
pausedDraftWrite(() =>
|
|
231
|
+
useScene.getState().updateNode(node.id, {
|
|
232
|
+
metadata: { ...meta, isTransient: true },
|
|
233
|
+
}),
|
|
234
|
+
)
|
|
155
235
|
usePlacementPreview
|
|
156
236
|
.getState()
|
|
157
237
|
.set(
|
|
@@ -172,6 +252,10 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
172
252
|
): string | null => {
|
|
173
253
|
const draft = draftRef.current
|
|
174
254
|
if (!draft) return null
|
|
255
|
+
if (finalUpdate.parentId && !useScene.getState().nodes[finalUpdate.parentId as AnyNodeId]) {
|
|
256
|
+
options?.onReject?.('no-surface')
|
|
257
|
+
return null
|
|
258
|
+
}
|
|
175
259
|
|
|
176
260
|
const surfaceId = surfaceAttachmentId(useScene.getState().nodes[draft.id] ?? draft)
|
|
177
261
|
const stored = surfaceFramePose(
|
|
@@ -181,6 +265,10 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
181
265
|
true,
|
|
182
266
|
)
|
|
183
267
|
finalUpdate = { ...finalUpdate, ...stored }
|
|
268
|
+
// The drop ends the carry on the item's live metadata: placement strategies hand in the
|
|
269
|
+
// adoption-time snapshot, which would drop anything an agent wrote meanwhile.
|
|
270
|
+
const liveDraft = useScene.getState().nodes[draft.id]
|
|
271
|
+
if (liveDraft) finalUpdate.metadata = stripTransient(liveDraft.metadata)
|
|
184
272
|
if (isFreshPlacementMetadata(originalStateRef.current?.metadata)) {
|
|
185
273
|
const effectiveNode = ItemNode.parse({ ...draft, ...finalUpdate })
|
|
186
274
|
const id = commitFreshPlacementSubtree(
|
|
@@ -196,6 +284,7 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
196
284
|
options?.onReject,
|
|
197
285
|
)
|
|
198
286
|
if (!id) return null
|
|
287
|
+
releaseHistoryDraft(endHistoryDraftRef)
|
|
199
288
|
if (usePlacementPreview.getState().node?.id === draft.id) {
|
|
200
289
|
usePlacementPreview.getState().clear()
|
|
201
290
|
}
|
|
@@ -207,31 +296,15 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
207
296
|
if (adoptedRef.current) {
|
|
208
297
|
// Move mode: update in place (single undoable action)
|
|
209
298
|
const { parentId: newParentId, ...updateProps } = finalUpdate
|
|
210
|
-
const parentId =
|
|
211
|
-
newParentId ??
|
|
212
|
-
originalStateRef.current?.parentId ??
|
|
213
|
-
useViewer.getState().selection.levelId
|
|
214
299
|
const original = originalStateRef.current!
|
|
300
|
+
const nodesNow = useScene.getState().nodes
|
|
301
|
+
const restoredParentId = restoreOriginalState(draft.id, original, draft.parentId)
|
|
302
|
+
const parentId =
|
|
303
|
+
[newParentId, restoredParentId].find((id) => id && nodesNow[id as AnyNodeId]) ??
|
|
304
|
+
restoredParentId
|
|
215
305
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
draft.id,
|
|
219
|
-
{
|
|
220
|
-
position: original.position,
|
|
221
|
-
rotation: original.rotation,
|
|
222
|
-
side: original.side,
|
|
223
|
-
parentId: original.parentId,
|
|
224
|
-
roofSegmentId: original.roofSegmentId,
|
|
225
|
-
roofFace: original.roofFace,
|
|
226
|
-
blockFaceId: original.blockFaceId,
|
|
227
|
-
metadata: original.metadata,
|
|
228
|
-
},
|
|
229
|
-
original.surfaceId,
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
// Resume → tracked update (undo reverts to original)
|
|
233
|
-
useScene.temporal.getState().resume()
|
|
234
|
-
|
|
306
|
+
// The original is restored above (a carry write), so the one tracked write below has
|
|
307
|
+
// the true baseline as its undo state.
|
|
235
308
|
const effectiveNode = ItemNode.parse({
|
|
236
309
|
...draft,
|
|
237
310
|
...updateProps,
|
|
@@ -239,34 +312,35 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
239
312
|
metadata: updateProps.metadata ?? stripTransient(draft.metadata),
|
|
240
313
|
})
|
|
241
314
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
315
|
+
withSceneHistoryDraftSuspended(draft.id, () => {
|
|
316
|
+
updateSurfaceNode(
|
|
317
|
+
draft.id,
|
|
318
|
+
{
|
|
319
|
+
position: updateProps.position ?? draft.position,
|
|
320
|
+
rotation: updateProps.rotation ?? draft.rotation,
|
|
321
|
+
side: updateProps.side ?? draft.side,
|
|
322
|
+
metadata: updateProps.metadata ?? stripTransient(draft.metadata),
|
|
323
|
+
parentId: parentId as string,
|
|
324
|
+
// Forward the roof host explicitly: strategies set it on every
|
|
325
|
+
// commit (segment id on a roof face, undefined elsewhere), and
|
|
326
|
+
// dropping it here strands the item in the roof frame without
|
|
327
|
+
// the segment transform.
|
|
328
|
+
roofSegmentId: updateProps.roofSegmentId,
|
|
329
|
+
roofFace: updateProps.roofFace,
|
|
330
|
+
blockFaceId: updateProps.blockFaceId,
|
|
331
|
+
// Only when the strategy decided about wallId (roof commits clear
|
|
332
|
+
// it) — floor/ceiling commits never managed the field.
|
|
333
|
+
...('wallId' in updateProps ? { wallId: updateProps.wallId } : {}),
|
|
334
|
+
...resolveSupportSlabPatch(effectiveNode, useScene.getState().nodes, {
|
|
335
|
+
maxElevation: options?.supportElevationCap,
|
|
336
|
+
preferredSlabId: options?.preferredSupportSlabId,
|
|
337
|
+
pinSupport: options?.pinSupport,
|
|
338
|
+
}),
|
|
339
|
+
},
|
|
340
|
+
surfaceId,
|
|
341
|
+
)
|
|
342
|
+
})
|
|
343
|
+
releaseHistoryDraft(endHistoryDraftRef)
|
|
270
344
|
|
|
271
345
|
const id = draft.id
|
|
272
346
|
if (usePlacementPreview.getState().node?.id === id) {
|
|
@@ -278,20 +352,19 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
278
352
|
return id
|
|
279
353
|
}
|
|
280
354
|
|
|
281
|
-
// Create mode: delete draft (paused),
|
|
355
|
+
// Create mode: delete the draft (paused), then create the fresh node (the tracked write)
|
|
282
356
|
const { parentId: newParentId, ...updateProps } = finalUpdate
|
|
283
357
|
const parentId = (newParentId ?? useViewer.getState().selection.levelId) as AnyNodeId
|
|
284
358
|
if (!parentId) return null
|
|
285
359
|
|
|
286
360
|
beginPerfAction('place:item', draft.id)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
361
|
+
pausedDraftWrite(() => {
|
|
362
|
+
updateSurfaceNode(draft.id, {}, null)
|
|
363
|
+
useScene.getState().deleteNode(draft.id)
|
|
364
|
+
})
|
|
365
|
+
releaseHistoryDraft(endHistoryDraftRef)
|
|
290
366
|
draftRef.current = null
|
|
291
367
|
|
|
292
|
-
// Briefly resume → create fresh node (the single undoable action)
|
|
293
|
-
useScene.temporal.getState().resume()
|
|
294
|
-
|
|
295
368
|
const finalNode = ItemNode.parse({
|
|
296
369
|
name: draft.name,
|
|
297
370
|
asset: draft.asset,
|
|
@@ -299,8 +372,9 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
299
372
|
rotation: updateProps.rotation ?? draft.rotation,
|
|
300
373
|
scale: updateProps.scale ?? draft.scale,
|
|
301
374
|
side: updateProps.side ?? draft.side,
|
|
302
|
-
//
|
|
375
|
+
// A duplicated item keeps its painted slots and its collections.
|
|
303
376
|
...(draft.slots ? { slots: draft.slots } : {}),
|
|
377
|
+
...(draft.collectionIds ? { collectionIds: draft.collectionIds } : {}),
|
|
304
378
|
// Roof host — see the move-mode commit above for why this must be
|
|
305
379
|
// forwarded explicitly.
|
|
306
380
|
roofSegmentId: updateProps.roofSegmentId,
|
|
@@ -331,9 +405,6 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
331
405
|
usePlacementPreview.getState().clear()
|
|
332
406
|
}
|
|
333
407
|
|
|
334
|
-
// Re-pause for next draft cycle
|
|
335
|
-
useScene.temporal.getState().pause()
|
|
336
|
-
|
|
337
408
|
adoptedRef.current = false
|
|
338
409
|
originalStateRef.current = null
|
|
339
410
|
commitPerfAction()
|
|
@@ -347,6 +418,7 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
347
418
|
|
|
348
419
|
const draftId = draftRef.current.id
|
|
349
420
|
if (ownsSubtreeRef.current) {
|
|
421
|
+
releaseHistoryDraft(endHistoryDraftRef)
|
|
350
422
|
draftRef.current = null
|
|
351
423
|
adoptedRef.current = false
|
|
352
424
|
originalStateRef.current = null
|
|
@@ -358,7 +430,8 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
358
430
|
// `useScene.updateNodes` before unmounting the legacy mover, and
|
|
359
431
|
// an unconditional restore here would wipe that commit. By
|
|
360
432
|
// comparing the live state to the snapshot we took in `adopt()`,
|
|
361
|
-
// we let an external committer's write stick.
|
|
433
|
+
// we let an external committer's write stick. A preview after a rejected drop may
|
|
434
|
+
// lack the transient marker, but its still-owned position must be restored on cancel.
|
|
362
435
|
const original = originalStateRef.current
|
|
363
436
|
const id = draftRef.current.id
|
|
364
437
|
const live = useScene.getState().nodes[id as AnyNodeId] as ItemNode | undefined
|
|
@@ -366,10 +439,14 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
366
439
|
const externallyMoved =
|
|
367
440
|
!live?.metadata?.isTransient &&
|
|
368
441
|
!!livePosition &&
|
|
442
|
+
!sceneHistoryDraftRevertUpdates([id]).some(
|
|
443
|
+
(update) => update.id === id && 'position' in update.data,
|
|
444
|
+
) &&
|
|
369
445
|
(livePosition[0] !== original.position[0] ||
|
|
370
446
|
livePosition[1] !== original.position[1] ||
|
|
371
447
|
livePosition[2] !== original.position[2])
|
|
372
448
|
if (externallyMoved) {
|
|
449
|
+
releaseHistoryDraft(endHistoryDraftRef)
|
|
373
450
|
draftRef.current = null
|
|
374
451
|
adoptedRef.current = false
|
|
375
452
|
originalStateRef.current = null
|
|
@@ -379,36 +456,28 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
379
456
|
return
|
|
380
457
|
}
|
|
381
458
|
|
|
382
|
-
|
|
383
|
-
id,
|
|
384
|
-
{
|
|
385
|
-
position: original.position,
|
|
386
|
-
rotation: original.rotation,
|
|
387
|
-
side: original.side,
|
|
388
|
-
parentId: original.parentId,
|
|
389
|
-
roofSegmentId: original.roofSegmentId,
|
|
390
|
-
roofFace: original.roofFace,
|
|
391
|
-
blockFaceId: original.blockFaceId,
|
|
392
|
-
metadata: original.metadata,
|
|
393
|
-
},
|
|
394
|
-
original.surfaceId,
|
|
395
|
-
)
|
|
459
|
+
restoreOriginalState(id, original, draftRef.current.parentId)
|
|
396
460
|
|
|
397
461
|
// Also reset the Three.js mesh directly — the store update triggers a React
|
|
398
462
|
// re-render but the mesh position was mutated by useFrame and may not reset
|
|
399
463
|
// until the next render cycle, leaving a visual glitch.
|
|
400
464
|
const mesh = sceneRegistry.nodes.get(id as AnyNodeId)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
mesh.
|
|
465
|
+
const restored = useScene.getState().nodes[id as AnyNodeId] as ItemNode | undefined
|
|
466
|
+
if (mesh && restored) {
|
|
467
|
+
mesh.position.set(restored.position[0], restored.position[1], restored.position[2])
|
|
468
|
+
mesh.rotation.y = restored.rotation[1] ?? 0
|
|
404
469
|
mesh.visible = true
|
|
405
470
|
}
|
|
406
471
|
} else {
|
|
407
472
|
// Create mode: delete the transient node
|
|
408
|
-
|
|
409
|
-
|
|
473
|
+
const id = draftRef.current.id
|
|
474
|
+
pausedDraftWrite(() => {
|
|
475
|
+
updateSurfaceNode(id, {}, null)
|
|
476
|
+
useScene.getState().deleteNode(id)
|
|
477
|
+
})
|
|
410
478
|
}
|
|
411
479
|
|
|
480
|
+
releaseHistoryDraft(endHistoryDraftRef)
|
|
412
481
|
draftRef.current = null
|
|
413
482
|
adoptedRef.current = false
|
|
414
483
|
originalStateRef.current = null
|
|
@@ -421,10 +490,12 @@ export function useDraftNode(): DraftNodeHandle {
|
|
|
421
490
|
const draft = draftRef.current
|
|
422
491
|
if (!draft) return
|
|
423
492
|
const pose = { ...draft, ...data }
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
493
|
+
pausedDraftWrite(() =>
|
|
494
|
+
updateSurfaceNode(
|
|
495
|
+
draft.id,
|
|
496
|
+
{ ...data, ...surfaceFramePose(pose.parentId, surfaceId, pose, true) },
|
|
497
|
+
surfaceId,
|
|
498
|
+
),
|
|
428
499
|
)
|
|
429
500
|
}, [])
|
|
430
501
|
|