@pascal-app/editor 1.0.3 → 1.0.4
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 +11 -9
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +315 -4
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/custom-camera-controls.tsx +202 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +36 -25
- package/src/components/editor/editor-layout-v2.tsx +131 -58
- package/src/components/editor/export-manager.tsx +34 -8
- package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
- package/src/components/editor/first-person/build-collider-world.ts +6 -2
- package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
- package/src/components/editor/first-person/mechanism-targets.ts +36 -0
- package/src/components/editor/first-person-controls.tsx +205 -2
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-panel.tsx +344 -342
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +54 -2
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +294 -169
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +225 -0
- package/src/components/editor/room-labels.test.ts +357 -0
- package/src/components/editor/room-labels.tsx +990 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +451 -317
- package/src/components/editor/slab-hole-highlights.tsx +3 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
- package/src/components/editor/snapshot-capture.test.ts +52 -4
- package/src/components/editor/snapshot-capture.ts +30 -5
- package/src/components/editor/thumbnail-generator.test.ts +308 -0
- package/src/components/editor/thumbnail-generator.tsx +1035 -229
- package/src/components/editor/use-floorplan-background-placement.ts +31 -11
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +670 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.tsx +220 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +8 -4
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/stair/stair-defaults.ts +0 -3
- package/src/components/tools/stair/stair-tool.tsx +90 -42
- package/src/components/tools/tool-manager.tsx +46 -20
- package/src/components/tools/wall/wall-drafting.test.ts +19 -4
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +3 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -1
- package/src/components/ui/action-menu/control-modes.tsx +2 -35
- package/src/components/ui/action-menu/measurement-control.tsx +3 -10
- package/src/components/ui/build-panel/build-panel.tsx +235 -0
- package/src/components/ui/command-palette/editor-commands.tsx +4 -30
- package/src/components/ui/command-palette/index.tsx +5 -24
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/floating-level-selector.tsx +9 -14
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
- package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
- package/src/components/ui/helpers/helper-manager.tsx +325 -14
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +78 -36
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -4
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +725 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +2 -0
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +145 -0
- package/src/components/ui/sidebar/icon-rail.tsx +5 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
- package/src/components/ui/sidebar/tab-bar.tsx +10 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +175 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +123 -9
- package/src/hooks/use-auto-save.ts +34 -8
- package/src/hooks/use-keyboard.test.ts +178 -3
- package/src/hooks/use-keyboard.ts +89 -71
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +48 -0
- package/src/hooks/use-placement-notice.ts +42 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +246 -39
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +48 -0
- package/src/lib/editor-views.ts +144 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +283 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +100 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +232 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
- package/src/lib/floorplan/site-plan/geometry.ts +557 -0
- package/src/lib/floorplan/site-plan/index.ts +71 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/fresh-planar-placement.test.ts +36 -0
- package/src/lib/fresh-planar-placement.ts +59 -36
- package/src/lib/geometry-script/author.ts +152 -0
- package/src/lib/geometry-script/client.ts +152 -0
- package/src/lib/geometry-script/protocol.ts +12 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +485 -0
- package/src/lib/glb-export.ts +237 -119
- package/src/lib/history.test.ts +6 -0
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +18 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +186 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +407 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +165 -0
- package/src/lib/orbit-floor-clearance.ts +106 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +111 -0
- package/src/lib/paint-pass-through.ts +76 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +266 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +1 -1
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +50 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +266 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +973 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +364 -0
- package/src/lib/scene-clipboard.ts +229 -48
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +53 -74
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +569 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +13 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +175 -0
- package/src/store/view-layout.ts +196 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -9,13 +9,16 @@ import {
|
|
|
9
9
|
type SlabNode,
|
|
10
10
|
useScene,
|
|
11
11
|
type WallNode,
|
|
12
|
+
type ZoneNode,
|
|
12
13
|
} from '@pascal-app/core'
|
|
13
14
|
import { useViewer } from '@pascal-app/viewer'
|
|
14
15
|
import { type ComponentType, lazy, Suspense, useMemo } from 'react'
|
|
15
16
|
import { useRegisteredToolEnabled } from '../../hooks/use-registered-tool-enabled'
|
|
17
|
+
import { useCeilingEditCeilingId } from '../../lib/ceiling-edit-session'
|
|
16
18
|
import type { ReshapeKind } from '../../lib/interaction/scope'
|
|
19
|
+
import { paintRegionTargets, usePaintRegionMode } from '../../lib/paint-region-mode'
|
|
17
20
|
import { siteBoundaryHandlesEnabled } from '../../lib/site-boundary'
|
|
18
|
-
import useEditor, { type
|
|
21
|
+
import useEditor, { type Tool } from '../../store/use-editor'
|
|
19
22
|
import useInteractionScope, {
|
|
20
23
|
useControlPointReshape,
|
|
21
24
|
useEditingHole,
|
|
@@ -29,10 +32,14 @@ import useInteractionScope, {
|
|
|
29
32
|
} from '../../store/use-interaction-scope'
|
|
30
33
|
import { Alignment3DGuideLayer } from '../editor/alignment-3d-guide-layer'
|
|
31
34
|
import { Elevation3DGuideLayer } from '../editor/elevation-3d-guide-layer'
|
|
35
|
+
import { FloorOpeningsOverlay3D } from '../editor/floor-openings-overlay'
|
|
36
|
+
import { FloorRegionControls3D } from '../editor/floor-region-controls'
|
|
37
|
+
import { OpenWallEnds3DLayer } from '../editor/open-wall-ends-3d-layer'
|
|
32
38
|
import { OpeningGuides3DLayer } from '../editor/opening-guides-3d-layer'
|
|
33
39
|
import { WallSnapBeaconLayer } from '../editor/wall-snap-beacon-layer'
|
|
34
40
|
import { ElevatorTool } from './elevator/elevator-tool'
|
|
35
41
|
import { MoveTool } from './item/move-tool'
|
|
42
|
+
import { WallPaintRegionTool } from './paint-region/wall-paint-region-tool'
|
|
36
43
|
import { RegistryToolProvider } from './registry-tool-context'
|
|
37
44
|
import {
|
|
38
45
|
getRegistryAffordanceTool,
|
|
@@ -100,21 +107,17 @@ function getRegistryTool(tool: Tool | null): ComponentType | null {
|
|
|
100
107
|
// Legacy tool fallbacks — kinds whose placement tools haven't migrated
|
|
101
108
|
// to `def.tool` yet. Wall / fence / slab / ceiling / door / window /
|
|
102
109
|
// item / shelf / spawn now go through the registry path above.
|
|
103
|
-
const tools:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
structure: {
|
|
108
|
-
stair: StairTool,
|
|
109
|
-
zone: ZoneTool,
|
|
110
|
-
},
|
|
111
|
-
furnish: {},
|
|
110
|
+
const tools: Partial<Record<Tool, React.FC>> = {
|
|
111
|
+
'property-line': SiteBoundaryEditor,
|
|
112
|
+
stair: StairTool,
|
|
113
|
+
zone: ZoneTool,
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
export const ToolManager: React.FC = () => {
|
|
115
117
|
const phase = useEditor((state) => state.phase)
|
|
116
118
|
const mode = useEditor((state) => state.mode)
|
|
117
119
|
const tool = useEditor((state) => state.tool)
|
|
120
|
+
const paintsWallRegions = usePaintRegionMode((state) => paintRegionTargets(state.mode).wall)
|
|
118
121
|
const registeredToolEnabled = useRegisteredToolEnabled(tool)
|
|
119
122
|
const movingNode = useMovingNode()
|
|
120
123
|
const registryToolOwnsPlacement = useInteractionScope(
|
|
@@ -157,7 +160,18 @@ export const ToolManager: React.FC = () => {
|
|
|
157
160
|
}
|
|
158
161
|
}, [reshapingNode, tangentReshape])
|
|
159
162
|
const editingHole = useEditingHole()
|
|
160
|
-
const
|
|
163
|
+
const editCeilingId = useCeilingEditCeilingId()
|
|
164
|
+
// A zone picked as a zone, from the Scene list (zoneId) or its area or pill (sole selection).
|
|
165
|
+
const zoneSelectionId = useViewer((state) => state.selection.zoneId)
|
|
166
|
+
const soleSelectedId = useViewer((state) =>
|
|
167
|
+
state.selection.selectedIds.length === 1 ? state.selection.selectedIds[0] : undefined,
|
|
168
|
+
)
|
|
169
|
+
const soleSelectedZone = useScene((state) =>
|
|
170
|
+
soleSelectedId && state.nodes[soleSelectedId as AnyNodeId]?.type === 'zone'
|
|
171
|
+
? (soleSelectedId as ZoneNode['id'])
|
|
172
|
+
: null,
|
|
173
|
+
)
|
|
174
|
+
const selectedZoneId = zoneSelectionId ?? soleSelectedZone
|
|
161
175
|
const selectedIds = useViewer((state) => state.selection.selectedIds)
|
|
162
176
|
const buildingId = useViewer((state) => state.selection.buildingId)
|
|
163
177
|
const activeLevelId = useViewer((state) => state.selection.levelId)
|
|
@@ -213,12 +227,14 @@ export const ToolManager: React.FC = () => {
|
|
|
213
227
|
// edge handles — mounts only for a sole selection.
|
|
214
228
|
const isSoleSelection = selectedIds.length === 1
|
|
215
229
|
|
|
216
|
-
// Show slab boundary editor
|
|
230
|
+
// Show slab boundary editor in select mode with a slab selected (but not editing a hole)
|
|
217
231
|
const showSlabBoundaryEditor =
|
|
218
|
-
phase === 'structure' &&
|
|
219
232
|
mode === 'select' &&
|
|
220
233
|
isSoleSelection &&
|
|
221
234
|
selectedSlabId !== undefined &&
|
|
235
|
+
// Derived plates (a footprint's floor, a room's raised plate) follow their
|
|
236
|
+
// rooms; only user-drawn slabs get the outline editor.
|
|
237
|
+
!selectedSlab?.plateRole &&
|
|
222
238
|
!isFloorplanDrivenReshape &&
|
|
223
239
|
!editingSlabHoleIsManual
|
|
224
240
|
|
|
@@ -230,26 +246,28 @@ export const ToolManager: React.FC = () => {
|
|
|
230
246
|
!isFloorplanDrivenReshape &&
|
|
231
247
|
editingSlabHoleIsManual
|
|
232
248
|
|
|
233
|
-
// Show ceiling boundary editor
|
|
249
|
+
// Show ceiling boundary editor only inside the ceiling's Edit ceiling session (not while editing a hole)
|
|
234
250
|
const showCeilingBoundaryEditor =
|
|
235
|
-
phase === 'structure' &&
|
|
236
251
|
mode === 'select' &&
|
|
237
252
|
isSoleSelection &&
|
|
238
253
|
selectedCeilingId !== undefined &&
|
|
254
|
+
selectedCeilingId === editCeilingId &&
|
|
239
255
|
!isFloorplanDrivenReshape &&
|
|
240
256
|
(!editingHole || editingHole.nodeId !== selectedCeilingId)
|
|
241
257
|
|
|
242
|
-
// Show ceiling hole editor when editing a hole
|
|
258
|
+
// Show ceiling hole editor when editing a manual hole inside the ceiling's Edit ceiling session
|
|
243
259
|
const showCeilingHoleEditor =
|
|
244
260
|
selectedCeilingId !== undefined &&
|
|
261
|
+
selectedCeilingId === editCeilingId &&
|
|
245
262
|
editingHole !== null &&
|
|
246
263
|
editingHole.nodeId === selectedCeilingId &&
|
|
264
|
+
((nodes[selectedCeilingId as AnyNodeId] as CeilingNode).holeMetadata?.[editingHole.holeIndex]
|
|
265
|
+
?.source ?? 'manual') === 'manual' &&
|
|
247
266
|
!isFloorplanDrivenReshape
|
|
248
267
|
|
|
249
|
-
// Show zone boundary editor
|
|
268
|
+
// Show zone boundary editor in select mode with a zone selected
|
|
250
269
|
// Hide when editing a slab or ceiling to avoid overlapping handles
|
|
251
270
|
const showZoneBoundaryEditor =
|
|
252
|
-
phase === 'structure' &&
|
|
253
271
|
mode === 'select' &&
|
|
254
272
|
selectedZoneId !== null &&
|
|
255
273
|
!isFloorplanDrivenReshape &&
|
|
@@ -274,7 +292,7 @@ export const ToolManager: React.FC = () => {
|
|
|
274
292
|
const RegistryToolComponent = showBuildTool ? getRegistryTool(tool) : null
|
|
275
293
|
const useRegistryTool = RegistryToolComponent != null
|
|
276
294
|
|
|
277
|
-
const BuildToolComponent = showBuildTool && !useRegistryTool ? tools[
|
|
295
|
+
const BuildToolComponent = showBuildTool && !useRegistryTool ? tools[tool] : null
|
|
278
296
|
const handlePlacedNodeSelected = (nodeId: AnyNodeId) => {
|
|
279
297
|
setSelection({ selectedIds: [nodeId] })
|
|
280
298
|
}
|
|
@@ -298,10 +316,16 @@ export const ToolManager: React.FC = () => {
|
|
|
298
316
|
<RegistryToolProvider value={registryToolContext}>
|
|
299
317
|
{/* World-space tools: site boundary and building movement operate in world coordinates */}
|
|
300
318
|
{showSiteBoundaryEditor && <SiteBoundaryEditor />}
|
|
301
|
-
{/* Terrain sculpting is a mode rather than a `tools[
|
|
319
|
+
{/* Terrain sculpting is a mode rather than a `tools[tool]` entry —
|
|
302
320
|
it places no node — so it gets its own gate here. World-space, because
|
|
303
321
|
the ground is not building-local. */}
|
|
304
322
|
{sculpting && <TerrainSculptTool />}
|
|
323
|
+
{/* "Paint part of the floor": level-space, follows the level matrix itself. */}
|
|
324
|
+
<FloorRegionControls3D />
|
|
325
|
+
{/* Floor and ceiling openings: level-space, follows the level matrix itself. */}
|
|
326
|
+
<FloorOpeningsOverlay3D />
|
|
327
|
+
{/* "Paint part of the wall": follows the wall mesh's own matrix. */}
|
|
328
|
+
{mode === 'material-paint' && paintsWallRegions && <WallPaintRegionTool />}
|
|
305
329
|
{showMover && movingNode?.type === 'building' && (
|
|
306
330
|
<MoveTool onNodeMoved={handlePlacedNodeSelected} onSpawnMoved={handlePlacedNodeSelected} />
|
|
307
331
|
)}
|
|
@@ -454,6 +478,8 @@ export const ToolManager: React.FC = () => {
|
|
|
454
478
|
<Elevation3DGuideLayer />
|
|
455
479
|
{/* "Magnetic" beacon at the active wall-draft snap point. */}
|
|
456
480
|
<WallSnapBeaconLayer />
|
|
481
|
+
{/* Wall ends that aren't joined, with a one-click join — twin of the floor plan's. */}
|
|
482
|
+
<OpenWallEnds3DLayer />
|
|
457
483
|
</group>
|
|
458
484
|
</RegistryToolProvider>
|
|
459
485
|
)
|
|
@@ -22,6 +22,11 @@ import {
|
|
|
22
22
|
type WallNode,
|
|
23
23
|
WallNode as WallSchema,
|
|
24
24
|
} from '@pascal-app/core'
|
|
25
|
+
import {
|
|
26
|
+
migrateCeilingRoomLinks,
|
|
27
|
+
migrateFloorPlates,
|
|
28
|
+
migrateRoomZones,
|
|
29
|
+
} from '@pascal-app/core/scene-migrations'
|
|
25
30
|
import { useViewer } from '@pascal-app/viewer'
|
|
26
31
|
import useEditor from '../../../store/use-editor'
|
|
27
32
|
import useInteractionScope from '../../../store/use-interaction-scope'
|
|
@@ -721,7 +726,9 @@ describe('createWallOnCurrentLevel', () => {
|
|
|
721
726
|
expect(useScene.temporal.getState().pastStates.length - before).toBe(1)
|
|
722
727
|
})
|
|
723
728
|
|
|
724
|
-
test(
|
|
729
|
+
test.each([
|
|
730
|
+
0.05, 0.18,
|
|
731
|
+
])('a room divider preserves the footprint floor at %s and splits ceilings', (elevation) => {
|
|
725
732
|
const walls = [
|
|
726
733
|
makeWall([0, 0], [8, 0], 'wall_bottom'),
|
|
727
734
|
makeWall([8, 0], [8, 6], 'wall_right'),
|
|
@@ -737,7 +744,8 @@ describe('createWallOnCurrentLevel', () => {
|
|
|
737
744
|
[8, 6],
|
|
738
745
|
[0, 6],
|
|
739
746
|
],
|
|
740
|
-
elevation
|
|
747
|
+
elevation,
|
|
748
|
+
floorHeight: elevation,
|
|
741
749
|
thickness: 0.32,
|
|
742
750
|
autoFromWalls: true,
|
|
743
751
|
})
|
|
@@ -748,6 +756,11 @@ describe('createWallOnCurrentLevel', () => {
|
|
|
748
756
|
autoFromWalls: true,
|
|
749
757
|
})
|
|
750
758
|
seedLevel(walls, [slab, ceiling])
|
|
759
|
+
useScene.setState({
|
|
760
|
+
nodes: migrateFloorPlates(
|
|
761
|
+
migrateCeilingRoomLinks(migrateRoomZones(useScene.getState().nodes).nodes).nodes,
|
|
762
|
+
).nodes as Record<AnyNodeId, AnyNode>,
|
|
763
|
+
})
|
|
751
764
|
const stopDetection = initSpaceDetectionSync(useScene, useEditor)
|
|
752
765
|
|
|
753
766
|
try {
|
|
@@ -756,11 +769,13 @@ describe('createWallOnCurrentLevel', () => {
|
|
|
756
769
|
const nodes = Object.values(useScene.getState().nodes)
|
|
757
770
|
const slabs = nodes.filter((node) => node.type === 'slab')
|
|
758
771
|
const ceilings = nodes.filter((node) => node.type === 'ceiling')
|
|
759
|
-
expect(slabs).toHaveLength(
|
|
772
|
+
expect(slabs).toHaveLength(1)
|
|
773
|
+
expect(slabs[0]).toMatchObject({ plateRole: 'base', floorHeight: elevation })
|
|
774
|
+
expect(slabs[0]!.zoneIds).toHaveLength(2)
|
|
760
775
|
expect(ceilings).toHaveLength(2)
|
|
761
776
|
expect(
|
|
762
777
|
slabs.every(
|
|
763
|
-
(node) => node.autoFromWalls && node.elevation ===
|
|
778
|
+
(node) => node.autoFromWalls && node.elevation === elevation && node.thickness === 0.32,
|
|
764
779
|
),
|
|
765
780
|
).toBe(true)
|
|
766
781
|
expect(ceilings.every((node) => node.autoFromWalls)).toBe(true)
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
emitter,
|
|
4
4
|
type GridEvent,
|
|
5
5
|
type LevelNode,
|
|
6
|
+
newZone,
|
|
6
7
|
runAsSingleSceneHistoryStep,
|
|
7
8
|
snapPointAlongAngleRay,
|
|
8
9
|
useScene,
|
|
9
|
-
ZoneNode,
|
|
10
10
|
} from '@pascal-app/core'
|
|
11
11
|
import { useViewer } from '@pascal-app/viewer'
|
|
12
12
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from './../../../lib/surface-plan-snap'
|
|
20
20
|
import { focusedUnitNode } from './../../../lib/units'
|
|
21
21
|
import { snapWorldXZForActiveBuilding } from './../../../lib/world-grid-snap'
|
|
22
|
+
import { nextZoneName } from '../../../lib/zone-name'
|
|
22
23
|
import useEditor, { isAngleSnapActive, isGridSnapActive } from './../../../store/use-editor'
|
|
23
24
|
import { useFloorplanDraftPreview } from './../../../store/use-floorplan-draft-preview'
|
|
24
25
|
import { CursorSphere } from '../shared/cursor-sphere'
|
|
@@ -31,18 +32,9 @@ const Y_OFFSET = 0.02
|
|
|
31
32
|
const commitZoneDrawing = (levelId: LevelNode['id'], points: Array<[number, number]>) => {
|
|
32
33
|
const { createNode, updateNode, nodes } = useScene.getState()
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
const zoneCount = Object.values(nodes).filter((n) => n.type === 'zone').length
|
|
36
|
-
const name = `Zone ${zoneCount + 1}`
|
|
35
|
+
const name = nextZoneName(nodes)
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
const color = '#3b82f6'
|
|
40
|
-
|
|
41
|
-
const zone = ZoneNode.parse({
|
|
42
|
-
name,
|
|
43
|
-
polygon: points,
|
|
44
|
-
color,
|
|
45
|
-
})
|
|
37
|
+
const zone = newZone({ name, polygon: points })
|
|
46
38
|
|
|
47
39
|
const focusedUnit = focusedUnitNode()
|
|
48
40
|
|
|
@@ -36,6 +36,9 @@ export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProp
|
|
|
36
36
|
<Tooltip>
|
|
37
37
|
<TooltipTrigger asChild>
|
|
38
38
|
<Button
|
|
39
|
+
// These are toggles, not commands: the armed/unarmed state has to be
|
|
40
|
+
// readable without decoding the active-state styling.
|
|
41
|
+
aria-pressed={isActive}
|
|
39
42
|
className={cn('relative h-11 w-11 transition-all', className)}
|
|
40
43
|
onClick={(event) => {
|
|
41
44
|
triggerSFX('sfx:menu-click')
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
import { emitter } from '@pascal-app/core'
|
|
4
4
|
import Image from 'next/image'
|
|
5
5
|
import useEditor from '../../../store/use-editor'
|
|
6
|
+
import { visibleScene } from '../../../store/view-layout'
|
|
6
7
|
import { ActionButton } from './action-button'
|
|
7
8
|
|
|
8
9
|
export function CameraActions({ hideOrbit = false }: { hideOrbit?: boolean }) {
|
|
9
10
|
// Orbit stays useful in 2D-only (it spins the synced floorplan view), but
|
|
10
11
|
// top view only tilts the hidden 3D camera — pointless without the canvas.
|
|
11
|
-
const is2dOnly = useEditor((s) => s
|
|
12
|
+
const is2dOnly = useEditor((s) => visibleScene(s) === '2d')
|
|
12
13
|
|
|
13
14
|
const goToTopView = () => {
|
|
14
15
|
emitter.emit('camera-controls:top-view')
|
|
@@ -9,7 +9,7 @@ import useEditor from './../../../store/use-editor'
|
|
|
9
9
|
import { ActionButton } from './action-button'
|
|
10
10
|
import { MeasurementControl } from './measurement-control'
|
|
11
11
|
|
|
12
|
-
type ControlId = 'select' | 'box-select' | '
|
|
12
|
+
type ControlId = 'select' | 'box-select' | 'delete'
|
|
13
13
|
|
|
14
14
|
type ControlConfig = {
|
|
15
15
|
id: ControlId
|
|
@@ -32,14 +32,6 @@ const controls: ControlConfig[] = [
|
|
|
32
32
|
color: 'hover:bg-blue-500/20 hover:text-blue-400',
|
|
33
33
|
activeColor: 'bg-blue-500/20 text-blue-400',
|
|
34
34
|
},
|
|
35
|
-
{
|
|
36
|
-
id: 'zone',
|
|
37
|
-
imageSrc: '/icons/zone.webp',
|
|
38
|
-
label: 'Zone',
|
|
39
|
-
shortcut: 'Z',
|
|
40
|
-
color: 'hover:bg-green-500/20 hover:text-green-400',
|
|
41
|
-
activeColor: 'bg-green-500/20 text-green-400',
|
|
42
|
-
},
|
|
43
35
|
{
|
|
44
36
|
id: 'delete',
|
|
45
37
|
icon: Trash2,
|
|
@@ -52,49 +44,23 @@ const controls: ControlConfig[] = [
|
|
|
52
44
|
|
|
53
45
|
export function ControlModes() {
|
|
54
46
|
const mode = useEditor((state) => state.mode)
|
|
55
|
-
const phase = useEditor((state) => state.phase)
|
|
56
47
|
const selectionTool = useEditor((state) => state.floorplanSelectionTool)
|
|
57
48
|
const armToolMode = useEditor((state) => state.armToolMode)
|
|
58
|
-
const setPhase = useEditor((state) => state.setPhase)
|
|
59
|
-
const setStructureLayer = useEditor((state) => state.setStructureLayer)
|
|
60
49
|
const setSelectionTool = useEditor((state) => state.setFloorplanSelectionTool)
|
|
61
50
|
|
|
62
|
-
const isSiteEditing = phase === 'site'
|
|
63
|
-
|
|
64
|
-
const structureLayer = useEditor((state) => state.structureLayer)
|
|
65
|
-
|
|
66
51
|
const getIsActive = (id: ControlId): boolean => {
|
|
67
52
|
if (id === 'select') return mode === 'select' && selectionTool === 'click'
|
|
68
53
|
if (id === 'box-select') return mode === 'select' && selectionTool === 'marquee'
|
|
69
|
-
if (id === 'zone')
|
|
70
|
-
return mode === 'build' && phase === 'structure' && structureLayer === 'zones'
|
|
71
54
|
return mode === id
|
|
72
55
|
}
|
|
73
56
|
|
|
74
57
|
const handleClick = (id: ControlId) => {
|
|
75
|
-
// Exit site editing first if needed. Sculpting is a site-phase mode, so
|
|
76
|
-
// leaving the phase is exactly the right way to leave the brush — but the
|
|
77
|
-
// order matters: `setPhase` resets the mode, and setting the mode first
|
|
78
|
-
// would have it overwritten below.
|
|
79
|
-
if (isSiteEditing) {
|
|
80
|
-
setPhase('structure')
|
|
81
|
-
setStructureLayer('elements')
|
|
82
|
-
}
|
|
83
|
-
|
|
84
58
|
if (id === 'select') {
|
|
85
59
|
armToolMode({ mode: 'select' })
|
|
86
60
|
setSelectionTool('click')
|
|
87
61
|
} else if (id === 'box-select') {
|
|
88
62
|
armToolMode({ mode: 'select' })
|
|
89
63
|
setSelectionTool('marquee')
|
|
90
|
-
} else if (id === 'zone') {
|
|
91
|
-
if (getIsActive('zone')) {
|
|
92
|
-
armToolMode({ mode: 'select' })
|
|
93
|
-
} else {
|
|
94
|
-
setPhase('structure')
|
|
95
|
-
setStructureLayer('zones')
|
|
96
|
-
armToolMode({ mode: 'build', tool: 'zone' })
|
|
97
|
-
}
|
|
98
64
|
} else {
|
|
99
65
|
armToolMode({ mode: id })
|
|
100
66
|
}
|
|
@@ -121,6 +87,7 @@ export function ControlModes() {
|
|
|
121
87
|
// A static hook for a host app that wants to point a first-run
|
|
122
88
|
// tour at this button. Nothing here reads it.
|
|
123
89
|
data-guide-target={c.id === 'select' ? 'mode-select' : undefined}
|
|
90
|
+
isActive={isActive}
|
|
124
91
|
label={c.label}
|
|
125
92
|
onClick={() => handleClick(c.id)}
|
|
126
93
|
shortcut={c.shortcut}
|
|
@@ -22,6 +22,7 @@ import { useState } from 'react'
|
|
|
22
22
|
import type { CreatableMeasurementKind } from '../../../lib/measurement-kind'
|
|
23
23
|
import { cn } from '../../../lib/utils'
|
|
24
24
|
import useEditor from '../../../store/use-editor'
|
|
25
|
+
import { VIEW_2D } from '../../../store/view-layout'
|
|
25
26
|
import useFloorplanMode from '../../../store/use-floorplan-mode'
|
|
26
27
|
import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover'
|
|
27
28
|
import { ActionButton } from './action-button'
|
|
@@ -74,12 +75,10 @@ export function MeasurementControl() {
|
|
|
74
75
|
(state) => state.toolDefaults['construction-dimension']?.mode,
|
|
75
76
|
)
|
|
76
77
|
const setMode = useEditor((state) => state.setMode)
|
|
77
|
-
const setPhase = useEditor((state) => state.setPhase)
|
|
78
78
|
const setLastMeasurementKind = useEditor((state) => state.setLastMeasurementKind)
|
|
79
|
-
const setStructureLayer = useEditor((state) => state.setStructureLayer)
|
|
80
79
|
const setTool = useEditor((state) => state.setTool)
|
|
81
80
|
const setToolDefaults = useEditor((state) => state.setToolDefaults)
|
|
82
|
-
const
|
|
81
|
+
const showView = useEditor((state) => state.showView)
|
|
83
82
|
|
|
84
83
|
const selectedOption =
|
|
85
84
|
measurementOptions.find((option) => option.kind === selectedKind) ?? measurementOptions[0]
|
|
@@ -104,8 +103,6 @@ export function MeasurementControl() {
|
|
|
104
103
|
: selectedOption.label
|
|
105
104
|
|
|
106
105
|
const activateMeasurement = (kind: CreatableMeasurementKind) => {
|
|
107
|
-
setPhase('structure')
|
|
108
|
-
setStructureLayer('elements')
|
|
109
106
|
setLastMeasurementKind(kind)
|
|
110
107
|
setToolDefaults('measurement', { kind })
|
|
111
108
|
setMode('build')
|
|
@@ -121,8 +118,6 @@ export function MeasurementControl() {
|
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
const activateSmartMeasurement = () => {
|
|
124
|
-
setPhase('structure')
|
|
125
|
-
setStructureLayer('elements')
|
|
126
121
|
setToolDefaults('measurement', { kind: 'smart' })
|
|
127
122
|
setMode('build')
|
|
128
123
|
setTool('measurement')
|
|
@@ -136,9 +131,7 @@ export function MeasurementControl() {
|
|
|
136
131
|
useFloorplanMode.getState().showExpertModeNotice('Construction Dimension')
|
|
137
132
|
return
|
|
138
133
|
}
|
|
139
|
-
|
|
140
|
-
setStructureLayer('elements')
|
|
141
|
-
setViewMode('2d')
|
|
134
|
+
showView(VIEW_2D)
|
|
142
135
|
setToolDefaults('construction-dimension', { chainMode, mode: dimensionMode })
|
|
143
136
|
setMode('build')
|
|
144
137
|
setTool('construction-dimension')
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ChevronRight } from 'lucide-react'
|
|
4
|
+
import { type ComponentPropsWithoutRef, type ReactNode, useEffect, useState } from 'react'
|
|
5
|
+
import { cn } from '../../../lib/utils'
|
|
6
|
+
import {
|
|
7
|
+
WALL_DRAW_IDLE_CAPTION,
|
|
8
|
+
WALL_DRAW_VARIANTS,
|
|
9
|
+
type WallDrawVariant,
|
|
10
|
+
} from '../../../lib/wall-draw-variant'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The Build panel's grouped layout (Rooms / Add to rooms / Outdoor / Advanced),
|
|
14
|
+
* shared by the community and standalone editor Build tabs so both read the
|
|
15
|
+
* same. Hosts own what each tile arms; these only lay the groups out.
|
|
16
|
+
*/
|
|
17
|
+
export function BuildPanelSection({
|
|
18
|
+
children,
|
|
19
|
+
hint,
|
|
20
|
+
id,
|
|
21
|
+
title,
|
|
22
|
+
}: {
|
|
23
|
+
children: ReactNode
|
|
24
|
+
hint?: string
|
|
25
|
+
id: string
|
|
26
|
+
title: string
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
<section className="flex flex-col" data-build-group={id}>
|
|
30
|
+
<div className="mb-2 flex items-baseline justify-between gap-2 px-0.5">
|
|
31
|
+
<h3 className="font-medium text-foreground text-xs">{title}</h3>
|
|
32
|
+
{hint ? <span className="truncate text-[11px] text-muted-foreground">{hint}</span> : null}
|
|
33
|
+
</div>
|
|
34
|
+
{children}
|
|
35
|
+
</section>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ADVANCED_OPEN_KEY = 'pascal-build-advanced-open'
|
|
40
|
+
|
|
41
|
+
function readAdvancedOpen(): boolean {
|
|
42
|
+
try {
|
|
43
|
+
return window.localStorage.getItem(ADVANCED_OPEN_KEY) === '1'
|
|
44
|
+
} catch {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function writeAdvancedOpen(open: boolean) {
|
|
50
|
+
try {
|
|
51
|
+
window.localStorage.setItem(ADVANCED_OPEN_KEY, open ? '1' : '0')
|
|
52
|
+
} catch {
|
|
53
|
+
// Storage blocked (private window, preview): the section just starts closed.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The collapsible Advanced group. Remembers its open state per viewer, and
|
|
59
|
+
* opens itself when one of its tools becomes the armed one (e.g. by shortcut)
|
|
60
|
+
* so the lit tile is never hidden.
|
|
61
|
+
*/
|
|
62
|
+
export function BuildPanelAdvancedSection({
|
|
63
|
+
children,
|
|
64
|
+
containsActiveTool,
|
|
65
|
+
description,
|
|
66
|
+
hint,
|
|
67
|
+
}: {
|
|
68
|
+
children: ReactNode
|
|
69
|
+
containsActiveTool: boolean
|
|
70
|
+
description?: string
|
|
71
|
+
hint?: string
|
|
72
|
+
}) {
|
|
73
|
+
const [open, setOpen] = useState(false)
|
|
74
|
+
useEffect(() => setOpen(readAdvancedOpen()), [])
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (containsActiveTool) setOpen(true)
|
|
77
|
+
}, [containsActiveTool])
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<section className="flex flex-col" data-build-group="advanced">
|
|
81
|
+
<button
|
|
82
|
+
aria-expanded={open}
|
|
83
|
+
className="-mx-1.5 flex items-center gap-2 rounded-lg px-1.5 py-1.5 text-left transition-colors hover:bg-muted/60"
|
|
84
|
+
onClick={() => {
|
|
85
|
+
const next = !open
|
|
86
|
+
setOpen(next)
|
|
87
|
+
writeAdvancedOpen(next)
|
|
88
|
+
}}
|
|
89
|
+
type="button"
|
|
90
|
+
>
|
|
91
|
+
<ChevronRight
|
|
92
|
+
className={cn(
|
|
93
|
+
'size-3 shrink-0 text-muted-foreground transition-transform duration-150',
|
|
94
|
+
open && 'rotate-90',
|
|
95
|
+
)}
|
|
96
|
+
/>
|
|
97
|
+
<h3 className="font-medium text-foreground text-xs">Advanced</h3>
|
|
98
|
+
{hint ? (
|
|
99
|
+
<span className="ml-auto truncate text-[11px] text-muted-foreground">{hint}</span>
|
|
100
|
+
) : null}
|
|
101
|
+
</button>
|
|
102
|
+
{open ? (
|
|
103
|
+
<div className="flex flex-col gap-2.5 pt-1.5">
|
|
104
|
+
{description ? (
|
|
105
|
+
<p className="px-0.5 text-[11px] text-muted-foreground leading-relaxed">
|
|
106
|
+
{description}
|
|
107
|
+
</p>
|
|
108
|
+
) : null}
|
|
109
|
+
{children}
|
|
110
|
+
</div>
|
|
111
|
+
) : null}
|
|
112
|
+
</section>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function BuildToolGrid({ children, columns }: { children: ReactNode; columns: 3 | 4 }) {
|
|
117
|
+
return (
|
|
118
|
+
<div className={cn('grid gap-1.5', columns === 3 ? 'grid-cols-3' : 'grid-cols-4')}>
|
|
119
|
+
{children}
|
|
120
|
+
</div>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
type BuildToolTileProps = Omit<ComponentPropsWithoutRef<'button'>, 'children' | 'type'> & {
|
|
125
|
+
active: boolean
|
|
126
|
+
/** Corner mark, e.g. the snap-target badge of wall-hosted tools. */
|
|
127
|
+
badge?: ReactNode
|
|
128
|
+
iconSrc: string
|
|
129
|
+
label: string
|
|
130
|
+
size?: 'lg' | 'sm'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* One tool tile: the isometric icon over its name. Inactive icons stay grey and
|
|
135
|
+
* take their colour on hover; the armed tool keeps it, on the primary tint.
|
|
136
|
+
*/
|
|
137
|
+
export function BuildToolTile({
|
|
138
|
+
active,
|
|
139
|
+
badge,
|
|
140
|
+
className,
|
|
141
|
+
iconSrc,
|
|
142
|
+
label,
|
|
143
|
+
size = 'sm',
|
|
144
|
+
...buttonProps
|
|
145
|
+
}: BuildToolTileProps) {
|
|
146
|
+
return (
|
|
147
|
+
<button
|
|
148
|
+
aria-pressed={active}
|
|
149
|
+
className={cn(
|
|
150
|
+
'group relative flex min-w-0 flex-col items-center justify-end rounded-xl transition-all duration-200',
|
|
151
|
+
// Small tiles grow to two label lines ("Spawn point"); a grid row's
|
|
152
|
+
// tiles stretch together, so a row keeps one height.
|
|
153
|
+
size === 'lg' ? 'h-[104px] gap-1.5 px-1.5 pt-2 pb-2.5' : 'min-h-[74px] gap-1 px-1 pt-1.5 pb-2',
|
|
154
|
+
active ? 'bg-primary/10 ring-1 ring-primary/50' : 'bg-muted/40 hover:bg-muted',
|
|
155
|
+
className,
|
|
156
|
+
)}
|
|
157
|
+
type="button"
|
|
158
|
+
{...buttonProps}
|
|
159
|
+
>
|
|
160
|
+
<img
|
|
161
|
+
alt=""
|
|
162
|
+
className={cn(
|
|
163
|
+
'min-h-0 object-contain transition duration-200 group-hover:scale-110',
|
|
164
|
+
size === 'lg' ? 'size-14' : 'size-9',
|
|
165
|
+
!active && 'opacity-70 grayscale group-hover:opacity-100 group-hover:grayscale-0',
|
|
166
|
+
)}
|
|
167
|
+
draggable={false}
|
|
168
|
+
src={iconSrc}
|
|
169
|
+
/>
|
|
170
|
+
<span
|
|
171
|
+
className={cn(
|
|
172
|
+
'line-clamp-2 max-w-full text-center font-medium leading-tight',
|
|
173
|
+
size === 'lg' ? 'text-xs' : 'text-[11px]',
|
|
174
|
+
active ? 'text-foreground' : 'text-muted-foreground group-hover:text-foreground',
|
|
175
|
+
)}
|
|
176
|
+
>
|
|
177
|
+
{label}
|
|
178
|
+
</span>
|
|
179
|
+
{badge ? <span className="absolute top-1 right-1">{badge}</span> : null}
|
|
180
|
+
</button>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The line under the Rooms tiles: what the armed shape does, lead in bold. */
|
|
185
|
+
export function BuildPanelCaption({ lead, rest }: { lead?: string; rest: string }) {
|
|
186
|
+
return (
|
|
187
|
+
<p className="mt-2 min-h-[2lh] px-0.5 text-[11px] text-muted-foreground leading-relaxed">
|
|
188
|
+
{lead ? <span className="font-medium text-foreground">{lead} </span> : null}
|
|
189
|
+
{rest}
|
|
190
|
+
</p>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The Rooms group: one large tile per wall drawing variant, and the armed
|
|
196
|
+
* variant's caption. `activeVariant` is null while the wall tool is not armed.
|
|
197
|
+
*/
|
|
198
|
+
export function BuildPanelRoomsSection({
|
|
199
|
+
activeVariant,
|
|
200
|
+
onHover,
|
|
201
|
+
onSelect,
|
|
202
|
+
tileProps,
|
|
203
|
+
}: {
|
|
204
|
+
activeVariant: WallDrawVariant | null
|
|
205
|
+
onHover?: (variant: WallDrawVariant) => void
|
|
206
|
+
onSelect: (variant: WallDrawVariant) => void
|
|
207
|
+
/** Extra attributes per tile (e.g. a host's guide-target hook). */
|
|
208
|
+
tileProps?: (variant: WallDrawVariant) => Record<string, string | undefined>
|
|
209
|
+
}) {
|
|
210
|
+
const active = WALL_DRAW_VARIANTS.find((variant) => variant.id === activeVariant)
|
|
211
|
+
return (
|
|
212
|
+
<BuildPanelSection hint="walls · floor · ceiling" id="rooms" title="Rooms">
|
|
213
|
+
<BuildToolGrid columns={3}>
|
|
214
|
+
{WALL_DRAW_VARIANTS.map((variant) => (
|
|
215
|
+
<BuildToolTile
|
|
216
|
+
active={variant.id === activeVariant}
|
|
217
|
+
data-build-tool={`room-${variant.id}`}
|
|
218
|
+
iconSrc={variant.iconSrc}
|
|
219
|
+
key={variant.id}
|
|
220
|
+
label={variant.label}
|
|
221
|
+
onClick={() => onSelect(variant.id)}
|
|
222
|
+
onMouseEnter={() => onHover?.(variant.id)}
|
|
223
|
+
size="lg"
|
|
224
|
+
title={variant.title}
|
|
225
|
+
{...tileProps?.(variant.id)}
|
|
226
|
+
/>
|
|
227
|
+
))}
|
|
228
|
+
</BuildToolGrid>
|
|
229
|
+
<BuildPanelCaption
|
|
230
|
+
lead={active?.caption.lead}
|
|
231
|
+
rest={active ? active.caption.rest : WALL_DRAW_IDLE_CAPTION}
|
|
232
|
+
/>
|
|
233
|
+
</BuildPanelSection>
|
|
234
|
+
)
|
|
235
|
+
}
|