@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,12 +1,14 @@
|
|
|
1
|
+
import type { MessageDescriptor } from '@pascal-app/core'
|
|
2
|
+
|
|
1
3
|
export type ContextualShortcutHint = {
|
|
2
4
|
// A combo of keys pressed together (rendered joined by "+"). An entry may
|
|
3
5
|
// itself be an array of alternatives (rendered joined by "/"), e.g.
|
|
4
6
|
// [['Cmd/Ctrl', 'Shift'], 'Left click'] → "⌘ / ⇧ + click".
|
|
5
7
|
keys: Array<string | string[]>
|
|
6
|
-
label: string
|
|
8
|
+
label: MessageDescriptor | string
|
|
7
9
|
// Optional secondary line under the label for a terser qualifier
|
|
8
10
|
// (e.g. "disable 15° snap"). The HUD wraps both lines rather than truncating.
|
|
9
|
-
subtitle?: string
|
|
11
|
+
subtitle?: MessageDescriptor | string
|
|
10
12
|
active?: boolean
|
|
11
13
|
}
|
|
12
14
|
|
|
@@ -29,11 +31,25 @@ export const RESIZE_HANDLE_DRAG_LABEL = 'resize-handle'
|
|
|
29
31
|
export const GROUP_MOVE_DRAG_LABEL = 'group-move-handle'
|
|
30
32
|
export const GROUP_ROTATE_DRAG_LABEL = 'group-rotate-handle'
|
|
31
33
|
|
|
34
|
+
// `activeHandleDrag.label`s for the MEP handles, in 3D and on the plan: one
|
|
35
|
+
// point of a run, where Alt detaches the joint, and a fitting, a run or one
|
|
36
|
+
// run segment slid along an axis. Their snap context is the kind's own.
|
|
37
|
+
export const PATH_POINT_DRAG_LABEL = 'path-point'
|
|
38
|
+
export const AXIS_MOVE_DRAG_LABEL = 'axis-move'
|
|
39
|
+
|
|
32
40
|
export function resolveRotateHandleHelpHints(altPressed: boolean): ContextualShortcutHint[] {
|
|
33
41
|
return [
|
|
34
42
|
{
|
|
35
43
|
keys: [ALT_KEY],
|
|
36
|
-
label: altPressed
|
|
44
|
+
label: altPressed
|
|
45
|
+
? /* i18n */ {
|
|
46
|
+
id: 'editor.lib.contextualHelp.rotatingFreely',
|
|
47
|
+
message: 'Rotating freely (no angle step)',
|
|
48
|
+
}
|
|
49
|
+
: /* i18n */ {
|
|
50
|
+
id: 'editor.lib.contextualHelp.holdToRotateFreely',
|
|
51
|
+
message: 'Hold to rotate freely',
|
|
52
|
+
},
|
|
37
53
|
active: altPressed,
|
|
38
54
|
},
|
|
39
55
|
]
|
|
@@ -77,7 +93,10 @@ export function resolveSelectModeHelpHints({
|
|
|
77
93
|
|
|
78
94
|
hints.push({
|
|
79
95
|
keys: [[COMMAND_KEY, SHIFT_KEY], LEFT_CLICK],
|
|
80
|
-
label:
|
|
96
|
+
label: /* i18n */ {
|
|
97
|
+
id: 'editor.lib.contextualHelp.addOrRemoveObjectsFromThe',
|
|
98
|
+
message: 'Add or remove objects from the selection',
|
|
99
|
+
},
|
|
81
100
|
active: true,
|
|
82
101
|
})
|
|
83
102
|
return hints
|
|
@@ -89,23 +108,47 @@ export function resolveSelectModeHelpHints({
|
|
|
89
108
|
if (selectedCount > 1) {
|
|
90
109
|
hints.push({
|
|
91
110
|
keys: [LEFT_CLICK],
|
|
92
|
-
label:
|
|
111
|
+
label: /* i18n */ {
|
|
112
|
+
id: 'editor.lib.contextualHelp.clickOrDragTheSelectionTo',
|
|
113
|
+
message: 'Click or drag the selection to move it as one',
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
hints.push({
|
|
117
|
+
keys: [ROTATE_KEYS],
|
|
118
|
+
label: /* i18n */ {
|
|
119
|
+
id: 'editor.lib.contextualHelp.rotateTheSelection45',
|
|
120
|
+
message: 'Rotate the selection ±45°',
|
|
121
|
+
},
|
|
93
122
|
})
|
|
94
|
-
hints.push({ keys: [ROTATE_KEYS], label: 'Rotate the selection ±45°' })
|
|
95
123
|
hints.push({
|
|
96
124
|
keys: [COMMAND_KEY, 'G'],
|
|
97
|
-
label:
|
|
125
|
+
label: /* i18n */ {
|
|
126
|
+
id: 'editor.lib.contextualHelp.groupSelectionSessionOnly',
|
|
127
|
+
message: 'Group selection (session only)',
|
|
128
|
+
},
|
|
98
129
|
})
|
|
99
130
|
hints.push({
|
|
100
131
|
keys: [COMMAND_KEY, SHIFT_KEY, 'G'],
|
|
101
|
-
label:
|
|
132
|
+
label: /* i18n */ {
|
|
133
|
+
id: 'editor.lib.contextualHelp.ungroupSessionSelection',
|
|
134
|
+
message: 'Ungroup session selection',
|
|
135
|
+
},
|
|
102
136
|
})
|
|
103
137
|
hints.push({
|
|
104
138
|
keys: [[COMMAND_KEY, SHIFT_KEY], LEFT_CLICK],
|
|
105
|
-
label:
|
|
139
|
+
label: /* i18n */ {
|
|
140
|
+
id: 'editor.lib.contextualHelp.addOrRemoveObjectsFromThe',
|
|
141
|
+
message: 'Add or remove objects from the selection',
|
|
142
|
+
},
|
|
106
143
|
active: commandPressed || shiftPressed,
|
|
107
144
|
})
|
|
108
|
-
hints.push({
|
|
145
|
+
hints.push({
|
|
146
|
+
keys: [ESC_KEY],
|
|
147
|
+
label: /* i18n */ {
|
|
148
|
+
id: 'editor.lib.contextualHelp.clearTheSelectionOrClickOutside',
|
|
149
|
+
message: 'Clear the selection (or click outside)',
|
|
150
|
+
},
|
|
151
|
+
})
|
|
109
152
|
return hints
|
|
110
153
|
}
|
|
111
154
|
|
|
@@ -115,12 +158,39 @@ export function resolveSelectModeHelpHints({
|
|
|
115
158
|
// detaches the joint mid-drag; a fitting's cluster adds rotate arcs, with
|
|
116
159
|
// R / T (and Alt to switch axis) for keyboard rotation.
|
|
117
160
|
if (mepSelection === 'run') {
|
|
118
|
-
hints.push({
|
|
119
|
-
|
|
161
|
+
hints.push({
|
|
162
|
+
keys: [CLICK],
|
|
163
|
+
label: /* i18n */ {
|
|
164
|
+
id: 'editor.lib.contextualHelp.clickAHandleDotToShow',
|
|
165
|
+
message: 'Click a handle dot to show move arrows',
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
hints.push({
|
|
169
|
+
keys: [ALT_KEY],
|
|
170
|
+
label: /* i18n */ {
|
|
171
|
+
id: 'editor.lib.contextualHelp.detachTheJointWhileDraggingAn',
|
|
172
|
+
message: 'Detach the joint while dragging an arrow',
|
|
173
|
+
},
|
|
174
|
+
})
|
|
120
175
|
} else if (mepSelection === 'fitting') {
|
|
121
|
-
hints.push({
|
|
122
|
-
|
|
123
|
-
|
|
176
|
+
hints.push({
|
|
177
|
+
keys: [CLICK],
|
|
178
|
+
label: /* i18n */ {
|
|
179
|
+
id: 'editor.lib.contextualHelp.clickTheHandleDotToShow',
|
|
180
|
+
message: 'Click the handle dot to show move + rotate handles',
|
|
181
|
+
},
|
|
182
|
+
})
|
|
183
|
+
hints.push({
|
|
184
|
+
keys: [ROTATE_KEYS],
|
|
185
|
+
label: /* i18n */ { id: 'editor.lib.contextualHelp.rotate45', message: 'Rotate ±45°' },
|
|
186
|
+
})
|
|
187
|
+
hints.push({
|
|
188
|
+
keys: [ALT_KEY],
|
|
189
|
+
label: /* i18n */ {
|
|
190
|
+
id: 'editor.lib.contextualHelp.switchTheRotationAxisYX',
|
|
191
|
+
message: 'Switch the rotation axis (Y → X → Z)',
|
|
192
|
+
},
|
|
193
|
+
})
|
|
124
194
|
}
|
|
125
195
|
|
|
126
196
|
// The rows are the same whatever modifier is held — guides/snapping are
|
|
@@ -130,28 +200,40 @@ export function resolveSelectModeHelpHints({
|
|
|
130
200
|
if (hasMovableSelection) {
|
|
131
201
|
hints.push({
|
|
132
202
|
keys: [LEFT_CLICK],
|
|
133
|
-
label:
|
|
203
|
+
label: /* i18n */ {
|
|
204
|
+
id: 'editor.lib.contextualHelp.dragSelectedMovableObject',
|
|
205
|
+
message: 'Drag selected movable object',
|
|
206
|
+
},
|
|
134
207
|
})
|
|
135
208
|
}
|
|
136
209
|
|
|
137
210
|
if (hasRotatableSelection) {
|
|
138
211
|
hints.push({
|
|
139
212
|
keys: [COMMAND_KEY, RIGHT_CLICK],
|
|
140
|
-
label:
|
|
213
|
+
label: /* i18n */ {
|
|
214
|
+
id: 'editor.lib.contextualHelp.dragLeftOrRightToRotate',
|
|
215
|
+
message: 'Drag left or right to rotate selected object',
|
|
216
|
+
},
|
|
141
217
|
})
|
|
142
218
|
}
|
|
143
219
|
|
|
144
220
|
if (hasOpeningRadiusSelection) {
|
|
145
221
|
hints.push({
|
|
146
222
|
keys: [SHIFT_KEY],
|
|
147
|
-
label:
|
|
223
|
+
label: /* i18n */ {
|
|
224
|
+
id: 'editor.lib.contextualHelp.adjustOneSideSCornerRadius',
|
|
225
|
+
message: 'Adjust one side’s corner radius',
|
|
226
|
+
},
|
|
148
227
|
active: shiftPressed,
|
|
149
228
|
})
|
|
150
229
|
}
|
|
151
230
|
|
|
152
231
|
hints.push({
|
|
153
232
|
keys: [[COMMAND_KEY, SHIFT_KEY], LEFT_CLICK],
|
|
154
|
-
label:
|
|
233
|
+
label: /* i18n */ {
|
|
234
|
+
id: 'editor.lib.contextualHelp.addOrRemoveObjectsFromThe',
|
|
235
|
+
message: 'Add or remove objects from the selection',
|
|
236
|
+
},
|
|
155
237
|
active: commandPressed || shiftPressed,
|
|
156
238
|
})
|
|
157
239
|
|
package/src/lib/continuation.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MessageDescriptor } from '@pascal-app/core'
|
|
1
2
|
export type ContinuationContext = 'wall' | 'fence' | 'point' | 'cabinet' | 'canopy'
|
|
2
3
|
export type ContinuationMode = string
|
|
3
4
|
|
|
@@ -6,7 +7,7 @@ export const CONTINUATION_PROFILES: Record<
|
|
|
6
7
|
{
|
|
7
8
|
options: ContinuationMode[]
|
|
8
9
|
default: ContinuationMode
|
|
9
|
-
labels: Record<string,
|
|
10
|
+
labels: Record<string, MessageDescriptor>
|
|
10
11
|
icons: Record<string, string>
|
|
11
12
|
/**
|
|
12
13
|
* Picked once from the Build panel before drawing (`wall-draw-variant.ts`),
|
|
@@ -17,11 +18,19 @@ export const CONTINUATION_PROFILES: Record<
|
|
|
17
18
|
> = {
|
|
18
19
|
// The wall tool's drawing mode: a chain that closes into a room ('room', the
|
|
19
20
|
// Build panel's Polygon), one wall per two clicks ('single', Walls), or a
|
|
20
|
-
// two-corner box of four walls ('rectangle', Rectangle).
|
|
21
|
+
// two-corner box of four walls ('rectangle', Rectangle). The last one picked
|
|
22
|
+
// is what B arms; Rectangle until one is.
|
|
21
23
|
wall: {
|
|
22
24
|
options: ['room', 'single', 'rectangle'],
|
|
23
|
-
default: '
|
|
24
|
-
labels: {
|
|
25
|
+
default: 'rectangle',
|
|
26
|
+
labels: {
|
|
27
|
+
room: /* i18n */ { id: 'editor.lib.continuation.polygonRoom', message: 'Polygon room' },
|
|
28
|
+
single: /* i18n */ { id: 'editor.lib.continuation.walls', message: 'Walls' },
|
|
29
|
+
rectangle: /* i18n */ {
|
|
30
|
+
id: 'editor.lib.continuation.rectangleRoom',
|
|
31
|
+
message: 'Rectangle room',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
25
34
|
icons: { room: 'lucide:pentagon', single: 'lucide:minus', rectangle: 'lucide:square' },
|
|
26
35
|
chosenInPanel: true,
|
|
27
36
|
},
|
|
@@ -29,10 +38,13 @@ export const CONTINUATION_PROFILES: Record<
|
|
|
29
38
|
options: ['single', 'continuous', 'curved', 'freehand'],
|
|
30
39
|
default: 'continuous',
|
|
31
40
|
labels: {
|
|
32
|
-
continuous: 'Continuous',
|
|
33
|
-
single: 'Single fence',
|
|
34
|
-
curved: 'Curved fence',
|
|
35
|
-
freehand:
|
|
41
|
+
continuous: /* i18n */ { id: 'editor.lib.continuation.continuous', message: 'Continuous' },
|
|
42
|
+
single: /* i18n */ { id: 'editor.lib.continuation.singleFence', message: 'Single fence' },
|
|
43
|
+
curved: /* i18n */ { id: 'editor.lib.continuation.curvedFence', message: 'Curved fence' },
|
|
44
|
+
freehand: /* i18n */ {
|
|
45
|
+
id: 'editor.lib.continuation.freehandFence',
|
|
46
|
+
message: 'Freehand fence',
|
|
47
|
+
},
|
|
36
48
|
},
|
|
37
49
|
icons: {
|
|
38
50
|
continuous: 'lucide:waypoints',
|
|
@@ -44,19 +56,34 @@ export const CONTINUATION_PROFILES: Record<
|
|
|
44
56
|
point: {
|
|
45
57
|
options: ['once', 'repeat'],
|
|
46
58
|
default: 'once',
|
|
47
|
-
labels: {
|
|
59
|
+
labels: {
|
|
60
|
+
once: /* i18n */ { id: 'editor.lib.continuation.placeOnce', message: 'Place once' },
|
|
61
|
+
repeat: /* i18n */ { id: 'editor.lib.continuation.placeMultiple', message: 'Place multiple' },
|
|
62
|
+
},
|
|
48
63
|
icons: { once: 'lucide:target', repeat: 'lucide:copy-plus' },
|
|
49
64
|
},
|
|
50
65
|
cabinet: {
|
|
51
66
|
options: ['single', 'continuous'],
|
|
52
67
|
default: 'single',
|
|
53
|
-
labels: {
|
|
68
|
+
labels: {
|
|
69
|
+
single: /* i18n */ { id: 'editor.lib.continuation.singleCabinet', message: 'Single cabinet' },
|
|
70
|
+
continuous: /* i18n */ {
|
|
71
|
+
id: 'editor.lib.continuation.continuousRun',
|
|
72
|
+
message: 'Continuous run',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
54
75
|
icons: { single: 'lucide:minus', continuous: 'lucide:waypoints' },
|
|
55
76
|
},
|
|
56
77
|
canopy: {
|
|
57
78
|
options: ['single', 'continuous'],
|
|
58
79
|
default: 'single',
|
|
59
|
-
labels: {
|
|
80
|
+
labels: {
|
|
81
|
+
single: /* i18n */ { id: 'editor.lib.continuation.singleCanopy', message: 'Single canopy' },
|
|
82
|
+
continuous: /* i18n */ {
|
|
83
|
+
id: 'editor.lib.continuation.continuousCanopy',
|
|
84
|
+
message: 'Continuous canopy',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
60
87
|
icons: { single: 'lucide:minus', continuous: 'lucide:waypoints' },
|
|
61
88
|
},
|
|
62
89
|
}
|
|
@@ -154,12 +154,27 @@ export function collectElevationSnapTargets(
|
|
|
154
154
|
anchor: center,
|
|
155
155
|
label: 'Wall base',
|
|
156
156
|
})
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
if (node.endHeightOffset) {
|
|
158
|
+
targets.push({
|
|
159
|
+
id: `${node.id}:top-start`,
|
|
160
|
+
elevation: base + getWallEffectiveHeightForNodes(node, nodes, 0),
|
|
161
|
+
anchor: node.start,
|
|
162
|
+
label: 'Wall top start',
|
|
163
|
+
})
|
|
164
|
+
targets.push({
|
|
165
|
+
id: `${node.id}:top-end`,
|
|
166
|
+
elevation: base + getWallEffectiveHeightForNodes(node, nodes, 1),
|
|
167
|
+
anchor: node.end,
|
|
168
|
+
label: 'Wall top end',
|
|
169
|
+
})
|
|
170
|
+
} else {
|
|
171
|
+
targets.push({
|
|
172
|
+
id: `${node.id}:top`,
|
|
173
|
+
elevation: base + getWallEffectiveHeightForNodes(node, nodes, 0.5),
|
|
174
|
+
anchor: center,
|
|
175
|
+
label: 'Wall top',
|
|
176
|
+
})
|
|
177
|
+
}
|
|
163
178
|
continue
|
|
164
179
|
}
|
|
165
180
|
|
|
@@ -1,26 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AnyNode,
|
|
3
|
-
type AnyNodeId,
|
|
4
|
-
type BuildingNode,
|
|
5
|
-
type ElevatorNode,
|
|
6
|
-
type LevelNode,
|
|
7
|
-
spatialGridManager,
|
|
8
|
-
useScene,
|
|
9
|
-
} from '@pascal-app/core'
|
|
10
|
-
|
|
11
|
-
function getBuildingLevels(
|
|
12
|
-
buildingId: string | null | undefined,
|
|
13
|
-
nodes: Record<string, AnyNode>,
|
|
14
|
-
): LevelNode[] {
|
|
15
|
-
if (!buildingId) return []
|
|
16
|
-
const building = nodes[buildingId as AnyNodeId]
|
|
17
|
-
if (building?.type !== 'building') return []
|
|
18
|
-
|
|
19
|
-
return building.children
|
|
20
|
-
.map((childId) => nodes[childId as AnyNodeId])
|
|
21
|
-
.filter((entry): entry is LevelNode => entry?.type === 'level')
|
|
22
|
-
.sort((left, right) => left.level - right.level)
|
|
23
|
-
}
|
|
1
|
+
import type { AnyNode, AnyNodeId, BuildingNode, LevelNode } from '@pascal-app/core'
|
|
24
2
|
|
|
25
3
|
export function resolveCurrentBuildingId({
|
|
26
4
|
buildingId,
|
|
@@ -45,52 +23,3 @@ export function resolveCurrentBuildingId({
|
|
|
45
23
|
|
|
46
24
|
return null
|
|
47
25
|
}
|
|
48
|
-
|
|
49
|
-
export function resolveElevatorSupportLevelId({
|
|
50
|
-
buildingId,
|
|
51
|
-
preferredLevelId,
|
|
52
|
-
}: {
|
|
53
|
-
buildingId: string | null | undefined
|
|
54
|
-
preferredLevelId?: string | null
|
|
55
|
-
}): LevelNode['id'] | null {
|
|
56
|
-
const nodes = useScene.getState().nodes
|
|
57
|
-
const preferred = preferredLevelId ? nodes[preferredLevelId as AnyNodeId] : undefined
|
|
58
|
-
const levels = getBuildingLevels(buildingId, nodes)
|
|
59
|
-
const preferredInBuilding = preferredLevelId
|
|
60
|
-
? levels.find((level) => level.id === preferredLevelId)
|
|
61
|
-
: undefined
|
|
62
|
-
|
|
63
|
-
if (preferredInBuilding) return preferredInBuilding.id
|
|
64
|
-
if (levels.length === 0) return preferred?.type === 'level' ? preferred.id : null
|
|
65
|
-
|
|
66
|
-
return levels[0]?.id ?? null
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function resolveElevatorSupportY({
|
|
70
|
-
buildingId,
|
|
71
|
-
preferredLevelId,
|
|
72
|
-
x,
|
|
73
|
-
z,
|
|
74
|
-
}: {
|
|
75
|
-
buildingId: string | null | undefined
|
|
76
|
-
preferredLevelId?: string | null
|
|
77
|
-
x: number
|
|
78
|
-
z: number
|
|
79
|
-
}): number {
|
|
80
|
-
const levelId = resolveElevatorSupportLevelId({ buildingId, preferredLevelId })
|
|
81
|
-
if (!levelId) return 0
|
|
82
|
-
|
|
83
|
-
return Math.max(0, spatialGridManager.getSlabElevationAt(levelId, x, z))
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function resolveElevatorNodeSupportY(
|
|
87
|
-
node: ElevatorNode,
|
|
88
|
-
position: [number, number, number] = node.position,
|
|
89
|
-
): number {
|
|
90
|
-
return resolveElevatorSupportY({
|
|
91
|
-
buildingId: node.parentId,
|
|
92
|
-
preferredLevelId: node.fromLevelId ?? node.defaultLevelId,
|
|
93
|
-
x: position[0],
|
|
94
|
-
z: position[2],
|
|
95
|
-
})
|
|
96
|
-
}
|
|
@@ -4,12 +4,14 @@ import {
|
|
|
4
4
|
applySceneOperationPatch,
|
|
5
5
|
BuildingNode,
|
|
6
6
|
clearSceneHistory,
|
|
7
|
+
createI18n,
|
|
7
8
|
createZone,
|
|
8
9
|
floorFootprintName,
|
|
9
10
|
generateId,
|
|
10
11
|
ItemNode,
|
|
11
12
|
initSpaceDetectionSync,
|
|
12
13
|
LevelNode,
|
|
14
|
+
levelFootprints,
|
|
13
15
|
type SceneCommit,
|
|
14
16
|
type SlabNode,
|
|
15
17
|
SlabNode as SlabSchema,
|
|
@@ -28,7 +30,6 @@ import {
|
|
|
28
30
|
footprintsToMoveTogether,
|
|
29
31
|
foundationHeight,
|
|
30
32
|
isFootprintConstructionHit,
|
|
31
|
-
levelFootprints,
|
|
32
33
|
presetPatch,
|
|
33
34
|
roomFloorOwner,
|
|
34
35
|
roomFootprint,
|
|
@@ -418,7 +419,7 @@ describe('floor footprints', () => {
|
|
|
418
419
|
// What the notice offers instead: both footprints, together, in one step.
|
|
419
420
|
const together = footprintsToMoveTogether(nodes(), house.id, patch)
|
|
420
421
|
expect(together).toEqual([house.id, shed.id])
|
|
421
|
-
expect(moveTogetherLabel(together!.length, false)).toBe('Raise both')
|
|
422
|
+
expect(moveTogetherLabel(together!.length, false, createI18n())).toBe('Raise both')
|
|
422
423
|
const steps = history()
|
|
423
424
|
expect(applyFloorFoundation(together!, patch)).toBeNull()
|
|
424
425
|
expect(history()).toBe(steps + 1)
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getRoomBaseElevation,
|
|
11
11
|
groundFloorConstruction,
|
|
12
12
|
intersection,
|
|
13
|
+
levelFootprints,
|
|
13
14
|
roomDrawnFloor,
|
|
14
15
|
type SlabNode,
|
|
15
16
|
setFloorFoundation,
|
|
@@ -39,15 +40,6 @@ const ON_GROUND = 0.0005
|
|
|
39
40
|
const isBase = (node: AnyNode | undefined): node is SlabNode =>
|
|
40
41
|
node?.type === 'slab' && node.plateRole === 'base'
|
|
41
42
|
|
|
42
|
-
const footprintArea = (plate: SlabNode) => area([{ outer: plate.polygon, holes: plate.holes }])
|
|
43
|
-
|
|
44
|
-
/** The level's footprints, largest first (ties by id, so the order is stable). */
|
|
45
|
-
export function levelFootprints(nodes: Record<string, AnyNode>, levelId: string): SlabNode[] {
|
|
46
|
-
return Object.values(nodes)
|
|
47
|
-
.filter((node): node is SlabNode => isBase(node) && node.parentId === levelId)
|
|
48
|
-
.sort((a, b) => footprintArea(b) - footprintArea(a) || a.id.localeCompare(b.id))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
43
|
/**
|
|
52
44
|
* The drawn slab a room's floor is: the one it visibly stands on (a legacy
|
|
53
45
|
* floor can show a hand-drawn slab over the one it was taken from), else the
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type AnyNode,
|
|
4
4
|
type AnyNodeId,
|
|
5
5
|
BuildingNode,
|
|
6
|
+
createI18n,
|
|
6
7
|
createZone,
|
|
7
8
|
floorFootprintSupportClass,
|
|
8
9
|
generateId,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
getLevelElevations,
|
|
11
12
|
initSpaceDetectionSync,
|
|
12
13
|
LevelNode,
|
|
14
|
+
levelFootprints,
|
|
13
15
|
type SlabNode,
|
|
14
16
|
upperFloorHeightControl,
|
|
15
17
|
useScene,
|
|
@@ -29,13 +31,14 @@ import {
|
|
|
29
31
|
footprintHeightMinimum,
|
|
30
32
|
footprintHeightPatch,
|
|
31
33
|
footprintHeightValue,
|
|
32
|
-
levelFootprints,
|
|
33
34
|
presetPatch,
|
|
34
35
|
setFootprintHeight,
|
|
35
36
|
thickFloorAdvice,
|
|
36
37
|
} from './floor-footprints'
|
|
37
38
|
import { applyRoomPlan } from './room-structure-commands'
|
|
38
39
|
|
|
40
|
+
const english = createI18n()
|
|
41
|
+
|
|
39
42
|
const GROUND = 'level_support_ground'
|
|
40
43
|
const UPPER = 'level_support_upper'
|
|
41
44
|
let stop = () => {}
|
|
@@ -98,8 +101,10 @@ describe('Floor & foundation support class (point 5)', () => {
|
|
|
98
101
|
})
|
|
99
102
|
|
|
100
103
|
test('the kicker says where the plate is and what it rests on', () => {
|
|
101
|
-
expect(floorPlateKicker(nodes(), plateOf(GROUND, 'Living'))).toBe(
|
|
102
|
-
|
|
104
|
+
expect(floorPlateKicker(nodes(), plateOf(GROUND, 'Living'), english, 'en')).toBe(
|
|
105
|
+
'Floor plate · Ground floor',
|
|
106
|
+
)
|
|
107
|
+
expect(floorPlateKicker(nodes(), plateOf(UPPER, 'Bedroom'), english, 'en')).toBe(
|
|
103
108
|
'Floor plate · Floor 1 · rests on Ground floor',
|
|
104
109
|
)
|
|
105
110
|
})
|
|
@@ -164,7 +169,6 @@ describe('Upper-floor height drag release (round-final item 6)', () => {
|
|
|
164
169
|
test(`a drag of ${delta} m lands on release`, () => {
|
|
165
170
|
const bedroom = plateOf(UPPER, 'Bedroom')
|
|
166
171
|
const start = footprintHeightValue(nodes(), bedroom)
|
|
167
|
-
const past = useScene.temporal.getState().pastStates.length
|
|
168
172
|
const preview = beginFootprintHeightPreview(bedroom.id)
|
|
169
173
|
expect(preview.preview(start + delta / 2)).toBeNull()
|
|
170
174
|
expect(preview.preview(start + delta)).toBeNull()
|
|
@@ -172,7 +176,13 @@ describe('Upper-floor height drag release (round-final item 6)', () => {
|
|
|
172
176
|
const after = nodes()[bedroom.id] as SlabNode
|
|
173
177
|
expect(after.thickness).toBeCloseTo(bedroom.thickness + delta, 6)
|
|
174
178
|
expect(footprintHeightValue(nodes(), after)).toBeCloseTo(start + delta, 6)
|
|
175
|
-
|
|
179
|
+
useScene.temporal.getState().undo()
|
|
180
|
+
expect(footprintHeightValue(nodes(), nodes()[bedroom.id] as SlabNode)).toBeCloseTo(start, 6)
|
|
181
|
+
useScene.temporal.getState().redo()
|
|
182
|
+
expect(footprintHeightValue(nodes(), nodes()[bedroom.id] as SlabNode)).toBeCloseTo(
|
|
183
|
+
start + delta,
|
|
184
|
+
6,
|
|
185
|
+
)
|
|
176
186
|
})
|
|
177
187
|
}
|
|
178
188
|
})
|
|
@@ -183,7 +193,6 @@ describe('Floor & foundation panel (point 5)', () => {
|
|
|
183
193
|
expect(ground).toMatchObject({ upper: false, preset: 'ground', underFloor: true, minHeight: 0 })
|
|
184
194
|
const upper = floorFoundationModel(nodes(), plateOf(UPPER, 'Bedroom'))
|
|
185
195
|
expect(upper).toMatchObject({ upper: true, underFloor: false, thickFloor: false })
|
|
186
|
-
expect(upper.kicker).toBe('Floor plate · Floor 1 · rests on Ground floor')
|
|
187
196
|
})
|
|
188
197
|
|
|
189
198
|
test('on the ground the two inputs are slab thickness and foundation height; the top follows', () => {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
generateId,
|
|
8
8
|
type LevelNode,
|
|
9
9
|
type MultiPolygon,
|
|
10
|
+
roomFloorElevation,
|
|
10
11
|
type StructurePlan,
|
|
11
12
|
structureChangeBatch,
|
|
12
13
|
useScene,
|
|
@@ -31,7 +32,6 @@ import {
|
|
|
31
32
|
import { type FloorRegionPoint, floorRegionSnapTargets } from './floor-region-snap'
|
|
32
33
|
import { beginGesture, type GestureHandle } from './gesture-lifecycle'
|
|
33
34
|
import { resolveRoomAssemblyHeights } from './room-assembly-overlay'
|
|
34
|
-
import { roomFloorElevation } from './room-handle-drag'
|
|
35
35
|
import { sfxEmitter } from './sfx-bus'
|
|
36
36
|
|
|
37
37
|
// "Cut opening": draws an opening on a room's floor (or its ceiling) with the
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
ceilingRegionsOwner,
|
|
8
8
|
type MultiPolygon,
|
|
9
9
|
resolveCeilingHeight,
|
|
10
|
+
roomFloorElevation,
|
|
10
11
|
union,
|
|
11
12
|
useScene,
|
|
12
13
|
} from '@pascal-app/core'
|
|
@@ -32,7 +33,6 @@ import { beginGesture, type GestureHandle } from './gesture-lifecycle'
|
|
|
32
33
|
import { hasActivePaintMaterial } from './material-paint'
|
|
33
34
|
import { isPaintErasing, usePaintRegionMode } from './paint-region-mode'
|
|
34
35
|
import { addCeilingRegion, addFloorRegion } from './paint-regions'
|
|
35
|
-
import { roomFloorElevation } from './room-handle-drag'
|
|
36
36
|
|
|
37
37
|
// An outline drawn on one room's floor: a rectangle (press, drag, release; a
|
|
38
38
|
// persistent tool also takes two clicks) or a polygon (click points, close on
|
|
@@ -26,6 +26,11 @@ describe('parseDimensionInput', () => {
|
|
|
26
26
|
expect(parseDimensionInput('12.5', 'imperial')).toBeCloseTo(12.5 * FOOT, 9)
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
+
test('decimal commas follow the scoped locale', () => {
|
|
30
|
+
expect(parseDimensionInput('2,75', 'metric', 'fr')).toBe(2.75)
|
|
31
|
+
expect(parseDimensionInput('2,75', 'metric', 'en')).toBeNull()
|
|
32
|
+
})
|
|
33
|
+
|
|
29
34
|
test('rejects garbage and non-positive values', () => {
|
|
30
35
|
expect(parseDimensionInput('')).toBeNull()
|
|
31
36
|
expect(parseDimensionInput('abc')).toBeNull()
|
|
@@ -152,7 +157,7 @@ describe('driving a wall length', () => {
|
|
|
152
157
|
expect(plan?.updates[0]?.data).toEqual({ start: [0, 0], end: [3.5, 0] })
|
|
153
158
|
})
|
|
154
159
|
|
|
155
|
-
test('a dimension across the wall is not drivable', () => {
|
|
160
|
+
test('a dimension across the wall that ends on neither face is not drivable', () => {
|
|
156
161
|
const nodes = scene()
|
|
157
162
|
const resolution = resolveDimensionDrive({
|
|
158
163
|
nodes,
|
|
@@ -160,7 +165,10 @@ describe('driving a wall length', () => {
|
|
|
160
165
|
start: [1.5, -1],
|
|
161
166
|
end: [1.5, 1],
|
|
162
167
|
})
|
|
163
|
-
expect(resolution).toEqual({
|
|
168
|
+
expect(resolution).toEqual({
|
|
169
|
+
drivable: false,
|
|
170
|
+
reason: 'dimension does not start or end on this wall',
|
|
171
|
+
})
|
|
164
172
|
})
|
|
165
173
|
|
|
166
174
|
test('a locked wall refuses to drive', () => {
|
|
@@ -358,3 +366,29 @@ describe('an opening width tag resizes the opening', () => {
|
|
|
358
366
|
expect(plan?.updates).toEqual([{ id: 'win_1' as AnyNodeId, data: { width: 1.2192 } }])
|
|
359
367
|
})
|
|
360
368
|
})
|
|
369
|
+
|
|
370
|
+
describe('a dimension across a wall slides the wall', () => {
|
|
371
|
+
test('a room width from the wall face drives the wall along its normal, away from the far end', () => {
|
|
372
|
+
// wall_a runs east along z = 0; the dimension leaves its south face for a wall 3 m south.
|
|
373
|
+
const resolution = resolveDimensionDrive({
|
|
374
|
+
nodes: scene(),
|
|
375
|
+
ownerNodeId: 'wall_a' as AnyNodeId,
|
|
376
|
+
start: [1.5, 0.075],
|
|
377
|
+
end: [1.5, 3],
|
|
378
|
+
})
|
|
379
|
+
expect(resolution).toEqual({
|
|
380
|
+
drivable: true,
|
|
381
|
+
target: { kind: 'wall-offset', wallId: 'wall_a' as AnyNodeId, direction: [0, -1] },
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
test('the wall end of the dimension may be either witness point', () => {
|
|
386
|
+
const resolution = resolveDimensionDrive({
|
|
387
|
+
nodes: scene(),
|
|
388
|
+
ownerNodeId: 'wall_a' as AnyNodeId,
|
|
389
|
+
start: [1.5, -4],
|
|
390
|
+
end: [1.5, -0.075],
|
|
391
|
+
})
|
|
392
|
+
expect(resolution).toMatchObject({ drivable: true, target: { direction: [0, 1] } })
|
|
393
|
+
})
|
|
394
|
+
})
|