@pascal-app/editor 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +18 -15
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +110 -42
- package/src/components/editor/duplicate-node.ts +2 -1
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +77 -68
- package/src/components/editor/fit-scene-framing.test.ts +318 -0
- package/src/components/editor/fit-scene-framing.ts +142 -0
- package/src/components/editor/floating-action-menu.tsx +31 -11
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floor-openings-overlay.tsx +1 -1
- package/src/components/editor/floor-region-controls.tsx +2 -2
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +458 -140
- package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
- package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
- package/src/components/editor/footprint-height-handle.tsx +2 -2
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +23 -1
- package/src/components/editor/index.tsx +150 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/measurement-pill.tsx +1 -1
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +2 -3
- package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
- package/src/components/editor/open-wall-ends-hint.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/quick-measurement-card.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +4 -3
- package/src/components/editor/room-handles-2d.tsx +158 -7
- package/src/components/editor/room-handles.tsx +244 -54
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +33 -17
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +7 -3
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +31 -15
- package/src/components/editor/wall-move-side-handles.tsx +15 -7
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-region-handles.tsx +2 -2
- package/src/components/editor/wall-snap-beacon-layer.tsx +134 -33
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +32 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +33 -24
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +12 -5
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +29 -5
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +34 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +211 -39
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
- package/src/components/systems/roof/roof-edit-system.tsx +30 -19
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +200 -58
- package/src/components/tools/item/move-tool.tsx +1 -6
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +31 -35
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +2 -6
- package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/shared/segment-angle.ts +0 -4
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/tool-manager.tsx +1 -24
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
- package/src/components/tools/wall/wall-typed-entry.ts +70 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +47 -19
- package/src/components/ui/controls/tool-options-panel.tsx +74 -44
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +106 -3
- package/src/components/ui/helpers/helper-manager.tsx +136 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +114 -39
- package/src/components/ui/panels/floor-opening-panel.tsx +74 -26
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +34 -28
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +11 -10
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +76 -36
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +9 -5
- package/src/components/ui/panels/panel-wrapper.tsx +63 -127
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +465 -66
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +22 -13
- package/src/components/ui/panels/room-panel.tsx +207 -100
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/right-stack.test.ts +13 -6
- package/src/components/ui/right-stack.tsx +21 -10
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +118 -72
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +19 -9
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +62 -5
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15849 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +39331 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +39331 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +39331 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +39331 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +224 -45
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +101 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/elevation-guides.ts +21 -6
- package/src/lib/elevator-support.ts +1 -72
- package/src/lib/floor-footprints.test.ts +3 -2
- package/src/lib/floor-footprints.ts +1 -9
- package/src/lib/floor-foundation-support.test.ts +15 -6
- package/src/lib/floor-opening-draft.ts +1 -1
- package/src/lib/floor-region-session.ts +1 -1
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/open-wall-ends.ts +2 -1
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/gesture-lifecycle.ts +3 -1
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/interaction/scope.ts +1 -0
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/level-selection.test.ts +18 -2
- package/src/lib/level-selection.ts +31 -8
- package/src/lib/linear-display.ts +1 -1
- package/src/lib/live-gesture-round-trip.test.ts +1 -1
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-label.test.ts +1 -1
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +24 -6
- package/src/lib/measurements.ts +1 -203
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/node-action-movement.test.tsx +115 -0
- package/src/lib/node-action-movement.ts +2 -0
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/placement-metadata.ts +4 -0
- package/src/lib/point-ask/describe-target.ts +6 -8
- package/src/lib/point-ask/region.ts +1 -1
- package/src/lib/print-shell-compiler.ts +12 -1
- package/src/lib/print-wall-solids.test.ts +11 -0
- package/src/lib/print-wall-solids.ts +9 -0
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-assembly-overlay.ts +1 -1
- package/src/lib/room-built-on.test.ts +28 -17
- package/src/lib/room-built-on.ts +21 -17
- package/src/lib/room-construction-commands.ts +3 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +141 -1
- package/src/lib/room-handle-drag.ts +554 -58
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +38 -1
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-bounds.test.ts +144 -0
- package/src/lib/scene-bounds.ts +79 -15
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +11 -5
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/snapping-mode.test.ts +23 -1
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +21 -6
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +17 -6
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +44 -0
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +53 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-placement-typing.test.ts +165 -0
- package/src/store/use-placement-typing.ts +186 -0
- package/src/store/use-wall-snap-indicator.ts +15 -2
- package/src/components/tools/elevator/elevator-defaults.ts +0 -8
- package/src/components/tools/elevator/elevator-tool.tsx +0 -372
- package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
- package/src/lib/door-interaction.ts +0 -102
- package/src/lib/measurement-label.ts +0 -68
- package/src/lib/window-interaction.ts +0 -100
|
@@ -12,12 +12,16 @@ import {
|
|
|
12
12
|
calculateLevelMiters,
|
|
13
13
|
createSizedStairFlight,
|
|
14
14
|
DEFAULT_ANGLE_STEP,
|
|
15
|
+
DEFAULT_WALL_THICKNESS,
|
|
15
16
|
type DoorNode,
|
|
16
17
|
DoorNode as DoorNodeSchema,
|
|
17
18
|
type ElevatorNode,
|
|
18
19
|
emitter,
|
|
19
20
|
type FenceNode,
|
|
20
21
|
type FloorplanGeometry,
|
|
22
|
+
formatAngleRadians,
|
|
23
|
+
formatLinearMeasurement,
|
|
24
|
+
formatNumber as formatLocalizedNumber,
|
|
21
25
|
type GeometryContext,
|
|
22
26
|
type GuideNode,
|
|
23
27
|
getRenderableSlabPolygon,
|
|
@@ -27,6 +31,7 @@ import {
|
|
|
27
31
|
type ItemNode,
|
|
28
32
|
isCurvedWall,
|
|
29
33
|
type LevelNode,
|
|
34
|
+
linearUnitToMeters,
|
|
30
35
|
loadAssetUrl,
|
|
31
36
|
newZone,
|
|
32
37
|
nodeRegistry,
|
|
@@ -37,6 +42,7 @@ import {
|
|
|
37
42
|
resolveSlabPlacementElevation,
|
|
38
43
|
resolveTerrainWallConstructionOptions,
|
|
39
44
|
runAsSingleSceneHistoryStep,
|
|
45
|
+
type SeparatorNode,
|
|
40
46
|
type SiteNode,
|
|
41
47
|
type SlabNode,
|
|
42
48
|
SlabNode as SlabNodeSchema,
|
|
@@ -50,6 +56,7 @@ import {
|
|
|
50
56
|
useInteractive,
|
|
51
57
|
useLiveNodeOverrides,
|
|
52
58
|
useLiveTransforms,
|
|
59
|
+
useLocale,
|
|
53
60
|
useScene,
|
|
54
61
|
type WallNode,
|
|
55
62
|
WallNode as WallNodeSchema,
|
|
@@ -120,7 +127,13 @@ import {
|
|
|
120
127
|
syncKeyboardPanMotion,
|
|
121
128
|
} from '../../lib/keyboard-pan'
|
|
122
129
|
import { measurementHint, parseMeasurement } from '../../lib/measurement-parser'
|
|
123
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
createGuideScaleReference,
|
|
132
|
+
formatReferenceScaleLabel,
|
|
133
|
+
isValidReferenceScaleDisplayLength,
|
|
134
|
+
MAX_REFERENCE_SCALE_DISPLAY_LENGTH,
|
|
135
|
+
type ReferenceScaleUnit,
|
|
136
|
+
} from '../../lib/reference-scale'
|
|
124
137
|
import { snapRegisteredDraftPoint } from '../../lib/registered-draft-snap'
|
|
125
138
|
import { selectRoomFromHit } from '../../lib/room-selection-commands'
|
|
126
139
|
import { enterBuildingFromPlanHit } from '../../lib/selection-routing'
|
|
@@ -135,7 +148,13 @@ import {
|
|
|
135
148
|
} from '../../lib/units'
|
|
136
149
|
import { cn } from '../../lib/utils'
|
|
137
150
|
import { framingAnimates } from '../../lib/viewer-selection'
|
|
138
|
-
import {
|
|
151
|
+
import {
|
|
152
|
+
getWallDrawVariant,
|
|
153
|
+
getWallLineKind,
|
|
154
|
+
invisibleWallDraftSnapLines,
|
|
155
|
+
useWallDraftThickness,
|
|
156
|
+
} from '../../lib/wall-draw-variant'
|
|
157
|
+
import { wallSideOfPlanPoint } from '../../lib/wall-placement-pointer'
|
|
139
158
|
import {
|
|
140
159
|
addWallPolygonDraftCorner,
|
|
141
160
|
commitWallPolygonDraft,
|
|
@@ -143,6 +162,18 @@ import {
|
|
|
143
162
|
startWallPolygonDraft,
|
|
144
163
|
wallPolygonDraftWalls,
|
|
145
164
|
} from '../../lib/wall-polygon-draft'
|
|
165
|
+
import {
|
|
166
|
+
endWallPullRun,
|
|
167
|
+
releaseWallPullDrag,
|
|
168
|
+
useWallPullRun,
|
|
169
|
+
wallDraftIsRun,
|
|
170
|
+
} from '../../lib/wall-pull'
|
|
171
|
+
import {
|
|
172
|
+
isWallRunFree,
|
|
173
|
+
publishWallRunFeedback,
|
|
174
|
+
snapWallRunDraftPoint,
|
|
175
|
+
wallRunDirection,
|
|
176
|
+
} from '../../lib/wall-run-snap'
|
|
146
177
|
import { snapBuildingLocalToWorldGrid } from '../../lib/world-grid-snap'
|
|
147
178
|
import { nextZoneName } from '../../lib/zone-name'
|
|
148
179
|
import { subscribeNavigationSyncPose } from '../../store/navigation-sync-pose-store'
|
|
@@ -164,6 +195,7 @@ import useInteractionScope, {
|
|
|
164
195
|
useReshapingNode,
|
|
165
196
|
} from '../../store/use-interaction-scope'
|
|
166
197
|
import usePlacementPreview from '../../store/use-placement-preview'
|
|
198
|
+
import { placementTypingLengthOptions, usePlacementTyping } from '../../store/use-placement-typing'
|
|
167
199
|
import { expandSessionSelectionForNode } from '../../store/use-session-groups'
|
|
168
200
|
import { useStairBuildPreview } from '../../store/use-stair-build-preview'
|
|
169
201
|
import {
|
|
@@ -173,6 +205,7 @@ import {
|
|
|
173
205
|
VIEW_3D,
|
|
174
206
|
visibleScene,
|
|
175
207
|
} from '../../store/view-layout'
|
|
208
|
+
import { FloorplanAdjacentWallsLayer } from '../editor-2d/floorplan-adjacent-walls-layer'
|
|
176
209
|
import { FloorplanAlignmentGuideLayer } from '../editor-2d/floorplan-alignment-guide-layer'
|
|
177
210
|
import { FloorplanCursorIndicatorOverlay as Editor2dFloorplanCursorIndicatorOverlay } from '../editor-2d/floorplan-cursor-indicator-overlay'
|
|
178
211
|
import { FloorplanGroupActionMenu } from '../editor-2d/floorplan-group-action-menu'
|
|
@@ -193,6 +226,7 @@ import { FloorplanDraftMeasurement } from '../editor-2d/renderers/floorplan-draf
|
|
|
193
226
|
import { FloorplanGeometryRenderer } from '../editor-2d/renderers/floorplan-geometry-renderer'
|
|
194
227
|
import { FloorplanMarqueeLayer } from '../editor-2d/renderers/floorplan-marquee-layer'
|
|
195
228
|
import { FloorplanPlacementPreviewLayer } from '../editor-2d/renderers/floorplan-placement-preview-layer'
|
|
229
|
+
import { FloorplanPlacementTypingHud } from '../editor-2d/renderers/floorplan-placement-typing-hud'
|
|
196
230
|
import {
|
|
197
231
|
FloorplanRegistryLayer,
|
|
198
232
|
RotationAngleOverlay,
|
|
@@ -223,14 +257,15 @@ import {
|
|
|
223
257
|
} from '../tools/select/screen-rectangle-selection'
|
|
224
258
|
import { collectSelectableCandidateIds } from '../tools/select/select-candidates'
|
|
225
259
|
import {
|
|
226
|
-
formatAngleRadians,
|
|
227
260
|
getAngleArcToSegmentReference,
|
|
228
261
|
getAngleToSegmentReference,
|
|
229
262
|
getSegmentAngleReferenceAtPoint,
|
|
230
263
|
} from '../tools/shared/segment-angle'
|
|
231
264
|
import {
|
|
232
265
|
chainEndJoinsExistingWall,
|
|
266
|
+
createInvisibleWallOnCurrentLevel,
|
|
233
267
|
createWallOnCurrentLevel,
|
|
268
|
+
getSegmentGridStep,
|
|
234
269
|
isSegmentLongEnough,
|
|
235
270
|
snapWallDraftPoint,
|
|
236
271
|
snapWallDraftPointDetailed,
|
|
@@ -239,6 +274,7 @@ import {
|
|
|
239
274
|
WALL_JOIN_SNAP_RADIUS,
|
|
240
275
|
type WallPlanPoint,
|
|
241
276
|
} from '../tools/wall/wall-drafting'
|
|
277
|
+
import { formatWallDraftHeading, resolveTypedWallEnd } from '../tools/wall/wall-typed-entry'
|
|
242
278
|
import { FloorplanCompassButton } from '../viewer/floorplan-compass-button'
|
|
243
279
|
import { resolveFloorplanBackgroundSelection } from './floorplan-background-selection'
|
|
244
280
|
import {
|
|
@@ -258,6 +294,11 @@ import {
|
|
|
258
294
|
resolveFloorplanPresentationViewBox,
|
|
259
295
|
setFloorplanCompassRotation,
|
|
260
296
|
} from './floorplan-navigation-presentation'
|
|
297
|
+
import {
|
|
298
|
+
endFloorplanWallTypingOnCancel,
|
|
299
|
+
installFloorplanWallTypingKeys,
|
|
300
|
+
} from './floorplan-wall-typing-keys'
|
|
301
|
+
import { swallowNextClick } from './handles/use-handle-drag'
|
|
261
302
|
import { useFloorplanBackgroundPlacement } from './use-floorplan-background-placement'
|
|
262
303
|
import { useFloorplanHitTesting } from './use-floorplan-hit-testing'
|
|
263
304
|
import { useFloorplanSceneData } from './use-floorplan-scene-data'
|
|
@@ -266,6 +307,9 @@ import { sceneSpan } from './views/scene-region'
|
|
|
266
307
|
const FALLBACK_VIEW_SIZE = 12
|
|
267
308
|
const FLOORPLAN_PADDING = 2
|
|
268
309
|
const MIN_VIEWPORT_WIDTH_RATIO = 0.08
|
|
310
|
+
// However large the fitted plan (a whole lot), zooming in goes down to a view this wide (m): a
|
|
311
|
+
// casing leg or a stud's edge fills the screen.
|
|
312
|
+
const MIN_VIEWPORT_WIDTH = 0.6
|
|
269
313
|
const MAX_VIEWPORT_WIDTH_RATIO = 40
|
|
270
314
|
const PANEL_MIN_WIDTH = 420
|
|
271
315
|
const PANEL_MIN_HEIGHT = 320
|
|
@@ -526,8 +570,6 @@ type GuideTransformDraft = {
|
|
|
526
570
|
rotation: number
|
|
527
571
|
}
|
|
528
572
|
|
|
529
|
-
type ReferenceScaleUnit = 'meters' | 'centimeters' | 'feet' | 'inches'
|
|
530
|
-
|
|
531
573
|
// The in-flight reference-scale measurement. Only the per-CLICK fields live
|
|
532
574
|
// here (guide + start anchor); the rubber-band's moving END is the shared
|
|
533
575
|
// `useFloorplanDraftPreview.cursorPoint` (set on every move anyway), so it
|
|
@@ -835,7 +877,7 @@ function resolveFloorplanViewWidth(
|
|
|
835
877
|
return Math.max(0.001, requestedWidth)
|
|
836
878
|
}
|
|
837
879
|
|
|
838
|
-
const minWidth = fittedViewport.width * MIN_VIEWPORT_WIDTH_RATIO
|
|
880
|
+
const minWidth = Math.min(fittedViewport.width * MIN_VIEWPORT_WIDTH_RATIO, MIN_VIEWPORT_WIDTH)
|
|
839
881
|
const maxWidth = fittedViewport.width * MAX_VIEWPORT_WIDTH_RATIO
|
|
840
882
|
|
|
841
883
|
return clamp(requestedWidth, Math.min(minWidth, currentWidth), Math.max(maxWidth, currentWidth))
|
|
@@ -2281,7 +2323,12 @@ function getWallHoverSidePaths(polygon: Point2D[], wall: WallNode): [string, str
|
|
|
2281
2323
|
return [rightSidePath, leftSidePath]
|
|
2282
2324
|
}
|
|
2283
2325
|
|
|
2284
|
-
function buildDraftWall(
|
|
2326
|
+
function buildDraftWall(
|
|
2327
|
+
levelId: string,
|
|
2328
|
+
start: WallPlanPoint,
|
|
2329
|
+
end: WallPlanPoint,
|
|
2330
|
+
thickness?: number,
|
|
2331
|
+
): WallNode {
|
|
2285
2332
|
return {
|
|
2286
2333
|
object: 'node',
|
|
2287
2334
|
id: 'wall_draft' as WallNode['id'],
|
|
@@ -2293,6 +2340,7 @@ function buildDraftWall(levelId: string, start: WallPlanPoint, end: WallPlanPoin
|
|
|
2293
2340
|
children: [],
|
|
2294
2341
|
start,
|
|
2295
2342
|
end,
|
|
2343
|
+
thickness,
|
|
2296
2344
|
frontSide: 'unknown',
|
|
2297
2345
|
backSide: 'unknown',
|
|
2298
2346
|
}
|
|
@@ -2438,8 +2486,8 @@ function formatMeasurement(
|
|
|
2438
2486
|
return formatLinearMeasurement(measuredValue, unit, metricNotation)
|
|
2439
2487
|
}
|
|
2440
2488
|
|
|
2441
|
-
function
|
|
2442
|
-
return
|
|
2489
|
+
export function formatFloorplanNumber(value: number, locale: string, fractionDigits = 2) {
|
|
2490
|
+
return formatLocalizedNumber(value, { locale, max: fractionDigits })
|
|
2443
2491
|
}
|
|
2444
2492
|
|
|
2445
2493
|
function convertReferenceLengthToMeters(value: number, unit: ReferenceScaleUnit) {
|
|
@@ -2465,22 +2513,52 @@ const REFERENCE_SCALE_LINGO_UNIT: Record<ReferenceScaleUnit, 'm' | 'cm' | 'ft' |
|
|
|
2465
2513
|
/** Lingo-parse the free-text real-length input in the dropdown's unit — a
|
|
2466
2514
|
* bare number means the dropdown unit, while `180cm`, `1m80` or `5'11"`
|
|
2467
2515
|
* override it. Returns `null` when the text isn't a readable length. */
|
|
2468
|
-
function parseReferenceScaleLength(
|
|
2516
|
+
export function parseReferenceScaleLength(
|
|
2517
|
+
raw: string,
|
|
2518
|
+
unit: ReferenceScaleUnit,
|
|
2519
|
+
locale: string,
|
|
2520
|
+
): number | null {
|
|
2469
2521
|
const unitId = REFERENCE_SCALE_LINGO_UNIT[unit]
|
|
2470
2522
|
return parseMeasurement(
|
|
2471
2523
|
raw,
|
|
2472
2524
|
{ kind: 'length', unitId },
|
|
2473
|
-
{
|
|
2525
|
+
{
|
|
2526
|
+
bareUnit: unitId,
|
|
2527
|
+
locale,
|
|
2528
|
+
system: unit === 'feet' || unit === 'inches' ? 'us' : 'metric',
|
|
2529
|
+
},
|
|
2474
2530
|
)
|
|
2475
2531
|
}
|
|
2476
2532
|
|
|
2477
|
-
function
|
|
2533
|
+
export function getReferenceScaleInputError(
|
|
2534
|
+
raw: string,
|
|
2535
|
+
unit: ReferenceScaleUnit,
|
|
2536
|
+
locale: string,
|
|
2537
|
+
): string | null {
|
|
2538
|
+
if (raw.trim() === '') return 'Enter the real length of the line.'
|
|
2539
|
+
const displayLength = parseReferenceScaleLength(raw, unit, locale)
|
|
2540
|
+
if (displayLength === null || Number.isNaN(displayLength)) {
|
|
2541
|
+
return `Enter a length like 3.5, 180cm or 5'11".`
|
|
2542
|
+
}
|
|
2543
|
+
if (!(displayLength > 0)) return 'Length must be greater than 0.'
|
|
2544
|
+
if (!isValidReferenceScaleDisplayLength(displayLength)) {
|
|
2545
|
+
return `Length must be at most ${formatFloorplanNumber(MAX_REFERENCE_SCALE_DISPLAY_LENGTH, locale)} ${REFERENCE_SCALE_LINGO_UNIT[unit]}.`
|
|
2546
|
+
}
|
|
2547
|
+
return null
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
function referenceScaleLengthHint(
|
|
2551
|
+
raw: string,
|
|
2552
|
+
unit: ReferenceScaleUnit,
|
|
2553
|
+
locale: string,
|
|
2554
|
+
): string | null {
|
|
2478
2555
|
const unitId = REFERENCE_SCALE_LINGO_UNIT[unit]
|
|
2479
2556
|
return measurementHint(
|
|
2480
2557
|
raw,
|
|
2481
2558
|
{ kind: 'length', unitId },
|
|
2482
2559
|
{
|
|
2483
2560
|
bareUnit: unitId,
|
|
2561
|
+
locale,
|
|
2484
2562
|
system: unit === 'feet' || unit === 'inches' ? 'us' : 'metric',
|
|
2485
2563
|
displayUnit: unitId,
|
|
2486
2564
|
precision: 2,
|
|
@@ -2488,23 +2566,6 @@ function referenceScaleLengthHint(raw: string, unit: ReferenceScaleUnit): string
|
|
|
2488
2566
|
)
|
|
2489
2567
|
}
|
|
2490
2568
|
|
|
2491
|
-
function getReferenceScaleUnitLabel(unit: ReferenceScaleUnit) {
|
|
2492
|
-
switch (unit) {
|
|
2493
|
-
case 'centimeters':
|
|
2494
|
-
return 'cm'
|
|
2495
|
-
case 'feet':
|
|
2496
|
-
return 'ft'
|
|
2497
|
-
case 'inches':
|
|
2498
|
-
return 'in'
|
|
2499
|
-
default:
|
|
2500
|
-
return 'm'
|
|
2501
|
-
}
|
|
2502
|
-
}
|
|
2503
|
-
|
|
2504
|
-
function formatReferenceScaleLabel(value: number, unit: ReferenceScaleUnit) {
|
|
2505
|
-
return `${formatNumber(value)} ${getReferenceScaleUnitLabel(unit)}`
|
|
2506
|
-
}
|
|
2507
|
-
|
|
2508
2569
|
function getPolygonAreaAndCentroid(polygon: Point2D[]) {
|
|
2509
2570
|
let cx = 0
|
|
2510
2571
|
let cy = 0
|
|
@@ -2810,8 +2871,10 @@ function findClosestWallPoint(
|
|
|
2810
2871
|
const distSq = (point[0] - px) ** 2 + (point[1] - pz) ** 2
|
|
2811
2872
|
if (distSq < bestDistSq) {
|
|
2812
2873
|
bestDistSq = distSq
|
|
2813
|
-
//
|
|
2814
|
-
|
|
2874
|
+
// The face the cursor is on, as a 3D hover's normal reports it: a door
|
|
2875
|
+
// swings and a window faces toward the cursor's side, in 2D as in 3D.
|
|
2876
|
+
const side = wallSideOfPlanPoint(wall, [point[0], point[1]])
|
|
2877
|
+
best = { wall, point: [px, pz], t, normal: [0, 0, side === 'front' ? 1 : -1] }
|
|
2815
2878
|
}
|
|
2816
2879
|
}
|
|
2817
2880
|
|
|
@@ -3281,6 +3344,7 @@ function FloorplanReferenceScaleLayer({
|
|
|
3281
3344
|
unit: 'metric' | 'imperial'
|
|
3282
3345
|
unitsPerPixel: number
|
|
3283
3346
|
}) {
|
|
3347
|
+
const locale = useLocale()
|
|
3284
3348
|
const visibleReferences = guides
|
|
3285
3349
|
.filter((guide) => guideUi[guide.id]?.scaleReferenceVisible !== false)
|
|
3286
3350
|
.map((guide) => guide.scaleReference)
|
|
@@ -3290,16 +3354,19 @@ function FloorplanReferenceScaleLayer({
|
|
|
3290
3354
|
|
|
3291
3355
|
return (
|
|
3292
3356
|
<>
|
|
3293
|
-
{visibleReferences.map((reference, index) =>
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3357
|
+
{visibleReferences.map((reference, index) => {
|
|
3358
|
+
const label = formatReferenceScaleLabel(reference, locale)
|
|
3359
|
+
return (
|
|
3360
|
+
<FloorplanReferenceScaleLine
|
|
3361
|
+
end={reference.end}
|
|
3362
|
+
key={`${index}-${reference.start.join(',')}-${reference.end.join(',')}`}
|
|
3363
|
+
label={label}
|
|
3364
|
+
palette={palette}
|
|
3365
|
+
start={reference.start}
|
|
3366
|
+
unitsPerPixel={unitsPerPixel}
|
|
3367
|
+
/>
|
|
3368
|
+
)
|
|
3369
|
+
})}
|
|
3303
3370
|
{draft?.start && (
|
|
3304
3371
|
<FloorplanReferenceScaleDraftLine
|
|
3305
3372
|
palette={palette}
|
|
@@ -4442,7 +4509,10 @@ function FloorplanLinearDraftLayer({
|
|
|
4442
4509
|
sceneRotationDeg: number
|
|
4443
4510
|
}) {
|
|
4444
4511
|
const metricNotation = useViewer((state) => state.metricNotation)
|
|
4512
|
+
// The thickness the wall commits at, as the 3D draft shows it.
|
|
4513
|
+
const wallDraftThickness = useWallDraftThickness(DEFAULT_WALL_THICKNESS)
|
|
4445
4514
|
const wallDraftEnd = useFloorplanDraftPreview((s) => s.wallDraftEnd)
|
|
4515
|
+
const wallTypingActive = usePlacementTyping((s) => s.isActive)
|
|
4446
4516
|
const fenceDraftEnd = useFloorplanDraftPreview((s) => s.fenceDraftEnd)
|
|
4447
4517
|
const roofDraftEnd = useFloorplanDraftPreview((s) => s.roofDraftEnd)
|
|
4448
4518
|
const roofDraftQuarterTurn = useFloorplanDraftPreview((s) => s.roofDraftQuarterTurn)
|
|
@@ -4465,10 +4535,12 @@ function FloorplanLinearDraftLayer({
|
|
|
4465
4535
|
) {
|
|
4466
4536
|
return null
|
|
4467
4537
|
}
|
|
4468
|
-
const draftWall = getSharedFloorplanWall(
|
|
4538
|
+
const draftWall = getSharedFloorplanWall(
|
|
4539
|
+
buildDraftWall(levelId, wallDraftStart, wallDraftEnd, wallDraftThickness),
|
|
4540
|
+
)
|
|
4469
4541
|
// Keep the live draft preview cheap; full level-wide mitering here runs on every mouse move.
|
|
4470
4542
|
return getWallPlanFootprint(draftWall, EMPTY_WALL_MITER_DATA)
|
|
4471
|
-
}, [levelId, wallDraftStart, wallDraftEnd])
|
|
4543
|
+
}, [levelId, wallDraftStart, wallDraftEnd, wallDraftThickness])
|
|
4472
4544
|
|
|
4473
4545
|
// Rectangle mode drafts four walls at once; they read exactly like a line
|
|
4474
4546
|
// draft — same mitered footprints, measurement plates and axis guides.
|
|
@@ -4766,6 +4838,7 @@ function FloorplanLinearDraftLayer({
|
|
|
4766
4838
|
<FloorplanDraftMeasurement
|
|
4767
4839
|
labelBackground={isDark ? '#0f172a' : '#ffffff'}
|
|
4768
4840
|
labelText={isDark ? '#e2e8f0' : '#171717'}
|
|
4841
|
+
lengthHidden={wallTypingActive}
|
|
4769
4842
|
measurement={draftWallMeasurement}
|
|
4770
4843
|
measurementStroke={measurementStroke}
|
|
4771
4844
|
sceneRotationDeg={sceneRotationDeg}
|
|
@@ -4773,6 +4846,19 @@ function FloorplanLinearDraftLayer({
|
|
|
4773
4846
|
/>
|
|
4774
4847
|
)}
|
|
4775
4848
|
|
|
4849
|
+
{wallTypingActive && isWallBuildActive && wallDraftStart ? (
|
|
4850
|
+
<FloorplanPlacementTypingHud
|
|
4851
|
+
placeholders={[
|
|
4852
|
+
draftWallMeasurement?.lengthLabel ?? '',
|
|
4853
|
+
wallDraftEnd ? formatWallDraftHeading(wallDraftStart, wallDraftEnd) : '',
|
|
4854
|
+
]}
|
|
4855
|
+
sceneRotationDeg={sceneRotationDeg}
|
|
4856
|
+
unitsPerPixel={unitsPerPixel}
|
|
4857
|
+
x={(draftWallMeasurement?.midpoint ?? wallDraftStart)[0]}
|
|
4858
|
+
y={(draftWallMeasurement?.midpoint ?? wallDraftStart)[1]}
|
|
4859
|
+
/>
|
|
4860
|
+
) : null}
|
|
4861
|
+
|
|
4776
4862
|
{polygonRoomDraft?.map((points, index) => (
|
|
4777
4863
|
<FloorplanDraftLayer
|
|
4778
4864
|
anchorFill={draftStroke}
|
|
@@ -4838,6 +4924,7 @@ export function FloorplanPanel({
|
|
|
4838
4924
|
floorplanSceneSlot?: ReactNode
|
|
4839
4925
|
}) {
|
|
4840
4926
|
useFloorplanCameraSyncBridge()
|
|
4927
|
+
const locale = useLocale()
|
|
4841
4928
|
const viewportHostRef = useRef<HTMLDivElement>(null)
|
|
4842
4929
|
const svgRef = useRef<SVGSVGElement>(null)
|
|
4843
4930
|
const floorplanBackgroundRef = useRef<SVGRectElement>(null)
|
|
@@ -4849,6 +4936,9 @@ export function FloorplanPanel({
|
|
|
4849
4936
|
const floorplanSpacePanPressedRef = useRef(false)
|
|
4850
4937
|
const floorplanNavigationClickSuppressedRef = useRef(false)
|
|
4851
4938
|
const guideInteractionRef = useRef<GuideInteractionState | null>(null)
|
|
4939
|
+
// Late-bound so the window keydown effect can commit the wall draft at the
|
|
4940
|
+
// typed length without an ordering dependency on `handleWallPlacementPoint`.
|
|
4941
|
+
const wallPlacementPointRef = useRef<((point: WallPlanPoint) => void) | null>(null)
|
|
4852
4942
|
const guideTransformDraftRef = useRef<GuideTransformDraft | null>(null)
|
|
4853
4943
|
const pendingFenceDragRef = useRef<PendingFenceDragState | null>(null)
|
|
4854
4944
|
const wallEndpointDragRef = useRef<WallEndpointDragState | null>(null)
|
|
@@ -5042,6 +5132,28 @@ export function FloorplanPanel({
|
|
|
5042
5132
|
// Walls committed by the current 2D-only chain — exclusion set for the
|
|
5043
5133
|
// T-junction chain-termination test (mirrors the 3D tool's `chainWallIds`).
|
|
5044
5134
|
const wallChainWallIdsRef = useRef<string[]>([])
|
|
5135
|
+
// Split view: the 3D wall tool owns the commit and publishes each segment's
|
|
5136
|
+
// resolved end as the chain start. Re-base this panel's draft onto it so a
|
|
5137
|
+
// typed Enter in the plan projects from the segment the 3D chain is
|
|
5138
|
+
// actually drafting — without this, `draftStart` stays on the last point
|
|
5139
|
+
// the panel itself placed and the typed commit uses a stale origin. In
|
|
5140
|
+
// 2D-only the panel is the committer and never publishes, so this
|
|
5141
|
+
// subscription stays dormant there.
|
|
5142
|
+
useEffect(() => {
|
|
5143
|
+
if (!(mode === 'build' && tool === 'wall')) return
|
|
5144
|
+
return useSegmentDraftChain.subscribe((state, previousState) => {
|
|
5145
|
+
const next = state.wall
|
|
5146
|
+
if (next === previousState.wall) return
|
|
5147
|
+
if (!next) return
|
|
5148
|
+
setDraftStart((current) =>
|
|
5149
|
+
current && current[0] === next[0] && current[1] === next[1] ? current : next,
|
|
5150
|
+
)
|
|
5151
|
+
setWallChainFirstVertex((current) => current ?? next)
|
|
5152
|
+
})
|
|
5153
|
+
}, [mode, tool])
|
|
5154
|
+
// Direction of the run's last wall (a pulled run's source first): the next
|
|
5155
|
+
// holds square to it. Null before the run has a wall.
|
|
5156
|
+
const wallRunReferenceRef = useRef<WallPlanPoint | null>(null)
|
|
5045
5157
|
const setDraftEnd = useCallback(
|
|
5046
5158
|
(next: WallPlanPoint | null | ((prev: WallPlanPoint | null) => WallPlanPoint | null)) => {
|
|
5047
5159
|
const store = useFloorplanDraftPreview.getState()
|
|
@@ -5049,6 +5161,8 @@ export function FloorplanPanel({
|
|
|
5049
5161
|
},
|
|
5050
5162
|
[],
|
|
5051
5163
|
)
|
|
5164
|
+
// The pointer's own (snapped) wall draft end; a typed session re-projects it.
|
|
5165
|
+
const wallDraftPointerRef = useRef<WallPlanPoint | null>(null)
|
|
5052
5166
|
const [fenceDraftStart, setFenceDraftStart] = useState<WallPlanPoint | null>(null)
|
|
5053
5167
|
const setFenceDraftEnd = useCallback(
|
|
5054
5168
|
(next: WallPlanPoint | null | ((prev: WallPlanPoint | null) => WallPlanPoint | null)) => {
|
|
@@ -5057,14 +5171,10 @@ export function FloorplanPanel({
|
|
|
5057
5171
|
},
|
|
5058
5172
|
[],
|
|
5059
5173
|
)
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
store.setRoofDraftEnd(typeof next === 'function' ? next(store.roofDraftEnd) : next)
|
|
5065
|
-
},
|
|
5066
|
-
[],
|
|
5067
|
-
)
|
|
5174
|
+
// The roof tool that owns the input (the 3D tool, or the plan's own in 2D-only view) writes the
|
|
5175
|
+
// roof draft from the corners it snaps and commits; the plan only draws it, so its rectangle is
|
|
5176
|
+
// the roof the next click makes.
|
|
5177
|
+
const roofDraftStart = useFloorplanDraftPreview((s) => s.roofDraftStart)
|
|
5068
5178
|
const [ceilingDraftPoints, setCeilingDraftPoints] = useState<WallPlanPoint[]>([])
|
|
5069
5179
|
const [slabDraftPoints, setSlabDraftPoints] = useState<WallPlanPoint[]>([])
|
|
5070
5180
|
const slabPlacementBaseRef = useRef<number | null>(null)
|
|
@@ -5077,9 +5187,6 @@ export function FloorplanPanel({
|
|
|
5077
5187
|
useEffect(() => {
|
|
5078
5188
|
useFloorplanDraftPreview.getState().setFenceDraftStart(fenceDraftStart)
|
|
5079
5189
|
}, [fenceDraftStart])
|
|
5080
|
-
useEffect(() => {
|
|
5081
|
-
useFloorplanDraftPreview.getState().setRoofDraftStart(roofDraftStart)
|
|
5082
|
-
}, [roofDraftStart])
|
|
5083
5190
|
const [zoneDraftPoints, setZoneDraftPoints] = useState<WallPlanPoint[]>([])
|
|
5084
5191
|
const [siteBoundaryDraft, setSiteBoundaryDraft] = useState<SiteBoundaryDraft | null>(null)
|
|
5085
5192
|
const [siteVertexDragState, setSiteVertexDragState] = useState<SiteVertexDragState | null>(null)
|
|
@@ -7161,8 +7268,8 @@ export function FloorplanPanel({
|
|
|
7161
7268
|
return
|
|
7162
7269
|
}
|
|
7163
7270
|
|
|
7164
|
-
const displayLength = parseReferenceScaleLength(referenceScaleValue, referenceScaleUnit)
|
|
7165
|
-
if (
|
|
7271
|
+
const displayLength = parseReferenceScaleLength(referenceScaleValue, referenceScaleUnit, locale)
|
|
7272
|
+
if (displayLength === null || !isValidReferenceScaleDisplayLength(displayLength)) {
|
|
7166
7273
|
return
|
|
7167
7274
|
}
|
|
7168
7275
|
|
|
@@ -7199,14 +7306,17 @@ export function FloorplanPanel({
|
|
|
7199
7306
|
{
|
|
7200
7307
|
position: nextGuidePosition,
|
|
7201
7308
|
scale: nextGuideScale,
|
|
7202
|
-
scaleReference:
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7309
|
+
scaleReference: createGuideScaleReference(
|
|
7310
|
+
{
|
|
7311
|
+
start: pendingReferenceScale.start,
|
|
7312
|
+
end: scaledEnd,
|
|
7313
|
+
realLengthMeters,
|
|
7314
|
+
measuredLengthUnits: scaledMeasuredLengthUnits,
|
|
7315
|
+
metersPerUnit,
|
|
7316
|
+
},
|
|
7317
|
+
displayLength,
|
|
7318
|
+
referenceScaleUnit,
|
|
7319
|
+
),
|
|
7210
7320
|
} as Partial<GuideNode>,
|
|
7211
7321
|
)
|
|
7212
7322
|
setGuideLocked(pendingReferenceScale.guideId, true)
|
|
@@ -7215,6 +7325,7 @@ export function FloorplanPanel({
|
|
|
7215
7325
|
setPendingReferenceScale(null)
|
|
7216
7326
|
}, [
|
|
7217
7327
|
guideById,
|
|
7328
|
+
locale,
|
|
7218
7329
|
pendingReferenceScale,
|
|
7219
7330
|
referenceScaleUnit,
|
|
7220
7331
|
referenceScaleValue,
|
|
@@ -7853,6 +7964,26 @@ export function FloorplanPanel({
|
|
|
7853
7964
|
}
|
|
7854
7965
|
}, [isFloorplanOpen, stopFloorplanViewAnimation])
|
|
7855
7966
|
|
|
7967
|
+
// Typing reshapes the plan's own draft without a pointer move.
|
|
7968
|
+
useEffect(() => {
|
|
7969
|
+
if (!draftStart) return
|
|
7970
|
+
return usePlacementTyping.subscribe((state, previous) => {
|
|
7971
|
+
if (state.fields === previous.fields && state.isActive === previous.isActive) return
|
|
7972
|
+
const pointer = wallDraftPointerRef.current
|
|
7973
|
+
if (!pointer) return
|
|
7974
|
+
setDraftEnd(
|
|
7975
|
+
(state.isActive &&
|
|
7976
|
+
resolveTypedWallEnd(
|
|
7977
|
+
draftStart,
|
|
7978
|
+
pointer,
|
|
7979
|
+
state.fields,
|
|
7980
|
+
placementTypingLengthOptions(unit, metricNotation),
|
|
7981
|
+
getSegmentGridStep(),
|
|
7982
|
+
)) ||
|
|
7983
|
+
pointer,
|
|
7984
|
+
)
|
|
7985
|
+
})
|
|
7986
|
+
}, [draftStart, metricNotation, setDraftEnd, unit])
|
|
7856
7987
|
// Clearing an open wall draft drops a Polygon room in progress (nothing of
|
|
7857
7988
|
// it was written); paths that keep it commit first.
|
|
7858
7989
|
const clearWallPlacementDraft = useCallback(() => {
|
|
@@ -7861,20 +7992,31 @@ export function FloorplanPanel({
|
|
|
7861
7992
|
setWallChainFirstVertex(null)
|
|
7862
7993
|
wallConstructionOptionsRef.current = undefined
|
|
7863
7994
|
wallChainWallIdsRef.current = []
|
|
7995
|
+
wallRunReferenceRef.current = null
|
|
7864
7996
|
setDraftEnd(null)
|
|
7997
|
+
wallDraftPointerRef.current = null
|
|
7998
|
+
usePlacementTyping.getState().clear()
|
|
7865
7999
|
useSegmentDraftChain.getState().clear('wall')
|
|
8000
|
+
// A run pulled from a wall end ends with the plan's draft: back to Select.
|
|
8001
|
+
endWallPullRun()
|
|
7866
8002
|
}, [setDraftEnd])
|
|
7867
8003
|
const clearFencePlacementDraft = useCallback(() => {
|
|
7868
8004
|
setFenceDraftStart(null)
|
|
7869
8005
|
setFenceDraftEnd(null)
|
|
7870
8006
|
}, [setFenceDraftEnd])
|
|
7871
8007
|
const clearRoofPlacementDraft = useCallback(() => {
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
8008
|
+
const draft = useFloorplanDraftPreview.getState()
|
|
8009
|
+
draft.setRoofDraftStart(null)
|
|
8010
|
+
draft.setRoofDraftEnd(null)
|
|
8011
|
+
}, [])
|
|
7875
8012
|
const clearCeilingPlacementDraft = useCallback(() => {
|
|
7876
8013
|
setCeilingDraftPoints([])
|
|
7877
8014
|
}, [])
|
|
8015
|
+
// Beside the 3D view its ceiling tool commits; in 2D-only view the plan does. Each keeps its own
|
|
8016
|
+
// corners, so a polygon begun under one owner must not be closed against the other's.
|
|
8017
|
+
useEffect(() => {
|
|
8018
|
+
if (shownScene) clearCeilingPlacementDraft()
|
|
8019
|
+
}, [clearCeilingPlacementDraft, shownScene])
|
|
7878
8020
|
const clearSlabPlacementDraft = useCallback(() => {
|
|
7879
8021
|
setSlabDraftPoints([])
|
|
7880
8022
|
slabPlacementBaseRef.current = null
|
|
@@ -7961,6 +8103,7 @@ export function FloorplanPanel({
|
|
|
7961
8103
|
|
|
7962
8104
|
useEffect(() => {
|
|
7963
8105
|
const handleCancel = () => {
|
|
8106
|
+
if (endFloorplanWallTypingOnCancel()) return
|
|
7964
8107
|
clearDraft()
|
|
7965
8108
|
}
|
|
7966
8109
|
|
|
@@ -7970,6 +8113,44 @@ export function FloorplanPanel({
|
|
|
7970
8113
|
}
|
|
7971
8114
|
}, [clearDraft])
|
|
7972
8115
|
|
|
8116
|
+
// Split view: when the 3D wall tool stops drafting (auto-close,
|
|
8117
|
+
// T-junction, single-segment, cancel) it clears the published chain start.
|
|
8118
|
+
// That null clear must tear this panel's draft down too, or the 2D side
|
|
8119
|
+
// keeps a live draft that swallows typing keys against a chain the 3D
|
|
8120
|
+
// view already ended. Lives here (below `clearDraft`)
|
|
8121
|
+
// rather than beside the re-base subscription so the teardown stays
|
|
8122
|
+
// reachable.
|
|
8123
|
+
useEffect(() => {
|
|
8124
|
+
if (!(mode === 'build' && tool === 'wall')) return
|
|
8125
|
+
return useSegmentDraftChain.subscribe((state, previousState) => {
|
|
8126
|
+
if (state.wall === null && previousState.wall !== null) clearDraft()
|
|
8127
|
+
})
|
|
8128
|
+
}, [mode, tool, clearDraft])
|
|
8129
|
+
|
|
8130
|
+
// A wall end's pull grip pressed in the plan opens the Walls run at that
|
|
8131
|
+
// end: start the plan's draft there, as if the end had been clicked. A pull
|
|
8132
|
+
// from the 3D view drafts there (the plan beside it follows its moves).
|
|
8133
|
+
const wallPullRun = useWallPullRun((state) => state.run)
|
|
8134
|
+
const seededWallPullSeqRef = useRef<number | null>(null)
|
|
8135
|
+
useEffect(() => {
|
|
8136
|
+
if (!(wallPullRun && wallPullRun.origin === '2d' && isWallBuildActive && levelId)) return
|
|
8137
|
+
if (seededWallPullSeqRef.current === wallPullRun.seq) return
|
|
8138
|
+
seededWallPullSeqRef.current = wallPullRun.seq
|
|
8139
|
+
const start = wallPullRun.start
|
|
8140
|
+
wallConstructionOptionsRef.current = resolveTerrainWallConstructionOptions(
|
|
8141
|
+
useScene.getState().nodes,
|
|
8142
|
+
levelId,
|
|
8143
|
+
start,
|
|
8144
|
+
useEditor.getState().toolDefaults.wall,
|
|
8145
|
+
)
|
|
8146
|
+
wallChainWallIdsRef.current = []
|
|
8147
|
+
wallRunReferenceRef.current = wallPullRun.reference
|
|
8148
|
+
setDraftStart(start)
|
|
8149
|
+
setWallChainFirstVertex(start)
|
|
8150
|
+
setDraftEnd(start)
|
|
8151
|
+
setCursorPoint(start)
|
|
8152
|
+
}, [isWallBuildActive, levelId, setCursorPoint, setDraftEnd, wallPullRun])
|
|
8153
|
+
|
|
7973
8154
|
const createZoneOnCurrentLevel = useCallback(
|
|
7974
8155
|
(points: WallPlanPoint[]) => {
|
|
7975
8156
|
if (!levelId) {
|
|
@@ -8219,6 +8400,31 @@ export function FloorplanPanel({
|
|
|
8219
8400
|
return unsubscribe
|
|
8220
8401
|
}, [movingNode, scheduleMovingFloorplanNodeRefresh])
|
|
8221
8402
|
|
|
8403
|
+
// Read by the typed-entry listener, registered once so it stays ahead of
|
|
8404
|
+
// the global shortcuts.
|
|
8405
|
+
const wallTypingSourceRef = useRef({
|
|
8406
|
+
draftStart: null as WallPlanPoint | null,
|
|
8407
|
+
lengthOptions: placementTypingLengthOptions(unit, metricNotation),
|
|
8408
|
+
})
|
|
8409
|
+
wallTypingSourceRef.current = {
|
|
8410
|
+
draftStart: isWallBuildActive ? draftStart : null,
|
|
8411
|
+
lengthOptions: placementTypingLengthOptions(unit, metricNotation),
|
|
8412
|
+
}
|
|
8413
|
+
useEffect(
|
|
8414
|
+
() =>
|
|
8415
|
+
installFloorplanWallTypingKeys({
|
|
8416
|
+
getDraft: () => {
|
|
8417
|
+
const { draftStart: start, lengthOptions } = wallTypingSourceRef.current
|
|
8418
|
+
if (!start) return null
|
|
8419
|
+
const pointer =
|
|
8420
|
+
wallDraftPointerRef.current ?? useFloorplanDraftPreview.getState().wallDraftEnd ?? start
|
|
8421
|
+
return { start, pointer, lengthOptions }
|
|
8422
|
+
},
|
|
8423
|
+
commit: (pointer) => wallPlacementPointRef.current?.(pointer),
|
|
8424
|
+
}),
|
|
8425
|
+
[],
|
|
8426
|
+
)
|
|
8427
|
+
|
|
8222
8428
|
useEffect(() => {
|
|
8223
8429
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
8224
8430
|
const target = event.target as HTMLElement | null
|
|
@@ -8297,7 +8503,16 @@ export function FloorplanPanel({
|
|
|
8297
8503
|
window.removeEventListener('keyup', handleKeyUp)
|
|
8298
8504
|
window.removeEventListener('blur', handleBlur)
|
|
8299
8505
|
}
|
|
8300
|
-
}, [
|
|
8506
|
+
}, [
|
|
8507
|
+
isFloorplanOpen,
|
|
8508
|
+
isStairBuildActive,
|
|
8509
|
+
movingNode,
|
|
8510
|
+
isWallBuildActive,
|
|
8511
|
+
draftStart,
|
|
8512
|
+
unit,
|
|
8513
|
+
metricNotation,
|
|
8514
|
+
setDraftEnd,
|
|
8515
|
+
])
|
|
8301
8516
|
|
|
8302
8517
|
useEffect(() => {
|
|
8303
8518
|
const handleWindowPointerMove = (event: PointerEvent) => {
|
|
@@ -9284,14 +9499,6 @@ export function FloorplanPanel({
|
|
|
9284
9499
|
setCursorPoint((previousPoint) =>
|
|
9285
9500
|
previousPoint && pointsEqual(previousPoint, snappedPoint) ? previousPoint : snappedPoint,
|
|
9286
9501
|
)
|
|
9287
|
-
|
|
9288
|
-
if (roofDraftStart) {
|
|
9289
|
-
setRoofDraftEnd((previousPoint) =>
|
|
9290
|
-
previousPoint && pointsEqual(previousPoint, snappedPoint)
|
|
9291
|
-
? previousPoint
|
|
9292
|
-
: snappedPoint,
|
|
9293
|
-
)
|
|
9294
|
-
}
|
|
9295
9502
|
return
|
|
9296
9503
|
}
|
|
9297
9504
|
|
|
@@ -9506,41 +9713,82 @@ export function FloorplanPanel({
|
|
|
9506
9713
|
return
|
|
9507
9714
|
}
|
|
9508
9715
|
|
|
9509
|
-
// Wall draft: grid + magnetic snap, then Figma-style alignment.
|
|
9510
|
-
//
|
|
9511
|
-
//
|
|
9512
|
-
//
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9716
|
+
// Wall draft: grid + magnetic snap, then Figma-style alignment. A Walls
|
|
9717
|
+
// segment locks to 15° rays from its start; a run's segment (a Polygon
|
|
9718
|
+
// side, a pulled run) holds square to the last wall and the house's axes,
|
|
9719
|
+
// with projection guides and a labelled snap marker
|
|
9720
|
+
// (`snapWallRunDraftPoint`). Snapping is governed by the snapping mode
|
|
9721
|
+
// (`'off'` is the bypass); Alt frees a run's segment. An invisible wall
|
|
9722
|
+
// draft catches the invisible walls already drawn as well.
|
|
9723
|
+
let snappedPoint: WallPlanPoint
|
|
9724
|
+
const draftSnapWalls = [
|
|
9725
|
+
...walls,
|
|
9726
|
+
...wallPolygonDraftWalls(),
|
|
9727
|
+
...invisibleWallDraftSnapLines(),
|
|
9728
|
+
]
|
|
9729
|
+
if (draftStart && wallDraftIsRun()) {
|
|
9730
|
+
const runSnap = snapWallRunDraftPoint({
|
|
9731
|
+
point: planPoint,
|
|
9732
|
+
start: draftStart,
|
|
9733
|
+
runStart: wallChainFirstVertex ?? draftStart,
|
|
9734
|
+
reference: wallRunReferenceRef.current,
|
|
9735
|
+
walls: draftSnapWalls,
|
|
9736
|
+
free: isWallRunFree(event),
|
|
9737
|
+
})
|
|
9738
|
+
snappedPoint = runSnap.point
|
|
9739
|
+
publishWallRunFeedback(runSnap)
|
|
9528
9740
|
} else {
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9741
|
+
const wallAngleSnap = draftStart !== null && isAngleSnapActive()
|
|
9742
|
+
const wallSnap = snapWallDraftPointDetailed({
|
|
9743
|
+
point: planPoint,
|
|
9744
|
+
walls: draftSnapWalls,
|
|
9745
|
+
start: draftStart ?? undefined,
|
|
9746
|
+
angleSnap: wallAngleSnap,
|
|
9747
|
+
magnetic: isMagneticSnapActive(),
|
|
9748
|
+
// Same as the click: an end aimed into a wall's body joins it.
|
|
9749
|
+
captureWallBodies: true,
|
|
9533
9750
|
})
|
|
9751
|
+
const wallSnapped = wallSnap.point
|
|
9752
|
+
// Locked onto existing geometry (corner / midpoint / crossing / edge) →
|
|
9753
|
+
// that snap wins, so skip Figma alignment and stand the beacon there.
|
|
9754
|
+
const lockedToWall = wallSnap.snap !== null
|
|
9755
|
+
snappedPoint = wallSnapped
|
|
9756
|
+
if (lockedToWall) {
|
|
9757
|
+
useAlignmentGuides.getState().clear()
|
|
9758
|
+
} else {
|
|
9759
|
+
// Alignment lines show in every mode; the pull applies only when
|
|
9760
|
+
// magnetic ('lines') and the segment isn't angle-locked.
|
|
9761
|
+
snappedPoint = alignFloorplanDraftPoint(wallSnapped, {
|
|
9762
|
+
applySnap: isMagneticSnapActive() && !wallAngleSnap,
|
|
9763
|
+
})
|
|
9764
|
+
}
|
|
9765
|
+
useWallSnapIndicator
|
|
9766
|
+
.getState()
|
|
9767
|
+
.set(
|
|
9768
|
+
wallSnap.snap ? { x: snappedPoint[0], z: snappedPoint[1], kind: wallSnap.snap } : null,
|
|
9769
|
+
)
|
|
9534
9770
|
}
|
|
9535
|
-
useWallSnapIndicator
|
|
9536
|
-
.getState()
|
|
9537
|
-
.set(wallSnap.snap ? { x: snappedPoint[0], z: snappedPoint[1], kind: wallSnap.snap } : null)
|
|
9538
9771
|
|
|
9539
9772
|
// Emit `grid:move` so the registry-driven wall tool's 3D preview
|
|
9540
9773
|
// tracks the cursor. The local draftEnd update below is what
|
|
9541
9774
|
// drives the 2D draft polygon — both views update in parallel.
|
|
9775
|
+
// An open typed session re-projects the pointer onto what was typed;
|
|
9776
|
+
// the 3D tool gets the pointer itself and projects it the same way.
|
|
9777
|
+
wallDraftPointerRef.current = draftStart ? snappedPoint : null
|
|
9778
|
+
const wallTyping = usePlacementTyping.getState()
|
|
9779
|
+
const draftEndPoint =
|
|
9780
|
+
(draftStart &&
|
|
9781
|
+
wallTyping.isActive &&
|
|
9782
|
+
resolveTypedWallEnd(
|
|
9783
|
+
draftStart,
|
|
9784
|
+
snappedPoint,
|
|
9785
|
+
wallTyping.fields,
|
|
9786
|
+
placementTypingLengthOptions(unit, metricNotation),
|
|
9787
|
+
getSegmentGridStep(),
|
|
9788
|
+
)) ||
|
|
9789
|
+
snappedPoint
|
|
9542
9790
|
emitFloorplanGridEvent('move', snappedPoint, event)
|
|
9543
|
-
setCursorPoint(
|
|
9791
|
+
setCursorPoint(draftEndPoint)
|
|
9544
9792
|
|
|
9545
9793
|
if (!draftStart) {
|
|
9546
9794
|
return
|
|
@@ -9549,13 +9797,13 @@ export function FloorplanPanel({
|
|
|
9549
9797
|
setDraftEnd((previousEnd) => {
|
|
9550
9798
|
if (
|
|
9551
9799
|
!previousEnd ||
|
|
9552
|
-
previousEnd[0] !==
|
|
9553
|
-
previousEnd[1] !==
|
|
9800
|
+
previousEnd[0] !== draftEndPoint[0] ||
|
|
9801
|
+
previousEnd[1] !== draftEndPoint[1]
|
|
9554
9802
|
) {
|
|
9555
9803
|
sfxEmitter.emit('sfx:grid-snap')
|
|
9556
9804
|
}
|
|
9557
9805
|
|
|
9558
|
-
return
|
|
9806
|
+
return draftEndPoint
|
|
9559
9807
|
})
|
|
9560
9808
|
},
|
|
9561
9809
|
[
|
|
@@ -9590,17 +9838,21 @@ export function FloorplanPanel({
|
|
|
9590
9838
|
isRoofBuildActive,
|
|
9591
9839
|
isWallBuildActive,
|
|
9592
9840
|
levelId,
|
|
9841
|
+
// `unit` / `metricNotation` feed the typed-length projection helpers
|
|
9842
|
+
// shared with the commit path; listed so a unit change re-binds the
|
|
9843
|
+
// move preview to the new bare-unit parse.
|
|
9844
|
+
metricNotation,
|
|
9845
|
+
unit,
|
|
9593
9846
|
publishFloorplanNavigationPose,
|
|
9594
9847
|
referenceScaleDraft,
|
|
9595
|
-
roofDraftStart,
|
|
9596
9848
|
siteVertexDragState,
|
|
9597
9849
|
surfaceSize.height,
|
|
9598
9850
|
surfaceSize.width,
|
|
9599
9851
|
svgAspectRatio,
|
|
9600
9852
|
walls,
|
|
9853
|
+
wallChainFirstVertex,
|
|
9601
9854
|
setCursorPoint,
|
|
9602
9855
|
setDraftEnd,
|
|
9603
|
-
setRoofDraftEnd,
|
|
9604
9856
|
setFenceDraftEnd,
|
|
9605
9857
|
],
|
|
9606
9858
|
)
|
|
@@ -9776,7 +10028,21 @@ export function FloorplanPanel({
|
|
|
9776
10028
|
)
|
|
9777
10029
|
|
|
9778
10030
|
const handleWallPlacementPoint = useCallback(
|
|
9779
|
-
(
|
|
10031
|
+
(rawPoint: WallPlanPoint) => {
|
|
10032
|
+
// An open typed session commits what its preview shows: the pointer
|
|
10033
|
+
// re-projected onto what was typed, never re-snapped.
|
|
10034
|
+
const typing = usePlacementTyping.getState()
|
|
10035
|
+
const typedEnd =
|
|
10036
|
+
draftStart && typing.isActive
|
|
10037
|
+
? resolveTypedWallEnd(
|
|
10038
|
+
draftStart,
|
|
10039
|
+
rawPoint,
|
|
10040
|
+
typing.fields,
|
|
10041
|
+
placementTypingLengthOptions(unit, metricNotation),
|
|
10042
|
+
getSegmentGridStep(),
|
|
10043
|
+
)
|
|
10044
|
+
: null
|
|
10045
|
+
const point: WallPlanPoint = typedEnd ?? rawPoint
|
|
9780
10046
|
if (!draftStart) {
|
|
9781
10047
|
wallConstructionOptionsRef.current = levelId
|
|
9782
10048
|
? resolveTerrainWallConstructionOptions(
|
|
@@ -9794,6 +10060,7 @@ export function FloorplanPanel({
|
|
|
9794
10060
|
) {
|
|
9795
10061
|
startWallPolygonDraft(levelId as AnyNodeId, point, wallConstructionOptionsRef.current)
|
|
9796
10062
|
}
|
|
10063
|
+
wallRunReferenceRef.current = null
|
|
9797
10064
|
setDraftStart(point)
|
|
9798
10065
|
setWallChainFirstVertex(point)
|
|
9799
10066
|
setDraftEnd(point)
|
|
@@ -9804,6 +10071,8 @@ export function FloorplanPanel({
|
|
|
9804
10071
|
if (!isSegmentLongEnough(draftStart, point)) {
|
|
9805
10072
|
return
|
|
9806
10073
|
}
|
|
10074
|
+
// The run's next segment holds square to this one.
|
|
10075
|
+
const placedDirection = wallRunDirection(draftStart, point)
|
|
9807
10076
|
|
|
9808
10077
|
// The 3D wall tool's `grid:click` listener
|
|
9809
10078
|
// (`packages/nodes/src/wall/tool.tsx`) owns the wall-create
|
|
@@ -9820,7 +10089,7 @@ export function FloorplanPanel({
|
|
|
9820
10089
|
// ceiling 2D-only committers: create locally here, gated on the
|
|
9821
10090
|
// view, so split / 3D keep their single-owner tool commit.
|
|
9822
10091
|
const viewIs2DOnly = visibleScene(useEditor.getState()) === '2d'
|
|
9823
|
-
let createdWall: WallNode | null = null
|
|
10092
|
+
let createdWall: WallNode | SeparatorNode | null = null
|
|
9824
10093
|
// 2D-only Polygon room: the corner joins the draft; nothing is written
|
|
9825
10094
|
// until it closes, seals or tees (then every wall at once).
|
|
9826
10095
|
if (viewIs2DOnly && getWallDrawVariant() === 'polygon') {
|
|
@@ -9830,17 +10099,19 @@ export function FloorplanPanel({
|
|
|
9830
10099
|
setCursorPoint(null)
|
|
9831
10100
|
return
|
|
9832
10101
|
}
|
|
10102
|
+
wallRunReferenceRef.current = placedDirection
|
|
9833
10103
|
setDraftStart(point)
|
|
9834
10104
|
setDraftEnd(point)
|
|
9835
10105
|
setCursorPoint(point)
|
|
10106
|
+
usePlacementTyping.getState().clear()
|
|
9836
10107
|
return
|
|
9837
10108
|
}
|
|
9838
10109
|
if (viewIs2DOnly) {
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
10110
|
+
// The Walls variant's Invisible draws room separators, not walls.
|
|
10111
|
+
createdWall =
|
|
10112
|
+
getWallLineKind() === 'invisible'
|
|
10113
|
+
? createInvisibleWallOnCurrentLevel(draftStart, point)
|
|
10114
|
+
: createWallOnCurrentLevel(draftStart, point, wallConstructionOptionsRef.current)
|
|
9844
10115
|
}
|
|
9845
10116
|
if (createdWall) {
|
|
9846
10117
|
wallChainWallIdsRef.current.push(createdWall.id)
|
|
@@ -9856,7 +10127,7 @@ export function FloorplanPanel({
|
|
|
9856
10127
|
: (publishedNextStart ?? point)
|
|
9857
10128
|
|
|
9858
10129
|
if (
|
|
9859
|
-
|
|
10130
|
+
!wallDraftIsRun() ||
|
|
9860
10131
|
(wallChainFirstVertex && isWithinWallJoinSnapRadius(nextStart, wallChainFirstVertex))
|
|
9861
10132
|
) {
|
|
9862
10133
|
clearWallPlacementDraft()
|
|
@@ -9878,7 +10149,7 @@ export function FloorplanPanel({
|
|
|
9878
10149
|
levelWalls,
|
|
9879
10150
|
wallChainWallIdsRef.current,
|
|
9880
10151
|
) ||
|
|
9881
|
-
wallClosesRoom(levelWalls, createdWall)
|
|
10152
|
+
(createdWall.type === 'wall' && wallClosesRoom(levelWalls, createdWall))
|
|
9882
10153
|
) {
|
|
9883
10154
|
clearWallPlacementDraft()
|
|
9884
10155
|
setCursorPoint(null)
|
|
@@ -9894,19 +10165,30 @@ export function FloorplanPanel({
|
|
|
9894
10165
|
return
|
|
9895
10166
|
}
|
|
9896
10167
|
|
|
10168
|
+
wallRunReferenceRef.current = placedDirection
|
|
9897
10169
|
setDraftStart(nextStart)
|
|
9898
10170
|
setDraftEnd(nextStart)
|
|
9899
10171
|
setCursorPoint(nextStart)
|
|
10172
|
+
// What was typed belongs to this segment; the next starts afresh.
|
|
10173
|
+
usePlacementTyping.getState().clear()
|
|
9900
10174
|
},
|
|
9901
10175
|
[
|
|
9902
10176
|
clearWallPlacementDraft,
|
|
9903
10177
|
draftStart,
|
|
9904
10178
|
levelId,
|
|
10179
|
+
metricNotation,
|
|
10180
|
+
unit,
|
|
9905
10181
|
wallChainFirstVertex,
|
|
9906
10182
|
setDraftEnd,
|
|
9907
10183
|
setCursorPoint,
|
|
9908
10184
|
],
|
|
9909
10185
|
)
|
|
10186
|
+
// Latest-ref for the Enter-commit path: assigning the ref inside the
|
|
10187
|
+
// callback itself froze the first-render closure (draftStart === null), so
|
|
10188
|
+
// a typed Enter re-started the draft instead of committing the wall.
|
|
10189
|
+
useEffect(() => {
|
|
10190
|
+
wallPlacementPointRef.current = handleWallPlacementPoint
|
|
10191
|
+
}, [handleWallPlacementPoint])
|
|
9910
10192
|
const { getFloorplanHitIdAtPoint, getFloorplanSelectionIdsInBounds } = useFloorplanHitTesting({
|
|
9911
10193
|
sceneRef: floorplanSceneRef,
|
|
9912
10194
|
})
|
|
@@ -9924,6 +10206,11 @@ export function FloorplanPanel({
|
|
|
9924
10206
|
}) => snapWallDraftPoint({ ...args, magnetic: isMagneticSnapActive() }),
|
|
9925
10207
|
[],
|
|
9926
10208
|
)
|
|
10209
|
+
// What the click needs to snap an open run's segment as the move did.
|
|
10210
|
+
const getWallRunContext = useCallback(
|
|
10211
|
+
() => ({ runStart: wallChainFirstVertex, reference: wallRunReferenceRef.current }),
|
|
10212
|
+
[wallChainFirstVertex],
|
|
10213
|
+
)
|
|
9927
10214
|
const { handleBackgroundPlacementClick } = useFloorplanBackgroundPlacement({
|
|
9928
10215
|
activePolygonDraftPoints,
|
|
9929
10216
|
ceilingDraftPoints,
|
|
@@ -9936,6 +10223,7 @@ export function FloorplanPanel({
|
|
|
9936
10223
|
findClosestWallPoint,
|
|
9937
10224
|
floorplanOpeningLocalY,
|
|
9938
10225
|
getSnappedFloorplanPoint,
|
|
10226
|
+
getWallRunContext,
|
|
9939
10227
|
handleCeilingItemPlacementClick,
|
|
9940
10228
|
handleCeilingPlacementPoint,
|
|
9941
10229
|
handleSlabPlacementPoint,
|
|
@@ -9959,20 +10247,16 @@ export function FloorplanPanel({
|
|
|
9959
10247
|
isWallBuildActive,
|
|
9960
10248
|
isZoneBuildActive,
|
|
9961
10249
|
levelId,
|
|
9962
|
-
roofDraftStart,
|
|
9963
10250
|
setCursorPoint,
|
|
9964
10251
|
setFenceDraftEnd,
|
|
9965
10252
|
setFenceDraftStart,
|
|
9966
|
-
setRoofDraftEnd,
|
|
9967
|
-
setRoofDraftStart,
|
|
9968
10253
|
snapPolygonDraftPoint,
|
|
9969
10254
|
snapWallDraftPoint: snapWallDraftPointMagnetic,
|
|
9970
10255
|
toPoint2D,
|
|
9971
10256
|
walls,
|
|
9972
|
-
//
|
|
9973
|
-
//
|
|
9974
|
-
//
|
|
9975
|
-
// building rotation / position.
|
|
10257
|
+
// Building-grid snap: the lattice this plan draws inside its rotated
|
|
10258
|
+
// scene group, so drafts land on the visible grid however the
|
|
10259
|
+
// building is turned, and where the 3D tool would put them.
|
|
9976
10260
|
worldGridSnap: snapBuildingLocalToWorldGrid,
|
|
9977
10261
|
})
|
|
9978
10262
|
|
|
@@ -10021,10 +10305,11 @@ export function FloorplanPanel({
|
|
|
10021
10305
|
// confirming without editing is a no-op instead of a surprise
|
|
10022
10306
|
// rescale (plan units are meters; convert when defaulting to feet).
|
|
10023
10307
|
setReferenceScaleValue(
|
|
10024
|
-
|
|
10308
|
+
formatFloorplanNumber(
|
|
10025
10309
|
unit === 'imperial'
|
|
10026
10310
|
? measuredLengthUnits / linearUnitToMeters(1, 'imperial')
|
|
10027
10311
|
: measuredLengthUnits,
|
|
10312
|
+
locale,
|
|
10028
10313
|
2,
|
|
10029
10314
|
),
|
|
10030
10315
|
)
|
|
@@ -10133,6 +10418,7 @@ export function FloorplanPanel({
|
|
|
10133
10418
|
unit,
|
|
10134
10419
|
emitFloorplanGridEvent,
|
|
10135
10420
|
setCursorPoint,
|
|
10421
|
+
locale,
|
|
10136
10422
|
],
|
|
10137
10423
|
)
|
|
10138
10424
|
const handleSvgClick = useCallback(
|
|
@@ -10174,6 +10460,16 @@ export function FloorplanPanel({
|
|
|
10174
10460
|
)
|
|
10175
10461
|
const handleSvgPointerUp = useCallback(
|
|
10176
10462
|
(event: ReactPointerEvent<SVGSVGElement>) => {
|
|
10463
|
+
// Letting go of a wall end's pull grip places the pulled wall, as a click
|
|
10464
|
+
// there would; the click the browser fires next is eaten.
|
|
10465
|
+
const pull = useWallPullRun.getState().run
|
|
10466
|
+
if (event.button === 0 && pull?.dragging && pull.origin === '2d') {
|
|
10467
|
+
releaseWallPullDrag()
|
|
10468
|
+
if (isWallBuildActive && draftStart) {
|
|
10469
|
+
swallowNextClick()
|
|
10470
|
+
handleBackgroundClick(event)
|
|
10471
|
+
}
|
|
10472
|
+
}
|
|
10177
10473
|
if (
|
|
10178
10474
|
isFenceBuildActive &&
|
|
10179
10475
|
useEditor.getState().getContinuation('fence') === 'freehand' &&
|
|
@@ -10185,14 +10481,30 @@ export function FloorplanPanel({
|
|
|
10185
10481
|
endFloorplanNavigation(event)
|
|
10186
10482
|
},
|
|
10187
10483
|
[
|
|
10484
|
+
draftStart,
|
|
10188
10485
|
emitFloorplanGridEvent,
|
|
10189
10486
|
endFloorplanNavigation,
|
|
10190
10487
|
getPlanPointFromClientPoint,
|
|
10488
|
+
handleBackgroundClick,
|
|
10191
10489
|
isFenceBuildActive,
|
|
10490
|
+
isWallBuildActive,
|
|
10192
10491
|
],
|
|
10193
10492
|
)
|
|
10194
10493
|
const handleBackgroundDoubleClick = useCallback(
|
|
10195
10494
|
(event: ReactMouseEvent<SVGSVGElement>) => {
|
|
10495
|
+
// A double-click ends a wall run as the 3D tool's detail-2 click does:
|
|
10496
|
+
// the walls already drawn stay, an open Polygon room is kept, and the
|
|
10497
|
+
// draft goes. The click path does this on its second click; this also
|
|
10498
|
+
// covers one the plan never routed there (the walls the run drew sit
|
|
10499
|
+
// under the pointer by then).
|
|
10500
|
+
if (isWallBuildActive) {
|
|
10501
|
+
if (draftStart) {
|
|
10502
|
+
commitWallPolygonDraft()
|
|
10503
|
+
clearWallPlacementDraft()
|
|
10504
|
+
setCursorPoint(null)
|
|
10505
|
+
}
|
|
10506
|
+
return
|
|
10507
|
+
}
|
|
10196
10508
|
if (!(isPolygonDraftBuildActive && !isRoofBuildActive)) {
|
|
10197
10509
|
return
|
|
10198
10510
|
}
|
|
@@ -10238,6 +10550,8 @@ export function FloorplanPanel({
|
|
|
10238
10550
|
},
|
|
10239
10551
|
[
|
|
10240
10552
|
activePolygonDraftPoints,
|
|
10553
|
+
clearWallPlacementDraft,
|
|
10554
|
+
draftStart,
|
|
10241
10555
|
emitFloorplanGridEvent,
|
|
10242
10556
|
handleCeilingPlacementConfirm,
|
|
10243
10557
|
getPlanPointFromClientPoint,
|
|
@@ -10246,8 +10560,10 @@ export function FloorplanPanel({
|
|
|
10246
10560
|
isCeilingBuildActive,
|
|
10247
10561
|
isPolygonDraftBuildActive,
|
|
10248
10562
|
isRoofBuildActive,
|
|
10563
|
+
isWallBuildActive,
|
|
10249
10564
|
isZoneBuildActive,
|
|
10250
10565
|
levelId,
|
|
10566
|
+
setCursorPoint,
|
|
10251
10567
|
],
|
|
10252
10568
|
)
|
|
10253
10569
|
|
|
@@ -11205,9 +11521,9 @@ export function FloorplanPanel({
|
|
|
11205
11521
|
isPanning || isRotatingFloorplan ? 'grabbing' : isSpacePanPressed ? 'grab' : null
|
|
11206
11522
|
const isFloorplanNavigationOverlayVisible = isSpacePanPressed || isPanning || isRotatingFloorplan
|
|
11207
11523
|
const pendingReferenceDisplayLength =
|
|
11208
|
-
parseReferenceScaleLength(referenceScaleValue, referenceScaleUnit) ?? Number.NaN
|
|
11524
|
+
parseReferenceScaleLength(referenceScaleValue, referenceScaleUnit, locale) ?? Number.NaN
|
|
11209
11525
|
const pendingReferenceRealLengthMeters =
|
|
11210
|
-
pendingReferenceScale && pendingReferenceDisplayLength
|
|
11526
|
+
pendingReferenceScale && isValidReferenceScaleDisplayLength(pendingReferenceDisplayLength)
|
|
11211
11527
|
? convertReferenceLengthToMeters(pendingReferenceDisplayLength, referenceScaleUnit)
|
|
11212
11528
|
: null
|
|
11213
11529
|
const pendingReferenceMetersPerUnit =
|
|
@@ -11218,17 +11534,14 @@ export function FloorplanPanel({
|
|
|
11218
11534
|
pendingReferenceScale && pendingReferenceRealLengthMeters
|
|
11219
11535
|
? pendingReferenceRealLengthMeters / pendingReferenceScale.measuredLengthUnits
|
|
11220
11536
|
: null
|
|
11221
|
-
const referenceScaleInputError =
|
|
11222
|
-
referenceScaleValue
|
|
11223
|
-
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
: pendingReferenceDisplayLength > 0
|
|
11227
|
-
? null
|
|
11228
|
-
: 'Length must be greater than 0.'
|
|
11537
|
+
const referenceScaleInputError = getReferenceScaleInputError(
|
|
11538
|
+
referenceScaleValue,
|
|
11539
|
+
referenceScaleUnit,
|
|
11540
|
+
locale,
|
|
11541
|
+
)
|
|
11229
11542
|
const referenceScaleHint = referenceScaleInputError
|
|
11230
11543
|
? null
|
|
11231
|
-
: referenceScaleLengthHint(referenceScaleValue, referenceScaleUnit)
|
|
11544
|
+
: referenceScaleLengthHint(referenceScaleValue, referenceScaleUnit, locale)
|
|
11232
11545
|
return (
|
|
11233
11546
|
<div
|
|
11234
11547
|
className="pointer-events-auto flex h-full w-full flex-col overflow-hidden bg-background/95"
|
|
@@ -11383,8 +11696,8 @@ export function FloorplanPanel({
|
|
|
11383
11696
|
|
|
11384
11697
|
<div className="mt-3 rounded-lg bg-muted/45 px-3 py-2 text-muted-foreground text-xs">
|
|
11385
11698
|
{pendingReferenceImageScaleFactor
|
|
11386
|
-
? `Image will scale ${
|
|
11387
|
-
: 'Enter a length
|
|
11699
|
+
? `Image will scale ${formatFloorplanNumber(pendingReferenceImageScaleFactor, locale, 3)}x from the first point.`
|
|
11700
|
+
: 'Enter a valid length to preview the scale.'}
|
|
11388
11701
|
</div>
|
|
11389
11702
|
|
|
11390
11703
|
<div className="mt-4 flex justify-end gap-2">
|
|
@@ -11599,6 +11912,11 @@ export function FloorplanPanel({
|
|
|
11599
11912
|
`floorplan-wall-move-ghost-layer.tsx`. */}
|
|
11600
11913
|
<FloorplanWallMoveGhostLayer />
|
|
11601
11914
|
</g>
|
|
11915
|
+
{/* Dashed walls of the level below / above, over the plan but
|
|
11916
|
+
outside the measured group so they never change the fit. */}
|
|
11917
|
+
{activeDrawingType !== 'site-plan' && (
|
|
11918
|
+
<FloorplanAdjacentWallsLayer levelId={levelNode?.id} />
|
|
11919
|
+
)}
|
|
11602
11920
|
{activeDrawingType !== 'site-plan' && <FloorplanOpenWallEndsLayer />}
|
|
11603
11921
|
<FloorplanMeasurementToolLayer />
|
|
11604
11922
|
<FloorplanRegisteredToolLayer />
|
|
@@ -11614,7 +11932,7 @@ export function FloorplanPanel({
|
|
|
11614
11932
|
overlay during a free-translate drag. Sits above the
|
|
11615
11933
|
registry layer so the red lines and distance pills
|
|
11616
11934
|
paint on top of node geometry. */}
|
|
11617
|
-
<FloorplanAlignmentGuideLayer />
|
|
11935
|
+
<FloorplanAlignmentGuideLayer unitsPerPixel={floorplanUnitsPerPixel} />
|
|
11618
11936
|
|
|
11619
11937
|
{/* The property line, its vertex handles and its edge labels are
|
|
11620
11938
|
drawn in BUILDING-LOCAL metres (the frame the level's walls
|
|
@@ -11668,7 +11986,7 @@ export function FloorplanPanel({
|
|
|
11668
11986
|
{/* "Magnetic" wall-snap beacon — per-kind glyph at the active
|
|
11669
11987
|
draft / endpoint-move snap point. Same store + coord space as
|
|
11670
11988
|
the alignment guides. */}
|
|
11671
|
-
<FloorplanSnapBeaconLayer />
|
|
11989
|
+
<FloorplanSnapBeaconLayer unitsPerPixel={floorplanUnitsPerPixel} />
|
|
11672
11990
|
|
|
11673
11991
|
<FloorplanMarqueeOverlay cursorColor={palette.cursor} />
|
|
11674
11992
|
|