@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
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
createAngleAccumulator,
|
|
10
10
|
createSceneApi,
|
|
11
11
|
DEFAULT_ANGLE_STEP,
|
|
12
|
+
formatAngleRadians,
|
|
12
13
|
type HandleDescriptor,
|
|
13
14
|
type HandlePortal,
|
|
14
15
|
type LatchHandle,
|
|
@@ -21,7 +22,6 @@ import {
|
|
|
21
22
|
useScene,
|
|
22
23
|
} from '@pascal-app/core'
|
|
23
24
|
import { useViewer } from '@pascal-app/viewer'
|
|
24
|
-
import { Html } from '@react-three/drei'
|
|
25
25
|
import { createPortal, type ThreeEvent, useFrame, useThree } from '@react-three/fiber'
|
|
26
26
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
27
27
|
import {
|
|
@@ -41,7 +41,6 @@ import {
|
|
|
41
41
|
Vector3,
|
|
42
42
|
} from 'three'
|
|
43
43
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
|
44
|
-
|
|
45
44
|
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
46
45
|
import { useCeilingEditCeilingId } from '../../lib/ceiling-edit-session'
|
|
47
46
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
@@ -61,7 +60,6 @@ import useInteractionScope, {
|
|
|
61
60
|
useMovingNode,
|
|
62
61
|
} from '../../store/use-interaction-scope'
|
|
63
62
|
import useOpeningGuides from '../../store/use-opening-guides'
|
|
64
|
-
import { formatAngleRadians } from '../tools/shared/segment-angle'
|
|
65
63
|
import {
|
|
66
64
|
ARROW_COLOR,
|
|
67
65
|
ARROW_HOVER_COLOR,
|
|
@@ -78,6 +76,7 @@ import {
|
|
|
78
76
|
import { createLinearResizeDragBinding, linearResizeFactor } from './handles/linear-resize-drag'
|
|
79
77
|
import { resolveResizeSnapValue } from './handles/resize-snap'
|
|
80
78
|
import { type HandleDragControls, useHandleDrag } from './handles/use-handle-drag'
|
|
79
|
+
import { Html } from './localized-html'
|
|
81
80
|
|
|
82
81
|
// Pooled scratch for the handle rig's world-relative pose mapping.
|
|
83
82
|
const _rigRelative = new Matrix4()
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { type OpenWallEnd, sceneRegistry } from '@pascal-app/core'
|
|
4
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
5
|
-
import { Html } from '@react-three/drei'
|
|
4
|
+
import { MEASUREMENT_DANGLING_COLOR, useViewer } from '@pascal-app/viewer'
|
|
6
5
|
import { useFrame } from '@react-three/fiber'
|
|
7
6
|
import { memo, useEffect, useMemo, useRef } from 'react'
|
|
8
7
|
import { BoxGeometry, CircleGeometry, type Group, RingGeometry } from 'three'
|
|
@@ -14,11 +13,11 @@ import {
|
|
|
14
13
|
} from '../../hooks/use-open-wall-ends'
|
|
15
14
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
16
15
|
import { openWallEndKey, openWallEndLabel } from '../../lib/floorplan/open-wall-ends'
|
|
17
|
-
import { MEASUREMENT_DANGLING_COLOR } from '../../lib/measurements'
|
|
18
16
|
import { cn } from '../../lib/utils'
|
|
19
17
|
import useEditor from '../../store/use-editor'
|
|
20
18
|
import { isViewVisible, VIEW_3D, visibleScene } from '../../store/view-layout'
|
|
21
19
|
import { JoinWallsPill } from './join-walls-pill'
|
|
20
|
+
import { Html } from './localized-html'
|
|
22
21
|
import { OpenWallEndsHint } from './open-wall-ends-hint'
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { type AnyNodeId, emitter, type OpenWallEnd } from '@pascal-app/core'
|
|
4
|
+
import { MEASUREMENT_DANGLING_COLOR } from '@pascal-app/viewer'
|
|
4
5
|
import { useEffect, useState } from 'react'
|
|
5
6
|
import { createPortal } from 'react-dom'
|
|
6
7
|
import { openWallEndsSummary } from '../../lib/floorplan/open-wall-ends'
|
|
7
|
-
import { MEASUREMENT_DANGLING_COLOR } from '../../lib/measurements'
|
|
8
8
|
|
|
9
9
|
// The viewer area holds the floor plan and the 3D view side by side, so its
|
|
10
10
|
// top centre clears both views' corner controls in 2D, 3D and split alike.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useViewer } from '@pascal-app/viewer'
|
|
4
|
-
import { Html } from '@react-three/drei'
|
|
5
4
|
import { memo, useEffect, useLayoutEffect, useMemo } from 'react'
|
|
6
5
|
import { BufferGeometry, Float32BufferAttribute, Line as ThreeLine } from 'three'
|
|
7
6
|
import { LineBasicNodeMaterial } from 'three/webgpu'
|
|
@@ -10,6 +9,7 @@ import useOpeningGuides, {
|
|
|
10
9
|
type OpeningGuide3D,
|
|
11
10
|
type OpeningGuideVec3,
|
|
12
11
|
} from '../../store/use-opening-guides'
|
|
12
|
+
import { Html } from './localized-html'
|
|
13
13
|
import { formatMeasurement } from './measurement-pill'
|
|
14
14
|
|
|
15
15
|
const DIMENSION_COLOR = 0x81_8c_f8 // indigo — a neutral measurement
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import type { QuickMeasurementMetric, QuickMeasurementReport } from '@pascal-app/core'
|
|
4
|
+
import { formatAreaLabel, formatLinearMeasurement, formatVolumeLabel } from '@pascal-app/core'
|
|
4
5
|
import { Crosshair, MapPin } from 'lucide-react'
|
|
5
6
|
import { memo } from 'react'
|
|
6
|
-
import { formatAreaLabel, formatLinearMeasurement, formatVolumeLabel } from '../../lib/measurements'
|
|
7
7
|
|
|
8
8
|
function formatMetric(
|
|
9
9
|
metric: QuickMeasurementMetric,
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import { type AnyNode, registerNode, useInteractive } from '@pascal-app/core'
|
|
2
|
+
import { type AnyNode, type MechanismPart, registerNode, useInteractive } from '@pascal-app/core'
|
|
3
|
+
import type { MouseEvent, ReactElement } from 'react'
|
|
3
4
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
MechanismButton,
|
|
7
|
+
MechanismPartButton,
|
|
8
|
+
NodeMechanismActions,
|
|
9
|
+
soleSelectedNode,
|
|
10
|
+
} from './registry-action-contributions'
|
|
5
11
|
|
|
6
12
|
const switchMechanism = {
|
|
7
13
|
has: (node: AnyNode) => (node as unknown as { joints: number }).joints > 0,
|
|
@@ -23,6 +29,30 @@ function articulated(joints: number): AnyNode {
|
|
|
23
29
|
return { object: 'node', id: `${kind}_1`, type: kind, joints } as unknown as AnyNode
|
|
24
30
|
}
|
|
25
31
|
|
|
32
|
+
const shutters: MechanismPart = {
|
|
33
|
+
id: 'shutters',
|
|
34
|
+
label: 'Shutters',
|
|
35
|
+
icon: 'shutters',
|
|
36
|
+
verb: 'open',
|
|
37
|
+
has: () => true,
|
|
38
|
+
isOn: (node, state) => Boolean(state.mechanisms[`${node.id}:shutters` as never]),
|
|
39
|
+
set: (node, on) => useInteractive.getState().setMechanism(`${node.id}:shutters` as never, on),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function shuttered(): AnyNode {
|
|
43
|
+
const kind = `test:shuttered-${crypto.randomUUID()}`
|
|
44
|
+
registerNode({
|
|
45
|
+
kind,
|
|
46
|
+
schemaVersion: 1,
|
|
47
|
+
category: 'structure',
|
|
48
|
+
defaults: () => ({}),
|
|
49
|
+
capabilities: {
|
|
50
|
+
mechanism: { ...switchMechanism, verb: 'open', icon: 'window', parts: [shutters] },
|
|
51
|
+
},
|
|
52
|
+
} as never)
|
|
53
|
+
return { object: 'node', id: `${kind}_1`, type: kind, joints: 1 } as unknown as AnyNode
|
|
54
|
+
}
|
|
55
|
+
|
|
26
56
|
const byId = (...nodes: AnyNode[]) => Object.fromEntries(nodes.map((node) => [node.id, node]))
|
|
27
57
|
|
|
28
58
|
afterEach(() => {
|
|
@@ -33,17 +63,20 @@ afterEach(() => {
|
|
|
33
63
|
describe('mechanism action in the action menu', () => {
|
|
34
64
|
test('the only selected node gets Play when its kind declares a mechanism', () => {
|
|
35
65
|
const node = articulated(2)
|
|
36
|
-
expect(
|
|
66
|
+
expect(soleSelectedNode([node.id], byId(node))).toBe(node)
|
|
67
|
+
expect(renderToStaticMarkup(<NodeMechanismActions node={node} />)).toContain(
|
|
68
|
+
'aria-label="Play"',
|
|
69
|
+
)
|
|
37
70
|
})
|
|
38
71
|
|
|
39
72
|
test('no Play without moving parts, for a multi-selection, or without the capability', () => {
|
|
40
73
|
const still = articulated(0)
|
|
41
|
-
expect(
|
|
74
|
+
expect(renderToStaticMarkup(<NodeMechanismActions node={still} />)).toBe('')
|
|
42
75
|
|
|
43
76
|
const node = articulated(2)
|
|
44
77
|
const other = { object: 'node', id: 'other_1', type: 'other' } as unknown as AnyNode
|
|
45
|
-
expect(
|
|
46
|
-
expect(
|
|
78
|
+
expect(soleSelectedNode([node.id, other.id], byId(node, other))).toBeUndefined()
|
|
79
|
+
expect(renderToStaticMarkup(<NodeMechanismActions node={other} />)).toBe('')
|
|
47
80
|
})
|
|
48
81
|
|
|
49
82
|
test('the button reads Play, then Stop while the mechanism runs', () => {
|
|
@@ -61,3 +94,31 @@ describe('mechanism action in the action menu', () => {
|
|
|
61
94
|
expect(running).toContain('aria-pressed="true"')
|
|
62
95
|
})
|
|
63
96
|
})
|
|
97
|
+
|
|
98
|
+
describe('mechanism parts in the action menu', () => {
|
|
99
|
+
test('a window with shutters gets Open shutters beside Open', () => {
|
|
100
|
+
const markup = renderToStaticMarkup(<NodeMechanismActions node={shuttered()} />)
|
|
101
|
+
expect(markup.match(/aria-label="[^"]*"/g)).toEqual([
|
|
102
|
+
'aria-label="Open"',
|
|
103
|
+
'aria-label="Open shutters"',
|
|
104
|
+
])
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('the shutters button opens them alone, then reads Close shutters', () => {
|
|
108
|
+
const node = shuttered()
|
|
109
|
+
let button: ReactElement<{ onClick: (event: MouseEvent) => void }> | undefined
|
|
110
|
+
// Rendering the button as a hook call hands over the element it returns, onClick included.
|
|
111
|
+
function Probe() {
|
|
112
|
+
button = MechanismPartButton({ node, part: shutters, running: false }) as typeof button
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
renderToStaticMarkup(<Probe />)
|
|
116
|
+
button?.props.onClick({ stopPropagation: () => {} } as MouseEvent)
|
|
117
|
+
expect(shutters.isOn(node, useInteractive.getState())).toBe(true)
|
|
118
|
+
expect(switchMechanism.isOn(node, useInteractive.getState())).toBe(false)
|
|
119
|
+
|
|
120
|
+
const open = renderToStaticMarkup(<MechanismPartButton node={node} part={shutters} running />)
|
|
121
|
+
expect(open).toContain('aria-label="Close shutters"')
|
|
122
|
+
expect(open).toContain('aria-pressed="true"')
|
|
123
|
+
})
|
|
124
|
+
})
|
|
@@ -4,10 +4,14 @@ import {
|
|
|
4
4
|
type AnyNode,
|
|
5
5
|
type AnyNodeId,
|
|
6
6
|
type MechanismCapability,
|
|
7
|
+
type MechanismPart,
|
|
7
8
|
nodeMechanism,
|
|
9
|
+
nodeMechanismParts,
|
|
8
10
|
nodeRegistry,
|
|
9
11
|
toggleMechanism,
|
|
12
|
+
toggleMechanismPart,
|
|
10
13
|
useInteractive,
|
|
14
|
+
usePascalI18n,
|
|
11
15
|
useScene,
|
|
12
16
|
} from '@pascal-app/core'
|
|
13
17
|
import { useViewer } from '@pascal-app/viewer'
|
|
@@ -16,6 +20,7 @@ import { type ComponentType, lazy, Suspense } from 'react'
|
|
|
16
20
|
import { useShallow } from 'zustand/react/shallow'
|
|
17
21
|
import { getFloorplanNodeExtension } from '../../lib/floorplan/floorplan-extension'
|
|
18
22
|
import { ActionMenuButton } from './action-menu-button'
|
|
23
|
+
import { InspectorExtensionActions } from './inspector-extension-actions'
|
|
19
24
|
|
|
20
25
|
type Loader = () => Promise<{ default: ComponentType }>
|
|
21
26
|
const lazyCache = new WeakMap<Loader, ComponentType>()
|
|
@@ -30,13 +35,12 @@ function contribution(kind: string): ComponentType | null {
|
|
|
30
35
|
return component
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
/** The node the action menu's
|
|
34
|
-
export function
|
|
38
|
+
/** The node the action menu's mechanism buttons run: the only selected node. */
|
|
39
|
+
export function soleSelectedNode(
|
|
35
40
|
selectedIds: readonly string[],
|
|
36
41
|
nodes: Readonly<Record<string, AnyNode>>,
|
|
37
42
|
): AnyNode | undefined {
|
|
38
|
-
|
|
39
|
-
return nodeMechanism(node) ? node : undefined
|
|
43
|
+
return selectedIds.length === 1 ? nodes[selectedIds[0]!] : undefined
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
function MechanismGlyph({
|
|
@@ -61,6 +65,34 @@ function MechanismGlyph({
|
|
|
61
65
|
return <Glyph className="h-4 w-4" />
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
/** Two louvred leaves: shut across the window, or swung out to its sides. */
|
|
69
|
+
function ShuttersGlyph({ open }: { open: boolean }) {
|
|
70
|
+
return (
|
|
71
|
+
<svg
|
|
72
|
+
aria-hidden="true"
|
|
73
|
+
className="h-4 w-4"
|
|
74
|
+
fill="none"
|
|
75
|
+
stroke="currentColor"
|
|
76
|
+
strokeLinecap="round"
|
|
77
|
+
strokeLinejoin="round"
|
|
78
|
+
strokeWidth={2}
|
|
79
|
+
viewBox="0 0 24 24"
|
|
80
|
+
>
|
|
81
|
+
{open ? (
|
|
82
|
+
<>
|
|
83
|
+
<rect height="16" rx="1" width="8" x="8" y="4" />
|
|
84
|
+
<path d="M8 4 3 6v12l5 2M16 4l5 2v12l-5 2" />
|
|
85
|
+
</>
|
|
86
|
+
) : (
|
|
87
|
+
<>
|
|
88
|
+
<rect height="18" rx="1" width="16" x="4" y="3" />
|
|
89
|
+
<path d="M12 3v18M7 8h2M7 12h2M7 16h2M15 8h2M15 12h2M15 16h2" />
|
|
90
|
+
</>
|
|
91
|
+
)}
|
|
92
|
+
</svg>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
64
96
|
export function MechanismButton({
|
|
65
97
|
node,
|
|
66
98
|
mechanism,
|
|
@@ -70,7 +102,12 @@ export function MechanismButton({
|
|
|
70
102
|
mechanism: MechanismCapability
|
|
71
103
|
running: boolean
|
|
72
104
|
}) {
|
|
73
|
-
const
|
|
105
|
+
const i18n = usePascalI18n()
|
|
106
|
+
const label = i18n._({
|
|
107
|
+
id: 'editor.mechanismAction',
|
|
108
|
+
message: '{action, select, close {Close} open {Open} stop {Stop} other {Play}}',
|
|
109
|
+
values: { action: mechanism.icon ? (running ? 'close' : 'open') : running ? 'stop' : 'play' },
|
|
110
|
+
})
|
|
74
111
|
return (
|
|
75
112
|
<ActionMenuButton
|
|
76
113
|
keys={['E']}
|
|
@@ -86,21 +123,83 @@ export function MechanismButton({
|
|
|
86
123
|
)
|
|
87
124
|
}
|
|
88
125
|
|
|
89
|
-
/**
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
126
|
+
/** A second control beside the main one (a window's shutters), with its own running state. */
|
|
127
|
+
export function MechanismPartButton({
|
|
128
|
+
node,
|
|
129
|
+
part,
|
|
130
|
+
running,
|
|
131
|
+
}: {
|
|
132
|
+
node: AnyNode
|
|
133
|
+
part: MechanismPart
|
|
134
|
+
running: boolean
|
|
135
|
+
}) {
|
|
136
|
+
const i18n = usePascalI18n()
|
|
137
|
+
const label =
|
|
138
|
+
part.icon === 'shutters'
|
|
139
|
+
? i18n._({
|
|
140
|
+
id: 'editor.mechanismPartAction.shutters',
|
|
141
|
+
message: '{action, select, close {Close shutters} other {Open shutters}}',
|
|
142
|
+
values: { action: running ? 'close' : 'open' },
|
|
143
|
+
})
|
|
144
|
+
: i18n._({
|
|
145
|
+
id: 'editor.mechanismAction',
|
|
146
|
+
message: '{action, select, close {Close} open {Open} stop {Stop} other {Play}}',
|
|
147
|
+
values: {
|
|
148
|
+
action: part.verb === 'open' ? (running ? 'close' : 'open') : running ? 'stop' : 'play',
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
return (
|
|
152
|
+
<ActionMenuButton
|
|
153
|
+
label={label}
|
|
154
|
+
onClick={(event) => {
|
|
155
|
+
event.stopPropagation()
|
|
156
|
+
toggleMechanismPart(part, node)
|
|
157
|
+
}}
|
|
158
|
+
pressed={running}
|
|
159
|
+
>
|
|
160
|
+
{part.icon === 'shutters' ? (
|
|
161
|
+
<ShuttersGlyph open={!running} />
|
|
162
|
+
) : (
|
|
163
|
+
<MechanismGlyph icon={undefined} running={running} />
|
|
164
|
+
)}
|
|
165
|
+
</ActionMenuButton>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function MechanismPartAction({ node, part }: { node: AnyNode; part: MechanismPart }) {
|
|
170
|
+
const running = useInteractive((s) => part.isOn(node, s))
|
|
171
|
+
return <MechanismPartButton node={node} part={part} running={running} />
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Play/Stop for a kind declaring `capabilities.mechanism`, then one button per
|
|
176
|
+
* part the node has. Parts don't need the main mechanism: a fixed window can
|
|
177
|
+
* still have shutters.
|
|
178
|
+
*/
|
|
179
|
+
export function NodeMechanismActions({ node }: { node: AnyNode }) {
|
|
93
180
|
const mechanism = nodeMechanism(node)
|
|
94
|
-
const running = useInteractive((s) => (
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
|
|
181
|
+
const running = useInteractive((s) => (mechanism ? mechanism.isOn(node, s) : false))
|
|
182
|
+
return (
|
|
183
|
+
<>
|
|
184
|
+
{mechanism ? <MechanismButton mechanism={mechanism} node={node} running={running} /> : null}
|
|
185
|
+
{nodeMechanismParts(node).map((part) => (
|
|
186
|
+
<MechanismPartAction key={part.id} node={node} part={part} />
|
|
187
|
+
))}
|
|
188
|
+
</>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function MechanismActions() {
|
|
193
|
+
const selected = useViewer((s) => s.selection.selectedIds)
|
|
194
|
+
const node = useScene((s) => soleSelectedNode(selected, s.nodes))
|
|
195
|
+
return node ? <NodeMechanismActions node={node} /> : null
|
|
98
196
|
}
|
|
99
197
|
|
|
100
198
|
/**
|
|
101
199
|
* The buttons kinds add to the action menu for selections holding them
|
|
102
200
|
* (`extensions['pascal:editor/floorplan'].actionMenu.actions`), in 2D and 3D
|
|
103
|
-
* alike
|
|
201
|
+
* alike, then one per plugin inspector extension of the selected node. Each
|
|
202
|
+
* contribution reads the selection and decides its own visibility.
|
|
104
203
|
*/
|
|
105
204
|
export function RegistryActionContributions() {
|
|
106
205
|
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
@@ -118,7 +217,7 @@ export function RegistryActionContributions() {
|
|
|
118
217
|
)
|
|
119
218
|
return (
|
|
120
219
|
<>
|
|
121
|
-
<
|
|
220
|
+
<MechanismActions />
|
|
122
221
|
{kinds.map((kind) => {
|
|
123
222
|
const Contribution = contribution(kind)
|
|
124
223
|
return Contribution ? (
|
|
@@ -127,6 +226,7 @@ export function RegistryActionContributions() {
|
|
|
127
226
|
</Suspense>
|
|
128
227
|
) : null
|
|
129
228
|
})}
|
|
229
|
+
<InspectorExtensionActions />
|
|
130
230
|
</>
|
|
131
231
|
)
|
|
132
232
|
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type AnyNode,
|
|
5
|
+
type AnyNodeId,
|
|
6
|
+
buildRiserDiagram,
|
|
7
|
+
type PascalI18n,
|
|
8
|
+
type RiserMarker,
|
|
9
|
+
usePascalI18n,
|
|
10
|
+
useScene,
|
|
11
|
+
} from '@pascal-app/core'
|
|
4
12
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
13
|
import { X } from 'lucide-react'
|
|
6
14
|
import { useMemo } from 'react'
|
|
15
|
+
import { inspectorOptionDescriptor } from '../../i18n/node-messages'
|
|
7
16
|
import useEditor from '../../store/use-editor'
|
|
8
17
|
|
|
9
18
|
// Set per theme on the panel root; the light values are darker so the 9-unit
|
|
@@ -33,6 +42,7 @@ export function RiserDiagramPanel() {
|
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
function RiserDiagramContent() {
|
|
45
|
+
const i18n = usePascalI18n()
|
|
36
46
|
const setRiserOpen = useEditor((s) => s.setRiserOpen)
|
|
37
47
|
const nodes = useScene((s) => s.nodes)
|
|
38
48
|
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
@@ -47,15 +57,30 @@ function RiserDiagramContent() {
|
|
|
47
57
|
const ty = diagram ? -diagram.bounds.minY * SCALE + PADDING : 0
|
|
48
58
|
|
|
49
59
|
return (
|
|
50
|
-
<div
|
|
60
|
+
<div
|
|
61
|
+
data-surface="riser-diagram"
|
|
62
|
+
className="pointer-events-auto absolute top-4 right-4 z-30 flex max-h-[80vh] w-[26rem] flex-col overflow-hidden rounded-2xl border border-border/40 bg-background/95 text-foreground shadow-lg backdrop-blur-xl [--riser-vent:#9333ea] [--riser-waste:#0369a1] dark:[--riser-vent:#a855f7] dark:[--riser-waste:#0ea5e9]"
|
|
63
|
+
>
|
|
51
64
|
<div className="flex items-center justify-between border-border/40 border-b px-4 py-2.5">
|
|
52
65
|
<div className="flex flex-col">
|
|
53
|
-
<span className="font-medium text-sm">
|
|
54
|
-
|
|
66
|
+
<span className="font-medium text-sm">
|
|
67
|
+
{i18n._({ id: 'editor.riserDiagramPanel.riserDiagram', message: 'Riser Diagram' })}
|
|
68
|
+
</span>
|
|
69
|
+
<span className="text-muted-foreground text-xs">
|
|
70
|
+
{i18n._({
|
|
71
|
+
id: 'editor.riserDiagramPanel.dwvPlumbingIsometric',
|
|
72
|
+
message: 'DWV plumbing isometric',
|
|
73
|
+
})}
|
|
74
|
+
</span>
|
|
55
75
|
</div>
|
|
56
76
|
<button
|
|
77
|
+
aria-label={i18n._({
|
|
78
|
+
id: 'editor.riserDiagramPanel.close',
|
|
79
|
+
message: 'Close riser diagram',
|
|
80
|
+
})}
|
|
57
81
|
className="flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-black/6 dark:hover:bg-white/10"
|
|
58
82
|
onClick={() => setRiserOpen(false)}
|
|
83
|
+
title={i18n._({ id: 'editor.riserDiagramPanel.close', message: 'Close riser diagram' })}
|
|
59
84
|
>
|
|
60
85
|
<X className="h-4 w-4 text-muted-foreground" />
|
|
61
86
|
</button>
|
|
@@ -63,11 +88,12 @@ function RiserDiagramContent() {
|
|
|
63
88
|
|
|
64
89
|
<div className="flex items-center gap-3 border-border/40 border-b px-4 py-2 text-xs">
|
|
65
90
|
<span className="flex items-center gap-1.5">
|
|
66
|
-
<span className="h-0.5 w-4" style={{ background: WASTE_COLOR }} />
|
|
91
|
+
<span className="h-0.5 w-4" style={{ background: WASTE_COLOR }} />{' '}
|
|
92
|
+
{i18n._({ id: 'editor.riserDiagramPanel.waste', message: 'Waste' })}
|
|
67
93
|
</span>
|
|
68
94
|
<span className="flex items-center gap-1.5">
|
|
69
95
|
<span className="h-0 w-4 border-t-2 border-dashed" style={{ borderColor: VENT_COLOR }} />{' '}
|
|
70
|
-
Vent
|
|
96
|
+
{i18n._({ id: 'editor.riserDiagramPanel.vent', message: 'Vent' })}
|
|
71
97
|
</span>
|
|
72
98
|
</div>
|
|
73
99
|
|
|
@@ -76,7 +102,10 @@ function RiserDiagramContent() {
|
|
|
76
102
|
<svg
|
|
77
103
|
height={Math.max(height, 120)}
|
|
78
104
|
role="img"
|
|
79
|
-
aria-label=
|
|
105
|
+
aria-label={i18n._({
|
|
106
|
+
id: 'editor.riserDiagramPanel.dwvRiserDiagram',
|
|
107
|
+
message: 'DWV riser diagram',
|
|
108
|
+
})}
|
|
80
109
|
viewBox={`0 0 ${Math.max(width, 200)} ${Math.max(height, 120)}`}
|
|
81
110
|
width="100%"
|
|
82
111
|
>
|
|
@@ -127,7 +156,7 @@ function RiserDiagramContent() {
|
|
|
127
156
|
<circle fill={MARKER_COLOR} r={3} stroke={MARKER_COLOR} strokeWidth={1.5} />
|
|
128
157
|
)}
|
|
129
158
|
<text fill={MARKER_COLOR} fontSize={9} x={8} y={3}>
|
|
130
|
-
{marker
|
|
159
|
+
{markerLabel(marker, nodes, i18n)}
|
|
131
160
|
</text>
|
|
132
161
|
</g>
|
|
133
162
|
))}
|
|
@@ -135,10 +164,31 @@ function RiserDiagramContent() {
|
|
|
135
164
|
</svg>
|
|
136
165
|
) : (
|
|
137
166
|
<div className="flex h-32 items-center justify-center px-6 text-center text-muted-foreground text-sm">
|
|
138
|
-
|
|
167
|
+
{i18n._({
|
|
168
|
+
id: 'editor.riserDiagramPanel.noDrainWasteOrVentPipes',
|
|
169
|
+
message:
|
|
170
|
+
'No drain, waste, or vent pipes yet. Draw plumbing to see the riser diagram.',
|
|
171
|
+
})}
|
|
139
172
|
</div>
|
|
140
173
|
)}
|
|
141
174
|
</div>
|
|
142
175
|
</div>
|
|
143
176
|
)
|
|
144
177
|
}
|
|
178
|
+
|
|
179
|
+
/** Core's marker labels are English ("2\" P-trap"); the panel words them in the UI language. */
|
|
180
|
+
function markerLabel(marker: RiserMarker, nodes: Record<string, AnyNode>, i18n: PascalI18n) {
|
|
181
|
+
const node = nodes[marker.nodeId]
|
|
182
|
+
if (marker.kind === 'fitting' && node?.type === 'pipe-fitting')
|
|
183
|
+
return i18n._(inspectorOptionDescriptor(node.fittingType))
|
|
184
|
+
if (!(node && 'diameter' in node && typeof node.diameter === 'number')) return marker.label
|
|
185
|
+
const values = { diameter: String(node.diameter) }
|
|
186
|
+
return marker.kind === 'trap'
|
|
187
|
+
? i18n._({ id: 'editor.riserDiagramPanel.trap', message: '{diameter}" P-trap', values })
|
|
188
|
+
: i18n._({
|
|
189
|
+
id: 'editor.riserDiagramPanel.ventTermination',
|
|
190
|
+
message: '{diameter}" VTR',
|
|
191
|
+
comment: 'Vent through roof: where a plumbing vent stack exits the roof.',
|
|
192
|
+
values,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
DEFAULT_LEVEL_HEIGHT,
|
|
7
7
|
deleteZone,
|
|
8
8
|
emitter,
|
|
9
|
+
formatLinearMeasurement,
|
|
9
10
|
type Point,
|
|
11
|
+
roomFloorElevation,
|
|
10
12
|
type StructureNodes,
|
|
11
13
|
sceneRegistry,
|
|
12
14
|
useScene,
|
|
13
15
|
} from '@pascal-app/core'
|
|
14
16
|
import { getSceneTheme, setSurfaceRaycastLayers, useViewer } from '@pascal-app/viewer'
|
|
15
|
-
import { Html } from '@react-three/drei'
|
|
16
17
|
import { useFrame, useThree } from '@react-three/fiber'
|
|
17
18
|
import { SquareSplitHorizontal } from 'lucide-react'
|
|
18
19
|
import Image from 'next/image'
|
|
@@ -36,7 +37,6 @@ import { getRoomSelectionIndex } from '../../hooks/use-selected-room'
|
|
|
36
37
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
37
38
|
import { getFloatingMenuScale } from '../../lib/floating-menu-scale'
|
|
38
39
|
import { clientToPlan } from '../../lib/floorplan/plan-coords'
|
|
39
|
-
import { formatLinearMeasurement } from '../../lib/measurements'
|
|
40
40
|
import { addMezzanineStairs } from '../../lib/mezzanine-stairs'
|
|
41
41
|
import {
|
|
42
42
|
cancelRoomDivide,
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
roomDivideContains,
|
|
48
48
|
startRoomDivide,
|
|
49
49
|
} from '../../lib/room-divide-session'
|
|
50
|
-
import {
|
|
50
|
+
import { useRoomHandleDrag } from '../../lib/room-handle-drag'
|
|
51
51
|
import type { RoomSelectionRecord } from '../../lib/room-selection'
|
|
52
52
|
import { requestRoomDeletion } from '../../lib/room-structure-commands'
|
|
53
53
|
import {
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
} from '../tools/shared/draft-measurement-label'
|
|
70
70
|
import { ActionMenuButton } from './action-menu-button'
|
|
71
71
|
import { getMenuYOffset } from './floating-action-menu'
|
|
72
|
+
import { Html } from './localized-html'
|
|
72
73
|
import { NodeActionMenu } from './node-action-menu'
|
|
73
74
|
import { RoomFloorHighlight3D } from './room-floor-highlight'
|
|
74
75
|
import { RoomHandleDragPreview3D, RoomHandles3D } from './room-handles'
|