@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
|
@@ -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,
|
|
@@ -55,14 +56,14 @@ import {
|
|
|
55
56
|
type WindowNode,
|
|
56
57
|
WindowNode as WindowNodeSchema,
|
|
57
58
|
wallClosesRoom,
|
|
58
|
-
|
|
59
|
-
type ZoneNode as ZoneNodeType,
|
|
59
|
+
wallRectangleCorners,
|
|
60
60
|
} from '@pascal-app/core'
|
|
61
61
|
import { useSegmentDraftChain, useWallSnapIndicator } from '@pascal-app/editor'
|
|
62
62
|
import { getSceneTheme, useViewer } from '@pascal-app/viewer'
|
|
63
63
|
import { Command, Ruler } from 'lucide-react'
|
|
64
64
|
import {
|
|
65
65
|
type ComponentProps,
|
|
66
|
+
type CSSProperties,
|
|
66
67
|
memo,
|
|
67
68
|
type MouseEvent as ReactMouseEvent,
|
|
68
69
|
type ReactNode,
|
|
@@ -77,11 +78,15 @@ import {
|
|
|
77
78
|
import { createPortal } from 'react-dom'
|
|
78
79
|
import { Vector3 } from 'three'
|
|
79
80
|
import { useShallow } from 'zustand/react/shallow'
|
|
81
|
+
import {
|
|
82
|
+
hoverRoomFromHit,
|
|
83
|
+
resolvePlanRoomHit,
|
|
84
|
+
roomPickingEnabled,
|
|
85
|
+
} from '../../hooks/use-selected-room'
|
|
80
86
|
import { resolveCeilingPlanPointSnap } from '../../lib/ceiling-plan-snap'
|
|
81
87
|
import {
|
|
82
88
|
alignFloorplanDraftPoint,
|
|
83
89
|
buildFloorplanItemEntry,
|
|
84
|
-
buildFloorplanStairEntry as buildSharedFloorplanStairEntry,
|
|
85
90
|
collectLevelDescendants,
|
|
86
91
|
FLOORPLAN_VIEW_ROTATION_DEG,
|
|
87
92
|
floorplanLocalToWorldPoint,
|
|
@@ -90,20 +95,58 @@ import {
|
|
|
90
95
|
type FloorplanNodeTransform as SharedFloorplanNodeTransform,
|
|
91
96
|
worldToFloorplanLocalPoint,
|
|
92
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'
|
|
93
106
|
import { resolveGenericFloorplanGridEventPoint } from '../../lib/floorplan-grid-event-point'
|
|
94
107
|
import type { EditorGridEvent } from '../../lib/grid-event-presentation'
|
|
95
108
|
import { groundHeightAt } from '../../lib/ground-surface'
|
|
96
109
|
import { guideEmitter } from '../../lib/guide-events'
|
|
110
|
+
import {
|
|
111
|
+
acceptsKeyboardPan,
|
|
112
|
+
advanceKeyboardPanMotion,
|
|
113
|
+
clearKeyboardPanKeys,
|
|
114
|
+
createKeyboardPanMotion,
|
|
115
|
+
isKeyboardPanKey,
|
|
116
|
+
type KeyboardPanState,
|
|
117
|
+
keyboardPanSpeed,
|
|
118
|
+
resetKeyboardPanMotion,
|
|
119
|
+
setKeyboardPanKey,
|
|
120
|
+
syncKeyboardPanMotion,
|
|
121
|
+
} from '../../lib/keyboard-pan'
|
|
97
122
|
import { measurementHint, parseMeasurement } from '../../lib/measurement-parser'
|
|
98
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'
|
|
99
127
|
import { sfxEmitter } from '../../lib/sfx-bus'
|
|
100
128
|
import { SITE_BOUNDARY_DRAG_LABEL, siteBoundaryHandlesEnabled } from '../../lib/site-boundary'
|
|
101
129
|
import { resolveSlabPlanPointSnap } from '../../lib/slab-plan-snap'
|
|
102
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
cancelPendingZonePaint,
|
|
132
|
+
focusedUnitNode,
|
|
133
|
+
paintZoneMembership,
|
|
134
|
+
zoneAtLevelPoint,
|
|
135
|
+
} from '../../lib/units'
|
|
103
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'
|
|
104
145
|
import { snapBuildingLocalToWorldGrid } from '../../lib/world-grid-snap'
|
|
146
|
+
import { nextZoneName } from '../../lib/zone-name'
|
|
105
147
|
import { subscribeNavigationSyncPose } from '../../store/navigation-sync-pose-store'
|
|
106
148
|
import useAlignmentGuides from '../../store/use-alignment-guides'
|
|
149
|
+
import useDrawingView from '../../store/use-drawing-view'
|
|
107
150
|
import type { GuideUiState, NavigationSyncPose } from '../../store/use-editor'
|
|
108
151
|
import useEditor, {
|
|
109
152
|
isAngleSnapActive,
|
|
@@ -122,11 +165,13 @@ import useInteractionScope, {
|
|
|
122
165
|
import usePlacementPreview from '../../store/use-placement-preview'
|
|
123
166
|
import { expandSessionSelectionForNode } from '../../store/use-session-groups'
|
|
124
167
|
import { useStairBuildPreview } from '../../store/use-stair-build-preview'
|
|
168
|
+
import { activeViewLayout, isViewVisible, VIEW_2D, visibleScene } from '../../store/view-layout'
|
|
125
169
|
import { FloorplanAlignmentGuideLayer } from '../editor-2d/floorplan-alignment-guide-layer'
|
|
126
170
|
import { FloorplanCursorIndicatorOverlay as Editor2dFloorplanCursorIndicatorOverlay } from '../editor-2d/floorplan-cursor-indicator-overlay'
|
|
127
171
|
import { FloorplanGroupActionMenu } from '../editor-2d/floorplan-group-action-menu'
|
|
128
172
|
import { FloorplanSiteKeyHandler } from '../editor-2d/floorplan-hotkey-handlers'
|
|
129
173
|
import { FloorplanMeasurementToolLayer } from '../editor-2d/floorplan-measurement-tool-layer'
|
|
174
|
+
import { FloorplanOpenWallEndsLayer } from '../editor-2d/floorplan-open-wall-ends-layer'
|
|
130
175
|
import { FloorplanRegisteredToolLayer } from '../editor-2d/floorplan-registered-tool-layer'
|
|
131
176
|
import { FloorplanRegistryActionMenu } from '../editor-2d/floorplan-registry-action-menu'
|
|
132
177
|
import { FloorplanRegistryMoveOverlay } from '../editor-2d/floorplan-registry-move-overlay'
|
|
@@ -137,6 +182,7 @@ import {
|
|
|
137
182
|
import { FloorplanSnapBeaconLayer } from '../editor-2d/floorplan-snap-beacon-layer'
|
|
138
183
|
import { FloorplanWallMoveGhostLayer } from '../editor-2d/floorplan-wall-move-ghost-layer'
|
|
139
184
|
import { FloorplanDraftLayer } from '../editor-2d/renderers/floorplan-draft-layer'
|
|
185
|
+
import { FloorplanDraftMeasurement } from '../editor-2d/renderers/floorplan-draft-measurement'
|
|
140
186
|
import { FloorplanGeometryRenderer } from '../editor-2d/renderers/floorplan-geometry-renderer'
|
|
141
187
|
import { FloorplanMarqueeLayer } from '../editor-2d/renderers/floorplan-marquee-layer'
|
|
142
188
|
import { FloorplanPlacementPreviewLayer } from '../editor-2d/renderers/floorplan-placement-preview-layer'
|
|
@@ -144,10 +190,8 @@ import {
|
|
|
144
190
|
FloorplanRegistryLayer,
|
|
145
191
|
RotationAngleOverlay,
|
|
146
192
|
} from '../editor-2d/renderers/floorplan-registry-layer'
|
|
147
|
-
import { FloorplanStairLayer } from '../editor-2d/renderers/floorplan-stair-layer'
|
|
148
193
|
import { FloorplanVoronoiLayer } from '../editor-2d/renderers/floorplan-voronoi-layer'
|
|
149
|
-
import { buildSvgPolylinePath, formatPolygonPath
|
|
150
|
-
import { snapFenceDraftPoint } from '../tools/fence/fence-drafting'
|
|
194
|
+
import { buildSvgPolylinePath, formatPolygonPath } from '../editor-2d/svg-paths'
|
|
151
195
|
import { snapToHalf } from '../tools/item/placement-math'
|
|
152
196
|
import {
|
|
153
197
|
isBoxSelectPointerSuppressed,
|
|
@@ -177,15 +221,6 @@ import {
|
|
|
177
221
|
getAngleToSegmentReference,
|
|
178
222
|
getSegmentAngleReferenceAtPoint,
|
|
179
223
|
} from '../tools/shared/segment-angle'
|
|
180
|
-
import {
|
|
181
|
-
DEFAULT_STAIR_ATTACHMENT_SIDE,
|
|
182
|
-
DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
183
|
-
DEFAULT_STAIR_HEIGHT,
|
|
184
|
-
DEFAULT_STAIR_LENGTH,
|
|
185
|
-
DEFAULT_STAIR_STEP_COUNT,
|
|
186
|
-
DEFAULT_STAIR_THICKNESS,
|
|
187
|
-
DEFAULT_STAIR_WIDTH,
|
|
188
|
-
} from '../tools/stair/stair-defaults'
|
|
189
224
|
import {
|
|
190
225
|
chainEndJoinsExistingWall,
|
|
191
226
|
createWallOnCurrentLevel,
|
|
@@ -197,8 +232,6 @@ import {
|
|
|
197
232
|
WALL_JOIN_SNAP_RADIUS,
|
|
198
233
|
type WallPlanPoint,
|
|
199
234
|
} from '../tools/wall/wall-drafting'
|
|
200
|
-
|
|
201
|
-
import { PALETTE_COLORS } from '../ui/primitives/color-dot'
|
|
202
235
|
import { FloorplanCompassButton } from '../viewer/floorplan-compass-button'
|
|
203
236
|
import { resolveFloorplanBackgroundSelection } from './floorplan-background-selection'
|
|
204
237
|
import {
|
|
@@ -221,6 +254,7 @@ import {
|
|
|
221
254
|
import { useFloorplanBackgroundPlacement } from './use-floorplan-background-placement'
|
|
222
255
|
import { useFloorplanHitTesting } from './use-floorplan-hit-testing'
|
|
223
256
|
import { useFloorplanSceneData } from './use-floorplan-scene-data'
|
|
257
|
+
import { sceneSpan } from './views/scene-region'
|
|
224
258
|
|
|
225
259
|
const FALLBACK_VIEW_SIZE = 12
|
|
226
260
|
const FLOORPLAN_PADDING = 2
|
|
@@ -271,7 +305,6 @@ const FLOORPLAN_HOVER_TRANSITION = 'opacity 180ms cubic-bezier(0.2, 0, 0, 1)'
|
|
|
271
305
|
const FLOORPLAN_WALL_HIT_STROKE_WIDTH = 18
|
|
272
306
|
const FLOORPLAN_WALL_STROKE_WIDTH = '1'
|
|
273
307
|
const FLOORPLAN_OPENING_HIT_STROKE_WIDTH = 16
|
|
274
|
-
const noopFloorplanStairHandler = () => {}
|
|
275
308
|
const FLOORPLAN_OPENING_STROKE_WIDTH = 0.05
|
|
276
309
|
const FLOORPLAN_ENDPOINT_HIT_STROKE_WIDTH = 18
|
|
277
310
|
const FLOORPLAN_ENDPOINT_HOVER_GLOW_STROKE_WIDTH = 16
|
|
@@ -2113,68 +2146,6 @@ function movePlanPointTowards(start: Point2D, end: Point2D, distance: number): P
|
|
|
2113
2146
|
return interpolatePlanPoint(start, end, Math.min(1, distance / totalDistance))
|
|
2114
2147
|
}
|
|
2115
2148
|
|
|
2116
|
-
function getNormalizedFloorplanStairSweepAngle(stair: StairNode) {
|
|
2117
|
-
const stairType = stair.stairType ?? 'straight'
|
|
2118
|
-
const baseSweepAngle = stair.sweepAngle ?? (stairType === 'spiral' ? Math.PI * 2 : Math.PI / 2)
|
|
2119
|
-
|
|
2120
|
-
if (Math.abs(baseSweepAngle) >= Math.PI * 2) {
|
|
2121
|
-
return Math.sign(baseSweepAngle || 1) * (Math.PI * 2 - 0.001)
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
return baseSweepAngle
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
|
-
function getFloorplanSpiralLandingSweep(stair: StairNode, sweepAngle: number) {
|
|
2128
|
-
if (
|
|
2129
|
-
(stair.stairType ?? 'straight') !== 'spiral' ||
|
|
2130
|
-
(stair.topLandingMode ?? 'none') !== 'integrated'
|
|
2131
|
-
) {
|
|
2132
|
-
return 0
|
|
2133
|
-
}
|
|
2134
|
-
|
|
2135
|
-
const innerRadius = Math.max(0.05, stair.innerRadius ?? 0.9)
|
|
2136
|
-
const width = Math.max(stair.width ?? 1, 0.4)
|
|
2137
|
-
const landingDepth = Math.max(0.3, stair.topLandingDepth ?? Math.max(width * 0.9, 0.8))
|
|
2138
|
-
|
|
2139
|
-
return (
|
|
2140
|
-
Math.min(Math.PI * 0.75, landingDepth / Math.max(innerRadius + width / 2, 0.1)) *
|
|
2141
|
-
Math.sign(sweepAngle || 1)
|
|
2142
|
-
)
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
|
-
function getFloorplanCurvedStairHitPolygon(stair: StairNode): Point2D[] {
|
|
2146
|
-
const stairType = stair.stairType ?? 'straight'
|
|
2147
|
-
const sweepAngle = getNormalizedFloorplanStairSweepAngle(stair)
|
|
2148
|
-
const startAngle = -stair.rotation - sweepAngle / 2
|
|
2149
|
-
const endAngle = startAngle + sweepAngle + getFloorplanSpiralLandingSweep(stair, sweepAngle)
|
|
2150
|
-
const center = {
|
|
2151
|
-
x: stair.position[0],
|
|
2152
|
-
y: stair.position[2],
|
|
2153
|
-
}
|
|
2154
|
-
const innerRadius = Math.max(
|
|
2155
|
-
stairType === 'spiral' ? 0.05 : 0.2,
|
|
2156
|
-
stair.innerRadius ?? (stairType === 'spiral' ? 0.2 : 0.9),
|
|
2157
|
-
)
|
|
2158
|
-
const outerRadius = innerRadius + stair.width
|
|
2159
|
-
const outerArcLength = Math.abs(sweepAngle) * outerRadius
|
|
2160
|
-
const segmentCount = Math.max(
|
|
2161
|
-
24,
|
|
2162
|
-
Math.ceil(Math.abs(sweepAngle) / (Math.PI / 24)),
|
|
2163
|
-
Math.ceil(outerArcLength / 0.14),
|
|
2164
|
-
)
|
|
2165
|
-
const outerPoints: Point2D[] = []
|
|
2166
|
-
const innerPoints: Point2D[] = []
|
|
2167
|
-
|
|
2168
|
-
for (let index = 0; index <= segmentCount; index += 1) {
|
|
2169
|
-
const t = index / segmentCount
|
|
2170
|
-
const angle = startAngle + (endAngle - startAngle) * t
|
|
2171
|
-
outerPoints.push(getArcPlanPoint(center, outerRadius, angle))
|
|
2172
|
-
innerPoints.push(getArcPlanPoint(center, innerRadius, angle))
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
return [...outerPoints, ...innerPoints.reverse()]
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
2149
|
function isPointInsidePolygonWithHoles(
|
|
2179
2150
|
point: Point2D,
|
|
2180
2151
|
polygon: Point2D[],
|
|
@@ -2320,168 +2291,6 @@ function buildDraftWall(levelId: string, start: WallPlanPoint, end: WallPlanPoin
|
|
|
2320
2291
|
}
|
|
2321
2292
|
}
|
|
2322
2293
|
|
|
2323
|
-
type DraftWallMeasurement = {
|
|
2324
|
-
lengthLabel: string
|
|
2325
|
-
midpoint: WallPlanPoint
|
|
2326
|
-
direction: WallPlanPoint
|
|
2327
|
-
angleLabels: {
|
|
2328
|
-
id: string
|
|
2329
|
-
label: string
|
|
2330
|
-
center: WallPlanPoint
|
|
2331
|
-
radius: number
|
|
2332
|
-
startAngle: number
|
|
2333
|
-
endAngle: number
|
|
2334
|
-
midAngle: number
|
|
2335
|
-
}[]
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
function FloorplanDraftWallMeasurement({
|
|
2339
|
-
measurement,
|
|
2340
|
-
measurementStroke,
|
|
2341
|
-
labelBackground,
|
|
2342
|
-
labelText,
|
|
2343
|
-
sceneRotationDeg,
|
|
2344
|
-
unitsPerPixel,
|
|
2345
|
-
}: {
|
|
2346
|
-
measurement: DraftWallMeasurement
|
|
2347
|
-
measurementStroke: string
|
|
2348
|
-
labelBackground: string
|
|
2349
|
-
labelText: string
|
|
2350
|
-
sceneRotationDeg: number
|
|
2351
|
-
unitsPerPixel: number
|
|
2352
|
-
}) {
|
|
2353
|
-
const stroke = measurementStroke
|
|
2354
|
-
const labelBg = labelBackground
|
|
2355
|
-
|
|
2356
|
-
const upx = unitsPerPixel
|
|
2357
|
-
const fontSize = Math.max(upx * 10, 0.08)
|
|
2358
|
-
const padX = upx * 6
|
|
2359
|
-
const padY = upx * 3
|
|
2360
|
-
|
|
2361
|
-
// Length plate: rotates to follow the wall direction, but flips 180°
|
|
2362
|
-
// when its on-screen orientation would read upside-down (same trick as
|
|
2363
|
-
// `floorplan-registry-layer.tsx` for dimension labels).
|
|
2364
|
-
const wallAngleDeg =
|
|
2365
|
-
(Math.atan2(measurement.direction[1], measurement.direction[0]) * 180) / Math.PI
|
|
2366
|
-
let labelAngleDeg = wallAngleDeg
|
|
2367
|
-
let screenDeg = wallAngleDeg + sceneRotationDeg
|
|
2368
|
-
screenDeg = ((((screenDeg + 180) % 360) + 360) % 360) - 180
|
|
2369
|
-
if (screenDeg > 90) labelAngleDeg -= 180
|
|
2370
|
-
else if (screenDeg <= -90) labelAngleDeg += 180
|
|
2371
|
-
|
|
2372
|
-
// Push the plate perpendicular to the wall so the dashed footprint
|
|
2373
|
-
// stays visible underneath.
|
|
2374
|
-
const perpX = -measurement.direction[1]
|
|
2375
|
-
const perpY = measurement.direction[0]
|
|
2376
|
-
const offset = upx * 18
|
|
2377
|
-
const cx = measurement.midpoint[0] + perpX * offset
|
|
2378
|
-
const cy = measurement.midpoint[1] + perpY * offset
|
|
2379
|
-
|
|
2380
|
-
const lengthTextWidth = measurement.lengthLabel.length * upx * 6.2
|
|
2381
|
-
const lengthPlateW = lengthTextWidth + padX * 2
|
|
2382
|
-
const lengthPlateH = fontSize + padY * 2
|
|
2383
|
-
|
|
2384
|
-
const arcSampleCount = 32
|
|
2385
|
-
|
|
2386
|
-
return (
|
|
2387
|
-
<g pointerEvents="none">
|
|
2388
|
-
<g transform={`translate(${cx} ${cy}) rotate(${labelAngleDeg})`}>
|
|
2389
|
-
<rect
|
|
2390
|
-
fill={labelBg}
|
|
2391
|
-
height={lengthPlateH}
|
|
2392
|
-
opacity={0.92}
|
|
2393
|
-
rx={upx * 3}
|
|
2394
|
-
ry={upx * 3}
|
|
2395
|
-
stroke={stroke}
|
|
2396
|
-
strokeWidth={upx * 0.5}
|
|
2397
|
-
vectorEffect="non-scaling-stroke"
|
|
2398
|
-
width={lengthPlateW}
|
|
2399
|
-
x={-lengthPlateW / 2}
|
|
2400
|
-
y={-lengthPlateH / 2}
|
|
2401
|
-
/>
|
|
2402
|
-
<text
|
|
2403
|
-
dominantBaseline="middle"
|
|
2404
|
-
fill={labelText}
|
|
2405
|
-
fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
2406
|
-
fontSize={fontSize}
|
|
2407
|
-
fontWeight={600}
|
|
2408
|
-
textAnchor="middle"
|
|
2409
|
-
x={0}
|
|
2410
|
-
y={0}
|
|
2411
|
-
>
|
|
2412
|
-
{measurement.lengthLabel}
|
|
2413
|
-
</text>
|
|
2414
|
-
</g>
|
|
2415
|
-
|
|
2416
|
-
{measurement.angleLabels.map((arc) => {
|
|
2417
|
-
// Sample the arc as a polyline — avoids the SVG arc command's
|
|
2418
|
-
// sweep-flag direction quirks across negative/positive sweeps.
|
|
2419
|
-
const points: string[] = []
|
|
2420
|
-
for (let i = 0; i <= arcSampleCount; i += 1) {
|
|
2421
|
-
const t = i / arcSampleCount
|
|
2422
|
-
const a = arc.startAngle + (arc.endAngle - arc.startAngle) * t
|
|
2423
|
-
const px = arc.center[0] + Math.cos(a) * arc.radius
|
|
2424
|
-
const py = arc.center[1] + Math.sin(a) * arc.radius
|
|
2425
|
-
points.push(`${px},${py}`)
|
|
2426
|
-
}
|
|
2427
|
-
|
|
2428
|
-
const aFontSize = Math.max(upx * 9, 0.075)
|
|
2429
|
-
const aPadX = upx * 5
|
|
2430
|
-
const aPadY = upx * 2.5
|
|
2431
|
-
const aTextWidth = arc.label.length * upx * 6.2
|
|
2432
|
-
const aPlateW = aTextWidth + aPadX * 2
|
|
2433
|
-
const aPlateH = aFontSize + aPadY * 2
|
|
2434
|
-
|
|
2435
|
-
const labelDist = arc.radius + upx * 16
|
|
2436
|
-
const lx = arc.center[0] + Math.cos(arc.midAngle) * labelDist
|
|
2437
|
-
const ly = arc.center[1] + Math.sin(arc.midAngle) * labelDist
|
|
2438
|
-
|
|
2439
|
-
return (
|
|
2440
|
-
<g key={`draft-angle-${arc.id}`}>
|
|
2441
|
-
<polyline
|
|
2442
|
-
fill="none"
|
|
2443
|
-
points={points.join(' ')}
|
|
2444
|
-
stroke={stroke}
|
|
2445
|
-
strokeLinecap="round"
|
|
2446
|
-
strokeLinejoin="round"
|
|
2447
|
-
strokeOpacity={0.95}
|
|
2448
|
-
strokeWidth={upx * 1.2}
|
|
2449
|
-
vectorEffect="non-scaling-stroke"
|
|
2450
|
-
/>
|
|
2451
|
-
<g transform={`translate(${lx} ${ly})`}>
|
|
2452
|
-
<rect
|
|
2453
|
-
fill={labelBg}
|
|
2454
|
-
height={aPlateH}
|
|
2455
|
-
opacity={0.92}
|
|
2456
|
-
rx={upx * 3}
|
|
2457
|
-
ry={upx * 3}
|
|
2458
|
-
stroke={stroke}
|
|
2459
|
-
strokeWidth={upx * 0.5}
|
|
2460
|
-
vectorEffect="non-scaling-stroke"
|
|
2461
|
-
width={aPlateW}
|
|
2462
|
-
x={-aPlateW / 2}
|
|
2463
|
-
y={-aPlateH / 2}
|
|
2464
|
-
/>
|
|
2465
|
-
<text
|
|
2466
|
-
dominantBaseline="middle"
|
|
2467
|
-
fill={labelText}
|
|
2468
|
-
fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
2469
|
-
fontSize={aFontSize}
|
|
2470
|
-
fontWeight={600}
|
|
2471
|
-
textAnchor="middle"
|
|
2472
|
-
x={0}
|
|
2473
|
-
y={0}
|
|
2474
|
-
>
|
|
2475
|
-
{arc.label}
|
|
2476
|
-
</text>
|
|
2477
|
-
</g>
|
|
2478
|
-
</g>
|
|
2479
|
-
)
|
|
2480
|
-
})}
|
|
2481
|
-
</g>
|
|
2482
|
-
)
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
2294
|
function pointsEqual(a: WallPlanPoint, b: WallPlanPoint): boolean {
|
|
2486
2295
|
return a[0] === b[0] && a[1] === b[1]
|
|
2487
2296
|
}
|
|
@@ -4377,107 +4186,53 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({
|
|
|
4377
4186
|
)
|
|
4378
4187
|
})
|
|
4379
4188
|
|
|
4380
|
-
// Static segment for the in-flight stair build preview. No per-render
|
|
4381
|
-
// dependency (the geometry only moves / rotates), so it lives at module scope
|
|
4382
|
-
// instead of a `useMemo`.
|
|
4383
|
-
const FLOORPLAN_PREVIEW_STAIR_SEGMENT = StairSegmentNodeSchema.parse({
|
|
4384
|
-
id: 'sseg_floorplan_preview',
|
|
4385
|
-
segmentType: 'stair',
|
|
4386
|
-
width: DEFAULT_STAIR_WIDTH,
|
|
4387
|
-
length: DEFAULT_STAIR_LENGTH,
|
|
4388
|
-
height: DEFAULT_STAIR_HEIGHT,
|
|
4389
|
-
stepCount: DEFAULT_STAIR_STEP_COUNT,
|
|
4390
|
-
attachmentSide: DEFAULT_STAIR_ATTACHMENT_SIDE,
|
|
4391
|
-
fillToFloor: DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
4392
|
-
thickness: DEFAULT_STAIR_THICKNESS,
|
|
4393
|
-
position: [0, 0, 0],
|
|
4394
|
-
metadata: { isTransient: true, isFloorplanPreview: true },
|
|
4395
|
-
})
|
|
4396
|
-
|
|
4397
|
-
const EMPTY_FLOORPLAN_ID_SET: ReadonlySet<string> = new Set()
|
|
4398
|
-
|
|
4399
|
-
type FloorplanStairLayerPalette = ComponentProps<typeof FloorplanStairLayer>['palette']
|
|
4400
|
-
|
|
4401
|
-
// Leaf layer for the stair tool's in-flight 2D build preview. Subscribes to the
|
|
4402
|
-
// `useStairBuildPreview` store directly so a per-`grid:move` point update (or an
|
|
4403
|
-
// R/T rotation) re-renders ONLY this tiny layer — never the ~120-220ms
|
|
4404
|
-
// `FloorplanPanel`. This mirrors how column / elevator placement stays smooth by
|
|
4405
|
-
// routing preview state through a store + leaf. Committed stairs render through
|
|
4406
|
-
// `FloorplanRegistryLayer`; this layer is non-interactive (noop handlers, empty
|
|
4407
|
-
// hit sets), so it never participates in hover / select.
|
|
4408
4189
|
function FloorplanStairBuildPreviewLayer({
|
|
4409
4190
|
palette,
|
|
4410
|
-
isDeleteMode,
|
|
4411
4191
|
}: {
|
|
4412
|
-
palette:
|
|
4413
|
-
isDeleteMode: boolean
|
|
4192
|
+
palette: FloorplanRenderContextValue['palette']
|
|
4414
4193
|
}) {
|
|
4415
|
-
const phase = useEditor((s) => s.phase)
|
|
4416
4194
|
const mode = useEditor((s) => s.mode)
|
|
4417
4195
|
const tool = useEditor((s) => s.tool)
|
|
4418
4196
|
const point = useStairBuildPreview((s) => s.point)
|
|
4419
4197
|
const rotation = useStairBuildPreview((s) => s.rotation)
|
|
4420
|
-
const
|
|
4198
|
+
const rise = useStairBuildPreview((s) => s.rise)
|
|
4199
|
+
const isActive = mode === 'build' && tool === 'stair'
|
|
4421
4200
|
|
|
4422
|
-
const
|
|
4423
|
-
if (!(isActive && point)) {
|
|
4201
|
+
const previewGeometry = useMemo(() => {
|
|
4202
|
+
if (!(isActive && point && rise && rise > 0)) {
|
|
4424
4203
|
return null
|
|
4425
4204
|
}
|
|
4205
|
+
const previewSegment = {
|
|
4206
|
+
...createSizedStairFlight(rise),
|
|
4207
|
+
id: 'sseg_floorplan_preview' as const,
|
|
4208
|
+
parentId: 'stair_floorplan_preview' as const,
|
|
4209
|
+
}
|
|
4426
4210
|
const previewStair = StairNodeSchema.parse({
|
|
4427
4211
|
id: 'stair_floorplan_preview',
|
|
4428
4212
|
name: 'Staircase preview',
|
|
4429
4213
|
position: [point[0], 0, point[1]],
|
|
4430
4214
|
rotation,
|
|
4431
|
-
children: [
|
|
4215
|
+
children: [previewSegment.id],
|
|
4432
4216
|
metadata: { isTransient: true, isFloorplanPreview: true },
|
|
4433
4217
|
})
|
|
4434
|
-
const
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
points: formatPolygonPoints(segmentEntry.polygon),
|
|
4450
|
-
treadBars: segmentEntry.treadBars.map((polygon) => ({
|
|
4451
|
-
points: formatPolygonPoints(polygon),
|
|
4452
|
-
polygon,
|
|
4453
|
-
})),
|
|
4454
|
-
})),
|
|
4455
|
-
}
|
|
4456
|
-
}, [isActive, point, rotation])
|
|
4457
|
-
|
|
4458
|
-
if (!previewEntry) {
|
|
4459
|
-
return null
|
|
4460
|
-
}
|
|
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])
|
|
4461
4233
|
|
|
4462
|
-
return
|
|
4463
|
-
|
|
4464
|
-
canFocusStairs={false}
|
|
4465
|
-
canSelectStairs={false}
|
|
4466
|
-
cursor={EDITOR_CURSOR}
|
|
4467
|
-
highlightedIdSet={EMPTY_FLOORPLAN_ID_SET}
|
|
4468
|
-
hitStrokeWidth={FLOORPLAN_OPENING_HIT_STROKE_WIDTH}
|
|
4469
|
-
hoveredStairId={null}
|
|
4470
|
-
isDeleteMode={isDeleteMode}
|
|
4471
|
-
onStairDoubleClick={noopFloorplanStairHandler}
|
|
4472
|
-
onStairHoverChange={noopFloorplanStairHandler}
|
|
4473
|
-
onStairHoverEnter={noopFloorplanStairHandler}
|
|
4474
|
-
onStairPointerDown={noopFloorplanStairHandler}
|
|
4475
|
-
onStairSelect={noopFloorplanStairHandler}
|
|
4476
|
-
palette={palette}
|
|
4477
|
-
selectedIdSet={EMPTY_FLOORPLAN_ID_SET}
|
|
4478
|
-
stairEntries={[previewEntry]}
|
|
4479
|
-
/>
|
|
4480
|
-
)
|
|
4234
|
+
if (!previewGeometry) return null
|
|
4235
|
+
return <FloorplanGeometryRenderer geometry={previewGeometry} pointerEventsOverride="none" />
|
|
4481
4236
|
}
|
|
4482
4237
|
|
|
4483
4238
|
// Leaf overlay for the cursor-following draft preview: the cursor crosshair plus
|
|
@@ -4570,7 +4325,7 @@ function FloorplanDraftCursorLayer({
|
|
|
4570
4325
|
)}
|
|
4571
4326
|
|
|
4572
4327
|
{cursorPoint && (
|
|
4573
|
-
<g>
|
|
4328
|
+
<g pointerEvents="none">
|
|
4574
4329
|
<circle
|
|
4575
4330
|
cx={toSvgX(cursorPoint[0])}
|
|
4576
4331
|
cy={toSvgY(cursorPoint[1])}
|
|
@@ -4680,6 +4435,13 @@ function FloorplanLinearDraftLayer({
|
|
|
4680
4435
|
const fenceDraftEnd = useFloorplanDraftPreview((s) => s.fenceDraftEnd)
|
|
4681
4436
|
const roofDraftEnd = useFloorplanDraftPreview((s) => s.roofDraftEnd)
|
|
4682
4437
|
const roofDraftQuarterTurn = useFloorplanDraftPreview((s) => s.roofDraftQuarterTurn)
|
|
4438
|
+
const wallRectangleDraftStart = useFloorplanDraftPreview((s) => s.wallRectangleDraftStart)
|
|
4439
|
+
const wallPolygonDraftPoints = useFloorplanDraftPreview((s) => s.wallPolygonDraftPoints)
|
|
4440
|
+
// The live cursor is the rectangle's opposite corner; select it only while a
|
|
4441
|
+
// rectangle draft is open so idle moves don't re-render this layer.
|
|
4442
|
+
const wallRectangleDraftEnd = useFloorplanDraftPreview((s) =>
|
|
4443
|
+
s.wallRectangleDraftStart ? s.cursorPoint : null,
|
|
4444
|
+
)
|
|
4683
4445
|
|
|
4684
4446
|
const draftPolygon = useMemo(() => {
|
|
4685
4447
|
if (
|
|
@@ -4697,6 +4459,67 @@ function FloorplanLinearDraftLayer({
|
|
|
4697
4459
|
return getWallPlanFootprint(draftWall, EMPTY_WALL_MITER_DATA)
|
|
4698
4460
|
}, [levelId, wallDraftStart, wallDraftEnd])
|
|
4699
4461
|
|
|
4462
|
+
// Rectangle mode drafts four walls at once; they read exactly like a line
|
|
4463
|
+
// draft — same mitered footprints, measurement plates and axis guides.
|
|
4464
|
+
const rectangleDraft = useMemo(() => {
|
|
4465
|
+
if (!(levelId && isWallBuildActive && wallRectangleDraftStart && wallRectangleDraftEnd)) {
|
|
4466
|
+
return null
|
|
4467
|
+
}
|
|
4468
|
+
const corners = wallRectangleCorners(wallRectangleDraftStart, wallRectangleDraftEnd)
|
|
4469
|
+
if (corners.length !== 4) return null
|
|
4470
|
+
const draftWalls = corners.map((start, index) =>
|
|
4471
|
+
getSharedFloorplanWall({
|
|
4472
|
+
...buildDraftWall(levelId, start, corners[(index + 1) % 4]!),
|
|
4473
|
+
id: `wall_draft_${index}` as WallNode['id'],
|
|
4474
|
+
}),
|
|
4475
|
+
)
|
|
4476
|
+
const miterData = calculateLevelMiters(draftWalls)
|
|
4477
|
+
const polygons = draftWalls.map((wall) =>
|
|
4478
|
+
formatPolygonPoints(getWallPlanFootprint(wall, miterData)),
|
|
4479
|
+
)
|
|
4480
|
+
// Measure the two sides meeting at the cursor corner. Corners wind
|
|
4481
|
+
// counter-clockwise, so each side is measured end → start to push its
|
|
4482
|
+
// plate outside the rectangle.
|
|
4483
|
+
const cursorIndex = corners.findIndex(
|
|
4484
|
+
([x, z]) => x === wallRectangleDraftEnd[0] && z === wallRectangleDraftEnd[1],
|
|
4485
|
+
)
|
|
4486
|
+
const measurements = [cursorIndex - 1, cursorIndex].map((side) => {
|
|
4487
|
+
const from = corners[(side + 5) % 4]!
|
|
4488
|
+
const to = corners[(side + 4) % 4]!
|
|
4489
|
+
const dx = to[0] - from[0]
|
|
4490
|
+
const dy = to[1] - from[1]
|
|
4491
|
+
const length = Math.hypot(dx, dy)
|
|
4492
|
+
return {
|
|
4493
|
+
lengthLabel: formatMeasurement(length, unit, null, metricNotation),
|
|
4494
|
+
midpoint: [(from[0] + to[0]) / 2, (from[1] + to[1]) / 2] as WallPlanPoint,
|
|
4495
|
+
direction: [dx / length, dy / length] as WallPlanPoint,
|
|
4496
|
+
angleLabels: [],
|
|
4497
|
+
}
|
|
4498
|
+
})
|
|
4499
|
+
return { polygons, measurements }
|
|
4500
|
+
}, [
|
|
4501
|
+
isWallBuildActive,
|
|
4502
|
+
levelId,
|
|
4503
|
+
metricNotation,
|
|
4504
|
+
unit,
|
|
4505
|
+
wallRectangleDraftEnd,
|
|
4506
|
+
wallRectangleDraftStart,
|
|
4507
|
+
])
|
|
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
|
+
|
|
4700
4523
|
const draftPolygonPoints = useMemo(() => {
|
|
4701
4524
|
if (isRoofBuildActive && roofDraftStart && roofDraftEnd) {
|
|
4702
4525
|
const minX = Math.min(roofDraftStart[0], roofDraftEnd[0])
|
|
@@ -4864,6 +4687,10 @@ function FloorplanLinearDraftLayer({
|
|
|
4864
4687
|
if (isWallBuildActive && wallDraftStart && wallDraftEnd) {
|
|
4865
4688
|
pushDraft(wallDraftStart, wallDraftEnd)
|
|
4866
4689
|
}
|
|
4690
|
+
if (isWallBuildActive && wallRectangleDraftStart && wallRectangleDraftEnd) {
|
|
4691
|
+
pushCross(wallRectangleDraftStart)
|
|
4692
|
+
pushCross(wallRectangleDraftEnd)
|
|
4693
|
+
}
|
|
4867
4694
|
if (isFenceBuildActive && fenceDraftStart && fenceDraftEnd) {
|
|
4868
4695
|
pushDraft(fenceDraftStart, fenceDraftEnd)
|
|
4869
4696
|
}
|
|
@@ -4875,6 +4702,8 @@ function FloorplanLinearDraftLayer({
|
|
|
4875
4702
|
fenceDraftStart,
|
|
4876
4703
|
wallDraftEnd,
|
|
4877
4704
|
wallDraftStart,
|
|
4705
|
+
wallRectangleDraftEnd,
|
|
4706
|
+
wallRectangleDraftStart,
|
|
4878
4707
|
])
|
|
4879
4708
|
|
|
4880
4709
|
return (
|
|
@@ -4923,7 +4752,7 @@ function FloorplanLinearDraftLayer({
|
|
|
4923
4752
|
)}
|
|
4924
4753
|
|
|
4925
4754
|
{draftWallMeasurement && (
|
|
4926
|
-
<
|
|
4755
|
+
<FloorplanDraftMeasurement
|
|
4927
4756
|
labelBackground={isDark ? '#0f172a' : '#ffffff'}
|
|
4928
4757
|
labelText={isDark ? '#e2e8f0' : '#171717'}
|
|
4929
4758
|
measurement={draftWallMeasurement}
|
|
@@ -4932,6 +4761,50 @@ function FloorplanLinearDraftLayer({
|
|
|
4932
4761
|
unitsPerPixel={unitsPerPixel}
|
|
4933
4762
|
/>
|
|
4934
4763
|
)}
|
|
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
|
+
|
|
4781
|
+
{rectangleDraft?.polygons.map((points, index) => (
|
|
4782
|
+
<FloorplanDraftLayer
|
|
4783
|
+
anchorFill={draftStroke}
|
|
4784
|
+
draftAnchorPoints={EMPTY_DRAFT_ANCHOR_POINTS}
|
|
4785
|
+
draftFill={draftFill}
|
|
4786
|
+
draftPolygonPoints={points}
|
|
4787
|
+
draftStroke={draftStroke}
|
|
4788
|
+
key={index}
|
|
4789
|
+
linearDraftSegment={null}
|
|
4790
|
+
polygonDraftClosingSegment={null}
|
|
4791
|
+
polygonDraftPolygonPoints={null}
|
|
4792
|
+
polygonDraftPolylinePoints={null}
|
|
4793
|
+
unitsPerPixel={unitsPerPixel}
|
|
4794
|
+
/>
|
|
4795
|
+
))}
|
|
4796
|
+
|
|
4797
|
+
{rectangleDraft?.measurements.map((measurement, index) => (
|
|
4798
|
+
<FloorplanDraftMeasurement
|
|
4799
|
+
key={index}
|
|
4800
|
+
labelBackground={isDark ? '#0f172a' : '#ffffff'}
|
|
4801
|
+
labelText={isDark ? '#e2e8f0' : '#171717'}
|
|
4802
|
+
measurement={measurement}
|
|
4803
|
+
measurementStroke={measurementStroke}
|
|
4804
|
+
sceneRotationDeg={sceneRotationDeg}
|
|
4805
|
+
unitsPerPixel={unitsPerPixel}
|
|
4806
|
+
/>
|
|
4807
|
+
))}
|
|
4935
4808
|
</>
|
|
4936
4809
|
)
|
|
4937
4810
|
}
|
|
@@ -5026,6 +4899,12 @@ export function FloorplanPanel({
|
|
|
5026
4899
|
const unit = useViewer((state) => state.unit)
|
|
5027
4900
|
const metricNotation = useViewer((state) => state.metricNotation)
|
|
5028
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'
|
|
5029
4908
|
const showGuides = useViewer((state) => state.showGuides)
|
|
5030
4909
|
const setShowGuides = useViewer((state) => state.setShowGuides)
|
|
5031
4910
|
const selectedItem = useEditor((state) => state.selectedItem)
|
|
@@ -5035,7 +4914,21 @@ export function FloorplanPanel({
|
|
|
5035
4914
|
// editor/index.tsx — subscribing here lets us re-fit the viewport when
|
|
5036
4915
|
// the user closes and re-opens the 2D editor instead of restoring the
|
|
5037
4916
|
// stale viewport from before they closed it.
|
|
5038
|
-
const isFloorplanOpen = useEditor((state) => state
|
|
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)
|
|
5039
4932
|
// Mirror for callbacks that fire outside React's render (the per-frame
|
|
5040
4933
|
// navigation-pose subscriber): when the 2D panel is hidden (`display:none` in
|
|
5041
4934
|
// 3D mode) it must NOT re-render on every camera-zoom frame.
|
|
@@ -5053,8 +4946,6 @@ export function FloorplanPanel({
|
|
|
5053
4946
|
const activeHandleDrag = useActiveHandleDrag()
|
|
5054
4947
|
const setPhase = useEditor((state) => state.setPhase)
|
|
5055
4948
|
const setMovingNode = useEditor((state) => state.setMovingNode)
|
|
5056
|
-
const structureLayer = useEditor((state) => state.structureLayer)
|
|
5057
|
-
const setStructureLayer = useEditor((state) => state.setStructureLayer)
|
|
5058
4949
|
const setTool = useEditor((state) => state.setTool)
|
|
5059
4950
|
const tool = useEditor((state) => state.tool)
|
|
5060
4951
|
const deleteNode = useScene((state) => state.deleteNode)
|
|
@@ -5336,12 +5227,6 @@ export function FloorplanPanel({
|
|
|
5336
5227
|
height: number
|
|
5337
5228
|
} | null>(null)
|
|
5338
5229
|
|
|
5339
|
-
useEffect(() => {
|
|
5340
|
-
if (structureLayer === 'zones' && floorplanSelectionTool === 'marquee') {
|
|
5341
|
-
setFloorplanSelectionTool('click')
|
|
5342
|
-
}
|
|
5343
|
-
}, [floorplanSelectionTool, setFloorplanSelectionTool, structureLayer])
|
|
5344
|
-
|
|
5345
5230
|
useEffect(() => {
|
|
5346
5231
|
setIsMacPlatform(navigator.platform.toUpperCase().includes('MAC'))
|
|
5347
5232
|
}, [])
|
|
@@ -5855,21 +5740,21 @@ export function FloorplanPanel({
|
|
|
5855
5740
|
)
|
|
5856
5741
|
|
|
5857
5742
|
const isSiteEditActive = phase === 'site'
|
|
5858
|
-
const isWallBuildActive =
|
|
5859
|
-
const isSlabBuildActive =
|
|
5860
|
-
const isCeilingBuildActive =
|
|
5861
|
-
const isZoneBuildActive =
|
|
5862
|
-
const isDoorBuildActive =
|
|
5863
|
-
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'
|
|
5864
5749
|
const isPolygonBuildActive = isSlabBuildActive || isZoneBuildActive
|
|
5865
5750
|
const isPolygonDraftBuildActive = isPolygonBuildActive || isCeilingBuildActive
|
|
5866
5751
|
const isOpeningBuildActive = isDoorBuildActive || isWindowBuildActive
|
|
5867
5752
|
const isOpeningMoveActive = movingOpeningType !== null
|
|
5868
5753
|
const isOpeningPlacementActive = isOpeningBuildActive || isOpeningMoveActive
|
|
5869
|
-
const isFenceBuildActive =
|
|
5754
|
+
const isFenceBuildActive = mode === 'build' && tool === 'fence'
|
|
5870
5755
|
const fenceContinuation = useEditor((state) => state.continuationByContext.fence)
|
|
5871
|
-
const isRoofBuildActive =
|
|
5872
|
-
const isStairBuildActive =
|
|
5756
|
+
const isRoofBuildActive = mode === 'build' && tool === 'roof'
|
|
5757
|
+
const isStairBuildActive = mode === 'build' && tool === 'stair'
|
|
5873
5758
|
const isStairMoveActive = movingNode?.type === 'stair'
|
|
5874
5759
|
const isRoofMoveActive = movingNode?.type === 'roof' || movingNode?.type === 'roof-segment'
|
|
5875
5760
|
const isSlabMoveActive = movingNode?.type === 'slab'
|
|
@@ -5995,12 +5880,11 @@ export function FloorplanPanel({
|
|
|
5995
5880
|
mode === 'select' &&
|
|
5996
5881
|
floorplanSelectionTool === 'marquee' &&
|
|
5997
5882
|
!movingNode &&
|
|
5998
|
-
!isFenceEndpointMoveActive
|
|
5999
|
-
structureLayer !== 'zones'
|
|
5883
|
+
!isFenceEndpointMoveActive
|
|
6000
5884
|
const isScreenSelectionToolActive =
|
|
6001
5885
|
mode === 'select' &&
|
|
6002
5886
|
floorplanSelectionTool === 'click' &&
|
|
6003
|
-
|
|
5887
|
+
phase === 'building' &&
|
|
6004
5888
|
!movingNode &&
|
|
6005
5889
|
!isFenceEndpointMoveActive &&
|
|
6006
5890
|
!referenceScaleDraft &&
|
|
@@ -6010,8 +5894,7 @@ export function FloorplanPanel({
|
|
|
6010
5894
|
mode === 'select' &&
|
|
6011
5895
|
floorplanSelectionTool === 'click' &&
|
|
6012
5896
|
!movingNode &&
|
|
6013
|
-
!isFenceEndpointMoveActive
|
|
6014
|
-
structureLayer !== 'zones'
|
|
5897
|
+
!isFenceEndpointMoveActive
|
|
6015
5898
|
const canInteractElementFloorplanGeometry = isDeleteMode || canSelectElementFloorplanGeometry
|
|
6016
5899
|
const canInteractFloorplanSlabs = isDeleteMode || canSelectElementFloorplanGeometry
|
|
6017
5900
|
const canInteractWithGuides =
|
|
@@ -6023,42 +5906,7 @@ export function FloorplanPanel({
|
|
|
6023
5906
|
mode === 'select' &&
|
|
6024
5907
|
floorplanSelectionTool === 'click' &&
|
|
6025
5908
|
!movingNode &&
|
|
6026
|
-
!isFenceEndpointMoveActive
|
|
6027
|
-
structureLayer === 'zones'
|
|
6028
|
-
const canInteractFloorplanZones = isDeleteMode || canSelectFloorplanZones
|
|
6029
|
-
const isFloorplanStructureContextActive = phase === 'structure' && structureLayer !== 'zones'
|
|
6030
|
-
const isFloorplanFurnishContextActive = phase === 'furnish'
|
|
6031
|
-
const isFloorplanItemContextActive =
|
|
6032
|
-
isFloorplanFurnishContextActive || isFloorplanStructureContextActive
|
|
6033
|
-
const canSelectFloorplanStairs =
|
|
6034
|
-
(mode === 'select' &&
|
|
6035
|
-
floorplanSelectionTool === 'click' &&
|
|
6036
|
-
!movingNode &&
|
|
6037
|
-
!isFenceEndpointMoveActive &&
|
|
6038
|
-
isFloorplanStructureContextActive) ||
|
|
6039
|
-
isDeleteMode
|
|
6040
|
-
const canSelectFloorplanElevators = canSelectFloorplanStairs
|
|
6041
|
-
const canSelectFloorplanSpawns = canSelectFloorplanStairs
|
|
6042
|
-
const canSelectFloorplanItems =
|
|
6043
|
-
(mode === 'select' &&
|
|
6044
|
-
floorplanSelectionTool === 'click' &&
|
|
6045
|
-
!movingNode &&
|
|
6046
|
-
!isFenceEndpointMoveActive &&
|
|
6047
|
-
isFloorplanItemContextActive) ||
|
|
6048
|
-
isDeleteMode
|
|
6049
|
-
const canFocusFloorplanStairs =
|
|
6050
|
-
mode === 'select' &&
|
|
6051
|
-
floorplanSelectionTool === 'click' &&
|
|
6052
|
-
!movingNode &&
|
|
6053
|
-
!isFenceEndpointMoveActive &&
|
|
6054
|
-
isFloorplanStructureContextActive
|
|
6055
|
-
const canFocusFloorplanSpawns = canFocusFloorplanStairs
|
|
6056
|
-
const canFocusFloorplanItems =
|
|
6057
|
-
mode === 'select' &&
|
|
6058
|
-
floorplanSelectionTool === 'click' &&
|
|
6059
|
-
!movingNode &&
|
|
6060
|
-
!isFenceEndpointMoveActive &&
|
|
6061
|
-
isFloorplanItemContextActive
|
|
5909
|
+
!isFenceEndpointMoveActive
|
|
6062
5910
|
const visibleSitePolygon = displaySitePolygon
|
|
6063
5911
|
const canUseSiteBoundaryVertexHandles =
|
|
6064
5912
|
visibleSitePolygon !== null && siteBoundaryHandlesEnabled({ mode, phase })
|
|
@@ -7711,7 +7559,7 @@ export function FloorplanPanel({
|
|
|
7711
7559
|
scheduleFloorplanZoomCommit()
|
|
7712
7560
|
const userRotationDeg = latestFloorplanUserRotationDegRef.current
|
|
7713
7561
|
floorplanZoomPoseRef.current = { localCenter, userRotationDeg, viewWidth: nextWidth }
|
|
7714
|
-
if (useEditor.getState()
|
|
7562
|
+
if (visibleScene(useEditor.getState()) === 'split') {
|
|
7715
7563
|
publishFloorplanNavigationPose(localCenter, userRotationDeg, nextWidth)
|
|
7716
7564
|
}
|
|
7717
7565
|
},
|
|
@@ -7784,6 +7632,115 @@ export function FloorplanPanel({
|
|
|
7784
7632
|
[publishFloorplanNavigationPose],
|
|
7785
7633
|
)
|
|
7786
7634
|
|
|
7635
|
+
// WASD and the orbit buttons in 2D-only view. The 3D canvas is paused there
|
|
7636
|
+
// (`renderPaused`), so the plan drives them itself — same keys and speed as
|
|
7637
|
+
// the camera — and hands the pose to 3D when the move ends.
|
|
7638
|
+
const keyboardPanKeysRef = useRef<KeyboardPanState>({
|
|
7639
|
+
forward: false,
|
|
7640
|
+
backward: false,
|
|
7641
|
+
left: false,
|
|
7642
|
+
right: false,
|
|
7643
|
+
})
|
|
7644
|
+
useEffect(() => {
|
|
7645
|
+
if (shownScene !== '2d') return
|
|
7646
|
+
const keys = keyboardPanKeysRef.current
|
|
7647
|
+
const motion = createKeyboardPanMotion()
|
|
7648
|
+
let frame: number | null = null
|
|
7649
|
+
const sceneRotationDeg = (userRotationDeg: number) =>
|
|
7650
|
+
FLOORPLAN_VIEW_ROTATION_DEG + userRotationDeg - buildingRotationDeg
|
|
7651
|
+
const step = () => {
|
|
7652
|
+
const viewport = latestViewportRef.current ?? latestFittedViewportRef.current
|
|
7653
|
+
if (!(viewport && advanceKeyboardPanMotion(motion, keys, performance.now()))) {
|
|
7654
|
+
frame = null
|
|
7655
|
+
resetKeyboardPanMotion(motion)
|
|
7656
|
+
commitFloorplanPan()
|
|
7657
|
+
return
|
|
7658
|
+
}
|
|
7659
|
+
const speed = keyboardPanSpeed(viewport.width)
|
|
7660
|
+
const next = {
|
|
7661
|
+
centerX: viewport.centerX + motion.stepX * speed,
|
|
7662
|
+
centerY: viewport.centerY - motion.stepY * speed,
|
|
7663
|
+
width: viewport.width,
|
|
7664
|
+
}
|
|
7665
|
+
const userRotationDeg = latestFloorplanUserRotationDegRef.current
|
|
7666
|
+
floorplanViewportInteractionInProgressRef.current = true
|
|
7667
|
+
applyFloorplanViewportImperatively(next)
|
|
7668
|
+
floorplanPanPoseRef.current = {
|
|
7669
|
+
localCenter: rotateSvgPoint(
|
|
7670
|
+
{ x: next.centerX, y: next.centerY },
|
|
7671
|
+
-sceneRotationDeg(userRotationDeg),
|
|
7672
|
+
),
|
|
7673
|
+
userRotationDeg,
|
|
7674
|
+
viewWidth: next.width,
|
|
7675
|
+
}
|
|
7676
|
+
frame = requestAnimationFrame(step)
|
|
7677
|
+
}
|
|
7678
|
+
const start = () => {
|
|
7679
|
+
if (frame === null) frame = requestAnimationFrame(step)
|
|
7680
|
+
}
|
|
7681
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
7682
|
+
if (!(isKeyboardPanKey(event.code) && acceptsKeyboardPan(event))) return
|
|
7683
|
+
syncKeyboardPanMotion(motion, keys, performance.now())
|
|
7684
|
+
if (setKeyboardPanKey(keys, event.code, true)) start()
|
|
7685
|
+
event.preventDefault()
|
|
7686
|
+
event.stopPropagation()
|
|
7687
|
+
}
|
|
7688
|
+
const onKeyUp = (event: KeyboardEvent) => {
|
|
7689
|
+
if (!isKeyboardPanKey(event.code)) return
|
|
7690
|
+
syncKeyboardPanMotion(motion, keys, performance.now())
|
|
7691
|
+
if (setKeyboardPanKey(keys, event.code, false)) {
|
|
7692
|
+
event.preventDefault()
|
|
7693
|
+
event.stopPropagation()
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
const onBlur = () => clearKeyboardPanKeys(keys)
|
|
7697
|
+
// Same rule as the 3D buttons: snap to the nearest quarter turn, then turn one more.
|
|
7698
|
+
const orbit = (clockwise: boolean) => {
|
|
7699
|
+
const viewport = latestViewportRef.current ?? latestFittedViewportRef.current
|
|
7700
|
+
if (!viewport) return
|
|
7701
|
+
const userRotationDeg = latestFloorplanUserRotationDegRef.current
|
|
7702
|
+
const quarter = Math.PI / 2
|
|
7703
|
+
const azimuth =
|
|
7704
|
+
Math.round(cameraAzimuthFromFloorplanRotation(userRotationDeg) / quarter) * quarter +
|
|
7705
|
+
(clockwise ? -quarter : quarter)
|
|
7706
|
+
const nextRotationDeg = floorplanRotationFromCameraAzimuth(azimuth, userRotationDeg)
|
|
7707
|
+
const localCenter = rotateSvgPoint(
|
|
7708
|
+
{ x: viewport.centerX, y: viewport.centerY },
|
|
7709
|
+
-sceneRotationDeg(userRotationDeg),
|
|
7710
|
+
)
|
|
7711
|
+
smoothFloorplanNavigationView(localCenter, nextRotationDeg, viewport.width)
|
|
7712
|
+
publishFloorplanNavigationPose(localCenter, nextRotationDeg, viewport.width)
|
|
7713
|
+
}
|
|
7714
|
+
const orbitClockwise = () => orbit(true)
|
|
7715
|
+
const orbitCounterClockwise = () => orbit(false)
|
|
7716
|
+
document.addEventListener('keydown', onKeyDown)
|
|
7717
|
+
document.addEventListener('keyup', onKeyUp)
|
|
7718
|
+
window.addEventListener('blur', onBlur)
|
|
7719
|
+
emitter.on('camera-controls:orbit-cw', orbitClockwise)
|
|
7720
|
+
emitter.on('camera-controls:orbit-ccw', orbitCounterClockwise)
|
|
7721
|
+
return () => {
|
|
7722
|
+
document.removeEventListener('keydown', onKeyDown)
|
|
7723
|
+
document.removeEventListener('keyup', onKeyUp)
|
|
7724
|
+
window.removeEventListener('blur', onBlur)
|
|
7725
|
+
emitter.off('camera-controls:orbit-cw', orbitClockwise)
|
|
7726
|
+
emitter.off('camera-controls:orbit-ccw', orbitCounterClockwise)
|
|
7727
|
+
// A key released while 3D owns the keys would otherwise still read as
|
|
7728
|
+
// held when the plan takes them back; the camera clears its own the same way.
|
|
7729
|
+
clearKeyboardPanKeys(keys)
|
|
7730
|
+
if (frame !== null) {
|
|
7731
|
+
cancelAnimationFrame(frame)
|
|
7732
|
+
commitFloorplanPan()
|
|
7733
|
+
}
|
|
7734
|
+
}
|
|
7735
|
+
}, [
|
|
7736
|
+
applyFloorplanViewportImperatively,
|
|
7737
|
+
buildingRotationDeg,
|
|
7738
|
+
commitFloorplanPan,
|
|
7739
|
+
publishFloorplanNavigationPose,
|
|
7740
|
+
smoothFloorplanNavigationView,
|
|
7741
|
+
shownScene,
|
|
7742
|
+
])
|
|
7743
|
+
|
|
7787
7744
|
useLayoutEffect(() => {
|
|
7788
7745
|
flushFloorplanRotationPresentationRestore(pendingFloorplanRotationRestoreRef)
|
|
7789
7746
|
})
|
|
@@ -7833,7 +7790,10 @@ export function FloorplanPanel({
|
|
|
7833
7790
|
}
|
|
7834
7791
|
}, [isFloorplanOpen, stopFloorplanViewAnimation])
|
|
7835
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.
|
|
7836
7795
|
const clearWallPlacementDraft = useCallback(() => {
|
|
7796
|
+
discardWallPolygonDraft()
|
|
7837
7797
|
setDraftStart(null)
|
|
7838
7798
|
setWallChainFirstVertex(null)
|
|
7839
7799
|
wallConstructionOptionsRef.current = undefined
|
|
@@ -7890,10 +7850,9 @@ export function FloorplanPanel({
|
|
|
7890
7850
|
setHoveredSiteHandleId(null)
|
|
7891
7851
|
}, [clearSiteBoundaryLivePreview])
|
|
7892
7852
|
const exitSiteEditingToSelect = useCallback(() => {
|
|
7893
|
-
setPhase('
|
|
7894
|
-
setStructureLayer('elements')
|
|
7853
|
+
setPhase('building')
|
|
7895
7854
|
setMode('select')
|
|
7896
|
-
}, [setMode, setPhase
|
|
7855
|
+
}, [setMode, setPhase])
|
|
7897
7856
|
|
|
7898
7857
|
const clearDraft = useCallback(() => {
|
|
7899
7858
|
clearWallPlacementDraft()
|
|
@@ -7955,10 +7914,8 @@ export function FloorplanPanel({
|
|
|
7955
7914
|
}
|
|
7956
7915
|
|
|
7957
7916
|
const { createNode, updateNode, nodes } = useScene.getState()
|
|
7958
|
-
const
|
|
7959
|
-
|
|
7960
|
-
color: PALETTE_COLORS[zoneCount % PALETTE_COLORS.length],
|
|
7961
|
-
name: `Zone ${zoneCount + 1}`,
|
|
7917
|
+
const zone = newZone({
|
|
7918
|
+
name: nextZoneName(nodes),
|
|
7962
7919
|
polygon: points.map(([x, z]) => [x, z] as [number, number]),
|
|
7963
7920
|
})
|
|
7964
7921
|
|
|
@@ -8240,13 +8197,13 @@ export function FloorplanPanel({
|
|
|
8240
8197
|
|
|
8241
8198
|
if (
|
|
8242
8199
|
isStairBuildActive &&
|
|
8243
|
-
useEditor.getState()
|
|
8200
|
+
visibleScene(useEditor.getState()) === '2d' &&
|
|
8244
8201
|
(event.key === 'r' || event.key === 'R')
|
|
8245
8202
|
) {
|
|
8246
8203
|
useStairBuildPreview.getState().rotateBy(Math.PI / 4)
|
|
8247
8204
|
} else if (
|
|
8248
8205
|
isStairBuildActive &&
|
|
8249
|
-
useEditor.getState()
|
|
8206
|
+
visibleScene(useEditor.getState()) === '2d' &&
|
|
8250
8207
|
(event.key === 't' || event.key === 'T')
|
|
8251
8208
|
) {
|
|
8252
8209
|
useStairBuildPreview.getState().rotateBy(-Math.PI / 4)
|
|
@@ -8957,7 +8914,7 @@ export function FloorplanPanel({
|
|
|
8957
8914
|
// tools that subscribe to these grid events.
|
|
8958
8915
|
const emitFloorplanGridEvent = useCallback(
|
|
8959
8916
|
(
|
|
8960
|
-
eventType: 'move' | 'click' | 'double-click',
|
|
8917
|
+
eventType: 'move' | 'click' | 'double-click' | 'pointerdown' | 'pointerup',
|
|
8961
8918
|
planPoint: WallPlanPoint,
|
|
8962
8919
|
nativeEvent: ReactMouseEvent<SVGSVGElement> | ReactPointerEvent<SVGSVGElement>,
|
|
8963
8920
|
) => {
|
|
@@ -9158,7 +9115,7 @@ export function FloorplanPanel({
|
|
|
9158
9115
|
const nextUserRotationDeg = rotationState.initialUserRotationDeg + angleDeltaDeg
|
|
9159
9116
|
|
|
9160
9117
|
applyFloorplanRotationImperatively(rotationState, nextUserRotationDeg)
|
|
9161
|
-
if (useEditor.getState()
|
|
9118
|
+
if (visibleScene(useEditor.getState()) === 'split') {
|
|
9162
9119
|
publishFloorplanNavigationPose(
|
|
9163
9120
|
rotationState.viewportCenterLocal,
|
|
9164
9121
|
nextUserRotationDeg,
|
|
@@ -9200,7 +9157,7 @@ export function FloorplanPanel({
|
|
|
9200
9157
|
userRotationDeg: currentUserRotationDeg,
|
|
9201
9158
|
viewWidth: currentViewport.width,
|
|
9202
9159
|
}
|
|
9203
|
-
if (useEditor.getState()
|
|
9160
|
+
if (visibleScene(useEditor.getState()) === 'split') {
|
|
9204
9161
|
publishFloorplanNavigationPose(localCenter, currentUserRotationDeg, currentViewport.width)
|
|
9205
9162
|
}
|
|
9206
9163
|
|
|
@@ -9292,6 +9249,14 @@ export function FloorplanPanel({
|
|
|
9292
9249
|
}
|
|
9293
9250
|
|
|
9294
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
|
+
}
|
|
9295
9260
|
// Fence draft: grid snap (+ existing-wall/fence endpoint snap), then
|
|
9296
9261
|
// Figma alignment — same endpoint-wins precedence as the wall branch.
|
|
9297
9262
|
// While a draft is open the segment locks to 15° rays from its start.
|
|
@@ -9299,14 +9264,18 @@ export function FloorplanPanel({
|
|
|
9299
9264
|
// there is no Shift hold-to-bypass. Alignment follows the magnetic snap
|
|
9300
9265
|
// mode, not Alt (continuation is cycled through the HUD / C).
|
|
9301
9266
|
const fenceAngleSnap = fenceDraftStart !== null && isAngleSnapActive()
|
|
9302
|
-
const fenceSnapped =
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
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
|
+
)
|
|
9310
9279
|
const fenceGridBase = snapWallPointToGrid(planPoint)
|
|
9311
9280
|
const fenceLocked =
|
|
9312
9281
|
fenceSnapped[0] !== fenceGridBase[0] || fenceSnapped[1] !== fenceGridBase[1]
|
|
@@ -9495,7 +9464,7 @@ export function FloorplanPanel({
|
|
|
9495
9464
|
const wallAngleSnap = draftStart !== null && isAngleSnapActive()
|
|
9496
9465
|
const wallSnap = snapWallDraftPointDetailed({
|
|
9497
9466
|
point: planPoint,
|
|
9498
|
-
walls,
|
|
9467
|
+
walls: [...walls, ...wallPolygonDraftWalls()],
|
|
9499
9468
|
start: draftStart ?? undefined,
|
|
9500
9469
|
angleSnap: wallAngleSnap,
|
|
9501
9470
|
magnetic: isMagneticSnapActive(),
|
|
@@ -9602,7 +9571,7 @@ export function FloorplanPanel({
|
|
|
9602
9571
|
const firstPoint = slabDraftPoints[0]
|
|
9603
9572
|
if (firstPoint && slabDraftPoints.length >= 3 && isPointNearPlanPoint(point, firstPoint)) {
|
|
9604
9573
|
// 2D-only view: the 3D tool can't commit, so close the polygon here.
|
|
9605
|
-
if (useEditor.getState()
|
|
9574
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9606
9575
|
createSlabOnCurrentLevel(slabDraftPoints, slabPlacementBaseRef.current)
|
|
9607
9576
|
}
|
|
9608
9577
|
clearDraft()
|
|
@@ -9652,7 +9621,7 @@ export function FloorplanPanel({
|
|
|
9652
9621
|
}
|
|
9653
9622
|
|
|
9654
9623
|
// 2D-only view: the 3D tool can't commit, so create the slab here.
|
|
9655
|
-
if (useEditor.getState()
|
|
9624
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9656
9625
|
createSlabOnCurrentLevel(nextPoints, slabPlacementBaseRef.current)
|
|
9657
9626
|
}
|
|
9658
9627
|
clearDraft()
|
|
@@ -9668,7 +9637,7 @@ export function FloorplanPanel({
|
|
|
9668
9637
|
|
|
9669
9638
|
const firstPoint = ceilingDraftPoints[0]
|
|
9670
9639
|
if (firstPoint && ceilingDraftPoints.length >= 3 && isPointNearPlanPoint(point, firstPoint)) {
|
|
9671
|
-
if (useEditor.getState()
|
|
9640
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9672
9641
|
createCeilingOnCurrentLevel(ceilingDraftPoints)
|
|
9673
9642
|
}
|
|
9674
9643
|
clearCeilingPlacementDraft()
|
|
@@ -9701,7 +9670,7 @@ export function FloorplanPanel({
|
|
|
9701
9670
|
return
|
|
9702
9671
|
}
|
|
9703
9672
|
|
|
9704
|
-
if (useEditor.getState()
|
|
9673
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9705
9674
|
createCeilingOnCurrentLevel(nextPoints)
|
|
9706
9675
|
}
|
|
9707
9676
|
clearCeilingPlacementDraft()
|
|
@@ -9768,6 +9737,14 @@ export function FloorplanPanel({
|
|
|
9768
9737
|
useEditor.getState().toolDefaults.wall,
|
|
9769
9738
|
)
|
|
9770
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
|
+
}
|
|
9771
9748
|
setDraftStart(point)
|
|
9772
9749
|
setWallChainFirstVertex(point)
|
|
9773
9750
|
setDraftEnd(point)
|
|
@@ -9793,8 +9770,22 @@ export function FloorplanPanel({
|
|
|
9793
9770
|
// `display:none`, so the tool never commits. Mirror the slab /
|
|
9794
9771
|
// ceiling 2D-only committers: create locally here, gated on the
|
|
9795
9772
|
// view, so split / 3D keep their single-owner tool commit.
|
|
9796
|
-
const viewIs2DOnly = useEditor.getState()
|
|
9773
|
+
const viewIs2DOnly = visibleScene(useEditor.getState()) === '2d'
|
|
9797
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
|
+
}
|
|
9798
9789
|
if (viewIs2DOnly) {
|
|
9799
9790
|
createdWall = createWallOnCurrentLevel(
|
|
9800
9791
|
draftStart,
|
|
@@ -9998,18 +9989,25 @@ export function FloorplanPanel({
|
|
|
9998
9989
|
return
|
|
9999
9990
|
}
|
|
10000
9991
|
|
|
9992
|
+
if (useInteractionScope.getState().scope.kind !== 'idle') return
|
|
10001
9993
|
const modifierKeys = getSelectionModifierKeys(event)
|
|
10002
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
|
+
}
|
|
10003
10000
|
const backgroundSelection = resolveFloorplanBackgroundSelection({
|
|
10004
10001
|
canSelectElementFloorplanGeometry,
|
|
10005
|
-
canSelectFloorplanZones,
|
|
10002
|
+
canSelectFloorplanZones: canSelectFloorplanZones && !!useViewer.getState().focusedUnitId,
|
|
10006
10003
|
currentSelectedIds: useViewer.getState().selection.selectedIds,
|
|
10007
10004
|
expandIdsForNode: expandSessionSelectionForNode,
|
|
10008
|
-
getFloorplanHitIdAtPoint
|
|
10005
|
+
getFloorplanHitIdAtPoint: useViewer.getState().focusedUnitId
|
|
10006
|
+
? (point) => zoneAtLevelPoint(point[0], point[1])?.id ?? null
|
|
10007
|
+
: getFloorplanHitIdAtPoint,
|
|
10009
10008
|
isWallBuildActive,
|
|
10010
10009
|
modifierKeys,
|
|
10011
10010
|
planPoint,
|
|
10012
|
-
structureLayer,
|
|
10013
10011
|
})
|
|
10014
10012
|
|
|
10015
10013
|
if (backgroundSelection.handled) {
|
|
@@ -10057,15 +10055,14 @@ export function FloorplanPanel({
|
|
|
10057
10055
|
}
|
|
10058
10056
|
|
|
10059
10057
|
if (backgroundSelection.kind === 'clear-zones') {
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
useEditor.getState().setStructureLayer('elements')
|
|
10063
|
-
useEditor.getState().setMode('select')
|
|
10058
|
+
useEditor.getState().clearRoom()
|
|
10059
|
+
setSelection({ selectedIds: [], zoneId: null })
|
|
10064
10060
|
return
|
|
10065
10061
|
}
|
|
10066
10062
|
|
|
10067
10063
|
if (!backgroundSelection.preserveSelection) {
|
|
10068
|
-
|
|
10064
|
+
useEditor.getState().clearRoom()
|
|
10065
|
+
setSelection({ selectedIds: [], zoneId: null })
|
|
10069
10066
|
}
|
|
10070
10067
|
return
|
|
10071
10068
|
}
|
|
@@ -10083,7 +10080,6 @@ export function FloorplanPanel({
|
|
|
10083
10080
|
referenceScaleDraft,
|
|
10084
10081
|
setSelectedReferenceId,
|
|
10085
10082
|
setSelection,
|
|
10086
|
-
structureLayer,
|
|
10087
10083
|
getFloorplanHitIdAtPoint,
|
|
10088
10084
|
unit,
|
|
10089
10085
|
emitFloorplanGridEvent,
|
|
@@ -10103,6 +10099,49 @@ export function FloorplanPanel({
|
|
|
10103
10099
|
},
|
|
10104
10100
|
[handleBackgroundClick],
|
|
10105
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
|
+
)
|
|
10106
10145
|
const handleBackgroundDoubleClick = useCallback(
|
|
10107
10146
|
(event: ReactMouseEvent<SVGSVGElement>) => {
|
|
10108
10147
|
if (!(isPolygonDraftBuildActive && !isRoofBuildActive)) {
|
|
@@ -10253,28 +10292,18 @@ export function FloorplanPanel({
|
|
|
10253
10292
|
if (floorplanScreenSelectionOwnsInputDraggingRef.current) {
|
|
10254
10293
|
useViewer.getState().setInputDragging(false)
|
|
10255
10294
|
floorplanScreenSelectionOwnsInputDraggingRef.current = false
|
|
10295
|
+
useInteractionScope.getState().endIf((scope) => scope.kind === 'box-select')
|
|
10256
10296
|
}
|
|
10257
10297
|
}, [syncPreviewSelectedIds])
|
|
10258
10298
|
|
|
10259
10299
|
const commitFloorplanScreenSelection = useCallback(
|
|
10260
10300
|
(nextSelectedIds: string[], event: PointerEvent) => {
|
|
10261
10301
|
const modifierKeys = getSelectionModifierKeys(event)
|
|
10262
|
-
const shouldAppend = modifierKeys.meta || modifierKeys.ctrl || modifierKeys.shift
|
|
10263
|
-
|
|
10264
10302
|
setSelectedReferenceId(null)
|
|
10265
10303
|
|
|
10266
|
-
if (phase === 'structure' && structureLayer === 'zones') {
|
|
10267
|
-
if (nextSelectedIds.length > 0) {
|
|
10268
|
-
setSelection({ zoneId: nextSelectedIds[0] as ZoneNodeType['id'] })
|
|
10269
|
-
} else if (!shouldAppend) {
|
|
10270
|
-
setSelection({ zoneId: null })
|
|
10271
|
-
}
|
|
10272
|
-
return
|
|
10273
|
-
}
|
|
10274
|
-
|
|
10275
10304
|
addFloorplanSelection(nextSelectedIds, modifierKeys)
|
|
10276
10305
|
},
|
|
10277
|
-
[addFloorplanSelection,
|
|
10306
|
+
[addFloorplanSelection, setSelectedReferenceId],
|
|
10278
10307
|
)
|
|
10279
10308
|
|
|
10280
10309
|
useEffect(() => {
|
|
@@ -10324,6 +10353,7 @@ export function FloorplanPanel({
|
|
|
10324
10353
|
if (!state.isDragging && dragDistance >= SCREEN_RECTANGLE_SELECTION_DRAG_THRESHOLD_PX) {
|
|
10325
10354
|
state.isDragging = true
|
|
10326
10355
|
floorplanScreenSelectionOwnsInputDraggingRef.current = true
|
|
10356
|
+
useInteractionScope.getState().begin({ kind: 'box-select' })
|
|
10327
10357
|
useViewer.getState().setInputDragging(true)
|
|
10328
10358
|
markBoxSelectHandled()
|
|
10329
10359
|
|
|
@@ -10728,11 +10758,16 @@ export function FloorplanPanel({
|
|
|
10728
10758
|
const hasFloorplanCursorIndicator =
|
|
10729
10759
|
Boolean(movingOpeningType) ||
|
|
10730
10760
|
(mode === 'build' && tool !== null) ||
|
|
10731
|
-
(mode === 'select' && floorplanSelectionTool === 'marquee'
|
|
10761
|
+
(mode === 'select' && floorplanSelectionTool === 'marquee') ||
|
|
10732
10762
|
mode === 'delete'
|
|
10733
10763
|
|
|
10734
10764
|
const handleSvgPointerMove = useCallback(
|
|
10735
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
|
+
}
|
|
10736
10771
|
if (
|
|
10737
10772
|
hasFloorplanCursorIndicator &&
|
|
10738
10773
|
!isSpacePanPressed &&
|
|
@@ -10765,6 +10800,7 @@ export function FloorplanPanel({
|
|
|
10765
10800
|
[
|
|
10766
10801
|
handlePointerMove,
|
|
10767
10802
|
hasFloorplanCursorIndicator,
|
|
10803
|
+
getPlanPointFromClientPoint,
|
|
10768
10804
|
isSpacePanPressed,
|
|
10769
10805
|
siteVertexDragState,
|
|
10770
10806
|
setFloorplanCursorPosition,
|
|
@@ -10772,6 +10808,7 @@ export function FloorplanPanel({
|
|
|
10772
10808
|
)
|
|
10773
10809
|
|
|
10774
10810
|
const handleSvgPointerLeave = useCallback(() => {
|
|
10811
|
+
useEditor.getState().setHoveredRoom(null)
|
|
10775
10812
|
setFloorplanCursorPosition(null)
|
|
10776
10813
|
setHoveredGuideCorner(null)
|
|
10777
10814
|
handlePointerLeave()
|
|
@@ -11082,8 +11119,7 @@ export function FloorplanPanel({
|
|
|
11082
11119
|
)
|
|
11083
11120
|
})()
|
|
11084
11121
|
|
|
11085
|
-
setPhase('
|
|
11086
|
-
setStructureLayer('elements')
|
|
11122
|
+
setPhase('building')
|
|
11087
11123
|
setMode('select')
|
|
11088
11124
|
|
|
11089
11125
|
const nextSelection: Parameters<typeof setSelection>[0] = {
|
|
@@ -11100,16 +11136,7 @@ export function FloorplanPanel({
|
|
|
11100
11136
|
}
|
|
11101
11137
|
|
|
11102
11138
|
setSelection(nextSelection)
|
|
11103
|
-
}, [
|
|
11104
|
-
currentBuildingId,
|
|
11105
|
-
floorplanLevels,
|
|
11106
|
-
levelNode,
|
|
11107
|
-
setMode,
|
|
11108
|
-
setPhase,
|
|
11109
|
-
setSelection,
|
|
11110
|
-
setStructureLayer,
|
|
11111
|
-
site,
|
|
11112
|
-
])
|
|
11139
|
+
}, [currentBuildingId, floorplanLevels, levelNode, setMode, setPhase, setSelection, site])
|
|
11113
11140
|
const activeDraftAnchorPoint =
|
|
11114
11141
|
referenceScaleDraft?.start ??
|
|
11115
11142
|
draftStart ??
|
|
@@ -11189,8 +11216,10 @@ export function FloorplanPanel({
|
|
|
11189
11216
|
pill, plus the group pill for multi-selections. */}
|
|
11190
11217
|
<FloorplanRegistryActionMenu />
|
|
11191
11218
|
<FloorplanGroupActionMenu />
|
|
11219
|
+
<FloorplanDrawingTypeSwitch className={planOnRight ? 'right-24 left-auto' : undefined} />
|
|
11192
11220
|
|
|
11193
11221
|
{!isStudioWorkspace &&
|
|
11222
|
+
sceneLeft !== null &&
|
|
11194
11223
|
(levelNode?.type === 'level' || hasAmbientBuildingLevel) &&
|
|
11195
11224
|
(compassHost ? (
|
|
11196
11225
|
createPortal(
|
|
@@ -11198,6 +11227,7 @@ export function FloorplanPanel({
|
|
|
11198
11227
|
needleRef={compassNeedleRef}
|
|
11199
11228
|
northRotationDeg={floorplanUserRotationDeg}
|
|
11200
11229
|
onAlignNorth={alignFloorplanViewToNorth}
|
|
11230
|
+
style={compassStyle}
|
|
11201
11231
|
/>,
|
|
11202
11232
|
compassHost,
|
|
11203
11233
|
)
|
|
@@ -11338,7 +11368,7 @@ export function FloorplanPanel({
|
|
|
11338
11368
|
// renderer, handle layers) must NOT render/reconcile while hidden —
|
|
11339
11369
|
// otherwise every scene/selection change in pure 3D re-rendered the
|
|
11340
11370
|
// whole floorplan tree (profiler: 150–200ms on a wall-endpoint drag).
|
|
11341
|
-
// `isFloorplanOpen` is
|
|
11371
|
+
// `isFloorplanOpen` is "the plan is in a pane", so this still renders fully
|
|
11342
11372
|
// in both 2D and split. Viewport state lives on the still-mounted
|
|
11343
11373
|
// panel, so pan/zoom is preserved across the toggle.
|
|
11344
11374
|
<svg
|
|
@@ -11348,11 +11378,11 @@ export function FloorplanPanel({
|
|
|
11348
11378
|
onContextMenu={(event) => event.preventDefault()}
|
|
11349
11379
|
onDoubleClick={isMarqueeSelectionToolActive ? undefined : handleBackgroundDoubleClick}
|
|
11350
11380
|
onPointerCancel={endFloorplanNavigation}
|
|
11351
|
-
onPointerDown={
|
|
11381
|
+
onPointerDown={handleSvgPointerDown}
|
|
11352
11382
|
onPointerDownCapture={handleNavigationPointerDown}
|
|
11353
11383
|
onPointerLeave={handleSvgPointerLeave}
|
|
11354
11384
|
onPointerMove={handleSvgPointerMove}
|
|
11355
|
-
onPointerUp={
|
|
11385
|
+
onPointerUp={handleSvgPointerUp}
|
|
11356
11386
|
ref={svgRef}
|
|
11357
11387
|
style={{
|
|
11358
11388
|
cursor:
|
|
@@ -11449,7 +11479,7 @@ export function FloorplanPanel({
|
|
|
11449
11479
|
subscribes to the `useStairBuildPreview` store directly, so a
|
|
11450
11480
|
per-`grid:move` cursor update re-renders only that tiny layer
|
|
11451
11481
|
rather than this whole panel. */}
|
|
11452
|
-
<FloorplanStairBuildPreviewLayer
|
|
11482
|
+
<FloorplanStairBuildPreviewLayer palette={floorplanRegistryPalette} />
|
|
11453
11483
|
|
|
11454
11484
|
<FloorplanReferenceScaleLayer
|
|
11455
11485
|
draft={referenceScaleDraft}
|
|
@@ -11500,7 +11530,13 @@ export function FloorplanPanel({
|
|
|
11500
11530
|
whose extent is derived from the current viewBox and
|
|
11501
11531
|
would create a measure→fit→measure loop. */}
|
|
11502
11532
|
<g ref={floorplanContentRef}>
|
|
11503
|
-
|
|
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
|
+
)}
|
|
11504
11540
|
{/* Faint footprint ghost of the node being placed by a
|
|
11505
11541
|
registry placement tool (e.g. column), following the
|
|
11506
11542
|
cursor. The 3D mesh preview is hidden in 2D, so this is
|
|
@@ -11514,6 +11550,7 @@ export function FloorplanPanel({
|
|
|
11514
11550
|
`floorplan-wall-move-ghost-layer.tsx`. */}
|
|
11515
11551
|
<FloorplanWallMoveGhostLayer />
|
|
11516
11552
|
</g>
|
|
11553
|
+
{activeDrawingType !== 'site-plan' && <FloorplanOpenWallEndsLayer />}
|
|
11517
11554
|
<FloorplanMeasurementToolLayer />
|
|
11518
11555
|
<FloorplanRegisteredToolLayer />
|
|
11519
11556
|
{floorplanSceneSlot}
|
|
@@ -11530,42 +11567,54 @@ export function FloorplanPanel({
|
|
|
11530
11567
|
paint on top of node geometry. */}
|
|
11531
11568
|
<FloorplanAlignmentGuideLayer />
|
|
11532
11569
|
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
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
|
+
)}
|
|
11539
11584
|
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
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
|
+
)}
|
|
11558
11605
|
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
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
|
+
)}
|
|
11569
11618
|
|
|
11570
11619
|
{/* "Magnetic" wall-snap beacon — per-kind glyph at the active
|
|
11571
11620
|
draft / endpoint-move snap point. Same store + coord space as
|