@pascal-app/editor 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -9
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +315 -4
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/custom-camera-controls.tsx +202 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +36 -25
- package/src/components/editor/editor-layout-v2.tsx +131 -58
- package/src/components/editor/export-manager.tsx +34 -8
- package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
- package/src/components/editor/first-person/build-collider-world.ts +6 -2
- package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
- package/src/components/editor/first-person/mechanism-targets.ts +36 -0
- package/src/components/editor/first-person-controls.tsx +205 -2
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-panel.tsx +344 -342
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +54 -2
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +294 -169
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +225 -0
- package/src/components/editor/room-labels.test.ts +357 -0
- package/src/components/editor/room-labels.tsx +990 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +451 -317
- package/src/components/editor/slab-hole-highlights.tsx +3 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
- package/src/components/editor/snapshot-capture.test.ts +52 -4
- package/src/components/editor/snapshot-capture.ts +30 -5
- package/src/components/editor/thumbnail-generator.test.ts +308 -0
- package/src/components/editor/thumbnail-generator.tsx +1035 -229
- package/src/components/editor/use-floorplan-background-placement.ts +31 -11
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +670 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.tsx +220 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +8 -4
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/stair/stair-defaults.ts +0 -3
- package/src/components/tools/stair/stair-tool.tsx +90 -42
- package/src/components/tools/tool-manager.tsx +46 -20
- package/src/components/tools/wall/wall-drafting.test.ts +19 -4
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +3 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -1
- package/src/components/ui/action-menu/control-modes.tsx +2 -35
- package/src/components/ui/action-menu/measurement-control.tsx +3 -10
- package/src/components/ui/build-panel/build-panel.tsx +235 -0
- package/src/components/ui/command-palette/editor-commands.tsx +4 -30
- package/src/components/ui/command-palette/index.tsx +5 -24
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/floating-level-selector.tsx +9 -14
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
- package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
- package/src/components/ui/helpers/helper-manager.tsx +325 -14
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +78 -36
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -4
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +725 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +2 -0
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +145 -0
- package/src/components/ui/sidebar/icon-rail.tsx +5 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
- package/src/components/ui/sidebar/tab-bar.tsx +10 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +175 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +123 -9
- package/src/hooks/use-auto-save.ts +34 -8
- package/src/hooks/use-keyboard.test.ts +178 -3
- package/src/hooks/use-keyboard.ts +89 -71
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +48 -0
- package/src/hooks/use-placement-notice.ts +42 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +246 -39
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +48 -0
- package/src/lib/editor-views.ts +144 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +283 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +100 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +232 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
- package/src/lib/floorplan/site-plan/geometry.ts +557 -0
- package/src/lib/floorplan/site-plan/index.ts +71 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/fresh-planar-placement.test.ts +36 -0
- package/src/lib/fresh-planar-placement.ts +59 -36
- package/src/lib/geometry-script/author.ts +152 -0
- package/src/lib/geometry-script/client.ts +152 -0
- package/src/lib/geometry-script/protocol.ts +12 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +485 -0
- package/src/lib/glb-export.ts +237 -119
- package/src/lib/history.test.ts +6 -0
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +18 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +186 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +407 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +165 -0
- package/src/lib/orbit-floor-clearance.ts +106 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +111 -0
- package/src/lib/paint-pass-through.ts +76 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +266 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +1 -1
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +50 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +266 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +973 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +364 -0
- package/src/lib/scene-clipboard.ts +229 -48
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +53 -74
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +569 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +13 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +175 -0
- package/src/store/view-layout.ts +196 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
CeilingNode as CeilingNodeSchema,
|
|
10
10
|
type ColumnNode,
|
|
11
11
|
calculateLevelMiters,
|
|
12
|
+
createSizedStairFlight,
|
|
12
13
|
DEFAULT_ANGLE_STEP,
|
|
13
14
|
type DoorNode,
|
|
14
15
|
DoorNode as DoorNodeSchema,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
isCurvedWall,
|
|
28
29
|
type LevelNode,
|
|
29
30
|
loadAssetUrl,
|
|
31
|
+
newZone,
|
|
30
32
|
nodeRegistry,
|
|
31
33
|
normalizeWallCurveOffset,
|
|
32
34
|
type Point2D,
|
|
@@ -40,7 +42,6 @@ import {
|
|
|
40
42
|
SlabNode as SlabNodeSchema,
|
|
41
43
|
type StairNode,
|
|
42
44
|
StairNode as StairNodeSchema,
|
|
43
|
-
StairSegmentNode as StairSegmentNodeSchema,
|
|
44
45
|
sampleWallCenterline,
|
|
45
46
|
sceneRegistry,
|
|
46
47
|
snapPointAlongAngleRay,
|
|
@@ -56,14 +57,13 @@ import {
|
|
|
56
57
|
WindowNode as WindowNodeSchema,
|
|
57
58
|
wallClosesRoom,
|
|
58
59
|
wallRectangleCorners,
|
|
59
|
-
ZoneNode as ZoneNodeSchema,
|
|
60
|
-
type ZoneNode as ZoneNodeType,
|
|
61
60
|
} from '@pascal-app/core'
|
|
62
61
|
import { useSegmentDraftChain, useWallSnapIndicator } from '@pascal-app/editor'
|
|
63
62
|
import { getSceneTheme, useViewer } from '@pascal-app/viewer'
|
|
64
63
|
import { Command, Ruler } from 'lucide-react'
|
|
65
64
|
import {
|
|
66
65
|
type ComponentProps,
|
|
66
|
+
type CSSProperties,
|
|
67
67
|
memo,
|
|
68
68
|
type MouseEvent as ReactMouseEvent,
|
|
69
69
|
type ReactNode,
|
|
@@ -78,11 +78,15 @@ import {
|
|
|
78
78
|
import { createPortal } from 'react-dom'
|
|
79
79
|
import { Vector3 } from 'three'
|
|
80
80
|
import { useShallow } from 'zustand/react/shallow'
|
|
81
|
+
import {
|
|
82
|
+
hoverRoomFromHit,
|
|
83
|
+
resolvePlanRoomHit,
|
|
84
|
+
roomPickingEnabled,
|
|
85
|
+
} from '../../hooks/use-selected-room'
|
|
81
86
|
import { resolveCeilingPlanPointSnap } from '../../lib/ceiling-plan-snap'
|
|
82
87
|
import {
|
|
83
88
|
alignFloorplanDraftPoint,
|
|
84
89
|
buildFloorplanItemEntry,
|
|
85
|
-
buildFloorplanStairEntry as buildSharedFloorplanStairEntry,
|
|
86
90
|
collectLevelDescendants,
|
|
87
91
|
FLOORPLAN_VIEW_ROTATION_DEG,
|
|
88
92
|
floorplanLocalToWorldPoint,
|
|
@@ -91,29 +95,58 @@ import {
|
|
|
91
95
|
type FloorplanNodeTransform as SharedFloorplanNodeTransform,
|
|
92
96
|
worldToFloorplanLocalPoint,
|
|
93
97
|
} from '../../lib/floorplan'
|
|
98
|
+
import { buildFloorplanContext } from '../../lib/floorplan/floorplan-readonly'
|
|
99
|
+
// Site-plan view. Owned under `lib/floorplan/site-plan/**`; this panel
|
|
100
|
+
// only chooses between it and the registry layer, and mounts the switch.
|
|
101
|
+
import {
|
|
102
|
+
FloorplanDrawingTypeSwitch,
|
|
103
|
+
useSitePlanAvailable,
|
|
104
|
+
} from '../../lib/floorplan/site-plan/drawing-type-switch'
|
|
105
|
+
import { FloorplanSitePlanLayer } from '../../lib/floorplan/site-plan/site-plan-layer'
|
|
94
106
|
import { resolveGenericFloorplanGridEventPoint } from '../../lib/floorplan-grid-event-point'
|
|
95
107
|
import type { EditorGridEvent } from '../../lib/grid-event-presentation'
|
|
96
108
|
import { groundHeightAt } from '../../lib/ground-surface'
|
|
97
109
|
import { guideEmitter } from '../../lib/guide-events'
|
|
98
110
|
import {
|
|
99
111
|
acceptsKeyboardPan,
|
|
112
|
+
advanceKeyboardPanMotion,
|
|
100
113
|
clearKeyboardPanKeys,
|
|
114
|
+
createKeyboardPanMotion,
|
|
101
115
|
isKeyboardPanKey,
|
|
102
116
|
type KeyboardPanState,
|
|
103
|
-
keyboardPanDirection,
|
|
104
117
|
keyboardPanSpeed,
|
|
118
|
+
resetKeyboardPanMotion,
|
|
105
119
|
setKeyboardPanKey,
|
|
120
|
+
syncKeyboardPanMotion,
|
|
106
121
|
} from '../../lib/keyboard-pan'
|
|
107
122
|
import { measurementHint, parseMeasurement } from '../../lib/measurement-parser'
|
|
108
123
|
import { formatLinearMeasurement, linearUnitToMeters } from '../../lib/measurements'
|
|
124
|
+
import { snapRegisteredDraftPoint } from '../../lib/registered-draft-snap'
|
|
125
|
+
import { selectRoomFromHit } from '../../lib/room-selection-commands'
|
|
126
|
+
import { enterBuildingFromPlanHit } from '../../lib/selection-routing'
|
|
109
127
|
import { sfxEmitter } from '../../lib/sfx-bus'
|
|
110
128
|
import { SITE_BOUNDARY_DRAG_LABEL, siteBoundaryHandlesEnabled } from '../../lib/site-boundary'
|
|
111
129
|
import { resolveSlabPlanPointSnap } from '../../lib/slab-plan-snap'
|
|
112
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
cancelPendingZonePaint,
|
|
132
|
+
focusedUnitNode,
|
|
133
|
+
paintZoneMembership,
|
|
134
|
+
zoneAtLevelPoint,
|
|
135
|
+
} from '../../lib/units'
|
|
113
136
|
import { cn } from '../../lib/utils'
|
|
137
|
+
import { getWallDrawVariant } from '../../lib/wall-draw-variant'
|
|
138
|
+
import {
|
|
139
|
+
addWallPolygonDraftCorner,
|
|
140
|
+
commitWallPolygonDraft,
|
|
141
|
+
discardWallPolygonDraft,
|
|
142
|
+
startWallPolygonDraft,
|
|
143
|
+
wallPolygonDraftWalls,
|
|
144
|
+
} from '../../lib/wall-polygon-draft'
|
|
114
145
|
import { snapBuildingLocalToWorldGrid } from '../../lib/world-grid-snap'
|
|
146
|
+
import { nextZoneName } from '../../lib/zone-name'
|
|
115
147
|
import { subscribeNavigationSyncPose } from '../../store/navigation-sync-pose-store'
|
|
116
148
|
import useAlignmentGuides from '../../store/use-alignment-guides'
|
|
149
|
+
import useDrawingView from '../../store/use-drawing-view'
|
|
117
150
|
import type { GuideUiState, NavigationSyncPose } from '../../store/use-editor'
|
|
118
151
|
import useEditor, {
|
|
119
152
|
isAngleSnapActive,
|
|
@@ -132,11 +165,13 @@ import useInteractionScope, {
|
|
|
132
165
|
import usePlacementPreview from '../../store/use-placement-preview'
|
|
133
166
|
import { expandSessionSelectionForNode } from '../../store/use-session-groups'
|
|
134
167
|
import { useStairBuildPreview } from '../../store/use-stair-build-preview'
|
|
168
|
+
import { activeViewLayout, isViewVisible, VIEW_2D, visibleScene } from '../../store/view-layout'
|
|
135
169
|
import { FloorplanAlignmentGuideLayer } from '../editor-2d/floorplan-alignment-guide-layer'
|
|
136
170
|
import { FloorplanCursorIndicatorOverlay as Editor2dFloorplanCursorIndicatorOverlay } from '../editor-2d/floorplan-cursor-indicator-overlay'
|
|
137
171
|
import { FloorplanGroupActionMenu } from '../editor-2d/floorplan-group-action-menu'
|
|
138
172
|
import { FloorplanSiteKeyHandler } from '../editor-2d/floorplan-hotkey-handlers'
|
|
139
173
|
import { FloorplanMeasurementToolLayer } from '../editor-2d/floorplan-measurement-tool-layer'
|
|
174
|
+
import { FloorplanOpenWallEndsLayer } from '../editor-2d/floorplan-open-wall-ends-layer'
|
|
140
175
|
import { FloorplanRegisteredToolLayer } from '../editor-2d/floorplan-registered-tool-layer'
|
|
141
176
|
import { FloorplanRegistryActionMenu } from '../editor-2d/floorplan-registry-action-menu'
|
|
142
177
|
import { FloorplanRegistryMoveOverlay } from '../editor-2d/floorplan-registry-move-overlay'
|
|
@@ -147,7 +182,7 @@ import {
|
|
|
147
182
|
import { FloorplanSnapBeaconLayer } from '../editor-2d/floorplan-snap-beacon-layer'
|
|
148
183
|
import { FloorplanWallMoveGhostLayer } from '../editor-2d/floorplan-wall-move-ghost-layer'
|
|
149
184
|
import { FloorplanDraftLayer } from '../editor-2d/renderers/floorplan-draft-layer'
|
|
150
|
-
import {
|
|
185
|
+
import { FloorplanDraftMeasurement } from '../editor-2d/renderers/floorplan-draft-measurement'
|
|
151
186
|
import { FloorplanGeometryRenderer } from '../editor-2d/renderers/floorplan-geometry-renderer'
|
|
152
187
|
import { FloorplanMarqueeLayer } from '../editor-2d/renderers/floorplan-marquee-layer'
|
|
153
188
|
import { FloorplanPlacementPreviewLayer } from '../editor-2d/renderers/floorplan-placement-preview-layer'
|
|
@@ -155,10 +190,8 @@ import {
|
|
|
155
190
|
FloorplanRegistryLayer,
|
|
156
191
|
RotationAngleOverlay,
|
|
157
192
|
} from '../editor-2d/renderers/floorplan-registry-layer'
|
|
158
|
-
import { FloorplanStairLayer } from '../editor-2d/renderers/floorplan-stair-layer'
|
|
159
193
|
import { FloorplanVoronoiLayer } from '../editor-2d/renderers/floorplan-voronoi-layer'
|
|
160
|
-
import { buildSvgPolylinePath, formatPolygonPath
|
|
161
|
-
import { snapFenceDraftPoint } from '../tools/fence/fence-drafting'
|
|
194
|
+
import { buildSvgPolylinePath, formatPolygonPath } from '../editor-2d/svg-paths'
|
|
162
195
|
import { snapToHalf } from '../tools/item/placement-math'
|
|
163
196
|
import {
|
|
164
197
|
isBoxSelectPointerSuppressed,
|
|
@@ -188,15 +221,6 @@ import {
|
|
|
188
221
|
getAngleToSegmentReference,
|
|
189
222
|
getSegmentAngleReferenceAtPoint,
|
|
190
223
|
} from '../tools/shared/segment-angle'
|
|
191
|
-
import {
|
|
192
|
-
DEFAULT_STAIR_ATTACHMENT_SIDE,
|
|
193
|
-
DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
194
|
-
DEFAULT_STAIR_HEIGHT,
|
|
195
|
-
DEFAULT_STAIR_LENGTH,
|
|
196
|
-
DEFAULT_STAIR_STEP_COUNT,
|
|
197
|
-
DEFAULT_STAIR_THICKNESS,
|
|
198
|
-
DEFAULT_STAIR_WIDTH,
|
|
199
|
-
} from '../tools/stair/stair-defaults'
|
|
200
224
|
import {
|
|
201
225
|
chainEndJoinsExistingWall,
|
|
202
226
|
createWallOnCurrentLevel,
|
|
@@ -208,8 +232,6 @@ import {
|
|
|
208
232
|
WALL_JOIN_SNAP_RADIUS,
|
|
209
233
|
type WallPlanPoint,
|
|
210
234
|
} from '../tools/wall/wall-drafting'
|
|
211
|
-
|
|
212
|
-
import { PALETTE_COLORS } from '../ui/primitives/color-dot'
|
|
213
235
|
import { FloorplanCompassButton } from '../viewer/floorplan-compass-button'
|
|
214
236
|
import { resolveFloorplanBackgroundSelection } from './floorplan-background-selection'
|
|
215
237
|
import {
|
|
@@ -232,6 +254,7 @@ import {
|
|
|
232
254
|
import { useFloorplanBackgroundPlacement } from './use-floorplan-background-placement'
|
|
233
255
|
import { useFloorplanHitTesting } from './use-floorplan-hit-testing'
|
|
234
256
|
import { useFloorplanSceneData } from './use-floorplan-scene-data'
|
|
257
|
+
import { sceneSpan } from './views/scene-region'
|
|
235
258
|
|
|
236
259
|
const FALLBACK_VIEW_SIZE = 12
|
|
237
260
|
const FLOORPLAN_PADDING = 2
|
|
@@ -282,7 +305,6 @@ const FLOORPLAN_HOVER_TRANSITION = 'opacity 180ms cubic-bezier(0.2, 0, 0, 1)'
|
|
|
282
305
|
const FLOORPLAN_WALL_HIT_STROKE_WIDTH = 18
|
|
283
306
|
const FLOORPLAN_WALL_STROKE_WIDTH = '1'
|
|
284
307
|
const FLOORPLAN_OPENING_HIT_STROKE_WIDTH = 16
|
|
285
|
-
const noopFloorplanStairHandler = () => {}
|
|
286
308
|
const FLOORPLAN_OPENING_STROKE_WIDTH = 0.05
|
|
287
309
|
const FLOORPLAN_ENDPOINT_HIT_STROKE_WIDTH = 18
|
|
288
310
|
const FLOORPLAN_ENDPOINT_HOVER_GLOW_STROKE_WIDTH = 16
|
|
@@ -2124,68 +2146,6 @@ function movePlanPointTowards(start: Point2D, end: Point2D, distance: number): P
|
|
|
2124
2146
|
return interpolatePlanPoint(start, end, Math.min(1, distance / totalDistance))
|
|
2125
2147
|
}
|
|
2126
2148
|
|
|
2127
|
-
function getNormalizedFloorplanStairSweepAngle(stair: StairNode) {
|
|
2128
|
-
const stairType = stair.stairType ?? 'straight'
|
|
2129
|
-
const baseSweepAngle = stair.sweepAngle ?? (stairType === 'spiral' ? Math.PI * 2 : Math.PI / 2)
|
|
2130
|
-
|
|
2131
|
-
if (Math.abs(baseSweepAngle) >= Math.PI * 2) {
|
|
2132
|
-
return Math.sign(baseSweepAngle || 1) * (Math.PI * 2 - 0.001)
|
|
2133
|
-
}
|
|
2134
|
-
|
|
2135
|
-
return baseSweepAngle
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
function getFloorplanSpiralLandingSweep(stair: StairNode, sweepAngle: number) {
|
|
2139
|
-
if (
|
|
2140
|
-
(stair.stairType ?? 'straight') !== 'spiral' ||
|
|
2141
|
-
(stair.topLandingMode ?? 'none') !== 'integrated'
|
|
2142
|
-
) {
|
|
2143
|
-
return 0
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
const innerRadius = Math.max(0.05, stair.innerRadius ?? 0.9)
|
|
2147
|
-
const width = Math.max(stair.width ?? 1, 0.4)
|
|
2148
|
-
const landingDepth = Math.max(0.3, stair.topLandingDepth ?? Math.max(width * 0.9, 0.8))
|
|
2149
|
-
|
|
2150
|
-
return (
|
|
2151
|
-
Math.min(Math.PI * 0.75, landingDepth / Math.max(innerRadius + width / 2, 0.1)) *
|
|
2152
|
-
Math.sign(sweepAngle || 1)
|
|
2153
|
-
)
|
|
2154
|
-
}
|
|
2155
|
-
|
|
2156
|
-
function getFloorplanCurvedStairHitPolygon(stair: StairNode): Point2D[] {
|
|
2157
|
-
const stairType = stair.stairType ?? 'straight'
|
|
2158
|
-
const sweepAngle = getNormalizedFloorplanStairSweepAngle(stair)
|
|
2159
|
-
const startAngle = -stair.rotation - sweepAngle / 2
|
|
2160
|
-
const endAngle = startAngle + sweepAngle + getFloorplanSpiralLandingSweep(stair, sweepAngle)
|
|
2161
|
-
const center = {
|
|
2162
|
-
x: stair.position[0],
|
|
2163
|
-
y: stair.position[2],
|
|
2164
|
-
}
|
|
2165
|
-
const innerRadius = Math.max(
|
|
2166
|
-
stairType === 'spiral' ? 0.05 : 0.2,
|
|
2167
|
-
stair.innerRadius ?? (stairType === 'spiral' ? 0.2 : 0.9),
|
|
2168
|
-
)
|
|
2169
|
-
const outerRadius = innerRadius + stair.width
|
|
2170
|
-
const outerArcLength = Math.abs(sweepAngle) * outerRadius
|
|
2171
|
-
const segmentCount = Math.max(
|
|
2172
|
-
24,
|
|
2173
|
-
Math.ceil(Math.abs(sweepAngle) / (Math.PI / 24)),
|
|
2174
|
-
Math.ceil(outerArcLength / 0.14),
|
|
2175
|
-
)
|
|
2176
|
-
const outerPoints: Point2D[] = []
|
|
2177
|
-
const innerPoints: Point2D[] = []
|
|
2178
|
-
|
|
2179
|
-
for (let index = 0; index <= segmentCount; index += 1) {
|
|
2180
|
-
const t = index / segmentCount
|
|
2181
|
-
const angle = startAngle + (endAngle - startAngle) * t
|
|
2182
|
-
outerPoints.push(getArcPlanPoint(center, outerRadius, angle))
|
|
2183
|
-
innerPoints.push(getArcPlanPoint(center, innerRadius, angle))
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
return [...outerPoints, ...innerPoints.reverse()]
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
2149
|
function isPointInsidePolygonWithHoles(
|
|
2190
2150
|
point: Point2D,
|
|
2191
2151
|
polygon: Point2D[],
|
|
@@ -4226,107 +4186,53 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({
|
|
|
4226
4186
|
)
|
|
4227
4187
|
})
|
|
4228
4188
|
|
|
4229
|
-
// Static segment for the in-flight stair build preview. No per-render
|
|
4230
|
-
// dependency (the geometry only moves / rotates), so it lives at module scope
|
|
4231
|
-
// instead of a `useMemo`.
|
|
4232
|
-
const FLOORPLAN_PREVIEW_STAIR_SEGMENT = StairSegmentNodeSchema.parse({
|
|
4233
|
-
id: 'sseg_floorplan_preview',
|
|
4234
|
-
segmentType: 'stair',
|
|
4235
|
-
width: DEFAULT_STAIR_WIDTH,
|
|
4236
|
-
length: DEFAULT_STAIR_LENGTH,
|
|
4237
|
-
height: DEFAULT_STAIR_HEIGHT,
|
|
4238
|
-
stepCount: DEFAULT_STAIR_STEP_COUNT,
|
|
4239
|
-
attachmentSide: DEFAULT_STAIR_ATTACHMENT_SIDE,
|
|
4240
|
-
fillToFloor: DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
4241
|
-
thickness: DEFAULT_STAIR_THICKNESS,
|
|
4242
|
-
position: [0, 0, 0],
|
|
4243
|
-
metadata: { isTransient: true, isFloorplanPreview: true },
|
|
4244
|
-
})
|
|
4245
|
-
|
|
4246
|
-
const EMPTY_FLOORPLAN_ID_SET: ReadonlySet<string> = new Set()
|
|
4247
|
-
|
|
4248
|
-
type FloorplanStairLayerPalette = ComponentProps<typeof FloorplanStairLayer>['palette']
|
|
4249
|
-
|
|
4250
|
-
// Leaf layer for the stair tool's in-flight 2D build preview. Subscribes to the
|
|
4251
|
-
// `useStairBuildPreview` store directly so a per-`grid:move` point update (or an
|
|
4252
|
-
// R/T rotation) re-renders ONLY this tiny layer — never the ~120-220ms
|
|
4253
|
-
// `FloorplanPanel`. This mirrors how column / elevator placement stays smooth by
|
|
4254
|
-
// routing preview state through a store + leaf. Committed stairs render through
|
|
4255
|
-
// `FloorplanRegistryLayer`; this layer is non-interactive (noop handlers, empty
|
|
4256
|
-
// hit sets), so it never participates in hover / select.
|
|
4257
4189
|
function FloorplanStairBuildPreviewLayer({
|
|
4258
4190
|
palette,
|
|
4259
|
-
isDeleteMode,
|
|
4260
4191
|
}: {
|
|
4261
|
-
palette:
|
|
4262
|
-
isDeleteMode: boolean
|
|
4192
|
+
palette: FloorplanRenderContextValue['palette']
|
|
4263
4193
|
}) {
|
|
4264
|
-
const phase = useEditor((s) => s.phase)
|
|
4265
4194
|
const mode = useEditor((s) => s.mode)
|
|
4266
4195
|
const tool = useEditor((s) => s.tool)
|
|
4267
4196
|
const point = useStairBuildPreview((s) => s.point)
|
|
4268
4197
|
const rotation = useStairBuildPreview((s) => s.rotation)
|
|
4269
|
-
const
|
|
4198
|
+
const rise = useStairBuildPreview((s) => s.rise)
|
|
4199
|
+
const isActive = mode === 'build' && tool === 'stair'
|
|
4270
4200
|
|
|
4271
|
-
const
|
|
4272
|
-
if (!(isActive && point)) {
|
|
4201
|
+
const previewGeometry = useMemo(() => {
|
|
4202
|
+
if (!(isActive && point && rise && rise > 0)) {
|
|
4273
4203
|
return null
|
|
4274
4204
|
}
|
|
4205
|
+
const previewSegment = {
|
|
4206
|
+
...createSizedStairFlight(rise),
|
|
4207
|
+
id: 'sseg_floorplan_preview' as const,
|
|
4208
|
+
parentId: 'stair_floorplan_preview' as const,
|
|
4209
|
+
}
|
|
4275
4210
|
const previewStair = StairNodeSchema.parse({
|
|
4276
4211
|
id: 'stair_floorplan_preview',
|
|
4277
4212
|
name: 'Staircase preview',
|
|
4278
4213
|
position: [point[0], 0, point[1]],
|
|
4279
4214
|
rotation,
|
|
4280
|
-
children: [
|
|
4215
|
+
children: [previewSegment.id],
|
|
4281
4216
|
metadata: { isTransient: true, isFloorplanPreview: true },
|
|
4282
4217
|
})
|
|
4283
|
-
const
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
points: formatPolygonPoints(segmentEntry.polygon),
|
|
4299
|
-
treadBars: segmentEntry.treadBars.map((polygon) => ({
|
|
4300
|
-
points: formatPolygonPoints(polygon),
|
|
4301
|
-
polygon,
|
|
4302
|
-
})),
|
|
4303
|
-
})),
|
|
4304
|
-
}
|
|
4305
|
-
}, [isActive, point, rotation])
|
|
4306
|
-
|
|
4307
|
-
if (!previewEntry) {
|
|
4308
|
-
return null
|
|
4309
|
-
}
|
|
4218
|
+
const nodes = { [previewStair.id]: previewStair, [previewSegment.id]: previewSegment }
|
|
4219
|
+
return (
|
|
4220
|
+
nodeRegistry.get(previewStair.type)?.floorplan?.(
|
|
4221
|
+
previewStair,
|
|
4222
|
+
buildFloorplanContext(previewStair, nodes, {
|
|
4223
|
+
selected: false,
|
|
4224
|
+
highlighted: false,
|
|
4225
|
+
hovered: false,
|
|
4226
|
+
moving: true,
|
|
4227
|
+
unit: 'metric',
|
|
4228
|
+
palette,
|
|
4229
|
+
}),
|
|
4230
|
+
) ?? null
|
|
4231
|
+
)
|
|
4232
|
+
}, [isActive, point, rotation, rise, palette])
|
|
4310
4233
|
|
|
4311
|
-
return
|
|
4312
|
-
|
|
4313
|
-
canFocusStairs={false}
|
|
4314
|
-
canSelectStairs={false}
|
|
4315
|
-
cursor={EDITOR_CURSOR}
|
|
4316
|
-
highlightedIdSet={EMPTY_FLOORPLAN_ID_SET}
|
|
4317
|
-
hitStrokeWidth={FLOORPLAN_OPENING_HIT_STROKE_WIDTH}
|
|
4318
|
-
hoveredStairId={null}
|
|
4319
|
-
isDeleteMode={isDeleteMode}
|
|
4320
|
-
onStairDoubleClick={noopFloorplanStairHandler}
|
|
4321
|
-
onStairHoverChange={noopFloorplanStairHandler}
|
|
4322
|
-
onStairHoverEnter={noopFloorplanStairHandler}
|
|
4323
|
-
onStairPointerDown={noopFloorplanStairHandler}
|
|
4324
|
-
onStairSelect={noopFloorplanStairHandler}
|
|
4325
|
-
palette={palette}
|
|
4326
|
-
selectedIdSet={EMPTY_FLOORPLAN_ID_SET}
|
|
4327
|
-
stairEntries={[previewEntry]}
|
|
4328
|
-
/>
|
|
4329
|
-
)
|
|
4234
|
+
if (!previewGeometry) return null
|
|
4235
|
+
return <FloorplanGeometryRenderer geometry={previewGeometry} pointerEventsOverride="none" />
|
|
4330
4236
|
}
|
|
4331
4237
|
|
|
4332
4238
|
// Leaf overlay for the cursor-following draft preview: the cursor crosshair plus
|
|
@@ -4530,6 +4436,7 @@ function FloorplanLinearDraftLayer({
|
|
|
4530
4436
|
const roofDraftEnd = useFloorplanDraftPreview((s) => s.roofDraftEnd)
|
|
4531
4437
|
const roofDraftQuarterTurn = useFloorplanDraftPreview((s) => s.roofDraftQuarterTurn)
|
|
4532
4438
|
const wallRectangleDraftStart = useFloorplanDraftPreview((s) => s.wallRectangleDraftStart)
|
|
4439
|
+
const wallPolygonDraftPoints = useFloorplanDraftPreview((s) => s.wallPolygonDraftPoints)
|
|
4533
4440
|
// The live cursor is the rectangle's opposite corner; select it only while a
|
|
4534
4441
|
// rectangle draft is open so idle moves don't re-render this layer.
|
|
4535
4442
|
const wallRectangleDraftEnd = useFloorplanDraftPreview((s) =>
|
|
@@ -4599,6 +4506,20 @@ function FloorplanLinearDraftLayer({
|
|
|
4599
4506
|
wallRectangleDraftStart,
|
|
4600
4507
|
])
|
|
4601
4508
|
|
|
4509
|
+
// An open Polygon room's placed sides — draft state, drawn like the
|
|
4510
|
+
// rectangle draft (mitered together) until the polygon is written.
|
|
4511
|
+
const polygonRoomDraft = useMemo(() => {
|
|
4512
|
+
if (!(levelId && isWallBuildActive && wallPolygonDraftPoints.length >= 2)) return null
|
|
4513
|
+
const draftWalls = wallPolygonDraftPoints.slice(1).map((end, index) =>
|
|
4514
|
+
getSharedFloorplanWall({
|
|
4515
|
+
...buildDraftWall(levelId, wallPolygonDraftPoints[index]!, end),
|
|
4516
|
+
id: `wall_polygon_draft_${index}` as WallNode['id'],
|
|
4517
|
+
}),
|
|
4518
|
+
)
|
|
4519
|
+
const miterData = calculateLevelMiters(draftWalls)
|
|
4520
|
+
return draftWalls.map((wall) => formatPolygonPoints(getWallPlanFootprint(wall, miterData)))
|
|
4521
|
+
}, [isWallBuildActive, levelId, wallPolygonDraftPoints])
|
|
4522
|
+
|
|
4602
4523
|
const draftPolygonPoints = useMemo(() => {
|
|
4603
4524
|
if (isRoofBuildActive && roofDraftStart && roofDraftEnd) {
|
|
4604
4525
|
const minX = Math.min(roofDraftStart[0], roofDraftEnd[0])
|
|
@@ -4831,7 +4752,7 @@ function FloorplanLinearDraftLayer({
|
|
|
4831
4752
|
)}
|
|
4832
4753
|
|
|
4833
4754
|
{draftWallMeasurement && (
|
|
4834
|
-
<
|
|
4755
|
+
<FloorplanDraftMeasurement
|
|
4835
4756
|
labelBackground={isDark ? '#0f172a' : '#ffffff'}
|
|
4836
4757
|
labelText={isDark ? '#e2e8f0' : '#171717'}
|
|
4837
4758
|
measurement={draftWallMeasurement}
|
|
@@ -4841,6 +4762,22 @@ function FloorplanLinearDraftLayer({
|
|
|
4841
4762
|
/>
|
|
4842
4763
|
)}
|
|
4843
4764
|
|
|
4765
|
+
{polygonRoomDraft?.map((points, index) => (
|
|
4766
|
+
<FloorplanDraftLayer
|
|
4767
|
+
anchorFill={draftStroke}
|
|
4768
|
+
draftAnchorPoints={EMPTY_DRAFT_ANCHOR_POINTS}
|
|
4769
|
+
draftFill={draftFill}
|
|
4770
|
+
draftPolygonPoints={points}
|
|
4771
|
+
draftStroke={draftStroke}
|
|
4772
|
+
key={`polygon-room-${index}`}
|
|
4773
|
+
linearDraftSegment={null}
|
|
4774
|
+
polygonDraftClosingSegment={null}
|
|
4775
|
+
polygonDraftPolygonPoints={null}
|
|
4776
|
+
polygonDraftPolylinePoints={null}
|
|
4777
|
+
unitsPerPixel={unitsPerPixel}
|
|
4778
|
+
/>
|
|
4779
|
+
))}
|
|
4780
|
+
|
|
4844
4781
|
{rectangleDraft?.polygons.map((points, index) => (
|
|
4845
4782
|
<FloorplanDraftLayer
|
|
4846
4783
|
anchorFill={draftStroke}
|
|
@@ -4858,7 +4795,7 @@ function FloorplanLinearDraftLayer({
|
|
|
4858
4795
|
))}
|
|
4859
4796
|
|
|
4860
4797
|
{rectangleDraft?.measurements.map((measurement, index) => (
|
|
4861
|
-
<
|
|
4798
|
+
<FloorplanDraftMeasurement
|
|
4862
4799
|
key={index}
|
|
4863
4800
|
labelBackground={isDark ? '#0f172a' : '#ffffff'}
|
|
4864
4801
|
labelText={isDark ? '#e2e8f0' : '#171717'}
|
|
@@ -4962,6 +4899,12 @@ export function FloorplanPanel({
|
|
|
4962
4899
|
const unit = useViewer((state) => state.unit)
|
|
4963
4900
|
const metricNotation = useViewer((state) => state.metricNotation)
|
|
4964
4901
|
const showGrid = useViewer((state) => state.showGrid)
|
|
4902
|
+
// Floor plan vs. site plan. Site plan hides the level geometry and draws
|
|
4903
|
+
// the lot instead — see `lib/floorplan/site-plan/site-plan-layer.tsx`. A
|
|
4904
|
+
// persisted site-plan choice on a scene with no lot reads as the floor plan.
|
|
4905
|
+
const sitePlanAvailable = useSitePlanAvailable()
|
|
4906
|
+
const storedDrawingType = useDrawingView((state) => state.drawingType)
|
|
4907
|
+
const activeDrawingType = sitePlanAvailable ? storedDrawingType : 'floor-plan'
|
|
4965
4908
|
const showGuides = useViewer((state) => state.showGuides)
|
|
4966
4909
|
const setShowGuides = useViewer((state) => state.setShowGuides)
|
|
4967
4910
|
const selectedItem = useEditor((state) => state.selectedItem)
|
|
@@ -4971,8 +4914,21 @@ export function FloorplanPanel({
|
|
|
4971
4914
|
// editor/index.tsx — subscribing here lets us re-fit the viewport when
|
|
4972
4915
|
// the user closes and re-opens the 2D editor instead of restoring the
|
|
4973
4916
|
// stale viewport from before they closed it.
|
|
4974
|
-
const isFloorplanOpen = useEditor((state) => state
|
|
4975
|
-
|
|
4917
|
+
const isFloorplanOpen = useEditor((state) => isViewVisible(state, VIEW_2D))
|
|
4918
|
+
// The compass sits at the scene's bottom-left corner, the drawing switch
|
|
4919
|
+
// beside it. With the plan in the right pane both move to its bottom-right
|
|
4920
|
+
// corner (compass outermost), clear of the dock centred over the scene.
|
|
4921
|
+
const planOnRight = useEditor((state) => {
|
|
4922
|
+
const layout = activeViewLayout(state)
|
|
4923
|
+
return layout.split && layout.panes[1] === VIEW_2D
|
|
4924
|
+
})
|
|
4925
|
+
// `null` while no scene view is on screen: the compass has nothing to orient.
|
|
4926
|
+
const sceneLeft = useEditor((state) => sceneSpan(activeViewLayout(state))?.left ?? null)
|
|
4927
|
+
// Mirrored, the pair stays left of the helper toggle in the stage's corner.
|
|
4928
|
+
const compassStyle: CSSProperties = planOnRight
|
|
4929
|
+
? { right: 56 }
|
|
4930
|
+
: { left: `calc(${(sceneLeft ?? 0) * 100}% + 12px)` }
|
|
4931
|
+
const shownScene = useEditor(visibleScene)
|
|
4976
4932
|
// Mirror for callbacks that fire outside React's render (the per-frame
|
|
4977
4933
|
// navigation-pose subscriber): when the 2D panel is hidden (`display:none` in
|
|
4978
4934
|
// 3D mode) it must NOT re-render on every camera-zoom frame.
|
|
@@ -4990,8 +4946,6 @@ export function FloorplanPanel({
|
|
|
4990
4946
|
const activeHandleDrag = useActiveHandleDrag()
|
|
4991
4947
|
const setPhase = useEditor((state) => state.setPhase)
|
|
4992
4948
|
const setMovingNode = useEditor((state) => state.setMovingNode)
|
|
4993
|
-
const structureLayer = useEditor((state) => state.structureLayer)
|
|
4994
|
-
const setStructureLayer = useEditor((state) => state.setStructureLayer)
|
|
4995
4949
|
const setTool = useEditor((state) => state.setTool)
|
|
4996
4950
|
const tool = useEditor((state) => state.tool)
|
|
4997
4951
|
const deleteNode = useScene((state) => state.deleteNode)
|
|
@@ -5273,12 +5227,6 @@ export function FloorplanPanel({
|
|
|
5273
5227
|
height: number
|
|
5274
5228
|
} | null>(null)
|
|
5275
5229
|
|
|
5276
|
-
useEffect(() => {
|
|
5277
|
-
if (structureLayer === 'zones' && floorplanSelectionTool === 'marquee') {
|
|
5278
|
-
setFloorplanSelectionTool('click')
|
|
5279
|
-
}
|
|
5280
|
-
}, [floorplanSelectionTool, setFloorplanSelectionTool, structureLayer])
|
|
5281
|
-
|
|
5282
5230
|
useEffect(() => {
|
|
5283
5231
|
setIsMacPlatform(navigator.platform.toUpperCase().includes('MAC'))
|
|
5284
5232
|
}, [])
|
|
@@ -5792,21 +5740,21 @@ export function FloorplanPanel({
|
|
|
5792
5740
|
)
|
|
5793
5741
|
|
|
5794
5742
|
const isSiteEditActive = phase === 'site'
|
|
5795
|
-
const isWallBuildActive =
|
|
5796
|
-
const isSlabBuildActive =
|
|
5797
|
-
const isCeilingBuildActive =
|
|
5798
|
-
const isZoneBuildActive =
|
|
5799
|
-
const isDoorBuildActive =
|
|
5800
|
-
const isWindowBuildActive =
|
|
5743
|
+
const isWallBuildActive = mode === 'build' && tool === 'wall'
|
|
5744
|
+
const isSlabBuildActive = mode === 'build' && tool === 'slab'
|
|
5745
|
+
const isCeilingBuildActive = mode === 'build' && tool === 'ceiling'
|
|
5746
|
+
const isZoneBuildActive = mode === 'build' && tool === 'zone'
|
|
5747
|
+
const isDoorBuildActive = mode === 'build' && tool === 'door'
|
|
5748
|
+
const isWindowBuildActive = mode === 'build' && tool === 'window'
|
|
5801
5749
|
const isPolygonBuildActive = isSlabBuildActive || isZoneBuildActive
|
|
5802
5750
|
const isPolygonDraftBuildActive = isPolygonBuildActive || isCeilingBuildActive
|
|
5803
5751
|
const isOpeningBuildActive = isDoorBuildActive || isWindowBuildActive
|
|
5804
5752
|
const isOpeningMoveActive = movingOpeningType !== null
|
|
5805
5753
|
const isOpeningPlacementActive = isOpeningBuildActive || isOpeningMoveActive
|
|
5806
|
-
const isFenceBuildActive =
|
|
5754
|
+
const isFenceBuildActive = mode === 'build' && tool === 'fence'
|
|
5807
5755
|
const fenceContinuation = useEditor((state) => state.continuationByContext.fence)
|
|
5808
|
-
const isRoofBuildActive =
|
|
5809
|
-
const isStairBuildActive =
|
|
5756
|
+
const isRoofBuildActive = mode === 'build' && tool === 'roof'
|
|
5757
|
+
const isStairBuildActive = mode === 'build' && tool === 'stair'
|
|
5810
5758
|
const isStairMoveActive = movingNode?.type === 'stair'
|
|
5811
5759
|
const isRoofMoveActive = movingNode?.type === 'roof' || movingNode?.type === 'roof-segment'
|
|
5812
5760
|
const isSlabMoveActive = movingNode?.type === 'slab'
|
|
@@ -5932,12 +5880,11 @@ export function FloorplanPanel({
|
|
|
5932
5880
|
mode === 'select' &&
|
|
5933
5881
|
floorplanSelectionTool === 'marquee' &&
|
|
5934
5882
|
!movingNode &&
|
|
5935
|
-
!isFenceEndpointMoveActive
|
|
5936
|
-
structureLayer !== 'zones'
|
|
5883
|
+
!isFenceEndpointMoveActive
|
|
5937
5884
|
const isScreenSelectionToolActive =
|
|
5938
5885
|
mode === 'select' &&
|
|
5939
5886
|
floorplanSelectionTool === 'click' &&
|
|
5940
|
-
|
|
5887
|
+
phase === 'building' &&
|
|
5941
5888
|
!movingNode &&
|
|
5942
5889
|
!isFenceEndpointMoveActive &&
|
|
5943
5890
|
!referenceScaleDraft &&
|
|
@@ -5947,8 +5894,7 @@ export function FloorplanPanel({
|
|
|
5947
5894
|
mode === 'select' &&
|
|
5948
5895
|
floorplanSelectionTool === 'click' &&
|
|
5949
5896
|
!movingNode &&
|
|
5950
|
-
!isFenceEndpointMoveActive
|
|
5951
|
-
structureLayer !== 'zones'
|
|
5897
|
+
!isFenceEndpointMoveActive
|
|
5952
5898
|
const canInteractElementFloorplanGeometry = isDeleteMode || canSelectElementFloorplanGeometry
|
|
5953
5899
|
const canInteractFloorplanSlabs = isDeleteMode || canSelectElementFloorplanGeometry
|
|
5954
5900
|
const canInteractWithGuides =
|
|
@@ -5960,42 +5906,7 @@ export function FloorplanPanel({
|
|
|
5960
5906
|
mode === 'select' &&
|
|
5961
5907
|
floorplanSelectionTool === 'click' &&
|
|
5962
5908
|
!movingNode &&
|
|
5963
|
-
!isFenceEndpointMoveActive
|
|
5964
|
-
structureLayer === 'zones'
|
|
5965
|
-
const canInteractFloorplanZones = isDeleteMode || canSelectFloorplanZones
|
|
5966
|
-
const isFloorplanStructureContextActive = phase === 'structure' && structureLayer !== 'zones'
|
|
5967
|
-
const isFloorplanFurnishContextActive = phase === 'furnish'
|
|
5968
|
-
const isFloorplanItemContextActive =
|
|
5969
|
-
isFloorplanFurnishContextActive || isFloorplanStructureContextActive
|
|
5970
|
-
const canSelectFloorplanStairs =
|
|
5971
|
-
(mode === 'select' &&
|
|
5972
|
-
floorplanSelectionTool === 'click' &&
|
|
5973
|
-
!movingNode &&
|
|
5974
|
-
!isFenceEndpointMoveActive &&
|
|
5975
|
-
isFloorplanStructureContextActive) ||
|
|
5976
|
-
isDeleteMode
|
|
5977
|
-
const canSelectFloorplanElevators = canSelectFloorplanStairs
|
|
5978
|
-
const canSelectFloorplanSpawns = canSelectFloorplanStairs
|
|
5979
|
-
const canSelectFloorplanItems =
|
|
5980
|
-
(mode === 'select' &&
|
|
5981
|
-
floorplanSelectionTool === 'click' &&
|
|
5982
|
-
!movingNode &&
|
|
5983
|
-
!isFenceEndpointMoveActive &&
|
|
5984
|
-
isFloorplanItemContextActive) ||
|
|
5985
|
-
isDeleteMode
|
|
5986
|
-
const canFocusFloorplanStairs =
|
|
5987
|
-
mode === 'select' &&
|
|
5988
|
-
floorplanSelectionTool === 'click' &&
|
|
5989
|
-
!movingNode &&
|
|
5990
|
-
!isFenceEndpointMoveActive &&
|
|
5991
|
-
isFloorplanStructureContextActive
|
|
5992
|
-
const canFocusFloorplanSpawns = canFocusFloorplanStairs
|
|
5993
|
-
const canFocusFloorplanItems =
|
|
5994
|
-
mode === 'select' &&
|
|
5995
|
-
floorplanSelectionTool === 'click' &&
|
|
5996
|
-
!movingNode &&
|
|
5997
|
-
!isFenceEndpointMoveActive &&
|
|
5998
|
-
isFloorplanItemContextActive
|
|
5909
|
+
!isFenceEndpointMoveActive
|
|
5999
5910
|
const visibleSitePolygon = displaySitePolygon
|
|
6000
5911
|
const canUseSiteBoundaryVertexHandles =
|
|
6001
5912
|
visibleSitePolygon !== null && siteBoundaryHandlesEnabled({ mode, phase })
|
|
@@ -7648,7 +7559,7 @@ export function FloorplanPanel({
|
|
|
7648
7559
|
scheduleFloorplanZoomCommit()
|
|
7649
7560
|
const userRotationDeg = latestFloorplanUserRotationDegRef.current
|
|
7650
7561
|
floorplanZoomPoseRef.current = { localCenter, userRotationDeg, viewWidth: nextWidth }
|
|
7651
|
-
if (useEditor.getState()
|
|
7562
|
+
if (visibleScene(useEditor.getState()) === 'split') {
|
|
7652
7563
|
publishFloorplanNavigationPose(localCenter, userRotationDeg, nextWidth)
|
|
7653
7564
|
}
|
|
7654
7565
|
},
|
|
@@ -7731,28 +7642,24 @@ export function FloorplanPanel({
|
|
|
7731
7642
|
right: false,
|
|
7732
7643
|
})
|
|
7733
7644
|
useEffect(() => {
|
|
7734
|
-
if (
|
|
7645
|
+
if (shownScene !== '2d') return
|
|
7735
7646
|
const keys = keyboardPanKeysRef.current
|
|
7647
|
+
const motion = createKeyboardPanMotion()
|
|
7736
7648
|
let frame: number | null = null
|
|
7737
|
-
let lastTime: number | null = null
|
|
7738
7649
|
const sceneRotationDeg = (userRotationDeg: number) =>
|
|
7739
7650
|
FLOORPLAN_VIEW_ROTATION_DEG + userRotationDeg - buildingRotationDeg
|
|
7740
|
-
const step = (
|
|
7741
|
-
const { horizontal, vertical } = keyboardPanDirection(keys)
|
|
7651
|
+
const step = () => {
|
|
7742
7652
|
const viewport = latestViewportRef.current ?? latestFittedViewportRef.current
|
|
7743
|
-
if ((
|
|
7653
|
+
if (!(viewport && advanceKeyboardPanMotion(motion, keys, performance.now()))) {
|
|
7744
7654
|
frame = null
|
|
7745
|
-
|
|
7655
|
+
resetKeyboardPanMotion(motion)
|
|
7746
7656
|
commitFloorplanPan()
|
|
7747
7657
|
return
|
|
7748
7658
|
}
|
|
7749
|
-
const
|
|
7750
|
-
lastTime = time
|
|
7751
|
-
const distance =
|
|
7752
|
-
(keyboardPanSpeed(viewport.width) * elapsed) / Math.hypot(horizontal, vertical)
|
|
7659
|
+
const speed = keyboardPanSpeed(viewport.width)
|
|
7753
7660
|
const next = {
|
|
7754
|
-
centerX: viewport.centerX +
|
|
7755
|
-
centerY: viewport.centerY -
|
|
7661
|
+
centerX: viewport.centerX + motion.stepX * speed,
|
|
7662
|
+
centerY: viewport.centerY - motion.stepY * speed,
|
|
7756
7663
|
width: viewport.width,
|
|
7757
7664
|
}
|
|
7758
7665
|
const userRotationDeg = latestFloorplanUserRotationDegRef.current
|
|
@@ -7773,12 +7680,15 @@ export function FloorplanPanel({
|
|
|
7773
7680
|
}
|
|
7774
7681
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
7775
7682
|
if (!(isKeyboardPanKey(event.code) && acceptsKeyboardPan(event))) return
|
|
7683
|
+
syncKeyboardPanMotion(motion, keys, performance.now())
|
|
7776
7684
|
if (setKeyboardPanKey(keys, event.code, true)) start()
|
|
7777
7685
|
event.preventDefault()
|
|
7778
7686
|
event.stopPropagation()
|
|
7779
7687
|
}
|
|
7780
7688
|
const onKeyUp = (event: KeyboardEvent) => {
|
|
7781
|
-
if (isKeyboardPanKey(event.code)
|
|
7689
|
+
if (!isKeyboardPanKey(event.code)) return
|
|
7690
|
+
syncKeyboardPanMotion(motion, keys, performance.now())
|
|
7691
|
+
if (setKeyboardPanKey(keys, event.code, false)) {
|
|
7782
7692
|
event.preventDefault()
|
|
7783
7693
|
event.stopPropagation()
|
|
7784
7694
|
}
|
|
@@ -7828,7 +7738,7 @@ export function FloorplanPanel({
|
|
|
7828
7738
|
commitFloorplanPan,
|
|
7829
7739
|
publishFloorplanNavigationPose,
|
|
7830
7740
|
smoothFloorplanNavigationView,
|
|
7831
|
-
|
|
7741
|
+
shownScene,
|
|
7832
7742
|
])
|
|
7833
7743
|
|
|
7834
7744
|
useLayoutEffect(() => {
|
|
@@ -7880,7 +7790,10 @@ export function FloorplanPanel({
|
|
|
7880
7790
|
}
|
|
7881
7791
|
}, [isFloorplanOpen, stopFloorplanViewAnimation])
|
|
7882
7792
|
|
|
7793
|
+
// Clearing an open wall draft drops a Polygon room in progress (nothing of
|
|
7794
|
+
// it was written); paths that keep it commit first.
|
|
7883
7795
|
const clearWallPlacementDraft = useCallback(() => {
|
|
7796
|
+
discardWallPolygonDraft()
|
|
7884
7797
|
setDraftStart(null)
|
|
7885
7798
|
setWallChainFirstVertex(null)
|
|
7886
7799
|
wallConstructionOptionsRef.current = undefined
|
|
@@ -7937,10 +7850,9 @@ export function FloorplanPanel({
|
|
|
7937
7850
|
setHoveredSiteHandleId(null)
|
|
7938
7851
|
}, [clearSiteBoundaryLivePreview])
|
|
7939
7852
|
const exitSiteEditingToSelect = useCallback(() => {
|
|
7940
|
-
setPhase('
|
|
7941
|
-
setStructureLayer('elements')
|
|
7853
|
+
setPhase('building')
|
|
7942
7854
|
setMode('select')
|
|
7943
|
-
}, [setMode, setPhase
|
|
7855
|
+
}, [setMode, setPhase])
|
|
7944
7856
|
|
|
7945
7857
|
const clearDraft = useCallback(() => {
|
|
7946
7858
|
clearWallPlacementDraft()
|
|
@@ -8002,10 +7914,8 @@ export function FloorplanPanel({
|
|
|
8002
7914
|
}
|
|
8003
7915
|
|
|
8004
7916
|
const { createNode, updateNode, nodes } = useScene.getState()
|
|
8005
|
-
const
|
|
8006
|
-
|
|
8007
|
-
color: PALETTE_COLORS[zoneCount % PALETTE_COLORS.length],
|
|
8008
|
-
name: `Zone ${zoneCount + 1}`,
|
|
7917
|
+
const zone = newZone({
|
|
7918
|
+
name: nextZoneName(nodes),
|
|
8009
7919
|
polygon: points.map(([x, z]) => [x, z] as [number, number]),
|
|
8010
7920
|
})
|
|
8011
7921
|
|
|
@@ -8287,13 +8197,13 @@ export function FloorplanPanel({
|
|
|
8287
8197
|
|
|
8288
8198
|
if (
|
|
8289
8199
|
isStairBuildActive &&
|
|
8290
|
-
useEditor.getState()
|
|
8200
|
+
visibleScene(useEditor.getState()) === '2d' &&
|
|
8291
8201
|
(event.key === 'r' || event.key === 'R')
|
|
8292
8202
|
) {
|
|
8293
8203
|
useStairBuildPreview.getState().rotateBy(Math.PI / 4)
|
|
8294
8204
|
} else if (
|
|
8295
8205
|
isStairBuildActive &&
|
|
8296
|
-
useEditor.getState()
|
|
8206
|
+
visibleScene(useEditor.getState()) === '2d' &&
|
|
8297
8207
|
(event.key === 't' || event.key === 'T')
|
|
8298
8208
|
) {
|
|
8299
8209
|
useStairBuildPreview.getState().rotateBy(-Math.PI / 4)
|
|
@@ -9004,7 +8914,7 @@ export function FloorplanPanel({
|
|
|
9004
8914
|
// tools that subscribe to these grid events.
|
|
9005
8915
|
const emitFloorplanGridEvent = useCallback(
|
|
9006
8916
|
(
|
|
9007
|
-
eventType: 'move' | 'click' | 'double-click',
|
|
8917
|
+
eventType: 'move' | 'click' | 'double-click' | 'pointerdown' | 'pointerup',
|
|
9008
8918
|
planPoint: WallPlanPoint,
|
|
9009
8919
|
nativeEvent: ReactMouseEvent<SVGSVGElement> | ReactPointerEvent<SVGSVGElement>,
|
|
9010
8920
|
) => {
|
|
@@ -9205,7 +9115,7 @@ export function FloorplanPanel({
|
|
|
9205
9115
|
const nextUserRotationDeg = rotationState.initialUserRotationDeg + angleDeltaDeg
|
|
9206
9116
|
|
|
9207
9117
|
applyFloorplanRotationImperatively(rotationState, nextUserRotationDeg)
|
|
9208
|
-
if (useEditor.getState()
|
|
9118
|
+
if (visibleScene(useEditor.getState()) === 'split') {
|
|
9209
9119
|
publishFloorplanNavigationPose(
|
|
9210
9120
|
rotationState.viewportCenterLocal,
|
|
9211
9121
|
nextUserRotationDeg,
|
|
@@ -9247,7 +9157,7 @@ export function FloorplanPanel({
|
|
|
9247
9157
|
userRotationDeg: currentUserRotationDeg,
|
|
9248
9158
|
viewWidth: currentViewport.width,
|
|
9249
9159
|
}
|
|
9250
|
-
if (useEditor.getState()
|
|
9160
|
+
if (visibleScene(useEditor.getState()) === 'split') {
|
|
9251
9161
|
publishFloorplanNavigationPose(localCenter, currentUserRotationDeg, currentViewport.width)
|
|
9252
9162
|
}
|
|
9253
9163
|
|
|
@@ -9339,6 +9249,14 @@ export function FloorplanPanel({
|
|
|
9339
9249
|
}
|
|
9340
9250
|
|
|
9341
9251
|
if (isFenceBuildActive) {
|
|
9252
|
+
if (useEditor.getState().getContinuation('fence') === 'freehand') {
|
|
9253
|
+
useAlignmentGuides.getState().clear()
|
|
9254
|
+
emitFloorplanGridEvent('move', planPoint, event)
|
|
9255
|
+
setCursorPoint((previousPoint) =>
|
|
9256
|
+
previousPoint && pointsEqual(previousPoint, planPoint) ? previousPoint : planPoint,
|
|
9257
|
+
)
|
|
9258
|
+
return
|
|
9259
|
+
}
|
|
9342
9260
|
// Fence draft: grid snap (+ existing-wall/fence endpoint snap), then
|
|
9343
9261
|
// Figma alignment — same endpoint-wins precedence as the wall branch.
|
|
9344
9262
|
// While a draft is open the segment locks to 15° rays from its start.
|
|
@@ -9346,14 +9264,18 @@ export function FloorplanPanel({
|
|
|
9346
9264
|
// there is no Shift hold-to-bypass. Alignment follows the magnetic snap
|
|
9347
9265
|
// mode, not Alt (continuation is cycled through the HUD / C).
|
|
9348
9266
|
const fenceAngleSnap = fenceDraftStart !== null && isAngleSnapActive()
|
|
9349
|
-
const fenceSnapped =
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9267
|
+
const fenceSnapped = snapRegisteredDraftPoint(
|
|
9268
|
+
'fence',
|
|
9269
|
+
{
|
|
9270
|
+
point: planPoint,
|
|
9271
|
+
walls,
|
|
9272
|
+
fences,
|
|
9273
|
+
start: fenceDraftStart ?? undefined,
|
|
9274
|
+
angleSnap: fenceAngleSnap,
|
|
9275
|
+
magnetic: isMagneticSnapActive(),
|
|
9276
|
+
},
|
|
9277
|
+
planPoint,
|
|
9278
|
+
)
|
|
9357
9279
|
const fenceGridBase = snapWallPointToGrid(planPoint)
|
|
9358
9280
|
const fenceLocked =
|
|
9359
9281
|
fenceSnapped[0] !== fenceGridBase[0] || fenceSnapped[1] !== fenceGridBase[1]
|
|
@@ -9542,7 +9464,7 @@ export function FloorplanPanel({
|
|
|
9542
9464
|
const wallAngleSnap = draftStart !== null && isAngleSnapActive()
|
|
9543
9465
|
const wallSnap = snapWallDraftPointDetailed({
|
|
9544
9466
|
point: planPoint,
|
|
9545
|
-
walls,
|
|
9467
|
+
walls: [...walls, ...wallPolygonDraftWalls()],
|
|
9546
9468
|
start: draftStart ?? undefined,
|
|
9547
9469
|
angleSnap: wallAngleSnap,
|
|
9548
9470
|
magnetic: isMagneticSnapActive(),
|
|
@@ -9649,7 +9571,7 @@ export function FloorplanPanel({
|
|
|
9649
9571
|
const firstPoint = slabDraftPoints[0]
|
|
9650
9572
|
if (firstPoint && slabDraftPoints.length >= 3 && isPointNearPlanPoint(point, firstPoint)) {
|
|
9651
9573
|
// 2D-only view: the 3D tool can't commit, so close the polygon here.
|
|
9652
|
-
if (useEditor.getState()
|
|
9574
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9653
9575
|
createSlabOnCurrentLevel(slabDraftPoints, slabPlacementBaseRef.current)
|
|
9654
9576
|
}
|
|
9655
9577
|
clearDraft()
|
|
@@ -9699,7 +9621,7 @@ export function FloorplanPanel({
|
|
|
9699
9621
|
}
|
|
9700
9622
|
|
|
9701
9623
|
// 2D-only view: the 3D tool can't commit, so create the slab here.
|
|
9702
|
-
if (useEditor.getState()
|
|
9624
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9703
9625
|
createSlabOnCurrentLevel(nextPoints, slabPlacementBaseRef.current)
|
|
9704
9626
|
}
|
|
9705
9627
|
clearDraft()
|
|
@@ -9715,7 +9637,7 @@ export function FloorplanPanel({
|
|
|
9715
9637
|
|
|
9716
9638
|
const firstPoint = ceilingDraftPoints[0]
|
|
9717
9639
|
if (firstPoint && ceilingDraftPoints.length >= 3 && isPointNearPlanPoint(point, firstPoint)) {
|
|
9718
|
-
if (useEditor.getState()
|
|
9640
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9719
9641
|
createCeilingOnCurrentLevel(ceilingDraftPoints)
|
|
9720
9642
|
}
|
|
9721
9643
|
clearCeilingPlacementDraft()
|
|
@@ -9748,7 +9670,7 @@ export function FloorplanPanel({
|
|
|
9748
9670
|
return
|
|
9749
9671
|
}
|
|
9750
9672
|
|
|
9751
|
-
if (useEditor.getState()
|
|
9673
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9752
9674
|
createCeilingOnCurrentLevel(nextPoints)
|
|
9753
9675
|
}
|
|
9754
9676
|
clearCeilingPlacementDraft()
|
|
@@ -9815,6 +9737,14 @@ export function FloorplanPanel({
|
|
|
9815
9737
|
useEditor.getState().toolDefaults.wall,
|
|
9816
9738
|
)
|
|
9817
9739
|
: undefined
|
|
9740
|
+
// 2D-only owns a Polygon room's draft; split view leaves it to the 3D tool.
|
|
9741
|
+
if (
|
|
9742
|
+
levelId &&
|
|
9743
|
+
visibleScene(useEditor.getState()) === '2d' &&
|
|
9744
|
+
getWallDrawVariant() === 'polygon'
|
|
9745
|
+
) {
|
|
9746
|
+
startWallPolygonDraft(levelId as AnyNodeId, point, wallConstructionOptionsRef.current)
|
|
9747
|
+
}
|
|
9818
9748
|
setDraftStart(point)
|
|
9819
9749
|
setWallChainFirstVertex(point)
|
|
9820
9750
|
setDraftEnd(point)
|
|
@@ -9840,8 +9770,22 @@ export function FloorplanPanel({
|
|
|
9840
9770
|
// `display:none`, so the tool never commits. Mirror the slab /
|
|
9841
9771
|
// ceiling 2D-only committers: create locally here, gated on the
|
|
9842
9772
|
// view, so split / 3D keep their single-owner tool commit.
|
|
9843
|
-
const viewIs2DOnly = useEditor.getState()
|
|
9773
|
+
const viewIs2DOnly = visibleScene(useEditor.getState()) === '2d'
|
|
9844
9774
|
let createdWall: WallNode | null = null
|
|
9775
|
+
// 2D-only Polygon room: the corner joins the draft; nothing is written
|
|
9776
|
+
// until it closes, seals or tees (then every wall at once).
|
|
9777
|
+
if (viewIs2DOnly && getWallDrawVariant() === 'polygon') {
|
|
9778
|
+
if (addWallPolygonDraftCorner(point) !== 'open') {
|
|
9779
|
+
commitWallPolygonDraft()
|
|
9780
|
+
clearWallPlacementDraft()
|
|
9781
|
+
setCursorPoint(null)
|
|
9782
|
+
return
|
|
9783
|
+
}
|
|
9784
|
+
setDraftStart(point)
|
|
9785
|
+
setDraftEnd(point)
|
|
9786
|
+
setCursorPoint(point)
|
|
9787
|
+
return
|
|
9788
|
+
}
|
|
9845
9789
|
if (viewIs2DOnly) {
|
|
9846
9790
|
createdWall = createWallOnCurrentLevel(
|
|
9847
9791
|
draftStart,
|
|
@@ -10045,18 +9989,25 @@ export function FloorplanPanel({
|
|
|
10045
9989
|
return
|
|
10046
9990
|
}
|
|
10047
9991
|
|
|
9992
|
+
if (useInteractionScope.getState().scope.kind !== 'idle') return
|
|
10048
9993
|
const modifierKeys = getSelectionModifierKeys(event)
|
|
10049
9994
|
|
|
9995
|
+
if (enterBuildingFromPlanHit(getFloorplanHitIdAtPoint(planPoint))) return
|
|
9996
|
+
if (roomPickingEnabled()) {
|
|
9997
|
+
const hitId = getFloorplanHitIdAtPoint(planPoint)
|
|
9998
|
+
if (selectRoomFromHit(resolvePlanRoomHit(hitId, planPoint), modifierKeys, hitId)) return
|
|
9999
|
+
}
|
|
10050
10000
|
const backgroundSelection = resolveFloorplanBackgroundSelection({
|
|
10051
10001
|
canSelectElementFloorplanGeometry,
|
|
10052
|
-
canSelectFloorplanZones,
|
|
10002
|
+
canSelectFloorplanZones: canSelectFloorplanZones && !!useViewer.getState().focusedUnitId,
|
|
10053
10003
|
currentSelectedIds: useViewer.getState().selection.selectedIds,
|
|
10054
10004
|
expandIdsForNode: expandSessionSelectionForNode,
|
|
10055
|
-
getFloorplanHitIdAtPoint
|
|
10005
|
+
getFloorplanHitIdAtPoint: useViewer.getState().focusedUnitId
|
|
10006
|
+
? (point) => zoneAtLevelPoint(point[0], point[1])?.id ?? null
|
|
10007
|
+
: getFloorplanHitIdAtPoint,
|
|
10056
10008
|
isWallBuildActive,
|
|
10057
10009
|
modifierKeys,
|
|
10058
10010
|
planPoint,
|
|
10059
|
-
structureLayer,
|
|
10060
10011
|
})
|
|
10061
10012
|
|
|
10062
10013
|
if (backgroundSelection.handled) {
|
|
@@ -10104,15 +10055,14 @@ export function FloorplanPanel({
|
|
|
10104
10055
|
}
|
|
10105
10056
|
|
|
10106
10057
|
if (backgroundSelection.kind === 'clear-zones') {
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
useEditor.getState().setStructureLayer('elements')
|
|
10110
|
-
useEditor.getState().setMode('select')
|
|
10058
|
+
useEditor.getState().clearRoom()
|
|
10059
|
+
setSelection({ selectedIds: [], zoneId: null })
|
|
10111
10060
|
return
|
|
10112
10061
|
}
|
|
10113
10062
|
|
|
10114
10063
|
if (!backgroundSelection.preserveSelection) {
|
|
10115
|
-
|
|
10064
|
+
useEditor.getState().clearRoom()
|
|
10065
|
+
setSelection({ selectedIds: [], zoneId: null })
|
|
10116
10066
|
}
|
|
10117
10067
|
return
|
|
10118
10068
|
}
|
|
@@ -10130,7 +10080,6 @@ export function FloorplanPanel({
|
|
|
10130
10080
|
referenceScaleDraft,
|
|
10131
10081
|
setSelectedReferenceId,
|
|
10132
10082
|
setSelection,
|
|
10133
|
-
structureLayer,
|
|
10134
10083
|
getFloorplanHitIdAtPoint,
|
|
10135
10084
|
unit,
|
|
10136
10085
|
emitFloorplanGridEvent,
|
|
@@ -10150,6 +10099,49 @@ export function FloorplanPanel({
|
|
|
10150
10099
|
},
|
|
10151
10100
|
[handleBackgroundClick],
|
|
10152
10101
|
)
|
|
10102
|
+
const handleSvgPointerDown = useCallback(
|
|
10103
|
+
(event: ReactPointerEvent<SVGSVGElement>) => {
|
|
10104
|
+
if (
|
|
10105
|
+
isFenceBuildActive &&
|
|
10106
|
+
useEditor.getState().getContinuation('fence') === 'freehand' &&
|
|
10107
|
+
event.button === 0
|
|
10108
|
+
) {
|
|
10109
|
+
const point = getPlanPointFromClientPoint(event.clientX, event.clientY)
|
|
10110
|
+
if (point) {
|
|
10111
|
+
clearFencePlacementDraft()
|
|
10112
|
+
event.currentTarget.setPointerCapture(event.pointerId)
|
|
10113
|
+
emitFloorplanGridEvent('pointerdown', point, event)
|
|
10114
|
+
}
|
|
10115
|
+
}
|
|
10116
|
+
handlePointerDown(event)
|
|
10117
|
+
},
|
|
10118
|
+
[
|
|
10119
|
+
clearFencePlacementDraft,
|
|
10120
|
+
emitFloorplanGridEvent,
|
|
10121
|
+
getPlanPointFromClientPoint,
|
|
10122
|
+
handlePointerDown,
|
|
10123
|
+
isFenceBuildActive,
|
|
10124
|
+
],
|
|
10125
|
+
)
|
|
10126
|
+
const handleSvgPointerUp = useCallback(
|
|
10127
|
+
(event: ReactPointerEvent<SVGSVGElement>) => {
|
|
10128
|
+
if (
|
|
10129
|
+
isFenceBuildActive &&
|
|
10130
|
+
useEditor.getState().getContinuation('fence') === 'freehand' &&
|
|
10131
|
+
event.button === 0
|
|
10132
|
+
) {
|
|
10133
|
+
const point = getPlanPointFromClientPoint(event.clientX, event.clientY)
|
|
10134
|
+
if (point) emitFloorplanGridEvent('pointerup', point, event)
|
|
10135
|
+
}
|
|
10136
|
+
endFloorplanNavigation(event)
|
|
10137
|
+
},
|
|
10138
|
+
[
|
|
10139
|
+
emitFloorplanGridEvent,
|
|
10140
|
+
endFloorplanNavigation,
|
|
10141
|
+
getPlanPointFromClientPoint,
|
|
10142
|
+
isFenceBuildActive,
|
|
10143
|
+
],
|
|
10144
|
+
)
|
|
10153
10145
|
const handleBackgroundDoubleClick = useCallback(
|
|
10154
10146
|
(event: ReactMouseEvent<SVGSVGElement>) => {
|
|
10155
10147
|
if (!(isPolygonDraftBuildActive && !isRoofBuildActive)) {
|
|
@@ -10300,28 +10292,18 @@ export function FloorplanPanel({
|
|
|
10300
10292
|
if (floorplanScreenSelectionOwnsInputDraggingRef.current) {
|
|
10301
10293
|
useViewer.getState().setInputDragging(false)
|
|
10302
10294
|
floorplanScreenSelectionOwnsInputDraggingRef.current = false
|
|
10295
|
+
useInteractionScope.getState().endIf((scope) => scope.kind === 'box-select')
|
|
10303
10296
|
}
|
|
10304
10297
|
}, [syncPreviewSelectedIds])
|
|
10305
10298
|
|
|
10306
10299
|
const commitFloorplanScreenSelection = useCallback(
|
|
10307
10300
|
(nextSelectedIds: string[], event: PointerEvent) => {
|
|
10308
10301
|
const modifierKeys = getSelectionModifierKeys(event)
|
|
10309
|
-
const shouldAppend = modifierKeys.meta || modifierKeys.ctrl || modifierKeys.shift
|
|
10310
|
-
|
|
10311
10302
|
setSelectedReferenceId(null)
|
|
10312
10303
|
|
|
10313
|
-
if (phase === 'structure' && structureLayer === 'zones') {
|
|
10314
|
-
if (nextSelectedIds.length > 0) {
|
|
10315
|
-
setSelection({ zoneId: nextSelectedIds[0] as ZoneNodeType['id'] })
|
|
10316
|
-
} else if (!shouldAppend) {
|
|
10317
|
-
setSelection({ zoneId: null })
|
|
10318
|
-
}
|
|
10319
|
-
return
|
|
10320
|
-
}
|
|
10321
|
-
|
|
10322
10304
|
addFloorplanSelection(nextSelectedIds, modifierKeys)
|
|
10323
10305
|
},
|
|
10324
|
-
[addFloorplanSelection,
|
|
10306
|
+
[addFloorplanSelection, setSelectedReferenceId],
|
|
10325
10307
|
)
|
|
10326
10308
|
|
|
10327
10309
|
useEffect(() => {
|
|
@@ -10371,6 +10353,7 @@ export function FloorplanPanel({
|
|
|
10371
10353
|
if (!state.isDragging && dragDistance >= SCREEN_RECTANGLE_SELECTION_DRAG_THRESHOLD_PX) {
|
|
10372
10354
|
state.isDragging = true
|
|
10373
10355
|
floorplanScreenSelectionOwnsInputDraggingRef.current = true
|
|
10356
|
+
useInteractionScope.getState().begin({ kind: 'box-select' })
|
|
10374
10357
|
useViewer.getState().setInputDragging(true)
|
|
10375
10358
|
markBoxSelectHandled()
|
|
10376
10359
|
|
|
@@ -10775,11 +10758,16 @@ export function FloorplanPanel({
|
|
|
10775
10758
|
const hasFloorplanCursorIndicator =
|
|
10776
10759
|
Boolean(movingOpeningType) ||
|
|
10777
10760
|
(mode === 'build' && tool !== null) ||
|
|
10778
|
-
(mode === 'select' && floorplanSelectionTool === 'marquee'
|
|
10761
|
+
(mode === 'select' && floorplanSelectionTool === 'marquee') ||
|
|
10779
10762
|
mode === 'delete'
|
|
10780
10763
|
|
|
10781
10764
|
const handleSvgPointerMove = useCallback(
|
|
10782
10765
|
(event: ReactPointerEvent<SVGSVGElement>) => {
|
|
10766
|
+
if (roomPickingEnabled() && !(event.target as Element).closest('.floorplan-registry-entry')) {
|
|
10767
|
+
const point = getPlanPointFromClientPoint(event.clientX, event.clientY)
|
|
10768
|
+
if (point)
|
|
10769
|
+
hoverRoomFromHit(resolvePlanRoomHit(null, point), getSelectionModifierKeys(event), null)
|
|
10770
|
+
}
|
|
10783
10771
|
if (
|
|
10784
10772
|
hasFloorplanCursorIndicator &&
|
|
10785
10773
|
!isSpacePanPressed &&
|
|
@@ -10812,6 +10800,7 @@ export function FloorplanPanel({
|
|
|
10812
10800
|
[
|
|
10813
10801
|
handlePointerMove,
|
|
10814
10802
|
hasFloorplanCursorIndicator,
|
|
10803
|
+
getPlanPointFromClientPoint,
|
|
10815
10804
|
isSpacePanPressed,
|
|
10816
10805
|
siteVertexDragState,
|
|
10817
10806
|
setFloorplanCursorPosition,
|
|
@@ -10819,6 +10808,7 @@ export function FloorplanPanel({
|
|
|
10819
10808
|
)
|
|
10820
10809
|
|
|
10821
10810
|
const handleSvgPointerLeave = useCallback(() => {
|
|
10811
|
+
useEditor.getState().setHoveredRoom(null)
|
|
10822
10812
|
setFloorplanCursorPosition(null)
|
|
10823
10813
|
setHoveredGuideCorner(null)
|
|
10824
10814
|
handlePointerLeave()
|
|
@@ -11129,8 +11119,7 @@ export function FloorplanPanel({
|
|
|
11129
11119
|
)
|
|
11130
11120
|
})()
|
|
11131
11121
|
|
|
11132
|
-
setPhase('
|
|
11133
|
-
setStructureLayer('elements')
|
|
11122
|
+
setPhase('building')
|
|
11134
11123
|
setMode('select')
|
|
11135
11124
|
|
|
11136
11125
|
const nextSelection: Parameters<typeof setSelection>[0] = {
|
|
@@ -11147,16 +11136,7 @@ export function FloorplanPanel({
|
|
|
11147
11136
|
}
|
|
11148
11137
|
|
|
11149
11138
|
setSelection(nextSelection)
|
|
11150
|
-
}, [
|
|
11151
|
-
currentBuildingId,
|
|
11152
|
-
floorplanLevels,
|
|
11153
|
-
levelNode,
|
|
11154
|
-
setMode,
|
|
11155
|
-
setPhase,
|
|
11156
|
-
setSelection,
|
|
11157
|
-
setStructureLayer,
|
|
11158
|
-
site,
|
|
11159
|
-
])
|
|
11139
|
+
}, [currentBuildingId, floorplanLevels, levelNode, setMode, setPhase, setSelection, site])
|
|
11160
11140
|
const activeDraftAnchorPoint =
|
|
11161
11141
|
referenceScaleDraft?.start ??
|
|
11162
11142
|
draftStart ??
|
|
@@ -11236,8 +11216,10 @@ export function FloorplanPanel({
|
|
|
11236
11216
|
pill, plus the group pill for multi-selections. */}
|
|
11237
11217
|
<FloorplanRegistryActionMenu />
|
|
11238
11218
|
<FloorplanGroupActionMenu />
|
|
11219
|
+
<FloorplanDrawingTypeSwitch className={planOnRight ? 'right-24 left-auto' : undefined} />
|
|
11239
11220
|
|
|
11240
11221
|
{!isStudioWorkspace &&
|
|
11222
|
+
sceneLeft !== null &&
|
|
11241
11223
|
(levelNode?.type === 'level' || hasAmbientBuildingLevel) &&
|
|
11242
11224
|
(compassHost ? (
|
|
11243
11225
|
createPortal(
|
|
@@ -11245,6 +11227,7 @@ export function FloorplanPanel({
|
|
|
11245
11227
|
needleRef={compassNeedleRef}
|
|
11246
11228
|
northRotationDeg={floorplanUserRotationDeg}
|
|
11247
11229
|
onAlignNorth={alignFloorplanViewToNorth}
|
|
11230
|
+
style={compassStyle}
|
|
11248
11231
|
/>,
|
|
11249
11232
|
compassHost,
|
|
11250
11233
|
)
|
|
@@ -11385,7 +11368,7 @@ export function FloorplanPanel({
|
|
|
11385
11368
|
// renderer, handle layers) must NOT render/reconcile while hidden —
|
|
11386
11369
|
// otherwise every scene/selection change in pure 3D re-rendered the
|
|
11387
11370
|
// whole floorplan tree (profiler: 150–200ms on a wall-endpoint drag).
|
|
11388
|
-
// `isFloorplanOpen` is
|
|
11371
|
+
// `isFloorplanOpen` is "the plan is in a pane", so this still renders fully
|
|
11389
11372
|
// in both 2D and split. Viewport state lives on the still-mounted
|
|
11390
11373
|
// panel, so pan/zoom is preserved across the toggle.
|
|
11391
11374
|
<svg
|
|
@@ -11395,11 +11378,11 @@ export function FloorplanPanel({
|
|
|
11395
11378
|
onContextMenu={(event) => event.preventDefault()}
|
|
11396
11379
|
onDoubleClick={isMarqueeSelectionToolActive ? undefined : handleBackgroundDoubleClick}
|
|
11397
11380
|
onPointerCancel={endFloorplanNavigation}
|
|
11398
|
-
onPointerDown={
|
|
11381
|
+
onPointerDown={handleSvgPointerDown}
|
|
11399
11382
|
onPointerDownCapture={handleNavigationPointerDown}
|
|
11400
11383
|
onPointerLeave={handleSvgPointerLeave}
|
|
11401
11384
|
onPointerMove={handleSvgPointerMove}
|
|
11402
|
-
onPointerUp={
|
|
11385
|
+
onPointerUp={handleSvgPointerUp}
|
|
11403
11386
|
ref={svgRef}
|
|
11404
11387
|
style={{
|
|
11405
11388
|
cursor:
|
|
@@ -11496,7 +11479,7 @@ export function FloorplanPanel({
|
|
|
11496
11479
|
subscribes to the `useStairBuildPreview` store directly, so a
|
|
11497
11480
|
per-`grid:move` cursor update re-renders only that tiny layer
|
|
11498
11481
|
rather than this whole panel. */}
|
|
11499
|
-
<FloorplanStairBuildPreviewLayer
|
|
11482
|
+
<FloorplanStairBuildPreviewLayer palette={floorplanRegistryPalette} />
|
|
11500
11483
|
|
|
11501
11484
|
<FloorplanReferenceScaleLayer
|
|
11502
11485
|
draft={referenceScaleDraft}
|
|
@@ -11547,7 +11530,13 @@ export function FloorplanPanel({
|
|
|
11547
11530
|
whose extent is derived from the current viewBox and
|
|
11548
11531
|
would create a measure→fit→measure loop. */}
|
|
11549
11532
|
<g ref={floorplanContentRef}>
|
|
11550
|
-
|
|
11533
|
+
{/* Site-plan mode swaps the level drawing for the lot /
|
|
11534
|
+
setback / footprint drawing, in SITE coordinates. */}
|
|
11535
|
+
{activeDrawingType === 'site-plan' ? (
|
|
11536
|
+
<FloorplanSitePlanLayer />
|
|
11537
|
+
) : (
|
|
11538
|
+
<FloorplanRegistryLayer />
|
|
11539
|
+
)}
|
|
11551
11540
|
{/* Faint footprint ghost of the node being placed by a
|
|
11552
11541
|
registry placement tool (e.g. column), following the
|
|
11553
11542
|
cursor. The 3D mesh preview is hidden in 2D, so this is
|
|
@@ -11561,6 +11550,7 @@ export function FloorplanPanel({
|
|
|
11561
11550
|
`floorplan-wall-move-ghost-layer.tsx`. */}
|
|
11562
11551
|
<FloorplanWallMoveGhostLayer />
|
|
11563
11552
|
</g>
|
|
11553
|
+
{activeDrawingType !== 'site-plan' && <FloorplanOpenWallEndsLayer />}
|
|
11564
11554
|
<FloorplanMeasurementToolLayer />
|
|
11565
11555
|
<FloorplanRegisteredToolLayer />
|
|
11566
11556
|
{floorplanSceneSlot}
|
|
@@ -11577,42 +11567,54 @@ export function FloorplanPanel({
|
|
|
11577
11567
|
paint on top of node geometry. */}
|
|
11578
11568
|
<FloorplanAlignmentGuideLayer />
|
|
11579
11569
|
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11570
|
+
{/* The property line, its vertex handles and its edge labels are
|
|
11571
|
+
drawn in BUILDING-LOCAL metres (the frame the level's walls
|
|
11572
|
+
live in). The site-plan drawing type draws the lot itself, in
|
|
11573
|
+
SITE metres, so mounting these there paints a second lot
|
|
11574
|
+
offset by the building's own position — the "two dashed
|
|
11575
|
+
boxes" — and it shifts whenever the house moves. */}
|
|
11576
|
+
{activeDrawingType !== 'site-plan' && (
|
|
11577
|
+
<FloorplanSiteLayer
|
|
11578
|
+
dimmed={selectedIds.length > 1 || previewSelectedIds.length > 1}
|
|
11579
|
+
isHighlighted={isSiteBoundaryHighlighted}
|
|
11580
|
+
palette={palette}
|
|
11581
|
+
sitePolygon={visibleSitePolygon}
|
|
11582
|
+
/>
|
|
11583
|
+
)}
|
|
11586
11584
|
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11585
|
+
{activeDrawingType !== 'site-plan' && (
|
|
11586
|
+
<FloorplanPolygonHandleLayer
|
|
11587
|
+
edgeHandles={siteEdgeHandles}
|
|
11588
|
+
hoveredHandleId={hoveredSiteHandleId}
|
|
11589
|
+
midpointHandles={siteMidpointHandles}
|
|
11590
|
+
onHandleHoverChange={setHoveredSiteHandleId}
|
|
11591
|
+
onMidpointPointerDown={(nodeId, edgeIndex, event) =>
|
|
11592
|
+
handleSiteMidpointPointerDown(nodeId as SiteNode['id'], edgeIndex, event)
|
|
11593
|
+
}
|
|
11594
|
+
onVertexDoubleClick={(nodeId, vertexIndex, event) =>
|
|
11595
|
+
handleSiteVertexDoubleClick(nodeId as SiteNode['id'], vertexIndex, event)
|
|
11596
|
+
}
|
|
11597
|
+
onVertexPointerDown={(nodeId, vertexIndex, event) =>
|
|
11598
|
+
handleSiteVertexPointerDown(nodeId as SiteNode['id'], vertexIndex, event)
|
|
11599
|
+
}
|
|
11600
|
+
palette={palette}
|
|
11601
|
+
unitsPerPixel={floorplanUnitsPerPixel}
|
|
11602
|
+
vertexHandles={siteVertexHandles}
|
|
11603
|
+
/>
|
|
11604
|
+
)}
|
|
11605
11605
|
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11606
|
+
{activeDrawingType !== 'site-plan' && (
|
|
11607
|
+
<FloorplanSiteEdgeLabelLayer
|
|
11608
|
+
labelBackground={isDark ? '#0f172a' : '#ffffff'}
|
|
11609
|
+
labelText={isDark ? '#e2e8f0' : '#171717'}
|
|
11610
|
+
palette={palette}
|
|
11611
|
+
sceneRotationDeg={floorplanSceneRotationDeg}
|
|
11612
|
+
shouldShow={shouldShowSiteEdgeLabels}
|
|
11613
|
+
sitePolygon={visibleSitePolygon}
|
|
11614
|
+
unit={unit}
|
|
11615
|
+
unitsPerPixel={floorplanUnitsPerPixel}
|
|
11616
|
+
/>
|
|
11617
|
+
)}
|
|
11616
11618
|
|
|
11617
11619
|
{/* "Magnetic" wall-snap beacon — per-kind glyph at the active
|
|
11618
11620
|
draft / endpoint-move snap point. Same store + coord space as
|