@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
|
@@ -15,6 +15,8 @@ export type WallPlanPoint = [number, number]
|
|
|
15
15
|
/** Which kind of existing-geometry snap produced a drafted point. */
|
|
16
16
|
export type WallDraftSnapKind = 'endpoint' | 'midpoint' | 'intersection' | 'wall'
|
|
17
17
|
|
|
18
|
+
export type WallSnapRadii = Partial<Record<WallDraftSnapKind, number>>
|
|
19
|
+
|
|
18
20
|
export type WallDraftSnapResult = {
|
|
19
21
|
point: WallPlanPoint
|
|
20
22
|
/**
|
|
@@ -23,9 +25,23 @@ export type WallDraftSnapResult = {
|
|
|
23
25
|
* the "magnetic" snap the beacon visualises; `null` for grid/angle-only.
|
|
24
26
|
*/
|
|
25
27
|
snap: WallDraftSnapKind | null
|
|
28
|
+
/**
|
|
29
|
+
* Walls whose geometry produced the snap. Kept separate from `snap` so a
|
|
30
|
+
* caller can use geometry for XZ alignment while independently deciding
|
|
31
|
+
* whether the target is allowed to transfer its construction plane.
|
|
32
|
+
*/
|
|
33
|
+
targetWallIds: string[]
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
export const WALL_JOIN_SNAP_RADIUS = 0.35
|
|
37
|
+
// Tight capture radius for the connectivity snap that runs in NON-magnetic modes
|
|
38
|
+
// (grid / off / angles). Joining an existing wall is treated as connectivity —
|
|
39
|
+
// separate from the 'lines' magnetic alignment — so a room can still close in
|
|
40
|
+
// those modes: within this distance of a wall the endpoint sticks onto it (and
|
|
41
|
+
// the beacon shows); beyond it, positioning is left to the mode. Kept small so
|
|
42
|
+
// only the last few cm near a wall stick, well above the room-detection junction
|
|
43
|
+
// tolerance so a captured endpoint always registers as connected.
|
|
44
|
+
export const WALL_CONNECT_SNAP_RADIUS = 0.05
|
|
29
45
|
// Generous radius for snapping to an *existing* wall's endpoint while
|
|
30
46
|
// drafting. Larger than `WALL_JOIN_SNAP_RADIUS` because endpoint snap
|
|
31
47
|
// is the strongest user intent (closing a polygon, attaching to a
|
|
@@ -108,6 +124,53 @@ export function findWallSnapTarget(
|
|
|
108
124
|
return bestTarget
|
|
109
125
|
}
|
|
110
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Wall ids that contain an already-resolved snap point.
|
|
129
|
+
*
|
|
130
|
+
* This is provenance, not another snap pass: the tolerance only absorbs float
|
|
131
|
+
* drift around a point the snap pipeline already chose.
|
|
132
|
+
*/
|
|
133
|
+
export function wallIdsAtSnapPoint(
|
|
134
|
+
point: WallPlanPoint,
|
|
135
|
+
walls: WallNode[],
|
|
136
|
+
ignoreWallIds?: string[],
|
|
137
|
+
tolerance = 1e-6,
|
|
138
|
+
): string[] {
|
|
139
|
+
const ignored = new Set(ignoreWallIds ?? [])
|
|
140
|
+
const toleranceSquared = tolerance * tolerance
|
|
141
|
+
const ids: string[] = []
|
|
142
|
+
|
|
143
|
+
for (const wall of walls) {
|
|
144
|
+
if (ignored.has(wall.id)) continue
|
|
145
|
+
if (
|
|
146
|
+
distanceSquared(point, wall.start) <= toleranceSquared ||
|
|
147
|
+
distanceSquared(point, wall.end) <= toleranceSquared
|
|
148
|
+
) {
|
|
149
|
+
ids.push(wall.id)
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (isCurvedWall(wall)) {
|
|
154
|
+
const sampleCount = Math.max(8, Math.ceil(getWallCurveLength(wall) / 0.3))
|
|
155
|
+
for (let index = 1; index < sampleCount; index += 1) {
|
|
156
|
+
const frame = getWallCurveFrameAt(wall, index / sampleCount)
|
|
157
|
+
if (distanceSquared(point, [frame.point.x, frame.point.y]) <= toleranceSquared) {
|
|
158
|
+
ids.push(wall.id)
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const projected = projectPointOntoWall(point, wall)
|
|
166
|
+
if (projected && distanceSquared(point, projected) <= toleranceSquared) {
|
|
167
|
+
ids.push(wall.id)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return ids
|
|
172
|
+
}
|
|
173
|
+
|
|
111
174
|
/**
|
|
112
175
|
* Endpoint-only snap from the *raw* cursor (no grid pre-snap), with a
|
|
113
176
|
* generous radius. Use this before `findWallSnapTarget` so the strong
|
|
@@ -119,9 +182,10 @@ export function findWallEndpointFromRaw(
|
|
|
119
182
|
point: WallPlanPoint,
|
|
120
183
|
walls: WallNode[],
|
|
121
184
|
ignoreWallIds?: string[],
|
|
185
|
+
radius = WALL_ENDPOINT_SNAP_RADIUS,
|
|
122
186
|
): WallPlanPoint | null {
|
|
123
187
|
const ignored = new Set(ignoreWallIds ?? [])
|
|
124
|
-
const radiusSquared =
|
|
188
|
+
const radiusSquared = radius ** 2
|
|
125
189
|
let best: WallPlanPoint | null = null
|
|
126
190
|
let bestDistSq = Number.POSITIVE_INFINITY
|
|
127
191
|
|
|
@@ -152,9 +216,10 @@ export function findWallMidpointFromRaw(
|
|
|
152
216
|
point: WallPlanPoint,
|
|
153
217
|
walls: WallNode[],
|
|
154
218
|
ignoreWallIds?: string[],
|
|
219
|
+
radius = WALL_MIDPOINT_SNAP_RADIUS,
|
|
155
220
|
): WallPlanPoint | null {
|
|
156
221
|
const ignored = new Set(ignoreWallIds ?? [])
|
|
157
|
-
const radiusSquared =
|
|
222
|
+
const radiusSquared = radius ** 2
|
|
158
223
|
let best: WallPlanPoint | null = null
|
|
159
224
|
let bestDistSq = Number.POSITIVE_INFINITY
|
|
160
225
|
|
|
@@ -202,10 +267,11 @@ export function findWallIntersectionFromRaw(
|
|
|
202
267
|
point: WallPlanPoint,
|
|
203
268
|
walls: WallNode[],
|
|
204
269
|
ignoreWallIds?: string[],
|
|
270
|
+
radius = WALL_INTERSECTION_SNAP_RADIUS,
|
|
205
271
|
): WallPlanPoint | null {
|
|
206
272
|
const ignored = new Set(ignoreWallIds ?? [])
|
|
207
273
|
const straight = walls.filter((wall) => !ignored.has(wall.id) && !isCurvedWall(wall))
|
|
208
|
-
const radiusSquared =
|
|
274
|
+
const radiusSquared = radius ** 2
|
|
209
275
|
let best: WallPlanPoint | null = null
|
|
210
276
|
let bestDistSq = Number.POSITIVE_INFINITY
|
|
211
277
|
|
|
@@ -246,6 +312,53 @@ function nearestCandidate(
|
|
|
246
312
|
return best
|
|
247
313
|
}
|
|
248
314
|
|
|
315
|
+
// Tolerance for "the committed endpoint actually lies on existing wall
|
|
316
|
+
// geometry". Commit-time resolution (corner join, connect snap, split) puts
|
|
317
|
+
// the endpoint exactly on the geometry, so this only needs to absorb float
|
|
318
|
+
// drift — it is NOT a snap radius.
|
|
319
|
+
export const WALL_CHAIN_JOIN_TOLERANCE = 1e-3
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* True when a committed chain segment's resolved `end` lies on wall geometry
|
|
323
|
+
* (an endpoint, or a straight wall's interior) of a wall outside the current
|
|
324
|
+
* draft chain. The wall tools stop chaining there: a segment that tees into
|
|
325
|
+
* the existing network is a termination — continuing would draft the next
|
|
326
|
+
* segment on top of existing walls. `chainWallIds` excludes the chain's own
|
|
327
|
+
* segments (including the just-committed one) so edge/midpoint snaps onto a
|
|
328
|
+
* previous own segment don't read as a join. Curved wall interiors are
|
|
329
|
+
* skipped (their endpoints still count) — resolving an end onto a curve body
|
|
330
|
+
* is rare and continuing there matches the previous behaviour.
|
|
331
|
+
*/
|
|
332
|
+
export function chainEndJoinsExistingWall(
|
|
333
|
+
end: WallPlanPoint,
|
|
334
|
+
walls: WallNode[],
|
|
335
|
+
chainWallIds: string[],
|
|
336
|
+
tolerance = WALL_CHAIN_JOIN_TOLERANCE,
|
|
337
|
+
): boolean {
|
|
338
|
+
const ignored = new Set(chainWallIds)
|
|
339
|
+
const toleranceSquared = tolerance * tolerance
|
|
340
|
+
|
|
341
|
+
for (const wall of walls) {
|
|
342
|
+
if (ignored.has(wall.id)) continue
|
|
343
|
+
|
|
344
|
+
if (
|
|
345
|
+
distanceSquared(end, wall.start) <= toleranceSquared ||
|
|
346
|
+
distanceSquared(end, wall.end) <= toleranceSquared
|
|
347
|
+
) {
|
|
348
|
+
return true
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (isCurvedWall(wall)) continue
|
|
352
|
+
|
|
353
|
+
const projected = projectPointOntoWall(end, wall)
|
|
354
|
+
if (projected && distanceSquared(end, projected) <= toleranceSquared) {
|
|
355
|
+
return true
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return false
|
|
360
|
+
}
|
|
361
|
+
|
|
249
362
|
/**
|
|
250
363
|
* Discrete "special point" snap from the raw cursor, in priority order:
|
|
251
364
|
* 1. corners (endpoints) — strongest intent, largest radius
|
|
@@ -257,14 +370,29 @@ export function findWallSpecialPointSnap(
|
|
|
257
370
|
point: WallPlanPoint,
|
|
258
371
|
walls: WallNode[],
|
|
259
372
|
ignoreWallIds?: string[],
|
|
373
|
+
radii?: WallSnapRadii,
|
|
260
374
|
): WallDraftSnapResult | null {
|
|
261
|
-
const endpoint = findWallEndpointFromRaw(point, walls, ignoreWallIds)
|
|
262
|
-
if (endpoint)
|
|
375
|
+
const endpoint = findWallEndpointFromRaw(point, walls, ignoreWallIds, radii?.endpoint)
|
|
376
|
+
if (endpoint) {
|
|
377
|
+
return {
|
|
378
|
+
point: endpoint,
|
|
379
|
+
snap: 'endpoint',
|
|
380
|
+
targetWallIds: wallIdsAtSnapPoint(endpoint, walls, ignoreWallIds),
|
|
381
|
+
}
|
|
382
|
+
}
|
|
263
383
|
|
|
264
|
-
const midpoint = findWallMidpointFromRaw(point, walls, ignoreWallIds)
|
|
265
|
-
const intersection = findWallIntersectionFromRaw(point, walls, ignoreWallIds)
|
|
384
|
+
const midpoint = findWallMidpointFromRaw(point, walls, ignoreWallIds, radii?.midpoint)
|
|
385
|
+
const intersection = findWallIntersectionFromRaw(point, walls, ignoreWallIds, radii?.intersection)
|
|
266
386
|
return nearestCandidate(point, [
|
|
267
|
-
midpoint && {
|
|
268
|
-
|
|
387
|
+
midpoint && {
|
|
388
|
+
point: midpoint,
|
|
389
|
+
snap: 'midpoint',
|
|
390
|
+
targetWallIds: wallIdsAtSnapPoint(midpoint, walls, ignoreWallIds),
|
|
391
|
+
},
|
|
392
|
+
intersection && {
|
|
393
|
+
point: intersection,
|
|
394
|
+
snap: 'intersection',
|
|
395
|
+
targetWallIds: wallIdsAtSnapPoint(intersection, walls, ignoreWallIds),
|
|
396
|
+
},
|
|
269
397
|
])
|
|
270
398
|
}
|
|
@@ -18,7 +18,11 @@ export const ZoneBoundaryEditor: React.FC<ZoneBoundaryEditorProps> = ({ zoneId }
|
|
|
18
18
|
|
|
19
19
|
const handlePolygonChange = useCallback(
|
|
20
20
|
(newPolygon: Array<[number, number]>) => {
|
|
21
|
-
updateNode(zoneId, {
|
|
21
|
+
updateNode(zoneId, {
|
|
22
|
+
polygon: newPolygon,
|
|
23
|
+
autoFromWalls: false,
|
|
24
|
+
boundaryWallIds: [],
|
|
25
|
+
})
|
|
22
26
|
},
|
|
23
27
|
[zoneId, updateNode],
|
|
24
28
|
)
|
|
@@ -1,51 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ANGLE_STEP,
|
|
3
|
+
emitter,
|
|
4
|
+
type GridEvent,
|
|
5
|
+
type LevelNode,
|
|
6
|
+
snapPointAlongAngleRay,
|
|
7
|
+
useScene,
|
|
8
|
+
ZoneNode,
|
|
9
|
+
} from '@pascal-app/core'
|
|
2
10
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
11
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
4
12
|
import { BufferGeometry, DoubleSide, type Group, type Line, Shape, Vector3 } from 'three'
|
|
5
13
|
import { EDITOR_LAYER } from './../../../lib/constants'
|
|
6
14
|
import { sfxEmitter } from './../../../lib/sfx-bus'
|
|
15
|
+
import {
|
|
16
|
+
clearSurfacePlanSnapFeedback,
|
|
17
|
+
resolveSurfacePlanPointSnap,
|
|
18
|
+
} from './../../../lib/surface-plan-snap'
|
|
7
19
|
import { snapWorldXZForActiveBuilding } from './../../../lib/world-grid-snap'
|
|
8
|
-
import useEditor from './../../../store/use-editor'
|
|
20
|
+
import useEditor, { isAngleSnapActive, isGridSnapActive } from './../../../store/use-editor'
|
|
21
|
+
import { useFloorplanDraftPreview } from './../../../store/use-floorplan-draft-preview'
|
|
9
22
|
import { CursorSphere } from '../shared/cursor-sphere'
|
|
10
23
|
|
|
11
24
|
const Y_OFFSET = 0.02
|
|
12
25
|
|
|
13
|
-
/**
|
|
14
|
-
* Snaps a point to the nearest axis-aligned or 45-degree diagonal from the last point
|
|
15
|
-
*/
|
|
16
|
-
const calculateSnapPoint = (
|
|
17
|
-
lastPoint: [number, number],
|
|
18
|
-
currentPoint: [number, number],
|
|
19
|
-
): [number, number] => {
|
|
20
|
-
const [x1, y1] = lastPoint
|
|
21
|
-
const [x, y] = currentPoint
|
|
22
|
-
|
|
23
|
-
const dx = x - x1
|
|
24
|
-
const dy = y - y1
|
|
25
|
-
const absDx = Math.abs(dx)
|
|
26
|
-
const absDy = Math.abs(dy)
|
|
27
|
-
|
|
28
|
-
// Calculate distances to horizontal, vertical, and diagonal lines
|
|
29
|
-
const horizontalDist = absDy
|
|
30
|
-
const verticalDist = absDx
|
|
31
|
-
const diagonalDist = Math.abs(absDx - absDy)
|
|
32
|
-
|
|
33
|
-
// Find the minimum distance to determine which axis to snap to
|
|
34
|
-
const minDist = Math.min(horizontalDist, verticalDist, diagonalDist)
|
|
35
|
-
|
|
36
|
-
if (minDist === diagonalDist) {
|
|
37
|
-
// Snap to 45° diagonal
|
|
38
|
-
const diagonalLength = Math.min(absDx, absDy)
|
|
39
|
-
return [x1 + Math.sign(dx) * diagonalLength, y1 + Math.sign(dy) * diagonalLength]
|
|
40
|
-
}
|
|
41
|
-
if (minDist === horizontalDist) {
|
|
42
|
-
// Snap to horizontal
|
|
43
|
-
return [x, y1]
|
|
44
|
-
}
|
|
45
|
-
// Snap to vertical
|
|
46
|
-
return [x1, y]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
26
|
/**
|
|
50
27
|
* Creates a zone with the given polygon points
|
|
51
28
|
*/
|
|
@@ -103,15 +80,53 @@ export const ZoneTool: React.FC = () => {
|
|
|
103
80
|
levelY: 0,
|
|
104
81
|
})
|
|
105
82
|
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
useFloorplanDraftPreview.getState().setPolygonDraft('zone', preview.points)
|
|
85
|
+
}, [preview.points])
|
|
86
|
+
useEffect(
|
|
87
|
+
() => () => {
|
|
88
|
+
const draftPreview = useFloorplanDraftPreview.getState()
|
|
89
|
+
if (draftPreview.polygonDraftType === 'zone') {
|
|
90
|
+
draftPreview.setPolygonDraft(null, [])
|
|
91
|
+
}
|
|
92
|
+
draftPreview.setCursorPoint(null)
|
|
93
|
+
},
|
|
94
|
+
[],
|
|
95
|
+
)
|
|
96
|
+
|
|
106
97
|
useEffect(() => {
|
|
107
98
|
if (!currentLevelId) return
|
|
108
99
|
|
|
109
100
|
let cursorPosition: [number, number] = [0, 0]
|
|
101
|
+
let snappedCursorPosition: [number, number] | null = null
|
|
110
102
|
|
|
111
103
|
// Initialize line geometries
|
|
112
104
|
mainLineRef.current.geometry = new BufferGeometry()
|
|
113
105
|
closingLineRef.current.geometry = new BufferGeometry()
|
|
114
106
|
|
|
107
|
+
// Snapping follows the active mode: `angles` locks the ray to 15° from the
|
|
108
|
+
// last vertex (grid quantizes the distance along it), otherwise `grid`
|
|
109
|
+
// quantizes to the world-aligned grid; the shared surface snap then layers
|
|
110
|
+
// wall-corner/midpoint/crossing magnetics and alignment guides on top —
|
|
111
|
+
// the same pipeline slab/ceiling drafting uses. No held-Shift bypass —
|
|
112
|
+
// Shift cycles the mode (see interaction-scope.md).
|
|
113
|
+
const snapDraftPoint = (
|
|
114
|
+
lastPoint: [number, number] | undefined,
|
|
115
|
+
gridPoint: [number, number],
|
|
116
|
+
rawPoint: [number, number],
|
|
117
|
+
): [number, number] => {
|
|
118
|
+
const gridStep = isGridSnapActive() ? useEditor.getState().gridSnapStep : 0
|
|
119
|
+
const orthoPoint: [number, number] =
|
|
120
|
+
isAngleSnapActive() && lastPoint
|
|
121
|
+
? [...snapPointAlongAngleRay(lastPoint, rawPoint, DEFAULT_ANGLE_STEP, gridStep)]
|
|
122
|
+
: gridPoint
|
|
123
|
+
return resolveSurfacePlanPointSnap({
|
|
124
|
+
rawPoint,
|
|
125
|
+
fallbackPoint: orthoPoint,
|
|
126
|
+
levelId: currentLevelId,
|
|
127
|
+
}).point
|
|
128
|
+
}
|
|
129
|
+
|
|
115
130
|
const updateLines = () => {
|
|
116
131
|
const points = pointsRef.current
|
|
117
132
|
const y = levelYRef.current + Y_OFFSET
|
|
@@ -127,11 +142,9 @@ export const ZoneTool: React.FC = () => {
|
|
|
127
142
|
|
|
128
143
|
// Add cursor point
|
|
129
144
|
const lastPoint = points[points.length - 1]
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
linePoints.push(new Vector3(snapped[0], y, snapped[1]))
|
|
134
|
-
}
|
|
145
|
+
const snapped = snappedCursorPosition ?? cursorPosition
|
|
146
|
+
if (lastPoint && isValidPoint(snapped)) {
|
|
147
|
+
linePoints.push(new Vector3(snapped[0], y, snapped[1]))
|
|
135
148
|
}
|
|
136
149
|
|
|
137
150
|
// Update main line geometry
|
|
@@ -145,17 +158,14 @@ export const ZoneTool: React.FC = () => {
|
|
|
145
158
|
|
|
146
159
|
// Update closing line (from cursor back to first point)
|
|
147
160
|
const firstPoint = points[0]
|
|
148
|
-
if (points.length >= 2 && lastPoint && isValidPoint(firstPoint)) {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
closingLineRef.current.geometry = new BufferGeometry().setFromPoints(closingPoints)
|
|
157
|
-
closingLineRef.current.visible = true
|
|
158
|
-
}
|
|
161
|
+
if (points.length >= 2 && lastPoint && isValidPoint(firstPoint) && isValidPoint(snapped)) {
|
|
162
|
+
const closingPoints = [
|
|
163
|
+
new Vector3(snapped[0], y, snapped[1]),
|
|
164
|
+
new Vector3(firstPoint[0], y, firstPoint[1]),
|
|
165
|
+
]
|
|
166
|
+
closingLineRef.current.geometry.dispose()
|
|
167
|
+
closingLineRef.current.geometry = new BufferGeometry().setFromPoints(closingPoints)
|
|
168
|
+
closingLineRef.current.visible = true
|
|
159
169
|
} else {
|
|
160
170
|
closingLineRef.current.visible = false
|
|
161
171
|
}
|
|
@@ -163,40 +173,46 @@ export const ZoneTool: React.FC = () => {
|
|
|
163
173
|
|
|
164
174
|
const updatePreview = () => {
|
|
165
175
|
const points = pointsRef.current
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
let cursorPt: [number, number] | null = null
|
|
169
|
-
if (lastPoint) {
|
|
170
|
-
cursorPt = calculateSnapPoint(lastPoint, cursorPosition)
|
|
171
|
-
} else if (points.length === 0) {
|
|
172
|
-
cursorPt = cursorPosition
|
|
173
|
-
}
|
|
176
|
+
const cursorPt = snappedCursorPosition ?? cursorPosition
|
|
174
177
|
|
|
175
|
-
setPreview({
|
|
178
|
+
setPreview({
|
|
179
|
+
points: [...points],
|
|
180
|
+
cursorPoint: isValidPoint(cursorPt) ? cursorPt : null,
|
|
181
|
+
levelY: levelYRef.current,
|
|
182
|
+
})
|
|
176
183
|
updateLines()
|
|
177
184
|
}
|
|
178
185
|
|
|
186
|
+
// World-grid snap projected into building-local; rotated buildings
|
|
187
|
+
// used to pull the snap off the visible grid lines. Grid quantize only
|
|
188
|
+
// in grid mode; off / lines / angles leave the raw cursor.
|
|
189
|
+
const gridPointOf = (event: GridEvent): [number, number] =>
|
|
190
|
+
isGridSnapActive()
|
|
191
|
+
? snapWorldXZForActiveBuilding(
|
|
192
|
+
event.position[0],
|
|
193
|
+
event.position[2],
|
|
194
|
+
useEditor.getState().gridSnapStep,
|
|
195
|
+
).local
|
|
196
|
+
: [event.localPosition[0], event.localPosition[2]]
|
|
197
|
+
|
|
179
198
|
const onGridMove = (event: GridEvent) => {
|
|
180
199
|
if (!cursorRef.current) return
|
|
181
200
|
|
|
182
|
-
|
|
183
|
-
// used to pull the snap off the visible grid lines.
|
|
184
|
-
const [gridX, gridZ] = snapWorldXZForActiveBuilding(
|
|
185
|
-
event.position[0],
|
|
186
|
-
event.position[2],
|
|
187
|
-
0.5,
|
|
188
|
-
).local
|
|
189
|
-
cursorPosition = [gridX, gridZ]
|
|
201
|
+
cursorPosition = gridPointOf(event)
|
|
190
202
|
levelYRef.current = event.localPosition[1]
|
|
191
203
|
|
|
192
|
-
// If we have points, snap to axis from last point
|
|
193
204
|
const lastPoint = pointsRef.current[pointsRef.current.length - 1]
|
|
194
|
-
const displayPoint = lastPoint
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
205
|
+
const displayPoint = snapDraftPoint(lastPoint, cursorPosition, [
|
|
206
|
+
event.localPosition[0],
|
|
207
|
+
event.localPosition[2],
|
|
208
|
+
])
|
|
209
|
+
snappedCursorPosition = displayPoint
|
|
210
|
+
useFloorplanDraftPreview.getState().setCursorPoint(displayPoint)
|
|
211
|
+
|
|
212
|
+
// Play snap sound when the snapped position changes during drawing — only
|
|
213
|
+
// when a quantizing mode is active (off / lines move continuously).
|
|
199
214
|
if (
|
|
215
|
+
(isGridSnapActive() || isAngleSnapActive()) &&
|
|
200
216
|
pointsRef.current.length > 0 &&
|
|
201
217
|
previousSnappedPointRef.current &&
|
|
202
218
|
(displayPoint[0] !== previousSnappedPointRef.current[0] ||
|
|
@@ -214,18 +230,11 @@ export const ZoneTool: React.FC = () => {
|
|
|
214
230
|
const onGridClick = (event: GridEvent) => {
|
|
215
231
|
if (!currentLevelId) return
|
|
216
232
|
|
|
217
|
-
const [gridX, gridZ] = snapWorldXZForActiveBuilding(
|
|
218
|
-
event.position[0],
|
|
219
|
-
event.position[2],
|
|
220
|
-
0.5,
|
|
221
|
-
).local
|
|
222
|
-
let clickPoint: [number, number] = [gridX, gridZ]
|
|
223
|
-
|
|
224
|
-
// Snap to axis from last point
|
|
225
233
|
const lastPoint = pointsRef.current[pointsRef.current.length - 1]
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
234
|
+
const clickPoint = snapDraftPoint(lastPoint, gridPointOf(event), [
|
|
235
|
+
event.localPosition[0],
|
|
236
|
+
event.localPosition[2],
|
|
237
|
+
])
|
|
229
238
|
|
|
230
239
|
// Check if clicking on the first point to close the shape
|
|
231
240
|
const firstPoint = pointsRef.current[0]
|
|
@@ -279,6 +288,7 @@ export const ZoneTool: React.FC = () => {
|
|
|
279
288
|
|
|
280
289
|
// Reset state on unmount
|
|
281
290
|
pointsRef.current = []
|
|
291
|
+
clearSurfacePlanSnapFeedback()
|
|
282
292
|
}
|
|
283
293
|
}, [currentLevelId])
|
|
284
294
|
|
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { emitter } from '@pascal-app/core'
|
|
4
4
|
import Image from 'next/image'
|
|
5
|
+
import useEditor from '../../../store/use-editor'
|
|
5
6
|
import { ActionButton } from './action-button'
|
|
6
7
|
|
|
7
8
|
export function CameraActions({ hideOrbit = false }: { hideOrbit?: boolean }) {
|
|
9
|
+
// Orbit stays useful in 2D-only (it spins the synced floorplan view), but
|
|
10
|
+
// top view only tilts the hidden 3D camera — pointless without the canvas.
|
|
11
|
+
const is2dOnly = useEditor((s) => s.viewMode === '2d')
|
|
12
|
+
|
|
8
13
|
const goToTopView = () => {
|
|
9
14
|
emitter.emit('camera-controls:top-view')
|
|
10
15
|
}
|
|
@@ -33,7 +38,7 @@ export function CameraActions({ hideOrbit = false }: { hideOrbit?: boolean }) {
|
|
|
33
38
|
alt="Orbit Left"
|
|
34
39
|
className="h-[28px] w-[28px] -scale-x-100 object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
|
35
40
|
height={28}
|
|
36
|
-
src="/icons/rotate.
|
|
41
|
+
src="/icons/rotate.webp"
|
|
37
42
|
width={28}
|
|
38
43
|
/>
|
|
39
44
|
</ActionButton>
|
|
@@ -50,7 +55,7 @@ export function CameraActions({ hideOrbit = false }: { hideOrbit?: boolean }) {
|
|
|
50
55
|
alt="Orbit Right"
|
|
51
56
|
className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
|
52
57
|
height={28}
|
|
53
|
-
src="/icons/rotate.
|
|
58
|
+
src="/icons/rotate.webp"
|
|
54
59
|
width={28}
|
|
55
60
|
/>
|
|
56
61
|
</ActionButton>
|
|
@@ -58,21 +63,23 @@ export function CameraActions({ hideOrbit = false }: { hideOrbit?: boolean }) {
|
|
|
58
63
|
)}
|
|
59
64
|
|
|
60
65
|
{/* Top View */}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
{!is2dOnly && (
|
|
67
|
+
<ActionButton
|
|
68
|
+
className="group hover:bg-white/5"
|
|
69
|
+
label="Top View"
|
|
70
|
+
onClick={goToTopView}
|
|
71
|
+
size="icon"
|
|
72
|
+
variant="ghost"
|
|
73
|
+
>
|
|
74
|
+
<Image
|
|
75
|
+
alt="Top View"
|
|
76
|
+
className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
|
77
|
+
height={28}
|
|
78
|
+
src="/icons/topview.webp"
|
|
79
|
+
width={28}
|
|
80
|
+
/>
|
|
81
|
+
</ActionButton>
|
|
82
|
+
)}
|
|
76
83
|
</div>
|
|
77
84
|
)
|
|
78
85
|
}
|