@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
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
type AnyNode,
|
|
5
4
|
type AnyNodeId,
|
|
6
5
|
type FenceNode,
|
|
7
6
|
getFenceCenterlineFrameAt,
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
getWallCurveFrameAt,
|
|
11
10
|
getWallCurveLength,
|
|
12
11
|
getWallEffectiveHeightForNodes,
|
|
12
|
+
getWallLocalFaceZ,
|
|
13
13
|
getWallThickness,
|
|
14
14
|
isCurvedWall,
|
|
15
15
|
MIN_WALL_HEIGHT,
|
|
@@ -48,7 +48,10 @@ import {
|
|
|
48
48
|
} from '../../lib/elevation-guides'
|
|
49
49
|
import { isHistoryShortcut } from '../../lib/history'
|
|
50
50
|
import { endpointReshapeScope } from '../../lib/interaction/scope'
|
|
51
|
+
import { useRoomHandleDrag, wallPushHandles } from '../../lib/room-handle-drag'
|
|
51
52
|
import { sfxEmitter } from '../../lib/sfx-bus'
|
|
53
|
+
import { intersectSpatialDragPlane, spatialDragLocalY } from '../../lib/spatial-drag-plane'
|
|
54
|
+
import { getSpatialPointerId, spatialPointerInput } from '../../lib/spatial-pointer-input'
|
|
52
55
|
import useEditor, { isGridSnapActive, isMagneticSnapActive } from '../../store/use-editor'
|
|
53
56
|
import useInteractionScope, {
|
|
54
57
|
useEndpointReshape,
|
|
@@ -67,6 +70,7 @@ import {
|
|
|
67
70
|
NO_RAYCAST,
|
|
68
71
|
useInvisibleHitAreaMaterial,
|
|
69
72
|
} from './node-arrow-handles'
|
|
73
|
+
import { WallPushArrow } from './wall-push-arrow'
|
|
70
74
|
|
|
71
75
|
const HANDLE_OFFSET = 0.27
|
|
72
76
|
const HANDLE_MIN_OFFSET = 0.33
|
|
@@ -147,6 +151,7 @@ export function WallMoveSideHandles() {
|
|
|
147
151
|
const movingNode = useMovingNode()
|
|
148
152
|
const endpointReshape = useEndpointReshape()
|
|
149
153
|
const isCurveReshape = useIsCurveReshape()
|
|
154
|
+
const isPushing = useRoomHandleDrag((state) => state.drag?.kind === 'push')
|
|
150
155
|
|
|
151
156
|
const selectedId = selectedIds.length === 1 ? selectedIds[0] : null
|
|
152
157
|
// Walls still use this legacy handle renderer. Fences retain their registry
|
|
@@ -162,9 +167,10 @@ export function WallMoveSideHandles() {
|
|
|
162
167
|
mode !== 'delete' &&
|
|
163
168
|
!movingNode &&
|
|
164
169
|
!endpointReshape &&
|
|
165
|
-
!isCurveReshape
|
|
170
|
+
!isCurveReshape &&
|
|
171
|
+
!isPushing
|
|
166
172
|
|
|
167
|
-
if (!shouldRender
|
|
173
|
+
if (!(shouldRender && selectedNode)) return null
|
|
168
174
|
|
|
169
175
|
return selectedNode.type === 'wall' ? (
|
|
170
176
|
<WallMoveSideHandlesForWall wall={selectedNode} />
|
|
@@ -219,16 +225,17 @@ function WallMoveSideHandlesForWall({ wall }: { wall: WallNode }) {
|
|
|
219
225
|
}, [wall.parentId])
|
|
220
226
|
|
|
221
227
|
const baseElevation = getWallBaseElevationForNodes(effectiveWall, nodes)
|
|
222
|
-
const handles = useMemo(() =>
|
|
228
|
+
const handles = useMemo(() => wallPushHandles(effectiveWall, nodes), [effectiveWall, nodes])
|
|
223
229
|
|
|
224
|
-
|
|
230
|
+
const levelId = wall.parentId
|
|
231
|
+
if (!(levelObject && levelId) || handles.length === 0) return null
|
|
225
232
|
|
|
226
233
|
return createPortal(
|
|
227
234
|
<>
|
|
235
|
+
{handles.map((handle) => (
|
|
236
|
+
<WallPushArrow handle={handle} key={handle.key} levelId={levelId} />
|
|
237
|
+
))}
|
|
228
238
|
<group position={[0, baseElevation, 0]}>
|
|
229
|
-
{handles.map((handle) => (
|
|
230
|
-
<WallMoveArrowHandle handle={handle} key={handle.key} wall={effectiveWall} />
|
|
231
|
-
))}
|
|
232
239
|
<WallHeightArrowHandle wall={effectiveWall} />
|
|
233
240
|
<StructureThicknessHandle
|
|
234
241
|
baseElevation={baseElevation}
|
|
@@ -292,7 +299,7 @@ function StructureThicknessHandle({
|
|
|
292
299
|
? (node.height ?? 1.8)
|
|
293
300
|
: getWallEffectiveHeightForNodes(node, useScene.getState().nodes)
|
|
294
301
|
const outward = new Vector2(frame.normal.x * side, frame.normal.y * side)
|
|
295
|
-
const faceOffset = thickness
|
|
302
|
+
const faceOffset = getWallLocalFaceZ({ ...node, thickness }, side > 0 ? 'a' : 'b') * side + 0.006
|
|
296
303
|
const position: [number, number, number] = [
|
|
297
304
|
frame.point.x + outward.x * faceOffset,
|
|
298
305
|
structureHeight / 2,
|
|
@@ -369,11 +376,18 @@ function StructureThicknessHandle({
|
|
|
369
376
|
initialNode.type === 'fence'
|
|
370
377
|
? (initialNode.thickness ?? 0.08)
|
|
371
378
|
: getWallThickness(initialNode)
|
|
379
|
+
const initialFaceOffset =
|
|
380
|
+
getWallLocalFaceZ(
|
|
381
|
+
{ ...initialNode, thickness: initialStructureThickness },
|
|
382
|
+
side > 0 ? 'a' : 'b',
|
|
383
|
+
) *
|
|
384
|
+
side +
|
|
385
|
+
0.006
|
|
372
386
|
const minimumThickness = initialNode.type === 'fence' ? 0.03 : MIN_WALL_THICKNESS
|
|
373
387
|
const localOrigin = new Vector3(
|
|
374
|
-
initialFrame.point.x + localAxis.x *
|
|
388
|
+
initialFrame.point.x + localAxis.x * initialFaceOffset,
|
|
375
389
|
baseElevation + initialStructureHeight / 2,
|
|
376
|
-
initialFrame.point.y + localAxis.z *
|
|
390
|
+
initialFrame.point.y + localAxis.z * initialFaceOffset,
|
|
377
391
|
)
|
|
378
392
|
const worldOrigin = localOrigin.clone().applyMatrix4(rideObject.matrixWorld)
|
|
379
393
|
const worldAxisEnd = localOrigin.clone().add(localAxis).applyMatrix4(rideObject.matrixWorld)
|
|
@@ -639,13 +653,14 @@ function WallCornerLeaderHandle({ wall, endpoint }: { wall: WallNode; endpoint:
|
|
|
639
653
|
}
|
|
640
654
|
})
|
|
641
655
|
|
|
642
|
-
useEffect(
|
|
643
|
-
|
|
656
|
+
useEffect(
|
|
657
|
+
() => () => {
|
|
644
658
|
if (document.body.style.cursor === 'grab' || document.body.style.cursor === 'grabbing') {
|
|
645
659
|
document.body.style.cursor = ''
|
|
646
660
|
}
|
|
647
|
-
}
|
|
648
|
-
|
|
661
|
+
},
|
|
662
|
+
[],
|
|
663
|
+
)
|
|
649
664
|
|
|
650
665
|
const activateEndpointMove = (event: ThreeEvent<PointerEvent>) => {
|
|
651
666
|
if (event.button !== 0) return
|
|
@@ -769,7 +784,9 @@ function WallBaseElevationHandle({
|
|
|
769
784
|
const midpointWorld = new Vector3(midpoint[0], initialBase, midpoint[1]).applyMatrix4(
|
|
770
785
|
levelObject.matrixWorld,
|
|
771
786
|
)
|
|
772
|
-
const planeNormal = new Vector3()
|
|
787
|
+
const planeNormal = new Vector3()
|
|
788
|
+
.subVectors(camera.getWorldPosition(new Vector3()), midpointWorld)
|
|
789
|
+
.setY(0)
|
|
773
790
|
if (planeNormal.lengthSq() === 0) return null
|
|
774
791
|
planeNormal.normalize()
|
|
775
792
|
const plane = new Plane().setFromNormalAndCoplanarPoint(planeNormal, midpointWorld)
|
|
@@ -873,14 +890,15 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
|
|
|
873
890
|
arrowMaterial.color.set(isHovered ? ARROW_HOVER_COLOR : ARROW_COLOR)
|
|
874
891
|
}, [arrowMaterial, isHovered])
|
|
875
892
|
|
|
876
|
-
useEffect(
|
|
877
|
-
|
|
893
|
+
useEffect(
|
|
894
|
+
() => () => {
|
|
878
895
|
if (document.body.style.cursor === 'ns-resize') {
|
|
879
896
|
document.body.style.cursor = ''
|
|
880
897
|
}
|
|
881
898
|
dragCleanupRef.current?.()
|
|
882
|
-
}
|
|
883
|
-
|
|
899
|
+
},
|
|
900
|
+
[],
|
|
901
|
+
)
|
|
884
902
|
|
|
885
903
|
useEffect(() => () => arrowGeometry.dispose(), [arrowGeometry])
|
|
886
904
|
useEffect(() => () => hitGeometry.dispose(), [hitGeometry])
|
|
@@ -889,7 +907,7 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
|
|
|
889
907
|
// Sit on the visual centre of the wall — for curved walls that's the
|
|
890
908
|
// arc apex at t=0.5, not the chord midpoint. Use the curve tangent for
|
|
891
909
|
// the yaw so the arrow's local frame matches the wall direction at the
|
|
892
|
-
// apex, consistent with `
|
|
910
|
+
// apex, consistent with `wallPushHandles`.
|
|
893
911
|
const curveFrame = isCurvedWall(wall) ? getWallCurveFrameAt(wall, 0.5) : null
|
|
894
912
|
const midX = curveFrame ? curveFrame.point.x : (wall.start[0] + wall.end[0]) / 2
|
|
895
913
|
const midZ = curveFrame ? curveFrame.point.y : (wall.start[1] + wall.end[1]) / 2
|
|
@@ -908,16 +926,12 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
|
|
|
908
926
|
const levelObject = wall.parentId ? sceneRegistry.nodes.get(wall.parentId) : null
|
|
909
927
|
if (!levelObject) return
|
|
910
928
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
// pointer movement into a world-space Y value.
|
|
929
|
+
levelObject.updateWorldMatrix(true, false)
|
|
930
|
+
const worldToLocal = levelObject.matrixWorld.clone().invert()
|
|
914
931
|
const midpointWorld = new Vector3(midX, 0, midZ).applyMatrix4(levelObject.matrixWorld)
|
|
915
|
-
const planeNormal = new Vector3().subVectors(camera.position, midpointWorld).setY(0)
|
|
916
|
-
if (planeNormal.lengthSq() === 0) return
|
|
917
|
-
planeNormal.normalize()
|
|
918
|
-
const plane = new Plane().setFromNormalAndCoplanarPoint(planeNormal, midpointWorld)
|
|
919
|
-
|
|
920
932
|
const ndc = new Vector2()
|
|
933
|
+
const spatialPointerId = getSpatialPointerId(event.nativeEvent)
|
|
934
|
+
const spatialRay = spatialPointerId ? event.ray.clone() : null
|
|
921
935
|
const setNDC = (clientX: number, clientY: number) => {
|
|
922
936
|
const rect = gl.domElement.getBoundingClientRect()
|
|
923
937
|
ndc.set(
|
|
@@ -926,17 +940,39 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
|
|
|
926
940
|
)
|
|
927
941
|
}
|
|
928
942
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
943
|
+
let plane: Plane | null = null
|
|
944
|
+
let initialY: number
|
|
945
|
+
if (spatialRay) {
|
|
946
|
+
const worldUp = new Vector3(0, 1, 0).transformDirection(levelObject.matrixWorld)
|
|
947
|
+
const axisPoint = new Vector3()
|
|
948
|
+
const spatialRayLocalY = (ray: Ray) => {
|
|
949
|
+
const axisParameter = closestAxisParameterToRay(midpointWorld, worldUp, ray)
|
|
950
|
+
axisPoint.copy(midpointWorld).addScaledVector(worldUp, axisParameter)
|
|
951
|
+
return spatialDragLocalY(axisPoint, worldToLocal)
|
|
952
|
+
}
|
|
953
|
+
initialY = spatialRayLocalY(spatialRay)
|
|
954
|
+
} else {
|
|
955
|
+
// A camera-facing vertical plane gives desktop pointer movement a stable
|
|
956
|
+
// world-space height. Spatial pointers use the closest point on the
|
|
957
|
+
// wall's vertical axis instead, which remains defined for vertical rays.
|
|
958
|
+
const planeNormal = new Vector3()
|
|
959
|
+
.subVectors(camera.getWorldPosition(new Vector3()), midpointWorld)
|
|
960
|
+
.setY(0)
|
|
961
|
+
if (planeNormal.lengthSq() === 0) return
|
|
962
|
+
plane = new Plane().setFromNormalAndCoplanarPoint(planeNormal.normalize(), midpointWorld)
|
|
963
|
+
setNDC(event.nativeEvent.clientX, event.nativeEvent.clientY)
|
|
964
|
+
raycaster.setFromCamera(ndc, camera)
|
|
965
|
+
const hit = new Vector3()
|
|
966
|
+
if (!intersectSpatialDragPlane(raycaster.ray, plane, hit)) return
|
|
967
|
+
initialY = spatialDragLocalY(hit, worldToLocal)
|
|
968
|
+
}
|
|
933
969
|
|
|
934
970
|
// Dragging the top makes the wall custom-height; seed from the resolved
|
|
935
971
|
// effective height so a plane-bound wall's drag starts at its real top.
|
|
936
972
|
const initialHeight = getWallEffectiveHeightForNodes(wall, useScene.getState().nodes)
|
|
937
|
-
const initialY = hit.y
|
|
938
973
|
const wallId = wall.id as AnyNodeId
|
|
939
974
|
let pendingHeight = initialHeight
|
|
975
|
+
let releaseSpatialCapture: (() => void) | null = null
|
|
940
976
|
|
|
941
977
|
document.body.style.cursor = 'ns-resize'
|
|
942
978
|
sfxEmitter.emit('sfx:item-pick')
|
|
@@ -954,9 +990,23 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
|
|
|
954
990
|
const onMove = (e: PointerEvent) => {
|
|
955
991
|
setNDC(e.clientX, e.clientY)
|
|
956
992
|
raycaster.setFromCamera(ndc, camera)
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
993
|
+
applyRay(raycaster.ray)
|
|
994
|
+
}
|
|
995
|
+
const applyRay = (ray: Ray) => {
|
|
996
|
+
let currentY: number
|
|
997
|
+
if (spatialRay) {
|
|
998
|
+
const worldUp = new Vector3(0, 1, 0).transformDirection(levelObject.matrixWorld)
|
|
999
|
+
const axisParameter = closestAxisParameterToRay(midpointWorld, worldUp, ray)
|
|
1000
|
+
const axisPoint = midpointWorld.clone().addScaledVector(worldUp, axisParameter)
|
|
1001
|
+
currentY = spatialDragLocalY(axisPoint, worldToLocal)
|
|
1002
|
+
} else {
|
|
1003
|
+
if (!plane) return
|
|
1004
|
+
const intersection = new Vector3()
|
|
1005
|
+
if (!intersectSpatialDragPlane(ray, plane, intersection)) return
|
|
1006
|
+
currentY = spatialDragLocalY(intersection, worldToLocal)
|
|
1007
|
+
}
|
|
1008
|
+
const newHeight = Math.max(MIN_WALL_HEIGHT, initialHeight + (currentY - initialY))
|
|
1009
|
+
if (Math.abs(newHeight - pendingHeight) < 1e-6) return
|
|
960
1010
|
pendingHeight = newHeight
|
|
961
1011
|
useLiveNodeOverrides.getState().set(wallId, { height: newHeight })
|
|
962
1012
|
useScene.getState().markDirty(wallId)
|
|
@@ -967,6 +1017,8 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
|
|
|
967
1017
|
window.removeEventListener('pointerup', onUp)
|
|
968
1018
|
window.removeEventListener('pointercancel', onCancel)
|
|
969
1019
|
window.removeEventListener('keydown', onKeyDown, true)
|
|
1020
|
+
releaseSpatialCapture?.()
|
|
1021
|
+
releaseSpatialCapture = null
|
|
970
1022
|
if (document.body.style.cursor === 'ns-resize') {
|
|
971
1023
|
document.body.style.cursor = ''
|
|
972
1024
|
}
|
|
@@ -981,6 +1033,7 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
|
|
|
981
1033
|
// Commit: write the final override-merged value to zustand once
|
|
982
1034
|
// (tracked, undoable), then drop the override so the renderer
|
|
983
1035
|
// falls back to the scene store.
|
|
1036
|
+
useScene.temporal.getState().resume()
|
|
984
1037
|
if (pendingHeight !== initialHeight) {
|
|
985
1038
|
useScene.getState().updateNode(wallId, { height: pendingHeight })
|
|
986
1039
|
}
|
|
@@ -1006,10 +1059,21 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
|
|
|
1006
1059
|
onCancel()
|
|
1007
1060
|
}
|
|
1008
1061
|
|
|
1009
|
-
dragCleanupRef.current =
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1062
|
+
dragCleanupRef.current = onCancel
|
|
1063
|
+
if (spatialPointerId && spatialRay) {
|
|
1064
|
+
releaseSpatialCapture = spatialPointerInput.capture(spatialPointerId, {
|
|
1065
|
+
onMove: (ray) => {
|
|
1066
|
+
spatialRay.copy(ray)
|
|
1067
|
+
applyRay(spatialRay)
|
|
1068
|
+
},
|
|
1069
|
+
onRelease: onUp,
|
|
1070
|
+
onCancel,
|
|
1071
|
+
})
|
|
1072
|
+
} else {
|
|
1073
|
+
window.addEventListener('pointermove', onMove)
|
|
1074
|
+
window.addEventListener('pointerup', onUp)
|
|
1075
|
+
window.addEventListener('pointercancel', onCancel)
|
|
1076
|
+
}
|
|
1013
1077
|
window.addEventListener('keydown', onKeyDown, true)
|
|
1014
1078
|
}
|
|
1015
1079
|
|
|
@@ -1035,7 +1099,10 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
|
|
|
1035
1099
|
scale={baseScale}
|
|
1036
1100
|
/>
|
|
1037
1101
|
<mesh
|
|
1038
|
-
//
|
|
1102
|
+
// Pass geometry as a prop (not `<primitive attach="geometry">`): with
|
|
1103
|
+
// `frustumCulled={false}` the attach path draws R3F's empty default
|
|
1104
|
+
// geometry on the first frame and WebGPU flags "Vertex buffer slot 0
|
|
1105
|
+
// ... was not set".
|
|
1039
1106
|
frustumCulled={false}
|
|
1040
1107
|
geometry={arrowGeometry}
|
|
1041
1108
|
material={arrowMaterial}
|
|
@@ -1048,7 +1115,7 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
|
|
|
1048
1115
|
)
|
|
1049
1116
|
}
|
|
1050
1117
|
|
|
1051
|
-
function
|
|
1118
|
+
function FenceMoveArrowHandle({ fence, handle }: { fence: FenceNode; handle: WallMoveHandle }) {
|
|
1052
1119
|
const [isHovered, setIsHovered] = useState(false)
|
|
1053
1120
|
const arrowGeometry = useMemo(() => createArrowHandleGeometry(), [])
|
|
1054
1121
|
const hitGeometry = useMemo(() => createArrowHitAreaGeometry(), [])
|
|
@@ -1068,7 +1135,6 @@ function WallMoveArrowHandle({ wall, handle }: { wall: WallNode; handle: WallMov
|
|
|
1068
1135
|
const { camera } = useThree()
|
|
1069
1136
|
|
|
1070
1137
|
const zoom = camera instanceof OrthographicCamera ? 1 / camera.zoom : 1
|
|
1071
|
-
|
|
1072
1138
|
const baseScale = zoom * ARROW_SCALE
|
|
1073
1139
|
const scale = (isHovered ? 1.12 : 1) * baseScale
|
|
1074
1140
|
|
|
@@ -1076,103 +1142,14 @@ function WallMoveArrowHandle({ wall, handle }: { wall: WallNode; handle: WallMov
|
|
|
1076
1142
|
arrowMaterial.color.set(isHovered ? ARROW_HOVER_COLOR : ARROW_COLOR)
|
|
1077
1143
|
}, [arrowMaterial, isHovered])
|
|
1078
1144
|
|
|
1079
|
-
useEffect(
|
|
1080
|
-
|
|
1145
|
+
useEffect(
|
|
1146
|
+
() => () => {
|
|
1081
1147
|
if (document.body.style.cursor === 'grab' || document.body.style.cursor === 'grabbing') {
|
|
1082
1148
|
document.body.style.cursor = ''
|
|
1083
1149
|
}
|
|
1084
|
-
}
|
|
1085
|
-
}, [])
|
|
1086
|
-
|
|
1087
|
-
useEffect(() => () => arrowGeometry.dispose(), [arrowGeometry])
|
|
1088
|
-
useEffect(() => () => hitGeometry.dispose(), [hitGeometry])
|
|
1089
|
-
useEffect(() => () => arrowMaterial.dispose(), [arrowMaterial])
|
|
1090
|
-
|
|
1091
|
-
const activateWallMove = (event: ThreeEvent<PointerEvent>) => {
|
|
1092
|
-
if (event.button !== 0) return
|
|
1093
|
-
event.stopPropagation()
|
|
1094
|
-
suppressBoxSelectForPointer(event)
|
|
1095
|
-
document.body.style.cursor = 'grabbing'
|
|
1096
|
-
|
|
1097
|
-
sfxEmitter.emit('sfx:item-pick')
|
|
1098
|
-
useEditor.getState().setMovingNode(wall)
|
|
1099
|
-
useInteractionScope.getState().endIf((s) => s.kind === 'reshaping' && s.reshape === 'endpoint')
|
|
1100
|
-
useInteractionScope.getState().endIf((s) => s.kind === 'reshaping' && s.reshape === 'curve')
|
|
1101
|
-
// Keep the wall selected so it stays the active item once the move
|
|
1102
|
-
// commits; the `!movingNode` guard on the handles hides them mid-drag.
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
return (
|
|
1106
|
-
<group position={handle.position} rotation={[0, handle.rotationY, 0]}>
|
|
1107
|
-
<InvisibleHandleHitArea
|
|
1108
|
-
geometry={hitGeometry}
|
|
1109
|
-
material={hitMaterial}
|
|
1110
|
-
onPointerDown={activateWallMove}
|
|
1111
|
-
onPointerEnter={(event) => {
|
|
1112
|
-
event.stopPropagation()
|
|
1113
|
-
setIsHovered(true)
|
|
1114
|
-
document.body.style.cursor = 'grab'
|
|
1115
|
-
}}
|
|
1116
|
-
onPointerLeave={(event) => {
|
|
1117
|
-
event.stopPropagation()
|
|
1118
|
-
setIsHovered(false)
|
|
1119
|
-
if (document.body.style.cursor === 'grab') {
|
|
1120
|
-
document.body.style.cursor = ''
|
|
1121
|
-
}
|
|
1122
|
-
}}
|
|
1123
|
-
scale={baseScale}
|
|
1124
|
-
/>
|
|
1125
|
-
<mesh
|
|
1126
|
-
// Pass geometry as a prop (not `<primitive attach="geometry">`)
|
|
1127
|
-
// so the mesh is never rendered with R3F's default empty
|
|
1128
|
-
// `BufferGeometry`. Combined with `frustumCulled={false}`, the
|
|
1129
|
-
// primitive-attach path emits a `Draw(0, 1, 0, 0)` on the first
|
|
1130
|
-
// frame and WebGPU flags "Vertex buffer slot 0 ... was not set".
|
|
1131
|
-
frustumCulled={false}
|
|
1132
|
-
geometry={arrowGeometry}
|
|
1133
|
-
material={arrowMaterial}
|
|
1134
|
-
raycast={NO_RAYCAST}
|
|
1135
|
-
renderOrder={1002}
|
|
1136
|
-
scale={scale}
|
|
1137
|
-
/>
|
|
1138
|
-
</group>
|
|
1139
|
-
)
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
function FenceMoveArrowHandle({ fence, handle }: { fence: FenceNode; handle: WallMoveHandle }) {
|
|
1143
|
-
const [isHovered, setIsHovered] = useState(false)
|
|
1144
|
-
const arrowGeometry = useMemo(() => createArrowHandleGeometry(), [])
|
|
1145
|
-
const hitGeometry = useMemo(() => createArrowHitAreaGeometry(), [])
|
|
1146
|
-
const hitMaterial = useInvisibleHitAreaMaterial()
|
|
1147
|
-
const arrowMaterial = useMemo(
|
|
1148
|
-
() =>
|
|
1149
|
-
new MeshBasicNodeMaterial({
|
|
1150
|
-
color: new Color(ARROW_COLOR),
|
|
1151
|
-
side: DoubleSide,
|
|
1152
|
-
depthTest: false,
|
|
1153
|
-
depthWrite: false,
|
|
1154
|
-
transparent: true,
|
|
1155
|
-
opacity: 1,
|
|
1156
|
-
}),
|
|
1150
|
+
},
|
|
1157
1151
|
[],
|
|
1158
1152
|
)
|
|
1159
|
-
const { camera } = useThree()
|
|
1160
|
-
|
|
1161
|
-
const zoom = camera instanceof OrthographicCamera ? 1 / camera.zoom : 1
|
|
1162
|
-
const baseScale = zoom * ARROW_SCALE
|
|
1163
|
-
const scale = (isHovered ? 1.12 : 1) * baseScale
|
|
1164
|
-
|
|
1165
|
-
useEffect(() => {
|
|
1166
|
-
arrowMaterial.color.set(isHovered ? ARROW_HOVER_COLOR : ARROW_COLOR)
|
|
1167
|
-
}, [arrowMaterial, isHovered])
|
|
1168
|
-
|
|
1169
|
-
useEffect(() => {
|
|
1170
|
-
return () => {
|
|
1171
|
-
if (document.body.style.cursor === 'grab' || document.body.style.cursor === 'grabbing') {
|
|
1172
|
-
document.body.style.cursor = ''
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
}, [])
|
|
1176
1153
|
|
|
1177
1154
|
useEffect(() => () => arrowGeometry.dispose(), [arrowGeometry])
|
|
1178
1155
|
useEffect(() => () => hitGeometry.dispose(), [hitGeometry])
|
|
@@ -1212,7 +1189,7 @@ function FenceMoveArrowHandle({ fence, handle }: { fence: FenceNode; handle: Wal
|
|
|
1212
1189
|
scale={baseScale}
|
|
1213
1190
|
/>
|
|
1214
1191
|
<mesh
|
|
1215
|
-
// Pass geometry as a prop — see
|
|
1192
|
+
// Pass geometry as a prop — see WallHeightArrowHandle for the
|
|
1216
1193
|
// WebGPU "Vertex buffer slot 0 ... was not set" rationale.
|
|
1217
1194
|
frustumCulled={false}
|
|
1218
1195
|
geometry={arrowGeometry}
|
|
@@ -1225,32 +1202,6 @@ function FenceMoveArrowHandle({ fence, handle }: { fence: FenceNode; handle: Wal
|
|
|
1225
1202
|
)
|
|
1226
1203
|
}
|
|
1227
1204
|
|
|
1228
|
-
function getWallMoveHandles(wall: WallNode, nodes: Record<string, AnyNode>): WallMoveHandle[] {
|
|
1229
|
-
const dx = wall.end[0] - wall.start[0]
|
|
1230
|
-
const dz = wall.end[1] - wall.start[1]
|
|
1231
|
-
const length = Math.hypot(dx, dz)
|
|
1232
|
-
|
|
1233
|
-
if (length < 1e-6) {
|
|
1234
|
-
return []
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
const frame = isCurvedWall(wall) ? getWallCurveFrameAt(wall, 0.5) : null
|
|
1238
|
-
const normal: [number, number] = frame
|
|
1239
|
-
? [frame.normal.x, frame.normal.y]
|
|
1240
|
-
: [-dz / length, dx / length]
|
|
1241
|
-
const midpoint: [number, number] = frame
|
|
1242
|
-
? [frame.point.x, frame.point.y]
|
|
1243
|
-
: [(wall.start[0] + wall.end[0]) / 2, (wall.start[1] + wall.end[1]) / 2]
|
|
1244
|
-
const wallHeight = getWallEffectiveHeightForNodes(wall, nodes)
|
|
1245
|
-
const handleHeight = Math.max(wallHeight - HANDLE_TOP_INSET, HANDLE_MIN_HEIGHT)
|
|
1246
|
-
const offset = Math.max(getWallThickness(wall) / 2 + HANDLE_OFFSET, HANDLE_MIN_OFFSET)
|
|
1247
|
-
|
|
1248
|
-
return [
|
|
1249
|
-
buildWallMoveHandle('front', midpoint, normal, offset, handleHeight),
|
|
1250
|
-
buildWallMoveHandle('back', midpoint, [-normal[0], -normal[1]], offset, handleHeight),
|
|
1251
|
-
]
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
1205
|
function WallMoveSideHandlesForFence({ fence }: { fence: FenceNode }) {
|
|
1255
1206
|
const [levelObject, setLevelObject] = useState<Object3D | null>(() =>
|
|
1256
1207
|
fence.parentId ? (sceneRegistry.nodes.get(fence.parentId) ?? null) : null,
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
type Group,
|
|
12
12
|
type Mesh,
|
|
13
13
|
MeshBasicMaterial,
|
|
14
|
-
Vector3,
|
|
15
14
|
} from 'three'
|
|
16
15
|
import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu'
|
|
17
16
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
17
|
+
import { copyOverlayWorldTransform } from '../../lib/overlay-transform'
|
|
18
18
|
|
|
19
19
|
// How far the outline sits outside the opening's own extents, so it reads as
|
|
20
20
|
// a frame *around* the opening rather than coinciding with its edges.
|
|
@@ -23,7 +23,6 @@ const PAD = 0.05
|
|
|
23
23
|
const ACCENT = 0x83_81_ed
|
|
24
24
|
|
|
25
25
|
const NO_RAYCAST = () => null
|
|
26
|
-
const scratchScale = new Vector3()
|
|
27
26
|
|
|
28
27
|
// Indigo accent matching the resize-arrow handles — deliberately distinct
|
|
29
28
|
// from the white selection outline so the highlight reads as "editable child
|
|
@@ -182,7 +181,7 @@ function OpeningHighlight({ openingId, depth }: { openingId: string; depth: numb
|
|
|
182
181
|
return
|
|
183
182
|
}
|
|
184
183
|
group.visible = true
|
|
185
|
-
obj
|
|
184
|
+
copyOverlayWorldTransform(obj, group)
|
|
186
185
|
})
|
|
187
186
|
|
|
188
187
|
if (!isOpening || !outlineGeometry || !fillGeometry) return null
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { sceneRegistry } from '@pascal-app/core'
|
|
4
|
+
import { type ThreeEvent, useThree } from '@react-three/fiber'
|
|
5
|
+
import { useState } from 'react'
|
|
6
|
+
import { OrthographicCamera, Plane, type Ray, Vector2, Vector3 } from 'three'
|
|
7
|
+
import { runWallPushDrag, type WallPushHandle } from '../../lib/room-handle-drag'
|
|
8
|
+
import { getSpatialPointerId } from '../../lib/spatial-pointer-input'
|
|
9
|
+
import { suppressBoxSelectForPointer } from '../tools/select/box-select-state'
|
|
10
|
+
import { ARROW_SCALE, HandleArrow } from './handles/handle-arrow'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The push/pull arrow of a wall stretch — a selected wall's side arrows and a
|
|
14
|
+
* selected room's boundary arrows are this one component, driven by the one
|
|
15
|
+
* `runWallPushDrag`. Rendered in the level frame. `zoneId` binds a room's
|
|
16
|
+
* arrow to its room.
|
|
17
|
+
*/
|
|
18
|
+
export function WallPushArrow({
|
|
19
|
+
handle,
|
|
20
|
+
levelId,
|
|
21
|
+
zoneId,
|
|
22
|
+
}: {
|
|
23
|
+
handle: WallPushHandle
|
|
24
|
+
levelId: string
|
|
25
|
+
zoneId?: string
|
|
26
|
+
}) {
|
|
27
|
+
const [hover, setHover] = useState(false)
|
|
28
|
+
const { camera, raycaster, gl } = useThree()
|
|
29
|
+
const zoom = camera instanceof OrthographicCamera ? 1 / camera.zoom : 1
|
|
30
|
+
const onPointerDown = (event: ThreeEvent<PointerEvent>) => {
|
|
31
|
+
if (event.button !== 0) return
|
|
32
|
+
event.stopPropagation()
|
|
33
|
+
suppressBoxSelectForPointer(event)
|
|
34
|
+
const level = sceneRegistry.nodes.get(levelId)
|
|
35
|
+
if (!level) return
|
|
36
|
+
level.updateWorldMatrix(true, false)
|
|
37
|
+
const toLocal = level.matrixWorld.clone().invert()
|
|
38
|
+
const origin = new Vector3(handle.position[0], handle.height, handle.position[1]).applyMatrix4(
|
|
39
|
+
level.matrixWorld,
|
|
40
|
+
)
|
|
41
|
+
const up = new Vector3(0, 1, 0).transformDirection(level.matrixWorld)
|
|
42
|
+
const plane = new Plane().setFromNormalAndCoplanarPoint(up, origin)
|
|
43
|
+
const along = (ray: Ray) => {
|
|
44
|
+
const hit = ray.intersectPlane(plane, new Vector3())
|
|
45
|
+
if (!hit) return null
|
|
46
|
+
const local = hit.applyMatrix4(toLocal)
|
|
47
|
+
return (
|
|
48
|
+
(local.x - handle.position[0]) * handle.outward[0] +
|
|
49
|
+
(local.z - handle.position[1]) * handle.outward[1]
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
const rayAt = (clientX: number, clientY: number) => {
|
|
53
|
+
const rect = gl.domElement.getBoundingClientRect()
|
|
54
|
+
raycaster.setFromCamera(
|
|
55
|
+
new Vector2(
|
|
56
|
+
((clientX - rect.left) / rect.width) * 2 - 1,
|
|
57
|
+
-((clientY - rect.top) / rect.height) * 2 + 1,
|
|
58
|
+
),
|
|
59
|
+
camera,
|
|
60
|
+
)
|
|
61
|
+
return raycaster.ray
|
|
62
|
+
}
|
|
63
|
+
const spatialPointerId = getSpatialPointerId(event.nativeEvent)
|
|
64
|
+
const from = along(
|
|
65
|
+
spatialPointerId ? event.ray : rayAt(event.nativeEvent.clientX, event.nativeEvent.clientY),
|
|
66
|
+
)
|
|
67
|
+
if (from === null) return
|
|
68
|
+
runWallPushDrag({
|
|
69
|
+
handle,
|
|
70
|
+
levelId,
|
|
71
|
+
zoneId,
|
|
72
|
+
from,
|
|
73
|
+
along: (clientX, clientY) => along(rayAt(clientX, clientY)),
|
|
74
|
+
spatial: spatialPointerId ? { pointerId: spatialPointerId, along } : undefined,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
return (
|
|
78
|
+
<HandleArrow
|
|
79
|
+
cursor="grab"
|
|
80
|
+
hover={hover}
|
|
81
|
+
onHoverChange={setHover}
|
|
82
|
+
onPointerDown={onPointerDown}
|
|
83
|
+
placement={{
|
|
84
|
+
position: [handle.position[0], handle.height, handle.position[1]],
|
|
85
|
+
rotation: [0, Math.atan2(-handle.outward[1], handle.outward[0]), 0],
|
|
86
|
+
baseScale: zoom * ARROW_SCALE,
|
|
87
|
+
}}
|
|
88
|
+
shape="chevron"
|
|
89
|
+
thin
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|