@pascal-app/editor 0.9.1 → 1.0.0-beta.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 +14 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
- package/src/components/editor/bake-exporter.tsx +49 -0
- package/src/components/editor/bake-thumbnail.tsx +95 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
- package/src/components/editor/custom-camera-controls.tsx +332 -199
- package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
- package/src/components/editor/editor-layout-mobile.tsx +5 -0
- package/src/components/editor/editor-layout-v2.tsx +18 -0
- package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
- package/src/components/editor/export-manager.tsx +71 -63
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +102 -5
- package/src/components/editor/first-person/build-collider-world.ts +52 -5
- package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
- package/src/components/editor/first-person/terrain-collider.ts +212 -0
- package/src/components/editor/first-person-controls.tsx +410 -136
- package/src/components/editor/floating-action-menu.tsx +435 -64
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-background-selection.test.ts +72 -0
- package/src/components/editor/floorplan-background-selection.ts +7 -20
- package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
- package/src/components/editor/floorplan-camera-sync.ts +213 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
- package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
- package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
- package/src/components/editor/floorplan-panel.tsx +2517 -1492
- package/src/components/editor/grid.tsx +166 -39
- package/src/components/editor/group-actions.ts +597 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +102 -64
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +192 -1
- package/src/components/editor/group-transform-shared.ts +240 -7
- package/src/components/editor/handles/handle-arrow.tsx +81 -23
- package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
- package/src/components/editor/handles/handle-drag-history.ts +14 -0
- package/src/components/editor/handles/preview-overrides.test.ts +26 -0
- package/src/components/editor/handles/preview-overrides.ts +13 -0
- package/src/components/editor/handles/resize-snap.test.ts +52 -0
- package/src/components/editor/handles/resize-snap.ts +23 -0
- package/src/components/editor/handles/use-handle-drag.ts +49 -6
- package/src/components/editor/index.tsx +289 -35
- package/src/components/editor/measurement-pill.tsx +68 -23
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +409 -180
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +76 -0
- package/src/components/editor/quick-measurement-hud.tsx +34 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +814 -466
- package/src/components/editor/site-edge-labels.tsx +28 -6
- package/src/components/editor/slab-hole-highlights.tsx +13 -5
- package/src/components/editor/snapshot-capture-overlay.tsx +258 -115
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +118 -253
- package/src/components/editor/use-floorplan-background-placement.ts +113 -45
- package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
- package/src/components/editor/use-floorplan-scene-data.ts +5 -5
- package/src/components/editor/use-mesh-settle-epoch.ts +27 -0
- package/src/components/editor/wall-measurement-label.tsx +10 -28
- package/src/components/editor/wall-move-side-handles.tsx +229 -39
- package/src/components/editor/wall-opening-highlights.tsx +63 -32
- package/src/components/editor/wall-snap-beacon-layer.tsx +162 -6
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +40 -9
- package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +744 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1672 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +219 -20
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +327 -59
- package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +89 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +106 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +94 -26
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +587 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +2280 -578
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +425 -61
- package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
- package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-tool.tsx +34 -8
- package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
- package/src/components/tools/fence/fence-drafting.ts +107 -8
- package/src/components/tools/item/move-tool.tsx +17 -13
- package/src/components/tools/item/placement-math.test.ts +13 -1
- package/src/components/tools/item/placement-math.ts +28 -2
- package/src/components/tools/item/placement-strategies.ts +246 -5
- package/src/components/tools/item/placement-types.ts +13 -1
- package/src/components/tools/item/use-draft-node.ts +146 -57
- package/src/components/tools/item/use-placement-coordinator.tsx +813 -125
- package/src/components/tools/registry/move-registry-node-tool.tsx +636 -103
- package/src/components/tools/roof/roof-tool.tsx +336 -94
- package/src/components/tools/select/box-select-state.ts +18 -7
- package/src/components/tools/select/box-select-tool.tsx +176 -40
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
- package/src/components/tools/select/select-candidates.ts +1 -1
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/floor-stack-preview.ts +4 -0
- package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +183 -1
- package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
- package/src/components/tools/shared/pointer-support-cap.ts +273 -0
- package/src/components/tools/shared/polygon-editor.tsx +276 -35
- package/src/components/tools/site/site-boundary-editor.tsx +101 -42
- package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
- package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
- package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
- package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
- package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
- package/src/components/tools/stair/stair-click-guard.ts +72 -0
- package/src/components/tools/stair/stair-defaults.ts +1 -0
- package/src/components/tools/stair/stair-tool.tsx +164 -42
- package/src/components/tools/tool-manager.tsx +149 -30
- package/src/components/tools/wall/wall-drafting.test.ts +447 -0
- package/src/components/tools/wall/wall-drafting.ts +304 -108
- package/src/components/tools/wall/wall-snap-geometry.test.ts +52 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -9
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +98 -88
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +44 -94
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +2 -4
- package/src/components/ui/action-menu/measurement-control.tsx +267 -0
- package/src/components/ui/action-menu/structure-tools.tsx +22 -13
- package/src/components/ui/action-menu/view-toggles.tsx +42 -90
- package/src/components/ui/command-palette/editor-commands.tsx +16 -3
- package/src/components/ui/command-palette/index.tsx +4 -3
- package/src/components/ui/controls/material-paint-panel.tsx +93 -18
- package/src/components/ui/controls/material-picker.tsx +162 -152
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +118 -44
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
- package/src/components/ui/floating-level-selector.tsx +74 -20
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +273 -14
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
- package/src/components/ui/panels/node-display.ts +17 -17
- package/src/components/ui/panels/panel-manager.tsx +36 -14
- package/src/components/ui/panels/panel-wrapper.tsx +24 -3
- package/src/components/ui/panels/parametric-inspector.tsx +19 -4
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
- package/src/components/ui/panels/selection-breakdown.ts +20 -0
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +82 -41
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +76 -6
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -17
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +42 -28
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer/viewer-controls-bar.tsx +466 -0
- package/src/components/viewer/viewer-scene-header.tsx +235 -0
- package/src/components/viewer-overlay.tsx +36 -596
- package/src/components/viewer-zone-system.tsx +6 -1
- package/src/components/walkthrough-hud.tsx +111 -0
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +54 -7
- package/src/hooks/use-ceiling-events.ts +3 -2
- package/src/hooks/use-drag-action.ts +4 -1
- package/src/hooks/use-grid-events.ts +24 -0
- package/src/hooks/use-keyboard.ts +438 -70
- package/src/index.tsx +355 -9
- package/src/lib/active-placement-surface.test.ts +24 -0
- package/src/lib/active-placement-surface.ts +42 -0
- package/src/lib/camera-pose.test.ts +237 -0
- package/src/lib/camera-pose.ts +172 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +275 -0
- package/src/lib/direct-manipulation.ts +118 -0
- package/src/lib/door-interaction.ts +1 -1
- package/src/lib/editor-api.ts +23 -35
- package/src/lib/elevation-guides.test.ts +107 -0
- package/src/lib/elevation-guides.ts +236 -0
- package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
- package/src/lib/floorplan/annotation-visibility.ts +133 -0
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +18 -12
- package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
- package/src/lib/floorplan/drawing-coordination.ts +13 -0
- package/src/lib/floorplan/floorplan-export.test.ts +319 -0
- package/src/lib/floorplan/floorplan-export.tsx +978 -0
- package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
- package/src/lib/floorplan/floorplan-extension.ts +181 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
- package/src/lib/floorplan/floorplan-mode.ts +73 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
- package/src/lib/floorplan/geometry.ts +52 -0
- package/src/lib/floorplan/index.ts +3 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/fresh-planar-placement.test.ts +283 -3
- package/src/lib/fresh-planar-placement.ts +85 -1
- package/src/lib/glb-export.test.ts +556 -0
- package/src/lib/glb-export.ts +1071 -0
- package/src/lib/ground-surface.ts +95 -0
- package/src/lib/history.test.ts +96 -0
- package/src/lib/history.ts +31 -0
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +52 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +243 -0
- package/src/lib/level-duplication.test.ts +2 -1
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +36 -49
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +188 -0
- package/src/lib/measurements.ts +210 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/planar-cursor-placement.test.ts +57 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-action-feedback.ts +33 -0
- package/src/lib/quick-action-nodes.test.ts +94 -0
- package/src/lib/quick-action-nodes.ts +47 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +6 -6
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +325 -0
- package/src/lib/scene-clipboard.ts +310 -21
- package/src/lib/scene.ts +28 -5
- package/src/lib/selection-routing.test.ts +313 -0
- package/src/lib/selection-routing.ts +180 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +82 -14
- package/src/lib/sfx-player.test.ts +266 -0
- package/src/lib/sfx-player.ts +262 -69
- package/src/lib/site-boundary.test.ts +53 -0
- package/src/lib/site-boundary.ts +27 -0
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +165 -0
- package/src/lib/snapping-mode.ts +191 -0
- package/src/lib/stair-duplication.test.ts +103 -0
- package/src/lib/stair-duplication.ts +58 -86
- package/src/lib/stair-levels.ts +2 -0
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/terrain-sculpt.test.ts +429 -0
- package/src/lib/terrain-sculpt.ts +301 -0
- package/src/lib/terrain-verb-color.test.ts +51 -0
- package/src/lib/terrain-verb-color.ts +58 -0
- package/src/lib/touch-gesture-priority.test.ts +45 -0
- package/src/lib/touch-gesture-priority.ts +30 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/window-interaction.ts +1 -1
- package/src/lib/world-grid-snap.ts +37 -5
- package/src/store/camera-pose-store.test.ts +29 -0
- package/src/store/camera-pose-store.ts +27 -0
- package/src/store/live-draft-preview-stores.test.ts +136 -0
- package/src/store/navigation-sync-pose-store.test.ts +34 -0
- package/src/store/navigation-sync-pose-store.ts +27 -0
- package/src/store/terrain-sculpt-mode.test.ts +223 -0
- package/src/store/use-delete-confirmation.ts +27 -0
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-drawing-view.test.ts +23 -0
- package/src/store/use-drawing-view.ts +84 -0
- package/src/store/use-editor.tsx +665 -126
- package/src/store/use-elevation-guides.ts +40 -0
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +51 -0
- package/src/store/use-first-person-hud.ts +40 -0
- package/src/store/use-floorplan-annotation-visibility.ts +60 -0
- package/src/store/use-floorplan-draft-preview.ts +151 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-floorplan-mode.ts +110 -0
- package/src/store/use-interaction-scope.test.ts +206 -0
- package/src/store/use-interaction-scope.ts +140 -0
- package/src/store/use-measurement-draft.test.ts +559 -0
- package/src/store/use-measurement-draft.ts +570 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-path-draft-preview.ts +90 -0
- package/src/store/use-placement-preview.ts +11 -3
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +53 -0
- package/src/store/use-wall-snap-indicator.ts +2 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/group-move-handle.tsx +0 -316
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/lib/floorplan/selection-tool.ts +0 -271
- package/src/lib/level-name.ts +0 -11
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type AnyNode,
|
|
2
3
|
type AnyNodeId,
|
|
3
|
-
generateId,
|
|
4
4
|
type StairNode,
|
|
5
|
-
StairNode as StairNodeSchema,
|
|
6
5
|
type StairSegmentNode,
|
|
7
|
-
StairSegmentNode as StairSegmentNodeSchema,
|
|
8
|
-
sceneRegistry,
|
|
9
6
|
useScene,
|
|
10
7
|
} from '@pascal-app/core'
|
|
11
8
|
import { useViewer } from '@pascal-app/viewer'
|
|
12
9
|
import useEditor from '../store/use-editor'
|
|
10
|
+
import { commitFreshPlacementSubtree, createFreshPlacementSubtree } from './fresh-planar-placement'
|
|
13
11
|
|
|
14
12
|
type DuplicateStairOptions = {
|
|
15
13
|
mode?: 'select' | 'move'
|
|
@@ -22,49 +20,21 @@ type DuplicateStairResult = {
|
|
|
22
20
|
segmentIds: StairSegmentNode['id'][]
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const nextMeta = { ...(metadata as Record<string, unknown>) }
|
|
33
|
-
delete nextMeta.isNew
|
|
34
|
-
delete nextMeta.isTransient
|
|
35
|
-
return nextMeta
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function moveStairWhenRegistered(stairId: StairNode['id'], attempt = 0) {
|
|
39
|
-
const latestStair = useScene.getState().nodes[stairId as AnyNodeId]
|
|
40
|
-
if (!latestStair || latestStair.type !== 'stair') {
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (sceneRegistry.nodes.has(stairId)) {
|
|
45
|
-
useEditor.getState().setMovingNode(latestStair)
|
|
46
|
-
useViewer.getState().setSelection({ selectedIds: [] })
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (attempt >= MOVE_REGISTRY_RETRY_LIMIT) {
|
|
51
|
-
console.warn(`Duplicated stair "${stairId}" did not register before move mode started`)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
requestAnimationFrame(() => moveStairWhenRegistered(stairId, attempt + 1))
|
|
56
|
-
}
|
|
57
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Duplicates a stair through the shared fresh-subtree placement path.
|
|
25
|
+
*
|
|
26
|
+
* The draft passed to the move tool is the exact node stored in the scene,
|
|
27
|
+
* so its geometry, descendants, selection identity, and eventual commit all
|
|
28
|
+
* use one fresh ID graph.
|
|
29
|
+
*/
|
|
58
30
|
export function duplicateStairSubtree(
|
|
59
31
|
sourceStairId: AnyNodeId,
|
|
60
32
|
options: DuplicateStairOptions = {},
|
|
61
33
|
): DuplicateStairResult {
|
|
62
34
|
const { mode = 'move', offset = [1, 0, 1], parentId: explicitParentId } = options
|
|
35
|
+
const sourceStair = useScene.getState().nodes[sourceStairId]
|
|
63
36
|
|
|
64
|
-
|
|
65
|
-
const sourceStair = scene.nodes[sourceStairId]
|
|
66
|
-
|
|
67
|
-
if (!sourceStair || sourceStair.type !== 'stair') {
|
|
37
|
+
if (sourceStair?.type !== 'stair') {
|
|
68
38
|
throw new Error(`Node "${sourceStairId}" is not a stair`)
|
|
69
39
|
}
|
|
70
40
|
|
|
@@ -73,54 +43,56 @@ export function duplicateStairSubtree(
|
|
|
73
43
|
throw new Error(`Stair "${sourceStairId}" is missing a parent level`)
|
|
74
44
|
}
|
|
75
45
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
continue
|
|
46
|
+
const temporal = useScene.temporal.getState()
|
|
47
|
+
const wasTracking = (temporal as { isTracking?: boolean }).isTracking !== false
|
|
48
|
+
if (wasTracking) temporal.pause()
|
|
49
|
+
|
|
50
|
+
let draftId: AnyNodeId | null = null
|
|
51
|
+
try {
|
|
52
|
+
draftId = createFreshPlacementSubtree(sourceStairId, {
|
|
53
|
+
parentId,
|
|
54
|
+
position: [
|
|
55
|
+
sourceStair.position[0] + offset[0],
|
|
56
|
+
sourceStair.position[1] + offset[1],
|
|
57
|
+
sourceStair.position[2] + offset[2],
|
|
58
|
+
],
|
|
59
|
+
} as Partial<AnyNode>)
|
|
60
|
+
const draft = draftId ? useScene.getState().nodes[draftId] : null
|
|
61
|
+
if (draft?.type !== 'stair') {
|
|
62
|
+
throw new Error(`Duplicated stair "${sourceStairId}" was not created`)
|
|
94
63
|
}
|
|
95
64
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const createdStair = useScene.getState().nodes[stairClone.id as AnyNodeId]
|
|
111
|
-
if (!createdStair || createdStair.type !== 'stair') {
|
|
112
|
-
throw new Error(`Duplicated stair "${stairClone.id}" was not created`)
|
|
113
|
-
}
|
|
65
|
+
if (mode === 'select') {
|
|
66
|
+
const committedId = commitFreshPlacementSubtree(draft.id as AnyNodeId, {})
|
|
67
|
+
const committed = committedId ? useScene.getState().nodes[committedId] : null
|
|
68
|
+
if (committed?.type !== 'stair') {
|
|
69
|
+
throw new Error(`Duplicated stair "${sourceStairId}" could not be committed`)
|
|
70
|
+
}
|
|
71
|
+
if (wasTracking) temporal.resume()
|
|
72
|
+
useViewer.getState().setSelection({ selectedIds: [committed.id] })
|
|
73
|
+
return {
|
|
74
|
+
stair: committed,
|
|
75
|
+
segmentIds: stairSegmentIds(committed),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
114
78
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
79
|
+
useViewer.getState().setSelection({ selectedIds: [] })
|
|
80
|
+
useEditor.getState().setMovingNode(draft)
|
|
81
|
+
return {
|
|
82
|
+
stair: draft,
|
|
83
|
+
segmentIds: stairSegmentIds(draft),
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (draftId && useScene.getState().nodes[draftId]) {
|
|
87
|
+
useScene.getState().deleteNode(draftId)
|
|
88
|
+
}
|
|
89
|
+
if (wasTracking) temporal.resume()
|
|
90
|
+
throw error
|
|
120
91
|
}
|
|
92
|
+
}
|
|
121
93
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
94
|
+
function stairSegmentIds(stair: StairNode): StairSegmentNode['id'][] {
|
|
95
|
+
return (stair.children ?? []).filter((childId): childId is StairSegmentNode['id'] => {
|
|
96
|
+
return useScene.getState().nodes[childId as AnyNodeId]?.type === 'stair-segment'
|
|
97
|
+
})
|
|
126
98
|
}
|
package/src/lib/stair-levels.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AnyNode,
|
|
3
3
|
type AnyNodeId,
|
|
4
|
+
DEFAULT_LEVEL_HEIGHT,
|
|
4
5
|
LevelNode,
|
|
5
6
|
type LevelNode as LevelNodeType,
|
|
6
7
|
resolveBuildingForLevel,
|
|
@@ -154,6 +155,7 @@ export function resolveStairDestinationLevel({
|
|
|
154
155
|
if (createMissing && buildingId) {
|
|
155
156
|
const createdLevel = LevelNode.parse({
|
|
156
157
|
children: [],
|
|
158
|
+
height: DEFAULT_LEVEL_HEIGHT,
|
|
157
159
|
level: fromLevel.level + 1,
|
|
158
160
|
parentId: buildingId,
|
|
159
161
|
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import type { AnyNode, WallNode } from '@pascal-app/core'
|
|
3
|
+
import { resolveSurfacePlanPointSnap } from './surface-plan-snap'
|
|
4
|
+
|
|
5
|
+
const wall = (id: string, start: [number, number], end: [number, number]): WallNode =>
|
|
6
|
+
({
|
|
7
|
+
id,
|
|
8
|
+
type: 'wall',
|
|
9
|
+
start,
|
|
10
|
+
end,
|
|
11
|
+
visible: true,
|
|
12
|
+
}) as WallNode
|
|
13
|
+
|
|
14
|
+
const nodesOf = (...walls: WallNode[]): Record<string, AnyNode> =>
|
|
15
|
+
Object.fromEntries(walls.map((node) => [node.id, node as AnyNode]))
|
|
16
|
+
|
|
17
|
+
// The measurement tool passes `magnetic` explicitly so its snapping never
|
|
18
|
+
// depends on the construction snapping-mode chip (whose default 'grid' mode
|
|
19
|
+
// turns `isMagneticSnapActive()` off). These tests pin that seam.
|
|
20
|
+
describe('resolveSurfacePlanPointSnap magnetic override', () => {
|
|
21
|
+
const walls = [wall('wall-a', [0, 0], [4, 0]), wall('wall-b', [0, 0], [0, 4])]
|
|
22
|
+
const nodes = nodesOf(...walls)
|
|
23
|
+
|
|
24
|
+
test('magnetic: true acquires a shared wall corner from the full endpoint radius', () => {
|
|
25
|
+
const result = resolveSurfacePlanPointSnap({
|
|
26
|
+
rawPoint: [0.3, 0.2],
|
|
27
|
+
nodes,
|
|
28
|
+
magnetic: true,
|
|
29
|
+
align: false,
|
|
30
|
+
})
|
|
31
|
+
expect(result.point).toEqual([0, 0])
|
|
32
|
+
expect(result.wallSnap).toBe('endpoint')
|
|
33
|
+
expect(result.wallIds.sort()).toEqual(['wall-a', 'wall-b'])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('magnetic: true acquires a T-junction crossing on a wall body', () => {
|
|
37
|
+
const crossing = [wall('wall-a', [0, 0], [4, 0]), wall('wall-c', [3, -1], [3, 3])]
|
|
38
|
+
const result = resolveSurfacePlanPointSnap({
|
|
39
|
+
rawPoint: [3.05, 0.1],
|
|
40
|
+
nodes: nodesOf(...crossing),
|
|
41
|
+
magnetic: true,
|
|
42
|
+
align: false,
|
|
43
|
+
})
|
|
44
|
+
expect(result.point).toEqual([3, 0])
|
|
45
|
+
expect(result.wallSnap).toBe('intersection')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('magnetic: false keeps the fallback point outside the tight connect radius', () => {
|
|
49
|
+
const result = resolveSurfacePlanPointSnap({
|
|
50
|
+
rawPoint: [0.3, 0.2],
|
|
51
|
+
fallbackPoint: [0.3, 0.2],
|
|
52
|
+
nodes,
|
|
53
|
+
magnetic: false,
|
|
54
|
+
align: false,
|
|
55
|
+
})
|
|
56
|
+
expect(result.point).toEqual([0.3, 0.2])
|
|
57
|
+
expect(result.wallSnap).toBeNull()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('magnetic: false still sticks within the connect radius so ends can meet', () => {
|
|
61
|
+
const result = resolveSurfacePlanPointSnap({
|
|
62
|
+
rawPoint: [0.03, 0.02],
|
|
63
|
+
fallbackPoint: [0.03, 0.02],
|
|
64
|
+
nodes,
|
|
65
|
+
magnetic: false,
|
|
66
|
+
align: false,
|
|
67
|
+
})
|
|
68
|
+
expect(result.point).toEqual([0, 0])
|
|
69
|
+
expect(result.wallSnap).toBe('endpoint')
|
|
70
|
+
})
|
|
71
|
+
})
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AlignmentAnchor,
|
|
3
|
+
type AlignmentGuide,
|
|
4
|
+
type AnyNode,
|
|
5
|
+
collectAlignmentAnchors,
|
|
6
|
+
getWallCurveFrameAt,
|
|
7
|
+
getWallCurveLength,
|
|
8
|
+
isCurvedWall,
|
|
9
|
+
resolveLevelId,
|
|
10
|
+
useScene,
|
|
11
|
+
type WallNode,
|
|
12
|
+
} from '@pascal-app/core'
|
|
13
|
+
import {
|
|
14
|
+
getSegmentGridStep,
|
|
15
|
+
snapWallDraftPointDetailed,
|
|
16
|
+
WALL_CONNECT_SNAP_RADIUS,
|
|
17
|
+
type WallDraftSnapKind,
|
|
18
|
+
type WallPlanPoint,
|
|
19
|
+
type WallSnapRadii,
|
|
20
|
+
} from '../components/tools/wall/wall-drafting'
|
|
21
|
+
import useAlignmentGuides from '../store/use-alignment-guides'
|
|
22
|
+
import { isMagneticSnapActive } from '../store/use-editor'
|
|
23
|
+
import useWallSnapIndicator from '../store/use-wall-snap-indicator'
|
|
24
|
+
import { resolveAlignmentForFloorplanView } from './world-grid-snap'
|
|
25
|
+
|
|
26
|
+
const SURFACE_SNAP_MOVING_ID = '__surface_snap__'
|
|
27
|
+
export const SURFACE_ALIGNMENT_THRESHOLD_M = 0.08
|
|
28
|
+
const SURFACE_WALL_SNAP_RADII = {
|
|
29
|
+
endpoint: 0.38,
|
|
30
|
+
midpoint: 0.28,
|
|
31
|
+
intersection: 0.28,
|
|
32
|
+
wall: 0.18,
|
|
33
|
+
} satisfies WallSnapRadii
|
|
34
|
+
const WALL_SOURCE_MATCH_EPSILON = 0.035
|
|
35
|
+
|
|
36
|
+
export type SurfacePlanSnapInput = {
|
|
37
|
+
rawPoint: WallPlanPoint
|
|
38
|
+
fallbackPoint?: WallPlanPoint
|
|
39
|
+
levelId?: string | null
|
|
40
|
+
excludeId?: string | null
|
|
41
|
+
movingId?: string
|
|
42
|
+
nodes?: Readonly<Record<string, AnyNode>>
|
|
43
|
+
walls?: readonly WallNode[]
|
|
44
|
+
candidates?: readonly AlignmentAnchor[]
|
|
45
|
+
threshold?: number
|
|
46
|
+
magnetic?: boolean
|
|
47
|
+
align?: boolean
|
|
48
|
+
highlightWalls?: boolean
|
|
49
|
+
step?: number
|
|
50
|
+
snapRadii?: WallSnapRadii
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type SurfacePlanSnapResult = {
|
|
54
|
+
point: WallPlanPoint
|
|
55
|
+
wallSnap: WallDraftSnapKind | null
|
|
56
|
+
guides: AlignmentGuide[]
|
|
57
|
+
wallIds: string[]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getLevelWalls(
|
|
61
|
+
nodes: Readonly<Record<string, AnyNode>>,
|
|
62
|
+
levelId: string | null | undefined,
|
|
63
|
+
walls?: readonly WallNode[],
|
|
64
|
+
): WallNode[] {
|
|
65
|
+
const source =
|
|
66
|
+
walls ?? Object.values(nodes).filter((node): node is WallNode => node.type === 'wall')
|
|
67
|
+
if (!levelId) return source.filter((wall) => wall.visible !== false)
|
|
68
|
+
|
|
69
|
+
return source.filter(
|
|
70
|
+
(wall) =>
|
|
71
|
+
wall.visible !== false && resolveLevelId(wall, nodes as Record<string, AnyNode>) === levelId,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function distanceSquared(a: WallPlanPoint, b: WallPlanPoint) {
|
|
76
|
+
const dx = a[0] - b[0]
|
|
77
|
+
const dz = a[1] - b[1]
|
|
78
|
+
return dx * dx + dz * dz
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function wallMidpoint(wall: WallNode): WallPlanPoint {
|
|
82
|
+
if (isCurvedWall(wall)) {
|
|
83
|
+
const frame = getWallCurveFrameAt(wall, 0.5)
|
|
84
|
+
return [frame.point.x, frame.point.y]
|
|
85
|
+
}
|
|
86
|
+
return [(wall.start[0] + wall.end[0]) / 2, (wall.start[1] + wall.end[1]) / 2]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function distanceToSegmentSquared(point: WallPlanPoint, start: WallPlanPoint, end: WallPlanPoint) {
|
|
90
|
+
const dx = end[0] - start[0]
|
|
91
|
+
const dz = end[1] - start[1]
|
|
92
|
+
const lengthSquared = dx * dx + dz * dz
|
|
93
|
+
if (lengthSquared < 1e-9) return distanceSquared(point, start)
|
|
94
|
+
|
|
95
|
+
const t = Math.max(
|
|
96
|
+
0,
|
|
97
|
+
Math.min(1, ((point[0] - start[0]) * dx + (point[1] - start[1]) * dz) / lengthSquared),
|
|
98
|
+
)
|
|
99
|
+
const projected: WallPlanPoint = [start[0] + dx * t, start[1] + dz * t]
|
|
100
|
+
return distanceSquared(point, projected)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function distanceToWallSquared(point: WallPlanPoint, wall: WallNode) {
|
|
104
|
+
if (!isCurvedWall(wall)) {
|
|
105
|
+
return distanceToSegmentSquared(point, wall.start, wall.end)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const sampleCount = Math.max(8, Math.ceil(getWallCurveLength(wall) / 0.3))
|
|
109
|
+
let bestDistanceSquared = Number.POSITIVE_INFINITY
|
|
110
|
+
let previous = getWallCurveFrameAt(wall, 0).point
|
|
111
|
+
for (let index = 1; index <= sampleCount; index += 1) {
|
|
112
|
+
const current = getWallCurveFrameAt(wall, index / sampleCount).point
|
|
113
|
+
const distance = distanceToSegmentSquared(
|
|
114
|
+
point,
|
|
115
|
+
[previous.x, previous.y],
|
|
116
|
+
[current.x, current.y],
|
|
117
|
+
)
|
|
118
|
+
bestDistanceSquared = Math.min(bestDistanceSquared, distance)
|
|
119
|
+
previous = current
|
|
120
|
+
}
|
|
121
|
+
return bestDistanceSquared
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function closestWallIds(point: WallPlanPoint, walls: readonly WallNode[], count: number) {
|
|
125
|
+
return walls
|
|
126
|
+
.map((wall) => ({ id: wall.id, distance: distanceToWallSquared(point, wall) }))
|
|
127
|
+
.sort((a, b) => a.distance - b.distance)
|
|
128
|
+
.slice(0, count)
|
|
129
|
+
.map(({ id }) => id)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function findSnapSourceWallIds(
|
|
133
|
+
point: WallPlanPoint,
|
|
134
|
+
kind: WallDraftSnapKind,
|
|
135
|
+
walls: readonly WallNode[],
|
|
136
|
+
): string[] {
|
|
137
|
+
const epsilonSquared = WALL_SOURCE_MATCH_EPSILON ** 2
|
|
138
|
+
|
|
139
|
+
if (kind === 'endpoint') {
|
|
140
|
+
const endpointMatches = walls.filter(
|
|
141
|
+
(wall) =>
|
|
142
|
+
distanceSquared(point, wall.start) <= epsilonSquared ||
|
|
143
|
+
distanceSquared(point, wall.end) <= epsilonSquared,
|
|
144
|
+
)
|
|
145
|
+
if (endpointMatches.length > 0) return endpointMatches.map((wall) => wall.id)
|
|
146
|
+
return closestWallIds(point, walls, 1)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (kind === 'midpoint') {
|
|
150
|
+
const midpointMatches = walls.filter(
|
|
151
|
+
(wall) => distanceSquared(point, wallMidpoint(wall)) <= epsilonSquared,
|
|
152
|
+
)
|
|
153
|
+
if (midpointMatches.length > 0) return midpointMatches.map((wall) => wall.id)
|
|
154
|
+
return closestWallIds(point, walls, 1)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (kind === 'intersection') {
|
|
158
|
+
const crossingMatches = walls.filter(
|
|
159
|
+
(wall) => distanceToWallSquared(point, wall) <= epsilonSquared,
|
|
160
|
+
)
|
|
161
|
+
if (crossingMatches.length > 0) return crossingMatches.map((wall) => wall.id).slice(0, 2)
|
|
162
|
+
return closestWallIds(point, walls, 2)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return closestWallIds(point, walls, 1)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function clearSurfacePlanSnapFeedback() {
|
|
169
|
+
useAlignmentGuides.getState().clear()
|
|
170
|
+
useWallSnapIndicator.getState().clear()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function resolveSurfacePlanPointSnap(input: SurfacePlanSnapInput): SurfacePlanSnapResult {
|
|
174
|
+
const nodes = input.nodes ?? useScene.getState().nodes
|
|
175
|
+
const walls = getLevelWalls(nodes, input.levelId, input.walls)
|
|
176
|
+
const fallbackPoint = input.fallbackPoint
|
|
177
|
+
const magnetic = input.magnetic ?? isMagneticSnapActive()
|
|
178
|
+
|
|
179
|
+
const wallSnap = snapWallDraftPointDetailed({
|
|
180
|
+
point: input.rawPoint,
|
|
181
|
+
walls,
|
|
182
|
+
step: input.step ?? getSegmentGridStep(),
|
|
183
|
+
magnetic,
|
|
184
|
+
snapRadii: input.snapRadii ?? SURFACE_WALL_SNAP_RADII,
|
|
185
|
+
gridSnap: fallbackPoint ? () => fallbackPoint : undefined,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
if (wallSnap.snap) {
|
|
189
|
+
const wallIds =
|
|
190
|
+
input.highlightWalls === false
|
|
191
|
+
? []
|
|
192
|
+
: findSnapSourceWallIds(wallSnap.point, wallSnap.snap, walls)
|
|
193
|
+
useWallSnapIndicator.getState().set({
|
|
194
|
+
x: wallSnap.point[0],
|
|
195
|
+
z: wallSnap.point[1],
|
|
196
|
+
kind: wallSnap.snap,
|
|
197
|
+
...(wallIds.length > 0 ? { wallIds } : {}),
|
|
198
|
+
})
|
|
199
|
+
useAlignmentGuides.getState().clear()
|
|
200
|
+
return { point: wallSnap.point, wallSnap: wallSnap.snap, guides: [], wallIds }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
useWallSnapIndicator.getState().clear()
|
|
204
|
+
|
|
205
|
+
// Alignment "lines" are DISPLAYED in every mode (grid / lines / angles /
|
|
206
|
+
// off); the magnetic pull onto an axis is applied only when magnetic
|
|
207
|
+
// ('lines'). `align: false` fully bypasses (no guides).
|
|
208
|
+
const basePoint = fallbackPoint ?? wallSnap.point
|
|
209
|
+
if (input.align === false) {
|
|
210
|
+
useAlignmentGuides.getState().clear()
|
|
211
|
+
return { point: basePoint, wallSnap: null, guides: [], wallIds: [] }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const movingId = input.movingId ?? SURFACE_SNAP_MOVING_ID
|
|
215
|
+
const allCandidates =
|
|
216
|
+
input.candidates ??
|
|
217
|
+
collectAlignmentAnchors(nodes, input.excludeId ?? movingId, input.levelId ?? null)
|
|
218
|
+
|
|
219
|
+
// In non-magnetic modes nothing pulls the point onto a guide, so restrict
|
|
220
|
+
// alignment to anchors already within connect distance — a corner then reads
|
|
221
|
+
// no differently from any other nearby point, and far corners stop lighting up
|
|
222
|
+
// from across the plan. Magnetic ('lines') keeps the full-range guides since
|
|
223
|
+
// its snap closes the gap. Mirrors the wall draft/endpoint tools. Filtering the
|
|
224
|
+
// candidates (both local-frame, like `basePoint`) rather than the resolved
|
|
225
|
+
// guides avoids the floor-plan view rotation baked into the guide coords.
|
|
226
|
+
const candidates = magnetic
|
|
227
|
+
? allCandidates
|
|
228
|
+
: allCandidates.filter(
|
|
229
|
+
(candidate) =>
|
|
230
|
+
distanceSquared(basePoint, [candidate.x, candidate.z]) <= WALL_CONNECT_SNAP_RADIUS ** 2,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if (candidates.length === 0) {
|
|
234
|
+
useAlignmentGuides.getState().clear()
|
|
235
|
+
return { point: basePoint, wallSnap: null, guides: [], wallIds: [] }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const alignment = resolveAlignmentForFloorplanView({
|
|
239
|
+
moving: [{ nodeId: movingId, kind: 'corner', x: basePoint[0], z: basePoint[1] }],
|
|
240
|
+
candidates,
|
|
241
|
+
threshold: input.threshold ?? SURFACE_ALIGNMENT_THRESHOLD_M,
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
useAlignmentGuides.getState().set(alignment.guides)
|
|
245
|
+
|
|
246
|
+
if (!alignment.snap || !magnetic) {
|
|
247
|
+
return { point: basePoint, wallSnap: null, guides: alignment.guides, wallIds: [] }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
point: [basePoint[0] + alignment.snap.dx, basePoint[1] + alignment.snap.dz],
|
|
252
|
+
wallSnap: null,
|
|
253
|
+
guides: alignment.guides,
|
|
254
|
+
wallIds: [],
|
|
255
|
+
}
|
|
256
|
+
}
|