@pascal-app/editor 1.0.3 → 1.0.5
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/README.md +106 -0
- package/package.json +42 -38
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +307 -5
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
- package/src/components/editor/construction-reveal-audio.tsx +40 -0
- package/src/components/editor/custom-camera-controls.tsx +275 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +37 -26
- package/src/components/editor/editor-layout-v2.tsx +132 -59
- package/src/components/editor/export-manager.tsx +47 -26
- package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
- package/src/components/editor/first-person/build-collider-world.ts +28 -7
- package/src/components/editor/first-person/ground-holes.test.ts +128 -0
- package/src/components/editor/first-person/ground-holes.ts +130 -0
- package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
- package/src/components/editor/first-person/mechanism-targets.ts +88 -0
- package/src/components/editor/first-person-controls.tsx +220 -9
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +466 -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-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +414 -363
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +56 -2
- package/src/components/editor/group-move-3d.ts +2 -0
- package/src/components/editor/group-rotate-handle.tsx +2 -0
- package/src/components/editor/group-transform-shared.test.ts +460 -0
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +409 -197
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/riser-diagram-panel.tsx +14 -7
- 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 +233 -0
- package/src/components/editor/room-labels.test.ts +850 -0
- package/src/components/editor/room-labels.tsx +1890 -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 +474 -419
- package/src/components/editor/slab-hole-highlights.tsx +5 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
- package/src/components/editor/snapshot-capture.test.ts +119 -60
- package/src/components/editor/snapshot-capture.ts +36 -5
- package/src/components/editor/thumbnail-generator.test.ts +654 -0
- package/src/components/editor/thumbnail-generator.tsx +1103 -223
- package/src/components/editor/use-floorplan-background-placement.ts +34 -12
- 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 +663 -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.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +343 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- 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 +2 -2
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
- package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
- package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/point-ask/bubble.tsx +538 -0
- package/src/components/point-ask/hover-chip.tsx +91 -0
- package/src/components/point-ask/mode-pill.tsx +92 -0
- package/src/components/point-ask/optional-motion.ts +22 -0
- package/src/components/point-ask/outline-corners.tsx +93 -0
- package/src/components/point-ask/pins.tsx +711 -0
- package/src/components/point-ask/point-ask-layer.tsx +83 -0
- package/src/components/point-ask/point-ask-styles.ts +105 -0
- package/src/components/point-ask/region-marquee-styles.ts +15 -0
- package/src/components/point-ask/region-marquee.tsx +64 -0
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- 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/roof/roof-edit-system.tsx +1 -0
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
- package/src/components/tools/distribution/connection-compatibility.ts +43 -0
- package/src/components/tools/distribution/distribution-public.ts +9 -0
- package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
- package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
- package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
- package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
- package/src/components/tools/distribution/draw-alignment.ts +35 -0
- package/src/components/tools/distribution/ports.test.ts +56 -0
- package/src/components/tools/distribution/ports.ts +378 -0
- package/src/components/tools/distribution/run-cursor.test.ts +135 -0
- package/src/components/tools/distribution/run-cursor.ts +36 -0
- package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
- package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
- package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
- package/src/components/tools/distribution/run-port-snap.ts +25 -0
- package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
- package/src/components/tools/distribution/run-screen-direction.ts +30 -0
- package/src/components/tools/elevator/elevator-tool.tsx +3 -6
- package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
- package/src/components/tools/item/placement-history.test.tsx +512 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +11 -10
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
- 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 +62 -63
- package/src/components/tools/registry/item-surface-move.ts +2 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
- package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/tool-manager.tsx +59 -24
- package/src/components/tools/wall/wall-drafting.test.ts +175 -47
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +9 -1
- package/src/components/ui/action-menu/camera-actions.tsx +9 -4
- package/src/components/ui/action-menu/control-modes.tsx +15 -42
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
- package/src/components/ui/action-menu/index.tsx +56 -33
- package/src/components/ui/action-menu/measurement-control.tsx +14 -19
- package/src/components/ui/action-menu/placement.ts +20 -0
- package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
- package/src/components/ui/action-menu/view-toggles.tsx +47 -42
- package/src/components/ui/build-panel/build-panel.tsx +242 -0
- package/src/components/ui/command-palette/editor-commands.tsx +7 -42
- package/src/components/ui/command-palette/index.tsx +50 -39
- package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
- package/src/components/ui/controls/action-button.tsx +5 -2
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/metric-control.tsx +1 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/controls/slider-control.tsx +1 -1
- package/src/components/ui/controls/toggle-control.tsx +3 -3
- package/src/components/ui/controls/tool-options-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +159 -59
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
- package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
- package/src/components/ui/helpers/helper-manager.tsx +331 -14
- package/src/components/ui/helpers/item-helper.tsx +17 -1
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
- 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 +4 -4
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
- 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 +130 -62
- package/src/components/ui/panels/parametric-field-control.tsx +3 -3
- package/src/components/ui/panels/parametric-inspector.tsx +7 -7
- 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 +790 -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/badge.tsx +8 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +3 -1
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +164 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +8 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
- package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
- 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 +181 -223
- 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 +13 -37
- 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 +2 -4
- package/src/components/ui/sidebar/tab-bar.tsx +14 -4
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +191 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +215 -43
- package/src/hooks/use-auto-save.ts +60 -9
- package/src/hooks/use-ceiling-events.test.ts +123 -118
- package/src/hooks/use-grid-events.test.ts +105 -105
- package/src/hooks/use-keyboard.test.ts +243 -19
- package/src/hooks/use-keyboard.ts +118 -83
- 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 +71 -0
- package/src/hooks/use-placement-notice.ts +73 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +312 -58
- package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
- package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
- package/src/lib/add-storey.test.ts +177 -0
- package/src/lib/add-storey.ts +59 -0
- package/src/lib/below-grade.test.tsx +171 -0
- package/src/lib/below-grade.ts +133 -0
- package/src/lib/camera-facing.test.ts +24 -0
- package/src/lib/camera-facing.ts +24 -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/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/direct-manipulation.ts +2 -0
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +55 -0
- package/src/lib/editor-views.ts +178 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/export-texture-utils.ts +2 -4
- 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 +284 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -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/pdfkit-standard-fonts.d.ts +5 -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 +124 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -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 +230 -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 +157 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
- package/src/lib/floorplan/site-plan/geometry.ts +142 -0
- package/src/lib/floorplan/site-plan/index.ts +55 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +287 -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 +70 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
- package/src/lib/follow-pascal-exterior.test.ts +171 -0
- package/src/lib/follow-pascal-exterior.ts +39 -0
- package/src/lib/follow-pascal-presentation.test.ts +203 -0
- package/src/lib/follow-pascal-presentation.ts +129 -0
- package/src/lib/follow-pascal-sight.ts +84 -0
- package/src/lib/follow-pascal-targets.test.ts +406 -0
- package/src/lib/follow-pascal-targets.ts +198 -0
- package/src/lib/follow-pascal.test.ts +350 -0
- package/src/lib/follow-pascal.ts +284 -0
- package/src/lib/fresh-planar-placement.test.ts +148 -0
- package/src/lib/fresh-planar-placement.ts +114 -37
- package/src/lib/geometry-script/author.ts +157 -0
- package/src/lib/geometry-script/client.ts +159 -0
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +13 -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 +636 -126
- package/src/lib/glb-export.ts +241 -120
- package/src/lib/history.test.ts +18 -7
- 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 +22 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- 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 +187 -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 +392 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -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 +201 -0
- package/src/lib/orbit-floor-clearance.ts +154 -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 +122 -0
- package/src/lib/paint-pass-through.ts +78 -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 +267 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +16 -7
- 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 +65 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
- package/src/lib/point-ask/__fixtures__/house.ts +81 -0
- package/src/lib/point-ask/ask-created.test.ts +195 -0
- package/src/lib/point-ask/ask-flow.test.ts +380 -0
- package/src/lib/point-ask/build-context.test.ts +173 -0
- package/src/lib/point-ask/build-context.ts +94 -0
- package/src/lib/point-ask/capture-crop.ts +129 -0
- package/src/lib/point-ask/choreography.ts +69 -0
- package/src/lib/point-ask/compare.test.ts +145 -0
- package/src/lib/point-ask/compare.ts +73 -0
- package/src/lib/point-ask/describe-target.test.ts +165 -0
- package/src/lib/point-ask/describe-target.ts +234 -0
- package/src/lib/point-ask/dictation.ts +79 -0
- package/src/lib/point-ask/edge-marker.test.ts +61 -0
- package/src/lib/point-ask/edge-marker.ts +27 -0
- package/src/lib/point-ask/frame-loop.ts +25 -0
- package/src/lib/point-ask/history-marks.test.ts +94 -0
- package/src/lib/point-ask/history-marks.ts +39 -0
- package/src/lib/point-ask/host-api.ts +32 -0
- package/src/lib/point-ask/outline-box.test.ts +30 -0
- package/src/lib/point-ask/outline-box.ts +11 -0
- package/src/lib/point-ask/overlay-metrics.ts +6 -0
- package/src/lib/point-ask/pick.test.ts +220 -0
- package/src/lib/point-ask/pick.ts +121 -0
- package/src/lib/point-ask/pin-machine.test.ts +494 -0
- package/src/lib/point-ask/pin-machine.ts +217 -0
- package/src/lib/point-ask/placement.test.ts +203 -0
- package/src/lib/point-ask/placement.ts +141 -0
- package/src/lib/point-ask/pointer-tracker.ts +34 -0
- package/src/lib/point-ask/projector.ts +83 -0
- package/src/lib/point-ask/region.test.ts +157 -0
- package/src/lib/point-ask/region.ts +136 -0
- package/src/lib/point-ask/retry.test.ts +217 -0
- package/src/lib/point-ask/retry.ts +37 -0
- package/src/lib/point-ask/scene-commit.test.ts +312 -0
- package/src/lib/point-ask/scene-commit.ts +32 -0
- package/src/lib/point-ask/session.ts +1391 -0
- package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
- package/src/lib/point-ask/spring-keyframes.ts +38 -0
- package/src/lib/point-ask/spring.test.ts +63 -0
- package/src/lib/point-ask/spring.ts +107 -0
- package/src/lib/point-ask/suggestions.test.ts +80 -0
- package/src/lib/point-ask/suggestions.ts +125 -0
- package/src/lib/point-ask/target-scope.test.ts +622 -0
- package/src/lib/point-ask/target-scope.ts +338 -0
- package/src/lib/point-ask/types.ts +48 -0
- package/src/lib/portable-export.test.ts +34 -1
- package/src/lib/portable-export.ts +119 -53
- package/src/lib/print-feature-thickness.ts +10 -1
- package/src/lib/print-roof-solids.test.ts +39 -1
- package/src/lib/print-roof-solids.ts +43 -10
- package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
- 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 +265 -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 +1017 -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 +610 -1
- package/src/lib/scene-clipboard.ts +269 -58
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +54 -75
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sfx-bus.ts +13 -0
- package/src/lib/sfx-player.test.ts +70 -9
- package/src/lib/sfx-player.ts +6 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +214 -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 +580 -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-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -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-changes.ts +35 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-audio.test.ts +146 -0
- package/src/store/use-audio.tsx +127 -3
- package/src/store/use-construction-reveal.test.ts +173 -0
- package/src/store/use-construction-reveal.ts +145 -0
- package/src/store/use-corner-snap-hint.test.ts +24 -0
- package/src/store/use-corner-snap-hint.ts +47 -0
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-follow-pascal.test.ts +62 -0
- package/src/store/use-follow-pascal.ts +53 -0
- package/src/store/use-point-ask.ts +247 -0
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +33 -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 +201 -0
- package/src/store/view-layout.ts +199 -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/render-current-markup.tsx +16 -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/tools/stair/stair-click-guard.test.ts +0 -58
- package/src/components/tools/stair/stair-click-guard.ts +0 -72
- package/src/components/tools/stair/stair-defaults.ts +0 -18
- package/src/components/tools/stair/stair-tool.tsx +0 -634
- 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
|
@@ -5,10 +5,12 @@ import {
|
|
|
5
5
|
type AnyNode,
|
|
6
6
|
type AnyNodeId,
|
|
7
7
|
type BuildingNode,
|
|
8
|
+
type CameraControlFrameEvent,
|
|
8
9
|
type CeilingNode,
|
|
9
10
|
CeilingNode as CeilingNodeSchema,
|
|
10
11
|
type ColumnNode,
|
|
11
12
|
calculateLevelMiters,
|
|
13
|
+
createSizedStairFlight,
|
|
12
14
|
DEFAULT_ANGLE_STEP,
|
|
13
15
|
type DoorNode,
|
|
14
16
|
DoorNode as DoorNodeSchema,
|
|
@@ -17,7 +19,6 @@ import {
|
|
|
17
19
|
type FenceNode,
|
|
18
20
|
type FloorplanGeometry,
|
|
19
21
|
type GeometryContext,
|
|
20
|
-
type GridEvent,
|
|
21
22
|
type GuideNode,
|
|
22
23
|
getRenderableSlabPolygon,
|
|
23
24
|
getWallChordFrame,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
isCurvedWall,
|
|
28
29
|
type LevelNode,
|
|
29
30
|
loadAssetUrl,
|
|
31
|
+
newZone,
|
|
30
32
|
nodeRegistry,
|
|
31
33
|
normalizeWallCurveOffset,
|
|
32
34
|
type Point2D,
|
|
@@ -40,7 +42,6 @@ import {
|
|
|
40
42
|
SlabNode as SlabNodeSchema,
|
|
41
43
|
type StairNode,
|
|
42
44
|
StairNode as StairNodeSchema,
|
|
43
|
-
StairSegmentNode as StairSegmentNodeSchema,
|
|
44
45
|
sampleWallCenterline,
|
|
45
46
|
sceneRegistry,
|
|
46
47
|
snapPointAlongAngleRay,
|
|
@@ -56,14 +57,13 @@ import {
|
|
|
56
57
|
WindowNode as WindowNodeSchema,
|
|
57
58
|
wallClosesRoom,
|
|
58
59
|
wallRectangleCorners,
|
|
59
|
-
ZoneNode as ZoneNodeSchema,
|
|
60
|
-
type ZoneNode as ZoneNodeType,
|
|
61
60
|
} from '@pascal-app/core'
|
|
62
61
|
import { useSegmentDraftChain, useWallSnapIndicator } from '@pascal-app/editor'
|
|
63
62
|
import { getSceneTheme, useViewer } from '@pascal-app/viewer'
|
|
64
63
|
import { Command, Ruler } from 'lucide-react'
|
|
65
64
|
import {
|
|
66
65
|
type ComponentProps,
|
|
66
|
+
type CSSProperties,
|
|
67
67
|
memo,
|
|
68
68
|
type MouseEvent as ReactMouseEvent,
|
|
69
69
|
type ReactNode,
|
|
@@ -78,11 +78,15 @@ import {
|
|
|
78
78
|
import { createPortal } from 'react-dom'
|
|
79
79
|
import { Vector3 } from 'three'
|
|
80
80
|
import { useShallow } from 'zustand/react/shallow'
|
|
81
|
+
import {
|
|
82
|
+
hoverRoomFromHit,
|
|
83
|
+
resolvePlanRoomHit,
|
|
84
|
+
roomPickingEnabled,
|
|
85
|
+
} from '../../hooks/use-selected-room'
|
|
81
86
|
import { resolveCeilingPlanPointSnap } from '../../lib/ceiling-plan-snap'
|
|
82
87
|
import {
|
|
83
88
|
alignFloorplanDraftPoint,
|
|
84
89
|
buildFloorplanItemEntry,
|
|
85
|
-
buildFloorplanStairEntry as buildSharedFloorplanStairEntry,
|
|
86
90
|
collectLevelDescendants,
|
|
87
91
|
FLOORPLAN_VIEW_ROTATION_DEG,
|
|
88
92
|
floorplanLocalToWorldPoint,
|
|
@@ -91,29 +95,59 @@ import {
|
|
|
91
95
|
type FloorplanNodeTransform as SharedFloorplanNodeTransform,
|
|
92
96
|
worldToFloorplanLocalPoint,
|
|
93
97
|
} from '../../lib/floorplan'
|
|
98
|
+
import { buildFloorplanContext } from '../../lib/floorplan/floorplan-readonly'
|
|
99
|
+
// Site-plan view. Owned under `lib/floorplan/site-plan/**`; this panel
|
|
100
|
+
// only chooses between it and the registry layer, and mounts the switch.
|
|
101
|
+
import {
|
|
102
|
+
FloorplanDrawingTypeSwitch,
|
|
103
|
+
useSitePlanAvailable,
|
|
104
|
+
} from '../../lib/floorplan/site-plan/drawing-type-switch'
|
|
105
|
+
import { FloorplanSitePlanLayer } from '../../lib/floorplan/site-plan/site-plan-layer'
|
|
94
106
|
import { resolveGenericFloorplanGridEventPoint } from '../../lib/floorplan-grid-event-point'
|
|
95
107
|
import type { EditorGridEvent } from '../../lib/grid-event-presentation'
|
|
96
108
|
import { groundHeightAt } from '../../lib/ground-surface'
|
|
97
109
|
import { guideEmitter } from '../../lib/guide-events'
|
|
98
110
|
import {
|
|
99
111
|
acceptsKeyboardPan,
|
|
112
|
+
advanceKeyboardPanMotion,
|
|
100
113
|
clearKeyboardPanKeys,
|
|
114
|
+
createKeyboardPanMotion,
|
|
101
115
|
isKeyboardPanKey,
|
|
102
116
|
type KeyboardPanState,
|
|
103
|
-
keyboardPanDirection,
|
|
104
117
|
keyboardPanSpeed,
|
|
118
|
+
resetKeyboardPanMotion,
|
|
105
119
|
setKeyboardPanKey,
|
|
120
|
+
syncKeyboardPanMotion,
|
|
106
121
|
} from '../../lib/keyboard-pan'
|
|
107
122
|
import { measurementHint, parseMeasurement } from '../../lib/measurement-parser'
|
|
108
123
|
import { formatLinearMeasurement, linearUnitToMeters } from '../../lib/measurements'
|
|
124
|
+
import { snapRegisteredDraftPoint } from '../../lib/registered-draft-snap'
|
|
125
|
+
import { selectRoomFromHit } from '../../lib/room-selection-commands'
|
|
126
|
+
import { enterBuildingFromPlanHit } from '../../lib/selection-routing'
|
|
109
127
|
import { sfxEmitter } from '../../lib/sfx-bus'
|
|
110
128
|
import { SITE_BOUNDARY_DRAG_LABEL, siteBoundaryHandlesEnabled } from '../../lib/site-boundary'
|
|
111
129
|
import { resolveSlabPlanPointSnap } from '../../lib/slab-plan-snap'
|
|
112
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
cancelPendingZonePaint,
|
|
132
|
+
focusedUnitNode,
|
|
133
|
+
paintZoneMembership,
|
|
134
|
+
zoneAtLevelPoint,
|
|
135
|
+
} from '../../lib/units'
|
|
113
136
|
import { cn } from '../../lib/utils'
|
|
137
|
+
import { framingAnimates } from '../../lib/viewer-selection'
|
|
138
|
+
import { getWallDrawVariant } from '../../lib/wall-draw-variant'
|
|
139
|
+
import {
|
|
140
|
+
addWallPolygonDraftCorner,
|
|
141
|
+
commitWallPolygonDraft,
|
|
142
|
+
discardWallPolygonDraft,
|
|
143
|
+
startWallPolygonDraft,
|
|
144
|
+
wallPolygonDraftWalls,
|
|
145
|
+
} from '../../lib/wall-polygon-draft'
|
|
114
146
|
import { snapBuildingLocalToWorldGrid } from '../../lib/world-grid-snap'
|
|
147
|
+
import { nextZoneName } from '../../lib/zone-name'
|
|
115
148
|
import { subscribeNavigationSyncPose } from '../../store/navigation-sync-pose-store'
|
|
116
149
|
import useAlignmentGuides from '../../store/use-alignment-guides'
|
|
150
|
+
import useDrawingView from '../../store/use-drawing-view'
|
|
117
151
|
import type { GuideUiState, NavigationSyncPose } from '../../store/use-editor'
|
|
118
152
|
import useEditor, {
|
|
119
153
|
isAngleSnapActive,
|
|
@@ -132,11 +166,19 @@ import useInteractionScope, {
|
|
|
132
166
|
import usePlacementPreview from '../../store/use-placement-preview'
|
|
133
167
|
import { expandSessionSelectionForNode } from '../../store/use-session-groups'
|
|
134
168
|
import { useStairBuildPreview } from '../../store/use-stair-build-preview'
|
|
169
|
+
import {
|
|
170
|
+
activeViewLayout,
|
|
171
|
+
isViewVisible,
|
|
172
|
+
VIEW_2D,
|
|
173
|
+
VIEW_3D,
|
|
174
|
+
visibleScene,
|
|
175
|
+
} from '../../store/view-layout'
|
|
135
176
|
import { FloorplanAlignmentGuideLayer } from '../editor-2d/floorplan-alignment-guide-layer'
|
|
136
177
|
import { FloorplanCursorIndicatorOverlay as Editor2dFloorplanCursorIndicatorOverlay } from '../editor-2d/floorplan-cursor-indicator-overlay'
|
|
137
178
|
import { FloorplanGroupActionMenu } from '../editor-2d/floorplan-group-action-menu'
|
|
138
179
|
import { FloorplanSiteKeyHandler } from '../editor-2d/floorplan-hotkey-handlers'
|
|
139
180
|
import { FloorplanMeasurementToolLayer } from '../editor-2d/floorplan-measurement-tool-layer'
|
|
181
|
+
import { FloorplanOpenWallEndsLayer } from '../editor-2d/floorplan-open-wall-ends-layer'
|
|
140
182
|
import { FloorplanRegisteredToolLayer } from '../editor-2d/floorplan-registered-tool-layer'
|
|
141
183
|
import { FloorplanRegistryActionMenu } from '../editor-2d/floorplan-registry-action-menu'
|
|
142
184
|
import { FloorplanRegistryMoveOverlay } from '../editor-2d/floorplan-registry-move-overlay'
|
|
@@ -147,7 +189,7 @@ import {
|
|
|
147
189
|
import { FloorplanSnapBeaconLayer } from '../editor-2d/floorplan-snap-beacon-layer'
|
|
148
190
|
import { FloorplanWallMoveGhostLayer } from '../editor-2d/floorplan-wall-move-ghost-layer'
|
|
149
191
|
import { FloorplanDraftLayer } from '../editor-2d/renderers/floorplan-draft-layer'
|
|
150
|
-
import {
|
|
192
|
+
import { FloorplanDraftMeasurement } from '../editor-2d/renderers/floorplan-draft-measurement'
|
|
151
193
|
import { FloorplanGeometryRenderer } from '../editor-2d/renderers/floorplan-geometry-renderer'
|
|
152
194
|
import { FloorplanMarqueeLayer } from '../editor-2d/renderers/floorplan-marquee-layer'
|
|
153
195
|
import { FloorplanPlacementPreviewLayer } from '../editor-2d/renderers/floorplan-placement-preview-layer'
|
|
@@ -155,10 +197,8 @@ import {
|
|
|
155
197
|
FloorplanRegistryLayer,
|
|
156
198
|
RotationAngleOverlay,
|
|
157
199
|
} from '../editor-2d/renderers/floorplan-registry-layer'
|
|
158
|
-
import { FloorplanStairLayer } from '../editor-2d/renderers/floorplan-stair-layer'
|
|
159
200
|
import { FloorplanVoronoiLayer } from '../editor-2d/renderers/floorplan-voronoi-layer'
|
|
160
|
-
import { buildSvgPolylinePath, formatPolygonPath
|
|
161
|
-
import { snapFenceDraftPoint } from '../tools/fence/fence-drafting'
|
|
201
|
+
import { buildSvgPolylinePath, formatPolygonPath } from '../editor-2d/svg-paths'
|
|
162
202
|
import { snapToHalf } from '../tools/item/placement-math'
|
|
163
203
|
import {
|
|
164
204
|
isBoxSelectPointerSuppressed,
|
|
@@ -188,15 +228,6 @@ import {
|
|
|
188
228
|
getAngleToSegmentReference,
|
|
189
229
|
getSegmentAngleReferenceAtPoint,
|
|
190
230
|
} from '../tools/shared/segment-angle'
|
|
191
|
-
import {
|
|
192
|
-
DEFAULT_STAIR_ATTACHMENT_SIDE,
|
|
193
|
-
DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
194
|
-
DEFAULT_STAIR_HEIGHT,
|
|
195
|
-
DEFAULT_STAIR_LENGTH,
|
|
196
|
-
DEFAULT_STAIR_STEP_COUNT,
|
|
197
|
-
DEFAULT_STAIR_THICKNESS,
|
|
198
|
-
DEFAULT_STAIR_WIDTH,
|
|
199
|
-
} from '../tools/stair/stair-defaults'
|
|
200
231
|
import {
|
|
201
232
|
chainEndJoinsExistingWall,
|
|
202
233
|
createWallOnCurrentLevel,
|
|
@@ -208,8 +239,6 @@ import {
|
|
|
208
239
|
WALL_JOIN_SNAP_RADIUS,
|
|
209
240
|
type WallPlanPoint,
|
|
210
241
|
} from '../tools/wall/wall-drafting'
|
|
211
|
-
|
|
212
|
-
import { PALETTE_COLORS } from '../ui/primitives/color-dot'
|
|
213
242
|
import { FloorplanCompassButton } from '../viewer/floorplan-compass-button'
|
|
214
243
|
import { resolveFloorplanBackgroundSelection } from './floorplan-background-selection'
|
|
215
244
|
import {
|
|
@@ -232,6 +261,7 @@ import {
|
|
|
232
261
|
import { useFloorplanBackgroundPlacement } from './use-floorplan-background-placement'
|
|
233
262
|
import { useFloorplanHitTesting } from './use-floorplan-hit-testing'
|
|
234
263
|
import { useFloorplanSceneData } from './use-floorplan-scene-data'
|
|
264
|
+
import { sceneSpan } from './views/scene-region'
|
|
235
265
|
|
|
236
266
|
const FALLBACK_VIEW_SIZE = 12
|
|
237
267
|
const FLOORPLAN_PADDING = 2
|
|
@@ -282,7 +312,6 @@ const FLOORPLAN_HOVER_TRANSITION = 'opacity 180ms cubic-bezier(0.2, 0, 0, 1)'
|
|
|
282
312
|
const FLOORPLAN_WALL_HIT_STROKE_WIDTH = 18
|
|
283
313
|
const FLOORPLAN_WALL_STROKE_WIDTH = '1'
|
|
284
314
|
const FLOORPLAN_OPENING_HIT_STROKE_WIDTH = 16
|
|
285
|
-
const noopFloorplanStairHandler = () => {}
|
|
286
315
|
const FLOORPLAN_OPENING_STROKE_WIDTH = 0.05
|
|
287
316
|
const FLOORPLAN_ENDPOINT_HIT_STROKE_WIDTH = 18
|
|
288
317
|
const FLOORPLAN_ENDPOINT_HOVER_GLOW_STROKE_WIDTH = 16
|
|
@@ -2124,68 +2153,6 @@ function movePlanPointTowards(start: Point2D, end: Point2D, distance: number): P
|
|
|
2124
2153
|
return interpolatePlanPoint(start, end, Math.min(1, distance / totalDistance))
|
|
2125
2154
|
}
|
|
2126
2155
|
|
|
2127
|
-
function getNormalizedFloorplanStairSweepAngle(stair: StairNode) {
|
|
2128
|
-
const stairType = stair.stairType ?? 'straight'
|
|
2129
|
-
const baseSweepAngle = stair.sweepAngle ?? (stairType === 'spiral' ? Math.PI * 2 : Math.PI / 2)
|
|
2130
|
-
|
|
2131
|
-
if (Math.abs(baseSweepAngle) >= Math.PI * 2) {
|
|
2132
|
-
return Math.sign(baseSweepAngle || 1) * (Math.PI * 2 - 0.001)
|
|
2133
|
-
}
|
|
2134
|
-
|
|
2135
|
-
return baseSweepAngle
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
function getFloorplanSpiralLandingSweep(stair: StairNode, sweepAngle: number) {
|
|
2139
|
-
if (
|
|
2140
|
-
(stair.stairType ?? 'straight') !== 'spiral' ||
|
|
2141
|
-
(stair.topLandingMode ?? 'none') !== 'integrated'
|
|
2142
|
-
) {
|
|
2143
|
-
return 0
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
const innerRadius = Math.max(0.05, stair.innerRadius ?? 0.9)
|
|
2147
|
-
const width = Math.max(stair.width ?? 1, 0.4)
|
|
2148
|
-
const landingDepth = Math.max(0.3, stair.topLandingDepth ?? Math.max(width * 0.9, 0.8))
|
|
2149
|
-
|
|
2150
|
-
return (
|
|
2151
|
-
Math.min(Math.PI * 0.75, landingDepth / Math.max(innerRadius + width / 2, 0.1)) *
|
|
2152
|
-
Math.sign(sweepAngle || 1)
|
|
2153
|
-
)
|
|
2154
|
-
}
|
|
2155
|
-
|
|
2156
|
-
function getFloorplanCurvedStairHitPolygon(stair: StairNode): Point2D[] {
|
|
2157
|
-
const stairType = stair.stairType ?? 'straight'
|
|
2158
|
-
const sweepAngle = getNormalizedFloorplanStairSweepAngle(stair)
|
|
2159
|
-
const startAngle = -stair.rotation - sweepAngle / 2
|
|
2160
|
-
const endAngle = startAngle + sweepAngle + getFloorplanSpiralLandingSweep(stair, sweepAngle)
|
|
2161
|
-
const center = {
|
|
2162
|
-
x: stair.position[0],
|
|
2163
|
-
y: stair.position[2],
|
|
2164
|
-
}
|
|
2165
|
-
const innerRadius = Math.max(
|
|
2166
|
-
stairType === 'spiral' ? 0.05 : 0.2,
|
|
2167
|
-
stair.innerRadius ?? (stairType === 'spiral' ? 0.2 : 0.9),
|
|
2168
|
-
)
|
|
2169
|
-
const outerRadius = innerRadius + stair.width
|
|
2170
|
-
const outerArcLength = Math.abs(sweepAngle) * outerRadius
|
|
2171
|
-
const segmentCount = Math.max(
|
|
2172
|
-
24,
|
|
2173
|
-
Math.ceil(Math.abs(sweepAngle) / (Math.PI / 24)),
|
|
2174
|
-
Math.ceil(outerArcLength / 0.14),
|
|
2175
|
-
)
|
|
2176
|
-
const outerPoints: Point2D[] = []
|
|
2177
|
-
const innerPoints: Point2D[] = []
|
|
2178
|
-
|
|
2179
|
-
for (let index = 0; index <= segmentCount; index += 1) {
|
|
2180
|
-
const t = index / segmentCount
|
|
2181
|
-
const angle = startAngle + (endAngle - startAngle) * t
|
|
2182
|
-
outerPoints.push(getArcPlanPoint(center, outerRadius, angle))
|
|
2183
|
-
innerPoints.push(getArcPlanPoint(center, innerRadius, angle))
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
return [...outerPoints, ...innerPoints.reverse()]
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
2156
|
function isPointInsidePolygonWithHoles(
|
|
2190
2157
|
point: Point2D,
|
|
2191
2158
|
polygon: Point2D[],
|
|
@@ -4226,107 +4193,57 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({
|
|
|
4226
4193
|
)
|
|
4227
4194
|
})
|
|
4228
4195
|
|
|
4229
|
-
// Static segment for the in-flight stair build preview. No per-render
|
|
4230
|
-
// dependency (the geometry only moves / rotates), so it lives at module scope
|
|
4231
|
-
// instead of a `useMemo`.
|
|
4232
|
-
const FLOORPLAN_PREVIEW_STAIR_SEGMENT = StairSegmentNodeSchema.parse({
|
|
4233
|
-
id: 'sseg_floorplan_preview',
|
|
4234
|
-
segmentType: 'stair',
|
|
4235
|
-
width: DEFAULT_STAIR_WIDTH,
|
|
4236
|
-
length: DEFAULT_STAIR_LENGTH,
|
|
4237
|
-
height: DEFAULT_STAIR_HEIGHT,
|
|
4238
|
-
stepCount: DEFAULT_STAIR_STEP_COUNT,
|
|
4239
|
-
attachmentSide: DEFAULT_STAIR_ATTACHMENT_SIDE,
|
|
4240
|
-
fillToFloor: DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
4241
|
-
thickness: DEFAULT_STAIR_THICKNESS,
|
|
4242
|
-
position: [0, 0, 0],
|
|
4243
|
-
metadata: { isTransient: true, isFloorplanPreview: true },
|
|
4244
|
-
})
|
|
4245
|
-
|
|
4246
|
-
const EMPTY_FLOORPLAN_ID_SET: ReadonlySet<string> = new Set()
|
|
4247
|
-
|
|
4248
|
-
type FloorplanStairLayerPalette = ComponentProps<typeof FloorplanStairLayer>['palette']
|
|
4249
|
-
|
|
4250
|
-
// Leaf layer for the stair tool's in-flight 2D build preview. Subscribes to the
|
|
4251
|
-
// `useStairBuildPreview` store directly so a per-`grid:move` point update (or an
|
|
4252
|
-
// R/T rotation) re-renders ONLY this tiny layer — never the ~120-220ms
|
|
4253
|
-
// `FloorplanPanel`. This mirrors how column / elevator placement stays smooth by
|
|
4254
|
-
// routing preview state through a store + leaf. Committed stairs render through
|
|
4255
|
-
// `FloorplanRegistryLayer`; this layer is non-interactive (noop handlers, empty
|
|
4256
|
-
// hit sets), so it never participates in hover / select.
|
|
4257
4196
|
function FloorplanStairBuildPreviewLayer({
|
|
4258
4197
|
palette,
|
|
4259
|
-
isDeleteMode,
|
|
4260
4198
|
}: {
|
|
4261
|
-
palette:
|
|
4262
|
-
isDeleteMode: boolean
|
|
4199
|
+
palette: FloorplanRenderContextValue['palette']
|
|
4263
4200
|
}) {
|
|
4264
|
-
const phase = useEditor((s) => s.phase)
|
|
4265
4201
|
const mode = useEditor((s) => s.mode)
|
|
4266
4202
|
const tool = useEditor((s) => s.tool)
|
|
4267
4203
|
const point = useStairBuildPreview((s) => s.point)
|
|
4268
4204
|
const rotation = useStairBuildPreview((s) => s.rotation)
|
|
4269
|
-
const
|
|
4205
|
+
const rise = useStairBuildPreview((s) => s.rise)
|
|
4206
|
+
const length = useStairBuildPreview((s) => s.length)
|
|
4207
|
+
const stepCount = useStairBuildPreview((s) => s.stepCount)
|
|
4208
|
+
const isActive = mode === 'build' && tool === 'stair'
|
|
4270
4209
|
|
|
4271
|
-
const
|
|
4272
|
-
if (!(isActive && point)) {
|
|
4210
|
+
const previewGeometry = useMemo(() => {
|
|
4211
|
+
if (!(isActive && point && rise && rise > 0)) {
|
|
4273
4212
|
return null
|
|
4274
4213
|
}
|
|
4214
|
+
const previewSegment = {
|
|
4215
|
+
...createSizedStairFlight(rise),
|
|
4216
|
+
...(length !== null ? { length } : {}),
|
|
4217
|
+
...(stepCount !== null ? { stepCount } : {}),
|
|
4218
|
+
id: 'sseg_floorplan_preview' as const,
|
|
4219
|
+
parentId: 'stair_floorplan_preview' as const,
|
|
4220
|
+
}
|
|
4275
4221
|
const previewStair = StairNodeSchema.parse({
|
|
4276
4222
|
id: 'stair_floorplan_preview',
|
|
4277
4223
|
name: 'Staircase preview',
|
|
4278
4224
|
position: [point[0], 0, point[1]],
|
|
4279
4225
|
rotation,
|
|
4280
|
-
children: [
|
|
4226
|
+
children: [previewSegment.id],
|
|
4281
4227
|
metadata: { isTransient: true, isFloorplanPreview: true },
|
|
4282
4228
|
})
|
|
4283
|
-
const
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
points: formatPolygonPoints(segmentEntry.polygon),
|
|
4299
|
-
treadBars: segmentEntry.treadBars.map((polygon) => ({
|
|
4300
|
-
points: formatPolygonPoints(polygon),
|
|
4301
|
-
polygon,
|
|
4302
|
-
})),
|
|
4303
|
-
})),
|
|
4304
|
-
}
|
|
4305
|
-
}, [isActive, point, rotation])
|
|
4306
|
-
|
|
4307
|
-
if (!previewEntry) {
|
|
4308
|
-
return null
|
|
4309
|
-
}
|
|
4229
|
+
const nodes = { [previewStair.id]: previewStair, [previewSegment.id]: previewSegment }
|
|
4230
|
+
return (
|
|
4231
|
+
nodeRegistry.get(previewStair.type)?.floorplan?.(
|
|
4232
|
+
previewStair,
|
|
4233
|
+
buildFloorplanContext(previewStair, nodes, {
|
|
4234
|
+
selected: false,
|
|
4235
|
+
highlighted: false,
|
|
4236
|
+
hovered: false,
|
|
4237
|
+
moving: true,
|
|
4238
|
+
unit: 'metric',
|
|
4239
|
+
palette,
|
|
4240
|
+
}),
|
|
4241
|
+
) ?? null
|
|
4242
|
+
)
|
|
4243
|
+
}, [isActive, point, rotation, rise, length, stepCount, palette])
|
|
4310
4244
|
|
|
4311
|
-
return
|
|
4312
|
-
|
|
4313
|
-
canFocusStairs={false}
|
|
4314
|
-
canSelectStairs={false}
|
|
4315
|
-
cursor={EDITOR_CURSOR}
|
|
4316
|
-
highlightedIdSet={EMPTY_FLOORPLAN_ID_SET}
|
|
4317
|
-
hitStrokeWidth={FLOORPLAN_OPENING_HIT_STROKE_WIDTH}
|
|
4318
|
-
hoveredStairId={null}
|
|
4319
|
-
isDeleteMode={isDeleteMode}
|
|
4320
|
-
onStairDoubleClick={noopFloorplanStairHandler}
|
|
4321
|
-
onStairHoverChange={noopFloorplanStairHandler}
|
|
4322
|
-
onStairHoverEnter={noopFloorplanStairHandler}
|
|
4323
|
-
onStairPointerDown={noopFloorplanStairHandler}
|
|
4324
|
-
onStairSelect={noopFloorplanStairHandler}
|
|
4325
|
-
palette={palette}
|
|
4326
|
-
selectedIdSet={EMPTY_FLOORPLAN_ID_SET}
|
|
4327
|
-
stairEntries={[previewEntry]}
|
|
4328
|
-
/>
|
|
4329
|
-
)
|
|
4245
|
+
if (!previewGeometry) return null
|
|
4246
|
+
return <FloorplanGeometryRenderer geometry={previewGeometry} pointerEventsOverride="none" />
|
|
4330
4247
|
}
|
|
4331
4248
|
|
|
4332
4249
|
// Leaf overlay for the cursor-following draft preview: the cursor crosshair plus
|
|
@@ -4530,6 +4447,7 @@ function FloorplanLinearDraftLayer({
|
|
|
4530
4447
|
const roofDraftEnd = useFloorplanDraftPreview((s) => s.roofDraftEnd)
|
|
4531
4448
|
const roofDraftQuarterTurn = useFloorplanDraftPreview((s) => s.roofDraftQuarterTurn)
|
|
4532
4449
|
const wallRectangleDraftStart = useFloorplanDraftPreview((s) => s.wallRectangleDraftStart)
|
|
4450
|
+
const wallPolygonDraftPoints = useFloorplanDraftPreview((s) => s.wallPolygonDraftPoints)
|
|
4533
4451
|
// The live cursor is the rectangle's opposite corner; select it only while a
|
|
4534
4452
|
// rectangle draft is open so idle moves don't re-render this layer.
|
|
4535
4453
|
const wallRectangleDraftEnd = useFloorplanDraftPreview((s) =>
|
|
@@ -4599,6 +4517,20 @@ function FloorplanLinearDraftLayer({
|
|
|
4599
4517
|
wallRectangleDraftStart,
|
|
4600
4518
|
])
|
|
4601
4519
|
|
|
4520
|
+
// An open Polygon room's placed sides — draft state, drawn like the
|
|
4521
|
+
// rectangle draft (mitered together) until the polygon is written.
|
|
4522
|
+
const polygonRoomDraft = useMemo(() => {
|
|
4523
|
+
if (!(levelId && isWallBuildActive && wallPolygonDraftPoints.length >= 2)) return null
|
|
4524
|
+
const draftWalls = wallPolygonDraftPoints.slice(1).map((end, index) =>
|
|
4525
|
+
getSharedFloorplanWall({
|
|
4526
|
+
...buildDraftWall(levelId, wallPolygonDraftPoints[index]!, end),
|
|
4527
|
+
id: `wall_polygon_draft_${index}` as WallNode['id'],
|
|
4528
|
+
}),
|
|
4529
|
+
)
|
|
4530
|
+
const miterData = calculateLevelMiters(draftWalls)
|
|
4531
|
+
return draftWalls.map((wall) => formatPolygonPoints(getWallPlanFootprint(wall, miterData)))
|
|
4532
|
+
}, [isWallBuildActive, levelId, wallPolygonDraftPoints])
|
|
4533
|
+
|
|
4602
4534
|
const draftPolygonPoints = useMemo(() => {
|
|
4603
4535
|
if (isRoofBuildActive && roofDraftStart && roofDraftEnd) {
|
|
4604
4536
|
const minX = Math.min(roofDraftStart[0], roofDraftEnd[0])
|
|
@@ -4831,7 +4763,7 @@ function FloorplanLinearDraftLayer({
|
|
|
4831
4763
|
)}
|
|
4832
4764
|
|
|
4833
4765
|
{draftWallMeasurement && (
|
|
4834
|
-
<
|
|
4766
|
+
<FloorplanDraftMeasurement
|
|
4835
4767
|
labelBackground={isDark ? '#0f172a' : '#ffffff'}
|
|
4836
4768
|
labelText={isDark ? '#e2e8f0' : '#171717'}
|
|
4837
4769
|
measurement={draftWallMeasurement}
|
|
@@ -4841,6 +4773,22 @@ function FloorplanLinearDraftLayer({
|
|
|
4841
4773
|
/>
|
|
4842
4774
|
)}
|
|
4843
4775
|
|
|
4776
|
+
{polygonRoomDraft?.map((points, index) => (
|
|
4777
|
+
<FloorplanDraftLayer
|
|
4778
|
+
anchorFill={draftStroke}
|
|
4779
|
+
draftAnchorPoints={EMPTY_DRAFT_ANCHOR_POINTS}
|
|
4780
|
+
draftFill={draftFill}
|
|
4781
|
+
draftPolygonPoints={points}
|
|
4782
|
+
draftStroke={draftStroke}
|
|
4783
|
+
key={`polygon-room-${index}`}
|
|
4784
|
+
linearDraftSegment={null}
|
|
4785
|
+
polygonDraftClosingSegment={null}
|
|
4786
|
+
polygonDraftPolygonPoints={null}
|
|
4787
|
+
polygonDraftPolylinePoints={null}
|
|
4788
|
+
unitsPerPixel={unitsPerPixel}
|
|
4789
|
+
/>
|
|
4790
|
+
))}
|
|
4791
|
+
|
|
4844
4792
|
{rectangleDraft?.polygons.map((points, index) => (
|
|
4845
4793
|
<FloorplanDraftLayer
|
|
4846
4794
|
anchorFill={draftStroke}
|
|
@@ -4858,7 +4806,7 @@ function FloorplanLinearDraftLayer({
|
|
|
4858
4806
|
))}
|
|
4859
4807
|
|
|
4860
4808
|
{rectangleDraft?.measurements.map((measurement, index) => (
|
|
4861
|
-
<
|
|
4809
|
+
<FloorplanDraftMeasurement
|
|
4862
4810
|
key={index}
|
|
4863
4811
|
labelBackground={isDark ? '#0f172a' : '#ffffff'}
|
|
4864
4812
|
labelText={isDark ? '#e2e8f0' : '#171717'}
|
|
@@ -4962,6 +4910,12 @@ export function FloorplanPanel({
|
|
|
4962
4910
|
const unit = useViewer((state) => state.unit)
|
|
4963
4911
|
const metricNotation = useViewer((state) => state.metricNotation)
|
|
4964
4912
|
const showGrid = useViewer((state) => state.showGrid)
|
|
4913
|
+
// Floor plan vs. site plan. Site plan hides the level geometry and draws
|
|
4914
|
+
// the lot instead — see `lib/floorplan/site-plan/site-plan-layer.tsx`. A
|
|
4915
|
+
// persisted site-plan choice on a scene with no lot reads as the floor plan.
|
|
4916
|
+
const sitePlanAvailable = useSitePlanAvailable()
|
|
4917
|
+
const storedDrawingType = useDrawingView((state) => state.drawingType)
|
|
4918
|
+
const activeDrawingType = sitePlanAvailable ? storedDrawingType : 'floor-plan'
|
|
4965
4919
|
const showGuides = useViewer((state) => state.showGuides)
|
|
4966
4920
|
const setShowGuides = useViewer((state) => state.setShowGuides)
|
|
4967
4921
|
const selectedItem = useEditor((state) => state.selectedItem)
|
|
@@ -4971,8 +4925,21 @@ export function FloorplanPanel({
|
|
|
4971
4925
|
// editor/index.tsx — subscribing here lets us re-fit the viewport when
|
|
4972
4926
|
// the user closes and re-opens the 2D editor instead of restoring the
|
|
4973
4927
|
// stale viewport from before they closed it.
|
|
4974
|
-
const isFloorplanOpen = useEditor((state) => state
|
|
4975
|
-
|
|
4928
|
+
const isFloorplanOpen = useEditor((state) => isViewVisible(state, VIEW_2D))
|
|
4929
|
+
// The compass sits at the scene's bottom-left corner, the drawing switch
|
|
4930
|
+
// beside it. With the plan in the right pane both move to its bottom-right
|
|
4931
|
+
// corner (compass outermost), clear of the dock centred over the scene.
|
|
4932
|
+
const planOnRight = useEditor((state) => {
|
|
4933
|
+
const layout = activeViewLayout(state)
|
|
4934
|
+
return layout.split && layout.panes[1] === VIEW_2D
|
|
4935
|
+
})
|
|
4936
|
+
// `null` while no scene view is on screen: the compass has nothing to orient.
|
|
4937
|
+
const sceneLeft = useEditor((state) => sceneSpan(activeViewLayout(state))?.left ?? null)
|
|
4938
|
+
// Mirrored, the pair stays left of the helper toggle in the stage's corner.
|
|
4939
|
+
const compassStyle: CSSProperties = planOnRight
|
|
4940
|
+
? { right: 56 }
|
|
4941
|
+
: { left: `calc(${(sceneLeft ?? 0) * 100}% + 12px)` }
|
|
4942
|
+
const shownScene = useEditor(visibleScene)
|
|
4976
4943
|
// Mirror for callbacks that fire outside React's render (the per-frame
|
|
4977
4944
|
// navigation-pose subscriber): when the 2D panel is hidden (`display:none` in
|
|
4978
4945
|
// 3D mode) it must NOT re-render on every camera-zoom frame.
|
|
@@ -4990,8 +4957,6 @@ export function FloorplanPanel({
|
|
|
4990
4957
|
const activeHandleDrag = useActiveHandleDrag()
|
|
4991
4958
|
const setPhase = useEditor((state) => state.setPhase)
|
|
4992
4959
|
const setMovingNode = useEditor((state) => state.setMovingNode)
|
|
4993
|
-
const structureLayer = useEditor((state) => state.structureLayer)
|
|
4994
|
-
const setStructureLayer = useEditor((state) => state.setStructureLayer)
|
|
4995
4960
|
const setTool = useEditor((state) => state.setTool)
|
|
4996
4961
|
const tool = useEditor((state) => state.tool)
|
|
4997
4962
|
const deleteNode = useScene((state) => state.deleteNode)
|
|
@@ -5273,12 +5238,6 @@ export function FloorplanPanel({
|
|
|
5273
5238
|
height: number
|
|
5274
5239
|
} | null>(null)
|
|
5275
5240
|
|
|
5276
|
-
useEffect(() => {
|
|
5277
|
-
if (structureLayer === 'zones' && floorplanSelectionTool === 'marquee') {
|
|
5278
|
-
setFloorplanSelectionTool('click')
|
|
5279
|
-
}
|
|
5280
|
-
}, [floorplanSelectionTool, setFloorplanSelectionTool, structureLayer])
|
|
5281
|
-
|
|
5282
5241
|
useEffect(() => {
|
|
5283
5242
|
setIsMacPlatform(navigator.platform.toUpperCase().includes('MAC'))
|
|
5284
5243
|
}, [])
|
|
@@ -5792,21 +5751,21 @@ export function FloorplanPanel({
|
|
|
5792
5751
|
)
|
|
5793
5752
|
|
|
5794
5753
|
const isSiteEditActive = phase === 'site'
|
|
5795
|
-
const isWallBuildActive =
|
|
5796
|
-
const isSlabBuildActive =
|
|
5797
|
-
const isCeilingBuildActive =
|
|
5798
|
-
const isZoneBuildActive =
|
|
5799
|
-
const isDoorBuildActive =
|
|
5800
|
-
const isWindowBuildActive =
|
|
5754
|
+
const isWallBuildActive = mode === 'build' && tool === 'wall'
|
|
5755
|
+
const isSlabBuildActive = mode === 'build' && tool === 'slab'
|
|
5756
|
+
const isCeilingBuildActive = mode === 'build' && tool === 'ceiling'
|
|
5757
|
+
const isZoneBuildActive = mode === 'build' && tool === 'zone'
|
|
5758
|
+
const isDoorBuildActive = mode === 'build' && tool === 'door'
|
|
5759
|
+
const isWindowBuildActive = mode === 'build' && tool === 'window'
|
|
5801
5760
|
const isPolygonBuildActive = isSlabBuildActive || isZoneBuildActive
|
|
5802
5761
|
const isPolygonDraftBuildActive = isPolygonBuildActive || isCeilingBuildActive
|
|
5803
5762
|
const isOpeningBuildActive = isDoorBuildActive || isWindowBuildActive
|
|
5804
5763
|
const isOpeningMoveActive = movingOpeningType !== null
|
|
5805
5764
|
const isOpeningPlacementActive = isOpeningBuildActive || isOpeningMoveActive
|
|
5806
|
-
const isFenceBuildActive =
|
|
5765
|
+
const isFenceBuildActive = mode === 'build' && tool === 'fence'
|
|
5807
5766
|
const fenceContinuation = useEditor((state) => state.continuationByContext.fence)
|
|
5808
|
-
const isRoofBuildActive =
|
|
5809
|
-
const isStairBuildActive =
|
|
5767
|
+
const isRoofBuildActive = mode === 'build' && tool === 'roof'
|
|
5768
|
+
const isStairBuildActive = mode === 'build' && tool === 'stair'
|
|
5810
5769
|
const isStairMoveActive = movingNode?.type === 'stair'
|
|
5811
5770
|
const isRoofMoveActive = movingNode?.type === 'roof' || movingNode?.type === 'roof-segment'
|
|
5812
5771
|
const isSlabMoveActive = movingNode?.type === 'slab'
|
|
@@ -5932,12 +5891,11 @@ export function FloorplanPanel({
|
|
|
5932
5891
|
mode === 'select' &&
|
|
5933
5892
|
floorplanSelectionTool === 'marquee' &&
|
|
5934
5893
|
!movingNode &&
|
|
5935
|
-
!isFenceEndpointMoveActive
|
|
5936
|
-
structureLayer !== 'zones'
|
|
5894
|
+
!isFenceEndpointMoveActive
|
|
5937
5895
|
const isScreenSelectionToolActive =
|
|
5938
5896
|
mode === 'select' &&
|
|
5939
5897
|
floorplanSelectionTool === 'click' &&
|
|
5940
|
-
|
|
5898
|
+
phase === 'building' &&
|
|
5941
5899
|
!movingNode &&
|
|
5942
5900
|
!isFenceEndpointMoveActive &&
|
|
5943
5901
|
!referenceScaleDraft &&
|
|
@@ -5947,8 +5905,7 @@ export function FloorplanPanel({
|
|
|
5947
5905
|
mode === 'select' &&
|
|
5948
5906
|
floorplanSelectionTool === 'click' &&
|
|
5949
5907
|
!movingNode &&
|
|
5950
|
-
!isFenceEndpointMoveActive
|
|
5951
|
-
structureLayer !== 'zones'
|
|
5908
|
+
!isFenceEndpointMoveActive
|
|
5952
5909
|
const canInteractElementFloorplanGeometry = isDeleteMode || canSelectElementFloorplanGeometry
|
|
5953
5910
|
const canInteractFloorplanSlabs = isDeleteMode || canSelectElementFloorplanGeometry
|
|
5954
5911
|
const canInteractWithGuides =
|
|
@@ -5960,42 +5917,7 @@ export function FloorplanPanel({
|
|
|
5960
5917
|
mode === 'select' &&
|
|
5961
5918
|
floorplanSelectionTool === 'click' &&
|
|
5962
5919
|
!movingNode &&
|
|
5963
|
-
!isFenceEndpointMoveActive
|
|
5964
|
-
structureLayer === 'zones'
|
|
5965
|
-
const canInteractFloorplanZones = isDeleteMode || canSelectFloorplanZones
|
|
5966
|
-
const isFloorplanStructureContextActive = phase === 'structure' && structureLayer !== 'zones'
|
|
5967
|
-
const isFloorplanFurnishContextActive = phase === 'furnish'
|
|
5968
|
-
const isFloorplanItemContextActive =
|
|
5969
|
-
isFloorplanFurnishContextActive || isFloorplanStructureContextActive
|
|
5970
|
-
const canSelectFloorplanStairs =
|
|
5971
|
-
(mode === 'select' &&
|
|
5972
|
-
floorplanSelectionTool === 'click' &&
|
|
5973
|
-
!movingNode &&
|
|
5974
|
-
!isFenceEndpointMoveActive &&
|
|
5975
|
-
isFloorplanStructureContextActive) ||
|
|
5976
|
-
isDeleteMode
|
|
5977
|
-
const canSelectFloorplanElevators = canSelectFloorplanStairs
|
|
5978
|
-
const canSelectFloorplanSpawns = canSelectFloorplanStairs
|
|
5979
|
-
const canSelectFloorplanItems =
|
|
5980
|
-
(mode === 'select' &&
|
|
5981
|
-
floorplanSelectionTool === 'click' &&
|
|
5982
|
-
!movingNode &&
|
|
5983
|
-
!isFenceEndpointMoveActive &&
|
|
5984
|
-
isFloorplanItemContextActive) ||
|
|
5985
|
-
isDeleteMode
|
|
5986
|
-
const canFocusFloorplanStairs =
|
|
5987
|
-
mode === 'select' &&
|
|
5988
|
-
floorplanSelectionTool === 'click' &&
|
|
5989
|
-
!movingNode &&
|
|
5990
|
-
!isFenceEndpointMoveActive &&
|
|
5991
|
-
isFloorplanStructureContextActive
|
|
5992
|
-
const canFocusFloorplanSpawns = canFocusFloorplanStairs
|
|
5993
|
-
const canFocusFloorplanItems =
|
|
5994
|
-
mode === 'select' &&
|
|
5995
|
-
floorplanSelectionTool === 'click' &&
|
|
5996
|
-
!movingNode &&
|
|
5997
|
-
!isFenceEndpointMoveActive &&
|
|
5998
|
-
isFloorplanItemContextActive
|
|
5920
|
+
!isFenceEndpointMoveActive
|
|
5999
5921
|
const visibleSitePolygon = displaySitePolygon
|
|
6000
5922
|
const canUseSiteBoundaryVertexHandles =
|
|
6001
5923
|
visibleSitePolygon !== null && siteBoundaryHandlesEnabled({ mode, phase })
|
|
@@ -6772,6 +6694,58 @@ export function FloorplanPanel({
|
|
|
6772
6694
|
stopFloorplanViewAnimation,
|
|
6773
6695
|
])
|
|
6774
6696
|
|
|
6697
|
+
// A frame request ("show me this node") moves the 3D camera and the plan follows its pose; with no
|
|
6698
|
+
// 3D view on screen no pose reaches the plan, so it frames the node itself. The request waits for
|
|
6699
|
+
// the selection it came with to render: a node on another floor or in another building is placed
|
|
6700
|
+
// with that building's transform, after the floor change has reset the view.
|
|
6701
|
+
const [pendingFrame, setPendingFrame] = useState<CameraControlFrameEvent | null>(null)
|
|
6702
|
+
useEffect(() => {
|
|
6703
|
+
const frame = (event: CameraControlFrameEvent) => {
|
|
6704
|
+
if (isFloorplanOpenRef.current && !isViewVisible(useEditor.getState(), VIEW_3D))
|
|
6705
|
+
setPendingFrame(event)
|
|
6706
|
+
}
|
|
6707
|
+
emitter.on('camera-controls:frame', frame)
|
|
6708
|
+
return () => emitter.off('camera-controls:frame', frame)
|
|
6709
|
+
}, [])
|
|
6710
|
+
|
|
6711
|
+
useEffect(() => {
|
|
6712
|
+
if (!pendingFrame) return
|
|
6713
|
+
setPendingFrame(null)
|
|
6714
|
+
const userRotationDeg = latestFloorplanUserRotationDegRef.current
|
|
6715
|
+
const toLocal = (x: number, z: number) =>
|
|
6716
|
+
worldToFloorplanLocalPoint(x, z, buildingPosition, buildingRotationY)
|
|
6717
|
+
const { bounds, pose } = pendingFrame
|
|
6718
|
+
let centre: SvgPoint
|
|
6719
|
+
let width: number | undefined
|
|
6720
|
+
if (bounds) {
|
|
6721
|
+
// The box's corners as the plan shows them now, so it fits the pane at any turn and aspect.
|
|
6722
|
+
const sceneRotationDeg = FLOORPLAN_VIEW_ROTATION_DEG + userRotationDeg - buildingRotationDeg
|
|
6723
|
+
const shown = [bounds.min[0], bounds.max[0]].flatMap((x) =>
|
|
6724
|
+
[bounds.min[2], bounds.max[2]].map((z) => rotateSvgPoint(toLocal(x, z), sceneRotationDeg)),
|
|
6725
|
+
)
|
|
6726
|
+
const xs = shown.map((point) => point.x)
|
|
6727
|
+
const ys = shown.map((point) => point.y)
|
|
6728
|
+
centre = toLocal((bounds.min[0] + bounds.max[0]) / 2, (bounds.min[2] + bounds.max[2]) / 2)
|
|
6729
|
+
width = Math.max(
|
|
6730
|
+
Math.max(...xs) - Math.min(...xs) + FLOORPLAN_PADDING * 2,
|
|
6731
|
+
(Math.max(...ys) - Math.min(...ys) + FLOORPLAN_PADDING * 2) * svgAspectRatio,
|
|
6732
|
+
)
|
|
6733
|
+
} else if (pose) {
|
|
6734
|
+
// A saved view with nothing to measure: its target, at the zoom the person has.
|
|
6735
|
+
centre = toLocal(pose.target[0], pose.target[2])
|
|
6736
|
+
} else return
|
|
6737
|
+
// A floor change has just reset the view to the whole floor: glide from there, not from nothing.
|
|
6738
|
+
latestViewportRef.current ??= latestFittedViewportRef.current
|
|
6739
|
+
applyFloorplanNavigationView(centre, userRotationDeg, width, { smooth: framingAnimates() })
|
|
6740
|
+
}, [
|
|
6741
|
+
applyFloorplanNavigationView,
|
|
6742
|
+
buildingPosition,
|
|
6743
|
+
buildingRotationDeg,
|
|
6744
|
+
buildingRotationY,
|
|
6745
|
+
pendingFrame,
|
|
6746
|
+
svgAspectRatio,
|
|
6747
|
+
])
|
|
6748
|
+
|
|
6775
6749
|
const viewBox = useMemo(() => {
|
|
6776
6750
|
const currentViewport = viewport ?? fittedViewport
|
|
6777
6751
|
const width = currentViewport.width
|
|
@@ -7648,7 +7622,7 @@ export function FloorplanPanel({
|
|
|
7648
7622
|
scheduleFloorplanZoomCommit()
|
|
7649
7623
|
const userRotationDeg = latestFloorplanUserRotationDegRef.current
|
|
7650
7624
|
floorplanZoomPoseRef.current = { localCenter, userRotationDeg, viewWidth: nextWidth }
|
|
7651
|
-
if (useEditor.getState()
|
|
7625
|
+
if (visibleScene(useEditor.getState()) === 'split') {
|
|
7652
7626
|
publishFloorplanNavigationPose(localCenter, userRotationDeg, nextWidth)
|
|
7653
7627
|
}
|
|
7654
7628
|
},
|
|
@@ -7731,28 +7705,24 @@ export function FloorplanPanel({
|
|
|
7731
7705
|
right: false,
|
|
7732
7706
|
})
|
|
7733
7707
|
useEffect(() => {
|
|
7734
|
-
if (
|
|
7708
|
+
if (shownScene !== '2d') return
|
|
7735
7709
|
const keys = keyboardPanKeysRef.current
|
|
7710
|
+
const motion = createKeyboardPanMotion()
|
|
7736
7711
|
let frame: number | null = null
|
|
7737
|
-
let lastTime: number | null = null
|
|
7738
7712
|
const sceneRotationDeg = (userRotationDeg: number) =>
|
|
7739
7713
|
FLOORPLAN_VIEW_ROTATION_DEG + userRotationDeg - buildingRotationDeg
|
|
7740
|
-
const step = (
|
|
7741
|
-
const { horizontal, vertical } = keyboardPanDirection(keys)
|
|
7714
|
+
const step = () => {
|
|
7742
7715
|
const viewport = latestViewportRef.current ?? latestFittedViewportRef.current
|
|
7743
|
-
if ((
|
|
7716
|
+
if (!(viewport && advanceKeyboardPanMotion(motion, keys, performance.now()))) {
|
|
7744
7717
|
frame = null
|
|
7745
|
-
|
|
7718
|
+
resetKeyboardPanMotion(motion)
|
|
7746
7719
|
commitFloorplanPan()
|
|
7747
7720
|
return
|
|
7748
7721
|
}
|
|
7749
|
-
const
|
|
7750
|
-
lastTime = time
|
|
7751
|
-
const distance =
|
|
7752
|
-
(keyboardPanSpeed(viewport.width) * elapsed) / Math.hypot(horizontal, vertical)
|
|
7722
|
+
const speed = keyboardPanSpeed(viewport.width)
|
|
7753
7723
|
const next = {
|
|
7754
|
-
centerX: viewport.centerX +
|
|
7755
|
-
centerY: viewport.centerY -
|
|
7724
|
+
centerX: viewport.centerX + motion.stepX * speed,
|
|
7725
|
+
centerY: viewport.centerY - motion.stepY * speed,
|
|
7756
7726
|
width: viewport.width,
|
|
7757
7727
|
}
|
|
7758
7728
|
const userRotationDeg = latestFloorplanUserRotationDegRef.current
|
|
@@ -7773,12 +7743,15 @@ export function FloorplanPanel({
|
|
|
7773
7743
|
}
|
|
7774
7744
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
7775
7745
|
if (!(isKeyboardPanKey(event.code) && acceptsKeyboardPan(event))) return
|
|
7746
|
+
syncKeyboardPanMotion(motion, keys, performance.now())
|
|
7776
7747
|
if (setKeyboardPanKey(keys, event.code, true)) start()
|
|
7777
7748
|
event.preventDefault()
|
|
7778
7749
|
event.stopPropagation()
|
|
7779
7750
|
}
|
|
7780
7751
|
const onKeyUp = (event: KeyboardEvent) => {
|
|
7781
|
-
if (isKeyboardPanKey(event.code)
|
|
7752
|
+
if (!isKeyboardPanKey(event.code)) return
|
|
7753
|
+
syncKeyboardPanMotion(motion, keys, performance.now())
|
|
7754
|
+
if (setKeyboardPanKey(keys, event.code, false)) {
|
|
7782
7755
|
event.preventDefault()
|
|
7783
7756
|
event.stopPropagation()
|
|
7784
7757
|
}
|
|
@@ -7828,7 +7801,7 @@ export function FloorplanPanel({
|
|
|
7828
7801
|
commitFloorplanPan,
|
|
7829
7802
|
publishFloorplanNavigationPose,
|
|
7830
7803
|
smoothFloorplanNavigationView,
|
|
7831
|
-
|
|
7804
|
+
shownScene,
|
|
7832
7805
|
])
|
|
7833
7806
|
|
|
7834
7807
|
useLayoutEffect(() => {
|
|
@@ -7880,7 +7853,10 @@ export function FloorplanPanel({
|
|
|
7880
7853
|
}
|
|
7881
7854
|
}, [isFloorplanOpen, stopFloorplanViewAnimation])
|
|
7882
7855
|
|
|
7856
|
+
// Clearing an open wall draft drops a Polygon room in progress (nothing of
|
|
7857
|
+
// it was written); paths that keep it commit first.
|
|
7883
7858
|
const clearWallPlacementDraft = useCallback(() => {
|
|
7859
|
+
discardWallPolygonDraft()
|
|
7884
7860
|
setDraftStart(null)
|
|
7885
7861
|
setWallChainFirstVertex(null)
|
|
7886
7862
|
wallConstructionOptionsRef.current = undefined
|
|
@@ -7937,10 +7913,9 @@ export function FloorplanPanel({
|
|
|
7937
7913
|
setHoveredSiteHandleId(null)
|
|
7938
7914
|
}, [clearSiteBoundaryLivePreview])
|
|
7939
7915
|
const exitSiteEditingToSelect = useCallback(() => {
|
|
7940
|
-
setPhase('
|
|
7941
|
-
setStructureLayer('elements')
|
|
7916
|
+
setPhase('building')
|
|
7942
7917
|
setMode('select')
|
|
7943
|
-
}, [setMode, setPhase
|
|
7918
|
+
}, [setMode, setPhase])
|
|
7944
7919
|
|
|
7945
7920
|
const clearDraft = useCallback(() => {
|
|
7946
7921
|
clearWallPlacementDraft()
|
|
@@ -8002,10 +7977,8 @@ export function FloorplanPanel({
|
|
|
8002
7977
|
}
|
|
8003
7978
|
|
|
8004
7979
|
const { createNode, updateNode, nodes } = useScene.getState()
|
|
8005
|
-
const
|
|
8006
|
-
|
|
8007
|
-
color: PALETTE_COLORS[zoneCount % PALETTE_COLORS.length],
|
|
8008
|
-
name: `Zone ${zoneCount + 1}`,
|
|
7980
|
+
const zone = newZone({
|
|
7981
|
+
name: nextZoneName(nodes),
|
|
8009
7982
|
polygon: points.map(([x, z]) => [x, z] as [number, number]),
|
|
8010
7983
|
})
|
|
8011
7984
|
|
|
@@ -8080,22 +8053,6 @@ export function FloorplanPanel({
|
|
|
8080
8053
|
useStairBuildPreview.getState().reset()
|
|
8081
8054
|
return
|
|
8082
8055
|
}
|
|
8083
|
-
|
|
8084
|
-
const handleGridMove = (event: GridEvent) => {
|
|
8085
|
-
// Publish to the dedicated store (deduped on the snapped point), NOT panel
|
|
8086
|
-
// state: the stair preview lives in `FloorplanStairBuildPreviewLayer`, so a
|
|
8087
|
-
// per-move update re-renders only that tiny leaf instead of this entire
|
|
8088
|
-
// (~200ms) panel — the same pattern that keeps column/elevator smooth.
|
|
8089
|
-
useStairBuildPreview
|
|
8090
|
-
.getState()
|
|
8091
|
-
.setPoint(getSnappedFloorplanPoint([event.localPosition[0], event.localPosition[2]]))
|
|
8092
|
-
}
|
|
8093
|
-
|
|
8094
|
-
emitter.on('grid:move', handleGridMove)
|
|
8095
|
-
|
|
8096
|
-
return () => {
|
|
8097
|
-
emitter.off('grid:move', handleGridMove)
|
|
8098
|
-
}
|
|
8099
8056
|
}, [isStairBuildActive])
|
|
8100
8057
|
|
|
8101
8058
|
useEffect(() => {
|
|
@@ -8287,13 +8244,13 @@ export function FloorplanPanel({
|
|
|
8287
8244
|
|
|
8288
8245
|
if (
|
|
8289
8246
|
isStairBuildActive &&
|
|
8290
|
-
useEditor.getState()
|
|
8247
|
+
visibleScene(useEditor.getState()) === '2d' &&
|
|
8291
8248
|
(event.key === 'r' || event.key === 'R')
|
|
8292
8249
|
) {
|
|
8293
8250
|
useStairBuildPreview.getState().rotateBy(Math.PI / 4)
|
|
8294
8251
|
} else if (
|
|
8295
8252
|
isStairBuildActive &&
|
|
8296
|
-
useEditor.getState()
|
|
8253
|
+
visibleScene(useEditor.getState()) === '2d' &&
|
|
8297
8254
|
(event.key === 't' || event.key === 'T')
|
|
8298
8255
|
) {
|
|
8299
8256
|
useStairBuildPreview.getState().rotateBy(-Math.PI / 4)
|
|
@@ -9004,7 +8961,7 @@ export function FloorplanPanel({
|
|
|
9004
8961
|
// tools that subscribe to these grid events.
|
|
9005
8962
|
const emitFloorplanGridEvent = useCallback(
|
|
9006
8963
|
(
|
|
9007
|
-
eventType: 'move' | 'click' | 'double-click',
|
|
8964
|
+
eventType: 'move' | 'click' | 'double-click' | 'pointerdown' | 'pointerup',
|
|
9008
8965
|
planPoint: WallPlanPoint,
|
|
9009
8966
|
nativeEvent: ReactMouseEvent<SVGSVGElement> | ReactPointerEvent<SVGSVGElement>,
|
|
9010
8967
|
) => {
|
|
@@ -9205,7 +9162,7 @@ export function FloorplanPanel({
|
|
|
9205
9162
|
const nextUserRotationDeg = rotationState.initialUserRotationDeg + angleDeltaDeg
|
|
9206
9163
|
|
|
9207
9164
|
applyFloorplanRotationImperatively(rotationState, nextUserRotationDeg)
|
|
9208
|
-
if (useEditor.getState()
|
|
9165
|
+
if (visibleScene(useEditor.getState()) === 'split') {
|
|
9209
9166
|
publishFloorplanNavigationPose(
|
|
9210
9167
|
rotationState.viewportCenterLocal,
|
|
9211
9168
|
nextUserRotationDeg,
|
|
@@ -9247,7 +9204,7 @@ export function FloorplanPanel({
|
|
|
9247
9204
|
userRotationDeg: currentUserRotationDeg,
|
|
9248
9205
|
viewWidth: currentViewport.width,
|
|
9249
9206
|
}
|
|
9250
|
-
if (useEditor.getState()
|
|
9207
|
+
if (visibleScene(useEditor.getState()) === 'split') {
|
|
9251
9208
|
publishFloorplanNavigationPose(localCenter, currentUserRotationDeg, currentViewport.width)
|
|
9252
9209
|
}
|
|
9253
9210
|
|
|
@@ -9339,6 +9296,14 @@ export function FloorplanPanel({
|
|
|
9339
9296
|
}
|
|
9340
9297
|
|
|
9341
9298
|
if (isFenceBuildActive) {
|
|
9299
|
+
if (useEditor.getState().getContinuation('fence') === 'freehand') {
|
|
9300
|
+
useAlignmentGuides.getState().clear()
|
|
9301
|
+
emitFloorplanGridEvent('move', planPoint, event)
|
|
9302
|
+
setCursorPoint((previousPoint) =>
|
|
9303
|
+
previousPoint && pointsEqual(previousPoint, planPoint) ? previousPoint : planPoint,
|
|
9304
|
+
)
|
|
9305
|
+
return
|
|
9306
|
+
}
|
|
9342
9307
|
// Fence draft: grid snap (+ existing-wall/fence endpoint snap), then
|
|
9343
9308
|
// Figma alignment — same endpoint-wins precedence as the wall branch.
|
|
9344
9309
|
// While a draft is open the segment locks to 15° rays from its start.
|
|
@@ -9346,17 +9311,23 @@ export function FloorplanPanel({
|
|
|
9346
9311
|
// there is no Shift hold-to-bypass. Alignment follows the magnetic snap
|
|
9347
9312
|
// mode, not Alt (continuation is cycled through the HUD / C).
|
|
9348
9313
|
const fenceAngleSnap = fenceDraftStart !== null && isAngleSnapActive()
|
|
9349
|
-
const fenceSnapped =
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9314
|
+
const fenceSnapped = snapRegisteredDraftPoint(
|
|
9315
|
+
'fence',
|
|
9316
|
+
{
|
|
9317
|
+
point: planPoint,
|
|
9318
|
+
walls,
|
|
9319
|
+
fences,
|
|
9320
|
+
start: fenceDraftStart ?? undefined,
|
|
9321
|
+
angleSnap: fenceAngleSnap,
|
|
9322
|
+
magnetic: isMagneticSnapActive(),
|
|
9323
|
+
},
|
|
9324
|
+
planPoint,
|
|
9325
|
+
)
|
|
9357
9326
|
const fenceGridBase = snapWallPointToGrid(planPoint)
|
|
9358
9327
|
const fenceLocked =
|
|
9359
|
-
|
|
9328
|
+
event.altKey ||
|
|
9329
|
+
fenceSnapped[0] !== fenceGridBase[0] ||
|
|
9330
|
+
fenceSnapped[1] !== fenceGridBase[1]
|
|
9360
9331
|
let snappedPoint = fenceSnapped
|
|
9361
9332
|
if (fenceLocked) useAlignmentGuides.getState().clear()
|
|
9362
9333
|
// Alignment lines show in every mode; the pull applies only when
|
|
@@ -9542,7 +9513,7 @@ export function FloorplanPanel({
|
|
|
9542
9513
|
const wallAngleSnap = draftStart !== null && isAngleSnapActive()
|
|
9543
9514
|
const wallSnap = snapWallDraftPointDetailed({
|
|
9544
9515
|
point: planPoint,
|
|
9545
|
-
walls,
|
|
9516
|
+
walls: [...walls, ...wallPolygonDraftWalls()],
|
|
9546
9517
|
start: draftStart ?? undefined,
|
|
9547
9518
|
angleSnap: wallAngleSnap,
|
|
9548
9519
|
magnetic: isMagneticSnapActive(),
|
|
@@ -9649,7 +9620,7 @@ export function FloorplanPanel({
|
|
|
9649
9620
|
const firstPoint = slabDraftPoints[0]
|
|
9650
9621
|
if (firstPoint && slabDraftPoints.length >= 3 && isPointNearPlanPoint(point, firstPoint)) {
|
|
9651
9622
|
// 2D-only view: the 3D tool can't commit, so close the polygon here.
|
|
9652
|
-
if (useEditor.getState()
|
|
9623
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9653
9624
|
createSlabOnCurrentLevel(slabDraftPoints, slabPlacementBaseRef.current)
|
|
9654
9625
|
}
|
|
9655
9626
|
clearDraft()
|
|
@@ -9699,7 +9670,7 @@ export function FloorplanPanel({
|
|
|
9699
9670
|
}
|
|
9700
9671
|
|
|
9701
9672
|
// 2D-only view: the 3D tool can't commit, so create the slab here.
|
|
9702
|
-
if (useEditor.getState()
|
|
9673
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9703
9674
|
createSlabOnCurrentLevel(nextPoints, slabPlacementBaseRef.current)
|
|
9704
9675
|
}
|
|
9705
9676
|
clearDraft()
|
|
@@ -9715,7 +9686,7 @@ export function FloorplanPanel({
|
|
|
9715
9686
|
|
|
9716
9687
|
const firstPoint = ceilingDraftPoints[0]
|
|
9717
9688
|
if (firstPoint && ceilingDraftPoints.length >= 3 && isPointNearPlanPoint(point, firstPoint)) {
|
|
9718
|
-
if (useEditor.getState()
|
|
9689
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9719
9690
|
createCeilingOnCurrentLevel(ceilingDraftPoints)
|
|
9720
9691
|
}
|
|
9721
9692
|
clearCeilingPlacementDraft()
|
|
@@ -9748,7 +9719,7 @@ export function FloorplanPanel({
|
|
|
9748
9719
|
return
|
|
9749
9720
|
}
|
|
9750
9721
|
|
|
9751
|
-
if (useEditor.getState()
|
|
9722
|
+
if (visibleScene(useEditor.getState()) === '2d') {
|
|
9752
9723
|
createCeilingOnCurrentLevel(nextPoints)
|
|
9753
9724
|
}
|
|
9754
9725
|
clearCeilingPlacementDraft()
|
|
@@ -9815,6 +9786,14 @@ export function FloorplanPanel({
|
|
|
9815
9786
|
useEditor.getState().toolDefaults.wall,
|
|
9816
9787
|
)
|
|
9817
9788
|
: undefined
|
|
9789
|
+
// 2D-only owns a Polygon room's draft; split view leaves it to the 3D tool.
|
|
9790
|
+
if (
|
|
9791
|
+
levelId &&
|
|
9792
|
+
visibleScene(useEditor.getState()) === '2d' &&
|
|
9793
|
+
getWallDrawVariant() === 'polygon'
|
|
9794
|
+
) {
|
|
9795
|
+
startWallPolygonDraft(levelId as AnyNodeId, point, wallConstructionOptionsRef.current)
|
|
9796
|
+
}
|
|
9818
9797
|
setDraftStart(point)
|
|
9819
9798
|
setWallChainFirstVertex(point)
|
|
9820
9799
|
setDraftEnd(point)
|
|
@@ -9840,8 +9819,22 @@ export function FloorplanPanel({
|
|
|
9840
9819
|
// `display:none`, so the tool never commits. Mirror the slab /
|
|
9841
9820
|
// ceiling 2D-only committers: create locally here, gated on the
|
|
9842
9821
|
// view, so split / 3D keep their single-owner tool commit.
|
|
9843
|
-
const viewIs2DOnly = useEditor.getState()
|
|
9822
|
+
const viewIs2DOnly = visibleScene(useEditor.getState()) === '2d'
|
|
9844
9823
|
let createdWall: WallNode | null = null
|
|
9824
|
+
// 2D-only Polygon room: the corner joins the draft; nothing is written
|
|
9825
|
+
// until it closes, seals or tees (then every wall at once).
|
|
9826
|
+
if (viewIs2DOnly && getWallDrawVariant() === 'polygon') {
|
|
9827
|
+
if (addWallPolygonDraftCorner(point) !== 'open') {
|
|
9828
|
+
commitWallPolygonDraft()
|
|
9829
|
+
clearWallPlacementDraft()
|
|
9830
|
+
setCursorPoint(null)
|
|
9831
|
+
return
|
|
9832
|
+
}
|
|
9833
|
+
setDraftStart(point)
|
|
9834
|
+
setDraftEnd(point)
|
|
9835
|
+
setCursorPoint(point)
|
|
9836
|
+
return
|
|
9837
|
+
}
|
|
9845
9838
|
if (viewIs2DOnly) {
|
|
9846
9839
|
createdWall = createWallOnCurrentLevel(
|
|
9847
9840
|
draftStart,
|
|
@@ -10045,18 +10038,25 @@ export function FloorplanPanel({
|
|
|
10045
10038
|
return
|
|
10046
10039
|
}
|
|
10047
10040
|
|
|
10041
|
+
if (useInteractionScope.getState().scope.kind !== 'idle') return
|
|
10048
10042
|
const modifierKeys = getSelectionModifierKeys(event)
|
|
10049
10043
|
|
|
10044
|
+
if (enterBuildingFromPlanHit(getFloorplanHitIdAtPoint(planPoint))) return
|
|
10045
|
+
if (roomPickingEnabled()) {
|
|
10046
|
+
const hitId = getFloorplanHitIdAtPoint(planPoint)
|
|
10047
|
+
if (selectRoomFromHit(resolvePlanRoomHit(hitId, planPoint), modifierKeys, hitId)) return
|
|
10048
|
+
}
|
|
10050
10049
|
const backgroundSelection = resolveFloorplanBackgroundSelection({
|
|
10051
10050
|
canSelectElementFloorplanGeometry,
|
|
10052
|
-
canSelectFloorplanZones,
|
|
10051
|
+
canSelectFloorplanZones: canSelectFloorplanZones && !!useViewer.getState().focusedUnitId,
|
|
10053
10052
|
currentSelectedIds: useViewer.getState().selection.selectedIds,
|
|
10054
10053
|
expandIdsForNode: expandSessionSelectionForNode,
|
|
10055
|
-
getFloorplanHitIdAtPoint
|
|
10054
|
+
getFloorplanHitIdAtPoint: useViewer.getState().focusedUnitId
|
|
10055
|
+
? (point) => zoneAtLevelPoint(point[0], point[1])?.id ?? null
|
|
10056
|
+
: getFloorplanHitIdAtPoint,
|
|
10056
10057
|
isWallBuildActive,
|
|
10057
10058
|
modifierKeys,
|
|
10058
10059
|
planPoint,
|
|
10059
|
-
structureLayer,
|
|
10060
10060
|
})
|
|
10061
10061
|
|
|
10062
10062
|
if (backgroundSelection.handled) {
|
|
@@ -10104,15 +10104,14 @@ export function FloorplanPanel({
|
|
|
10104
10104
|
}
|
|
10105
10105
|
|
|
10106
10106
|
if (backgroundSelection.kind === 'clear-zones') {
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
useEditor.getState().setStructureLayer('elements')
|
|
10110
|
-
useEditor.getState().setMode('select')
|
|
10107
|
+
useEditor.getState().clearRoom()
|
|
10108
|
+
setSelection({ selectedIds: [], zoneId: null })
|
|
10111
10109
|
return
|
|
10112
10110
|
}
|
|
10113
10111
|
|
|
10114
10112
|
if (!backgroundSelection.preserveSelection) {
|
|
10115
|
-
|
|
10113
|
+
useEditor.getState().clearRoom()
|
|
10114
|
+
setSelection({ selectedIds: [], zoneId: null })
|
|
10116
10115
|
}
|
|
10117
10116
|
return
|
|
10118
10117
|
}
|
|
@@ -10130,7 +10129,6 @@ export function FloorplanPanel({
|
|
|
10130
10129
|
referenceScaleDraft,
|
|
10131
10130
|
setSelectedReferenceId,
|
|
10132
10131
|
setSelection,
|
|
10133
|
-
structureLayer,
|
|
10134
10132
|
getFloorplanHitIdAtPoint,
|
|
10135
10133
|
unit,
|
|
10136
10134
|
emitFloorplanGridEvent,
|
|
@@ -10150,6 +10148,49 @@ export function FloorplanPanel({
|
|
|
10150
10148
|
},
|
|
10151
10149
|
[handleBackgroundClick],
|
|
10152
10150
|
)
|
|
10151
|
+
const handleSvgPointerDown = useCallback(
|
|
10152
|
+
(event: ReactPointerEvent<SVGSVGElement>) => {
|
|
10153
|
+
if (
|
|
10154
|
+
isFenceBuildActive &&
|
|
10155
|
+
useEditor.getState().getContinuation('fence') === 'freehand' &&
|
|
10156
|
+
event.button === 0
|
|
10157
|
+
) {
|
|
10158
|
+
const point = getPlanPointFromClientPoint(event.clientX, event.clientY)
|
|
10159
|
+
if (point) {
|
|
10160
|
+
clearFencePlacementDraft()
|
|
10161
|
+
event.currentTarget.setPointerCapture(event.pointerId)
|
|
10162
|
+
emitFloorplanGridEvent('pointerdown', point, event)
|
|
10163
|
+
}
|
|
10164
|
+
}
|
|
10165
|
+
handlePointerDown(event)
|
|
10166
|
+
},
|
|
10167
|
+
[
|
|
10168
|
+
clearFencePlacementDraft,
|
|
10169
|
+
emitFloorplanGridEvent,
|
|
10170
|
+
getPlanPointFromClientPoint,
|
|
10171
|
+
handlePointerDown,
|
|
10172
|
+
isFenceBuildActive,
|
|
10173
|
+
],
|
|
10174
|
+
)
|
|
10175
|
+
const handleSvgPointerUp = useCallback(
|
|
10176
|
+
(event: ReactPointerEvent<SVGSVGElement>) => {
|
|
10177
|
+
if (
|
|
10178
|
+
isFenceBuildActive &&
|
|
10179
|
+
useEditor.getState().getContinuation('fence') === 'freehand' &&
|
|
10180
|
+
event.button === 0
|
|
10181
|
+
) {
|
|
10182
|
+
const point = getPlanPointFromClientPoint(event.clientX, event.clientY)
|
|
10183
|
+
if (point) emitFloorplanGridEvent('pointerup', point, event)
|
|
10184
|
+
}
|
|
10185
|
+
endFloorplanNavigation(event)
|
|
10186
|
+
},
|
|
10187
|
+
[
|
|
10188
|
+
emitFloorplanGridEvent,
|
|
10189
|
+
endFloorplanNavigation,
|
|
10190
|
+
getPlanPointFromClientPoint,
|
|
10191
|
+
isFenceBuildActive,
|
|
10192
|
+
],
|
|
10193
|
+
)
|
|
10153
10194
|
const handleBackgroundDoubleClick = useCallback(
|
|
10154
10195
|
(event: ReactMouseEvent<SVGSVGElement>) => {
|
|
10155
10196
|
if (!(isPolygonDraftBuildActive && !isRoofBuildActive)) {
|
|
@@ -10300,28 +10341,18 @@ export function FloorplanPanel({
|
|
|
10300
10341
|
if (floorplanScreenSelectionOwnsInputDraggingRef.current) {
|
|
10301
10342
|
useViewer.getState().setInputDragging(false)
|
|
10302
10343
|
floorplanScreenSelectionOwnsInputDraggingRef.current = false
|
|
10344
|
+
useInteractionScope.getState().endIf((scope) => scope.kind === 'box-select')
|
|
10303
10345
|
}
|
|
10304
10346
|
}, [syncPreviewSelectedIds])
|
|
10305
10347
|
|
|
10306
10348
|
const commitFloorplanScreenSelection = useCallback(
|
|
10307
10349
|
(nextSelectedIds: string[], event: PointerEvent) => {
|
|
10308
10350
|
const modifierKeys = getSelectionModifierKeys(event)
|
|
10309
|
-
const shouldAppend = modifierKeys.meta || modifierKeys.ctrl || modifierKeys.shift
|
|
10310
|
-
|
|
10311
10351
|
setSelectedReferenceId(null)
|
|
10312
10352
|
|
|
10313
|
-
if (phase === 'structure' && structureLayer === 'zones') {
|
|
10314
|
-
if (nextSelectedIds.length > 0) {
|
|
10315
|
-
setSelection({ zoneId: nextSelectedIds[0] as ZoneNodeType['id'] })
|
|
10316
|
-
} else if (!shouldAppend) {
|
|
10317
|
-
setSelection({ zoneId: null })
|
|
10318
|
-
}
|
|
10319
|
-
return
|
|
10320
|
-
}
|
|
10321
|
-
|
|
10322
10353
|
addFloorplanSelection(nextSelectedIds, modifierKeys)
|
|
10323
10354
|
},
|
|
10324
|
-
[addFloorplanSelection,
|
|
10355
|
+
[addFloorplanSelection, setSelectedReferenceId],
|
|
10325
10356
|
)
|
|
10326
10357
|
|
|
10327
10358
|
useEffect(() => {
|
|
@@ -10371,6 +10402,7 @@ export function FloorplanPanel({
|
|
|
10371
10402
|
if (!state.isDragging && dragDistance >= SCREEN_RECTANGLE_SELECTION_DRAG_THRESHOLD_PX) {
|
|
10372
10403
|
state.isDragging = true
|
|
10373
10404
|
floorplanScreenSelectionOwnsInputDraggingRef.current = true
|
|
10405
|
+
useInteractionScope.getState().begin({ kind: 'box-select' })
|
|
10374
10406
|
useViewer.getState().setInputDragging(true)
|
|
10375
10407
|
markBoxSelectHandled()
|
|
10376
10408
|
|
|
@@ -10775,11 +10807,16 @@ export function FloorplanPanel({
|
|
|
10775
10807
|
const hasFloorplanCursorIndicator =
|
|
10776
10808
|
Boolean(movingOpeningType) ||
|
|
10777
10809
|
(mode === 'build' && tool !== null) ||
|
|
10778
|
-
(mode === 'select' && floorplanSelectionTool === 'marquee'
|
|
10810
|
+
(mode === 'select' && floorplanSelectionTool === 'marquee') ||
|
|
10779
10811
|
mode === 'delete'
|
|
10780
10812
|
|
|
10781
10813
|
const handleSvgPointerMove = useCallback(
|
|
10782
10814
|
(event: ReactPointerEvent<SVGSVGElement>) => {
|
|
10815
|
+
if (roomPickingEnabled() && !(event.target as Element).closest('.floorplan-registry-entry')) {
|
|
10816
|
+
const point = getPlanPointFromClientPoint(event.clientX, event.clientY)
|
|
10817
|
+
if (point)
|
|
10818
|
+
hoverRoomFromHit(resolvePlanRoomHit(null, point), getSelectionModifierKeys(event), null)
|
|
10819
|
+
}
|
|
10783
10820
|
if (
|
|
10784
10821
|
hasFloorplanCursorIndicator &&
|
|
10785
10822
|
!isSpacePanPressed &&
|
|
@@ -10812,6 +10849,7 @@ export function FloorplanPanel({
|
|
|
10812
10849
|
[
|
|
10813
10850
|
handlePointerMove,
|
|
10814
10851
|
hasFloorplanCursorIndicator,
|
|
10852
|
+
getPlanPointFromClientPoint,
|
|
10815
10853
|
isSpacePanPressed,
|
|
10816
10854
|
siteVertexDragState,
|
|
10817
10855
|
setFloorplanCursorPosition,
|
|
@@ -10819,6 +10857,7 @@ export function FloorplanPanel({
|
|
|
10819
10857
|
)
|
|
10820
10858
|
|
|
10821
10859
|
const handleSvgPointerLeave = useCallback(() => {
|
|
10860
|
+
useEditor.getState().setHoveredRoom(null)
|
|
10822
10861
|
setFloorplanCursorPosition(null)
|
|
10823
10862
|
setHoveredGuideCorner(null)
|
|
10824
10863
|
handlePointerLeave()
|
|
@@ -11129,8 +11168,7 @@ export function FloorplanPanel({
|
|
|
11129
11168
|
)
|
|
11130
11169
|
})()
|
|
11131
11170
|
|
|
11132
|
-
setPhase('
|
|
11133
|
-
setStructureLayer('elements')
|
|
11171
|
+
setPhase('building')
|
|
11134
11172
|
setMode('select')
|
|
11135
11173
|
|
|
11136
11174
|
const nextSelection: Parameters<typeof setSelection>[0] = {
|
|
@@ -11147,16 +11185,7 @@ export function FloorplanPanel({
|
|
|
11147
11185
|
}
|
|
11148
11186
|
|
|
11149
11187
|
setSelection(nextSelection)
|
|
11150
|
-
}, [
|
|
11151
|
-
currentBuildingId,
|
|
11152
|
-
floorplanLevels,
|
|
11153
|
-
levelNode,
|
|
11154
|
-
setMode,
|
|
11155
|
-
setPhase,
|
|
11156
|
-
setSelection,
|
|
11157
|
-
setStructureLayer,
|
|
11158
|
-
site,
|
|
11159
|
-
])
|
|
11188
|
+
}, [currentBuildingId, floorplanLevels, levelNode, setMode, setPhase, setSelection, site])
|
|
11160
11189
|
const activeDraftAnchorPoint =
|
|
11161
11190
|
referenceScaleDraft?.start ??
|
|
11162
11191
|
draftStart ??
|
|
@@ -11236,8 +11265,10 @@ export function FloorplanPanel({
|
|
|
11236
11265
|
pill, plus the group pill for multi-selections. */}
|
|
11237
11266
|
<FloorplanRegistryActionMenu />
|
|
11238
11267
|
<FloorplanGroupActionMenu />
|
|
11268
|
+
<FloorplanDrawingTypeSwitch className={planOnRight ? 'right-24 left-auto' : undefined} />
|
|
11239
11269
|
|
|
11240
11270
|
{!isStudioWorkspace &&
|
|
11271
|
+
sceneLeft !== null &&
|
|
11241
11272
|
(levelNode?.type === 'level' || hasAmbientBuildingLevel) &&
|
|
11242
11273
|
(compassHost ? (
|
|
11243
11274
|
createPortal(
|
|
@@ -11245,6 +11276,7 @@ export function FloorplanPanel({
|
|
|
11245
11276
|
needleRef={compassNeedleRef}
|
|
11246
11277
|
northRotationDeg={floorplanUserRotationDeg}
|
|
11247
11278
|
onAlignNorth={alignFloorplanViewToNorth}
|
|
11279
|
+
style={compassStyle}
|
|
11248
11280
|
/>,
|
|
11249
11281
|
compassHost,
|
|
11250
11282
|
)
|
|
@@ -11282,7 +11314,7 @@ export function FloorplanPanel({
|
|
|
11282
11314
|
}}
|
|
11283
11315
|
>
|
|
11284
11316
|
<div className="mb-3 flex items-start gap-2.5">
|
|
11285
|
-
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border border-border bg-white/5">
|
|
11317
|
+
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border border-border bg-black/4 dark:bg-white/5">
|
|
11286
11318
|
<Ruler className="h-4 w-4 text-foreground/80" />
|
|
11287
11319
|
</div>
|
|
11288
11320
|
<div className="min-w-0">
|
|
@@ -11294,7 +11326,7 @@ export function FloorplanPanel({
|
|
|
11294
11326
|
</div>
|
|
11295
11327
|
</div>
|
|
11296
11328
|
|
|
11297
|
-
<div className="mb-3 rounded-xl border border-border/70 bg-white/5 px-3 py-2">
|
|
11329
|
+
<div className="mb-3 rounded-xl border border-border/70 bg-black/4 dark:bg-white/5 px-3 py-2">
|
|
11298
11330
|
<div className="text-[11px] text-muted-foreground uppercase tracking-wide">
|
|
11299
11331
|
Drawn line
|
|
11300
11332
|
</div>
|
|
@@ -11357,7 +11389,7 @@ export function FloorplanPanel({
|
|
|
11357
11389
|
|
|
11358
11390
|
<div className="mt-4 flex justify-end gap-2">
|
|
11359
11391
|
<button
|
|
11360
|
-
className="h-8 rounded-lg border border-border px-3 font-medium text-muted-foreground text-xs transition hover:bg-white/8 hover:text-foreground"
|
|
11392
|
+
className="h-8 rounded-lg border border-border px-3 font-medium text-muted-foreground text-xs transition hover:bg-black/5 dark:hover:bg-white/8 hover:text-foreground"
|
|
11361
11393
|
onClick={() => setPendingReferenceScale(null)}
|
|
11362
11394
|
type="button"
|
|
11363
11395
|
>
|
|
@@ -11385,7 +11417,7 @@ export function FloorplanPanel({
|
|
|
11385
11417
|
// renderer, handle layers) must NOT render/reconcile while hidden —
|
|
11386
11418
|
// otherwise every scene/selection change in pure 3D re-rendered the
|
|
11387
11419
|
// whole floorplan tree (profiler: 150–200ms on a wall-endpoint drag).
|
|
11388
|
-
// `isFloorplanOpen` is
|
|
11420
|
+
// `isFloorplanOpen` is "the plan is in a pane", so this still renders fully
|
|
11389
11421
|
// in both 2D and split. Viewport state lives on the still-mounted
|
|
11390
11422
|
// panel, so pan/zoom is preserved across the toggle.
|
|
11391
11423
|
<svg
|
|
@@ -11395,11 +11427,11 @@ export function FloorplanPanel({
|
|
|
11395
11427
|
onContextMenu={(event) => event.preventDefault()}
|
|
11396
11428
|
onDoubleClick={isMarqueeSelectionToolActive ? undefined : handleBackgroundDoubleClick}
|
|
11397
11429
|
onPointerCancel={endFloorplanNavigation}
|
|
11398
|
-
onPointerDown={
|
|
11430
|
+
onPointerDown={handleSvgPointerDown}
|
|
11399
11431
|
onPointerDownCapture={handleNavigationPointerDown}
|
|
11400
11432
|
onPointerLeave={handleSvgPointerLeave}
|
|
11401
11433
|
onPointerMove={handleSvgPointerMove}
|
|
11402
|
-
onPointerUp={
|
|
11434
|
+
onPointerUp={handleSvgPointerUp}
|
|
11403
11435
|
ref={svgRef}
|
|
11404
11436
|
style={{
|
|
11405
11437
|
cursor:
|
|
@@ -11496,7 +11528,7 @@ export function FloorplanPanel({
|
|
|
11496
11528
|
subscribes to the `useStairBuildPreview` store directly, so a
|
|
11497
11529
|
per-`grid:move` cursor update re-renders only that tiny layer
|
|
11498
11530
|
rather than this whole panel. */}
|
|
11499
|
-
<FloorplanStairBuildPreviewLayer
|
|
11531
|
+
<FloorplanStairBuildPreviewLayer palette={floorplanRegistryPalette} />
|
|
11500
11532
|
|
|
11501
11533
|
<FloorplanReferenceScaleLayer
|
|
11502
11534
|
draft={referenceScaleDraft}
|
|
@@ -11547,7 +11579,13 @@ export function FloorplanPanel({
|
|
|
11547
11579
|
whose extent is derived from the current viewBox and
|
|
11548
11580
|
would create a measure→fit→measure loop. */}
|
|
11549
11581
|
<g ref={floorplanContentRef}>
|
|
11550
|
-
|
|
11582
|
+
{/* Site-plan mode swaps the level drawing for the lot /
|
|
11583
|
+
setback / footprint drawing, in SITE coordinates. */}
|
|
11584
|
+
{activeDrawingType === 'site-plan' ? (
|
|
11585
|
+
<FloorplanSitePlanLayer />
|
|
11586
|
+
) : (
|
|
11587
|
+
<FloorplanRegistryLayer />
|
|
11588
|
+
)}
|
|
11551
11589
|
{/* Faint footprint ghost of the node being placed by a
|
|
11552
11590
|
registry placement tool (e.g. column), following the
|
|
11553
11591
|
cursor. The 3D mesh preview is hidden in 2D, so this is
|
|
@@ -11561,6 +11599,7 @@ export function FloorplanPanel({
|
|
|
11561
11599
|
`floorplan-wall-move-ghost-layer.tsx`. */}
|
|
11562
11600
|
<FloorplanWallMoveGhostLayer />
|
|
11563
11601
|
</g>
|
|
11602
|
+
{activeDrawingType !== 'site-plan' && <FloorplanOpenWallEndsLayer />}
|
|
11564
11603
|
<FloorplanMeasurementToolLayer />
|
|
11565
11604
|
<FloorplanRegisteredToolLayer />
|
|
11566
11605
|
{floorplanSceneSlot}
|
|
@@ -11577,42 +11616,54 @@ export function FloorplanPanel({
|
|
|
11577
11616
|
paint on top of node geometry. */}
|
|
11578
11617
|
<FloorplanAlignmentGuideLayer />
|
|
11579
11618
|
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11619
|
+
{/* The property line, its vertex handles and its edge labels are
|
|
11620
|
+
drawn in BUILDING-LOCAL metres (the frame the level's walls
|
|
11621
|
+
live in). The site-plan drawing type draws the lot itself, in
|
|
11622
|
+
SITE metres, so mounting these there paints a second lot
|
|
11623
|
+
offset by the building's own position — the "two dashed
|
|
11624
|
+
boxes" — and it shifts whenever the house moves. */}
|
|
11625
|
+
{activeDrawingType !== 'site-plan' && (
|
|
11626
|
+
<FloorplanSiteLayer
|
|
11627
|
+
dimmed={selectedIds.length > 1 || previewSelectedIds.length > 1}
|
|
11628
|
+
isHighlighted={isSiteBoundaryHighlighted}
|
|
11629
|
+
palette={palette}
|
|
11630
|
+
sitePolygon={visibleSitePolygon}
|
|
11631
|
+
/>
|
|
11632
|
+
)}
|
|
11586
11633
|
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11634
|
+
{activeDrawingType !== 'site-plan' && (
|
|
11635
|
+
<FloorplanPolygonHandleLayer
|
|
11636
|
+
edgeHandles={siteEdgeHandles}
|
|
11637
|
+
hoveredHandleId={hoveredSiteHandleId}
|
|
11638
|
+
midpointHandles={siteMidpointHandles}
|
|
11639
|
+
onHandleHoverChange={setHoveredSiteHandleId}
|
|
11640
|
+
onMidpointPointerDown={(nodeId, edgeIndex, event) =>
|
|
11641
|
+
handleSiteMidpointPointerDown(nodeId as SiteNode['id'], edgeIndex, event)
|
|
11642
|
+
}
|
|
11643
|
+
onVertexDoubleClick={(nodeId, vertexIndex, event) =>
|
|
11644
|
+
handleSiteVertexDoubleClick(nodeId as SiteNode['id'], vertexIndex, event)
|
|
11645
|
+
}
|
|
11646
|
+
onVertexPointerDown={(nodeId, vertexIndex, event) =>
|
|
11647
|
+
handleSiteVertexPointerDown(nodeId as SiteNode['id'], vertexIndex, event)
|
|
11648
|
+
}
|
|
11649
|
+
palette={palette}
|
|
11650
|
+
unitsPerPixel={floorplanUnitsPerPixel}
|
|
11651
|
+
vertexHandles={siteVertexHandles}
|
|
11652
|
+
/>
|
|
11653
|
+
)}
|
|
11605
11654
|
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11655
|
+
{activeDrawingType !== 'site-plan' && (
|
|
11656
|
+
<FloorplanSiteEdgeLabelLayer
|
|
11657
|
+
labelBackground={isDark ? '#0f172a' : '#ffffff'}
|
|
11658
|
+
labelText={isDark ? '#e2e8f0' : '#171717'}
|
|
11659
|
+
palette={palette}
|
|
11660
|
+
sceneRotationDeg={floorplanSceneRotationDeg}
|
|
11661
|
+
shouldShow={shouldShowSiteEdgeLabels}
|
|
11662
|
+
sitePolygon={visibleSitePolygon}
|
|
11663
|
+
unit={unit}
|
|
11664
|
+
unitsPerPixel={floorplanUnitsPerPixel}
|
|
11665
|
+
/>
|
|
11666
|
+
)}
|
|
11616
11667
|
|
|
11617
11668
|
{/* "Magnetic" wall-snap beacon — per-kind glyph at the active
|
|
11618
11669
|
draft / endpoint-move snap point. Same store + coord space as
|