@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,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { Plus } from 'lucide-react'
|
|
3
4
|
import type { ComponentType, ReactNode } from 'react'
|
|
4
5
|
import {
|
|
5
6
|
Tooltip,
|
|
@@ -10,7 +11,13 @@ import { cn } from './../../../lib/utils'
|
|
|
10
11
|
|
|
11
12
|
export type PanelId = string
|
|
12
13
|
|
|
13
|
-
export type ExtraPanel = {
|
|
14
|
+
export type ExtraPanel = {
|
|
15
|
+
id: string
|
|
16
|
+
icon: ReactNode
|
|
17
|
+
label: string
|
|
18
|
+
component: ComponentType
|
|
19
|
+
pluginId?: string
|
|
20
|
+
}
|
|
14
21
|
|
|
15
22
|
interface IconRailProps {
|
|
16
23
|
activePanel: PanelId
|
|
@@ -22,13 +29,13 @@ interface IconRailProps {
|
|
|
22
29
|
|
|
23
30
|
const sitePanel: { id: PanelId; iconSrc: string; label: string } = {
|
|
24
31
|
id: 'site',
|
|
25
|
-
iconSrc: '/icons/level.
|
|
32
|
+
iconSrc: '/icons/level.webp',
|
|
26
33
|
label: 'Site',
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
const settingsPanel: { id: PanelId; iconSrc: string; label: string } = {
|
|
30
37
|
id: 'settings',
|
|
31
|
-
iconSrc: '/icons/settings.
|
|
38
|
+
iconSrc: '/icons/settings.webp',
|
|
32
39
|
label: 'Settings',
|
|
33
40
|
}
|
|
34
41
|
|
|
@@ -41,6 +48,38 @@ export function IconRail({
|
|
|
41
48
|
extraPanels,
|
|
42
49
|
className,
|
|
43
50
|
}: IconRailProps) {
|
|
51
|
+
const regularExtraPanels = extraPanels?.filter((panel) => !panel.pluginId && panel.id !== 'plugins')
|
|
52
|
+
const pluginPanels = extraPanels?.filter((panel) => panel.pluginId)
|
|
53
|
+
const pluginsPanel = extraPanels?.find((panel) => panel.id === 'plugins')
|
|
54
|
+
|
|
55
|
+
const renderExtraPanel = (panel: ExtraPanel) => {
|
|
56
|
+
const isActive = activePanel === panel.id
|
|
57
|
+
return (
|
|
58
|
+
<Tooltip key={panel.id}>
|
|
59
|
+
<TooltipTrigger asChild>
|
|
60
|
+
<button
|
|
61
|
+
className={cn(
|
|
62
|
+
'flex h-9 w-9 items-center justify-center rounded-lg transition-all',
|
|
63
|
+
isActive ? 'bg-accent' : 'hover:bg-accent',
|
|
64
|
+
)}
|
|
65
|
+
onClick={() => onPanelChange(panel.id)}
|
|
66
|
+
type="button"
|
|
67
|
+
>
|
|
68
|
+
<span
|
|
69
|
+
className={cn(
|
|
70
|
+
'flex h-6 w-6 items-center justify-center transition-all',
|
|
71
|
+
!isActive && 'opacity-50',
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
{panel.id === 'plugins' ? <Plus className="h-5 w-5" /> : panel.icon}
|
|
75
|
+
</span>
|
|
76
|
+
</button>
|
|
77
|
+
</TooltipTrigger>
|
|
78
|
+
<TooltipContent side="right">{panel.label}</TooltipContent>
|
|
79
|
+
</Tooltip>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
44
83
|
return (
|
|
45
84
|
<div
|
|
46
85
|
className={cn(
|
|
@@ -83,34 +122,7 @@ export function IconRail({
|
|
|
83
122
|
)
|
|
84
123
|
})}
|
|
85
124
|
|
|
86
|
-
{
|
|
87
|
-
{extraPanels?.map((panel) => {
|
|
88
|
-
const isActive = activePanel === panel.id
|
|
89
|
-
return (
|
|
90
|
-
<Tooltip key={panel.id}>
|
|
91
|
-
<TooltipTrigger asChild>
|
|
92
|
-
<button
|
|
93
|
-
className={cn(
|
|
94
|
-
'flex h-9 w-9 items-center justify-center rounded-lg transition-all',
|
|
95
|
-
isActive ? 'bg-accent' : 'hover:bg-accent',
|
|
96
|
-
)}
|
|
97
|
-
onClick={() => onPanelChange(panel.id)}
|
|
98
|
-
type="button"
|
|
99
|
-
>
|
|
100
|
-
<span
|
|
101
|
-
className={cn(
|
|
102
|
-
'flex h-6 w-6 items-center justify-center transition-all',
|
|
103
|
-
!isActive && 'opacity-50',
|
|
104
|
-
)}
|
|
105
|
-
>
|
|
106
|
-
{panel.icon}
|
|
107
|
-
</span>
|
|
108
|
-
</button>
|
|
109
|
-
</TooltipTrigger>
|
|
110
|
-
<TooltipContent side="right">{panel.label}</TooltipContent>
|
|
111
|
-
</Tooltip>
|
|
112
|
-
)
|
|
113
|
-
})}
|
|
125
|
+
{regularExtraPanels?.map(renderExtraPanel)}
|
|
114
126
|
|
|
115
127
|
{/* Settings panel */}
|
|
116
128
|
{[settingsPanel].map((panel) => {
|
|
@@ -140,6 +152,13 @@ export function IconRail({
|
|
|
140
152
|
</Tooltip>
|
|
141
153
|
)
|
|
142
154
|
})}
|
|
155
|
+
|
|
156
|
+
{(pluginPanels?.length || pluginsPanel) && (
|
|
157
|
+
<div className="mt-1 flex w-9 flex-col items-center gap-1 border-border/70 border-t pt-2">
|
|
158
|
+
{pluginPanels?.map(renderExtraPanel)}
|
|
159
|
+
{pluginsPanel && renderExtraPanel(pluginsPanel)}
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
143
162
|
</div>
|
|
144
163
|
)
|
|
145
164
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Icon } from '@iconify/react'
|
|
4
|
+
import { type IconRef, useScene } from '@pascal-app/core'
|
|
5
|
+
import { ChevronLeft, ChevronRight, ExternalLink, Puzzle } from 'lucide-react'
|
|
6
|
+
import { lazy, type ReactNode, Suspense, useState, useSyncExternalStore } from 'react'
|
|
7
|
+
import { editorHostPanelRegistry } from '../../../../lib/plugin-panels'
|
|
8
|
+
import { Button } from '../../primitives/button'
|
|
9
|
+
|
|
10
|
+
const PLUGIN_AUTHORING_URL =
|
|
11
|
+
'https://editor.pascal.app/docs/developers/plugins'
|
|
12
|
+
|
|
13
|
+
function renderPluginIcon(ref: IconRef): ReactNode {
|
|
14
|
+
if (ref.kind === 'url') {
|
|
15
|
+
return <img alt="" className="h-8 w-8 object-contain" src={ref.src} />
|
|
16
|
+
}
|
|
17
|
+
if (ref.kind === 'iconify') {
|
|
18
|
+
return <Icon height={28} icon={ref.name} width={28} />
|
|
19
|
+
}
|
|
20
|
+
if (ref.kind === 'svg') {
|
|
21
|
+
return (
|
|
22
|
+
<svg height={28} viewBox={ref.viewBox} width={28}>
|
|
23
|
+
<path d={ref.path} fill="currentColor" />
|
|
24
|
+
</svg>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
const LazyIcon = lazy(ref.module)
|
|
28
|
+
return (
|
|
29
|
+
<Suspense fallback={<Puzzle className="h-7 w-7" />}>
|
|
30
|
+
<LazyIcon />
|
|
31
|
+
</Suspense>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function PluginsPanel() {
|
|
36
|
+
const [selectedPluginId, setSelectedPluginId] = useState<string | null>(null)
|
|
37
|
+
const panels = useSyncExternalStore(
|
|
38
|
+
editorHostPanelRegistry.subscribe,
|
|
39
|
+
editorHostPanelRegistry.getSnapshot,
|
|
40
|
+
editorHostPanelRegistry.getSnapshot,
|
|
41
|
+
)
|
|
42
|
+
const installedPlugins = useScene((state) => state.installedPlugins)
|
|
43
|
+
const setInstalledPlugins = useScene((state) => state.setInstalledPlugins)
|
|
44
|
+
const readOnly = useScene((state) => state.readOnly)
|
|
45
|
+
const plugins = Array.from(
|
|
46
|
+
new Map(
|
|
47
|
+
panels
|
|
48
|
+
.filter((panel) => panel.pluginId)
|
|
49
|
+
.map((panel) => [panel.pluginId as string, panel]),
|
|
50
|
+
).entries(),
|
|
51
|
+
)
|
|
52
|
+
const selectedPlugin = selectedPluginId
|
|
53
|
+
? plugins.find(([pluginId]) => pluginId === selectedPluginId)
|
|
54
|
+
: undefined
|
|
55
|
+
|
|
56
|
+
if (selectedPlugin) {
|
|
57
|
+
const [pluginId, panel] = selectedPlugin
|
|
58
|
+
const installed = installedPlugins.includes(pluginId)
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="flex h-full flex-col overflow-y-auto p-4">
|
|
62
|
+
<div>
|
|
63
|
+
<Button
|
|
64
|
+
className="rounded-full"
|
|
65
|
+
onClick={() => setSelectedPluginId(null)}
|
|
66
|
+
size="sm"
|
|
67
|
+
variant="ghost"
|
|
68
|
+
>
|
|
69
|
+
<ChevronLeft className="h-4 w-4" />
|
|
70
|
+
All plugins
|
|
71
|
+
</Button>
|
|
72
|
+
|
|
73
|
+
<div className="mt-5 flex items-start gap-4">
|
|
74
|
+
<div className="flex h-16 w-16 shrink-0 items-center justify-center rounded-2xl bg-background/60">
|
|
75
|
+
{renderPluginIcon(panel.icon)}
|
|
76
|
+
</div>
|
|
77
|
+
<div className="min-w-0 pt-1">
|
|
78
|
+
<h2 className="font-semibold text-lg text-sidebar-foreground">{panel.label}</h2>
|
|
79
|
+
<p className="text-sidebar-foreground/50 text-sm">
|
|
80
|
+
{installed ? 'Installed' : 'Not installed'}
|
|
81
|
+
</p>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<p className="mt-5 text-sidebar-foreground/70 text-sm">
|
|
86
|
+
{panel.description ?? 'Adds a new tool panel to the editor.'}
|
|
87
|
+
</p>
|
|
88
|
+
|
|
89
|
+
<dl className="mt-6 divide-y divide-border/50 rounded-xl border border-border/60">
|
|
90
|
+
<div className="p-3">
|
|
91
|
+
<dt className="text-sidebar-foreground/50 text-xs">Plugin ID</dt>
|
|
92
|
+
<dd className="mt-1 break-all text-sidebar-foreground text-sm">{pluginId}</dd>
|
|
93
|
+
</div>
|
|
94
|
+
{panel.creator && (
|
|
95
|
+
<div className="p-3">
|
|
96
|
+
<dt className="text-sidebar-foreground/50 text-xs">Creator</dt>
|
|
97
|
+
<dd className="mt-1 text-sm">
|
|
98
|
+
{panel.creator.url ? (
|
|
99
|
+
<a
|
|
100
|
+
className="inline-flex items-center gap-1 text-sidebar-foreground underline-offset-4 hover:underline"
|
|
101
|
+
href={panel.creator.url}
|
|
102
|
+
rel="noreferrer"
|
|
103
|
+
target="_blank"
|
|
104
|
+
>
|
|
105
|
+
{panel.creator.name}
|
|
106
|
+
<ExternalLink className="h-3 w-3" />
|
|
107
|
+
</a>
|
|
108
|
+
) : (
|
|
109
|
+
panel.creator.name
|
|
110
|
+
)}
|
|
111
|
+
</dd>
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
{panel.pluginUrl && (
|
|
115
|
+
<div className="p-3">
|
|
116
|
+
<dt className="text-sidebar-foreground/50 text-xs">Plugin</dt>
|
|
117
|
+
<dd className="mt-1 text-sm">
|
|
118
|
+
<a
|
|
119
|
+
className="inline-flex items-center gap-1 text-sidebar-foreground underline-offset-4 hover:underline"
|
|
120
|
+
href={panel.pluginUrl}
|
|
121
|
+
rel="noreferrer"
|
|
122
|
+
target="_blank"
|
|
123
|
+
>
|
|
124
|
+
View plugin
|
|
125
|
+
<ExternalLink className="h-3 w-3" />
|
|
126
|
+
</a>
|
|
127
|
+
</dd>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</dl>
|
|
131
|
+
|
|
132
|
+
<Button
|
|
133
|
+
className="mt-5 rounded-full"
|
|
134
|
+
disabled={readOnly}
|
|
135
|
+
onClick={() => {
|
|
136
|
+
const next = installed
|
|
137
|
+
? installedPlugins.filter((id) => id !== pluginId)
|
|
138
|
+
: [...installedPlugins, pluginId]
|
|
139
|
+
setInstalledPlugins(next, { explicit: true })
|
|
140
|
+
}}
|
|
141
|
+
variant={installed ? 'outline' : 'default'}
|
|
142
|
+
>
|
|
143
|
+
{installed ? 'Uninstall' : 'Install'}
|
|
144
|
+
</Button>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div className="mt-auto pt-6">
|
|
148
|
+
<a
|
|
149
|
+
className="inline-flex items-center gap-1.5 text-sidebar-foreground/70 text-sm underline-offset-4 hover:text-sidebar-foreground hover:underline"
|
|
150
|
+
href={PLUGIN_AUTHORING_URL}
|
|
151
|
+
rel="noreferrer"
|
|
152
|
+
target="_blank"
|
|
153
|
+
>
|
|
154
|
+
Create a Pascal plugin
|
|
155
|
+
<ExternalLink className="h-3.5 w-3.5" />
|
|
156
|
+
</a>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className="flex h-full flex-col overflow-y-auto p-4">
|
|
164
|
+
<div className="mb-5">
|
|
165
|
+
<h2 className="font-semibold text-lg text-sidebar-foreground">Plugins</h2>
|
|
166
|
+
<p className="mt-1 text-sidebar-foreground/60 text-sm">
|
|
167
|
+
Add focused tools and content to this project.
|
|
168
|
+
</p>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div className="flex flex-col gap-3">
|
|
172
|
+
{plugins.map(([pluginId, panel]) => {
|
|
173
|
+
const installed = installedPlugins.includes(pluginId)
|
|
174
|
+
return (
|
|
175
|
+
<button
|
|
176
|
+
className="w-full rounded-xl border border-border/60 bg-accent/20 p-3 text-left transition-colors hover:bg-accent/40"
|
|
177
|
+
key={pluginId}
|
|
178
|
+
onClick={() => setSelectedPluginId(pluginId)}
|
|
179
|
+
type="button"
|
|
180
|
+
>
|
|
181
|
+
<div className="flex items-start gap-3">
|
|
182
|
+
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-background/60">
|
|
183
|
+
{renderPluginIcon(panel.icon)}
|
|
184
|
+
</div>
|
|
185
|
+
<div className="min-w-0 flex-1">
|
|
186
|
+
<div className="flex items-center justify-between gap-3">
|
|
187
|
+
<div>
|
|
188
|
+
<h3 className="font-medium text-sidebar-foreground">{panel.label}</h3>
|
|
189
|
+
<p className="text-sidebar-foreground/50 text-xs">
|
|
190
|
+
{installed ? 'Installed' : 'Not installed'}
|
|
191
|
+
</p>
|
|
192
|
+
</div>
|
|
193
|
+
<ChevronRight className="h-4 w-4 shrink-0 text-sidebar-foreground/50" />
|
|
194
|
+
</div>
|
|
195
|
+
<p className="mt-2 text-sidebar-foreground/60 text-sm">
|
|
196
|
+
{panel.description ?? 'Adds a new tool panel to the editor.'}
|
|
197
|
+
</p>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</button>
|
|
201
|
+
)
|
|
202
|
+
})}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div className="mt-auto pt-6">
|
|
206
|
+
<a
|
|
207
|
+
className="inline-flex items-center gap-1.5 text-sidebar-foreground/70 text-sm underline-offset-4 hover:text-sidebar-foreground hover:underline"
|
|
208
|
+
href={PLUGIN_AUTHORING_URL}
|
|
209
|
+
rel="noreferrer"
|
|
210
|
+
target="_blank"
|
|
211
|
+
>
|
|
212
|
+
Create a Pascal plugin
|
|
213
|
+
<ExternalLink className="h-3.5 w-3.5" />
|
|
214
|
+
</a>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
clearSceneHistory,
|
|
3
|
+
emitter,
|
|
4
|
+
useScene,
|
|
5
|
+
validateBuildJson,
|
|
6
|
+
} from '@pascal-app/core'
|
|
2
7
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
8
|
import { TreeView, VisualJson } from '@visual-json/react'
|
|
4
|
-
import { Camera, Download, Save, Trash2, Upload } from 'lucide-react'
|
|
9
|
+
import { Camera, Download, Map as MapIcon, Save, Trash2, Upload } from 'lucide-react'
|
|
5
10
|
import {
|
|
6
11
|
type KeyboardEvent,
|
|
7
12
|
type SyntheticEvent,
|
|
@@ -10,6 +15,7 @@ import {
|
|
|
10
15
|
useRef,
|
|
11
16
|
useState,
|
|
12
17
|
} from 'react'
|
|
18
|
+
import { exportFloorplanPdf } from '../../../../../lib/floorplan/floorplan-export'
|
|
13
19
|
import { Button } from './../../../../../components/ui/primitives/button'
|
|
14
20
|
import {
|
|
15
21
|
Dialog,
|
|
@@ -19,6 +25,7 @@ import {
|
|
|
19
25
|
} from './../../../../../components/ui/primitives/dialog'
|
|
20
26
|
import { Switch } from './../../../../../components/ui/primitives/switch'
|
|
21
27
|
import useEditor, { selectDefaultBuildingAndLevel } from './../../../../../store/use-editor'
|
|
28
|
+
import useFloorplanMode from './../../../../../store/use-floorplan-mode'
|
|
22
29
|
import { AudioSettingsDialog } from './audio-settings-dialog'
|
|
23
30
|
import { KeyboardShortcutsDialog } from './keyboard-shortcuts-dialog'
|
|
24
31
|
import { LoadBuildDialog, type PendingImport } from './load-build-dialog'
|
|
@@ -179,13 +186,14 @@ export function SettingsPanel({
|
|
|
179
186
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
180
187
|
const nodes = useScene((state) => state.nodes)
|
|
181
188
|
const rootNodeIds = useScene((state) => state.rootNodeIds)
|
|
189
|
+
const installedPlugins = useScene((state) => state.installedPlugins)
|
|
182
190
|
const setScene = useScene((state) => state.setScene)
|
|
183
191
|
const clearScene = useScene((state) => state.clearScene)
|
|
184
192
|
const resetSelection = useViewer((state) => state.resetSelection)
|
|
185
193
|
const exportScene = useViewer((state) => state.exportScene)
|
|
186
|
-
const showGrid = useViewer((state) => state.showGrid)
|
|
187
194
|
const shadows = useViewer((state) => state.shadows)
|
|
188
195
|
const setPhase = useEditor((state) => state.setPhase)
|
|
196
|
+
const floorplanMode = useFloorplanMode((state) => state.mode)
|
|
189
197
|
const [isGeneratingThumbnail, setIsGeneratingThumbnail] = useState(false)
|
|
190
198
|
const [pendingImport, setPendingImport] = useState<PendingImport | null>(null)
|
|
191
199
|
const sceneGraphValue = useMemo(
|
|
@@ -206,7 +214,7 @@ export function SettingsPanel({
|
|
|
206
214
|
const isLocalProject = false // Props-based; only show cloud sections when projectId provided
|
|
207
215
|
|
|
208
216
|
const handleSaveBuild = () => {
|
|
209
|
-
const sceneData = { nodes, rootNodeIds }
|
|
217
|
+
const sceneData = { nodes, rootNodeIds, installedPlugins }
|
|
210
218
|
const json = JSON.stringify(sceneData, null, 2)
|
|
211
219
|
const blob = new Blob([json], { type: 'application/json' })
|
|
212
220
|
const url = URL.createObjectURL(blob)
|
|
@@ -235,7 +243,7 @@ export function SettingsPanel({
|
|
|
235
243
|
result: {
|
|
236
244
|
ok: false,
|
|
237
245
|
parsed: null,
|
|
238
|
-
stats: { total: 0, byType: {}, unknownTypes: {}, floorAreaM2: 0 },
|
|
246
|
+
stats: { total: 0, byType: {}, pluginTypes: {}, unknownTypes: {}, floorAreaM2: 0 },
|
|
239
247
|
errors: [
|
|
240
248
|
{
|
|
241
249
|
severity: 'error',
|
|
@@ -262,11 +270,24 @@ export function SettingsPanel({
|
|
|
262
270
|
e.target.value = ''
|
|
263
271
|
}
|
|
264
272
|
|
|
265
|
-
const handleConfirmImport = (parsed: {
|
|
273
|
+
const handleConfirmImport = (parsed: {
|
|
274
|
+
nodes: Record<string, unknown>
|
|
275
|
+
rootNodeIds: string[]
|
|
276
|
+
installedPlugins?: string[]
|
|
277
|
+
}) => {
|
|
278
|
+
const currentScene = useScene.getState()
|
|
266
279
|
setScene(
|
|
267
280
|
parsed.nodes as Parameters<typeof setScene>[0],
|
|
268
281
|
parsed.rootNodeIds as Parameters<typeof setScene>[1],
|
|
282
|
+
{
|
|
283
|
+
installedPlugins: parsed.installedPlugins ?? currentScene.installedPlugins,
|
|
284
|
+
hasExplicitPluginInstallState:
|
|
285
|
+
parsed.installedPlugins !== undefined || currentScene.hasExplicitPluginInstallState,
|
|
286
|
+
},
|
|
269
287
|
)
|
|
288
|
+
// An import is a scene load: it becomes the undo floor. Without this,
|
|
289
|
+
// undo could step back into the pre-import scene state.
|
|
290
|
+
clearSceneHistory()
|
|
270
291
|
resetSelection()
|
|
271
292
|
setPhase('site')
|
|
272
293
|
setPendingImport(null)
|
|
@@ -274,6 +295,9 @@ export function SettingsPanel({
|
|
|
274
295
|
|
|
275
296
|
const handleResetToDefault = () => {
|
|
276
297
|
clearScene()
|
|
298
|
+
// Same floor rule as import — undo after a reset must not resurrect the
|
|
299
|
+
// old scene (or land on the empty intermediate `unloadScene` state).
|
|
300
|
+
clearSceneHistory()
|
|
277
301
|
resetSelection()
|
|
278
302
|
setPhase('structure')
|
|
279
303
|
selectDefaultBuildingAndLevel()
|
|
@@ -331,16 +355,6 @@ export function SettingsPanel({
|
|
|
331
355
|
onCheckedChange={(checked) => handleVisibilityChange('showGuidesPublic', checked)}
|
|
332
356
|
/>
|
|
333
357
|
</div>
|
|
334
|
-
<div className="flex items-center justify-between">
|
|
335
|
-
<div>
|
|
336
|
-
<div className="font-medium text-sm">Show Grid</div>
|
|
337
|
-
<div className="text-muted-foreground text-xs">Visible only in the editor</div>
|
|
338
|
-
</div>
|
|
339
|
-
<Switch
|
|
340
|
-
checked={showGrid}
|
|
341
|
-
onCheckedChange={(checked) => useViewer.getState().setShowGrid(checked)}
|
|
342
|
-
/>
|
|
343
|
-
</div>
|
|
344
358
|
<div className="flex items-center justify-between">
|
|
345
359
|
<div>
|
|
346
360
|
<div className="font-medium text-sm">Shadows</div>
|
|
@@ -355,32 +369,59 @@ export function SettingsPanel({
|
|
|
355
369
|
)}
|
|
356
370
|
|
|
357
371
|
{/* Export Section */}
|
|
358
|
-
<div className="space-y-
|
|
372
|
+
<div className="space-y-4">
|
|
359
373
|
<label className="font-medium text-muted-foreground text-xs uppercase">Export</label>
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
374
|
+
|
|
375
|
+
<div className="space-y-2">
|
|
376
|
+
<div className="font-medium text-muted-foreground text-xs">3D model</div>
|
|
377
|
+
<Button
|
|
378
|
+
className="w-full justify-start gap-2"
|
|
379
|
+
onClick={() => exportScene?.('glb')}
|
|
380
|
+
variant="outline"
|
|
381
|
+
>
|
|
382
|
+
<Download className="size-4" />
|
|
383
|
+
Export GLB
|
|
384
|
+
</Button>
|
|
385
|
+
<Button
|
|
386
|
+
className="w-full justify-start gap-2"
|
|
387
|
+
onClick={() => exportScene?.('stl')}
|
|
388
|
+
variant="outline"
|
|
389
|
+
>
|
|
390
|
+
<Download className="size-4" />
|
|
391
|
+
Export STL
|
|
392
|
+
</Button>
|
|
393
|
+
<Button
|
|
394
|
+
className="w-full justify-start gap-2"
|
|
395
|
+
onClick={() => exportScene?.('obj')}
|
|
396
|
+
variant="outline"
|
|
397
|
+
>
|
|
398
|
+
<Download className="size-4" />
|
|
399
|
+
Export OBJ
|
|
400
|
+
</Button>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<div className="space-y-2">
|
|
404
|
+
<div className="flex items-center justify-between font-medium text-muted-foreground text-xs">
|
|
405
|
+
<span>Floor plan</span>
|
|
406
|
+
<span>{floorplanMode === 'default' ? 'Default mode' : 'Expert mode'}</span>
|
|
407
|
+
</div>
|
|
408
|
+
<Button
|
|
409
|
+
className="w-full justify-start gap-2"
|
|
410
|
+
onClick={() => exportFloorplanPdf('full')}
|
|
411
|
+
variant="outline"
|
|
412
|
+
>
|
|
413
|
+
<MapIcon className="size-4" />
|
|
414
|
+
Full floor plan
|
|
415
|
+
</Button>
|
|
416
|
+
<Button
|
|
417
|
+
className="w-full justify-start gap-2"
|
|
418
|
+
onClick={() => exportFloorplanPdf('structure')}
|
|
419
|
+
variant="outline"
|
|
420
|
+
>
|
|
421
|
+
<MapIcon className="size-4" />
|
|
422
|
+
Structure only
|
|
423
|
+
</Button>
|
|
424
|
+
</div>
|
|
384
425
|
</div>
|
|
385
426
|
|
|
386
427
|
{/* Thumbnail Section (only for cloud projects) */}
|
|
@@ -55,6 +55,7 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
|
|
|
55
55
|
shortcuts: [
|
|
56
56
|
{ keys: ['V'], action: 'Switch to Select mode' },
|
|
57
57
|
{ keys: ['B'], action: 'Switch to Build mode' },
|
|
58
|
+
{ keys: ['M'], action: 'Activate the last measurement tool' },
|
|
58
59
|
{ keys: ['X'], action: 'Switch to Delete mode' },
|
|
59
60
|
{
|
|
60
61
|
keys: ['Esc'],
|
|
@@ -68,10 +69,67 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
|
|
|
68
69
|
{
|
|
69
70
|
title: 'Selection',
|
|
70
71
|
shortcuts: [
|
|
72
|
+
{
|
|
73
|
+
keys: ['Cmd/Ctrl', 'C'],
|
|
74
|
+
action: 'Copy the selected objects',
|
|
75
|
+
note: 'The copied selection can be pasted into another level, project, or browser tab.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
keys: ['Cmd/Ctrl', 'X'],
|
|
79
|
+
action: 'Cut the selected objects',
|
|
80
|
+
note: 'Copies the selection to the clipboard, then removes it from this scene.',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
keys: ['Cmd/Ctrl', 'V'],
|
|
84
|
+
action: 'Paste and place copied objects',
|
|
85
|
+
note:
|
|
86
|
+
'Carries a preview under the cursor. Click to place it, or press Escape to cancel.',
|
|
87
|
+
},
|
|
71
88
|
{
|
|
72
89
|
keys: ['Cmd/Ctrl', 'Left click'],
|
|
73
90
|
action: 'Add or remove an object from multi-selection',
|
|
74
|
-
note: 'Works
|
|
91
|
+
note: 'Works in Select mode on the 3D canvas, the 2D floor plan, and the scene graph.',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
keys: ['Shift', 'Left click'],
|
|
95
|
+
action: 'Add or remove an object from canvas multi-selection',
|
|
96
|
+
note: 'In the scene graph, Shift-click selects the visible range like a file browser.',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
keys: ['Left click'],
|
|
100
|
+
action: 'Move the whole multi-selection',
|
|
101
|
+
note:
|
|
102
|
+
'With 2+ objects selected, in 2D and 3D alike: drag the selection (or its dashed box) to slide it; click it to pick it up and place with the next click.',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
keys: ['R', 'T'],
|
|
106
|
+
action: 'Rotate a multi-selection ±45° around its center',
|
|
107
|
+
note: 'Also works mid-move while carrying the selection.',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
keys: ['Esc'],
|
|
111
|
+
action: 'Clear the selection',
|
|
112
|
+
note: 'Clicking empty space does the same.',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
title: 'Direct Manipulation',
|
|
118
|
+
shortcuts: [
|
|
119
|
+
{
|
|
120
|
+
keys: ['Cmd/Ctrl', 'Left click'],
|
|
121
|
+
action: 'Move the selected movable object under the cursor',
|
|
122
|
+
note: 'Drag in Select mode with a single object selected. Guided snapping and guides are enabled by default.',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
keys: ['Cmd/Ctrl', 'Right click'],
|
|
126
|
+
action: 'Rotate the selected object under the cursor',
|
|
127
|
+
note: 'Drag left or right in Select mode with a single object selected. Rotation snaps to 15° increments by default.',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
keys: ['Cmd/Ctrl', 'Shift', 'Right click'],
|
|
131
|
+
action: 'Rotate freely',
|
|
132
|
+
note: 'Hold Shift during the drag to bypass the 15° rotation increment.',
|
|
75
133
|
},
|
|
76
134
|
],
|
|
77
135
|
},
|
|
@@ -80,16 +138,27 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
|
|
|
80
138
|
shortcuts: [
|
|
81
139
|
{
|
|
82
140
|
keys: ['Shift'],
|
|
83
|
-
action: '
|
|
84
|
-
note: 'Hold
|
|
141
|
+
action: 'Bypass guided snapping and angle constraints',
|
|
142
|
+
note: 'Hold during the active gesture. Passive guide or measurement feedback may stay visible.',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
keys: ['Shift'],
|
|
146
|
+
action: 'Rotate freely, bypassing the default 15° rotation snap',
|
|
147
|
+
note: 'Hold while dragging a rotate handle or direct-rotation gesture.',
|
|
85
148
|
},
|
|
86
149
|
],
|
|
87
150
|
},
|
|
88
151
|
{
|
|
89
152
|
title: 'Item Placement',
|
|
90
153
|
shortcuts: [
|
|
91
|
-
{
|
|
92
|
-
|
|
154
|
+
{
|
|
155
|
+
keys: ['R', 'T'],
|
|
156
|
+
action: 'Rotate item; with a door selected, R toggles open/closed and T closes',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
keys: ['E'],
|
|
160
|
+
action: 'Operate the selected node — doors, windows, and cabinet doors/drawers animate open/closed',
|
|
161
|
+
},
|
|
93
162
|
{
|
|
94
163
|
keys: ['Shift'],
|
|
95
164
|
action: 'Temporarily bypass placement validation constraints',
|
|
@@ -157,7 +226,8 @@ export function KeyboardShortcutsDialog() {
|
|
|
157
226
|
<DialogHeader className="shrink-0 border-b px-6 py-4">
|
|
158
227
|
<DialogTitle>Keyboard Shortcuts</DialogTitle>
|
|
159
228
|
<DialogDescription>
|
|
160
|
-
Shortcuts are context-aware
|
|
229
|
+
Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift
|
|
230
|
+
during an active gesture to build freely.
|
|
161
231
|
</DialogDescription>
|
|
162
232
|
</DialogHeader>
|
|
163
233
|
|