@pascal-app/editor 1.0.7 → 1.1.1
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 +9 -15
- package/src/components/editor/action-menu-button.tsx +1 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
- package/src/components/editor/custom-camera-controls.tsx +101 -45
- package/src/components/editor/duplicate-node.ts +2 -1
- package/src/components/editor/editor-layout-v2.tsx +1 -1
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +20 -1983
- 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 +5 -3
- 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 +8 -4
- package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
- package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
- package/src/components/editor/floorplan-panel.tsx +199 -30
- 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/follow-pascal.test.tsx +1 -2
- package/src/components/editor/follow-pascal.tsx +1 -1
- package/src/components/editor/footprint-height-handle.tsx +2 -2
- package/src/components/editor/group-actions.ts +1 -2
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +1 -2
- package/src/components/editor/group-rotate-handle.tsx +1 -2
- package/src/components/editor/group-transform-shared.ts +3 -1
- package/src/components/editor/handles/use-handle-drag.ts +1 -2
- package/src/components/editor/index.tsx +43 -13
- package/src/components/editor/inspector-extension-actions.tsx +1 -1
- package/src/components/editor/measurement-pill.tsx +1 -1
- package/src/components/editor/node-arrow-handles.tsx +2 -2
- 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/room-controls.tsx +10 -6
- package/src/components/editor/room-handles-2d.tsx +3 -3
- package/src/components/editor/room-handles.tsx +4 -5
- package/src/components/editor/room-highlight.tsx +6 -105
- package/src/components/editor/room-labels.test.ts +8 -822
- package/src/components/editor/room-labels.tsx +41 -1670
- package/src/components/editor/room-webgpu-materials.test.ts +1 -1
- package/src/components/editor/selection-manager.tsx +5 -3
- package/src/components/editor/site-edge-labels.tsx +7 -3
- package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
- package/src/components/editor/use-floorplan-background-placement.ts +2 -1
- package/src/components/editor/views/plugins-view.tsx +1 -1
- package/src/components/editor/views/view-bar.tsx +9 -9
- package/src/components/editor/wall-measurement-label.tsx +31 -15
- package/src/components/editor/wall-move-side-handles.tsx +11 -7
- package/src/components/editor/wall-pull-grip.tsx +2 -2
- package/src/components/editor/wall-region-handles.tsx +2 -2
- package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +13 -13
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +2 -3
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +23 -6
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +14 -3
- 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 +30 -10
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +39 -35
- package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
- package/src/components/systems/roof/roof-edit-system.tsx +33 -21
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +151 -33
- 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/use-placement-coordinator.tsx +3 -4
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +7 -3
- package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +39 -16
- package/src/components/tools/shared/cursor-sphere.tsx +1 -1
- 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-typed-entry.test.ts +99 -0
- package/src/components/tools/wall/wall-typed-entry.ts +70 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -2
- package/src/components/ui/action-menu/control-modes.tsx +1 -1
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
- package/src/components/ui/action-menu/index.tsx +3 -3
- package/src/components/ui/action-menu/measurement-control.tsx +3 -3
- package/src/components/ui/action-menu/placement.ts +0 -18
- package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
- package/src/components/ui/action-menu/view-toggles.tsx +5 -5
- package/src/components/ui/controls/finish-swatch.tsx +1 -1
- package/src/components/ui/controls/material-paint-panel.tsx +2 -2
- package/src/components/ui/controls/scene-material-list.tsx +2 -2
- package/src/components/ui/controls/select-control.tsx +1 -1
- package/src/components/ui/controls/slider-control.tsx +18 -6
- package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
- package/src/components/ui/controls/tool-options-panel.tsx +74 -44
- package/src/components/ui/floating-level-selector.tsx +3 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
- package/src/components/ui/helpers/helper-manager.test.tsx +54 -1
- package/src/components/ui/helpers/helper-manager.tsx +55 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
- package/src/components/ui/panels/floor-foundation-panel.tsx +3 -3
- package/src/components/ui/panels/floor-opening-panel.tsx +5 -5
- package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
- package/src/components/ui/panels/multi-height-mode.tsx +3 -3
- package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
- package/src/components/ui/panels/node-display.ts +1 -1
- package/src/components/ui/panels/panel-manager.tsx +5 -3
- package/src/components/ui/panels/panel-wrapper.tsx +42 -19
- package/src/components/ui/panels/parametric-inspector.tsx +1 -1
- package/src/components/ui/panels/room-construction-rows.tsx +7 -7
- package/src/components/ui/panels/room-name-options.ts +1 -5
- package/src/components/ui/panels/room-openings-field.tsx +1 -1
- package/src/components/ui/panels/room-panel.tsx +7 -9
- package/src/components/ui/primitives/color-dot.tsx +2 -2
- package/src/components/ui/primitives/opacity-control.tsx +2 -2
- package/src/components/ui/primitives/sidebar.tsx +3 -3
- package/src/components/ui/right-stack.test.ts +13 -6
- package/src/components/ui/right-stack.tsx +22 -11
- package/src/components/ui/sidebar/icon-rail.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +2 -6
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +3 -10
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +1 -1
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +2 -2
- package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
- package/src/components/viewer-overlay.tsx +1 -2
- package/src/hooks/use-auto-frame.ts +1 -2
- package/src/hooks/use-keyboard.ts +1 -2
- package/src/hooks/use-selected-room.ts +5 -7
- package/src/i18n/floor-footprint-name.ts +1 -1
- package/src/i18n/locales/en/messages.po +1036 -306
- package/src/i18n/locales/en/messages.ts +1 -1
- package/src/i18n/locales/en-XA/messages.ts +1 -1
- package/src/i18n/locales/es/messages.po +2267 -439
- package/src/i18n/locales/fr/messages.po +2267 -439
- package/src/i18n/locales/ja/messages.po +2267 -439
- package/src/i18n/locales/zh-CN/messages.po +2267 -439
- package/src/i18n/locales.test.ts +1 -1
- package/src/index.tsx +183 -178
- package/src/lib/below-grade.test.tsx +4 -4
- package/src/lib/camera-facing.test.ts +1 -1
- package/src/lib/camera-facing.ts +2 -3
- package/src/lib/contextual-help.ts +6 -0
- package/src/lib/editor-level-display.ts +1 -1
- package/src/lib/elevation-guides.ts +21 -6
- package/src/lib/elevator-support.ts +1 -72
- package/src/lib/floor-footprints.test.ts +3 -3
- package/src/lib/floor-footprints.ts +1 -9
- package/src/lib/floor-foundation-support.test.ts +8 -3
- package/src/lib/floor-opening-draft.ts +2 -2
- package/src/lib/floor-region-session.ts +1 -1
- package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
- package/src/lib/floorplan/annotation-visibility.ts +1 -1
- package/src/lib/floorplan/floorplan-export.test.ts +1 -1
- package/src/lib/floorplan/floorplan-export.tsx +8 -11
- package/src/lib/floorplan/floorplan-extension.ts +4 -157
- package/src/lib/floorplan/floorplan-mode.ts +2 -1
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
- package/src/lib/floorplan/geometry.ts +2 -112
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +2 -1
- package/src/lib/floorplan/open-wall-ends.ts +2 -1
- package/src/lib/floorplan/schedules.ts +6 -3
- package/src/lib/floorplan/sheet-export.ts +7 -3
- package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
- package/src/lib/follow-pascal-presentation.ts +1 -1
- package/src/lib/gesture-lifecycle.ts +3 -1
- package/src/lib/glb-export.ts +1 -1
- package/src/lib/interaction/scope.ts +1 -0
- package/src/lib/layer-thickness.ts +1 -1
- 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/material-paint.ts +1 -1
- package/src/lib/measurement-label.test.ts +1 -1
- package/src/lib/measurements.test.ts +7 -5
- package/src/lib/mezzanine-selection.test.ts +1 -1
- package/src/lib/node-action-movement.test.tsx +115 -0
- package/src/lib/node-action-movement.ts +2 -0
- package/src/lib/paint-region-mode.ts +1 -1
- 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/point-ask/session.ts +1 -2
- 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/roof-duplication.test.ts +100 -0
- package/src/lib/roof-duplication.ts +30 -5
- package/src/lib/room-built-on.test.ts +2 -5
- package/src/lib/room-built-on.ts +1 -15
- package/src/lib/room-construction-commands.ts +3 -2
- package/src/lib/room-controls-phase6.test.ts +1 -1
- package/src/lib/room-handle-drag.ts +8 -37
- package/src/lib/room-name-catalog.test.ts +2 -1
- package/src/lib/room-name-messages.test.ts +2 -1
- package/src/lib/room-name-messages.ts +1 -118
- package/src/lib/room-name-storage.test.ts +2 -1
- package/src/lib/room-push-dimensions.test.ts +1 -1
- package/src/lib/room-selection-commands.ts +2 -2
- package/src/lib/room-selection.test.ts +6 -6
- package/src/lib/room-transform-session.ts +1 -2
- package/src/lib/room-zone-routing.ts +2 -2
- package/src/lib/scene-clipboard.ts +2 -1
- package/src/lib/selection-routing.test.ts +0 -28
- package/src/lib/selection-routing.ts +1 -40
- package/src/lib/sfx-bus.ts +8 -32
- package/src/lib/sfx-player.test.ts +72 -1
- package/src/lib/sfx-player.ts +49 -337
- package/src/lib/snapping-mode.test.ts +23 -1
- package/src/lib/viewer-selection.test.ts +38 -10
- package/src/lib/wall-pull.test.ts +1 -1
- package/src/lib/wall-pull.ts +1 -27
- package/src/lib/wall-region-list.ts +3 -1
- package/src/lib/window-interaction.test.ts +4 -2
- package/src/store/use-editor.tsx +22 -26
- package/src/store/use-floorplan-annotation-visibility.ts +5 -5
- package/src/store/use-placement-typing.test.ts +165 -0
- package/src/store/use-placement-typing.ts +186 -0
- package/src/store/use-wall-region-handles-selection.ts +1 -2
- package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
- package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
- package/src/components/editor/first-person/build-collider-world.ts +0 -549
- package/src/components/editor/first-person/ground-holes.test.ts +0 -128
- package/src/components/editor/first-person/ground-holes.ts +0 -130
- package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
- package/src/components/editor/first-person/mechanism-targets.ts +0 -88
- package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
- package/src/components/editor/first-person/terrain-collider.ts +0 -212
- package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
- package/src/components/editor/floorplan-camera-sync.ts +0 -213
- package/src/components/editor/localized-html.tsx +0 -22
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
- package/src/components/editor-2d/svg-paths.ts +0 -124
- 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/components/ui/action-menu/action-button.tsx +0 -89
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
- package/src/components/ui/primitives/button.tsx +0 -69
- package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
- package/src/components/ui/primitives/popover.tsx +0 -42
- package/src/components/ui/primitives/switch.tsx +0 -29
- package/src/components/ui/primitives/tooltip.tsx +0 -57
- package/src/components/ui/scene-loader.tsx +0 -73
- package/src/components/viewer/floorplan-compass-button.tsx +0 -67
- package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
- package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
- package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
- package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
- package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
- package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
- package/src/components/viewer/floorplan-preview.tsx +0 -1065
- package/src/components/viewer/render-settings-menu.tsx +0 -294
- package/src/components/viewer/sky-control.tsx +0 -710
- package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
- package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
- package/src/components/viewer/viewer-controls-bar.tsx +0 -496
- package/src/components/viewer/viewer-hover-label.tsx +0 -42
- package/src/components/viewer/viewer-rooms.tsx +0 -248
- package/src/components/viewer/viewer-scene-header.tsx +0 -286
- package/src/components/viewer/viewer-selection-manager.tsx +0 -124
- package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
- package/src/components/viewer/viewer-stage-modes.ts +0 -49
- package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
- package/src/components/viewer/viewer-stage.test.tsx +0 -47
- package/src/components/viewer/viewer-stage.tsx +0 -221
- package/src/components/walkthrough-hud.tsx +0 -193
- package/src/hooks/use-mobile.ts +0 -19
- package/src/i18n/level-name.ts +0 -39
- package/src/i18n/scene-theme-name.ts +0 -16
- package/src/lib/below-grade.ts +0 -133
- package/src/lib/camera-pose.test.ts +0 -237
- package/src/lib/camera-pose.ts +0 -172
- package/src/lib/door-interaction.ts +0 -102
- package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
- package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
- package/src/lib/floorplan/floorplan-readonly.ts +0 -95
- package/src/lib/floorplan/marks.test.ts +0 -213
- package/src/lib/floorplan/marks.ts +0 -238
- package/src/lib/measurement-label.ts +0 -68
- package/src/lib/measurements.ts +0 -225
- package/src/lib/node-display-label.ts +0 -37
- package/src/lib/room-assembly-overlay.test.ts +0 -275
- package/src/lib/room-assembly-overlay.ts +0 -265
- package/src/lib/room-selection.ts +0 -417
- package/src/lib/scene-bounds.test.ts +0 -183
- package/src/lib/scene-bounds.ts +0 -169
- package/src/lib/typing-target.ts +0 -9
- package/src/lib/viewer-selection.ts +0 -595
- package/src/lib/window-interaction.ts +0 -142
- package/src/lib/zone-content.ts +0 -133
- package/src/store/camera-pose-store.test.ts +0 -29
- package/src/store/camera-pose-store.ts +0 -27
- package/src/store/navigation-sync-pose-store.test.ts +0 -34
- package/src/store/navigation-sync-pose-store.ts +0 -27
- package/src/store/use-audio.test.ts +0 -146
- package/src/store/use-audio.tsx +0 -169
- package/src/store/use-first-person-hud.ts +0 -40
- package/src/store/use-follow-pascal.test.ts +0 -62
- package/src/store/use-follow-pascal.ts +0 -53
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
collectionIdsOf,
|
|
8
8
|
createSceneApi,
|
|
9
9
|
getWallMidpointHandlePoint,
|
|
10
|
+
isNodeDuplicable,
|
|
10
11
|
type NodeQuickAction,
|
|
11
12
|
nodeRegistry,
|
|
12
13
|
runAsSingleSceneHistoryStep,
|
|
@@ -255,7 +256,7 @@ export function FloorplanRegistryActionMenu() {
|
|
|
255
256
|
const canMove =
|
|
256
257
|
!registryMoveDisabled(node) &&
|
|
257
258
|
(!!def.capabilities.movable || !!def.floorplanMoveTarget || !!def.affordanceTools?.move)
|
|
258
|
-
const canDuplicate =
|
|
259
|
+
const canDuplicate = isNodeDuplicable(node)
|
|
259
260
|
const canDelete = def.capabilities.deletable !== false
|
|
260
261
|
const canAddHole = node.type === 'slab' || node.type === 'ceiling'
|
|
261
262
|
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
useScene,
|
|
27
27
|
} from '@pascal-app/core'
|
|
28
28
|
import { nodeLevelFrame } from '@pascal-app/core/procedural-items'
|
|
29
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
29
|
+
import { isTypingTarget, useViewer } from '@pascal-app/viewer'
|
|
30
30
|
import { useEffect, useMemo } from 'react'
|
|
31
31
|
import { commitFreshPlacementSubtree } from '../../lib/fresh-planar-placement'
|
|
32
32
|
import { isHistoryShortcut } from '../../lib/history'
|
|
@@ -39,7 +39,6 @@ import {
|
|
|
39
39
|
import { movementSfxStepKey } from '../../lib/sfx/movement-tick'
|
|
40
40
|
import { sfxEmitter } from '../../lib/sfx-bus'
|
|
41
41
|
import { surfaceAttachmentId } from '../../lib/surface-attachment'
|
|
42
|
-
import { isTypingTarget } from '../../lib/typing-target'
|
|
43
42
|
import { resolveAlignmentForFloorplanView } from '../../lib/world-grid-snap'
|
|
44
43
|
import useAlignmentGuides from '../../store/use-alignment-guides'
|
|
45
44
|
import useEditor, {
|
|
@@ -850,14 +849,25 @@ export function FloorplanRegistryMoveOverlay() {
|
|
|
850
849
|
}
|
|
851
850
|
boxEl.setAttribute('transform', transform)
|
|
852
851
|
const oldY = originalPosition[1]
|
|
852
|
+
const position = nodeRegistry.get(movingNode.type)?.capabilities?.movable?.resolvePosition?.({
|
|
853
|
+
node: useScene.getState().nodes[movingNode.id] ?? movingNode,
|
|
854
|
+
position: [finalX, oldY, finalZ],
|
|
855
|
+
nodes: useScene.getState().nodes,
|
|
856
|
+
}) ?? [finalX, oldY, finalZ]
|
|
857
|
+
if (def?.capabilities?.movable?.resolvePosition) {
|
|
858
|
+
useLiveTransforms.getState().set(movingNode.id, {
|
|
859
|
+
position: position as [number, number, number],
|
|
860
|
+
rotation: currentRotation,
|
|
861
|
+
})
|
|
862
|
+
}
|
|
853
863
|
lastPositionValid = movableValidityConfig?.isValidPosition
|
|
854
864
|
? movableValidityConfig.isValidPosition({
|
|
855
865
|
node: {
|
|
856
866
|
...movingNode,
|
|
857
|
-
position
|
|
867
|
+
position,
|
|
858
868
|
rotation: currentRotation,
|
|
859
869
|
} as AnyNode,
|
|
860
|
-
position: [
|
|
870
|
+
position: position as [number, number, number],
|
|
861
871
|
rotation: currentRotation,
|
|
862
872
|
levelId:
|
|
863
873
|
(useViewer.getState().selection.levelId as AnyNodeId | null) ??
|
|
@@ -885,6 +895,11 @@ export function FloorplanRegistryMoveOverlay() {
|
|
|
885
895
|
(storedRotation as [number?, number?, number?])[2] ?? 0,
|
|
886
896
|
]
|
|
887
897
|
: snapped.rotation
|
|
898
|
+
const position = nodeRegistry.get(movingNode.type)?.capabilities?.movable?.resolvePosition?.({
|
|
899
|
+
node: useScene.getState().nodes[movingNode.id] ?? movingNode,
|
|
900
|
+
position: [sx, oldY, sz],
|
|
901
|
+
nodes: useScene.getState().nodes,
|
|
902
|
+
}) ?? [sx, oldY, sz]
|
|
888
903
|
const rotationPatch = 'rotation' in movingNode ? { rotation } : {}
|
|
889
904
|
const atomicPreview =
|
|
890
905
|
isFreshPlacement &&
|
|
@@ -937,7 +952,7 @@ export function FloorplanRegistryMoveOverlay() {
|
|
|
937
952
|
const committedId = commitFreshPlacementSubtree(
|
|
938
953
|
movingNode.id as AnyNodeId,
|
|
939
954
|
{
|
|
940
|
-
position
|
|
955
|
+
position,
|
|
941
956
|
...rotationPatch,
|
|
942
957
|
metadata: stripPlacementMetadataFlags((movingNode as { metadata?: unknown }).metadata),
|
|
943
958
|
visible: true,
|
|
@@ -953,7 +968,7 @@ export function FloorplanRegistryMoveOverlay() {
|
|
|
953
968
|
useScene.getState().updateNode(
|
|
954
969
|
movingNode.id as AnyNodeId,
|
|
955
970
|
{
|
|
956
|
-
position
|
|
971
|
+
position,
|
|
957
972
|
...rotationPatch,
|
|
958
973
|
} as Partial<AnyNode>,
|
|
959
974
|
)
|
|
@@ -1005,6 +1020,8 @@ export function FloorplanRegistryMoveOverlay() {
|
|
|
1005
1020
|
for (const relatedEntry of relatedEntries) {
|
|
1006
1021
|
relatedEntry.style.visibility = ''
|
|
1007
1022
|
}
|
|
1023
|
+
if (def?.capabilities?.movable?.resolvePosition)
|
|
1024
|
+
useLiveTransforms.getState().clear(movingNode.id)
|
|
1008
1025
|
boxEl.remove()
|
|
1009
1026
|
useAlignmentGuides.getState().clear()
|
|
1010
1027
|
}
|
|
@@ -1,47 +1,10 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { floorplanGeometryMetadata } from '../../../lib/floorplan/floorplan-extension'
|
|
3
2
|
import {
|
|
4
|
-
floorplanAnnotationObstacleMode,
|
|
5
3
|
polylineObstacleRectangles,
|
|
6
4
|
resolveAnnotationLabelRectangles,
|
|
7
5
|
resolveSvgAnnotationCollisions,
|
|
8
6
|
} from './floorplan-annotation-layout'
|
|
9
7
|
|
|
10
|
-
describe('floorplanAnnotationObstacleMode', () => {
|
|
11
|
-
test('treats fixed annotation categories as layout obstacles', () => {
|
|
12
|
-
expect(
|
|
13
|
-
floorplanAnnotationObstacleMode({
|
|
14
|
-
kind: 'text',
|
|
15
|
-
x: 0,
|
|
16
|
-
y: 0,
|
|
17
|
-
text: 'BEDROOM',
|
|
18
|
-
fontSize: 0.18,
|
|
19
|
-
metadata: floorplanGeometryMetadata({ annotationRole: 'room-label' }),
|
|
20
|
-
}),
|
|
21
|
-
).toBe('bounds')
|
|
22
|
-
expect(
|
|
23
|
-
floorplanAnnotationObstacleMode({
|
|
24
|
-
kind: 'line',
|
|
25
|
-
x1: 0,
|
|
26
|
-
y1: 0,
|
|
27
|
-
x2: 1,
|
|
28
|
-
y2: 0,
|
|
29
|
-
metadata: floorplanGeometryMetadata({ annotationRole: 'column-center' }),
|
|
30
|
-
}),
|
|
31
|
-
).toBe('bounds')
|
|
32
|
-
expect(
|
|
33
|
-
floorplanAnnotationObstacleMode({
|
|
34
|
-
kind: 'polyline',
|
|
35
|
-
points: [
|
|
36
|
-
[0, 0],
|
|
37
|
-
[1, 0],
|
|
38
|
-
],
|
|
39
|
-
metadata: floorplanGeometryMetadata({ annotationRole: 'stair-annotation' }),
|
|
40
|
-
}),
|
|
41
|
-
).toBe('outline')
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
|
|
45
8
|
describe('resolveAnnotationLabelRectangles', () => {
|
|
46
9
|
test('keeps the higher-priority label and moves the conflicting label', () => {
|
|
47
10
|
const shifts = resolveAnnotationLabelRectangles([
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import type { FloorplanGeometry } from '@pascal-app/core'
|
|
2
|
-
import { readFloorplanGeometryMetadata } from '../../../lib/floorplan/floorplan-extension'
|
|
3
|
-
|
|
4
1
|
export type AnnotationLabelRectangle = {
|
|
5
2
|
id: string
|
|
6
3
|
x: number
|
|
@@ -467,33 +464,6 @@ function candidateCost(
|
|
|
467
464
|
return preference + distance + perpendicularMovement * 4
|
|
468
465
|
}
|
|
469
466
|
|
|
470
|
-
export function isFloorplanAnnotationObstacleGeometry(geometry: FloorplanGeometry): boolean {
|
|
471
|
-
if (floorplanAnnotationObstacleMode(geometry)) return true
|
|
472
|
-
if (geometry.kind !== 'group') return false
|
|
473
|
-
const hasPlate = geometry.children.some(
|
|
474
|
-
(child) => child.kind === 'rect' || child.kind === 'circle',
|
|
475
|
-
)
|
|
476
|
-
const hasUprightText = geometry.children.some((child) => child.kind === 'text' && child.upright)
|
|
477
|
-
return hasPlate && hasUprightText
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
export function floorplanAnnotationObstacleMode(
|
|
481
|
-
geometry: FloorplanGeometry,
|
|
482
|
-
): 'bounds' | 'outline' | '' | undefined {
|
|
483
|
-
const metadata = readFloorplanGeometryMetadata(geometry)
|
|
484
|
-
if (metadata.annotationObstacle) return metadata.annotationObstacle
|
|
485
|
-
switch (metadata.annotationRole) {
|
|
486
|
-
case 'room-label':
|
|
487
|
-
return geometry.kind === 'text' ? 'bounds' : undefined
|
|
488
|
-
case 'column-center':
|
|
489
|
-
return geometry.kind === 'line' || geometry.kind === 'text' ? 'bounds' : undefined
|
|
490
|
-
case 'stair-annotation':
|
|
491
|
-
return geometry.kind === 'polyline' || geometry.kind === 'line' ? 'outline' : 'bounds'
|
|
492
|
-
default:
|
|
493
|
-
return undefined
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
467
|
function rectanglesOverlap(
|
|
498
468
|
left: Pick<AnnotationLabelRectangle, 'x' | 'y' | 'width' | 'height'>,
|
|
499
469
|
right: Pick<AnnotationLabelRectangle, 'x' | 'y' | 'width' | 'height'>,
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import type { AnyNode, AnyNodeId } from '@pascal-app/core'
|
|
4
4
|
import { useLocale, useScene } from '@pascal-app/core'
|
|
5
5
|
import { useViewer } from '@pascal-app/viewer'
|
|
6
|
-
import {
|
|
6
|
+
import { FloorplanDimensionHitsProvider } from '@pascal-app/viewer/ui'
|
|
7
|
+
import { type ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
|
7
8
|
import { createPortal } from 'react-dom'
|
|
8
9
|
import {
|
|
9
10
|
parseDimensionInput,
|
|
@@ -26,8 +27,8 @@ import useEditor from '../../../store/use-editor'
|
|
|
26
27
|
* anchor the value is written as `textOverride` on a construction-dimension
|
|
27
28
|
* node instead, and the label renders an "override" badge.
|
|
28
29
|
*
|
|
29
|
-
* The label is found by DOM delegation on the attributes
|
|
30
|
-
*
|
|
30
|
+
* The label is found by DOM delegation on the attributes the viewer's
|
|
31
|
+
* dimension renderer emits under `FloorplanDimensionHitsGate`, so this works identically for
|
|
31
32
|
* `dimension` and `dimension-string` geometry, for automatic wall dimensions,
|
|
32
33
|
* contextual dimensions and manual construction-dimension nodes — and on
|
|
33
34
|
* sheets, which mount the same renderer.
|
|
@@ -62,6 +63,15 @@ export function useDimensionEditAllowed(): boolean {
|
|
|
62
63
|
return useEditor((state) => isDimensionEditAllowed(state, { readOnly }))
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
/** Plan dimension labels under the editor carry their typeable hit rects while typing is allowed. */
|
|
67
|
+
export function FloorplanDimensionHitsGate({ children }: { children: ReactNode }) {
|
|
68
|
+
return (
|
|
69
|
+
<FloorplanDimensionHitsProvider value={useDimensionEditAllowed()}>
|
|
70
|
+
{children}
|
|
71
|
+
</FloorplanDimensionHitsProvider>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
/**
|
|
66
76
|
* The typeable dimension value under a press, with the node typing it drives:
|
|
67
77
|
* the pressed element's or, when the plan's dashed group box lies over it, the
|
|
@@ -19,6 +19,7 @@ export type DraftMeasurement = {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function FloorplanDraftMeasurement({
|
|
22
|
+
lengthHidden = false,
|
|
22
23
|
measurement,
|
|
23
24
|
measurementStroke,
|
|
24
25
|
labelBackground,
|
|
@@ -26,6 +27,8 @@ export function FloorplanDraftMeasurement({
|
|
|
26
27
|
sceneRotationDeg,
|
|
27
28
|
unitsPerPixel,
|
|
28
29
|
}: {
|
|
30
|
+
/** Leave the length plate to something drawn in its place (the typed-entry HUD). */
|
|
31
|
+
lengthHidden?: boolean
|
|
29
32
|
measurement: DraftMeasurement
|
|
30
33
|
measurementStroke: string
|
|
31
34
|
labelBackground: string
|
|
@@ -68,7 +71,10 @@ export function FloorplanDraftMeasurement({
|
|
|
68
71
|
|
|
69
72
|
return (
|
|
70
73
|
<g pointerEvents="none">
|
|
71
|
-
<g
|
|
74
|
+
<g
|
|
75
|
+
display={lengthHidden ? 'none' : undefined}
|
|
76
|
+
transform={`translate(${cx} ${cy}) rotate(${labelAngleDeg})`}
|
|
77
|
+
>
|
|
72
78
|
<rect
|
|
73
79
|
fill={labelBg}
|
|
74
80
|
height={lengthPlateH}
|
|
@@ -4,17 +4,18 @@ import {
|
|
|
4
4
|
type AnyNode,
|
|
5
5
|
type AnyNodeId,
|
|
6
6
|
type FloorplanGeometry,
|
|
7
|
+
formatLinearMeasurement,
|
|
7
8
|
type GeometryContext,
|
|
8
9
|
nodeRegistry,
|
|
9
10
|
useScene,
|
|
10
11
|
} from '@pascal-app/core'
|
|
11
12
|
import { useViewer } from '@pascal-app/viewer'
|
|
13
|
+
import { FloorplanDimensionRenderer, FloorplanGeometryRenderer } from '@pascal-app/viewer/ui'
|
|
12
14
|
import { memo, useMemo } from 'react'
|
|
13
|
-
import { formatLinearMeasurement } from '../../../lib/measurements'
|
|
14
15
|
import usePlacementPreview from '../../../store/use-placement-preview'
|
|
16
|
+
import { usePlacementTyping } from '../../../store/use-placement-typing'
|
|
15
17
|
import { useFloorplanRender, useFloorplanSceneRotation } from '../floorplan-render-context'
|
|
16
|
-
import {
|
|
17
|
-
import { FloorplanGeometryRenderer } from './floorplan-geometry-renderer'
|
|
18
|
+
import { FloorplanPlacementTypingHud } from './floorplan-placement-typing-hud'
|
|
18
19
|
|
|
19
20
|
export interface FloorplanNodePreviewProps {
|
|
20
21
|
node: AnyNode
|
|
@@ -139,14 +140,24 @@ export const FloorplanPlacementPreviewLayer = memo(function FloorplanPlacementPr
|
|
|
139
140
|
const dimensions = usePlacementPreview((s) => s.dimensions)
|
|
140
141
|
const activeDimensionId = usePlacementPreview((s) => s.activeDimensionId)
|
|
141
142
|
const dimensionInput = usePlacementPreview((s) => s.dimensionInput)
|
|
143
|
+
const typingProjectedPosition = usePlacementTyping((s) => s.projectedPosition)
|
|
142
144
|
const unit = useViewer((s) => s.unit)
|
|
143
145
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
144
146
|
const sceneRotationDeg = useFloorplanSceneRotation()
|
|
147
|
+
const unitsPerPixel = useFloorplanRender()?.unitsPerPixel ?? 1
|
|
145
148
|
if (!node) return null
|
|
146
149
|
|
|
147
150
|
return (
|
|
148
151
|
<g data-floorplan-placement-preview>
|
|
149
152
|
<FloorplanNodePreview contextNodes={contextNodes} node={node} parentNode={parentNode} />
|
|
153
|
+
{typingProjectedPosition ? (
|
|
154
|
+
<FloorplanPlacementTypingHud
|
|
155
|
+
sceneRotationDeg={sceneRotationDeg}
|
|
156
|
+
unitsPerPixel={unitsPerPixel}
|
|
157
|
+
x={typingProjectedPosition[0]}
|
|
158
|
+
y={typingProjectedPosition[2]}
|
|
159
|
+
/>
|
|
160
|
+
) : null}
|
|
150
161
|
<g data-floorplan-placement-dimensions>
|
|
151
162
|
{dimensions
|
|
152
163
|
.filter((dimension) => dimension.renderInFloorplan !== false)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { afterEach, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
PLACEMENT_TYPING_LENGTH_ANGLE,
|
|
4
|
+
usePlacementTyping,
|
|
5
|
+
} from '../../../store/use-placement-typing'
|
|
6
|
+
import { renderCurrentMarkup } from '../../../test-utils/render-current-markup'
|
|
7
|
+
import { FloorplanPlacementTypingHud } from './floorplan-placement-typing-hud'
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
usePlacementTyping.getState().clear()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('sizes and counter-rotates the HUD from the props it is given', () => {
|
|
14
|
+
usePlacementTyping.getState().begin({ labels: PLACEMENT_TYPING_LENGTH_ANGLE, activeField: 0 })
|
|
15
|
+
|
|
16
|
+
const markup = renderCurrentMarkup(
|
|
17
|
+
<svg>
|
|
18
|
+
<FloorplanPlacementTypingHud sceneRotationDeg={30} unitsPerPixel={0.02} x={1} y={2} />
|
|
19
|
+
</svg>,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
expect(markup).toContain('transform="translate(1 2) rotate(-30)"')
|
|
23
|
+
expect(markup).toMatch(/<foreignObject[^>]*width="4.72"/)
|
|
24
|
+
expect(markup).toContain('scale(0.02)')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('renders nothing without a typing session', () => {
|
|
28
|
+
const markup = renderCurrentMarkup(
|
|
29
|
+
<svg>
|
|
30
|
+
<FloorplanPlacementTypingHud sceneRotationDeg={0} unitsPerPixel={0.02} x={0} y={0} />
|
|
31
|
+
</svg>,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
expect(markup).not.toContain('foreignObject')
|
|
35
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { usePlacementTyping } from '../../../store/use-placement-typing'
|
|
4
|
+
import { PlacementCoordinateInput } from '../../tools/shared/placement-coordinate-input'
|
|
5
|
+
|
|
6
|
+
const HUD_WIDTH_PX = 236
|
|
7
|
+
const HUD_HEIGHT_PX = 64
|
|
8
|
+
const HUD_LIFT_PX = 14
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The typed-entry HUD placed in the plan at `x`/`y` (level-local metres).
|
|
12
|
+
* HTML inside a `foreignObject` is laid out in CSS pixels equal to SVG user
|
|
13
|
+
* units, and the plan's `<g>` is zoomed and rotated, so the box is scaled by
|
|
14
|
+
* `unitsPerPixel` and counter-rotated to stay screen-sized and upright. Both
|
|
15
|
+
* come from the caller: the wall draft renders outside `FloorplanRenderProvider`.
|
|
16
|
+
* It sits just above its anchor, and only the HUD itself takes the pointer, so
|
|
17
|
+
* clicks on the ghost or the draft under it still land on the plan.
|
|
18
|
+
*/
|
|
19
|
+
export function FloorplanPlacementTypingHud({
|
|
20
|
+
placeholders,
|
|
21
|
+
sceneRotationDeg,
|
|
22
|
+
unitsPerPixel,
|
|
23
|
+
x,
|
|
24
|
+
y,
|
|
25
|
+
}: {
|
|
26
|
+
placeholders?: readonly string[]
|
|
27
|
+
sceneRotationDeg: number
|
|
28
|
+
unitsPerPixel: number
|
|
29
|
+
x: number
|
|
30
|
+
y: number
|
|
31
|
+
}) {
|
|
32
|
+
const isActive = usePlacementTyping((state) => state.isActive)
|
|
33
|
+
const scale = Math.max(unitsPerPixel, 1e-6)
|
|
34
|
+
if (!isActive) return null
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<g transform={`translate(${x} ${y}) rotate(${-sceneRotationDeg})`}>
|
|
38
|
+
<foreignObject
|
|
39
|
+
height={HUD_HEIGHT_PX * scale}
|
|
40
|
+
overflow="visible"
|
|
41
|
+
pointerEvents="none"
|
|
42
|
+
style={{ overflow: 'visible' }}
|
|
43
|
+
width={HUD_WIDTH_PX * scale}
|
|
44
|
+
x={(-HUD_WIDTH_PX / 2) * scale}
|
|
45
|
+
y={-(HUD_HEIGHT_PX + HUD_LIFT_PX) * scale}
|
|
46
|
+
>
|
|
47
|
+
<div
|
|
48
|
+
style={{
|
|
49
|
+
alignItems: 'flex-end',
|
|
50
|
+
display: 'flex',
|
|
51
|
+
height: HUD_HEIGHT_PX,
|
|
52
|
+
justifyContent: 'center',
|
|
53
|
+
overflow: 'visible',
|
|
54
|
+
pointerEvents: 'none',
|
|
55
|
+
transform: `scale(${scale})`,
|
|
56
|
+
transformOrigin: 'top left',
|
|
57
|
+
width: HUD_WIDTH_PX,
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<PlacementCoordinateInput placeholders={placeholders} />
|
|
61
|
+
</div>
|
|
62
|
+
</foreignObject>
|
|
63
|
+
</g>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -7,15 +7,19 @@ import type {
|
|
|
7
7
|
FloorplanPalette,
|
|
8
8
|
LiveNodeOverrides,
|
|
9
9
|
} from '@pascal-app/core'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type AnyNodeDefinition,
|
|
12
|
+
emitter,
|
|
13
|
+
floorplanGeometryMetadata,
|
|
14
|
+
nodeRegistry,
|
|
15
|
+
registerNode,
|
|
16
|
+
} from '@pascal-app/core'
|
|
11
17
|
import { createElement } from 'react'
|
|
12
18
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
13
19
|
import { z } from 'zod'
|
|
14
|
-
import {
|
|
15
|
-
FLOORPLAN_NODE_EXTENSION_KEY,
|
|
16
|
-
floorplanGeometryMetadata,
|
|
17
|
-
} from '../../../lib/floorplan/floorplan-extension'
|
|
20
|
+
import { AXIS_MOVE_DRAG_LABEL, PATH_POINT_DRAG_LABEL } from '../../../lib/contextual-help'
|
|
18
21
|
import type { SessionWrites } from '../../../lib/session-writes'
|
|
22
|
+
import { snapContextOf } from '../../../lib/snapping-mode'
|
|
19
23
|
import {
|
|
20
24
|
buildFloorplanEntryGeometry,
|
|
21
25
|
cancelFloorplanAffordanceDrag,
|
|
@@ -276,6 +280,26 @@ describe('floorplan affordance ownership', () => {
|
|
|
276
280
|
driver: 'floorplan',
|
|
277
281
|
})
|
|
278
282
|
})
|
|
283
|
+
|
|
284
|
+
// With no scope, Select mode reads every snap mode as off, so these drags
|
|
285
|
+
// never snapped to the grid on the plan.
|
|
286
|
+
test('gives the MEP run point and segment drags a handle-drag scope in the run snap context', () => {
|
|
287
|
+
const point = floorplanAffordanceReshapeScope('move-path-point', 'duct_1', { pointIndex: 0 })
|
|
288
|
+
const segment = floorplanAffordanceReshapeScope('move-segment', 'duct_1', { segmentIndex: 0 })
|
|
289
|
+
expect(point).toEqual({ kind: 'handle-drag', nodeId: 'duct_1', handle: PATH_POINT_DRAG_LABEL })
|
|
290
|
+
expect(segment).toEqual({ kind: 'handle-drag', nodeId: 'duct_1', handle: AXIS_MOVE_DRAG_LABEL })
|
|
291
|
+
for (const scope of [point, segment]) {
|
|
292
|
+
expect(
|
|
293
|
+
snapContextOf({
|
|
294
|
+
scope: scope!,
|
|
295
|
+
mode: 'select',
|
|
296
|
+
tool: null,
|
|
297
|
+
profileOf: () => undefined,
|
|
298
|
+
profileOfNode: () => 'structural',
|
|
299
|
+
}),
|
|
300
|
+
).toBe('polygon')
|
|
301
|
+
}
|
|
302
|
+
})
|
|
279
303
|
})
|
|
280
304
|
|
|
281
305
|
function cabinetRun(id: string, children: string[] = [], parentId: string | null = 'level_test') {
|
|
@@ -754,11 +778,7 @@ describe('collectFloorplanLinkedLevelNodes', () => {
|
|
|
754
778
|
category: 'structure',
|
|
755
779
|
defaults: () => ({}) as never,
|
|
756
780
|
floorplan: () => null,
|
|
757
|
-
|
|
758
|
-
[FLOORPLAN_NODE_EXTENSION_KEY]: {
|
|
759
|
-
linkedLevelIds: () => ['level_upper' as AnyNodeId],
|
|
760
|
-
},
|
|
761
|
-
},
|
|
781
|
+
floorplanLinkedLevelIds: () => ['level_upper' as AnyNodeId],
|
|
762
782
|
} as unknown as AnyNodeDefinition)
|
|
763
783
|
const child = {
|
|
764
784
|
id: 'linked_child',
|
|
@@ -4,15 +4,22 @@ import {
|
|
|
4
4
|
type AnyNode,
|
|
5
5
|
type AnyNodeDefinition,
|
|
6
6
|
type AnyNodeId,
|
|
7
|
+
buildFloorplanContext,
|
|
8
|
+
createFloorplanContextExtensions,
|
|
7
9
|
createSceneApi,
|
|
8
10
|
emitter,
|
|
9
11
|
type FloorplanAffordanceSession,
|
|
12
|
+
type FloorplanAnnotationRole,
|
|
10
13
|
type FloorplanGeometry,
|
|
11
14
|
type FloorplanPalette,
|
|
12
15
|
type FloorplanPoint,
|
|
13
16
|
type FloorplanScope,
|
|
17
|
+
type FloorplanWallDimensionFaces,
|
|
18
|
+
floorplanAnnotationObstacleMode,
|
|
19
|
+
floorplanLayerRank,
|
|
14
20
|
type GeometryContext,
|
|
15
21
|
hidesDescendants,
|
|
22
|
+
isFloorplanAnnotationObstacleGeometry,
|
|
16
23
|
isNodeKindEnabled,
|
|
17
24
|
isRegistryMovable,
|
|
18
25
|
kindsWithFloorplanScope,
|
|
@@ -20,6 +27,7 @@ import {
|
|
|
20
27
|
type LiveTransform,
|
|
21
28
|
nodeRegistry,
|
|
22
29
|
pauseSceneHistory,
|
|
30
|
+
readFloorplanGeometryMetadata,
|
|
23
31
|
resolveBuildingForLevel,
|
|
24
32
|
resolveSelectionProxyId,
|
|
25
33
|
resumeSceneHistory,
|
|
@@ -28,6 +36,10 @@ import {
|
|
|
28
36
|
useLiveNodeOverrides,
|
|
29
37
|
useLiveTransforms,
|
|
30
38
|
useScene,
|
|
39
|
+
WALL_PULL_AFFORDANCE,
|
|
40
|
+
WALL_PUSH_AFFORDANCE,
|
|
41
|
+
type WallPullGripPayload,
|
|
42
|
+
withFloorplanGeometryMetadata,
|
|
31
43
|
} from '@pascal-app/core'
|
|
32
44
|
import {
|
|
33
45
|
beginPerfAction,
|
|
@@ -37,6 +49,14 @@ import {
|
|
|
37
49
|
useRevealWaiting,
|
|
38
50
|
useViewer,
|
|
39
51
|
} from '@pascal-app/viewer'
|
|
52
|
+
import {
|
|
53
|
+
FloorplanDimensionRenderer,
|
|
54
|
+
FloorplanGeometryRenderer,
|
|
55
|
+
resolveFloorplanAnnotationUpdate,
|
|
56
|
+
resolveFloorplanLabelAngle,
|
|
57
|
+
selectionModifiersFromEvent,
|
|
58
|
+
updateSvgFloorplanLabelOrientations,
|
|
59
|
+
} from '@pascal-app/viewer/ui'
|
|
40
60
|
import {
|
|
41
61
|
type ComponentProps,
|
|
42
62
|
memo,
|
|
@@ -55,11 +75,14 @@ import {
|
|
|
55
75
|
hoverRoomFromHit,
|
|
56
76
|
resolvePlanRoomHit,
|
|
57
77
|
roomPickingEnabled,
|
|
58
|
-
selectionModifiersFromEvent,
|
|
59
78
|
useRoomRecords,
|
|
60
79
|
} from '../../../hooks/use-selected-room'
|
|
61
80
|
import { useCeilingEditSession } from '../../../lib/ceiling-edit-session'
|
|
62
|
-
import {
|
|
81
|
+
import {
|
|
82
|
+
AXIS_MOVE_DRAG_LABEL,
|
|
83
|
+
PATH_POINT_DRAG_LABEL,
|
|
84
|
+
ROTATE_HANDLE_DRAG_LABEL,
|
|
85
|
+
} from '../../../lib/contextual-help'
|
|
63
86
|
import {
|
|
64
87
|
canDirectRotateNode,
|
|
65
88
|
resolveDirectManipulationNode,
|
|
@@ -75,22 +98,11 @@ import {
|
|
|
75
98
|
filterFloorplanAnnotationGeometry,
|
|
76
99
|
} from '../../../lib/floorplan/annotation-visibility'
|
|
77
100
|
import { resolveNodeForDrawingType } from '../../../lib/floorplan/drawing-coordination'
|
|
78
|
-
import {
|
|
79
|
-
createFloorplanContextExtensions,
|
|
80
|
-
type FloorplanAnnotationRole,
|
|
81
|
-
type FloorplanWallDimensionFaces,
|
|
82
|
-
getFloorplanNodeExtension,
|
|
83
|
-
readFloorplanGeometryMetadata,
|
|
84
|
-
withFloorplanGeometryMetadata,
|
|
85
|
-
} from '../../../lib/floorplan/floorplan-extension'
|
|
101
|
+
import { getFloorplanNodeExtension } from '../../../lib/floorplan/floorplan-extension'
|
|
86
102
|
import {
|
|
87
103
|
type FloorplanMode,
|
|
88
104
|
resolveFloorplanAnnotationVisibility,
|
|
89
105
|
} from '../../../lib/floorplan/floorplan-mode'
|
|
90
|
-
import {
|
|
91
|
-
buildFloorplanContext,
|
|
92
|
-
floorplanLayerRank,
|
|
93
|
-
} from '../../../lib/floorplan/floorplan-readonly'
|
|
94
106
|
import { clientToPlan } from '../../../lib/floorplan/plan-coords'
|
|
95
107
|
import {
|
|
96
108
|
type ActiveInteractionScope,
|
|
@@ -102,7 +114,7 @@ import {
|
|
|
102
114
|
isIdle,
|
|
103
115
|
tangentReshapeScope,
|
|
104
116
|
} from '../../../lib/interaction/scope'
|
|
105
|
-
import { runFloorplanWallPush
|
|
117
|
+
import { runFloorplanWallPush } from '../../../lib/room-handle-drag'
|
|
106
118
|
import { selectRoom, selectRoomFromHit } from '../../../lib/room-selection-commands'
|
|
107
119
|
import { clickZoneArea, hoverZoneArea } from '../../../lib/room-zone-routing'
|
|
108
120
|
import {
|
|
@@ -114,11 +126,7 @@ import { createSessionWrites, type SessionWrites } from '../../../lib/session-wr
|
|
|
114
126
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
115
127
|
import { clearSurfacePlanSnapFeedback } from '../../../lib/surface-plan-snap'
|
|
116
128
|
import { paintZoneMembership } from '../../../lib/units'
|
|
117
|
-
import {
|
|
118
|
-
startWallPull,
|
|
119
|
-
WALL_PULL_AFFORDANCE,
|
|
120
|
-
type WallPullGripPayload,
|
|
121
|
-
} from '../../../lib/wall-pull'
|
|
129
|
+
import { startWallPull } from '../../../lib/wall-pull'
|
|
122
130
|
import useDirectManipulationFeedback from '../../../store/use-direct-manipulation-feedback'
|
|
123
131
|
import useDrawingView from '../../../store/use-drawing-view'
|
|
124
132
|
import useEditor, { isAngleSnapActive, type Mode } from '../../../store/use-editor'
|
|
@@ -153,23 +161,11 @@ import {
|
|
|
153
161
|
useFloorplanStaticRender,
|
|
154
162
|
useFloorplanStaticUnitsPerPixel,
|
|
155
163
|
} from '../floorplan-render-context'
|
|
156
|
-
import {
|
|
157
|
-
floorplanAnnotationObstacleMode,
|
|
158
|
-
isFloorplanAnnotationObstacleGeometry,
|
|
159
|
-
resolveSvgAnnotationCollisions,
|
|
160
|
-
svgAnnotationLabelId,
|
|
161
|
-
} from './floorplan-annotation-layout'
|
|
164
|
+
import { resolveSvgAnnotationCollisions, svgAnnotationLabelId } from './floorplan-annotation-layout'
|
|
162
165
|
import {
|
|
163
166
|
FloorplanDimensionEditOverlay,
|
|
164
167
|
typeableDimensionAt,
|
|
165
168
|
} from './floorplan-dimension-edit-overlay'
|
|
166
|
-
import { FloorplanDimensionRenderer } from './floorplan-dimension-renderer'
|
|
167
|
-
import { FloorplanGeometryRenderer } from './floorplan-geometry-renderer'
|
|
168
|
-
import {
|
|
169
|
-
resolveFloorplanAnnotationUpdate,
|
|
170
|
-
resolveFloorplanLabelAngle,
|
|
171
|
-
updateSvgFloorplanLabelOrientations,
|
|
172
|
-
} from './floorplan-label-angle'
|
|
173
169
|
import { useFloorplanReveal } from './floorplan-reveal'
|
|
174
170
|
|
|
175
171
|
/**
|
|
@@ -321,7 +317,7 @@ export function subscribeFloorplanAffordanceToolCancel(
|
|
|
321
317
|
// edits that set a direction/shape map to a scope; resize / rotate / body-move
|
|
322
318
|
// affordances return `null` (no polygon/wall snapping chip). Keyed off the
|
|
323
319
|
// affordance name the kinds register (`move-vertex` / `move-edge` / `add-vertex`
|
|
324
|
-
// / `curve` / `move-endpoint`).
|
|
320
|
+
// / `curve` / `move-endpoint` / `move-path-point` / `move-segment`).
|
|
325
321
|
export function floorplanAffordanceReshapeScope(
|
|
326
322
|
affordance: string,
|
|
327
323
|
nodeId: string,
|
|
@@ -362,6 +358,14 @@ export function floorplanAffordanceReshapeScope(
|
|
|
362
358
|
if (affordance.includes('rotate')) {
|
|
363
359
|
return { kind: 'handle-drag', nodeId, handle: ROTATE_HANDLE_DRAG_LABEL }
|
|
364
360
|
}
|
|
361
|
+
// MEP run point and run segment drags: the same handle-drag scopes as their
|
|
362
|
+
// 3D arrows, so they snap in the kind's own context (polygon for runs).
|
|
363
|
+
if (affordance === 'move-path-point') {
|
|
364
|
+
return { kind: 'handle-drag', nodeId, handle: PATH_POINT_DRAG_LABEL }
|
|
365
|
+
}
|
|
366
|
+
if (affordance === 'move-segment') {
|
|
367
|
+
return { kind: 'handle-drag', nodeId, handle: AXIS_MOVE_DRAG_LABEL }
|
|
368
|
+
}
|
|
365
369
|
return null
|
|
366
370
|
}
|
|
367
371
|
|
|
@@ -3633,7 +3637,7 @@ export function collectFloorplanLinkedLevelNodes(
|
|
|
3633
3637
|
for (const [rawId, node] of Object.entries(nodes)) {
|
|
3634
3638
|
if (!node) continue
|
|
3635
3639
|
const definition = nodeRegistry.get(node.type)
|
|
3636
|
-
const linkedLevelIds =
|
|
3640
|
+
const linkedLevelIds = definition?.floorplanLinkedLevelIds
|
|
3637
3641
|
if (!definition?.floorplan || !linkedLevelIds) continue
|
|
3638
3642
|
const id = rawId as AnyNodeId
|
|
3639
3643
|
if (excludedIds.has(id)) continue
|