@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
|
@@ -56,6 +56,8 @@ export function useCeilingEvents() {
|
|
|
56
56
|
const hoveredRef = useRef<string | null>(null)
|
|
57
57
|
|
|
58
58
|
useEffect(() => {
|
|
59
|
+
// XR placement is driven by the controller-ray bridge, never screen coordinates.
|
|
60
|
+
if (gl.xr.isPresenting) return
|
|
59
61
|
const canvas = gl.domElement
|
|
60
62
|
|
|
61
63
|
const isActive = (): boolean => {
|
|
@@ -70,6 +70,7 @@ test('grid moves throttle camera drags at 100 ms and resume immediately during t
|
|
|
70
70
|
render() {},
|
|
71
71
|
setSize() {},
|
|
72
72
|
setPixelRatio() {},
|
|
73
|
+
xr: { isPresenting: false },
|
|
73
74
|
} as unknown as WebGLRenderer,
|
|
74
75
|
camera,
|
|
75
76
|
frameloop: 'never',
|
|
@@ -199,6 +200,7 @@ test.each([
|
|
|
199
200
|
render() {},
|
|
200
201
|
setSize() {},
|
|
201
202
|
setPixelRatio() {},
|
|
203
|
+
xr: { isPresenting: false },
|
|
202
204
|
} as unknown as WebGLRenderer,
|
|
203
205
|
camera,
|
|
204
206
|
frameloop: 'never',
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type EventSuffix,
|
|
4
4
|
emitter,
|
|
5
5
|
type GridEvent,
|
|
6
|
+
hiddenWallPointerEventsHeld,
|
|
6
7
|
nodeRegistry,
|
|
7
8
|
sceneRegistry,
|
|
8
9
|
useScene,
|
|
@@ -58,6 +59,8 @@ export function useGridEvents(gridY: number) {
|
|
|
58
59
|
}, [gridY])
|
|
59
60
|
|
|
60
61
|
useEffect(() => {
|
|
62
|
+
// XR placement is driven by the controller-ray bridge, never screen coordinates.
|
|
63
|
+
if (gl.xr.isPresenting) return
|
|
61
64
|
const canvas = gl.domElement
|
|
62
65
|
setSurfaceRaycastLayers(raycaster.current.layers)
|
|
63
66
|
|
|
@@ -132,7 +135,10 @@ export function useGridEvents(gridY: number) {
|
|
|
132
135
|
// Architectural meshes are expensive to raycast and are meaningful only
|
|
133
136
|
// to an active placement/drafting interaction. Floor tools retain the
|
|
134
137
|
// ordinary terrain/grid intersection without scanning every wall.
|
|
135
|
-
const surfaceHit =
|
|
138
|
+
const surfaceHit =
|
|
139
|
+
semanticSurfaceQueryRef.current || hiddenWallPointerEventsHeld()
|
|
140
|
+
? getSurfaceIntersection()
|
|
141
|
+
: null
|
|
136
142
|
|
|
137
143
|
// A semantic architectural hit is the authoritative cursor position.
|
|
138
144
|
// Do not replace it with the terrain/grid intersection below: that would
|
|
@@ -244,16 +250,15 @@ export function useGridEvents(gridY: number) {
|
|
|
244
250
|
: undefined,
|
|
245
251
|
surfaceNormal: localNormal ? [localNormal.x, localNormal.y, localNormal.z] : undefined,
|
|
246
252
|
surfaceObject: point.surface?.object,
|
|
247
|
-
surfaceHit:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
: undefined,
|
|
253
|
+
surfaceHit: point.surface
|
|
254
|
+
? {
|
|
255
|
+
kind: point.surface.descriptor.kind,
|
|
256
|
+
hostId: point.surface.hostId,
|
|
257
|
+
face: classifiedFace?.face ?? 'unknown',
|
|
258
|
+
levelId: useViewer.getState().selection.levelId ?? undefined,
|
|
259
|
+
side: classifiedFace?.side,
|
|
260
|
+
}
|
|
261
|
+
: undefined,
|
|
257
262
|
nativeEvent: nativeEvent as any, // Type compatibility with ThreeEvent
|
|
258
263
|
}
|
|
259
264
|
|
|
@@ -10,20 +10,29 @@ import {
|
|
|
10
10
|
nodeRegistry,
|
|
11
11
|
PipeFittingNode,
|
|
12
12
|
PipeSegmentNode,
|
|
13
|
+
registerNode,
|
|
14
|
+
useInteractive,
|
|
13
15
|
useScene,
|
|
14
16
|
} from '@pascal-app/core'
|
|
17
|
+
import { useSidebarStore } from '../components/ui/primitives/sidebar'
|
|
15
18
|
import { runRedo, runUndo } from '../lib/history'
|
|
16
19
|
import { meshEditScope } from '../lib/interaction/scope'
|
|
20
|
+
import { setSidebarTabIds } from '../lib/sidebar-panel'
|
|
17
21
|
import useEditor from '../store/use-editor'
|
|
18
22
|
import useInteractionScope from '../store/use-interaction-scope'
|
|
19
23
|
import {
|
|
24
|
+
armPaintFromShortcut,
|
|
25
|
+
armWallToolFromShortcut,
|
|
20
26
|
blocksSnappingShortcut,
|
|
21
27
|
canCycleSnappingModeShortcut,
|
|
22
28
|
canRunGlobalRotationShortcut,
|
|
23
29
|
isToolOwnedCanopyForm,
|
|
24
30
|
isToolOwnedRotation,
|
|
25
31
|
markToolCancelConsumed,
|
|
32
|
+
openItemCatalogFromShortcut,
|
|
26
33
|
runHistoryShortcut,
|
|
34
|
+
runNodeInteraction,
|
|
35
|
+
switchPhaseFromShortcut,
|
|
27
36
|
} from './use-keyboard'
|
|
28
37
|
|
|
29
38
|
type RafFn = (callback: (time: number) => void) => number
|
|
@@ -106,6 +115,12 @@ describe('rotation shortcut ownership', () => {
|
|
|
106
115
|
expect(isToolOwnedRotation()).toBe(true)
|
|
107
116
|
})
|
|
108
117
|
|
|
118
|
+
test('does not reserve R for the wall tool: its shape is picked in the Build panel', () => {
|
|
119
|
+
useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' })
|
|
120
|
+
|
|
121
|
+
expect(isToolOwnedRotation()).toBe(false)
|
|
122
|
+
})
|
|
123
|
+
|
|
109
124
|
test('leaves R and T to a moving lean-to extension', () => {
|
|
110
125
|
const leanTo = { id: 'lean_to_moving', type: 'lean-to-extension' } as unknown as AnyNode
|
|
111
126
|
useInteractionScope.getState().begin({
|
|
@@ -242,3 +257,170 @@ describe('history while drawing distribution runs', () => {
|
|
|
242
257
|
})
|
|
243
258
|
}
|
|
244
259
|
})
|
|
260
|
+
|
|
261
|
+
describe('E on a single selected node', () => {
|
|
262
|
+
function registerKind(overrides: Record<string, unknown>) {
|
|
263
|
+
const kind = `test:e-${crypto.randomUUID()}`
|
|
264
|
+
registerNode({
|
|
265
|
+
kind,
|
|
266
|
+
schemaVersion: 1,
|
|
267
|
+
category: 'furnish',
|
|
268
|
+
defaults: () => ({}),
|
|
269
|
+
capabilities: {},
|
|
270
|
+
...overrides,
|
|
271
|
+
} as never)
|
|
272
|
+
return { object: 'node', id: `${kind}_1`, type: kind } as unknown as AnyNode
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const switchMechanism = {
|
|
276
|
+
has: () => true,
|
|
277
|
+
isOn: (node: AnyNode, state: ReturnType<typeof useInteractive.getState>) =>
|
|
278
|
+
Boolean(state.mechanisms[node.id]),
|
|
279
|
+
set: (node: AnyNode, on: boolean) => useInteractive.getState().setMechanism(node.id, on),
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
test('runs the mechanism of a kind that declares only the capability', () => {
|
|
283
|
+
const node = registerKind({ capabilities: { mechanism: switchMechanism } })
|
|
284
|
+
try {
|
|
285
|
+
expect(runNodeInteraction(node)).toBe(true)
|
|
286
|
+
expect(useInteractive.getState().mechanisms[node.id]).toBe(true)
|
|
287
|
+
expect(runNodeInteraction(node)).toBe(true)
|
|
288
|
+
expect(useInteractive.getState().mechanisms[node.id]).toBe(false)
|
|
289
|
+
} finally {
|
|
290
|
+
useInteractive.getState().removeMechanism(node.id)
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
test("prefers the kind's own E action over its mechanism", () => {
|
|
295
|
+
let ran = 0
|
|
296
|
+
const node = registerKind({
|
|
297
|
+
capabilities: { mechanism: switchMechanism },
|
|
298
|
+
keyboardActions: { e: { appliesTo: () => true, run: () => (ran += 1) } },
|
|
299
|
+
})
|
|
300
|
+
expect(runNodeInteraction(node)).toBe(true)
|
|
301
|
+
expect(ran).toBe(1)
|
|
302
|
+
expect(useInteractive.getState().mechanisms[node.id]).toBeUndefined()
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
test('leaves E to the door and window fallbacks when nothing applies', () => {
|
|
306
|
+
const node = registerKind({
|
|
307
|
+
capabilities: { mechanism: { ...switchMechanism, has: () => false } },
|
|
308
|
+
})
|
|
309
|
+
expect(runNodeInteraction(node)).toBe(false)
|
|
310
|
+
expect(runNodeInteraction(registerKind({}))).toBe(false)
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
describe('B and P open their panel', () => {
|
|
315
|
+
afterEach(() => {
|
|
316
|
+
setSidebarTabIds([])
|
|
317
|
+
useSidebarStore.getState().setIsCollapsed(false)
|
|
318
|
+
useEditor.getState().setContinuation('wall', 'room')
|
|
319
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test('1 goes to site and 2 into the building; 3 is no longer a shortcut', () => {
|
|
323
|
+
useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' })
|
|
324
|
+
expect(switchPhaseFromShortcut('1')).toBe(true)
|
|
325
|
+
expect(useEditor.getState().phase).toBe('site')
|
|
326
|
+
expect(useEditor.getState().mode).toBe('select')
|
|
327
|
+
expect(switchPhaseFromShortcut('2')).toBe(true)
|
|
328
|
+
expect(useEditor.getState().phase).toBe('building')
|
|
329
|
+
expect(switchPhaseFromShortcut('3')).toBe(false)
|
|
330
|
+
expect(useEditor.getState().phase).toBe('building')
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
test('F, B and P arm their tool and keep the building', () => {
|
|
334
|
+
setSidebarTabIds(['site', 'build', 'paint', 'items'])
|
|
335
|
+
useEditor.getState().setPhase('building')
|
|
336
|
+
for (const [arm, mode, tool] of [
|
|
337
|
+
[openItemCatalogFromShortcut, 'build', 'item'],
|
|
338
|
+
[armWallToolFromShortcut, 'build', 'wall'],
|
|
339
|
+
[armPaintFromShortcut, 'material-paint', null],
|
|
340
|
+
] as const) {
|
|
341
|
+
arm()
|
|
342
|
+
expect(useEditor.getState().phase).toBe('building')
|
|
343
|
+
expect(useEditor.getState().mode).toBe(mode)
|
|
344
|
+
expect(useEditor.getState().tool).toBe(tool)
|
|
345
|
+
}
|
|
346
|
+
openItemCatalogFromShortcut()
|
|
347
|
+
expect(useEditor.getState().activeSidebarPanel).toBe('items')
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
test('F, B and P also keep Site when arming their tool', () => {
|
|
351
|
+
setSidebarTabIds(['site', 'build', 'paint', 'items'])
|
|
352
|
+
for (const arm of [
|
|
353
|
+
openItemCatalogFromShortcut,
|
|
354
|
+
armWallToolFromShortcut,
|
|
355
|
+
armPaintFromShortcut,
|
|
356
|
+
]) {
|
|
357
|
+
useEditor.getState().setPhase('site')
|
|
358
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
359
|
+
arm()
|
|
360
|
+
expect(useEditor.getState().phase).toBe('site')
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
test('F opens a collapsed catalog sidebar', () => {
|
|
365
|
+
setSidebarTabIds(['site', 'build', 'items'])
|
|
366
|
+
useSidebarStore.getState().setIsCollapsed(true)
|
|
367
|
+
openItemCatalogFromShortcut()
|
|
368
|
+
expect(useEditor.getState().activeSidebarPanel).toBe('items')
|
|
369
|
+
expect(useSidebarStore.getState().isCollapsed).toBe(false)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
test('B arms the wall tool on Rectangle and shows the Build panel', () => {
|
|
373
|
+
setSidebarTabIds(['site', 'build', 'paint', 'items'])
|
|
374
|
+
useEditor.getState().setActiveSidebarPanel('items')
|
|
375
|
+
useSidebarStore.getState().setIsCollapsed(true)
|
|
376
|
+
useEditor.getState().setContinuation('wall', 'single')
|
|
377
|
+
|
|
378
|
+
armWallToolFromShortcut()
|
|
379
|
+
|
|
380
|
+
const editor = useEditor.getState()
|
|
381
|
+
expect(editor.mode).toBe('build')
|
|
382
|
+
expect(editor.tool).toBe('wall')
|
|
383
|
+
expect(editor.getContinuation('wall')).toBe('rectangle')
|
|
384
|
+
expect(editor.activeSidebarPanel).toBe('build')
|
|
385
|
+
expect(useSidebarStore.getState().isCollapsed).toBe(false)
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// A host may drop the armed tool when the panel changes (the community does,
|
|
389
|
+
// with any placement in flight), so B and F open their panel first.
|
|
390
|
+
test('B and F keep their tool from another panel where the host disarms on a panel change', () => {
|
|
391
|
+
setSidebarTabIds(['build', 'paint', 'items', 'ai'])
|
|
392
|
+
const unsubscribe = useEditor.subscribe((state, prev) => {
|
|
393
|
+
if (state.activeSidebarPanel !== prev.activeSidebarPanel) {
|
|
394
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
try {
|
|
398
|
+
for (const [arm, from, panel, tool] of [
|
|
399
|
+
[armWallToolFromShortcut, 'ai', 'build', 'wall'],
|
|
400
|
+
[openItemCatalogFromShortcut, 'paint', 'items', 'item'],
|
|
401
|
+
] as const) {
|
|
402
|
+
useEditor.getState().setActiveSidebarPanel(from)
|
|
403
|
+
arm()
|
|
404
|
+
const editor = useEditor.getState()
|
|
405
|
+
expect(editor.activeSidebarPanel).toBe(panel)
|
|
406
|
+
expect(editor.mode).toBe('build')
|
|
407
|
+
expect(editor.tool).toBe(tool)
|
|
408
|
+
}
|
|
409
|
+
} finally {
|
|
410
|
+
unsubscribe()
|
|
411
|
+
}
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
test('P arms paint and shows the Paint panel, or Build where the host has none', () => {
|
|
415
|
+
setSidebarTabIds(['build', 'paint', 'items'])
|
|
416
|
+
useEditor.getState().setActiveSidebarPanel('items')
|
|
417
|
+
armPaintFromShortcut()
|
|
418
|
+
expect(useEditor.getState().mode).toBe('material-paint')
|
|
419
|
+
expect(useEditor.getState().activeSidebarPanel).toBe('paint')
|
|
420
|
+
|
|
421
|
+
setSidebarTabIds(['site', 'build', 'items'])
|
|
422
|
+
useEditor.getState().setActiveSidebarPanel('items')
|
|
423
|
+
armPaintFromShortcut()
|
|
424
|
+
expect(useEditor.getState().activeSidebarPanel).toBe('build')
|
|
425
|
+
})
|
|
426
|
+
})
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
nodeRegistry,
|
|
6
6
|
pauseSpaceDetection,
|
|
7
7
|
resumeSpaceDetection,
|
|
8
|
+
toggleNodeMechanism,
|
|
8
9
|
useScene,
|
|
9
10
|
} from '@pascal-app/core'
|
|
10
11
|
import { cancelPerfAction, markPerfAction, useViewer } from '@pascal-app/viewer'
|
|
11
12
|
import { useEffect } from 'react'
|
|
12
|
-
import { Vector3 } from 'three'
|
|
13
13
|
import {
|
|
14
14
|
cutSelectionToEditorClipboard,
|
|
15
15
|
deleteSelection,
|
|
@@ -18,25 +18,32 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
classifyParticipant,
|
|
20
20
|
collectParticipants,
|
|
21
|
-
|
|
22
|
-
expandToComponent,
|
|
21
|
+
groupPlanBounds,
|
|
23
22
|
levelFrame,
|
|
23
|
+
planBoundsCenter,
|
|
24
24
|
rotateGroupPatches,
|
|
25
25
|
} from '../components/editor/group-transform-shared'
|
|
26
26
|
import { steppedRotation } from '../components/tools/item/placement-math'
|
|
27
|
+
import { keyCyclableContinuationContext } from '../lib/continuation'
|
|
27
28
|
import { resolveDirectManipulationNode } from '../lib/direct-manipulation'
|
|
28
|
-
import {
|
|
29
|
+
import { cancelGestures } from '../lib/gesture-lifecycle'
|
|
29
30
|
import { guideEmitter } from '../lib/guide-events'
|
|
30
31
|
import { isHistoryShortcut, runRedo, runUndo, shouldCancelDraftOnHistoryJump } from '../lib/history'
|
|
31
32
|
import { isActive } from '../lib/interaction/scope'
|
|
33
|
+
import { paintRegionModeActive } from '../lib/paint-region-mode'
|
|
34
|
+
import { popRoomSelection } from '../lib/room-selection-commands'
|
|
32
35
|
import { copySelectedNodesToEditorClipboard } from '../lib/scene-clipboard'
|
|
33
36
|
import { sfxEmitter } from '../lib/sfx-bus'
|
|
37
|
+
import { openSidebarPanel } from '../lib/sidebar-panel'
|
|
34
38
|
import { activeSiteNode, clampBrushRadius } from '../lib/terrain-sculpt'
|
|
35
39
|
import { leaveUnitFocus } from '../lib/units'
|
|
36
|
-
import {
|
|
40
|
+
import { selectWallDrawVariant } from '../lib/wall-draw-variant'
|
|
37
41
|
import useDeleteConfirmation from '../store/use-delete-confirmation'
|
|
38
42
|
import useEditor, { getActiveContinuationContext, getActiveSnapContext } from '../store/use-editor'
|
|
39
|
-
import useInteractionScope, {
|
|
43
|
+
import useInteractionScope, {
|
|
44
|
+
getMovingNode,
|
|
45
|
+
isInteractionSubtreeDraft,
|
|
46
|
+
} from '../store/use-interaction-scope'
|
|
40
47
|
import { groupCurrentSelection, ungroupCurrentSelection } from '../store/use-session-groups'
|
|
41
48
|
|
|
42
49
|
// References (guide/scan) are selected via `useEditor.selectedReferenceId`, not
|
|
@@ -66,27 +73,20 @@ function rotateGroupSelection(direction: 1 | -1): boolean {
|
|
|
66
73
|
(id) => classifyParticipant(nodes[id as AnyNodeId], levelId, nodes) !== null,
|
|
67
74
|
)
|
|
68
75
|
if (participantIds.length === 0) return false
|
|
69
|
-
const
|
|
70
|
-
const { starts, links } = collectParticipants(fullIds, nodes, levelId)
|
|
76
|
+
const { starts, links } = collectParticipants(participantIds, nodes, levelId)
|
|
71
77
|
if (starts.length === 0) return false
|
|
72
78
|
|
|
73
|
-
// Same pivot as the
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const worldCenter = new Vector3(
|
|
79
|
-
(box.min.x + box.max.x) / 2,
|
|
80
|
-
box.min.y,
|
|
81
|
-
(box.min.z + box.max.z) / 2,
|
|
82
|
-
)
|
|
83
|
-
const localCenter = worldCenter.applyMatrix4(levelFrame(levelId).inverse)
|
|
79
|
+
// Same pivot as the dashed boxes and the rotate gizmo: the selection's box
|
|
80
|
+
// centre in the level frame.
|
|
81
|
+
const bounds = groupPlanBounds(starts, levelFrame(levelId).inverse)
|
|
82
|
+
if (!bounds) return false
|
|
83
|
+
const [pivotX, pivotZ] = planBoundsCenter(bounds)
|
|
84
84
|
|
|
85
85
|
// R (+45° yaw) orbits by -45° in the atan2 x→z sense: yaw = rotation - delta
|
|
86
86
|
// (see rotateGroupPatches), so keyboard direction matches the single-node
|
|
87
87
|
// steppedRotation sense.
|
|
88
88
|
const delta = -direction * (Math.PI / 4)
|
|
89
|
-
const patches = rotateGroupPatches(starts, links, { x:
|
|
89
|
+
const patches = rotateGroupPatches(starts, links, { x: pivotX, z: pivotZ }, delta)
|
|
90
90
|
// Space detection stays out: a rigid rotation of existing walls must not
|
|
91
91
|
// re-create the room's auto floors/ceilings at the new bearing.
|
|
92
92
|
pauseSpaceDetection()
|
|
@@ -113,19 +113,23 @@ export const markToolCancelConsumed = () => {
|
|
|
113
113
|
// preset/item placement rely on this — they pass no coordinator onCancel, and
|
|
114
114
|
// it is the mode switch unmounting them that destroys the draft.
|
|
115
115
|
const exitToSelectAfterUnconsumedCancel = () => {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
const editor = useEditor.getState()
|
|
117
|
+
if (
|
|
118
|
+
editor.mode === 'select' &&
|
|
119
|
+
useInteractionScope.getState().scope.kind === 'idle' &&
|
|
120
|
+
popRoomSelection()
|
|
121
|
+
)
|
|
122
|
+
return
|
|
123
|
+
useInteractionScope
|
|
124
|
+
.getState()
|
|
125
|
+
.endIf(
|
|
126
|
+
(sc) =>
|
|
127
|
+
(sc.kind === 'placing' && isInteractionSubtreeDraft()) ||
|
|
128
|
+
(sc.kind === 'reshaping' && sc.reshape === 'hole'),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
// Back to the select tool, keeping the active building/level context.
|
|
132
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
129
133
|
|
|
130
134
|
useEditor.getState().setFloorplanSelectionTool('click')
|
|
131
135
|
|
|
@@ -134,6 +138,20 @@ const exitToSelectAfterUnconsumedCancel = () => {
|
|
|
134
138
|
useEditor.getState().setSelectedReferenceId(null)
|
|
135
139
|
}
|
|
136
140
|
|
|
141
|
+
// Cancel the active editor action with the same consume-or-exit semantics as
|
|
142
|
+
// Escape. Spatial inputs use this instead of unconditionally selecting the
|
|
143
|
+
// Select tool, so multi-step tools can keep their tool active after clearing
|
|
144
|
+
// the current draft.
|
|
145
|
+
export const cancelActiveTool = () => {
|
|
146
|
+
_toolCancelConsumed = false
|
|
147
|
+
emitter.emit('tool:cancel')
|
|
148
|
+
if (!_toolCancelConsumed) {
|
|
149
|
+
if (leaveUnitFocus()) useEditor.getState().armToolMode({ mode: 'select' })
|
|
150
|
+
else exitToSelectAfterUnconsumedCancel()
|
|
151
|
+
}
|
|
152
|
+
return _toolCancelConsumed
|
|
153
|
+
}
|
|
154
|
+
|
|
137
155
|
// ⌘Z pressed mid-interaction (moving a node, drawing a wall, mid-placement…)
|
|
138
156
|
// reads as "abort this action", not history undo — behave exactly like Escape
|
|
139
157
|
// and report whether anything was in flight so the undo/redo arms know to
|
|
@@ -145,6 +163,11 @@ const cancelInteractionForHistoryShortcut = () => {
|
|
|
145
163
|
guideEmitter.emit('guide:cancel-reference-scale')
|
|
146
164
|
return true
|
|
147
165
|
}
|
|
166
|
+
// A live gesture reads ⌘Z as "abort", never as a jump under the pointer.
|
|
167
|
+
if (cancelGestures('history')) {
|
|
168
|
+
cancelPerfAction()
|
|
169
|
+
return true
|
|
170
|
+
}
|
|
148
171
|
const activeScope = useInteractionScope.getState().scope
|
|
149
172
|
if (shouldCancelDraftOnHistoryJump()) return false
|
|
150
173
|
if (activeScope.kind === 'mesh-editing' && activeScope.phase === 'selecting') return false
|
|
@@ -176,6 +199,41 @@ export const runHistoryShortcut = (direction: 'undo' | 'redo') => {
|
|
|
176
199
|
return true
|
|
177
200
|
}
|
|
178
201
|
|
|
202
|
+
/**
|
|
203
|
+
* B: the wall tool on Rectangle — the quickest room — with the Build panel
|
|
204
|
+
* showing, so the lit tile says what is in hand. The panel's tiles still pick
|
|
205
|
+
* another shape for the session. The panel opens before the tool is armed: a
|
|
206
|
+
* host may drop the armed tool when the panel changes.
|
|
207
|
+
*/
|
|
208
|
+
export const armWallToolFromShortcut = () => {
|
|
209
|
+
selectWallDrawVariant('rectangle')
|
|
210
|
+
openSidebarPanel(['build'])
|
|
211
|
+
useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' })
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 1: site, 2: the building — each back on the select tool. Returns whether `key` was one. */
|
|
215
|
+
export const switchPhaseFromShortcut = (key: string): boolean => {
|
|
216
|
+
const phase = key === '1' ? 'site' : key === '2' ? 'building' : null
|
|
217
|
+
if (!phase) return false
|
|
218
|
+
useEditor.getState().setPhase(phase)
|
|
219
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
220
|
+
return true
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** F: the item tool with the items catalog showing, opened first as B's is. */
|
|
224
|
+
export const openItemCatalogFromShortcut = () => {
|
|
225
|
+
openSidebarPanel(['items'])
|
|
226
|
+
useEditor.getState().setActiveSidebarPanel('items')
|
|
227
|
+
useEditor.getState().armToolMode({ mode: 'build', tool: 'item' })
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** P: paint mode, with the Paint panel showing (the Build panel where a host has no Paint tab). */
|
|
231
|
+
export const armPaintFromShortcut = () => {
|
|
232
|
+
useEditor.getState().armMaterialPaint()
|
|
233
|
+
openSidebarPanel(['paint', 'build'])
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Whether an armed tool owns the rotation keys (`R` / `T`) instead of the selection. */
|
|
179
237
|
export const isToolOwnedRotation = () => {
|
|
180
238
|
const editor = useEditor.getState()
|
|
181
239
|
const moving = getMovingNode()
|
|
@@ -219,6 +277,16 @@ export function blocksSnappingShortcut(
|
|
|
219
277
|
return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable
|
|
220
278
|
}
|
|
221
279
|
|
|
280
|
+
/** E on one selected node: the kind's own E action, else its mechanism. False when neither applies. */
|
|
281
|
+
export function runNodeInteraction(node: AnyNode): boolean {
|
|
282
|
+
const action = nodeRegistry.get(node.type)?.keyboardActions?.e
|
|
283
|
+
if (action?.appliesTo(node)) {
|
|
284
|
+
action.run(node)
|
|
285
|
+
return true
|
|
286
|
+
}
|
|
287
|
+
return toggleNodeMechanism(node)
|
|
288
|
+
}
|
|
289
|
+
|
|
222
290
|
export const useKeyboard = ({
|
|
223
291
|
isVersionPreviewMode = false,
|
|
224
292
|
disabled = false,
|
|
@@ -290,7 +358,12 @@ export const useKeyboard = ({
|
|
|
290
358
|
return
|
|
291
359
|
}
|
|
292
360
|
|
|
293
|
-
if (
|
|
361
|
+
if (
|
|
362
|
+
e.key === 'Shift' &&
|
|
363
|
+
!e.repeat &&
|
|
364
|
+
useEditor.getState().mode === 'material-paint' &&
|
|
365
|
+
!paintRegionModeActive('material-paint')
|
|
366
|
+
) {
|
|
294
367
|
// In paint mode Shift cycles the application scope (this surface →
|
|
295
368
|
// whole item / all matching / room) — the paint-mode analogue of the
|
|
296
369
|
// snapping-mode cycle below. The scope chip mirrors this key.
|
|
@@ -342,7 +415,10 @@ export const useKeyboard = ({
|
|
|
342
415
|
const current = useEditor.getState().getContinuation('fence')
|
|
343
416
|
useEditor
|
|
344
417
|
.getState()
|
|
345
|
-
.setContinuation(
|
|
418
|
+
.setContinuation(
|
|
419
|
+
'fence',
|
|
420
|
+
current === 'curved' ? 'freehand' : current === 'freehand' ? 'continuous' : 'curved',
|
|
421
|
+
)
|
|
346
422
|
sfxEmitter.emit('sfx:grid-snap')
|
|
347
423
|
return
|
|
348
424
|
}
|
|
@@ -356,12 +432,13 @@ export const useKeyboard = ({
|
|
|
356
432
|
!e.shiftKey &&
|
|
357
433
|
!e.altKey
|
|
358
434
|
) {
|
|
359
|
-
|
|
435
|
+
// The wall's drawing mode is picked in the Build panel, not cycled here.
|
|
436
|
+
const context = keyCyclableContinuationContext(getActiveContinuationContext())
|
|
360
437
|
if (context) {
|
|
361
438
|
e.preventDefault()
|
|
362
439
|
if (context === 'fence') {
|
|
363
440
|
const current = useEditor.getState().getContinuation('fence')
|
|
364
|
-
if (current !== 'curved') {
|
|
441
|
+
if (current !== 'curved' && current !== 'freehand') {
|
|
365
442
|
useEditor
|
|
366
443
|
.getState()
|
|
367
444
|
.setContinuation('fence', current === 'single' ? 'continuous' : 'single')
|
|
@@ -385,46 +462,24 @@ export const useKeyboard = ({
|
|
|
385
462
|
return
|
|
386
463
|
}
|
|
387
464
|
|
|
388
|
-
_toolCancelConsumed = false
|
|
389
|
-
emitter.emit('tool:cancel')
|
|
390
|
-
|
|
391
465
|
// Only switch to select mode if no tool had an active mid-action to cancel.
|
|
392
466
|
// (e.g. mid-wall draw or mid-slab polygon should only cancel the action, not exit the tool)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
else exitToSelectAfterUnconsumedCancel()
|
|
396
|
-
}
|
|
397
|
-
} else if (e.key === '1' && !e.metaKey && !e.ctrlKey) {
|
|
398
|
-
e.preventDefault()
|
|
399
|
-
useEditor.getState().setPhase('site')
|
|
400
|
-
useEditor.getState().armToolMode({ mode: 'select' })
|
|
401
|
-
} else if (e.key === '2' && !e.metaKey && !e.ctrlKey) {
|
|
402
|
-
e.preventDefault()
|
|
403
|
-
useEditor.getState().setPhase('structure')
|
|
404
|
-
useEditor.getState().armToolMode({ mode: 'select' })
|
|
405
|
-
} else if (e.key === '3' && !e.metaKey && !e.ctrlKey) {
|
|
467
|
+
cancelActiveTool()
|
|
468
|
+
} else if (!e.metaKey && !e.ctrlKey && switchPhaseFromShortcut(e.key)) {
|
|
406
469
|
e.preventDefault()
|
|
407
|
-
useEditor.getState().setPhase('furnish')
|
|
408
|
-
useEditor.getState().armToolMode({ mode: 'select' })
|
|
409
470
|
} else if (e.key === 'f' && !e.metaKey && !e.ctrlKey) {
|
|
410
471
|
if (isVersionPreviewMode) return
|
|
411
472
|
if (isToolOwnedCanopyForm()) return
|
|
412
473
|
e.preventDefault()
|
|
413
|
-
|
|
414
|
-
useEditor.getState().armToolMode({ mode: 'build', tool: 'item' })
|
|
415
|
-
useEditor.getState().setActiveSidebarPanel('items')
|
|
474
|
+
openItemCatalogFromShortcut()
|
|
416
475
|
} else if (e.key === 'z' && !e.metaKey && !e.ctrlKey) {
|
|
417
476
|
if (isVersionPreviewMode) return
|
|
418
477
|
e.preventDefault()
|
|
419
|
-
useEditor.getState().setPhase('structure')
|
|
420
|
-
useEditor.getState().setStructureLayer('zones')
|
|
421
478
|
useEditor.getState().armToolMode({ mode: 'build', tool: 'zone' })
|
|
422
479
|
} else if (e.key === 'm' && !e.metaKey && !e.ctrlKey) {
|
|
423
480
|
if (isVersionPreviewMode) return
|
|
424
481
|
e.preventDefault()
|
|
425
482
|
const editor = useEditor.getState()
|
|
426
|
-
editor.setPhase('structure')
|
|
427
|
-
editor.setStructureLayer('elements')
|
|
428
483
|
editor.setToolDefaults('measurement', { kind: editor.lastMeasurementKind })
|
|
429
484
|
editor.armToolMode({ mode: 'build', tool: 'measurement' })
|
|
430
485
|
}
|
|
@@ -435,9 +490,7 @@ export const useKeyboard = ({
|
|
|
435
490
|
} else if (e.key === 'b' && !e.metaKey && !e.ctrlKey) {
|
|
436
491
|
if (isVersionPreviewMode) return
|
|
437
492
|
e.preventDefault()
|
|
438
|
-
|
|
439
|
-
useEditor.getState().setStructureLayer('elements')
|
|
440
|
-
useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' })
|
|
493
|
+
armWallToolFromShortcut()
|
|
441
494
|
} else if (e.key === 'x' && !e.metaKey && !e.ctrlKey) {
|
|
442
495
|
if (isVersionPreviewMode) return
|
|
443
496
|
e.preventDefault()
|
|
@@ -445,9 +498,7 @@ export const useKeyboard = ({
|
|
|
445
498
|
} else if (e.key === 'p' && !e.metaKey && !e.ctrlKey) {
|
|
446
499
|
if (isVersionPreviewMode) return
|
|
447
500
|
e.preventDefault()
|
|
448
|
-
|
|
449
|
-
useEditor.getState().setStructureLayer('elements')
|
|
450
|
-
useEditor.getState().armMaterialPaint()
|
|
501
|
+
armPaintFromShortcut()
|
|
451
502
|
} else if (e.key === 'g' && !e.metaKey && !e.ctrlKey) {
|
|
452
503
|
if (isVersionPreviewMode) return
|
|
453
504
|
e.preventDefault()
|
|
@@ -679,34 +730,14 @@ export const useKeyboard = ({
|
|
|
679
730
|
}
|
|
680
731
|
}
|
|
681
732
|
} else if ((e.key === 'e' || e.key === 'E') && !isVersionPreviewMode) {
|
|
682
|
-
//
|
|
683
|
-
// which now flips the opening (side + π rotation).
|
|
733
|
+
// E runs the kind's interaction or mechanism (doors and windows open and
|
|
734
|
+
// close). Moved off R, which now flips the opening (side + π rotation).
|
|
684
735
|
const selectedNodeIds = useViewer.getState().selection.selectedIds as AnyNodeId[]
|
|
685
736
|
if (selectedNodeIds.length === 1) {
|
|
686
737
|
const node = useScene.getState().nodes[selectedNodeIds[0]!]
|
|
687
|
-
|
|
688
|
-
if (node && registryE?.appliesTo(node)) {
|
|
738
|
+
if (node && runNodeInteraction(node)) {
|
|
689
739
|
// Registry-driven E interaction. Same shape as the R/T arms.
|
|
690
740
|
e.preventDefault()
|
|
691
|
-
registryE.run(node)
|
|
692
|
-
sfxEmitter.emit('sfx:item-rotate')
|
|
693
|
-
} else if (node?.type === 'door' && node.openingKind !== 'opening') {
|
|
694
|
-
e.preventDefault()
|
|
695
|
-
toggleDoorOpenState(node.id)
|
|
696
|
-
sfxEmitter.emit('sfx:item-rotate')
|
|
697
|
-
} else if (
|
|
698
|
-
node?.type === 'window' &&
|
|
699
|
-
node.openingKind !== 'opening' &&
|
|
700
|
-
(node.windowType === 'sliding' ||
|
|
701
|
-
node.windowType === 'casement' ||
|
|
702
|
-
node.windowType === 'awning' ||
|
|
703
|
-
node.windowType === 'hopper' ||
|
|
704
|
-
node.windowType === 'single-hung' ||
|
|
705
|
-
node.windowType === 'double-hung' ||
|
|
706
|
-
node.windowType === 'louvered')
|
|
707
|
-
) {
|
|
708
|
-
e.preventDefault()
|
|
709
|
-
toggleWindowOpenState(node.id)
|
|
710
741
|
sfxEmitter.emit('sfx:item-rotate')
|
|
711
742
|
}
|
|
712
743
|
}
|