@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
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type AnyNodeId,
|
|
5
|
+
getStoredLevelHeight,
|
|
6
|
+
type Point,
|
|
7
|
+
type SlabNode,
|
|
8
|
+
sceneRegistry,
|
|
9
|
+
upperFloorHeightControl,
|
|
10
|
+
useScene,
|
|
11
|
+
} from '@pascal-app/core'
|
|
12
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
13
|
+
import { Html } from '@react-three/drei'
|
|
14
|
+
import { type ThreeEvent, useFrame, useThree } from '@react-three/fiber'
|
|
15
|
+
import { useRef, useState } from 'react'
|
|
16
|
+
import { type Group, Plane, Vector2, Vector3 } from 'three'
|
|
17
|
+
import {
|
|
18
|
+
beginFootprintHeightPreview,
|
|
19
|
+
footprintHeightMinimum,
|
|
20
|
+
footprintHeightValue,
|
|
21
|
+
THICK_FLOOR_HINT,
|
|
22
|
+
thickFloorAdvice,
|
|
23
|
+
} from '../../lib/floor-footprints'
|
|
24
|
+
import { formatLinearMeasurement } from '../../lib/measurements'
|
|
25
|
+
import { runRoomHandleDrag } from '../../lib/room-handle-drag'
|
|
26
|
+
import useEditor, { isGridSnapActive } from '../../store/use-editor'
|
|
27
|
+
import useInteractionScope from '../../store/use-interaction-scope'
|
|
28
|
+
import { suppressBoxSelectForPointer } from '../tools/select/box-select-state'
|
|
29
|
+
import { ElevationArrows, outlineCentroid, useHandleScale } from './room-handles'
|
|
30
|
+
|
|
31
|
+
export const FOOTPRINT_HEIGHT_DRAG_LABEL = 'footprint-height'
|
|
32
|
+
|
|
33
|
+
const PILL =
|
|
34
|
+
'pointer-events-none flex items-center gap-2 whitespace-nowrap rounded-full border border-border/60 bg-background/90 px-4 py-1.5 text-xs tabular-nums shadow-sm backdrop-blur'
|
|
35
|
+
const NOTICE =
|
|
36
|
+
'pointer-events-none max-w-72 rounded-full border border-border/60 bg-background/95 px-3 py-1 text-center text-muted-foreground text-xs shadow-md backdrop-blur-md'
|
|
37
|
+
|
|
38
|
+
/** The selected footprint floor (base plate), when it is the one selection in select mode. */
|
|
39
|
+
function useSelectedFootprint(): SlabNode | null {
|
|
40
|
+
const selectedId = useViewer((s) =>
|
|
41
|
+
s.selection.selectedIds.length === 1 ? s.selection.selectedIds[0] : null,
|
|
42
|
+
)
|
|
43
|
+
const enabled = useEditor((s) => s.mode === 'select')
|
|
44
|
+
const idle = useInteractionScope(
|
|
45
|
+
(s) =>
|
|
46
|
+
s.scope.kind === 'idle' ||
|
|
47
|
+
(s.scope.kind === 'handle-drag' && s.scope.handle === FOOTPRINT_HEIGHT_DRAG_LABEL),
|
|
48
|
+
)
|
|
49
|
+
const plate = useScene((s) => {
|
|
50
|
+
const node = selectedId ? s.nodes[selectedId as AnyNodeId] : undefined
|
|
51
|
+
return node?.type === 'slab' && node.plateRole === 'base' ? node : null
|
|
52
|
+
})
|
|
53
|
+
return enabled && idle ? plate : null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A selected footprint floor's one control in 3D: the rooms' arrow–cube–arrow
|
|
58
|
+
* at the middle of the footprint, dragging its height above the ground. The
|
|
59
|
+
* building follows live; the release lands as one undo step. Refused heights
|
|
60
|
+
* (core's conflict, e.g. an upper floor over another footprint) show beside it.
|
|
61
|
+
*/
|
|
62
|
+
export function FootprintHeightHandle() {
|
|
63
|
+
const plate = useSelectedFootprint()
|
|
64
|
+
const root = useRef<Group>(null)
|
|
65
|
+
useFrame(() => {
|
|
66
|
+
const level = plate?.parentId ? sceneRegistry.nodes.get(plate.parentId) : null
|
|
67
|
+
if (!root.current) return
|
|
68
|
+
root.current.visible = !!level
|
|
69
|
+
if (level) {
|
|
70
|
+
level.updateWorldMatrix(true, false)
|
|
71
|
+
root.current.matrix.copy(level.matrixWorld)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
if (!plate) return null
|
|
75
|
+
return (
|
|
76
|
+
<group matrixAutoUpdate={false} ref={root}>
|
|
77
|
+
<FootprintHeightArrows plate={plate} />
|
|
78
|
+
</group>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function FootprintHeightArrows({ plate }: { plate: SlabNode }) {
|
|
83
|
+
const { camera, raycaster, gl } = useThree()
|
|
84
|
+
const zoom = useHandleScale()
|
|
85
|
+
const unit = useViewer((s) => s.unit)
|
|
86
|
+
const metricNotation = useViewer((s) => s.metricNotation)
|
|
87
|
+
const [hover, setHover] = useState(false)
|
|
88
|
+
const [dragValue, setDragValue] = useState<number | null>(null)
|
|
89
|
+
const [notice, setNotice] = useState<string | null>(null)
|
|
90
|
+
const [hint, setHint] = useState<string | null>(null)
|
|
91
|
+
const upper = useScene((s) => upperFloorHeightControl(s.nodes, plate) !== null)
|
|
92
|
+
const anchor: Point = outlineCentroid([plate.polygon as Point[], ...(plate.holes as Point[][])])
|
|
93
|
+
const elevation = plate.elevation
|
|
94
|
+
|
|
95
|
+
const onPointerDown = (event: ThreeEvent<PointerEvent>) => {
|
|
96
|
+
if (event.button !== 0) return
|
|
97
|
+
event.stopPropagation()
|
|
98
|
+
suppressBoxSelectForPointer(event)
|
|
99
|
+
const levelId = plate.parentId
|
|
100
|
+
const level = levelId ? sceneRegistry.nodes.get(levelId) : null
|
|
101
|
+
if (!(levelId && level)) return
|
|
102
|
+
level.updateWorldMatrix(true, false)
|
|
103
|
+
const toLocal = level.matrixWorld.clone().invert()
|
|
104
|
+
const world = new Vector3(anchor[0], elevation, anchor[1]).applyMatrix4(level.matrixWorld)
|
|
105
|
+
const normal = camera.getWorldPosition(new Vector3()).sub(world).setY(0)
|
|
106
|
+
if (normal.lengthSq() === 0) return
|
|
107
|
+
const plane = new Plane().setFromNormalAndCoplanarPoint(normal.normalize(), world)
|
|
108
|
+
const localY = (clientX: number, clientY: number) => {
|
|
109
|
+
const rect = gl.domElement.getBoundingClientRect()
|
|
110
|
+
raycaster.setFromCamera(
|
|
111
|
+
new Vector2(
|
|
112
|
+
((clientX - rect.left) / rect.width) * 2 - 1,
|
|
113
|
+
-((clientY - rect.top) / rect.height) * 2 + 1,
|
|
114
|
+
),
|
|
115
|
+
camera,
|
|
116
|
+
)
|
|
117
|
+
const hit = raycaster.ray.intersectPlane(plane, new Vector3())
|
|
118
|
+
return hit ? hit.applyMatrix4(toLocal).y : null
|
|
119
|
+
}
|
|
120
|
+
const startY = localY(event.nativeEvent.clientX, event.nativeEvent.clientY)
|
|
121
|
+
if (startY === null) return
|
|
122
|
+
const nodes = () => useScene.getState().nodes
|
|
123
|
+
// Heights are read against the plate as the drag found it (the preview moves it).
|
|
124
|
+
const startNodes = nodes()
|
|
125
|
+
const start = footprintHeightValue(startNodes, plate)
|
|
126
|
+
const min = footprintHeightMinimum(startNodes, plate)
|
|
127
|
+
const levelNode = nodes()[levelId as AnyNodeId]
|
|
128
|
+
const max = levelNode?.type === 'level' ? getStoredLevelHeight(levelNode) - 0.5 : 3
|
|
129
|
+
const step = isGridSnapActive() ? 0.05 : 0.01
|
|
130
|
+
let value = start
|
|
131
|
+
const preview = beginFootprintHeightPreview(plate.id)
|
|
132
|
+
setNotice(null)
|
|
133
|
+
setDragValue(start)
|
|
134
|
+
runRoomHandleDrag({
|
|
135
|
+
label: FOOTPRINT_HEIGHT_DRAG_LABEL,
|
|
136
|
+
nodeId: plate.id,
|
|
137
|
+
levelId,
|
|
138
|
+
requires: [plate.id],
|
|
139
|
+
sample: (x, y) => {
|
|
140
|
+
const pointerY = localY(x, y)
|
|
141
|
+
if (pointerY === null) return null
|
|
142
|
+
const raw = start + pointerY - startY
|
|
143
|
+
return Math.max(min, Math.min(max, Math.round(raw / step) * step))
|
|
144
|
+
},
|
|
145
|
+
onValue: (next) => {
|
|
146
|
+
if (Math.abs(next - value) < 1e-6) return
|
|
147
|
+
// Live: the building follows, outside history until the release.
|
|
148
|
+
const refused = preview.preview(next)
|
|
149
|
+
setNotice(refused)
|
|
150
|
+
setHint(thickFloorAdvice(startNodes, plate, next) ? THICK_FLOOR_HINT : null)
|
|
151
|
+
if (!refused) value = next
|
|
152
|
+
setDragValue(next)
|
|
153
|
+
},
|
|
154
|
+
onCommit: () => {
|
|
155
|
+
setNotice(preview.commit(value))
|
|
156
|
+
setDragValue(null)
|
|
157
|
+
setHint(null)
|
|
158
|
+
},
|
|
159
|
+
onCancel: () => {
|
|
160
|
+
preview.cancel()
|
|
161
|
+
setDragValue(null)
|
|
162
|
+
setHint(null)
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const shown = dragValue ?? null
|
|
168
|
+
return (
|
|
169
|
+
<group>
|
|
170
|
+
<ElevationArrows
|
|
171
|
+
anchor={[anchor[0], elevation, anchor[1]]}
|
|
172
|
+
hover={hover || shown !== null}
|
|
173
|
+
onHoverChange={setHover}
|
|
174
|
+
onPointerDown={onPointerDown}
|
|
175
|
+
zoom={zoom}
|
|
176
|
+
/>
|
|
177
|
+
{(shown !== null || notice || hint) && (
|
|
178
|
+
<Html
|
|
179
|
+
center
|
|
180
|
+
position={[anchor[0], elevation + 0.75 * zoom, anchor[1]]}
|
|
181
|
+
zIndexRange={[25, 0]}
|
|
182
|
+
>
|
|
183
|
+
<div className="flex flex-col items-center gap-1" data-footprint-height-pill>
|
|
184
|
+
{shown !== null && (
|
|
185
|
+
<div className={PILL}>
|
|
186
|
+
<span className="font-medium text-foreground">
|
|
187
|
+
{upper ? 'Floor top' : 'Foundation'}
|
|
188
|
+
</span>
|
|
189
|
+
<span className="text-muted-foreground">
|
|
190
|
+
{formatLinearMeasurement(shown, unit, metricNotation)}
|
|
191
|
+
</span>
|
|
192
|
+
</div>
|
|
193
|
+
)}
|
|
194
|
+
{(notice ?? hint) && (
|
|
195
|
+
<div className={NOTICE} data-footprint-height-notice role="status">
|
|
196
|
+
{notice ?? hint}
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
</Html>
|
|
201
|
+
)}
|
|
202
|
+
</group>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { type AnyNodeId, emitter, type GridEvent, sceneRegistry } from '@pascal-app/core'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
GRID_LAYER,
|
|
6
|
+
getSceneTheme,
|
|
7
|
+
useImmersiveXRPresentation,
|
|
8
|
+
useViewer,
|
|
9
|
+
} from '@pascal-app/viewer'
|
|
5
10
|
import { useFrame } from '@react-three/fiber'
|
|
6
11
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
7
12
|
import { DoubleSide, type Mesh, PlaneGeometry, Quaternion, Vector2, Vector3 } from 'three'
|
|
@@ -10,9 +15,12 @@ import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
|
10
15
|
import { useCeilingEvents } from '../../hooks/use-ceiling-events'
|
|
11
16
|
import { useGridEvents } from '../../hooks/use-grid-events'
|
|
12
17
|
import { getPlacementSurface, usesOrientedPlacementPlane } from '../../lib/active-placement-surface'
|
|
13
|
-
import
|
|
18
|
+
import { gridLocalNormal, gridLocalPoint } from '../../lib/grid-frame'
|
|
19
|
+
import useEditor, { getActiveSnapContext, isGridSnapActive } from '../../store/use-editor'
|
|
14
20
|
import { getMovingNode } from '../../store/use-interaction-scope'
|
|
15
21
|
|
|
22
|
+
export const EDITOR_GRID_INPUT_NAME = 'pascal-editor-grid-input'
|
|
23
|
+
|
|
16
24
|
// Reveal radius (m) of the cursor-local grid patch shown while placing/moving in
|
|
17
25
|
// grid-snap mode — much tighter than the idle reveal so only the area you're
|
|
18
26
|
// about to snap into lights up.
|
|
@@ -45,6 +53,7 @@ export const Grid = ({
|
|
|
45
53
|
fadeStrength?: number
|
|
46
54
|
revealRadius?: number
|
|
47
55
|
}) => {
|
|
56
|
+
const immersive = useImmersiveXRPresentation()
|
|
48
57
|
const isDark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark')
|
|
49
58
|
|
|
50
59
|
// Use slightly lighter colors for dark themes' grid to make it apparent
|
|
@@ -60,6 +69,9 @@ export const Grid = ({
|
|
|
60
69
|
const wallCursorRef = useRef(new Vector3())
|
|
61
70
|
// Scratch for deriving a moving wall-item's host normal from its mesh.
|
|
62
71
|
const wallNormalRef = useRef(new Vector3())
|
|
72
|
+
const localSurfaceRef = useRef(new Vector3())
|
|
73
|
+
const localNormalRef = useRef(new Vector3())
|
|
74
|
+
const localCursorRef = useRef(new Vector3())
|
|
63
75
|
// Last Y pushed to `gridY` state, so the per-frame surface follow only triggers
|
|
64
76
|
// a React re-render when the height actually changes (not every frame).
|
|
65
77
|
const lastGridYRef = useRef<number | null>(null)
|
|
@@ -176,10 +188,11 @@ export const Grid = ({
|
|
|
176
188
|
// laid-flat orientation maps `positionLocal.y` to world `-Z` relative to the
|
|
177
189
|
// mesh origin. The cursor is recomputed every frame from the stored world hit
|
|
178
190
|
// so the reveal stays put regardless of where the mesh origin sits.
|
|
179
|
-
const lastWorldCursorRef = useRef<
|
|
191
|
+
const lastWorldCursorRef = useRef<Vector3 | null>(null)
|
|
180
192
|
useEffect(() => {
|
|
181
193
|
const onGridMove = (event: GridEvent) => {
|
|
182
|
-
lastWorldCursorRef.current
|
|
194
|
+
lastWorldCursorRef.current ??= new Vector3()
|
|
195
|
+
lastWorldCursorRef.current.set(...event.position)
|
|
183
196
|
}
|
|
184
197
|
|
|
185
198
|
emitter.on('grid:move', onGridMove)
|
|
@@ -194,7 +207,12 @@ export const Grid = ({
|
|
|
194
207
|
if (levelId) {
|
|
195
208
|
const levelMesh = sceneRegistry.nodes.get(levelId)
|
|
196
209
|
if (levelMesh) {
|
|
197
|
-
|
|
210
|
+
levelMesh.getWorldPosition(worldPosRef.current)
|
|
211
|
+
levelY = gridLocalPoint(
|
|
212
|
+
gridRef.current.parent,
|
|
213
|
+
worldPosRef.current,
|
|
214
|
+
localSurfaceRef.current,
|
|
215
|
+
).y
|
|
198
216
|
}
|
|
199
217
|
}
|
|
200
218
|
|
|
@@ -229,6 +247,10 @@ export const Grid = ({
|
|
|
229
247
|
}
|
|
230
248
|
|
|
231
249
|
const gridMesh = gridRef.current
|
|
250
|
+
if (surfacePoint) {
|
|
251
|
+
surfacePoint = gridLocalPoint(gridMesh.parent, surfacePoint, localSurfaceRef.current)
|
|
252
|
+
surfaceNormal = gridLocalNormal(gridMesh.parent, surfaceNormal, localNormalRef.current)
|
|
253
|
+
}
|
|
232
254
|
const onOrientedPlane = surfacePoint != null && usesOrientedPlacementPlane(surfaceNormal)
|
|
233
255
|
if (onOrientedPlane && surfacePoint) {
|
|
234
256
|
// Surface-anchored lattice: orient the plane into the host and pin the mesh to
|
|
@@ -269,7 +291,8 @@ export const Grid = ({
|
|
|
269
291
|
gridMesh.quaternion.copy(HORIZONTAL_QUATERNION)
|
|
270
292
|
const world = lastWorldCursorRef.current
|
|
271
293
|
if (world) {
|
|
272
|
-
|
|
294
|
+
const local = gridLocalPoint(gridMesh.parent, world, localCursorRef.current)
|
|
295
|
+
cursorPositionRef.current.set(local.x, -local.z)
|
|
273
296
|
}
|
|
274
297
|
if (lastGridYRef.current !== targetY) {
|
|
275
298
|
lastGridYRef.current = targetY
|
|
@@ -293,7 +316,7 @@ export const Grid = ({
|
|
|
293
316
|
// from the interaction scope OR the armed build tool and is true only when
|
|
294
317
|
// that context resolves to grid, so it IS the gate. (Previously this also
|
|
295
318
|
// required a ghost in flight, so a merely-armed draft tool showed nothing.)
|
|
296
|
-
const snapPatchVisible = isGridSnapActive()
|
|
319
|
+
const snapPatchVisible = isGridSnapActive() || (immersive && getActiveSnapContext() !== null)
|
|
297
320
|
revealRadiusUniform.value = PLACEMENT_REVEAL_RADIUS
|
|
298
321
|
baseAlphaUniform.value = 0
|
|
299
322
|
cellSizeUniform.value = useEditor.getState().gridSnapStep
|
|
@@ -320,6 +343,7 @@ export const Grid = ({
|
|
|
320
343
|
geometry={geometry}
|
|
321
344
|
layers={GRID_LAYER}
|
|
322
345
|
material={material}
|
|
346
|
+
name={EDITOR_GRID_INPUT_NAME}
|
|
323
347
|
ref={gridRef}
|
|
324
348
|
renderOrder={1}
|
|
325
349
|
/>
|
|
@@ -18,10 +18,13 @@ import { markPerfAction, useViewer } from '@pascal-app/viewer'
|
|
|
18
18
|
import { Plane, Vector2, Vector3 } from 'three'
|
|
19
19
|
import { GROUP_MOVE_DRAG_LABEL } from '../../lib/contextual-help'
|
|
20
20
|
import { clientToPlan } from '../../lib/floorplan/plan-coords'
|
|
21
|
+
import { deleteSelectedSeparator, requestRoomDeletion } from '../../lib/room-structure-commands'
|
|
22
|
+
import { captureElementActionOrigin, completeElementAction } from '../../lib/room-zone-routing'
|
|
21
23
|
import {
|
|
22
24
|
copySelectedNodesToEditorClipboard,
|
|
23
25
|
duplicateNodesToLevel,
|
|
24
26
|
getEditorClipboardSnapshot,
|
|
27
|
+
type PasteRefusal,
|
|
25
28
|
pasteSystemEditorClipboardToLevel,
|
|
26
29
|
} from '../../lib/scene-clipboard'
|
|
27
30
|
import { emitDeleteSFX, sfxEmitter } from '../../lib/sfx-bus'
|
|
@@ -33,13 +36,13 @@ import useEditor, {
|
|
|
33
36
|
isGridSnapActive,
|
|
34
37
|
isMagneticSnapActive,
|
|
35
38
|
} from '../../store/use-editor'
|
|
39
|
+
import useFloorplanMode from '../../store/use-floorplan-mode'
|
|
36
40
|
import useInteractionScope from '../../store/use-interaction-scope'
|
|
37
41
|
import { useFloorplanGroupDrag } from '../editor-2d/floorplan-group-move'
|
|
38
42
|
import {
|
|
39
43
|
classifyParticipant,
|
|
40
44
|
collectParticipants,
|
|
41
45
|
computeGroupBox,
|
|
42
|
-
expandToComponent,
|
|
43
46
|
groupPlanBounds,
|
|
44
47
|
levelFrame,
|
|
45
48
|
planBoundsCenter,
|
|
@@ -83,8 +86,8 @@ export function canGroupPickUp(): boolean {
|
|
|
83
86
|
* until a click commits, mirroring the single-node `movingNode` flow. Returns
|
|
84
87
|
* false when the selection holds no transformable participants.
|
|
85
88
|
*
|
|
86
|
-
* `scopeToSelection`
|
|
87
|
-
*
|
|
89
|
+
* `scopeToSelection` drops the welded-neighbor endpoints, so connected walls
|
|
90
|
+
* don't stretch along with the move. The
|
|
88
91
|
* Duplicate flow needs this: its clones sit EXACTLY on the originals, so
|
|
89
92
|
* junction coincidence would otherwise weld the originals into the pick-up
|
|
90
93
|
* and drag them along with the copies.
|
|
@@ -96,10 +99,7 @@ export function startGroupPickUp(
|
|
|
96
99
|
const participantIds = groupParticipantIds()
|
|
97
100
|
if (participantIds.length === 0) return false
|
|
98
101
|
const nodes = useScene.getState().nodes
|
|
99
|
-
const
|
|
100
|
-
? participantIds
|
|
101
|
-
: expandToComponent(participantIds, nodes, levelId)
|
|
102
|
-
const collected = collectParticipants(fullIds, nodes, levelId)
|
|
102
|
+
const collected = collectParticipants(participantIds, nodes, levelId)
|
|
103
103
|
// Mutable: mid-carry R/T rotates these snapshots in place.
|
|
104
104
|
let starts = collected.starts
|
|
105
105
|
let links = opts.scopeToSelection ? [] : collected.links
|
|
@@ -107,8 +107,8 @@ export function startGroupPickUp(
|
|
|
107
107
|
const affectedIds: AnyNodeId[] = [...starts.map((s) => s.id), ...links.map((l) => l.id)]
|
|
108
108
|
|
|
109
109
|
const { inverse: frameInv } = levelFrame(levelId)
|
|
110
|
-
const restBox = computeGroupBox(
|
|
111
|
-
const startBounds = groupPlanBounds(
|
|
110
|
+
const restBox = computeGroupBox(participantIds)
|
|
111
|
+
const startBounds = groupPlanBounds(starts, frameInv)
|
|
112
112
|
if (!startBounds) return false
|
|
113
113
|
// Mutable: mid-carry R/T re-seeds the footprint around the same pivot.
|
|
114
114
|
let restBounds = startBounds
|
|
@@ -460,12 +460,39 @@ function removeUnusedPasteMaterials(materialIds: SceneMaterialId[]) {
|
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
+
const REFUSED_PASTE_REASONS: Record<PasteRefusal, [one: string, many: string]> = {
|
|
464
|
+
'no-access': [
|
|
465
|
+
'you no longer have access to the project it came from.',
|
|
466
|
+
'you no longer have access to the project they came from.',
|
|
467
|
+
],
|
|
468
|
+
'no-copies': [
|
|
469
|
+
'this version is not available to copy from the source project.',
|
|
470
|
+
'these versions are not available to copy from the source project.',
|
|
471
|
+
],
|
|
472
|
+
failed: ["it couldn't be copied. Try again.", "they couldn't be copied. Try again."],
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** Why scripted objects were left out of a paste, or of a build loaded from a file. */
|
|
476
|
+
export function refusedObjectsNotice(
|
|
477
|
+
count: number,
|
|
478
|
+
refusal: PasteRefusal,
|
|
479
|
+
action: 'pasted' | 'loaded' = 'pasted',
|
|
480
|
+
) {
|
|
481
|
+
const [one, many] = REFUSED_PASTE_REASONS[refusal]
|
|
482
|
+
return count === 1
|
|
483
|
+
? `1 object wasn't ${action}: ${one}`
|
|
484
|
+
: `${count} objects weren't ${action}: ${many}`
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let pastePending = false
|
|
488
|
+
|
|
463
489
|
/**
|
|
464
490
|
* Paste the Pascal scene payload from the browser clipboard onto the active
|
|
465
491
|
* level, then carry the clones under the cursor until click-to-place. Escape
|
|
466
492
|
* removes the uncommitted clones and any scene materials imported with them.
|
|
467
493
|
*/
|
|
468
494
|
export async function pasteSelectionAndPickUp(targetLevelId?: AnyNodeId): Promise<boolean> {
|
|
495
|
+
if (pastePending) return false
|
|
469
496
|
const activeScope = useInteractionScope.getState().scope
|
|
470
497
|
if (activeScope.kind === 'placing' || activeScope.kind === 'moving') {
|
|
471
498
|
emitter.emit('tool:cancel')
|
|
@@ -480,7 +507,18 @@ export async function pasteSelectionAndPickUp(targetLevelId?: AnyNodeId): Promis
|
|
|
480
507
|
// above there is nothing to abandon.
|
|
481
508
|
if (isBrushMode(useEditor.getState().mode)) useEditor.getState().setMode('select')
|
|
482
509
|
|
|
483
|
-
|
|
510
|
+
pastePending = true
|
|
511
|
+
let result: Awaited<ReturnType<typeof pasteSystemEditorClipboardToLevel>>
|
|
512
|
+
try {
|
|
513
|
+
result = await pasteSystemEditorClipboardToLevel(targetLevelId)
|
|
514
|
+
} finally {
|
|
515
|
+
pastePending = false
|
|
516
|
+
}
|
|
517
|
+
if (result?.refusal) {
|
|
518
|
+
useFloorplanMode
|
|
519
|
+
.getState()
|
|
520
|
+
.showNotice(refusedObjectsNotice(result.refusedIds.length, result.refusal))
|
|
521
|
+
}
|
|
484
522
|
if (!result || result.pastedIds.length === 0) return false
|
|
485
523
|
|
|
486
524
|
const discardPaste = () => {
|
|
@@ -551,7 +589,16 @@ export function cutSelectionToEditorClipboard(): boolean {
|
|
|
551
589
|
*/
|
|
552
590
|
export function deleteSelection(): boolean {
|
|
553
591
|
const selectedIds = useViewer.getState().selection.selectedIds as AnyNodeId[]
|
|
554
|
-
if (selectedIds.length === 0)
|
|
592
|
+
if (selectedIds.length === 0) {
|
|
593
|
+
const room = useEditor.getState().room
|
|
594
|
+
if (!room) return false
|
|
595
|
+
requestRoomDeletion(room.zoneId)
|
|
596
|
+
return true
|
|
597
|
+
}
|
|
598
|
+
// A drilled piece of a room (a wall, a separator, its ceiling) deleted: back to the room.
|
|
599
|
+
const origin = captureElementActionOrigin(selectedIds)
|
|
600
|
+
if (selectedIds.length === 1 && useScene.getState().nodes[selectedIds[0]!]?.type === 'separator')
|
|
601
|
+
return deleteSelectedSeparator(selectedIds[0]!, origin)
|
|
555
602
|
|
|
556
603
|
const commitDelete = () => {
|
|
557
604
|
const detail =
|
|
@@ -566,6 +613,7 @@ export function deleteSelection(): boolean {
|
|
|
566
613
|
}
|
|
567
614
|
useScene.getState().deleteNodes(selectedIds)
|
|
568
615
|
useViewer.getState().setSelection({ selectedIds: [] })
|
|
616
|
+
completeElementAction(origin)
|
|
569
617
|
}
|
|
570
618
|
|
|
571
619
|
if (selectedIds.length >= BULK_DELETE_THRESHOLD) {
|
|
@@ -15,7 +15,12 @@ import useSessionGroups, {
|
|
|
15
15
|
ungroupCurrentSelection,
|
|
16
16
|
} from '../../store/use-session-groups'
|
|
17
17
|
import { deleteSelection, duplicateSelectionAndPickUp, startGroupPickUp } from './group-actions'
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
classifyParticipant,
|
|
20
|
+
computeGroupBox,
|
|
21
|
+
computeGroupPlanBox,
|
|
22
|
+
levelFrame,
|
|
23
|
+
} from './group-transform-shared'
|
|
19
24
|
import { NodeActionMenu } from './node-action-menu'
|
|
20
25
|
import { useMeshSettleEpoch } from './use-mesh-settle-epoch'
|
|
21
26
|
|
|
@@ -69,15 +74,20 @@ export function GroupFloatingActionMenu() {
|
|
|
69
74
|
const anchor = useMemo(() => {
|
|
70
75
|
void meshEpoch
|
|
71
76
|
if (participantIds.length === 0) return null
|
|
72
|
-
const
|
|
73
|
-
const box = computeGroupBox(fullIds)
|
|
77
|
+
const box = computeGroupBox(participantIds)
|
|
74
78
|
if (!box) return null
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
// Over the level-frame box's centre (the dashed boxes' and gizmo's), so the
|
|
80
|
+
// pill stays on the box under a rotated building; the mesh box gives the top.
|
|
81
|
+
const levelId = useViewer.getState().selection.levelId
|
|
82
|
+
const plan = computeGroupPlanBox(participantIds, levelId)
|
|
83
|
+
const anchor = plan
|
|
84
|
+
? new THREE.Vector3((plan.minX + plan.maxX) / 2, 0, (plan.minZ + plan.maxZ) / 2).applyMatrix4(
|
|
85
|
+
levelFrame(levelId).matrix,
|
|
86
|
+
)
|
|
87
|
+
: new THREE.Vector3((box.min.x + box.max.x) / 2, 0, (box.min.z + box.max.z) / 2)
|
|
88
|
+
anchor.y = box.max.y + MENU_Y_OFFSET
|
|
89
|
+
return anchor
|
|
90
|
+
}, [participantIds, meshEpoch])
|
|
81
91
|
|
|
82
92
|
useFrame((state) => {
|
|
83
93
|
if (!(menuScaleRef.current && groupRef.current)) return
|
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
classifyParticipant,
|
|
32
32
|
collectParticipants,
|
|
33
33
|
computeGroupBox,
|
|
34
|
-
expandToComponent,
|
|
35
34
|
type GroupPlanBounds,
|
|
36
35
|
groupPlanBounds,
|
|
37
36
|
levelFrame,
|
|
@@ -107,16 +106,15 @@ export function armGroupMove3d(args: {
|
|
|
107
106
|
const participantIds = selectedIds.filter(
|
|
108
107
|
(id) => classifyParticipant(nodes[id as AnyNodeId], levelId, nodes) !== null,
|
|
109
108
|
)
|
|
110
|
-
//
|
|
111
|
-
const
|
|
112
|
-
const { starts, links } = collectParticipants(fullIds, nodes, levelId)
|
|
109
|
+
// Only the selection moves; connected walls stretch through `links`, as in 2D.
|
|
110
|
+
const { starts, links } = collectParticipants(participantIds, nodes, levelId)
|
|
113
111
|
if (starts.length === 0) return null
|
|
114
112
|
const affectedIds: AnyNodeId[] = [...starts.map((s) => s.id), ...links.map((l) => l.id)]
|
|
115
113
|
|
|
116
114
|
// Horizontal drag plane at the group's base; placements live in the level
|
|
117
115
|
// frame, so world-space plane hits convert through it (a rotated building
|
|
118
116
|
// would otherwise drift off-axis from the cursor).
|
|
119
|
-
const restBox = computeGroupBox(
|
|
117
|
+
const restBox = computeGroupBox(participantIds)
|
|
120
118
|
if (!restBox) return null
|
|
121
119
|
const plane = new Plane(new Vector3(0, 1, 0), -restBox.min.y)
|
|
122
120
|
const { inverse: frameInv } = levelFrame(levelId)
|
|
@@ -136,7 +134,7 @@ export function armGroupMove3d(args: {
|
|
|
136
134
|
if (n && !movingIdSet.has(nid)) staticNodes[nid] = n
|
|
137
135
|
}
|
|
138
136
|
const candidates = collectAlignmentAnchors(staticNodes, '', levelId)
|
|
139
|
-
const restBounds = groupPlanBounds(
|
|
137
|
+
const restBounds = groupPlanBounds(starts, frameInv)
|
|
140
138
|
if (!restBounds) return null
|
|
141
139
|
const restAnchors = bboxCornerAnchors(
|
|
142
140
|
'group-move',
|