@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,6 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { Icon } from '@iconify/react'
|
|
4
3
|
import {
|
|
5
4
|
type AnyNodeId,
|
|
6
5
|
type BuildingNode,
|
|
@@ -10,26 +9,22 @@ import {
|
|
|
10
9
|
useScene,
|
|
11
10
|
} from '@pascal-app/core'
|
|
12
11
|
import { useViewer } from '@pascal-app/viewer'
|
|
13
|
-
import { Check, ChevronDown, Eye, EyeOff, Layers2, Plus, Trash2 } from 'lucide-react'
|
|
12
|
+
import { Check, ChevronDown, Eye, EyeOff, Layers2, Plus, Trash2, Waypoints } from 'lucide-react'
|
|
14
13
|
import { useCallback, useRef, useState } from 'react'
|
|
15
14
|
import { useShallow } from 'zustand/react/shallow'
|
|
16
|
-
import { getLevelDisplayName } from '
|
|
15
|
+
import { getLevelDisplayName } from '@pascal-app/core'
|
|
17
16
|
import { createLocalGuideImage } from '../../../lib/local-guide-image'
|
|
18
17
|
import { cn } from '../../../lib/utils'
|
|
19
|
-
import useEditor
|
|
18
|
+
import useEditor from '../../../store/use-editor'
|
|
20
19
|
import { useUploadStore } from '../../../store/use-upload'
|
|
21
20
|
import { SliderControl } from '../controls/slider-control'
|
|
22
21
|
import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover'
|
|
23
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
|
|
24
22
|
import { ActionButton } from './action-button'
|
|
25
23
|
|
|
26
24
|
const MAX_FILE_SIZE = 200 * 1024 * 1024 // 200MB
|
|
27
25
|
const ACCEPTED_FILE_TYPES = '.glb,.gltf,image/jpeg,image/png,image/webp,image/gif'
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
function formatGridSnapStep(step: GridSnapStep) {
|
|
31
|
-
return step.toFixed(2)
|
|
32
|
-
}
|
|
26
|
+
const REFERENCES_EMPTY_TEXT =
|
|
27
|
+
'Upload GLB meshes as scan references or blueprint images as guide references.'
|
|
33
28
|
|
|
34
29
|
// ── Helper: get guide images for the current level ──────────────────────────
|
|
35
30
|
|
|
@@ -224,7 +219,7 @@ function GuidesControl() {
|
|
|
224
219
|
<img
|
|
225
220
|
alt="Guides"
|
|
226
221
|
className="h-[28px] w-[28px] object-contain"
|
|
227
|
-
src="/icons/floorplan.
|
|
222
|
+
src="/icons/floorplan.webp"
|
|
228
223
|
/>
|
|
229
224
|
<span className="absolute -right-1.5 -bottom-1 min-w-[14px] rounded-full bg-white/20 px-[3px] text-center font-medium text-[9px] text-white/70 leading-[14px]">
|
|
230
225
|
{guides.length}
|
|
@@ -263,7 +258,7 @@ function GuidesControl() {
|
|
|
263
258
|
<div className="space-y-3">
|
|
264
259
|
<div className="flex items-center gap-2">
|
|
265
260
|
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-background/80">
|
|
266
|
-
<img alt="" className="h-4 w-4 object-contain" src="/icons/floorplan.
|
|
261
|
+
<img alt="" className="h-4 w-4 object-contain" src="/icons/floorplan.webp" />
|
|
267
262
|
</span>
|
|
268
263
|
<div className="min-w-0 flex-1">
|
|
269
264
|
<p className="font-medium text-foreground text-sm">Guide images</p>
|
|
@@ -303,7 +298,7 @@ function GuidesControl() {
|
|
|
303
298
|
<img
|
|
304
299
|
alt=""
|
|
305
300
|
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70"
|
|
306
|
-
src="/icons/floorplan.
|
|
301
|
+
src="/icons/floorplan.webp"
|
|
307
302
|
/>
|
|
308
303
|
<p className="truncate font-medium text-foreground text-sm">
|
|
309
304
|
{guide.name || `Guide image ${index + 1}`}
|
|
@@ -342,7 +337,7 @@ function GuidesControl() {
|
|
|
342
337
|
</div>
|
|
343
338
|
) : (
|
|
344
339
|
<div className="rounded-xl border border-border/45 border-dashed bg-background/60 px-3 py-4 text-muted-foreground text-sm">
|
|
345
|
-
|
|
340
|
+
{REFERENCES_EMPTY_TEXT}
|
|
346
341
|
</div>
|
|
347
342
|
)}
|
|
348
343
|
</div>
|
|
@@ -351,70 +346,6 @@ function GuidesControl() {
|
|
|
351
346
|
)
|
|
352
347
|
}
|
|
353
348
|
|
|
354
|
-
// ── Grid snap toggle ────────────────────────────────────────────────────────
|
|
355
|
-
|
|
356
|
-
function GridSnapControl() {
|
|
357
|
-
const [isOpen, setIsOpen] = useState(false)
|
|
358
|
-
const gridSnapStep = useEditor((state) => state.gridSnapStep)
|
|
359
|
-
const setGridSnapStep = useEditor((state) => state.setGridSnapStep)
|
|
360
|
-
|
|
361
|
-
return (
|
|
362
|
-
<Popover onOpenChange={setIsOpen} open={isOpen}>
|
|
363
|
-
<Tooltip>
|
|
364
|
-
<TooltipTrigger asChild>
|
|
365
|
-
<PopoverTrigger asChild>
|
|
366
|
-
<button
|
|
367
|
-
aria-expanded={isOpen}
|
|
368
|
-
aria-label={`Grid snap: ${formatGridSnapStep(gridSnapStep)}`}
|
|
369
|
-
className={cn(
|
|
370
|
-
'flex h-11 w-11 flex-col items-center justify-center rounded-lg text-muted-foreground transition-all hover:bg-white/5 hover:text-foreground',
|
|
371
|
-
isOpen && 'bg-white/10 text-foreground',
|
|
372
|
-
)}
|
|
373
|
-
type="button"
|
|
374
|
-
>
|
|
375
|
-
<Icon height={16} icon="lucide:grid-2x2" width={16} />
|
|
376
|
-
<span className="mt-1 font-medium text-[9px] leading-none">
|
|
377
|
-
{formatGridSnapStep(gridSnapStep)}
|
|
378
|
-
</span>
|
|
379
|
-
</button>
|
|
380
|
-
</PopoverTrigger>
|
|
381
|
-
</TooltipTrigger>
|
|
382
|
-
<TooltipContent side="top">Grid snap: {formatGridSnapStep(gridSnapStep)}</TooltipContent>
|
|
383
|
-
</Tooltip>
|
|
384
|
-
|
|
385
|
-
<PopoverContent
|
|
386
|
-
align="center"
|
|
387
|
-
className="w-36 rounded-xl border-border/45 bg-background/96 p-2 shadow-elevation-3 backdrop-blur-xl"
|
|
388
|
-
side="top"
|
|
389
|
-
sideOffset={14}
|
|
390
|
-
>
|
|
391
|
-
<div className="space-y-1">
|
|
392
|
-
{GRID_SNAP_STEPS.map((step) => {
|
|
393
|
-
const isActive = step === gridSnapStep
|
|
394
|
-
return (
|
|
395
|
-
<button
|
|
396
|
-
className={cn(
|
|
397
|
-
'flex w-full items-center justify-between rounded-lg px-2.5 py-2 text-left text-sm transition-colors hover:bg-white/8',
|
|
398
|
-
isActive && 'bg-white/10 text-foreground',
|
|
399
|
-
)}
|
|
400
|
-
key={step}
|
|
401
|
-
onClick={() => {
|
|
402
|
-
setGridSnapStep(step)
|
|
403
|
-
setIsOpen(false)
|
|
404
|
-
}}
|
|
405
|
-
type="button"
|
|
406
|
-
>
|
|
407
|
-
<span>{formatGridSnapStep(step)}</span>
|
|
408
|
-
{isActive ? <Check className="h-3.5 w-3.5" /> : <span className="h-3.5 w-3.5" />}
|
|
409
|
-
</button>
|
|
410
|
-
)
|
|
411
|
-
})}
|
|
412
|
-
</div>
|
|
413
|
-
</PopoverContent>
|
|
414
|
-
</Popover>
|
|
415
|
-
)
|
|
416
|
-
}
|
|
417
|
-
|
|
418
349
|
// ── Scans toggle + dropdown ─────────────────────────────────────────────────
|
|
419
350
|
|
|
420
351
|
function ScansControl() {
|
|
@@ -464,7 +395,7 @@ function ScansControl() {
|
|
|
464
395
|
variant="ghost"
|
|
465
396
|
>
|
|
466
397
|
<div className="relative">
|
|
467
|
-
<img alt="Scans" className="h-[28px] w-[28px] object-contain" src="/icons/mesh.
|
|
398
|
+
<img alt="Scans" className="h-[28px] w-[28px] object-contain" src="/icons/mesh.webp" />
|
|
468
399
|
<span className="absolute -right-1.5 -bottom-1 min-w-[14px] rounded-full bg-white/20 px-[3px] text-center font-medium text-[9px] text-white/70 leading-[14px]">
|
|
469
400
|
{scans.length}
|
|
470
401
|
</span>
|
|
@@ -502,7 +433,7 @@ function ScansControl() {
|
|
|
502
433
|
<div className="space-y-3">
|
|
503
434
|
<div className="flex items-center gap-2">
|
|
504
435
|
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-background/80">
|
|
505
|
-
<img alt="" className="h-4 w-4 object-contain" src="/icons/mesh.
|
|
436
|
+
<img alt="" className="h-4 w-4 object-contain" src="/icons/mesh.webp" />
|
|
506
437
|
</span>
|
|
507
438
|
<div className="min-w-0 flex-1">
|
|
508
439
|
<p className="font-medium text-foreground text-sm">Scans</p>
|
|
@@ -542,7 +473,7 @@ function ScansControl() {
|
|
|
542
473
|
<img
|
|
543
474
|
alt=""
|
|
544
475
|
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70"
|
|
545
|
-
src="/icons/mesh.
|
|
476
|
+
src="/icons/mesh.webp"
|
|
546
477
|
/>
|
|
547
478
|
<p className="truncate font-medium text-foreground text-sm">
|
|
548
479
|
{scan.name || `Scan ${index + 1}`}
|
|
@@ -581,7 +512,7 @@ function ScansControl() {
|
|
|
581
512
|
</div>
|
|
582
513
|
) : (
|
|
583
514
|
<div className="rounded-xl border border-border/45 border-dashed bg-background/60 px-3 py-4 text-muted-foreground text-sm">
|
|
584
|
-
|
|
515
|
+
{REFERENCES_EMPTY_TEXT}
|
|
585
516
|
</div>
|
|
586
517
|
)}
|
|
587
518
|
</div>
|
|
@@ -763,7 +694,7 @@ function ReferencesControl() {
|
|
|
763
694
|
<img
|
|
764
695
|
alt="References"
|
|
765
696
|
className="h-[28px] w-[28px] object-contain"
|
|
766
|
-
src="/icons/floorplan.
|
|
697
|
+
src="/icons/floorplan.webp"
|
|
767
698
|
/>
|
|
768
699
|
<span className="absolute -right-1.5 -bottom-1 min-w-[14px] rounded-full bg-white/20 px-[3px] text-center font-medium text-[9px] text-white/70 leading-[14px]">
|
|
769
700
|
{total}
|
|
@@ -805,8 +736,8 @@ function ReferencesControl() {
|
|
|
805
736
|
</div>
|
|
806
737
|
)}
|
|
807
738
|
<ReferenceListSection
|
|
808
|
-
emptyText=
|
|
809
|
-
iconSrc="/icons/mesh.
|
|
739
|
+
emptyText={REFERENCES_EMPTY_TEXT}
|
|
740
|
+
iconSrc="/icons/mesh.webp"
|
|
810
741
|
nodes={scans}
|
|
811
742
|
noun="scan"
|
|
812
743
|
onError={setUploadError}
|
|
@@ -816,8 +747,8 @@ function ReferencesControl() {
|
|
|
816
747
|
/>
|
|
817
748
|
<div className="h-px bg-border/45" />
|
|
818
749
|
<ReferenceListSection
|
|
819
|
-
emptyText=
|
|
820
|
-
iconSrc="/icons/floorplan.
|
|
750
|
+
emptyText={REFERENCES_EMPTY_TEXT}
|
|
751
|
+
iconSrc="/icons/floorplan.webp"
|
|
821
752
|
nodes={guides}
|
|
822
753
|
noun="guide image"
|
|
823
754
|
onError={setUploadError}
|
|
@@ -987,9 +918,30 @@ function ReferenceFloorControl() {
|
|
|
987
918
|
)
|
|
988
919
|
}
|
|
989
920
|
|
|
990
|
-
// ──
|
|
921
|
+
// ── Riser diagram control ────────────────────────────────────────────────────
|
|
922
|
+
|
|
923
|
+
function RiserControl() {
|
|
924
|
+
const isRiserOpen = useEditor((state) => state.isRiserOpen)
|
|
925
|
+
const toggleRiserOpen = useEditor((state) => state.toggleRiserOpen)
|
|
926
|
+
|
|
927
|
+
return (
|
|
928
|
+
<ActionButton
|
|
929
|
+
className={cn(
|
|
930
|
+
isRiserOpen
|
|
931
|
+
? 'bg-white/15'
|
|
932
|
+
: 'opacity-60 grayscale hover:bg-white/5 hover:opacity-100 hover:grayscale-0',
|
|
933
|
+
)}
|
|
934
|
+
label="Riser diagram"
|
|
935
|
+
onClick={toggleRiserOpen}
|
|
936
|
+
size="icon"
|
|
937
|
+
variant="ghost"
|
|
938
|
+
>
|
|
939
|
+
<Waypoints className="h-4 w-4" />
|
|
940
|
+
</ActionButton>
|
|
941
|
+
)
|
|
942
|
+
}
|
|
991
943
|
|
|
992
|
-
|
|
944
|
+
// ── Exports ─────────────────────────────────────────────────────────────────
|
|
993
945
|
|
|
994
946
|
export function SecondaryToggles() {
|
|
995
947
|
return (
|
|
@@ -1002,10 +954,10 @@ export function SecondaryToggles() {
|
|
|
1002
954
|
export function ViewToggles() {
|
|
1003
955
|
return (
|
|
1004
956
|
<div className="flex items-center gap-1">
|
|
1005
|
-
<GridSnapControl />
|
|
1006
957
|
<ScansControl />
|
|
1007
958
|
<GuidesControl />
|
|
1008
959
|
<ReferenceFloorControl />
|
|
960
|
+
<RiserControl />
|
|
1009
961
|
</div>
|
|
1010
962
|
)
|
|
1011
963
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import type { AnyNodeId } from '@pascal-app/core'
|
|
4
|
-
import { LevelNode, useScene } from '@pascal-app/core'
|
|
4
|
+
import { DEFAULT_LEVEL_HEIGHT, LevelNode, useScene } from '@pascal-app/core'
|
|
5
5
|
import { useViewer } from '@pascal-app/viewer'
|
|
6
6
|
import {
|
|
7
7
|
AppWindow,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
Minimize2,
|
|
23
23
|
Moon,
|
|
24
24
|
MousePointer2,
|
|
25
|
+
Mountain,
|
|
25
26
|
Package,
|
|
26
27
|
PaintBucket,
|
|
27
28
|
PencilLine,
|
|
@@ -168,6 +169,17 @@ export function EditorCommands() {
|
|
|
168
169
|
setMode('material-paint')
|
|
169
170
|
}),
|
|
170
171
|
},
|
|
172
|
+
{
|
|
173
|
+
id: 'editor.mode.terrain-sculpt',
|
|
174
|
+
label: 'Sculpt Terrain',
|
|
175
|
+
group: 'Scene',
|
|
176
|
+
icon: <Mountain className="h-4 w-4" />,
|
|
177
|
+
keywords: ['terrain', 'ground', 'elevation', 'sculpt', 'hill', 'slope', 'grade', 'dig'],
|
|
178
|
+
shortcut: ['G'],
|
|
179
|
+
// No `setPhase`: `setMode` moves to the site phase itself, and doing it
|
|
180
|
+
// here would set the phase twice with a mode reset in between.
|
|
181
|
+
execute: () => run(() => setMode('terrain-sculpt')),
|
|
182
|
+
},
|
|
171
183
|
|
|
172
184
|
// ── Levels ───────────────────────────────────────────────────────────
|
|
173
185
|
{
|
|
@@ -196,6 +208,7 @@ export function EditorCommands() {
|
|
|
196
208
|
).length
|
|
197
209
|
const newLevel = LevelNode.parse({
|
|
198
210
|
level: levelCount,
|
|
211
|
+
height: DEFAULT_LEVEL_HEIGHT,
|
|
199
212
|
children: [],
|
|
200
213
|
parentId: building.id,
|
|
201
214
|
})
|
|
@@ -245,10 +258,10 @@ export function EditorCommands() {
|
|
|
245
258
|
label: 'Wall Mode',
|
|
246
259
|
group: 'Viewer Controls',
|
|
247
260
|
icon: <Layers className="h-4 w-4" />,
|
|
248
|
-
keywords: ['wall', 'cutaway', 'up', 'down', 'view'],
|
|
261
|
+
keywords: ['wall', 'cutaway', 'up', 'down', 'translucent', 'view'],
|
|
249
262
|
badge: () => {
|
|
250
263
|
const mode = useViewer.getState().wallMode
|
|
251
|
-
return { cutaway: 'Cutaway', up: 'Up', down: 'Down' }[mode]
|
|
264
|
+
return { cutaway: 'Cutaway', up: 'Up', down: 'Down', translucent: 'Translucent' }[mode]
|
|
252
265
|
},
|
|
253
266
|
navigate: true,
|
|
254
267
|
execute: () => navigateTo('wall-mode'),
|
|
@@ -10,7 +10,7 @@ import { useEffect, useState } from 'react'
|
|
|
10
10
|
import { create } from 'zustand'
|
|
11
11
|
import { useShallow } from 'zustand/shallow'
|
|
12
12
|
import { Dialog, DialogContent, DialogTitle } from './../../../components/ui/primitives/dialog'
|
|
13
|
-
import { getLevelDisplayName } from '
|
|
13
|
+
import { getLevelDisplayName } from '@pascal-app/core'
|
|
14
14
|
import { useCommandRegistry } from '../../../store/use-command-registry'
|
|
15
15
|
import { usePaletteViewRegistry } from '../../../store/use-palette-view-registry'
|
|
16
16
|
|
|
@@ -244,10 +244,11 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
|
|
|
244
244
|
setOpen(false)
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
const wallModeLabel: Record<'cutaway' | 'up' | 'down', string> = {
|
|
247
|
+
const wallModeLabel: Record<'cutaway' | 'up' | 'down' | 'translucent', string> = {
|
|
248
248
|
cutaway: 'Cutaway',
|
|
249
249
|
up: 'Up',
|
|
250
250
|
down: 'Down',
|
|
251
|
+
translucent: 'Translucent',
|
|
251
252
|
}
|
|
252
253
|
const levelModeLabel: Record<'manual' | 'stacked' | 'exploded' | 'solo', string> = {
|
|
253
254
|
manual: 'Manual',
|
|
@@ -373,7 +374,7 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
|
|
|
373
374
|
{/* ── Wall Mode sub-page ────────────────────────────────────── */}
|
|
374
375
|
{page === 'wall-mode' && (
|
|
375
376
|
<Command.Group heading="Wall Mode">
|
|
376
|
-
{(['cutaway', 'up', 'down'] as const).map((mode) => (
|
|
377
|
+
{(['cutaway', 'up', 'down', 'translucent'] as const).map((mode) => (
|
|
377
378
|
<OptionItem
|
|
378
379
|
isActive={wallMode === mode}
|
|
379
380
|
key={mode}
|
|
@@ -1,32 +1,49 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type AnyNodeId,
|
|
5
|
+
generateSceneMaterialId,
|
|
6
|
+
type SceneMaterialId,
|
|
7
|
+
toSceneMaterialRef,
|
|
8
|
+
useScene,
|
|
9
|
+
} from '@pascal-app/core'
|
|
4
10
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
|
-
import { Eraser, RotateCcw } from 'lucide-react'
|
|
6
|
-
import { useEffect } from 'react'
|
|
11
|
+
import { Eraser, Plus, RotateCcw } from 'lucide-react'
|
|
12
|
+
import { useEffect, useState } from 'react'
|
|
7
13
|
import {
|
|
8
14
|
buildResetSurfaceMaterialUpdates,
|
|
9
15
|
resolvePaintTargetFromSelection,
|
|
10
16
|
} from './../../../lib/material-paint'
|
|
11
17
|
import useEditor from './../../../store/use-editor'
|
|
12
18
|
import { Button } from '../primitives/button'
|
|
19
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
|
|
13
20
|
import { MaterialPicker } from './material-picker'
|
|
21
|
+
import { SceneMaterialList } from './scene-material-list'
|
|
14
22
|
|
|
15
23
|
/**
|
|
16
24
|
* Material picker for paint mode. Embedders render this wherever paint controls
|
|
17
25
|
* belong (the community editor places it in the Build sidebar while paint mode
|
|
18
|
-
* is active). It
|
|
19
|
-
*
|
|
26
|
+
* is active). It fills its container's height and lays out as three bands: a
|
|
27
|
+
* fixed control/category header, a single scrolling catalog grid, and a fixed
|
|
28
|
+
* scene-material footer (always visible, with a `+` to add a custom material).
|
|
20
29
|
*/
|
|
21
|
-
export
|
|
30
|
+
export type MaterialPaintPanelProps = {
|
|
31
|
+
/** When provided, the catalog grid leads with a "New material" tile that invokes it. */
|
|
32
|
+
onCreateMaterialRequest?: () => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function MaterialPaintPanel({ onCreateMaterialRequest }: MaterialPaintPanelProps) {
|
|
22
36
|
const activePaintMaterial = useEditor((state) => state.activePaintMaterial)
|
|
23
37
|
const activePaintTarget = useEditor((state) => state.activePaintTarget)
|
|
24
38
|
const setActivePaintMaterial = useEditor((state) => state.setActivePaintMaterial)
|
|
25
39
|
const setActivePaintTarget = useEditor((state) => state.setActivePaintTarget)
|
|
26
40
|
const paintEraser = useEditor((state) => state.paintEraser)
|
|
27
41
|
const setPaintEraser = useEditor((state) => state.setPaintEraser)
|
|
42
|
+
// Id of a just-created scene material whose inline editor should open on mount.
|
|
43
|
+
const [autoEditMaterialId, setAutoEditMaterialId] = useState<SceneMaterialId | null>(null)
|
|
28
44
|
const selectedIds = useViewer((state) => state.selection.selectedIds)
|
|
29
45
|
const nodes = useScene((state) => state.nodes)
|
|
46
|
+
const materialCount = useScene((state) => Object.keys(state.materials).length)
|
|
30
47
|
const selectedId = selectedIds.length === 1 ? (selectedIds[0] ?? null) : null
|
|
31
48
|
const selectedNode = selectedId ? nodes[selectedId as AnyNodeId] : null
|
|
32
49
|
const canResetSelection =
|
|
@@ -44,9 +61,34 @@ export function MaterialPaintPanel() {
|
|
|
44
61
|
useScene.getState().updateNodes(buildResetSurfaceMaterialUpdates(nodes, selectedNode))
|
|
45
62
|
}
|
|
46
63
|
|
|
64
|
+
// Create a blank custom scene material, select it as the brush (`scene:` ref so
|
|
65
|
+
// edits propagate), and open its inline editor. Available from any category.
|
|
66
|
+
const createCustomMaterial = () => {
|
|
67
|
+
const id = generateSceneMaterialId()
|
|
68
|
+
const count = Object.keys(useScene.getState().materials).length
|
|
69
|
+
useScene.getState().addSceneMaterial({
|
|
70
|
+
id,
|
|
71
|
+
name: `Material ${count + 1}`,
|
|
72
|
+
material: {
|
|
73
|
+
preset: 'custom',
|
|
74
|
+
properties: {
|
|
75
|
+
color: '#ffffff',
|
|
76
|
+
roughness: 0.5,
|
|
77
|
+
metalness: 0,
|
|
78
|
+
opacity: 1,
|
|
79
|
+
transparent: false,
|
|
80
|
+
side: 'front',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
setActivePaintMaterial({ materialPreset: toSceneMaterialRef(id), sourceTarget: activePaintTarget })
|
|
85
|
+
setAutoEditMaterialId(id)
|
|
86
|
+
}
|
|
87
|
+
|
|
47
88
|
return (
|
|
48
|
-
<div className="w-full
|
|
49
|
-
|
|
89
|
+
<div className="flex h-full min-h-0 w-full flex-col">
|
|
90
|
+
{/* Fixed: eraser / reset. */}
|
|
91
|
+
<div className="flex shrink-0 items-center gap-2 pb-2">
|
|
50
92
|
<Button
|
|
51
93
|
aria-pressed={paintEraser}
|
|
52
94
|
className="flex-1"
|
|
@@ -68,16 +110,49 @@ export function MaterialPaintPanel() {
|
|
|
68
110
|
Reset all
|
|
69
111
|
</Button>
|
|
70
112
|
</div>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
113
|
+
|
|
114
|
+
{/* Scrolls: category tabs (fixed inside) + catalog grid (the scroll). */}
|
|
115
|
+
<div className="min-h-0 flex-1">
|
|
116
|
+
<MaterialPicker
|
|
117
|
+
onCreateMaterialRequest={onCreateMaterialRequest}
|
|
118
|
+
onSelectMaterialPreset={(materialPreset) => {
|
|
119
|
+
setActivePaintMaterial({ materialPreset, sourceTarget: activePaintTarget })
|
|
120
|
+
}}
|
|
121
|
+
selectedMaterialPreset={activePaintMaterial?.materialPreset}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Fixed footer: scene materials, always visible, with a `+` to add one. */}
|
|
126
|
+
<div className="mt-2 shrink-0 space-y-1.5 border-border/60 border-t pt-2">
|
|
127
|
+
<div className="flex items-center justify-between">
|
|
128
|
+
<span className="font-medium text-muted-foreground text-xs uppercase tracking-[0.12em]">
|
|
129
|
+
Scene materials
|
|
130
|
+
</span>
|
|
131
|
+
<Tooltip>
|
|
132
|
+
<TooltipTrigger asChild>
|
|
133
|
+
<Button
|
|
134
|
+
aria-label="Add material"
|
|
135
|
+
onClick={createCustomMaterial}
|
|
136
|
+
size="icon-sm"
|
|
137
|
+
type="button"
|
|
138
|
+
variant="outline"
|
|
139
|
+
>
|
|
140
|
+
<Plus />
|
|
141
|
+
</Button>
|
|
142
|
+
</TooltipTrigger>
|
|
143
|
+
<TooltipContent>Add material</TooltipContent>
|
|
144
|
+
</Tooltip>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="subtle-scrollbar max-h-56 overflow-y-auto">
|
|
147
|
+
{materialCount > 0 ? (
|
|
148
|
+
<SceneMaterialList autoEditId={autoEditMaterialId} />
|
|
149
|
+
) : (
|
|
150
|
+
<p className="px-0.5 py-1 text-muted-foreground text-xs">
|
|
151
|
+
No custom materials yet — add one with +.
|
|
152
|
+
</p>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
81
156
|
</div>
|
|
82
157
|
)
|
|
83
158
|
}
|