@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
|
@@ -19,6 +19,8 @@ import { resolveGenericFloorplanGridEventPoint } from '../../lib/floorplan-grid-
|
|
|
19
19
|
import { snapRegisteredDraftPoint } from '../../lib/registered-draft-snap'
|
|
20
20
|
import { resolveSlabPlanPointSnap } from '../../lib/slab-plan-snap'
|
|
21
21
|
import { commitWallPolygonDraft, wallPolygonDraftWalls } from '../../lib/wall-polygon-draft'
|
|
22
|
+
import { wallDraftIsRun } from '../../lib/wall-pull'
|
|
23
|
+
import { isWallRunFree, snapWallRunDraftPoint } from '../../lib/wall-run-snap'
|
|
22
24
|
import useAlignmentGuides from '../../store/use-alignment-guides'
|
|
23
25
|
import useEditor, { isAngleSnapActive, isMagneticSnapActive } from '../../store/use-editor'
|
|
24
26
|
import usePlacementPreview from '../../store/use-placement-preview'
|
|
@@ -52,6 +54,8 @@ type UseFloorplanBackgroundPlacementArgs = {
|
|
|
52
54
|
wall: WallNode
|
|
53
55
|
} | null
|
|
54
56
|
floorplanOpeningLocalY: number
|
|
57
|
+
/** An open wall run's start and the direction of its last wall (null before one). */
|
|
58
|
+
getWallRunContext: () => { runStart: WallPlanPoint | null; reference: WallPlanPoint | null }
|
|
55
59
|
getSnappedFloorplanPoint: (point: WallPlanPoint) => WallPlanPoint
|
|
56
60
|
handleCeilingItemPlacementClick: (
|
|
57
61
|
planPoint: WallPlanPoint,
|
|
@@ -72,12 +76,9 @@ type UseFloorplanBackgroundPlacementArgs = {
|
|
|
72
76
|
isZoneBuildActive: boolean
|
|
73
77
|
levelId: string | null
|
|
74
78
|
registryToolOwnsSnapping: boolean
|
|
75
|
-
roofDraftStart: WallPlanPoint | null
|
|
76
79
|
setCursorPoint: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
77
80
|
setFenceDraftEnd: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
78
81
|
setFenceDraftStart: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
79
|
-
setRoofDraftEnd: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
80
|
-
setRoofDraftStart: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
81
82
|
snapWallDraftPoint: (args: {
|
|
82
83
|
point: WallPlanPoint
|
|
83
84
|
walls: WallNode[]
|
|
@@ -86,6 +87,7 @@ type UseFloorplanBackgroundPlacementArgs = {
|
|
|
86
87
|
bypassSnap?: boolean
|
|
87
88
|
step?: number
|
|
88
89
|
gridSnap?: (point: WallPlanPoint) => WallPlanPoint
|
|
90
|
+
captureWallBodies?: boolean
|
|
89
91
|
}) => WallPlanPoint
|
|
90
92
|
snapPolygonDraftPoint: (args: {
|
|
91
93
|
point: WallPlanPoint
|
|
@@ -95,10 +97,9 @@ type UseFloorplanBackgroundPlacementArgs = {
|
|
|
95
97
|
toPoint2D: (point: WallPlanPoint) => { x: number; y: number }
|
|
96
98
|
walls: WallNode[]
|
|
97
99
|
/**
|
|
98
|
-
* Snap a building-local plan point
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* step.
|
|
100
|
+
* Snap a building-local plan point onto the building's grid lattice at
|
|
101
|
+
* `step` (the lattice the plan draws, which turns with the building).
|
|
102
|
+
* Injected — used by wall / fence branches that snap at variable step.
|
|
102
103
|
*/
|
|
103
104
|
worldGridSnap: (point: WallPlanPoint, step: number) => WallPlanPoint
|
|
104
105
|
}
|
|
@@ -115,6 +116,7 @@ export function useFloorplanBackgroundPlacement({
|
|
|
115
116
|
findClosestWallPoint,
|
|
116
117
|
floorplanOpeningLocalY,
|
|
117
118
|
getSnappedFloorplanPoint,
|
|
119
|
+
getWallRunContext,
|
|
118
120
|
handleCeilingItemPlacementClick,
|
|
119
121
|
handleCeilingPlacementPoint,
|
|
120
122
|
handleSlabPlacementPoint,
|
|
@@ -131,12 +133,9 @@ export function useFloorplanBackgroundPlacement({
|
|
|
131
133
|
isZoneBuildActive,
|
|
132
134
|
levelId,
|
|
133
135
|
registryToolOwnsSnapping,
|
|
134
|
-
roofDraftStart,
|
|
135
136
|
setCursorPoint,
|
|
136
137
|
setFenceDraftEnd,
|
|
137
138
|
setFenceDraftStart,
|
|
138
|
-
setRoofDraftEnd,
|
|
139
|
-
setRoofDraftStart,
|
|
140
139
|
snapWallDraftPoint,
|
|
141
140
|
snapPolygonDraftPoint,
|
|
142
141
|
toPoint2D,
|
|
@@ -219,24 +218,14 @@ export function useFloorplanBackgroundPlacement({
|
|
|
219
218
|
if (isRoofBuildActive) {
|
|
220
219
|
// Footprint placement (polygon context: grid / lines / off, no angle),
|
|
221
220
|
// mode-driven to match the chip. Alt is force/free at commit time;
|
|
222
|
-
// alignment display/pull follows the active magnetic mode.
|
|
221
|
+
// alignment display/pull follows the active magnetic mode. The roof
|
|
222
|
+
// tool snaps the corner it commits and writes the draft rectangle from
|
|
223
|
+
// it, so the plan shows exactly the roof the click makes.
|
|
223
224
|
const snappedPoint = alignFloorplanDraftPoint(getSnappedFloorplanPoint(planPoint), {
|
|
224
225
|
applySnap: isMagneticSnapActive(),
|
|
225
226
|
})
|
|
226
227
|
emitFloorplanGridEvent('click', snappedPoint, event)
|
|
227
228
|
setCursorPoint(snappedPoint)
|
|
228
|
-
|
|
229
|
-
if (roofFootprintSource !== 'draw') {
|
|
230
|
-
clearRoofPlacementDraft()
|
|
231
|
-
return true
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (roofDraftStart) {
|
|
235
|
-
clearRoofPlacementDraft()
|
|
236
|
-
} else {
|
|
237
|
-
setRoofDraftStart(snappedPoint)
|
|
238
|
-
setRoofDraftEnd(snappedPoint)
|
|
239
|
-
}
|
|
240
229
|
return true
|
|
241
230
|
}
|
|
242
231
|
|
|
@@ -254,8 +243,8 @@ export function useFloorplanBackgroundPlacement({
|
|
|
254
243
|
|
|
255
244
|
if (isFenceBuildActive) {
|
|
256
245
|
// Fence draft: mode-driven (matches the chip), same as the move
|
|
257
|
-
// preview. `grid` snaps to the
|
|
258
|
-
//
|
|
246
|
+
// preview. `grid` snaps to the building's grid lattice (the `gridSnap`
|
|
247
|
+
// callback), `angles` locks 15° rays from the start, `lines`
|
|
259
248
|
// pulls onto walls / fences / alignment, `off` is free.
|
|
260
249
|
const fenceStep = getSegmentGridStep()
|
|
261
250
|
const fenceAngleSnap = fenceDraftStart !== null && isAngleSnapActive()
|
|
@@ -363,30 +352,48 @@ export function useFloorplanBackgroundPlacement({
|
|
|
363
352
|
// / draftEnd state in the floor plan would never update, leaving
|
|
364
353
|
// the dashed-line draft preview invisible.
|
|
365
354
|
if (isWallBuildActive) {
|
|
366
|
-
// Wall draft
|
|
367
|
-
// `grid` snaps to the
|
|
368
|
-
// `angles` locks 15° rays from
|
|
369
|
-
// onto existing wall corners / edges + alignment, `off`
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
355
|
+
// Wall draft, matching the move-preview branch: mode-driven (matches
|
|
356
|
+
// the chip). `grid` snaps to the building's grid lattice (`gridSnap`),
|
|
357
|
+
// `angles` locks a Walls segment to 15° rays from its start, `lines`
|
|
358
|
+
// pulls the point onto existing wall corners / edges + alignment, `off`
|
|
359
|
+
// is free, and in every mode but `lines` an end aimed into a wall's body
|
|
360
|
+
// joins that wall. A run's segment (a Polygon side, a pulled run) lands
|
|
361
|
+
// where the move showed it (`snapWallRunDraftPoint`).
|
|
362
|
+
let snappedPoint: WallPlanPoint
|
|
363
|
+
if (draftStart && wallDraftIsRun()) {
|
|
364
|
+
const run = getWallRunContext()
|
|
365
|
+
snappedPoint = snapWallRunDraftPoint({
|
|
366
|
+
point: planPoint,
|
|
367
|
+
start: draftStart,
|
|
368
|
+
runStart: run.runStart ?? draftStart,
|
|
369
|
+
reference: run.reference,
|
|
370
|
+
walls: [...walls, ...wallPolygonDraftWalls()],
|
|
371
|
+
free: isWallRunFree(event),
|
|
372
|
+
}).point
|
|
384
373
|
} else {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
374
|
+
const wallStep = getSegmentGridStep()
|
|
375
|
+
const wallAngleSnap = draftStart !== null && isAngleSnapActive()
|
|
376
|
+
const wallSnapped = snapWallDraftPoint({
|
|
377
|
+
point: planPoint,
|
|
378
|
+
walls: [...walls, ...wallPolygonDraftWalls()],
|
|
379
|
+
start: draftStart ?? undefined,
|
|
380
|
+
angleSnap: wallAngleSnap,
|
|
381
|
+
gridSnap: (p) => worldGridSnap(p, wallStep),
|
|
382
|
+
captureWallBodies: true,
|
|
389
383
|
})
|
|
384
|
+
const wallGridBase = worldGridSnap(planPoint, wallStep)
|
|
385
|
+
const wallLocked =
|
|
386
|
+
wallSnapped[0] !== wallGridBase[0] || wallSnapped[1] !== wallGridBase[1]
|
|
387
|
+
snappedPoint = wallSnapped
|
|
388
|
+
if (wallLocked) {
|
|
389
|
+
useAlignmentGuides.getState().clear()
|
|
390
|
+
} else {
|
|
391
|
+
// Alignment lines are shown in every mode; the pull applies only when
|
|
392
|
+
// magnetic ('lines') and the segment isn't angle-locked.
|
|
393
|
+
snappedPoint = alignFloorplanDraftPoint(wallSnapped, {
|
|
394
|
+
applySnap: isMagneticSnapActive() && !wallAngleSnap,
|
|
395
|
+
})
|
|
396
|
+
}
|
|
390
397
|
}
|
|
391
398
|
|
|
392
399
|
emitFloorplanGridEvent('click', snappedPoint, event)
|
|
@@ -437,12 +444,12 @@ export function useFloorplanBackgroundPlacement({
|
|
|
437
444
|
activePolygonDraftPoints,
|
|
438
445
|
ceilingDraftPoints,
|
|
439
446
|
clearFencePlacementDraft,
|
|
440
|
-
clearRoofPlacementDraft,
|
|
441
447
|
clearWallPlacementDraft,
|
|
442
448
|
emitFloorplanGridEvent,
|
|
443
449
|
fenceDraftStart,
|
|
444
450
|
fences,
|
|
445
451
|
findClosestWallPoint,
|
|
452
|
+
getWallRunContext,
|
|
446
453
|
floorplanOpeningLocalY,
|
|
447
454
|
getSnappedFloorplanPoint,
|
|
448
455
|
handleCeilingItemPlacementClick,
|
|
@@ -459,14 +466,10 @@ export function useFloorplanBackgroundPlacement({
|
|
|
459
466
|
isWallBuildActive,
|
|
460
467
|
isZoneBuildActive,
|
|
461
468
|
levelId,
|
|
462
|
-
roofDraftStart,
|
|
463
469
|
registryToolOwnsSnapping,
|
|
464
|
-
roofFootprintSource,
|
|
465
470
|
setCursorPoint,
|
|
466
471
|
setFenceDraftEnd,
|
|
467
472
|
setFenceDraftStart,
|
|
468
|
-
setRoofDraftEnd,
|
|
469
|
-
setRoofDraftStart,
|
|
470
473
|
snapWallDraftPoint,
|
|
471
474
|
snapPolygonDraftPoint,
|
|
472
475
|
toPoint2D,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { type MessageDescriptor, type PascalI18n, usePascalI18n } from '@pascal-app/core'
|
|
3
4
|
import { type GroundMode, useViewer } from '@pascal-app/viewer'
|
|
4
5
|
import { ArrowLeftRight, Check, Columns2, MoreHorizontal, Pin, PinOff, X } from 'lucide-react'
|
|
5
6
|
import { type ReactNode, useEffect, useRef } from 'react'
|
|
@@ -7,7 +8,7 @@ import { useIsMobile } from '../../../hooks/use-mobile'
|
|
|
7
8
|
import { type EditorViewDescriptor, isSceneView, isViewPinned } from '../../../lib/editor-views'
|
|
8
9
|
import { cn } from '../../../lib/utils'
|
|
9
10
|
import useEditor from '../../../store/use-editor'
|
|
10
|
-
import type
|
|
11
|
+
import { VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../../store/view-layout'
|
|
11
12
|
import { IconRefGlyph } from '../../ui/icon-ref'
|
|
12
13
|
import {
|
|
13
14
|
DropdownMenu,
|
|
@@ -30,7 +31,16 @@ const IDLE =
|
|
|
30
31
|
'text-muted-foreground/70 hover:bg-black/5 dark:hover:bg-white/8 hover:text-muted-foreground'
|
|
31
32
|
|
|
32
33
|
/** The views of one pane: its pinned views, the one it shows, and a menu with the rest. */
|
|
34
|
+
/** Built-in views are named by the editor; host and plugin views bring their own label. */
|
|
35
|
+
function viewLabel(view: EditorViewDescriptor, i18n: PascalI18n): string {
|
|
36
|
+
if (view.id === VIEW_3D) return i18n._({ id: 'editor.views.view3d', message: '3D' })
|
|
37
|
+
if (view.id === VIEW_2D) return i18n._({ id: 'editor.views.view2d', message: '2D' })
|
|
38
|
+
if (view.id === PLUGINS_VIEW_ID) return i18n._({ id: 'editor.views.plugins', message: 'Plugins' })
|
|
39
|
+
return view.label
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
export function ViewTabs({ pane, className }: { pane: ViewPaneIndex; className?: string }) {
|
|
43
|
+
const i18n = usePascalI18n()
|
|
34
44
|
const layout = useActiveViewLayout()
|
|
35
45
|
const views = useEditorViews()
|
|
36
46
|
const pinnedViews = useEditor((s) => s.pinnedViews)
|
|
@@ -58,7 +68,11 @@ export function ViewTabs({ pane, className }: { pane: ViewPaneIndex; className?:
|
|
|
58
68
|
return (
|
|
59
69
|
<div className={cn(GROUP, 'min-w-0 shrink', className)}>
|
|
60
70
|
<div
|
|
61
|
-
aria-label={
|
|
71
|
+
aria-label={i18n._({
|
|
72
|
+
id: 'editor.views.viewBar.paneViews',
|
|
73
|
+
message: 'Pane {pane} views',
|
|
74
|
+
values: { pane: pane + 1 },
|
|
75
|
+
})}
|
|
62
76
|
className="flex min-w-0 overflow-x-auto [scrollbar-width:none]"
|
|
63
77
|
role="tablist"
|
|
64
78
|
ref={tabList}
|
|
@@ -102,16 +116,20 @@ export function ViewTabs({ pane, className }: { pane: ViewPaneIndex; className?:
|
|
|
102
116
|
}
|
|
103
117
|
}}
|
|
104
118
|
tabIndex={view.id === current ? 0 : -1}
|
|
105
|
-
title={view
|
|
119
|
+
title={viewLabel(view, i18n)}
|
|
106
120
|
role="tab"
|
|
107
121
|
type="button"
|
|
108
122
|
>
|
|
109
123
|
<IconRefGlyph icon={view.icon} size={14} />
|
|
110
|
-
<span className="max-w-36 truncate">{view
|
|
124
|
+
<span className="max-w-36 truncate">{viewLabel(view, i18n)}</span>
|
|
111
125
|
</button>
|
|
112
126
|
{view.onClose && (
|
|
113
127
|
<button
|
|
114
|
-
aria-label={
|
|
128
|
+
aria-label={i18n._({
|
|
129
|
+
id: 'editor.views.viewBar.closeLabel',
|
|
130
|
+
message: 'Close {label}',
|
|
131
|
+
values: { label: viewLabel(view, i18n) },
|
|
132
|
+
})}
|
|
115
133
|
className="mr-1 flex size-6 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
|
|
116
134
|
onClick={view.onClose}
|
|
117
135
|
type="button"
|
|
@@ -128,9 +146,27 @@ export function ViewTabs({ pane, className }: { pane: ViewPaneIndex; className?:
|
|
|
128
146
|
}
|
|
129
147
|
|
|
130
148
|
const GROUND_MODES = [
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
{
|
|
150
|
+
mode: 'auto',
|
|
151
|
+
label: /* i18n */ { id: 'editor.views.viewBar.auto', message: 'Auto' },
|
|
152
|
+
title: /* i18n */ {
|
|
153
|
+
id: 'editor.views.viewBar.veilTheGroundOverALevel',
|
|
154
|
+
message: 'Veil the ground over a level below grade',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
mode: 'solid',
|
|
159
|
+
label: /* i18n */ { id: 'editor.views.viewBar.solid', message: 'Solid' },
|
|
160
|
+
title: /* i18n */ {
|
|
161
|
+
id: 'editor.views.viewBar.keepTheGroundSolid',
|
|
162
|
+
message: 'Keep the ground solid',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
] as const satisfies ReadonlyArray<{
|
|
166
|
+
mode: GroundMode
|
|
167
|
+
label: MessageDescriptor
|
|
168
|
+
title: MessageDescriptor
|
|
169
|
+
}>
|
|
134
170
|
|
|
135
171
|
/**
|
|
136
172
|
* Over a level below grade the ground is veiled so its rooms show, and the bar says so: the
|
|
@@ -139,6 +175,7 @@ const GROUND_MODES = [
|
|
|
139
175
|
* a level at grade or only partly below it.
|
|
140
176
|
*/
|
|
141
177
|
export function GroundModeToggle() {
|
|
178
|
+
const i18n = usePascalI18n()
|
|
142
179
|
const belowGrade = useViewer((s) => s.belowGrade)
|
|
143
180
|
const gradeStanding = useViewer((s) => s.gradeStanding)
|
|
144
181
|
const groundWindowVeiled = useViewer((s) => s.groundWindowVeiled)
|
|
@@ -147,15 +184,31 @@ export function GroundModeToggle() {
|
|
|
147
184
|
const veiled =
|
|
148
185
|
groundMode === 'auto' &&
|
|
149
186
|
((belowGrade && gradeStanding !== 'partly-below-grade') || groundWindowVeiled)
|
|
150
|
-
const
|
|
151
|
-
const label =
|
|
187
|
+
const standing =
|
|
152
188
|
gradeStanding === 'partly-below-grade'
|
|
153
|
-
?
|
|
189
|
+
? 'partly'
|
|
154
190
|
: gradeStanding === 'below-grade'
|
|
155
|
-
?
|
|
156
|
-
:
|
|
191
|
+
? 'below'
|
|
192
|
+
: 'other'
|
|
193
|
+
const label = veiled
|
|
194
|
+
? i18n._({
|
|
195
|
+
id: 'editor.views.viewBar.groundVeiled',
|
|
196
|
+
message:
|
|
197
|
+
'{standing, select, partly {Partly below grade · ground veiled} below {Below grade · ground veiled} other {Ground veiled}}',
|
|
198
|
+
values: { standing },
|
|
199
|
+
})
|
|
200
|
+
: i18n._({
|
|
201
|
+
id: 'editor.views.viewBar.groundSolid',
|
|
202
|
+
message:
|
|
203
|
+
'{standing, select, partly {Partly below grade · ground solid} below {Below grade · ground solid} other {Ground solid}}',
|
|
204
|
+
values: { standing },
|
|
205
|
+
})
|
|
157
206
|
return (
|
|
158
|
-
<div
|
|
207
|
+
<div
|
|
208
|
+
aria-label={i18n._({ id: 'editor.views.viewBar.ground', message: 'Ground' })}
|
|
209
|
+
className={GROUP}
|
|
210
|
+
role="group"
|
|
211
|
+
>
|
|
159
212
|
<span className="flex items-center px-2.5 text-muted-foreground text-xs">{label}</span>
|
|
160
213
|
{GROUND_MODES.map(({ mode, label, title }) => (
|
|
161
214
|
<button
|
|
@@ -163,10 +216,10 @@ export function GroundModeToggle() {
|
|
|
163
216
|
className={cn(SEGMENT, groundMode === mode ? ACTIVE : IDLE)}
|
|
164
217
|
key={mode}
|
|
165
218
|
onClick={() => useViewer.getState().setGroundMode(mode)}
|
|
166
|
-
title={title}
|
|
219
|
+
title={i18n._(title)}
|
|
167
220
|
type="button"
|
|
168
221
|
>
|
|
169
|
-
{label}
|
|
222
|
+
{i18n._(label)}
|
|
170
223
|
</button>
|
|
171
224
|
))}
|
|
172
225
|
</div>
|
|
@@ -175,15 +228,16 @@ export function GroundModeToggle() {
|
|
|
175
228
|
|
|
176
229
|
/** Split toggle and, split, the swap — at the right end of the last pane's bar. */
|
|
177
230
|
function SplitControls() {
|
|
231
|
+
const i18n = usePascalI18n()
|
|
178
232
|
const split = useActiveViewLayout().split
|
|
179
233
|
return (
|
|
180
234
|
<div className={GROUP}>
|
|
181
235
|
{split && (
|
|
182
236
|
<button
|
|
183
|
-
aria-label=
|
|
237
|
+
aria-label={i18n._({ id: 'editor.views.viewBar.swapPanes', message: 'Swap panes' })}
|
|
184
238
|
className={cn(SEGMENT, IDLE, 'px-2')}
|
|
185
239
|
onClick={() => useEditor.getState().swapPanes()}
|
|
186
|
-
title=
|
|
240
|
+
title={i18n._({ id: 'editor.views.viewBar.swapPanes', message: 'Swap panes' })}
|
|
187
241
|
type="button"
|
|
188
242
|
>
|
|
189
243
|
<ArrowLeftRight className="h-3 w-3" />
|
|
@@ -193,11 +247,15 @@ function SplitControls() {
|
|
|
193
247
|
aria-pressed={split}
|
|
194
248
|
className={cn(SEGMENT, split ? ACTIVE : IDLE)}
|
|
195
249
|
onClick={() => useEditor.getState().toggleSplit()}
|
|
196
|
-
title={
|
|
250
|
+
title={
|
|
251
|
+
split
|
|
252
|
+
? i18n._({ id: 'editor.views.viewBar.closeSplit', message: 'Close split (\\)' })
|
|
253
|
+
: i18n._({ id: 'editor.views.viewBar.split', message: 'Split (\\)' })
|
|
254
|
+
}
|
|
197
255
|
type="button"
|
|
198
256
|
>
|
|
199
257
|
<Columns2 className="h-3 w-3" />
|
|
200
|
-
<span>Split</span>
|
|
258
|
+
<span>{i18n._({ id: 'editor.views.viewBar.split2', message: 'Split' })}</span>
|
|
201
259
|
</button>
|
|
202
260
|
</div>
|
|
203
261
|
)
|
|
@@ -272,6 +330,7 @@ function MoreViewsMenu({
|
|
|
272
330
|
current: string
|
|
273
331
|
views: EditorViewDescriptor[]
|
|
274
332
|
}) {
|
|
333
|
+
const i18n = usePascalI18n()
|
|
275
334
|
const pinnedViews = useEditor((s) => s.pinnedViews)
|
|
276
335
|
const setPaneView = useEditor((s) => s.setPaneView)
|
|
277
336
|
const setViewPinned = useEditor((s) => s.setViewPinned)
|
|
@@ -282,7 +341,7 @@ function MoreViewsMenu({
|
|
|
282
341
|
<DropdownMenu>
|
|
283
342
|
<DropdownMenuTrigger asChild>
|
|
284
343
|
<button
|
|
285
|
-
aria-label=
|
|
344
|
+
aria-label={i18n._({ id: 'editor.views.viewBar.moreViews', message: 'More views' })}
|
|
286
345
|
className={cn(SEGMENT, IDLE, 'shrink-0 px-2')}
|
|
287
346
|
type="button"
|
|
288
347
|
>
|
|
@@ -290,7 +349,9 @@ function MoreViewsMenu({
|
|
|
290
349
|
</button>
|
|
291
350
|
</DropdownMenuTrigger>
|
|
292
351
|
<DropdownMenuContent align="start" className="w-64">
|
|
293
|
-
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
|
352
|
+
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
|
353
|
+
{i18n._({ id: 'editor.views.viewBar.views', message: 'Views' })}
|
|
354
|
+
</DropdownMenuLabel>
|
|
294
355
|
{listed.map((view) => {
|
|
295
356
|
const pinned = isViewPinned(view, pinnedViews)
|
|
296
357
|
return (
|
|
@@ -300,11 +361,23 @@ function MoreViewsMenu({
|
|
|
300
361
|
onSelect={() => setPaneView(pane, view.id)}
|
|
301
362
|
>
|
|
302
363
|
<IconRefGlyph icon={view.icon} size={16} />
|
|
303
|
-
<span className="min-w-0 flex-1 truncate">{view
|
|
364
|
+
<span className="min-w-0 flex-1 truncate">{viewLabel(view, i18n)}</span>
|
|
304
365
|
{view.id === current && <Check className="h-3.5 w-3.5 text-muted-foreground" />}
|
|
305
366
|
{!isSceneView(view.id) && !view.onClose && !view.alwaysPinned && (
|
|
306
367
|
<button
|
|
307
|
-
aria-label={
|
|
368
|
+
aria-label={
|
|
369
|
+
pinned
|
|
370
|
+
? i18n._({
|
|
371
|
+
id: 'editor.views.viewBar.unpinLabel',
|
|
372
|
+
message: 'Unpin {label}',
|
|
373
|
+
values: { label: viewLabel(view, i18n) },
|
|
374
|
+
})
|
|
375
|
+
: i18n._({
|
|
376
|
+
id: 'editor.views.viewBar.pinLabel',
|
|
377
|
+
message: 'Pin {label}',
|
|
378
|
+
values: { label: viewLabel(view, i18n) },
|
|
379
|
+
})
|
|
380
|
+
}
|
|
308
381
|
aria-pressed={pinned}
|
|
309
382
|
className={cn(
|
|
310
383
|
'flex h-6 w-6 items-center justify-center rounded-md transition-colors hover:bg-black/6 dark:hover:bg-white/10',
|
|
@@ -316,7 +389,17 @@ function MoreViewsMenu({
|
|
|
316
389
|
setViewPinned(view.id, !pinned)
|
|
317
390
|
}}
|
|
318
391
|
onPointerDown={(event) => event.stopPropagation()}
|
|
319
|
-
title={
|
|
392
|
+
title={
|
|
393
|
+
pinned
|
|
394
|
+
? i18n._({
|
|
395
|
+
id: 'editor.views.viewBar.unpinFromTheViewBar',
|
|
396
|
+
message: 'Unpin from the view bar',
|
|
397
|
+
})
|
|
398
|
+
: i18n._({
|
|
399
|
+
id: 'editor.views.viewBar.pinToTheViewBar',
|
|
400
|
+
message: 'Pin to the view bar',
|
|
401
|
+
})
|
|
402
|
+
}
|
|
320
403
|
type="button"
|
|
321
404
|
>
|
|
322
405
|
{pinned ? <PinOff className="h-3.5 w-3.5" /> : <Pin className="h-3.5 w-3.5" />}
|
|
@@ -333,7 +416,9 @@ function MoreViewsMenu({
|
|
|
333
416
|
onSelect={() => setPaneView(pane, PLUGINS_VIEW_ID)}
|
|
334
417
|
>
|
|
335
418
|
<img alt="" className="h-4 w-4 object-contain" src="/icons/plugins.webp" />
|
|
336
|
-
<span>
|
|
419
|
+
<span>
|
|
420
|
+
{i18n._({ id: 'editor.views.viewBar.browsePlugins', message: 'Browse plugins' })}
|
|
421
|
+
</span>
|
|
337
422
|
</DropdownMenuItem>
|
|
338
423
|
</>
|
|
339
424
|
)}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type AnyNode,
|
|
5
5
|
type AnyNodeId,
|
|
6
6
|
calculateLevelMiters,
|
|
7
|
+
formatLinearMeasurement,
|
|
7
8
|
getWallCurveLength,
|
|
8
9
|
getWallEffectiveHeightForNodes,
|
|
9
10
|
getWallMiterBoundaryPoints,
|
|
@@ -20,11 +21,10 @@ import {
|
|
|
20
21
|
type WallNode,
|
|
21
22
|
} from '@pascal-app/core'
|
|
22
23
|
import { getSceneTheme, useViewer } from '@pascal-app/viewer'
|
|
23
|
-
import { Html } from '@react-three/drei'
|
|
24
24
|
import { createPortal, useFrame } from '@react-three/fiber'
|
|
25
25
|
import { useMemo, useState } from 'react'
|
|
26
26
|
import * as THREE from 'three'
|
|
27
|
-
import {
|
|
27
|
+
import { Html } from './localized-html'
|
|
28
28
|
|
|
29
29
|
const GUIDE_Y_OFFSET = 0.08
|
|
30
30
|
const LABEL_LIFT = 0.08
|
|
@@ -315,16 +315,25 @@ function buildMeasurementGuide(
|
|
|
315
315
|
const measurementPoints = measurementLine ?? fallbackMiddlePoints
|
|
316
316
|
if (!measurementPoints) return null
|
|
317
317
|
|
|
318
|
-
const
|
|
318
|
+
const heightStart = getWallEffectiveHeightForNodes(wall, nodes, 0)
|
|
319
|
+
const heightEnd = getWallEffectiveHeightForNodes(wall, nodes, 1)
|
|
319
320
|
const startLocal = worldPointToWallLocal(wall, measurementPoints.start)
|
|
320
321
|
const endLocal = worldPointToWallLocal(wall, measurementPoints.end)
|
|
321
322
|
const curvedMeasurementPath = isCurvedWall(wall)
|
|
322
323
|
? getCurvedWallMeasurementPath(wall, miterData, levelWalls)
|
|
323
324
|
: null
|
|
325
|
+
const dx = wall.end[0] - wall.start[0]
|
|
326
|
+
const dz = wall.end[1] - wall.start[1]
|
|
327
|
+
const wallChordLength = Math.hypot(dx, dz)
|
|
328
|
+
const getChordT = (localX: number) =>
|
|
329
|
+
wallChordLength > 1e-6 ? Math.max(0, Math.min(1, localX / wallChordLength)) : 0
|
|
330
|
+
|
|
324
331
|
const guidePath: Vec3[] = curvedMeasurementPath
|
|
325
332
|
? curvedMeasurementPath.map((point) => {
|
|
326
333
|
const localPoint = worldPointToWallLocal(wall, point)
|
|
327
|
-
|
|
334
|
+
const t = getChordT(localPoint[0])
|
|
335
|
+
const h = getWallEffectiveHeightForNodes(wall, nodes, t)
|
|
336
|
+
return [localPoint[0], h + GUIDE_Y_OFFSET, localPoint[2]]
|
|
328
337
|
})
|
|
329
338
|
: isCurvedWall(wall)
|
|
330
339
|
? sampleWallCenterline(wall, 24).map((point, index, points) => {
|
|
@@ -334,12 +343,14 @@ function buildMeasurementGuide(
|
|
|
334
343
|
: index === points.length - 1
|
|
335
344
|
? endLocal
|
|
336
345
|
: worldPointToWallLocal(wall, point)
|
|
346
|
+
const t = getChordT(localPoint[0])
|
|
347
|
+
const h = getWallEffectiveHeightForNodes(wall, nodes, t)
|
|
337
348
|
|
|
338
|
-
return [localPoint[0],
|
|
349
|
+
return [localPoint[0], h + GUIDE_Y_OFFSET, localPoint[2]]
|
|
339
350
|
})
|
|
340
351
|
: [
|
|
341
|
-
[startLocal[0],
|
|
342
|
-
[endLocal[0],
|
|
352
|
+
[startLocal[0], heightStart + GUIDE_Y_OFFSET, startLocal[2]],
|
|
353
|
+
[endLocal[0], heightEnd + GUIDE_Y_OFFSET, endLocal[2]],
|
|
343
354
|
]
|
|
344
355
|
|
|
345
356
|
if (guidePath.length < 2) return null
|
|
@@ -397,26 +408,30 @@ function buildMeasurementGuide(
|
|
|
397
408
|
],
|
|
398
409
|
})
|
|
399
410
|
const bottomHeightTick = getHorizontalHeightTick(0)
|
|
400
|
-
const topHeightTick = getHorizontalHeightTick(
|
|
411
|
+
const topHeightTick = getHorizontalHeightTick(heightEnd)
|
|
401
412
|
|
|
402
413
|
return {
|
|
403
414
|
guidePath,
|
|
404
|
-
extStartStart: [extensionStartBase[0],
|
|
415
|
+
extStartStart: [extensionStartBase[0], heightStart, extensionStartBase[2]],
|
|
405
416
|
extStartEnd: [
|
|
406
417
|
extensionStartBase[0],
|
|
407
|
-
|
|
418
|
+
heightStart + GUIDE_Y_OFFSET + extOvershoot,
|
|
408
419
|
extensionStartBase[2],
|
|
409
420
|
],
|
|
410
|
-
extEndStart: [extensionEndBase[0],
|
|
411
|
-
extEndEnd: [
|
|
421
|
+
extEndStart: [extensionEndBase[0], heightEnd, extensionEndBase[2]],
|
|
422
|
+
extEndEnd: [
|
|
423
|
+
extensionEndBase[0],
|
|
424
|
+
heightEnd + GUIDE_Y_OFFSET + extOvershoot,
|
|
425
|
+
extensionEndBase[2],
|
|
426
|
+
],
|
|
412
427
|
labelPosition: [midpoint[0], midpoint[1] + LABEL_LIFT, midpoint[2]],
|
|
413
428
|
heightStart: [heightGuidePosition[0], 0, heightGuidePosition[2]],
|
|
414
|
-
heightEnd: [heightGuidePosition[0],
|
|
429
|
+
heightEnd: [heightGuidePosition[0], heightEnd, heightGuidePosition[2]],
|
|
415
430
|
heightBottomTickStart: bottomHeightTick.start,
|
|
416
431
|
heightBottomTickEnd: bottomHeightTick.end,
|
|
417
432
|
heightTopTickStart: topHeightTick.start,
|
|
418
433
|
heightTopTickEnd: topHeightTick.end,
|
|
419
|
-
heightLabelPosition: [heightGuidePosition[0],
|
|
434
|
+
heightLabelPosition: [heightGuidePosition[0], heightEnd / 2, heightGuidePosition[2]],
|
|
420
435
|
}
|
|
421
436
|
}
|
|
422
437
|
|
|
@@ -530,7 +545,8 @@ function WallMeasurementAnnotation({ wall }: { wall: WallNode }) {
|
|
|
530
545
|
return total
|
|
531
546
|
}, [guide, wall])
|
|
532
547
|
const label = formatLinearMeasurement(length, unit, metricNotation)
|
|
533
|
-
|
|
548
|
+
// Height annotation uses t=1 (wall end) because the vertical guide is drawn at the endpoint
|
|
549
|
+
const height = useMemo(() => getWallEffectiveHeightForNodes(wall, nodes, 1), [nodes, wall])
|
|
534
550
|
const heightLabel = `H ${formatLinearMeasurement(height, unit, metricNotation)}`
|
|
535
551
|
|
|
536
552
|
if (!(guide && Number.isFinite(length) && length >= 0.01)) return null
|