@pascal-app/editor 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +18 -15
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +110 -42
- package/src/components/editor/duplicate-node.ts +2 -1
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +77 -68
- package/src/components/editor/fit-scene-framing.test.ts +318 -0
- package/src/components/editor/fit-scene-framing.ts +142 -0
- package/src/components/editor/floating-action-menu.tsx +31 -11
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floor-openings-overlay.tsx +1 -1
- package/src/components/editor/floor-region-controls.tsx +2 -2
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +458 -140
- package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
- package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
- package/src/components/editor/footprint-height-handle.tsx +2 -2
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +23 -1
- package/src/components/editor/index.tsx +150 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/measurement-pill.tsx +1 -1
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +2 -3
- package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
- package/src/components/editor/open-wall-ends-hint.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/quick-measurement-card.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +4 -3
- package/src/components/editor/room-handles-2d.tsx +158 -7
- package/src/components/editor/room-handles.tsx +244 -54
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +33 -17
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +7 -3
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +31 -15
- package/src/components/editor/wall-move-side-handles.tsx +15 -7
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-region-handles.tsx +2 -2
- package/src/components/editor/wall-snap-beacon-layer.tsx +134 -33
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +32 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +33 -24
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +12 -5
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +29 -5
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +34 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +211 -39
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
- package/src/components/systems/roof/roof-edit-system.tsx +30 -19
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +200 -58
- package/src/components/tools/item/move-tool.tsx +1 -6
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +31 -35
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +2 -6
- package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/shared/segment-angle.ts +0 -4
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/tool-manager.tsx +1 -24
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
- package/src/components/tools/wall/wall-typed-entry.ts +70 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +47 -19
- package/src/components/ui/controls/tool-options-panel.tsx +74 -44
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +106 -3
- package/src/components/ui/helpers/helper-manager.tsx +136 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +114 -39
- package/src/components/ui/panels/floor-opening-panel.tsx +74 -26
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +34 -28
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +11 -10
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +76 -36
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +9 -5
- package/src/components/ui/panels/panel-wrapper.tsx +63 -127
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +465 -66
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +22 -13
- package/src/components/ui/panels/room-panel.tsx +207 -100
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/right-stack.test.ts +13 -6
- package/src/components/ui/right-stack.tsx +21 -10
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +118 -72
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +19 -9
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +62 -5
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15849 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +39331 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +39331 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +39331 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +39331 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +224 -45
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +101 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/elevation-guides.ts +21 -6
- package/src/lib/elevator-support.ts +1 -72
- package/src/lib/floor-footprints.test.ts +3 -2
- package/src/lib/floor-footprints.ts +1 -9
- package/src/lib/floor-foundation-support.test.ts +15 -6
- package/src/lib/floor-opening-draft.ts +1 -1
- package/src/lib/floor-region-session.ts +1 -1
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/open-wall-ends.ts +2 -1
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/gesture-lifecycle.ts +3 -1
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/interaction/scope.ts +1 -0
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/level-selection.test.ts +18 -2
- package/src/lib/level-selection.ts +31 -8
- package/src/lib/linear-display.ts +1 -1
- package/src/lib/live-gesture-round-trip.test.ts +1 -1
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-label.test.ts +1 -1
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +24 -6
- package/src/lib/measurements.ts +1 -203
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/node-action-movement.test.tsx +115 -0
- package/src/lib/node-action-movement.ts +2 -0
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/placement-metadata.ts +4 -0
- package/src/lib/point-ask/describe-target.ts +6 -8
- package/src/lib/point-ask/region.ts +1 -1
- package/src/lib/print-shell-compiler.ts +12 -1
- package/src/lib/print-wall-solids.test.ts +11 -0
- package/src/lib/print-wall-solids.ts +9 -0
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-assembly-overlay.ts +1 -1
- package/src/lib/room-built-on.test.ts +28 -17
- package/src/lib/room-built-on.ts +21 -17
- package/src/lib/room-construction-commands.ts +3 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +141 -1
- package/src/lib/room-handle-drag.ts +554 -58
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +38 -1
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-bounds.test.ts +144 -0
- package/src/lib/scene-bounds.ts +79 -15
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +11 -5
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/snapping-mode.test.ts +23 -1
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +21 -6
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +17 -6
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +44 -0
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +53 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-placement-typing.test.ts +165 -0
- package/src/store/use-placement-typing.ts +186 -0
- package/src/store/use-wall-snap-indicator.ts +15 -2
- package/src/components/tools/elevator/elevator-defaults.ts +0 -8
- package/src/components/tools/elevator/elevator-tool.tsx +0 -372
- package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
- package/src/lib/door-interaction.ts +0 -102
- package/src/lib/measurement-label.ts +0 -68
- package/src/lib/window-interaction.ts +0 -100
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import { type AnyNodeId, useLiveNodeOverrides, useScene } from '@pascal-app/core'
|
|
2
|
+
import { type AnyNodeId, useLiveNodeOverrides, usePascalI18n, useScene } from '@pascal-app/core'
|
|
3
3
|
import {
|
|
4
4
|
ProceduralItemNode,
|
|
5
5
|
snapParameters,
|
|
@@ -22,6 +22,7 @@ export function ProceduralInspector({
|
|
|
22
22
|
partId: string | null
|
|
23
23
|
onPartChange: (id: string | null) => void
|
|
24
24
|
}) {
|
|
25
|
+
const i18n = usePascalI18n()
|
|
25
26
|
const committed = useScene((s) => s.nodes[nodeId as AnyNodeId]) as unknown as
|
|
26
27
|
| ProceduralItemNode
|
|
27
28
|
| undefined
|
|
@@ -42,14 +43,24 @@ export function ProceduralInspector({
|
|
|
42
43
|
const parameters = commit ? snapParameters(node.recipe, values) : values
|
|
43
44
|
const parsed = ProceduralItemNode.safeParse({ ...node, parameters })
|
|
44
45
|
if (!parsed.success) {
|
|
45
|
-
setError(
|
|
46
|
+
setError(
|
|
47
|
+
parsed.error.issues[0]?.message ??
|
|
48
|
+
i18n._({ id: 'editor.proceduralItems.inspector.invalidEdit', message: 'Invalid edit' }),
|
|
49
|
+
)
|
|
46
50
|
if (commit) useLiveNodeOverrides.getState().clear(nodeId as AnyNodeId)
|
|
47
51
|
return
|
|
48
52
|
}
|
|
49
53
|
try {
|
|
50
54
|
validateProceduralRelations({ ...node, parameters }, useScene.getState().nodes)
|
|
51
55
|
} catch (e) {
|
|
52
|
-
setError(
|
|
56
|
+
setError(
|
|
57
|
+
e instanceof Error
|
|
58
|
+
? e.message
|
|
59
|
+
: i18n._({
|
|
60
|
+
id: 'editor.proceduralItems.inspector.invalidAttachment',
|
|
61
|
+
message: 'Invalid attachment',
|
|
62
|
+
}),
|
|
63
|
+
)
|
|
53
64
|
if (commit) useLiveNodeOverrides.getState().clear(nodeId as AnyNodeId)
|
|
54
65
|
return
|
|
55
66
|
}
|
|
@@ -64,7 +75,12 @@ export function ProceduralInspector({
|
|
|
64
75
|
<div className="space-y-5">
|
|
65
76
|
<div className="flex items-center justify-between">
|
|
66
77
|
<h2 className="font-semibold text-lg">
|
|
67
|
-
{partId
|
|
78
|
+
{partId
|
|
79
|
+
? node.recipe.parts.find((p) => p.id === partId)?.label
|
|
80
|
+
: i18n._({
|
|
81
|
+
id: 'editor.proceduralItems.inspector.adjustDesign',
|
|
82
|
+
message: 'Adjust design',
|
|
83
|
+
})}
|
|
68
84
|
</h2>
|
|
69
85
|
{partId && (
|
|
70
86
|
<button
|
|
@@ -72,7 +88,7 @@ export function ProceduralInspector({
|
|
|
72
88
|
onClick={() => onPartChange(null)}
|
|
73
89
|
type="button"
|
|
74
90
|
>
|
|
75
|
-
Back
|
|
91
|
+
{i18n._({ id: 'editor.proceduralItems.inspector.back', message: 'Back' })}
|
|
76
92
|
</button>
|
|
77
93
|
)}
|
|
78
94
|
</div>
|
|
@@ -128,11 +144,21 @@ export function ProceduralInspector({
|
|
|
128
144
|
useScene.getState().updateNode(nodeId as AnyNodeId, { parameters } as never)
|
|
129
145
|
setError('')
|
|
130
146
|
} catch (e) {
|
|
131
|
-
setError(
|
|
147
|
+
setError(
|
|
148
|
+
e instanceof Error
|
|
149
|
+
? e.message
|
|
150
|
+
: i18n._({
|
|
151
|
+
id: 'editor.proceduralItems.inspector.invalidReset',
|
|
152
|
+
message: 'Invalid reset',
|
|
153
|
+
}),
|
|
154
|
+
)
|
|
132
155
|
}
|
|
133
156
|
}}
|
|
134
157
|
>
|
|
135
|
-
|
|
158
|
+
{i18n._({
|
|
159
|
+
id: 'editor.proceduralItems.inspector.resetThisPart',
|
|
160
|
+
message: 'Reset this part',
|
|
161
|
+
})}
|
|
136
162
|
</button>
|
|
137
163
|
)}
|
|
138
164
|
{error && (
|
|
@@ -142,7 +168,9 @@ export function ProceduralInspector({
|
|
|
142
168
|
)}
|
|
143
169
|
{!partId && node.recipe.parameters.some((p) => p.part) && (
|
|
144
170
|
<div>
|
|
145
|
-
<h3 className="mb-2 font-medium text-sm">
|
|
171
|
+
<h3 className="mb-2 font-medium text-sm">
|
|
172
|
+
{i18n._({ id: 'editor.proceduralItems.inspector.editParts', message: 'Edit parts' })}
|
|
173
|
+
</h3>
|
|
146
174
|
<div className="flex flex-wrap gap-2">
|
|
147
175
|
{node.recipe.parts
|
|
148
176
|
.filter((part) => node.recipe.parameters.some((p) => p.part === part.id))
|
|
@@ -188,13 +216,21 @@ function ProceduralPanel({ node }: { node: ProceduralItemNode }) {
|
|
|
188
216
|
}
|
|
189
217
|
|
|
190
218
|
function ProceduralPlacementControls({ node }: { node: ProceduralItemNode }) {
|
|
219
|
+
const i18n = usePascalI18n()
|
|
191
220
|
const [error, setError] = useState('')
|
|
192
221
|
const update = (patch: Partial<ProceduralItemNode>) => {
|
|
193
222
|
try {
|
|
194
223
|
useScene.getState().updateNode(node.id as AnyNodeId, patch as never)
|
|
195
224
|
setError('')
|
|
196
225
|
} catch (e) {
|
|
197
|
-
setError(
|
|
226
|
+
setError(
|
|
227
|
+
e instanceof Error
|
|
228
|
+
? e.message
|
|
229
|
+
: i18n._({
|
|
230
|
+
id: 'editor.proceduralItems.inspector.invalidPlacement',
|
|
231
|
+
message: 'Invalid placement',
|
|
232
|
+
}),
|
|
233
|
+
)
|
|
198
234
|
}
|
|
199
235
|
}
|
|
200
236
|
const changePosition = (index: number, value: number, commit: boolean) => {
|
|
@@ -213,18 +249,29 @@ function ProceduralPlacementControls({ node }: { node: ProceduralItemNode }) {
|
|
|
213
249
|
setError('')
|
|
214
250
|
} catch (e) {
|
|
215
251
|
if (commit) useLiveNodeOverrides.getState().clear(node.id as AnyNodeId)
|
|
216
|
-
setError(
|
|
252
|
+
setError(
|
|
253
|
+
e instanceof Error
|
|
254
|
+
? e.message
|
|
255
|
+
: i18n._({
|
|
256
|
+
id: 'editor.proceduralItems.inspector.invalidPosition',
|
|
257
|
+
message: 'Invalid position',
|
|
258
|
+
}),
|
|
259
|
+
)
|
|
217
260
|
}
|
|
218
261
|
}
|
|
219
262
|
return (
|
|
220
|
-
<PanelSection
|
|
263
|
+
<PanelSection
|
|
264
|
+
title={i18n._({ id: 'editor.proceduralItems.inspector.position', message: 'Position' })}
|
|
265
|
+
>
|
|
221
266
|
{(['X', 'Y', 'Z'] as const).map((axis, index) => (
|
|
222
267
|
<SliderControl
|
|
223
268
|
key={axis}
|
|
224
269
|
label={
|
|
225
270
|
<>
|
|
226
271
|
{axis}
|
|
227
|
-
<sub className="ml-[1px] text-[11px] opacity-70">
|
|
272
|
+
<sub className="ml-[1px] text-[11px] opacity-70">
|
|
273
|
+
{i18n._({ id: 'editor.proceduralItems.inspector.pos', message: 'pos' })}
|
|
274
|
+
</sub>
|
|
228
275
|
</>
|
|
229
276
|
}
|
|
230
277
|
value={node.position[index]!}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type AttributesBackend = {
|
|
30
30
|
createAttribute(): void
|
|
31
31
|
updateAttribute(attribute: BufferAttribute): void
|
|
32
|
+
destroyAttribute(attribute: BufferAttribute): void
|
|
32
33
|
}
|
|
33
34
|
const Attributes = UntypedAttributes as new (
|
|
34
35
|
backend: AttributesBackend,
|
|
@@ -371,6 +372,7 @@ test('WebGPU attribute updates skip resting frames and upload only written slots
|
|
|
371
372
|
const attributes = new Attributes(
|
|
372
373
|
{
|
|
373
374
|
createAttribute() {},
|
|
375
|
+
destroyAttribute() {},
|
|
374
376
|
updateAttribute(attribute: BufferAttribute) {
|
|
375
377
|
uploads.push({ attribute, ranges: attribute.updateRanges.map((range) => ({ ...range })) })
|
|
376
378
|
attribute.clearUpdateRanges()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
import { BuildingNode, CeilingNode, LevelNode, useScene } from '@pascal-app/core'
|
|
3
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
4
|
+
import { act, createRoot } from '@react-three/fiber'
|
|
5
|
+
import { createElement, Fragment } from 'react'
|
|
6
|
+
import { OrthographicCamera, type WebGLRenderer } from 'three'
|
|
7
|
+
import useEditor, { normalizePersistedEditorLayoutState } from '../../../store/use-editor'
|
|
8
|
+
import useInteractionScope from '../../../store/use-interaction-scope'
|
|
9
|
+
import { CeilingSystem, CeilingTopsSync } from './ceiling-system'
|
|
10
|
+
|
|
11
|
+
test('ceilings start on for the roof level and off for the floor plans, each kept as set', () => {
|
|
12
|
+
const fresh = normalizePersistedEditorLayoutState(undefined)
|
|
13
|
+
expect(fresh.showRoofCeilings).toBe(true)
|
|
14
|
+
expect(fresh.showCeilings).toBe(false)
|
|
15
|
+
const turned = normalizePersistedEditorLayoutState({
|
|
16
|
+
showRoofCeilings: false,
|
|
17
|
+
showCeilings: true,
|
|
18
|
+
})
|
|
19
|
+
expect(turned.showRoofCeilings).toBe(false)
|
|
20
|
+
expect(turned.showCeilings).toBe(true)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('the level’s ceilings setting draws the ceiling tops, stepping aside to place on a ceiling', async () => {
|
|
24
|
+
const previous = {
|
|
25
|
+
scene: useScene.getState(),
|
|
26
|
+
viewer: useViewer.getState(),
|
|
27
|
+
editor: useEditor.getState(),
|
|
28
|
+
scope: useInteractionScope.getState(),
|
|
29
|
+
}
|
|
30
|
+
const actGlobal = globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
|
31
|
+
const previousAct = actGlobal.IS_REACT_ACT_ENVIRONMENT
|
|
32
|
+
actGlobal.IS_REACT_ACT_ENVIRONMENT = true
|
|
33
|
+
const makeCanvas = () =>
|
|
34
|
+
Object.assign(new EventTarget(), {
|
|
35
|
+
getBoundingClientRect: () => ({ left: 0, top: 0, width: 100, height: 100 }),
|
|
36
|
+
}) as unknown as HTMLCanvasElement
|
|
37
|
+
const canvas = makeCanvas()
|
|
38
|
+
const previewCanvas = makeCanvas()
|
|
39
|
+
const root = createRoot(canvas)
|
|
40
|
+
const previewRoot = createRoot(previewCanvas)
|
|
41
|
+
const building = BuildingNode.parse({})
|
|
42
|
+
const storey = LevelNode.parse({ parentId: building.id, level: 0, height: 3 })
|
|
43
|
+
const roof = LevelNode.parse({
|
|
44
|
+
parentId: building.id,
|
|
45
|
+
level: 1,
|
|
46
|
+
height: 3,
|
|
47
|
+
metadata: { role: 'roof' },
|
|
48
|
+
})
|
|
49
|
+
building.children = [storey.id, roof.id]
|
|
50
|
+
const ceiling = CeilingNode.parse({
|
|
51
|
+
parentId: storey.id,
|
|
52
|
+
height: 2.7,
|
|
53
|
+
polygon: [
|
|
54
|
+
[0, 0],
|
|
55
|
+
[4, 0],
|
|
56
|
+
[4, 4],
|
|
57
|
+
[0, 4],
|
|
58
|
+
],
|
|
59
|
+
})
|
|
60
|
+
const select = (levelId: typeof storey.id) =>
|
|
61
|
+
act(async () =>
|
|
62
|
+
useViewer.setState({
|
|
63
|
+
selection: { buildingId: building.id, levelId, zoneId: null, selectedIds: [] },
|
|
64
|
+
}),
|
|
65
|
+
)
|
|
66
|
+
const tops = () => useViewer.getState().showCeilingTops
|
|
67
|
+
try {
|
|
68
|
+
useScene.setState({
|
|
69
|
+
nodes: {
|
|
70
|
+
[building.id]: building,
|
|
71
|
+
[storey.id]: storey,
|
|
72
|
+
[roof.id]: roof,
|
|
73
|
+
[ceiling.id]: ceiling,
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
useEditor.setState({
|
|
77
|
+
...normalizePersistedEditorLayoutState(undefined),
|
|
78
|
+
tool: null,
|
|
79
|
+
selectedItem: null,
|
|
80
|
+
isPreviewMode: false,
|
|
81
|
+
isCaptureMode: false,
|
|
82
|
+
})
|
|
83
|
+
useInteractionScope.setState({ scope: { kind: 'idle' } })
|
|
84
|
+
useViewer.setState({ showCeilingTops: false, hoveredId: null })
|
|
85
|
+
await select(roof.id)
|
|
86
|
+
const configure = (target: typeof root, domElement: HTMLCanvasElement) =>
|
|
87
|
+
target.configure({
|
|
88
|
+
gl: {
|
|
89
|
+
domElement,
|
|
90
|
+
render() {},
|
|
91
|
+
setSize() {},
|
|
92
|
+
setPixelRatio() {},
|
|
93
|
+
} as unknown as WebGLRenderer,
|
|
94
|
+
camera: Object.assign(new OrthographicCamera(), { manual: true }),
|
|
95
|
+
frameloop: 'never',
|
|
96
|
+
dpr: 1,
|
|
97
|
+
size: { width: 100, height: 100, top: 0, left: 0 },
|
|
98
|
+
})
|
|
99
|
+
await configure(root, canvas)
|
|
100
|
+
await act(async () =>
|
|
101
|
+
root.render(
|
|
102
|
+
createElement(Fragment, null, createElement(CeilingTopsSync), createElement(CeilingSystem)),
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
expect(tops()).toBe(true)
|
|
106
|
+
|
|
107
|
+
// A second viewer's ceiling system (the preview's) going away leaves the tops as set.
|
|
108
|
+
await configure(previewRoot, previewCanvas)
|
|
109
|
+
await act(async () => previewRoot.render(createElement(CeilingSystem)))
|
|
110
|
+
await act(async () => previewRoot.unmount())
|
|
111
|
+
expect(tops()).toBe(true)
|
|
112
|
+
|
|
113
|
+
await select(storey.id)
|
|
114
|
+
expect(tops()).toBe(false)
|
|
115
|
+
await act(async () => useEditor.getState().setShowCeilings(true))
|
|
116
|
+
expect(tops()).toBe(true)
|
|
117
|
+
await act(async () => useEditor.setState({ tool: 'ceiling' }))
|
|
118
|
+
expect(tops()).toBe(false)
|
|
119
|
+
await act(async () => useEditor.setState({ tool: null, isPreviewMode: true }))
|
|
120
|
+
expect(tops()).toBe(false)
|
|
121
|
+
await act(async () => useEditor.setState({ isPreviewMode: false }))
|
|
122
|
+
expect(tops()).toBe(true)
|
|
123
|
+
|
|
124
|
+
// The roof level keeps its own setting.
|
|
125
|
+
await select(roof.id)
|
|
126
|
+
await act(async () => useEditor.getState().setShowRoofCeilings(false))
|
|
127
|
+
expect(tops()).toBe(false)
|
|
128
|
+
expect(useEditor.getState().showCeilings).toBe(true)
|
|
129
|
+
|
|
130
|
+
await act(async () => useEditor.getState().setShowRoofCeilings(true))
|
|
131
|
+
expect(tops()).toBe(true)
|
|
132
|
+
await act(async () => root.unmount())
|
|
133
|
+
expect(tops()).toBe(false)
|
|
134
|
+
} finally {
|
|
135
|
+
useScene.setState(previous.scene, true)
|
|
136
|
+
useViewer.setState(previous.viewer, true)
|
|
137
|
+
useEditor.setState(previous.editor, true)
|
|
138
|
+
useInteractionScope.setState(previous.scope, true)
|
|
139
|
+
actGlobal.IS_REACT_ACT_ENVIRONMENT = previousAct
|
|
140
|
+
}
|
|
141
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyNodeId, sceneRegistry, useScene } from '@pascal-app/core'
|
|
1
|
+
import { type AnyNodeId, sceneRegistry, servesAsRoofLevel, useScene } from '@pascal-app/core'
|
|
2
2
|
import { isProceduralItem } from '@pascal-app/core/procedural-items'
|
|
3
3
|
import { useViewer } from '@pascal-app/viewer'
|
|
4
4
|
import { useEffect } from 'react'
|
|
@@ -74,10 +74,59 @@ function setCeilingGridHighlighted(ceilingGrid: Mesh, highlighted: boolean) {
|
|
|
74
74
|
delete userData[CEILING_GRID_HIGHLIGHT_MATERIAL_KEY]
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The "Ceilings" setting that applies on `levelId`: the roof level's, on until turned off; the
|
|
79
|
+
* floor plans', off until turned on. On, ceilings read as solid from above too.
|
|
80
|
+
*/
|
|
81
|
+
export function useCeilingsSetting(levelId: string | null | undefined): {
|
|
82
|
+
onRoofLevel: boolean
|
|
83
|
+
shown: boolean
|
|
84
|
+
setShown: (show: boolean) => void
|
|
85
|
+
} {
|
|
86
|
+
const onRoofLevel = useScene((state) => {
|
|
87
|
+
const level = levelId ? state.nodes[levelId as AnyNodeId] : undefined
|
|
88
|
+
return level?.type === 'level' && servesAsRoofLevel(state.nodes, level)
|
|
89
|
+
})
|
|
90
|
+
const shown = useEditor((state) => (onRoofLevel ? state.showRoofCeilings : state.showCeilings))
|
|
91
|
+
const setRoof = useEditor((state) => state.setShowRoofCeilings)
|
|
92
|
+
const setFloor = useEditor((state) => state.setShowCeilings)
|
|
93
|
+
return { onRoofLevel, shown, setShown: onRoofLevel ? setRoof : setFloor }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function useIsCeilingToolActive(): boolean {
|
|
78
97
|
const tool = useEditor((state) => state.tool)
|
|
79
98
|
const selectedItem = useEditor((state) => state.selectedItem)
|
|
80
99
|
const movingNode = useMovingNode()
|
|
100
|
+
return (
|
|
101
|
+
tool === 'ceiling' ||
|
|
102
|
+
selectedItem?.attachTo === 'ceiling' ||
|
|
103
|
+
(movingNode?.type === 'item' && movingNode?.asset?.attachTo === 'ceiling') ||
|
|
104
|
+
(isProceduralItem(movingNode) && movingNode.recipe.mounting?.attachTo === 'ceiling')
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Whether the viewer draws ceiling tops (`showCeilingTops`), from the level's Ceilings setting.
|
|
110
|
+
* Mounted once by the editor, outside the canvases: the editor and preview viewers each mount a
|
|
111
|
+
* `CeilingSystem`, and two writers each turning the tops off on unmount raced.
|
|
112
|
+
*/
|
|
113
|
+
export const CeilingTopsSync = () => {
|
|
114
|
+
const activeLevelId = useViewer((state) => state.selection.levelId)
|
|
115
|
+
const ceilingsShown = useCeilingsSetting(activeLevelId).shown
|
|
116
|
+
const isPreviewMode = useEditor((state) => state.isPreviewMode)
|
|
117
|
+
const isCaptureMode = useEditor((state) => state.isCaptureMode)
|
|
118
|
+
const isCeilingToolActive = useIsCeilingToolActive()
|
|
119
|
+
// Placing on a ceiling looks through it from above, so the tops step aside; preview and
|
|
120
|
+
// captures keep the open look.
|
|
121
|
+
const showTops = ceilingsShown && !isCeilingToolActive && !isPreviewMode && !isCaptureMode
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
useViewer.getState().setShowCeilingTops(showTops)
|
|
124
|
+
return () => useViewer.getState().setShowCeilingTops(false)
|
|
125
|
+
}, [showTops])
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const CeilingSystem = () => {
|
|
81
130
|
const selectedIds = useViewer((state) => state.selection.selectedIds)
|
|
82
131
|
const activeLevelId = useViewer((state) => state.selection.levelId)
|
|
83
132
|
const hoveredId = useViewer((state) => state.hoveredId)
|
|
@@ -87,6 +136,7 @@ export const CeilingSystem = () => {
|
|
|
87
136
|
// reveal is gated here, on the consumer.
|
|
88
137
|
const inputDragging = useViewer((state) => state.inputDragging)
|
|
89
138
|
const scopeIdle = useInteractionScope((state) => state.scope.kind === 'idle')
|
|
139
|
+
const isCeilingToolActive = useIsCeilingToolActive()
|
|
90
140
|
|
|
91
141
|
useEffect(() => {
|
|
92
142
|
const nodes = useScene.getState().nodes
|
|
@@ -96,12 +146,6 @@ export const CeilingSystem = () => {
|
|
|
96
146
|
|
|
97
147
|
const levelsToShowCeilings = new Set<string>()
|
|
98
148
|
|
|
99
|
-
const isCeilingToolActive =
|
|
100
|
-
tool === 'ceiling' ||
|
|
101
|
-
selectedItem?.attachTo === 'ceiling' ||
|
|
102
|
-
(movingNode?.type === 'item' && movingNode?.asset?.attachTo === 'ceiling') ||
|
|
103
|
-
(isProceduralItem(movingNode) && movingNode.recipe.mounting?.attachTo === 'ceiling')
|
|
104
|
-
|
|
105
149
|
if (isCeilingToolActive && activeLevelId) {
|
|
106
150
|
levelsToShowCeilings.add(activeLevelId)
|
|
107
151
|
}
|
|
@@ -162,15 +206,6 @@ export const CeilingSystem = () => {
|
|
|
162
206
|
}
|
|
163
207
|
}
|
|
164
208
|
})
|
|
165
|
-
}, [
|
|
166
|
-
tool,
|
|
167
|
-
selectedItem,
|
|
168
|
-
movingNode,
|
|
169
|
-
selectedIds,
|
|
170
|
-
activeLevelId,
|
|
171
|
-
hoveredId,
|
|
172
|
-
scopeIdle,
|
|
173
|
-
inputDragging,
|
|
174
|
-
])
|
|
209
|
+
}, [isCeilingToolActive, selectedIds, activeLevelId, hoveredId, scopeIdle, inputDragging])
|
|
175
210
|
return null
|
|
176
211
|
}
|
|
@@ -38,7 +38,6 @@ test('roof trim drag keeps its plane hit over a surface joining and leaving a ba
|
|
|
38
38
|
id: 'rseg_batch_drag',
|
|
39
39
|
width: 10,
|
|
40
40
|
depth: 10,
|
|
41
|
-
metadata: { showTrimPlanes: true },
|
|
42
41
|
})
|
|
43
42
|
const source = new THREE.Group()
|
|
44
43
|
const surface = new THREE.Mesh(new THREE.BoxGeometry(30, 0.25, 30), new THREE.MeshBasicMaterial())
|
|
@@ -52,6 +51,9 @@ test('roof trim drag keeps its plane hit over a surface joining and leaving a ba
|
|
|
52
51
|
hoveredId: null,
|
|
53
52
|
selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [segment.id] },
|
|
54
53
|
})
|
|
54
|
+
useInteractionScope
|
|
55
|
+
.getState()
|
|
56
|
+
.begin({ kind: 'reshaping', reshape: 'trim', nodeId: segment.id, driver: 'tool' })
|
|
55
57
|
await root.configure({
|
|
56
58
|
gl: {
|
|
57
59
|
domElement: canvas,
|
|
@@ -99,6 +101,18 @@ test('roof trim drag keeps its plane hit over a surface joining and leaving a ba
|
|
|
99
101
|
events.dispatchEvent(new Event('pointercancel'))
|
|
100
102
|
})
|
|
101
103
|
expect(useLiveNodeOverrides.getState().overrides.has(segment.id)).toBe(false)
|
|
104
|
+
const fieldEscape = Object.assign(new Event('keydown'), { key: 'Escape' })
|
|
105
|
+
Object.defineProperty(fieldEscape, 'target', {
|
|
106
|
+
value: { closest: () => ({ tagName: 'INPUT' }) },
|
|
107
|
+
})
|
|
108
|
+
await act(async () => {
|
|
109
|
+
events.dispatchEvent(fieldEscape)
|
|
110
|
+
})
|
|
111
|
+
expect(useInteractionScope.getState().scope.kind).toBe('reshaping')
|
|
112
|
+
await act(async () => {
|
|
113
|
+
events.dispatchEvent(Object.assign(new Event('keydown'), { key: 'Escape' }))
|
|
114
|
+
})
|
|
115
|
+
expect(useInteractionScope.getState().scope.kind).toBe('idle')
|
|
102
116
|
} finally {
|
|
103
117
|
await act(async () => {
|
|
104
118
|
root.render(null)
|
|
@@ -34,6 +34,7 @@ import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu'
|
|
|
34
34
|
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
35
35
|
import { isHistoryShortcut } from '../../../lib/history'
|
|
36
36
|
import { getHoveredRoofSegmentOutlineProxyName } from '../../../lib/roof-hover-outline-proxy'
|
|
37
|
+
import { isTypingTarget } from '../../../lib/typing-target'
|
|
37
38
|
import useInteractionScope, { useMovingNode } from '../../../store/use-interaction-scope'
|
|
38
39
|
import { swallowNextClick } from '../../editor/handles/use-handle-drag'
|
|
39
40
|
|
|
@@ -1006,15 +1007,6 @@ function getTrimCursor(side: RoofTrimSide): string {
|
|
|
1006
1007
|
}
|
|
1007
1008
|
}
|
|
1008
1009
|
|
|
1009
|
-
function shouldShowTrimPlanes(metadata: unknown): boolean {
|
|
1010
|
-
return (
|
|
1011
|
-
typeof metadata === 'object' &&
|
|
1012
|
-
metadata !== null &&
|
|
1013
|
-
!Array.isArray(metadata) &&
|
|
1014
|
-
(metadata as Record<string, unknown>).showTrimPlanes === true
|
|
1015
|
-
)
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
1010
|
function commitSegmentTrim(segment: RoofSegmentNode, trim: RoofSegmentTrim) {
|
|
1019
1011
|
const scene = useScene.getState()
|
|
1020
1012
|
scene.applyNodeChanges({
|
|
@@ -1031,6 +1023,28 @@ function RoofTrimHandles() {
|
|
|
1031
1023
|
return node?.type === 'roof-segment' ? (node as RoofSegmentNode) : null
|
|
1032
1024
|
})
|
|
1033
1025
|
const readOnly = useScene((s) => s.readOnly)
|
|
1026
|
+
const showTrimPlanes = useInteractionScope(
|
|
1027
|
+
(s) =>
|
|
1028
|
+
s.scope.kind === 'reshaping' && s.scope.reshape === 'trim' && s.scope.nodeId === selectedId,
|
|
1029
|
+
)
|
|
1030
|
+
useEffect(() => {
|
|
1031
|
+
const end = () =>
|
|
1032
|
+
useInteractionScope
|
|
1033
|
+
.getState()
|
|
1034
|
+
.endIf(
|
|
1035
|
+
(scope) =>
|
|
1036
|
+
scope.kind === 'reshaping' && scope.reshape === 'trim' && scope.nodeId === selectedId,
|
|
1037
|
+
)
|
|
1038
|
+
const onKey = (event: KeyboardEvent) => {
|
|
1039
|
+
if (isTypingTarget(event.target)) return
|
|
1040
|
+
if (event.key === 'Escape') end()
|
|
1041
|
+
}
|
|
1042
|
+
window.addEventListener('keydown', onKey)
|
|
1043
|
+
return () => {
|
|
1044
|
+
window.removeEventListener('keydown', onKey)
|
|
1045
|
+
end()
|
|
1046
|
+
}
|
|
1047
|
+
}, [selectedId])
|
|
1034
1048
|
const liveOverrideKey = useLiveNodeOverrides((s) =>
|
|
1035
1049
|
segment ? JSON.stringify(s.overrides.get(segment.id) ?? null) : null,
|
|
1036
1050
|
)
|
|
@@ -1042,7 +1056,10 @@ function RoofTrimHandles() {
|
|
|
1042
1056
|
const zoom = camera instanceof THREE.OrthographicCamera ? 1 / camera.zoom : 1
|
|
1043
1057
|
const handleBaseScale = zoom * TRIM_HANDLE_BASE_SCALE
|
|
1044
1058
|
|
|
1045
|
-
useEffect(() =>
|
|
1059
|
+
useEffect(() => {
|
|
1060
|
+
if (!selectedId || !showTrimPlanes || readOnly) dragCleanupRef.current?.()
|
|
1061
|
+
return () => dragCleanupRef.current?.()
|
|
1062
|
+
}, [selectedId, showTrimPlanes, readOnly])
|
|
1046
1063
|
|
|
1047
1064
|
const liveSegment = useMemo(() => {
|
|
1048
1065
|
if (!segment) return null
|
|
@@ -1059,8 +1076,6 @@ function RoofTrimHandles() {
|
|
|
1059
1076
|
groupRef.current.matrixAutoUpdate = false
|
|
1060
1077
|
})
|
|
1061
1078
|
|
|
1062
|
-
const showTrimPlanes = shouldShowTrimPlanes(liveSegment?.metadata)
|
|
1063
|
-
|
|
1064
1079
|
if (readOnly || !segment || !liveSegment || !showTrimPlanes) return null
|
|
1065
1080
|
|
|
1066
1081
|
const trim = normalizeRoofSegmentTrim(liveSegment)
|
|
@@ -1314,9 +1329,7 @@ function RoofTrimHandles() {
|
|
|
1314
1329
|
|
|
1315
1330
|
document.body.style.cursor = getTrimCursor(side)
|
|
1316
1331
|
setDraggingSide(side)
|
|
1317
|
-
|
|
1318
|
-
.getState()
|
|
1319
|
-
.begin({ kind: 'handle-drag', nodeId: segmentId, handle: getTrimLabel(side) })
|
|
1332
|
+
|
|
1320
1333
|
useViewer.getState().setInputDragging(true)
|
|
1321
1334
|
useScene.temporal.getState().pause()
|
|
1322
1335
|
|
|
@@ -1355,9 +1368,7 @@ function RoofTrimHandles() {
|
|
|
1355
1368
|
document.body.style.cursor = ''
|
|
1356
1369
|
}
|
|
1357
1370
|
useScene.temporal.getState().resume()
|
|
1358
|
-
|
|
1359
|
-
.getState()
|
|
1360
|
-
.endIf((scope) => scope.kind === 'handle-drag' && scope.nodeId === segmentId)
|
|
1371
|
+
|
|
1361
1372
|
useViewer.getState().setInputDragging(false)
|
|
1362
1373
|
setDraggingSide(null)
|
|
1363
1374
|
dragCleanupRef.current = null
|
|
@@ -1393,7 +1404,7 @@ function RoofTrimHandles() {
|
|
|
1393
1404
|
onCancel()
|
|
1394
1405
|
}
|
|
1395
1406
|
|
|
1396
|
-
dragCleanupRef.current =
|
|
1407
|
+
dragCleanupRef.current = onCancel
|
|
1397
1408
|
window.addEventListener('pointermove', onMove)
|
|
1398
1409
|
window.addEventListener('pointerup', onUp)
|
|
1399
1410
|
window.addEventListener('pointercancel', onCancel)
|