@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
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type AnyNode,
|
|
5
|
+
type AnyNodeId,
|
|
6
|
+
type FloorplanGeometry,
|
|
7
|
+
formatLinearMeasurement,
|
|
8
|
+
nodeRegistry,
|
|
9
|
+
type Point,
|
|
10
|
+
roomDrawnFloor,
|
|
11
|
+
useScene,
|
|
12
|
+
} from '@pascal-app/core'
|
|
4
13
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
14
|
import { Minus, Plus } from 'lucide-react'
|
|
6
15
|
import { type PointerEvent as ReactPointerEvent, useMemo, useState } from 'react'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
16
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
17
|
+
import { useRoomRecords, useSelectedRoom } from '../../hooks/use-selected-room'
|
|
18
|
+
import { getFloorplanNodeExtension } from '../../lib/floorplan/floorplan-extension'
|
|
19
|
+
import { MEASUREMENT_FLOORPLAN_COLOR } from '../../lib/measurements'
|
|
9
20
|
import {
|
|
10
21
|
commitRoomFloorElevation,
|
|
11
22
|
roomRelativeFloorHeight,
|
|
@@ -21,8 +32,13 @@ import {
|
|
|
21
32
|
useRoomHandleDrag,
|
|
22
33
|
type WallPushHandle,
|
|
23
34
|
} from '../../lib/room-handle-drag'
|
|
24
|
-
import
|
|
35
|
+
import {
|
|
36
|
+
mergeSelectionDimensions,
|
|
37
|
+
type OwnedSelectionDimension,
|
|
38
|
+
type RoomDimension,
|
|
39
|
+
} from '../../lib/room-push-dimensions'
|
|
25
40
|
import useEditor from '../../store/use-editor'
|
|
41
|
+
import useFloorplanAnnotationVisibility from '../../store/use-floorplan-annotation-visibility'
|
|
26
42
|
import useInteractionScope from '../../store/use-interaction-scope'
|
|
27
43
|
import { useFloorplanRender } from '../editor-2d/floorplan-render-context'
|
|
28
44
|
import { FloorplanGeometryRenderer } from '../editor-2d/renderers/floorplan-geometry-renderer'
|
|
@@ -158,9 +174,10 @@ function startArrow(event: ReactPointerEvent<SVGPathElement>, run: () => unknown
|
|
|
158
174
|
|
|
159
175
|
/**
|
|
160
176
|
* The selected room's arrows on the plan: one push/pull chevron per boundary
|
|
161
|
-
* span (on the far face, along the normal the handle supplies)
|
|
162
|
-
* mezzanine, one per plate edge.
|
|
163
|
-
*
|
|
177
|
+
* wall span (on the far face, along the normal the handle supplies), one per
|
|
178
|
+
* straight edge of invisible walls and, for a mezzanine, one per plate edge.
|
|
179
|
+
* Mounted after every node's handles so a wall outline never takes the press.
|
|
180
|
+
* The drags are the 3D arrows' own.
|
|
164
181
|
*/
|
|
165
182
|
export function RoomHandles2D({ levelId }: { levelId: string | null }) {
|
|
166
183
|
const room = useRoomControls()
|
|
@@ -327,6 +344,13 @@ export function RoomHandleDragPreview2D({ levelId }: { levelId: string | null })
|
|
|
327
344
|
const unit = useViewer((s) => s.unit)
|
|
328
345
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
329
346
|
if (!drag || drag.levelId !== levelId || drag.kind === 'elevation') return null
|
|
347
|
+
if (drag.kind === 'wall-group-push')
|
|
348
|
+
return (
|
|
349
|
+
<g data-room-drag-preview-2d pointerEvents="none">
|
|
350
|
+
<PlanRoomDimensions dimensions={drag.dimensions} />
|
|
351
|
+
{drag.message && <PlanReadout at={drag.anchor} refused text={drag.message} />}
|
|
352
|
+
</g>
|
|
353
|
+
)
|
|
330
354
|
const plan = drag as PlanDrag
|
|
331
355
|
const at: Point = [
|
|
332
356
|
plan.anchor[0] + plan.outward[0] * plan.distance,
|
|
@@ -352,3 +376,130 @@ export function RoomHandleDragPreview2D({ levelId }: { levelId: string | null })
|
|
|
352
376
|
</g>
|
|
353
377
|
)
|
|
354
378
|
}
|
|
379
|
+
|
|
380
|
+
function selectionDimensionsOf(node: AnyNode | undefined) {
|
|
381
|
+
return node
|
|
382
|
+
? getFloorplanNodeExtension(nodeRegistry.get(node.type))?.selectionDimensions
|
|
383
|
+
: undefined
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const NO_NODES: readonly AnyNode[] = []
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* The selected nodes that show their temporary dimensions now: Select mode,
|
|
390
|
+
* nothing in progress, Display → Dimensions on, kinds with `selectionDimensions`
|
|
391
|
+
* only (a wall among other selected things still shows its own), on the first
|
|
392
|
+
* one's level. A new array only when one of them changes.
|
|
393
|
+
*/
|
|
394
|
+
export function useSelectionDimensionNodes(): readonly AnyNode[] {
|
|
395
|
+
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
396
|
+
const enabled = useEditor((s) => s.mode === 'select')
|
|
397
|
+
const idle = useInteractionScope((s) => s.scope.kind === 'idle')
|
|
398
|
+
const dragging = useRoomHandleDrag((s) => s.drag !== null)
|
|
399
|
+
const shown = useFloorplanAnnotationVisibility((s) => s.showDimensions)
|
|
400
|
+
const active = enabled && idle && !dragging && shown
|
|
401
|
+
return useScene(
|
|
402
|
+
useShallow((s) => {
|
|
403
|
+
if (!active) return NO_NODES
|
|
404
|
+
const found: AnyNode[] = []
|
|
405
|
+
for (const id of selectedIds) {
|
|
406
|
+
const node = s.nodes[id as AnyNodeId]
|
|
407
|
+
if (!node || !selectionDimensionsOf(node)) continue
|
|
408
|
+
if (found.length && node.parentId !== found[0]!.parentId) continue
|
|
409
|
+
found.push(node)
|
|
410
|
+
}
|
|
411
|
+
return found
|
|
412
|
+
}),
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* The selected nodes' temporary dimensions (`useSelectionDimensionNodes`),
|
|
418
|
+
* from each kind's `selectionDimensions` (a wall: the room widths its push
|
|
419
|
+
* shows and, for an exterior wall, the outside size, as Display → Measure
|
|
420
|
+
* walls asks). Several selected walls show each one's, a room two of them
|
|
421
|
+
* bound measured once. Shared by the plan and the 3D view; typing into one
|
|
422
|
+
* drives the node it runs from. Recomputed only when a measured node or its
|
|
423
|
+
* level's rooms change (the room index keeps its records while unrelated
|
|
424
|
+
* nodes edit).
|
|
425
|
+
*/
|
|
426
|
+
export function useSelectedWallDimensions(): {
|
|
427
|
+
levelId: string
|
|
428
|
+
dimensions: readonly OwnedSelectionDimension[]
|
|
429
|
+
} | null {
|
|
430
|
+
const measured = useSelectionDimensionNodes()
|
|
431
|
+
const faces = useFloorplanAnnotationVisibility((s) => s.wallDimensionFaces)
|
|
432
|
+
const levelId = measured[0]?.parentId ?? null
|
|
433
|
+
const rooms = useRoomRecords(levelId)
|
|
434
|
+
return useMemo(() => {
|
|
435
|
+
// `rooms` is the invalidation key: it changes when the level's room
|
|
436
|
+
// topology, slabs, ceilings or zones do.
|
|
437
|
+
if (!levelId || !rooms) return null
|
|
438
|
+
const nodes = useScene.getState().nodes
|
|
439
|
+
const dimensions = mergeSelectionDimensions(
|
|
440
|
+
measured.map((node) => ({
|
|
441
|
+
ownerId: node.id,
|
|
442
|
+
dimensions: selectionDimensionsOf(node)?.({ node, nodes, faces }) ?? [],
|
|
443
|
+
})),
|
|
444
|
+
)
|
|
445
|
+
return dimensions.length ? { levelId, dimensions } : null
|
|
446
|
+
}, [faces, levelId, measured, rooms])
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* The selected walls' temporary dimensions on the plan, drawn as the push
|
|
451
|
+
* drag draws its room widths (indigo line, end dots). Each is a `dimension`
|
|
452
|
+
* owned by the wall it runs from, so a click on its value opens the plan's
|
|
453
|
+
* type-a-value box (`FloorplanDimensionEditOverlay`) and the typed size slides
|
|
454
|
+
* that wall.
|
|
455
|
+
*/
|
|
456
|
+
export function SelectedWallDimensions2D({ levelId }: { levelId: string | null }) {
|
|
457
|
+
const selection = useSelectedWallDimensions()
|
|
458
|
+
const context = useFloorplanRender()
|
|
459
|
+
const unit = useViewer((s) => s.unit)
|
|
460
|
+
const metricNotation = useViewer((s) => s.metricNotation)
|
|
461
|
+
const owners = useMemo(() => {
|
|
462
|
+
const byOwner = new Map<string, FloorplanGeometry[]>()
|
|
463
|
+
for (const { from, to, distance, ownerId } of selection?.dimensions ?? []) {
|
|
464
|
+
const length = Math.hypot(to[0] - from[0], to[1] - from[1]) || 1
|
|
465
|
+
const geometry: FloorplanGeometry = {
|
|
466
|
+
kind: 'dimension',
|
|
467
|
+
start: from,
|
|
468
|
+
end: to,
|
|
469
|
+
offsetNormal: [-(to[1] - from[1]) / length, (to[0] - from[0]) / length],
|
|
470
|
+
offsetDistance: 0,
|
|
471
|
+
extensionOvershoot: 0,
|
|
472
|
+
extensionStartGap: 0,
|
|
473
|
+
terminator: 'dot',
|
|
474
|
+
text: formatLinearMeasurement(distance, unit, metricNotation),
|
|
475
|
+
stroke: MEASUREMENT_FLOORPLAN_COLOR,
|
|
476
|
+
}
|
|
477
|
+
const list = byOwner.get(ownerId)
|
|
478
|
+
if (list) list.push(geometry)
|
|
479
|
+
else byOwner.set(ownerId, [geometry])
|
|
480
|
+
}
|
|
481
|
+
return [...byOwner].map(([ownerId, children]) => ({
|
|
482
|
+
ownerId,
|
|
483
|
+
geometry: { kind: 'group', children } satisfies FloorplanGeometry,
|
|
484
|
+
}))
|
|
485
|
+
}, [metricNotation, selection, unit])
|
|
486
|
+
if (!selection || selection.levelId !== levelId) return null
|
|
487
|
+
return (
|
|
488
|
+
<g
|
|
489
|
+
data-selected-wall-dimensions-2d=""
|
|
490
|
+
// A press on a value is the type-a-value box's, not the plan's marquee or pan.
|
|
491
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
492
|
+
>
|
|
493
|
+
{owners.map(({ ownerId, geometry }) => (
|
|
494
|
+
<g data-node-id={ownerId} key={ownerId}>
|
|
495
|
+
<FloorplanGeometryRenderer
|
|
496
|
+
geometry={geometry}
|
|
497
|
+
pointerEventsOverride="none"
|
|
498
|
+
sceneRotationDeg={context?.sceneRotationDeg ?? 0}
|
|
499
|
+
screenUnitsPerPixel={context?.unitsPerPixel}
|
|
500
|
+
/>
|
|
501
|
+
</g>
|
|
502
|
+
))}
|
|
503
|
+
</g>
|
|
504
|
+
)
|
|
505
|
+
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
type AnyNodeId,
|
|
4
5
|
clampRoomFloorHandle,
|
|
6
|
+
formatLinearMeasurement,
|
|
5
7
|
type Point,
|
|
6
8
|
roomDrawnFloor,
|
|
9
|
+
roomFloorElevation,
|
|
7
10
|
sceneRegistry,
|
|
11
|
+
usePascalI18n,
|
|
8
12
|
useScene,
|
|
9
13
|
} from '@pascal-app/core'
|
|
10
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
11
|
-
import { Html } from '@react-three/drei'
|
|
14
|
+
import { MEASUREMENT_ACTIVE_COLOR, useViewer } from '@pascal-app/viewer'
|
|
12
15
|
import { type ThreeEvent, useFrame, useThree } from '@react-three/fiber'
|
|
13
16
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
14
17
|
import {
|
|
@@ -33,7 +36,7 @@ import {
|
|
|
33
36
|
publishStructuralElevationGuide,
|
|
34
37
|
resolveStructuralElevationSnap,
|
|
35
38
|
} from '../../lib/elevation-guides'
|
|
36
|
-
import {
|
|
39
|
+
import { parseDimensionInput } from '../../lib/floorplan/dimension-drive'
|
|
37
40
|
import { commitRoomFloorElevation, roomFloorBase } from '../../lib/room-construction-commands'
|
|
38
41
|
import {
|
|
39
42
|
type MezzanineEdgeHandle,
|
|
@@ -42,7 +45,6 @@ import {
|
|
|
42
45
|
ownFloorElevationBounds,
|
|
43
46
|
ROOM_ELEVATION_DRAG_LABEL,
|
|
44
47
|
type RoomHandleDrag,
|
|
45
|
-
roomFloorElevation,
|
|
46
48
|
roomPushHandles,
|
|
47
49
|
runMezzanineEdgeDrag,
|
|
48
50
|
runRoomHandleDrag,
|
|
@@ -51,10 +53,16 @@ import {
|
|
|
51
53
|
import type { RoomDimension } from '../../lib/room-push-dimensions'
|
|
52
54
|
import type { RoomSelectionRecord } from '../../lib/room-selection'
|
|
53
55
|
import useEditor, { isGridSnapActive, isMagneticSnapActive } from '../../store/use-editor'
|
|
56
|
+
import {
|
|
57
|
+
commitDimensionValue,
|
|
58
|
+
useDimensionEditAllowed,
|
|
59
|
+
} from '../editor-2d/renderers/floorplan-dimension-edit-overlay'
|
|
54
60
|
import { suppressBoxSelectForPointer } from '../tools/select/box-select-state'
|
|
55
61
|
import { ARROW_COLOR, ARROW_SCALE, HandleArrow } from './handles/handle-arrow'
|
|
56
62
|
import { resolveResizeSnapValue } from './handles/resize-snap'
|
|
63
|
+
import { Html } from './localized-html'
|
|
57
64
|
import { formatMeasurement } from './measurement-pill'
|
|
65
|
+
import { useSelectedWallDimensions } from './room-handles-2d'
|
|
58
66
|
import { WallPushArrow } from './wall-push-arrow'
|
|
59
67
|
|
|
60
68
|
const PILL =
|
|
@@ -295,8 +303,9 @@ export function ElevationArrows({
|
|
|
295
303
|
|
|
296
304
|
/**
|
|
297
305
|
* A selected room's 3D handles: the floor elevation tracker at its centroid
|
|
298
|
-
* and one push/pull arrow outside every boundary wall
|
|
299
|
-
* frame by `RoomControls3D`, only
|
|
306
|
+
* and one push/pull arrow outside every boundary wall and every straight edge
|
|
307
|
+
* of invisible walls. Rendered in the level frame by `RoomControls3D`, only
|
|
308
|
+
* while nothing else is in progress.
|
|
300
309
|
*/
|
|
301
310
|
export function RoomHandles3D({ room }: { room: RoomSelectionRecord }) {
|
|
302
311
|
const nodes = useScene((s) => s.nodes)
|
|
@@ -307,7 +316,11 @@ export function RoomHandles3D({ room }: { room: RoomSelectionRecord }) {
|
|
|
307
316
|
() => outlineCentroid([room.polygon as Point[], ...(room.holes as Point[][])]),
|
|
308
317
|
[room.polygon, room.holes],
|
|
309
318
|
)
|
|
310
|
-
|
|
319
|
+
// Its invisible walls' arrows lie on its floor, where their dashed lines are drawn.
|
|
320
|
+
const handles = useMemo(
|
|
321
|
+
() => roomPushHandles(nodes, room.spans, elevation),
|
|
322
|
+
[nodes, room.spans, elevation],
|
|
323
|
+
)
|
|
311
324
|
// A mezzanine has no walls: its plate edges carry the push arrows instead.
|
|
312
325
|
const edges = useMemo(
|
|
313
326
|
() => (room.mezzanine ? mezzanineEdgeHandles(nodes, room.zoneId) : []),
|
|
@@ -414,9 +427,20 @@ const DIMENSION_LABEL_OUTLINE = [
|
|
|
414
427
|
* above its room's floor and drawn over the walls, closed by cross ticks, with
|
|
415
428
|
* the measurement's outlined label — apart from the moved-distance pill.
|
|
416
429
|
*/
|
|
417
|
-
function RoomDimensionLines3D({
|
|
430
|
+
function RoomDimensionLines3D({
|
|
431
|
+
dimensions,
|
|
432
|
+
hidden = false,
|
|
433
|
+
}: {
|
|
434
|
+
/** With an `ownerId` (a selected wall's), a value can be typed to move that wall. */
|
|
435
|
+
dimensions: readonly (RoomDimension & { ownerId?: string })[]
|
|
436
|
+
/** Hides the labels while keeping their drei `Html` roots mounted. */
|
|
437
|
+
hidden?: boolean
|
|
438
|
+
}) {
|
|
439
|
+
const i18n = usePascalI18n()
|
|
418
440
|
const unit = useViewer((s) => s.unit)
|
|
419
441
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
442
|
+
const editAllowed = useDimensionEditAllowed()
|
|
443
|
+
const [editing, setEditing] = useState<string | null>(null)
|
|
420
444
|
const geometry = useMemo(() => {
|
|
421
445
|
if (!dimensions.length) return null
|
|
422
446
|
const positions: number[] = []
|
|
@@ -441,27 +465,175 @@ function RoomDimensionLines3D({ dimensions }: { dimensions: RoomDimension[] }) {
|
|
|
441
465
|
raycast={noRaycast}
|
|
442
466
|
renderOrder={1001}
|
|
443
467
|
/>
|
|
444
|
-
{dimensions.map((dimension) =>
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
className="-translate-y-3 whitespace-nowrap font-medium text-base text-white"
|
|
458
|
-
data-room-dimension-3d
|
|
459
|
-
style={{ textShadow: DIMENSION_LABEL_OUTLINE }}
|
|
468
|
+
{dimensions.map(({ ownerId: wallId, ...dimension }) => {
|
|
469
|
+
const editable = editAllowed && !!wallId
|
|
470
|
+
return (
|
|
471
|
+
<Html
|
|
472
|
+
center
|
|
473
|
+
key={dimension.key}
|
|
474
|
+
position={[
|
|
475
|
+
(dimension.from[0] + dimension.to[0]) / 2,
|
|
476
|
+
dimension.elevation,
|
|
477
|
+
(dimension.from[1] + dimension.to[1]) / 2,
|
|
478
|
+
]}
|
|
479
|
+
style={{ display: hidden ? 'none' : undefined, pointerEvents: 'none' }}
|
|
480
|
+
zIndexRange={[20, 0]}
|
|
460
481
|
>
|
|
461
|
-
{
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
482
|
+
{editable && wallId && !hidden && editing === dimension.key ? (
|
|
483
|
+
<DimensionValueInput
|
|
484
|
+
dimension={dimension}
|
|
485
|
+
initial={formatMeasurement(dimension.distance, unit, metricNotation)}
|
|
486
|
+
onClose={() => setEditing(null)}
|
|
487
|
+
unit={unit}
|
|
488
|
+
wallId={wallId}
|
|
489
|
+
/>
|
|
490
|
+
) : (
|
|
491
|
+
<div
|
|
492
|
+
className={`-translate-y-3 whitespace-nowrap font-medium text-base text-white ${editable ? 'pointer-events-auto cursor-text' : ''}`}
|
|
493
|
+
data-room-dimension-3d
|
|
494
|
+
onClick={editable ? () => setEditing(dimension.key) : undefined}
|
|
495
|
+
onKeyDown={
|
|
496
|
+
editable
|
|
497
|
+
? (event) => {
|
|
498
|
+
if (event.key === 'Enter') setEditing(dimension.key)
|
|
499
|
+
}
|
|
500
|
+
: undefined
|
|
501
|
+
}
|
|
502
|
+
onPointerDown={editable ? (event) => event.stopPropagation() : undefined}
|
|
503
|
+
role={editable ? 'button' : undefined}
|
|
504
|
+
style={{ textShadow: DIMENSION_LABEL_OUTLINE }}
|
|
505
|
+
tabIndex={editable ? 0 : undefined}
|
|
506
|
+
title={
|
|
507
|
+
editable
|
|
508
|
+
? i18n._({
|
|
509
|
+
id: 'editor.roomHandles.typeASizeToMoveThe',
|
|
510
|
+
message: 'Type a size to move the wall',
|
|
511
|
+
})
|
|
512
|
+
: undefined
|
|
513
|
+
}
|
|
514
|
+
>
|
|
515
|
+
{formatMeasurement(dimension.distance, unit, metricNotation)}
|
|
516
|
+
</div>
|
|
517
|
+
)}
|
|
518
|
+
</Html>
|
|
519
|
+
)
|
|
520
|
+
})}
|
|
521
|
+
</group>
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* A temporary dimension's value, typed: Enter moves the selected wall so the
|
|
527
|
+
* dimension reads it (`commitDimensionValue`, one undo step), Esc or a click
|
|
528
|
+
* away leaves it. Feet-inches, metric and bare numbers parse as in the plan.
|
|
529
|
+
*/
|
|
530
|
+
function DimensionValueInput({
|
|
531
|
+
dimension,
|
|
532
|
+
initial,
|
|
533
|
+
onClose,
|
|
534
|
+
unit,
|
|
535
|
+
wallId,
|
|
536
|
+
}: {
|
|
537
|
+
dimension: RoomDimension
|
|
538
|
+
initial: string
|
|
539
|
+
onClose: () => void
|
|
540
|
+
unit: 'metric' | 'imperial'
|
|
541
|
+
wallId: string
|
|
542
|
+
}) {
|
|
543
|
+
const i18n = usePascalI18n()
|
|
544
|
+
const [draft, setDraft] = useState(initial)
|
|
545
|
+
const [error, setError] = useState<string | null>(null)
|
|
546
|
+
const commit = () => {
|
|
547
|
+
const nextLength = parseDimensionInput(draft, unit)
|
|
548
|
+
if (nextLength === null) {
|
|
549
|
+
setError(
|
|
550
|
+
i18n._({
|
|
551
|
+
id: 'editor.roomHandles.cannotReadInput',
|
|
552
|
+
message: 'Cannot read "{input}"',
|
|
553
|
+
values: { input: draft.trim() },
|
|
554
|
+
}),
|
|
555
|
+
)
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
const outcome = commitDimensionValue({
|
|
559
|
+
target: {
|
|
560
|
+
ownerNodeId: wallId as AnyNodeId,
|
|
561
|
+
witnessStart: dimension.from,
|
|
562
|
+
witnessEnd: dimension.to,
|
|
563
|
+
value: dimension.distance,
|
|
564
|
+
},
|
|
565
|
+
nextLength,
|
|
566
|
+
unit,
|
|
567
|
+
})
|
|
568
|
+
if (outcome.ok) onClose()
|
|
569
|
+
else setError(outcome.reason)
|
|
570
|
+
}
|
|
571
|
+
return (
|
|
572
|
+
<div
|
|
573
|
+
className="pointer-events-auto -translate-y-3 w-24"
|
|
574
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
575
|
+
>
|
|
576
|
+
<input
|
|
577
|
+
// biome-ignore lint/a11y/noAutofocus: the box opens on a click to be typed in
|
|
578
|
+
autoFocus
|
|
579
|
+
aria-label={i18n._({ id: 'editor.roomHandles.dimensionValue', message: 'Dimension value' })}
|
|
580
|
+
className="w-full rounded-sm border border-primary bg-card px-1 py-0.5 text-center font-mono text-foreground text-xs shadow-sm outline-none"
|
|
581
|
+
onBlur={onClose}
|
|
582
|
+
onChange={(event) => {
|
|
583
|
+
setDraft(event.target.value)
|
|
584
|
+
setError(null)
|
|
585
|
+
}}
|
|
586
|
+
onFocus={(event) => event.target.select()}
|
|
587
|
+
onKeyDown={(event) => {
|
|
588
|
+
event.stopPropagation()
|
|
589
|
+
if (event.key === 'Enter') {
|
|
590
|
+
event.preventDefault()
|
|
591
|
+
commit()
|
|
592
|
+
} else if (event.key === 'Escape') {
|
|
593
|
+
event.preventDefault()
|
|
594
|
+
onClose()
|
|
595
|
+
}
|
|
596
|
+
}}
|
|
597
|
+
spellCheck={false}
|
|
598
|
+
value={draft}
|
|
599
|
+
/>
|
|
600
|
+
{error ? (
|
|
601
|
+
<div className="mt-0.5 whitespace-nowrap rounded-sm bg-card px-1 text-[10px] text-destructive shadow-sm">
|
|
602
|
+
{error}
|
|
603
|
+
</div>
|
|
604
|
+
) : null}
|
|
605
|
+
</div>
|
|
606
|
+
)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* The selected walls' temporary dimensions in 3D, the plan's twin
|
|
611
|
+
* (`SelectedWallDimensions2D`): the room widths their pushes show and the
|
|
612
|
+
* outside sizes, drawn as the push draws them, in the walls' level frame.
|
|
613
|
+
*/
|
|
614
|
+
export function SelectedWallDimensions3D() {
|
|
615
|
+
const selection = useSelectedWallDimensions()
|
|
616
|
+
const isFloorplanHovered = useEditor((s) => s.isFloorplanHovered)
|
|
617
|
+
const levelId = selection?.levelId ?? null
|
|
618
|
+
const root = useRef<Group>(null)
|
|
619
|
+
useFrame(() => {
|
|
620
|
+
const level = levelId ? sceneRegistry.nodes.get(levelId) : null
|
|
621
|
+
if (!root.current) return
|
|
622
|
+
root.current.visible = !!level && !isFloorplanHovered
|
|
623
|
+
if (level) {
|
|
624
|
+
level.updateWorldMatrix(true, false)
|
|
625
|
+
root.current.matrix.copy(level.matrixWorld)
|
|
626
|
+
}
|
|
627
|
+
})
|
|
628
|
+
// The labels are DOM (drei `Html`); with no document (a headless
|
|
629
|
+
// renderer) there is nothing to draw them into.
|
|
630
|
+
if (!selection || typeof document === 'undefined') return null
|
|
631
|
+
// Hovering the plan hides the dimensions rather than unmounting them: drei
|
|
632
|
+
// `Html` unmounts its DOM root synchronously, which React reports as an error
|
|
633
|
+
// when it happens inside a render.
|
|
634
|
+
return (
|
|
635
|
+
<group matrixAutoUpdate={false} ref={root}>
|
|
636
|
+
<RoomDimensionLines3D dimensions={selection.dimensions} hidden={isFloorplanHovered} />
|
|
465
637
|
</group>
|
|
466
638
|
)
|
|
467
639
|
}
|
|
@@ -524,6 +696,7 @@ function MezzanineEdgeArrow({
|
|
|
524
696
|
}
|
|
525
697
|
|
|
526
698
|
export function RoomHandleDragPreview3D() {
|
|
699
|
+
const i18n = usePascalI18n()
|
|
527
700
|
const drag = useRoomHandleDrag((s) => s.drag)
|
|
528
701
|
const unit = useViewer((s) => s.unit)
|
|
529
702
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
@@ -614,7 +787,9 @@ export function RoomHandleDragPreview3D() {
|
|
|
614
787
|
/>
|
|
615
788
|
<Html center position={[ax, drag.value + 0.35, az]} zIndexRange={[25, 0]}>
|
|
616
789
|
<div className={PILL}>
|
|
617
|
-
<span className="font-medium text-foreground">
|
|
790
|
+
<span className="font-medium text-foreground">
|
|
791
|
+
{i18n._({ id: 'editor.roomHandles.floor', message: 'Floor' })}
|
|
792
|
+
</span>
|
|
618
793
|
<span className="text-muted-foreground">
|
|
619
794
|
{formatMeasurement(
|
|
620
795
|
drag.value - roomFloorBase(useScene.getState().nodes, drag.zoneId),
|
|
@@ -627,6 +802,45 @@ export function RoomHandleDragPreview3D() {
|
|
|
627
802
|
</group>
|
|
628
803
|
)
|
|
629
804
|
}
|
|
805
|
+
const ghosts = drag.ghosts.map((ghost, index) => {
|
|
806
|
+
const length = Math.hypot(ghost.end[0] - ghost.start[0], ghost.end[1] - ghost.start[1])
|
|
807
|
+
if (length < 1e-3) return null
|
|
808
|
+
return (
|
|
809
|
+
<mesh
|
|
810
|
+
key={index}
|
|
811
|
+
layers={EDITOR_LAYER}
|
|
812
|
+
material={materials.fill}
|
|
813
|
+
position={[
|
|
814
|
+
(ghost.start[0] + ghost.end[0]) / 2,
|
|
815
|
+
drag.base + ghost.height / 2,
|
|
816
|
+
(ghost.start[1] + ghost.end[1]) / 2,
|
|
817
|
+
]}
|
|
818
|
+
raycast={noRaycast}
|
|
819
|
+
renderOrder={99}
|
|
820
|
+
rotation={[0, -Math.atan2(ghost.end[1] - ghost.start[1], ghost.end[0] - ghost.start[0]), 0]}
|
|
821
|
+
>
|
|
822
|
+
<boxGeometry args={[length, ghost.height, ghost.thickness]} />
|
|
823
|
+
</mesh>
|
|
824
|
+
)
|
|
825
|
+
})
|
|
826
|
+
if (drag.kind === 'wall-group-push')
|
|
827
|
+
return (
|
|
828
|
+
<group>
|
|
829
|
+
<RoomDimensionLines3D dimensions={drag.dimensions} />
|
|
830
|
+
{ghosts}
|
|
831
|
+
{drag.message && (
|
|
832
|
+
<Html
|
|
833
|
+
center
|
|
834
|
+
position={[drag.anchor[0], drag.height + 0.45, drag.anchor[1]]}
|
|
835
|
+
zIndexRange={[25, 0]}
|
|
836
|
+
>
|
|
837
|
+
<div className={PILL}>
|
|
838
|
+
<span className="font-medium text-red-500">{drag.message}</span>
|
|
839
|
+
</div>
|
|
840
|
+
</Html>
|
|
841
|
+
)}
|
|
842
|
+
</group>
|
|
843
|
+
)
|
|
630
844
|
const at: Point = [
|
|
631
845
|
drag.anchor[0] + drag.outward[0] * drag.distance,
|
|
632
846
|
drag.anchor[1] + drag.outward[1] * drag.distance,
|
|
@@ -636,31 +850,7 @@ export function RoomHandleDragPreview3D() {
|
|
|
636
850
|
return (
|
|
637
851
|
<group>
|
|
638
852
|
<RoomDimensionLines3D dimensions={drag.dimensions} />
|
|
639
|
-
{
|
|
640
|
-
const length = Math.hypot(ghost.end[0] - ghost.start[0], ghost.end[1] - ghost.start[1])
|
|
641
|
-
if (length < 1e-3) return null
|
|
642
|
-
return (
|
|
643
|
-
<mesh
|
|
644
|
-
key={index}
|
|
645
|
-
layers={EDITOR_LAYER}
|
|
646
|
-
material={materials.fill}
|
|
647
|
-
position={[
|
|
648
|
-
(ghost.start[0] + ghost.end[0]) / 2,
|
|
649
|
-
drag.base + ghost.height / 2,
|
|
650
|
-
(ghost.start[1] + ghost.end[1]) / 2,
|
|
651
|
-
]}
|
|
652
|
-
raycast={noRaycast}
|
|
653
|
-
renderOrder={99}
|
|
654
|
-
rotation={[
|
|
655
|
-
0,
|
|
656
|
-
-Math.atan2(ghost.end[1] - ghost.start[1], ghost.end[0] - ghost.start[0]),
|
|
657
|
-
0,
|
|
658
|
-
]}
|
|
659
|
-
>
|
|
660
|
-
<boxGeometry args={[length, ghost.height, ghost.thickness]} />
|
|
661
|
-
</mesh>
|
|
662
|
-
)
|
|
663
|
-
})}
|
|
853
|
+
{ghosts}
|
|
664
854
|
<Html center position={[at[0], drag.height + 0.45, at[1]]} zIndexRange={[25, 0]}>
|
|
665
855
|
<div className={PILL}>
|
|
666
856
|
{drag.message ? (
|
|
@@ -25,6 +25,7 @@ import { describeZoneOutline, type RoomSelectionRecord } from '../../lib/room-se
|
|
|
25
25
|
import useEditor from '../../store/use-editor'
|
|
26
26
|
import useInteractionScope from '../../store/use-interaction-scope'
|
|
27
27
|
import { RoomControls2D, RoomControls3D } from './room-controls'
|
|
28
|
+
import { SelectedWallDimensions3D } from './room-handles'
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* A zone reached as a zone (hovered or selected through its area or pill, not
|
|
@@ -188,6 +189,7 @@ export function RoomHighlight3D() {
|
|
|
188
189
|
return (
|
|
189
190
|
<>
|
|
190
191
|
<RoomControls3D />
|
|
192
|
+
<SelectedWallDimensions3D />
|
|
191
193
|
{room ? <RoomMesh room={room} /> : null}
|
|
192
194
|
</>
|
|
193
195
|
)
|