@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,5 +1,5 @@
|
|
|
1
1
|
import type { AnyNode, AnyNodeId, DoorNode, WallNode, WindowNode } from '@pascal-app/core'
|
|
2
|
-
import { getLinkedWallUpdates } from '@pascal-app/core'
|
|
2
|
+
import { getLinkedWallUpdates, normalizeDecimalInput } from '@pascal-app/core'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Driving dimensions — WS3.
|
|
@@ -34,8 +34,9 @@ const INCHES_PER_METER = 1 / 0.0254
|
|
|
34
34
|
export function parseDimensionInput(
|
|
35
35
|
raw: string,
|
|
36
36
|
unit: 'metric' | 'imperial' = 'metric',
|
|
37
|
+
locale = 'en',
|
|
37
38
|
): number | null {
|
|
38
|
-
const text = raw.trim().toLowerCase().replace(/\s+/g, ' ')
|
|
39
|
+
const text = normalizeDecimalInput(raw, locale).trim().toLowerCase().replace(/\s+/g, ' ')
|
|
39
40
|
if (!text) return null
|
|
40
41
|
|
|
41
42
|
// Explicit metric suffix.
|
|
@@ -119,6 +120,17 @@ export type DimensionDriveTarget =
|
|
|
119
120
|
openingId: AnyNodeId
|
|
120
121
|
wallId: AnyNodeId
|
|
121
122
|
}
|
|
123
|
+
| {
|
|
124
|
+
/**
|
|
125
|
+
* The dimension runs across the wall, from one of its faces to another
|
|
126
|
+
* wall (a room width, the outside size): the whole wall slides along
|
|
127
|
+
* its normal, its neighbours following, as a push of the wall does.
|
|
128
|
+
*/
|
|
129
|
+
kind: 'wall-offset'
|
|
130
|
+
wallId: AnyNodeId
|
|
131
|
+
/** Unit vector the wall travels along when the value grows. */
|
|
132
|
+
direction: DimensionPlanPoint
|
|
133
|
+
}
|
|
122
134
|
|
|
123
135
|
export type DimensionDriveResolution =
|
|
124
136
|
| { drivable: true; target: DimensionDriveTarget }
|
|
@@ -173,7 +185,7 @@ function resolveWallDrive(
|
|
|
173
185
|
const wallDirection = unit(subtract(wall.end, wall.start))
|
|
174
186
|
if (!wallDirection) return { drivable: false, reason: 'degenerate wall' }
|
|
175
187
|
if (Math.abs(cross(wallDirection, dimDirection)) > PARALLEL_TOLERANCE) {
|
|
176
|
-
return
|
|
188
|
+
return resolveWallOffsetDrive(query, wall, wallDirection, dimDirection)
|
|
177
189
|
}
|
|
178
190
|
if ((wall as { curvature?: number }).curvature) {
|
|
179
191
|
return { drivable: false, reason: 'curved walls are not drivable yet' }
|
|
@@ -209,6 +221,56 @@ function resolveWallDrive(
|
|
|
209
221
|
}
|
|
210
222
|
}
|
|
211
223
|
|
|
224
|
+
/** How far off a wall's face a witness point may sit and still be on that wall. */
|
|
225
|
+
const FACE_MATCH_TOLERANCE_M = 0.05
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* A dimension square to the wall, with one end on it: typing a value slides
|
|
229
|
+
* the wall along its normal, away from the other end when the value grows.
|
|
230
|
+
*/
|
|
231
|
+
function resolveWallOffsetDrive(
|
|
232
|
+
query: DimensionDriveQuery,
|
|
233
|
+
wall: WallNode,
|
|
234
|
+
wallDirection: DimensionPlanPoint,
|
|
235
|
+
dimDirection: DimensionPlanPoint,
|
|
236
|
+
): DimensionDriveResolution {
|
|
237
|
+
if (Math.abs(dot(wallDirection, dimDirection)) > PARALLEL_TOLERANCE) {
|
|
238
|
+
return { drivable: false, reason: 'dimension is neither along nor across the wall' }
|
|
239
|
+
}
|
|
240
|
+
if (wall.curveOffset) return { drivable: false, reason: 'curved walls are not drivable yet' }
|
|
241
|
+
const reach = (wall.thickness ?? 0.1) + FACE_MATCH_TOLERANCE_M
|
|
242
|
+
const onWall = (point: DimensionPlanPoint) =>
|
|
243
|
+
distanceToSegment(point, wall.start as DimensionPlanPoint, wall.end as DimensionPlanPoint) <=
|
|
244
|
+
reach
|
|
245
|
+
const startOn = onWall(query.start)
|
|
246
|
+
const endOn = onWall(query.end)
|
|
247
|
+
if (startOn === endOn) {
|
|
248
|
+
return { drivable: false, reason: 'dimension does not start or end on this wall' }
|
|
249
|
+
}
|
|
250
|
+
const direction = startOn
|
|
251
|
+
? unit(subtract(query.start, query.end))
|
|
252
|
+
: unit(subtract(query.end, query.start))
|
|
253
|
+
if (!direction) return { drivable: false, reason: 'degenerate dimension' }
|
|
254
|
+
return {
|
|
255
|
+
drivable: true,
|
|
256
|
+
target: { kind: 'wall-offset', wallId: wall.id as AnyNodeId, direction },
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function distanceToSegment(
|
|
261
|
+
point: DimensionPlanPoint,
|
|
262
|
+
start: DimensionPlanPoint,
|
|
263
|
+
end: DimensionPlanPoint,
|
|
264
|
+
): number {
|
|
265
|
+
const segment = subtract(end, start)
|
|
266
|
+
const lengthSq = dot(segment, segment)
|
|
267
|
+
const t =
|
|
268
|
+
lengthSq <= 1e-12
|
|
269
|
+
? 0
|
|
270
|
+
: Math.max(0, Math.min(1, dot(subtract(point, start), segment) / lengthSq))
|
|
271
|
+
return length(subtract(point, [start[0] + segment[0] * t, start[1] + segment[1] * t]))
|
|
272
|
+
}
|
|
273
|
+
|
|
212
274
|
function resolveOpeningDrive(
|
|
213
275
|
query: DimensionDriveQuery,
|
|
214
276
|
opening: DoorNode | WindowNode,
|
|
@@ -359,6 +421,9 @@ export function planDimensionDrive(args: {
|
|
|
359
421
|
}): DimensionDrivePlan | null {
|
|
360
422
|
const delta = args.nextLength - args.currentLength
|
|
361
423
|
if (!Number.isFinite(delta) || Math.abs(delta) < 1e-9) return { updates: [], delta: 0 }
|
|
424
|
+
// A wall offset moves the wall with its junctions re-planned, bridges and
|
|
425
|
+
// all: that is the wall-push planner's job (`commitDimensionValue`).
|
|
426
|
+
if (args.target.kind === 'wall-offset') return null
|
|
362
427
|
|
|
363
428
|
if (args.target.kind === 'opening-width') {
|
|
364
429
|
const opening = args.nodes[args.target.openingId]
|
|
@@ -311,16 +311,27 @@ describe('floor plan export policy', () => {
|
|
|
311
311
|
stairAnnotations: true,
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
expect(resolveFloorplanExportAnnotationVisibility('expert', liveVisibility)).toEqual(
|
|
314
|
+
expect(resolveFloorplanExportAnnotationVisibility('expert', liveVisibility, true)).toEqual(
|
|
315
315
|
liveVisibility,
|
|
316
316
|
)
|
|
317
317
|
})
|
|
318
318
|
|
|
319
|
+
test('exports no automatic dimensions while Display → Dimensions is off', () => {
|
|
320
|
+
expect(
|
|
321
|
+
resolveFloorplanExportAnnotationVisibility(
|
|
322
|
+
'expert',
|
|
323
|
+
DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
324
|
+
false,
|
|
325
|
+
),
|
|
326
|
+
).toEqual({ ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY, automaticDimensions: false })
|
|
327
|
+
})
|
|
328
|
+
|
|
319
329
|
test('exports only model geometry and room labels in Default', () => {
|
|
320
330
|
expect(
|
|
321
331
|
resolveFloorplanExportAnnotationVisibility(
|
|
322
332
|
'default',
|
|
323
333
|
DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
334
|
+
true,
|
|
324
335
|
),
|
|
325
336
|
).toEqual({
|
|
326
337
|
automaticDimensions: false,
|
|
@@ -700,7 +711,7 @@ describe('collectFloorplanGeometry', () => {
|
|
|
700
711
|
'meters',
|
|
701
712
|
DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
702
713
|
'floor-plan',
|
|
703
|
-
'
|
|
714
|
+
'both',
|
|
704
715
|
[enabledPluginId],
|
|
705
716
|
)
|
|
706
717
|
|
|
@@ -758,7 +769,7 @@ describe('collectFloorplanGeometry', () => {
|
|
|
758
769
|
'meters',
|
|
759
770
|
DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
760
771
|
'floor-plan',
|
|
761
|
-
'
|
|
772
|
+
'both',
|
|
762
773
|
[enabledPluginId],
|
|
763
774
|
)
|
|
764
775
|
expect(upper.map(({ id }) => id)).toEqual(['site_overlay', 'level_upper_architecture'])
|
|
@@ -775,7 +786,7 @@ describe('collectFloorplanGeometry', () => {
|
|
|
775
786
|
'meters',
|
|
776
787
|
DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
777
788
|
'floor-plan',
|
|
778
|
-
'
|
|
789
|
+
'both',
|
|
779
790
|
[enabledPluginId],
|
|
780
791
|
)
|
|
781
792
|
// A hidden Site hides only its own ground and boundary; the site-scoped
|
|
@@ -790,7 +801,7 @@ describe('collectFloorplanGeometry', () => {
|
|
|
790
801
|
'meters',
|
|
791
802
|
DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
792
803
|
'floor-plan',
|
|
793
|
-
'
|
|
804
|
+
'both',
|
|
794
805
|
[enabledPluginId],
|
|
795
806
|
)
|
|
796
807
|
expect(structure.map(({ id }) => id)).toEqual(['level_architecture'])
|
|
@@ -44,15 +44,11 @@ import { resolveNodeForDrawingType } from './drawing-coordination'
|
|
|
44
44
|
import {
|
|
45
45
|
type FloorplanMetricNotation,
|
|
46
46
|
type FloorplanSchedule,
|
|
47
|
-
type
|
|
47
|
+
type FloorplanWallDimensionFaces,
|
|
48
48
|
getFloorplanNodeExtension,
|
|
49
49
|
readFloorplanGeometryMetadata,
|
|
50
50
|
} from './floorplan-extension'
|
|
51
|
-
import {
|
|
52
|
-
type FloorplanMode,
|
|
53
|
-
resolveFloorplanAnnotationVisibility,
|
|
54
|
-
resolveFloorplanWallDimensionReference,
|
|
55
|
-
} from './floorplan-mode'
|
|
51
|
+
import { type FloorplanMode, resolveFloorplanAnnotationVisibility } from './floorplan-mode'
|
|
56
52
|
import { createFloorplanPdfDocument, type FloorplanPdfDocument } from './floorplan-pdfkit-document'
|
|
57
53
|
import { renderFloorplanGeometryToPdfKit } from './floorplan-pdfkit-renderer'
|
|
58
54
|
import { FLOORPLAN_VIEW_ROTATION_DEG } from './geometry'
|
|
@@ -131,7 +127,7 @@ const NEUTRAL_VIEW_STATE = {
|
|
|
131
127
|
export function resolveFloorplanExportViewState(
|
|
132
128
|
unit: 'metric' | 'imperial',
|
|
133
129
|
metricNotation: FloorplanMetricNotation,
|
|
134
|
-
|
|
130
|
+
wallDimensionFaces?: FloorplanWallDimensionFaces,
|
|
135
131
|
automaticDimensions = true,
|
|
136
132
|
) {
|
|
137
133
|
return {
|
|
@@ -139,7 +135,7 @@ export function resolveFloorplanExportViewState(
|
|
|
139
135
|
automaticDimensions,
|
|
140
136
|
unit,
|
|
141
137
|
metricNotation,
|
|
142
|
-
|
|
138
|
+
wallDimensionFaces,
|
|
143
139
|
}
|
|
144
140
|
}
|
|
145
141
|
|
|
@@ -195,10 +191,7 @@ export async function exportFloorplanPdf(scope: FloorplanExportScope): Promise<v
|
|
|
195
191
|
const annotationVisibility = resolveFloorplanExportAnnotationVisibility(
|
|
196
192
|
floorplanMode,
|
|
197
193
|
expertAnnotationState.visibility,
|
|
198
|
-
|
|
199
|
-
const wallDimensionReference = resolveFloorplanWallDimensionReference(
|
|
200
|
-
floorplanMode,
|
|
201
|
-
expertAnnotationState.wallDimensionReference,
|
|
194
|
+
expertAnnotationState.showDimensions,
|
|
202
195
|
)
|
|
203
196
|
const navigationAzimuth = useEditor.getState().navigationSyncPose?.azimuth
|
|
204
197
|
const drawingType = useDrawingView.getState().drawingType
|
|
@@ -232,7 +225,7 @@ export async function exportFloorplanPdf(scope: FloorplanExportScope): Promise<v
|
|
|
232
225
|
metricNotation,
|
|
233
226
|
annotationVisibility,
|
|
234
227
|
drawingType,
|
|
235
|
-
|
|
228
|
+
expertAnnotationState.wallDimensionFaces,
|
|
236
229
|
installedPlugins,
|
|
237
230
|
)
|
|
238
231
|
const schedules = collectFloorplanSchedules(nodes, level.id, unit, scope)
|
|
@@ -604,8 +597,12 @@ export function resolveFloorplanScreenUnitsPerPixel(
|
|
|
604
597
|
export function resolveFloorplanExportAnnotationVisibility(
|
|
605
598
|
mode: FloorplanMode,
|
|
606
599
|
liveVisibility: FloorplanAnnotationVisibility,
|
|
600
|
+
dimensions: boolean,
|
|
607
601
|
): FloorplanAnnotationVisibility {
|
|
608
|
-
return resolveFloorplanAnnotationVisibility(mode, liveVisibility, {
|
|
602
|
+
return resolveFloorplanAnnotationVisibility(mode, liveVisibility, {
|
|
603
|
+
target: 'export',
|
|
604
|
+
dimensions,
|
|
605
|
+
})
|
|
609
606
|
}
|
|
610
607
|
|
|
611
608
|
export function resolveFloorplanExportRotationDeg(
|
|
@@ -785,7 +782,7 @@ export function collectFloorplanGeometry(
|
|
|
785
782
|
metricNotation: FloorplanMetricNotation,
|
|
786
783
|
annotationVisibility: FloorplanAnnotationVisibility,
|
|
787
784
|
drawingType: ConstructionDrawingType,
|
|
788
|
-
|
|
785
|
+
wallDimensionFaces: FloorplanWallDimensionFaces,
|
|
789
786
|
/** `undefined` = legacy scene without install state: every loaded kind draws. */
|
|
790
787
|
installedPlugins: readonly string[] | undefined,
|
|
791
788
|
): ExportGeometry[] {
|
|
@@ -907,7 +904,7 @@ export function collectFloorplanGeometry(
|
|
|
907
904
|
resolveFloorplanExportViewState(
|
|
908
905
|
unit,
|
|
909
906
|
metricNotation,
|
|
910
|
-
|
|
907
|
+
wallDimensionFaces,
|
|
911
908
|
annotationVisibility.automaticDimensions,
|
|
912
909
|
),
|
|
913
910
|
levelData,
|
|
@@ -2,7 +2,7 @@ import { describe, expect, test } from 'bun:test'
|
|
|
2
2
|
import type { GeometryContext } from '@pascal-app/core'
|
|
3
3
|
import {
|
|
4
4
|
createFloorplanContextExtensions,
|
|
5
|
-
|
|
5
|
+
normalizeFloorplanWallDimensionFaces,
|
|
6
6
|
readFloorplanContext,
|
|
7
7
|
} from './floorplan-extension'
|
|
8
8
|
|
|
@@ -17,22 +17,21 @@ function context(extensions?: Readonly<Record<string, unknown>>): GeometryContex
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
describe('floor-plan context extensions', () => {
|
|
20
|
-
test('
|
|
21
|
-
expect(readFloorplanContext(context()).
|
|
20
|
+
test('measures walls to both faces by default', () => {
|
|
21
|
+
expect(readFloorplanContext(context()).wallDimensionFaces).toBe('both')
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
test('carries the
|
|
25
|
-
for (const
|
|
26
|
-
const extensions = createFloorplanContextExtensions({
|
|
27
|
-
expect(readFloorplanContext(context(extensions)).
|
|
28
|
-
wallDimensionReference,
|
|
29
|
-
)
|
|
24
|
+
test('carries the Measure walls choice', () => {
|
|
25
|
+
for (const wallDimensionFaces of ['interior', 'exterior', 'both'] as const) {
|
|
26
|
+
const extensions = createFloorplanContextExtensions({ wallDimensionFaces })
|
|
27
|
+
expect(readFloorplanContext(context(extensions)).wallDimensionFaces).toBe(wallDimensionFaces)
|
|
30
28
|
}
|
|
31
29
|
})
|
|
32
30
|
|
|
33
|
-
test('normalizes stale persisted
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
test('normalizes stale persisted values, including the retired references, to Both', () => {
|
|
32
|
+
for (const stale of ['finished-faces', 'centerline', 'stud-faces', 'unknown', null]) {
|
|
33
|
+
expect(normalizeFloorplanWallDimensionFaces(stale)).toBe('both')
|
|
34
|
+
}
|
|
36
35
|
})
|
|
37
36
|
|
|
38
37
|
test('lets presentation code suppress automatic annotation construction', () => {
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
SceneApi,
|
|
9
9
|
} from '@pascal-app/core'
|
|
10
10
|
import type { ComponentType } from 'react'
|
|
11
|
+
import type { WallSelectionDimension } from '../room-push-dimensions'
|
|
11
12
|
|
|
12
13
|
export const FLOORPLAN_NODE_EXTENSION_KEY = 'pascal:editor/floorplan'
|
|
13
14
|
export const FLOORPLAN_GEOMETRY_METADATA_KEY = 'pascal:editor/floorplan'
|
|
@@ -16,8 +17,20 @@ export const FLOORPLAN_CONTEXT_EXTENSION_KEY = 'pascal:editor/floorplan'
|
|
|
16
17
|
export type FloorplanRenderPurpose = 'edit' | 'document'
|
|
17
18
|
export type FloorplanMetricNotation = 'meters' | 'millimeters'
|
|
18
19
|
export type FloorplanToolMode = 'default' | 'expert'
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Display → Measure walls: which finished faces wall dimensions measure to —
|
|
22
|
+
* the interior faces (ID, the room sizes), the exterior faces (OD, the
|
|
23
|
+
* outside size) or both. Read by a selected wall's temporary dimensions in 2D
|
|
24
|
+
* and 3D and by the Expert plan's automatic dimension strings.
|
|
25
|
+
*/
|
|
26
|
+
export type FloorplanWallDimensionFaces = 'interior' | 'exterior' | 'both'
|
|
27
|
+
export const DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES: FloorplanWallDimensionFaces = 'both'
|
|
28
|
+
|
|
29
|
+
export function normalizeFloorplanWallDimensionFaces(value: unknown): FloorplanWallDimensionFaces {
|
|
30
|
+
return value === 'interior' || value === 'exterior'
|
|
31
|
+
? value
|
|
32
|
+
: DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES
|
|
33
|
+
}
|
|
21
34
|
export type FloorplanAnnotationRole =
|
|
22
35
|
| 'automatic-dimension'
|
|
23
36
|
| 'contextual-dimension'
|
|
@@ -67,6 +80,24 @@ export type FloorplanNodeExtension<N extends AnyNode = AnyNode> = {
|
|
|
67
80
|
preferredView?: '2d' | '3d'
|
|
68
81
|
referencedSelectionAnnotationRole?: FloorplanAnnotationRole
|
|
69
82
|
contextualDimensions?: (node: N, ctx: GeometryContext) => FloorplanGeometry | null
|
|
83
|
+
/**
|
|
84
|
+
* A plan hit on this kind selects the node itself, not the room it bounds
|
|
85
|
+
* (3D stays room-first). For thin boundary bands whose rooms are clickable
|
|
86
|
+
* on either side of them.
|
|
87
|
+
*/
|
|
88
|
+
planHitSelectsNode?: boolean
|
|
89
|
+
/**
|
|
90
|
+
* Temporary dimensions shown while this node is selected in Select mode,
|
|
91
|
+
* alone or with others, in 2D and 3D (level-local plan points). Its
|
|
92
|
+
* `contextualDimensions` also stay in a multi-selection. Each is typeable:
|
|
93
|
+
* `commitDimensionValue` drives the node so it reads the typed value.
|
|
94
|
+
* Recomputed when the node or its level's rooms change.
|
|
95
|
+
*/
|
|
96
|
+
selectionDimensions?: (args: {
|
|
97
|
+
node: N
|
|
98
|
+
nodes: Readonly<Record<AnyNodeId, AnyNode>>
|
|
99
|
+
faces: FloorplanWallDimensionFaces
|
|
100
|
+
}) => readonly WallSelectionDimension[]
|
|
70
101
|
actionMenu?: {
|
|
71
102
|
canCurve?: (args: { node: N; nodes: Readonly<Record<AnyNodeId, AnyNode>> }) => boolean
|
|
72
103
|
/** Buttons the kind adds to the selection's action menu (2D and 3D); each decides its own visibility. */
|
|
@@ -130,15 +161,7 @@ type FloorplanContextExtension = {
|
|
|
130
161
|
*/
|
|
131
162
|
roomLabelOverlay: boolean
|
|
132
163
|
metricNotation: FloorplanMetricNotation
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function normalizeFloorplanWallDimensionReference(
|
|
137
|
-
value: unknown,
|
|
138
|
-
): FloorplanWallDimensionReference {
|
|
139
|
-
return value === 'centerline' || value === 'stud-faces'
|
|
140
|
-
? value
|
|
141
|
-
: DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE
|
|
164
|
+
wallDimensionFaces: FloorplanWallDimensionFaces
|
|
142
165
|
}
|
|
143
166
|
|
|
144
167
|
export function getFloorplanNodeExtension(
|
|
@@ -185,9 +208,7 @@ export function createFloorplanContextExtensions(
|
|
|
185
208
|
drafting: values.drafting === true,
|
|
186
209
|
roomLabelOverlay: values.roomLabelOverlay === true,
|
|
187
210
|
metricNotation: values.metricNotation === 'millimeters' ? 'millimeters' : 'meters',
|
|
188
|
-
|
|
189
|
-
values.wallDimensionReference,
|
|
190
|
-
),
|
|
211
|
+
wallDimensionFaces: normalizeFloorplanWallDimensionFaces(values.wallDimensionFaces),
|
|
191
212
|
} satisfies FloorplanContextExtension,
|
|
192
213
|
}
|
|
193
214
|
}
|
|
@@ -202,9 +223,7 @@ export function readFloorplanContext(ctx: GeometryContext): FloorplanContextExte
|
|
|
202
223
|
drafting: extension.drafting === true,
|
|
203
224
|
roomLabelOverlay: extension.roomLabelOverlay === true,
|
|
204
225
|
metricNotation: extension.metricNotation === 'millimeters' ? 'millimeters' : 'meters',
|
|
205
|
-
|
|
206
|
-
extension.wallDimensionReference,
|
|
207
|
-
),
|
|
226
|
+
wallDimensionFaces: normalizeFloorplanWallDimensionFaces(extension.wallDimensionFaces),
|
|
208
227
|
}
|
|
209
228
|
}
|
|
210
229
|
return {
|
|
@@ -213,7 +232,7 @@ export function readFloorplanContext(ctx: GeometryContext): FloorplanContextExte
|
|
|
213
232
|
drafting: false,
|
|
214
233
|
roomLabelOverlay: false,
|
|
215
234
|
metricNotation: 'meters',
|
|
216
|
-
|
|
235
|
+
wallDimensionFaces: DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES,
|
|
217
236
|
}
|
|
218
237
|
}
|
|
219
238
|
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
normalizeFloorplanMode,
|
|
6
6
|
normalizeFloorplanModesByProject,
|
|
7
7
|
resolveFloorplanAnnotationVisibility,
|
|
8
|
-
resolveFloorplanWallDimensionReference,
|
|
9
8
|
} from './floorplan-mode'
|
|
10
9
|
|
|
11
10
|
describe('floor-plan mode', () => {
|
|
@@ -115,10 +114,18 @@ describe('floor-plan mode', () => {
|
|
|
115
114
|
})
|
|
116
115
|
})
|
|
117
116
|
|
|
118
|
-
test('
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
test('Dimensions off hides automatic and contextual dimensions in both modes', () => {
|
|
118
|
+
for (const mode of ['default', 'expert'] as const) {
|
|
119
|
+
const visibility = resolveFloorplanAnnotationVisibility(
|
|
120
|
+
mode,
|
|
121
|
+
{ ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY, contextualDimensions: true },
|
|
122
|
+
{ selected: true, target: 'editor', dimensions: false },
|
|
123
|
+
)
|
|
124
|
+
expect(visibility.automaticDimensions).toBe(false)
|
|
125
|
+
expect(visibility.contextualDimensions).toBe(false)
|
|
126
|
+
// Drawn dimensions and measurements are the user's own: they stay.
|
|
127
|
+
expect(visibility.manualDimensions).toBe(true)
|
|
128
|
+
expect(visibility.measurements).toBe(true)
|
|
129
|
+
}
|
|
123
130
|
})
|
|
124
131
|
})
|
|
@@ -2,11 +2,7 @@ import {
|
|
|
2
2
|
type FloorplanAnnotationVisibility,
|
|
3
3
|
revealFloorplanAnnotationRole,
|
|
4
4
|
} from './annotation-visibility'
|
|
5
|
-
import type {
|
|
6
|
-
FloorplanAnnotationRole,
|
|
7
|
-
FloorplanToolMode,
|
|
8
|
-
FloorplanWallDimensionReference,
|
|
9
|
-
} from './floorplan-extension'
|
|
5
|
+
import type { FloorplanAnnotationRole, FloorplanToolMode } from './floorplan-extension'
|
|
10
6
|
|
|
11
7
|
export const FLOORPLAN_MODES = ['default', 'expert'] as const
|
|
12
8
|
|
|
@@ -18,6 +14,8 @@ export type FloorplanPresentationContext = {
|
|
|
18
14
|
referencedAnnotationRole?: FloorplanAnnotationRole
|
|
19
15
|
selected?: boolean
|
|
20
16
|
target: 'editor' | 'export'
|
|
17
|
+
/** Display → Dimensions. Off hides the automatic and contextual dimensions in either mode. */
|
|
18
|
+
dimensions?: boolean
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
export function normalizeFloorplanMode(value: unknown): FloorplanMode {
|
|
@@ -45,6 +43,17 @@ export function resolveFloorplanAnnotationVisibility(
|
|
|
45
43
|
mode: FloorplanMode,
|
|
46
44
|
expertVisibility: FloorplanAnnotationVisibility,
|
|
47
45
|
context: FloorplanPresentationContext,
|
|
46
|
+
): FloorplanAnnotationVisibility {
|
|
47
|
+
const visibility = resolveModeAnnotationVisibility(mode, expertVisibility, context)
|
|
48
|
+
if (context.dimensions !== false) return visibility
|
|
49
|
+
if (!visibility.automaticDimensions && !visibility.contextualDimensions) return visibility
|
|
50
|
+
return { ...visibility, automaticDimensions: false, contextualDimensions: false }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveModeAnnotationVisibility(
|
|
54
|
+
mode: FloorplanMode,
|
|
55
|
+
expertVisibility: FloorplanAnnotationVisibility,
|
|
56
|
+
context: FloorplanPresentationContext,
|
|
48
57
|
): FloorplanAnnotationVisibility {
|
|
49
58
|
if (mode === 'expert') return expertVisibility
|
|
50
59
|
|
|
@@ -66,10 +75,3 @@ export function resolveFloorplanAnnotationVisibility(
|
|
|
66
75
|
? revealFloorplanAnnotationRole(visibility, context.referencedAnnotationRole)
|
|
67
76
|
: visibility
|
|
68
77
|
}
|
|
69
|
-
|
|
70
|
-
export function resolveFloorplanWallDimensionReference(
|
|
71
|
-
mode: FloorplanMode,
|
|
72
|
-
expertReference: FloorplanWallDimensionReference,
|
|
73
|
-
): FloorplanWallDimensionReference {
|
|
74
|
-
return mode === 'default' ? 'centerline' : expertReference
|
|
75
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AnyNode, AnyNodeId, FloorplanPalette, GeometryContext } from '@pascal-app/core'
|
|
2
2
|
import {
|
|
3
3
|
createFloorplanContextExtensions,
|
|
4
|
-
type
|
|
4
|
+
type FloorplanWallDimensionFaces,
|
|
5
5
|
} from './floorplan-extension'
|
|
6
6
|
|
|
7
7
|
export type FloorplanViewState = {
|
|
@@ -14,7 +14,7 @@ export type FloorplanViewState = {
|
|
|
14
14
|
drafting?: boolean
|
|
15
15
|
/** Room labels drawn as screen pills (see `FloorplanContextExtension.roomLabelOverlay`). */
|
|
16
16
|
roomLabelOverlay?: boolean
|
|
17
|
-
|
|
17
|
+
wallDimensionFaces?: FloorplanWallDimensionFaces
|
|
18
18
|
highlighted: boolean
|
|
19
19
|
hovered: boolean
|
|
20
20
|
moving: boolean
|
|
@@ -65,7 +65,7 @@ export function buildFloorplanContext(
|
|
|
65
65
|
purpose: viewState.purpose ?? 'edit',
|
|
66
66
|
drafting: viewState.drafting === true,
|
|
67
67
|
roomLabelOverlay: viewState.roomLabelOverlay === true,
|
|
68
|
-
|
|
68
|
+
wallDimensionFaces: viewState.wallDimensionFaces,
|
|
69
69
|
}),
|
|
70
70
|
viewState: viewState.palette
|
|
71
71
|
? {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
formatLinearMeasurement,
|
|
3
|
+
type LinearUnit,
|
|
2
4
|
type OpenWallEnd,
|
|
3
5
|
planJoinOpenWallEnd,
|
|
4
6
|
runAsSingleSceneHistoryStep,
|
|
5
7
|
useScene,
|
|
6
8
|
type WallJoinResult,
|
|
7
9
|
} from '@pascal-app/core'
|
|
8
|
-
import { formatLinearMeasurement, type LinearUnit } from '../measurements'
|
|
9
10
|
|
|
10
11
|
const JOIN_REFUSALS: Record<Extract<WallJoinResult, { ok: false }>['reason'], string> = {
|
|
11
12
|
'covered-existing-wall': 'An existing wall already covers this span',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnyNode, AnyNodeId, DoorNode, WallNode, WindowNode, ZoneNode } from '@pascal-app/core'
|
|
2
|
-
import { deriveZoneQuantityReport } from '@pascal-app/core'
|
|
2
|
+
import { deriveZoneQuantityReport, resolveHostedOpeningFrameDepth } from '@pascal-app/core'
|
|
3
3
|
import { collectFloorplanSchedules } from './floorplan-export'
|
|
4
4
|
import type { FloorplanSchedule } from './floorplan-extension'
|
|
5
5
|
import { resolveMarkDetail } from './marks'
|
|
@@ -150,7 +150,7 @@ function openingRow(
|
|
|
150
150
|
? `${formatScheduleLength(opening.roughOpeningWidth, unit)} x ${formatScheduleLength(opening.roughOpeningHeight, unit)}`
|
|
151
151
|
: null,
|
|
152
152
|
material: openingMaterial(opening),
|
|
153
|
-
frame: openingFrame(opening, unit),
|
|
153
|
+
frame: openingFrame(opening, nodes, unit),
|
|
154
154
|
hardware: opening.type === 'door' ? doorHardware(opening as DoorNode) : null,
|
|
155
155
|
count: 1,
|
|
156
156
|
remarks: remarks.join('; '),
|
|
@@ -190,10 +190,14 @@ function openingMaterial(opening: DoorNode | WindowNode): string | null {
|
|
|
190
190
|
return null
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
function openingFrame(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
function openingFrame(
|
|
194
|
+
opening: DoorNode | WindowNode,
|
|
195
|
+
nodes: Readonly<Record<string, AnyNode>>,
|
|
196
|
+
unit: ScheduleUnit,
|
|
197
|
+
): string | null {
|
|
198
|
+
const thickness = opening.frameThickness
|
|
199
|
+
if (typeof thickness !== 'number') return null
|
|
200
|
+
const depth = resolveHostedOpeningFrameDepth(opening, nodes)
|
|
197
201
|
return `${formatScheduleLength(thickness, unit)} / ${formatScheduleLength(depth, unit)}`
|
|
198
202
|
}
|
|
199
203
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createTerrainField,
|
|
4
|
+
LevelNode,
|
|
5
|
+
quantize,
|
|
6
|
+
type SceneSnapshot,
|
|
7
|
+
SlabNode,
|
|
8
|
+
ZoneNode,
|
|
9
|
+
} from '@pascal-app/core'
|
|
3
10
|
import { buildSitePlanDrawing, setbacksWarning } from './build-site-plan-drawing'
|
|
4
11
|
import { registerSitePlanContributor } from './contributors'
|
|
5
12
|
import { computeSiteCoverage } from './coverage'
|
|
@@ -14,6 +21,7 @@ import {
|
|
|
14
21
|
streetEdgeNames,
|
|
15
22
|
UNNAMED_STREET,
|
|
16
23
|
} from './site-annotations'
|
|
24
|
+
import { outdoorParts } from './site-parts'
|
|
17
25
|
|
|
18
26
|
/** The QA lot (4121 NW 34th St, Gainesville): a through lot, NW 34th Terrace on the west edge (1), NW 34th Street on the east (3). */
|
|
19
27
|
const LOT: [number, number][] = [
|
|
@@ -436,6 +444,43 @@ describe('the setbacks line a plans examiner reads', () => {
|
|
|
436
444
|
})
|
|
437
445
|
|
|
438
446
|
describe('the finish floor elevation', () => {
|
|
447
|
+
test('outdoor plates use their room type for site parts and stay out of the house floor elevation', () => {
|
|
448
|
+
const polygon: [number, number][] = [
|
|
449
|
+
[0, 0],
|
|
450
|
+
[2, 0],
|
|
451
|
+
[2, 2],
|
|
452
|
+
[0, 2],
|
|
453
|
+
]
|
|
454
|
+
const room = ZoneNode.parse({ name: 'Deck', roomType: 'deck', polygon })
|
|
455
|
+
const plate = SlabNode.parse({
|
|
456
|
+
zoneIds: [room.id],
|
|
457
|
+
plateRole: 'sunken',
|
|
458
|
+
polygon,
|
|
459
|
+
elevation: 0.4,
|
|
460
|
+
})
|
|
461
|
+
const floor = SlabNode.parse({
|
|
462
|
+
plateRole: 'base',
|
|
463
|
+
zoneIds: [room.id],
|
|
464
|
+
polygon: [],
|
|
465
|
+
elevation: 0.05,
|
|
466
|
+
})
|
|
467
|
+
const level = LevelNode.parse({ children: [plate.id, floor.id, room.id] })
|
|
468
|
+
for (const metadata of [{}, { floor: 'porch-slab' }]) {
|
|
469
|
+
const scene = {
|
|
470
|
+
nodes: {
|
|
471
|
+
[level.id]: level,
|
|
472
|
+
[room.id]: room,
|
|
473
|
+
[plate.id]: { ...plate, metadata },
|
|
474
|
+
[floor.id]: floor,
|
|
475
|
+
},
|
|
476
|
+
} as unknown as SceneSnapshot
|
|
477
|
+
expect(outdoorParts(scene, level, null, [], [])).toMatchObject([
|
|
478
|
+
{ id: plate.id, kind: 'deck', wood: true },
|
|
479
|
+
])
|
|
480
|
+
expect(floorTops(scene, level)).toEqual({ floor: 0.05, garage: null })
|
|
481
|
+
}
|
|
482
|
+
})
|
|
483
|
+
|
|
439
484
|
test('the driveway and walk (flatwork, no floor tag) never become the FF', () => {
|
|
440
485
|
const nodes = {
|
|
441
486
|
level_0: {
|
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
type AnyNodeId,
|
|
12
12
|
type BuildingNode,
|
|
13
13
|
type FloorplanGeometry,
|
|
14
|
+
floorPlateRoom,
|
|
14
15
|
heightAt,
|
|
16
|
+
isOutdoorRoomType,
|
|
15
17
|
type LevelNode,
|
|
16
18
|
outwardNormal,
|
|
17
19
|
type SceneSnapshot,
|
|
@@ -1072,6 +1074,7 @@ export function floorTops(
|
|
|
1072
1074
|
if (n?.type !== 'slab') continue
|
|
1073
1075
|
// the driveway and walks are paving at grade, never the finish floor
|
|
1074
1076
|
if (flatworkKindOf(n)) continue
|
|
1077
|
+
if (isOutdoorRoomType(floorPlateRoom(n, (id) => scene.nodes[id])?.roomType)) continue
|
|
1075
1078
|
const tag = n.metadata?.floor
|
|
1076
1079
|
const top = typeof n.elevation === 'number' ? n.elevation : 0.05
|
|
1077
1080
|
if (tag === 'garage-slab-at-grade') garage = garage === null ? top : Math.min(garage, top)
|