@pascal-app/editor 0.8.0 → 0.9.2
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 +15 -13
- package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
- package/src/components/editor/bake-exporter.tsx +47 -0
- package/src/components/editor/custom-camera-controls.tsx +795 -28
- package/src/components/editor/editor-layout-mobile.tsx +8 -3
- package/src/components/editor/editor-layout-v2.tsx +76 -51
- package/src/components/editor/export-manager.tsx +69 -32
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
- package/src/components/editor/first-person/build-collider-world.ts +228 -83
- package/src/components/editor/first-person-controls.tsx +943 -27
- package/src/components/editor/floating-action-menu.tsx +676 -208
- package/src/components/editor/floating-building-action-menu.tsx +2 -2
- package/src/components/editor/floorplan-background-selection.test.ts +58 -0
- package/src/components/editor/floorplan-background-selection.ts +3 -2
- package/src/components/editor/floorplan-panel.tsx +6916 -12137
- package/src/components/editor/grid.tsx +197 -36
- package/src/components/editor/group-actions.ts +457 -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 +420 -0
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +396 -0
- package/src/components/editor/group-transform-shared.ts +489 -0
- package/src/components/editor/handles/handle-arrow.tsx +581 -0
- package/src/components/editor/handles/use-handle-drag.ts +246 -0
- package/src/components/editor/index.tsx +412 -162
- package/src/components/editor/measurement-pill.tsx +115 -0
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +1646 -0
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +70 -0
- package/src/components/editor/quick-measurement-hud.tsx +28 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +1106 -483
- package/src/components/editor/site-edge-labels.tsx +45 -21
- package/src/components/editor/slab-hole-highlights.tsx +474 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +140 -41
- package/src/components/editor/use-floorplan-background-placement.ts +190 -35
- package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
- package/src/components/editor/use-floorplan-scene-data.ts +19 -7
- package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
- package/src/components/editor/wall-measurement-label.tsx +14 -19
- package/src/components/editor/wall-move-side-handles.tsx +870 -0
- package/src/components/editor/wall-opening-highlights.tsx +181 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
- package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/editor-2d/svg-paths.ts +10 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
- package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
- package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- 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-defaults.ts +8 -0
- package/src/components/tools/elevator/elevator-tool.tsx +375 -0
- package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
- package/src/components/tools/fence/fence-drafting.ts +141 -27
- package/src/components/tools/item/move-tool.tsx +45 -105
- package/src/components/tools/item/placement-math.test.ts +22 -0
- package/src/components/tools/item/placement-math.ts +59 -7
- package/src/components/tools/item/placement-strategies.ts +432 -16
- package/src/components/tools/item/placement-types.ts +22 -1
- package/src/components/tools/item/use-draft-node.ts +51 -2
- package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
- package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
- package/src/components/tools/roof/roof-tool.tsx +350 -28
- package/src/components/tools/select/box-select-state.ts +88 -0
- package/src/components/tools/select/box-select-tool.tsx +442 -520
- 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 +570 -0
- package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
- package/src/components/tools/select/select-candidates.test.ts +109 -0
- package/src/components/tools/select/select-candidates.ts +85 -0
- package/src/components/tools/shared/affordance-dispatch.ts +30 -0
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
- 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 +25 -0
- package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
- package/src/components/tools/shared/placement-box-geometry.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +305 -0
- package/src/components/tools/shared/polygon-editor.tsx +866 -175
- package/src/components/tools/shared/segment-angle.ts +56 -0
- package/src/components/tools/site/site-boundary-editor.tsx +520 -16
- package/src/components/tools/site/site-flag-model.ts +18 -0
- package/src/components/tools/stair/stair-defaults.ts +2 -1
- package/src/components/tools/stair/stair-tool.tsx +342 -37
- package/src/components/tools/tool-manager.tsx +239 -62
- package/src/components/tools/wall/wall-drafting.test.ts +330 -0
- package/src/components/tools/wall/wall-drafting.ts +248 -171
- package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +86 -82
- package/src/components/ui/action-menu/action-button.tsx +21 -1
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +40 -170
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +3 -117
- package/src/components/ui/action-menu/measurement-control.tsx +188 -0
- package/src/components/ui/action-menu/structure-tools.tsx +27 -93
- package/src/components/ui/action-menu/view-toggles.tsx +279 -90
- package/src/components/ui/command-palette/editor-commands.tsx +20 -15
- package/src/components/ui/command-palette/index.tsx +5 -3
- package/src/components/ui/controls/material-paint-panel.tsx +152 -0
- package/src/components/ui/controls/material-picker.tsx +87 -177
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +142 -46
- 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/floating-level-selector.tsx +47 -10
- 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 +235 -23
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
- 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 +40 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
- package/src/components/ui/level-duplicate-dialog.tsx +2 -1
- package/src/components/ui/panels/node-display.ts +17 -16
- package/src/components/ui/panels/panel-manager.tsx +55 -58
- package/src/components/ui/panels/panel-wrapper.tsx +238 -12
- package/src/components/ui/panels/parametric-inspector.tsx +482 -0
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/scene-loader.tsx +1 -3
- 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/items-panel/function-tree-panel.tsx +263 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
- 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 +92 -0
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
- 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 +2 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -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 +112 -39
- 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/panels/zone-panel/index.tsx +51 -2
- package/src/components/ui/sidebar/tab-bar.tsx +75 -1
- 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-overlay.tsx +253 -61
- package/src/components/viewer-zone-system.tsx +7 -2
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +64 -10
- package/src/hooks/use-ceiling-events.ts +176 -0
- package/src/hooks/use-drag-action.ts +127 -0
- package/src/hooks/use-keyboard.ts +493 -95
- package/src/hooks/use-selection.ts +64 -0
- package/src/index.tsx +451 -6
- package/src/lib/active-placement-surface.ts +35 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/constants.ts +6 -2
- 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 +192 -0
- package/src/lib/direct-manipulation.ts +109 -0
- package/src/lib/editor-api.ts +50 -0
- package/src/lib/elevator-support.ts +96 -0
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +117 -0
- package/src/lib/floorplan/floorplan-export.tsx +364 -0
- package/src/lib/floorplan/geometry.ts +53 -0
- package/src/lib/floorplan/index.ts +10 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/floorplan/selection-tool.ts +40 -0
- package/src/lib/floorplan/stairs.ts +12 -1
- package/src/lib/fresh-planar-placement.test.ts +339 -0
- package/src/lib/fresh-planar-placement.ts +130 -0
- package/src/lib/glb-export.test.ts +441 -0
- package/src/lib/glb-export.ts +874 -0
- package/src/lib/history.ts +11 -1
- 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 +51 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +211 -0
- package/src/lib/level-duplication.ts +54 -42
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +227 -48
- 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 +183 -0
- package/src/lib/measurements.ts +199 -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/placement-metadata.ts +29 -0
- package/src/lib/planar-cursor-placement.test.ts +100 -0
- package/src/lib/planar-cursor-placement.ts +42 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +7 -7
- 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 +196 -0
- package/src/lib/scene-clipboard.ts +332 -0
- package/src/lib/scene.ts +40 -8
- package/src/lib/selection-routing.test.ts +298 -0
- package/src/lib/selection-routing.ts +175 -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 +69 -10
- package/src/lib/sfx-player.test.ts +124 -0
- package/src/lib/sfx-player.ts +148 -55
- package/src/lib/site-boundary.ts +1 -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 +138 -0
- package/src/lib/snapping-mode.ts +179 -0
- package/src/lib/stair-duplication.ts +4 -4
- package/src/lib/stair-levels.test.ts +146 -0
- package/src/lib/stair-levels.ts +177 -0
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/world-grid-snap.ts +231 -0
- package/src/lib/zone-content.ts +130 -0
- package/src/store/use-alignment-guides.ts +21 -0
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-editor.tsx +767 -114
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +21 -0
- package/src/store/use-floorplan-draft-preview.ts +101 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-interaction-scope.test.ts +186 -0
- package/src/store/use-interaction-scope.ts +132 -0
- package/src/store/use-measurement-draft.test.ts +530 -0
- package/src/store/use-measurement-draft.ts +543 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-placement-preview.ts +39 -0
- 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 +43 -0
- package/src/store/use-wall-move-ghosts.ts +36 -0
- package/src/store/use-wall-snap-indicator.ts +34 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
- package/src/components/tools/building/move-building-tool.tsx +0 -157
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
- package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
- package/src/components/tools/column/column-tool.tsx +0 -97
- package/src/components/tools/column/move-column-tool.tsx +0 -105
- package/src/components/tools/door/door-math.ts +0 -110
- package/src/components/tools/door/door-tool.tsx +0 -324
- package/src/components/tools/door/move-door-tool.tsx +0 -412
- package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
- package/src/components/tools/fence/fence-tool.tsx +0 -346
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
- package/src/components/tools/fence/move-fence-tool.tsx +0 -302
- package/src/components/tools/item/item-tool.tsx +0 -26
- package/src/components/tools/roof/move-roof-tool.tsx +0 -364
- package/src/components/tools/slab/move-slab-tool.tsx +0 -182
- package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
- package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
- package/src/components/tools/slab/slab-tool.tsx +0 -322
- package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
- package/src/components/tools/spawn/spawn-tool.tsx +0 -130
- package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
- package/src/components/tools/wall/move-wall-tool.tsx +0 -358
- package/src/components/tools/wall/wall-tool.tsx +0 -332
- package/src/components/tools/window/move-window-tool.tsx +0 -447
- package/src/components/tools/window/window-math.ts +0 -117
- package/src/components/tools/window/window-tool.tsx +0 -332
- package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
- package/src/components/ui/helpers/slab-helper.tsx +0 -20
- package/src/components/ui/helpers/wall-helper.tsx +0 -20
- package/src/components/ui/panels/ceiling-panel.tsx +0 -249
- package/src/components/ui/panels/column-panel.tsx +0 -759
- package/src/components/ui/panels/door-panel.tsx +0 -1299
- package/src/components/ui/panels/fence-panel.tsx +0 -222
- package/src/components/ui/panels/item-panel.tsx +0 -306
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
- package/src/components/ui/panels/roof-panel.tsx +0 -280
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
- package/src/components/ui/panels/slab-panel.tsx +0 -250
- package/src/components/ui/panels/spawn-panel.tsx +0 -161
- package/src/components/ui/panels/stair-panel.tsx +0 -577
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
- package/src/components/ui/panels/wall-panel.tsx +0 -273
- package/src/components/ui/panels/window-panel.tsx +0 -968
- package/src/contexts/presets-context.tsx +0 -121
- package/src/hooks/use-contextual-tools.ts +0 -61
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { emitter, sceneRegistry } from '@pascal-app/core'
|
|
4
|
-
import { useThree } from '@react-three/fiber'
|
|
5
|
-
import { useCallback, useEffect } from 'react'
|
|
6
|
-
import * as THREE from 'three'
|
|
7
|
-
import { usePresetsAdapter } from '../../contexts/presets-context'
|
|
8
|
-
|
|
9
|
-
const THUMBNAIL_SIZE = 1080
|
|
10
|
-
const CAMERA_FOV = 45
|
|
11
|
-
|
|
12
|
-
export const PresetThumbnailGenerator = () => {
|
|
13
|
-
const gl = useThree((state) => state.gl)
|
|
14
|
-
const scene = useThree((state) => state.scene)
|
|
15
|
-
const adapter = usePresetsAdapter()
|
|
16
|
-
|
|
17
|
-
const generate = useCallback(
|
|
18
|
-
async ({ presetId, nodeId }: { presetId: string; nodeId: string }) => {
|
|
19
|
-
const target = sceneRegistry.nodes.get(nodeId)
|
|
20
|
-
if (!target) {
|
|
21
|
-
console.error('❌ PresetThumbnail: node not found', nodeId)
|
|
22
|
-
return
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Compute each mesh's transform relative to the target node (cancels world
|
|
26
|
-
// position/rotation), so the item is always rendered at origin with a known
|
|
27
|
-
// neutral orientation regardless of where it's placed in the scene.
|
|
28
|
-
target.updateWorldMatrix(true, true)
|
|
29
|
-
const targetInverse = new THREE.Matrix4().copy(target.matrixWorld).invert()
|
|
30
|
-
const relMatrix = new THREE.Matrix4()
|
|
31
|
-
|
|
32
|
-
const clones: THREE.Object3D[] = []
|
|
33
|
-
target.traverse((obj) => {
|
|
34
|
-
if (
|
|
35
|
-
!(obj instanceof THREE.Mesh || obj instanceof THREE.Line || obj instanceof THREE.Points)
|
|
36
|
-
)
|
|
37
|
-
return
|
|
38
|
-
const c = obj.clone(false) // shallow clone: copies geometry, material, visible — no children
|
|
39
|
-
relMatrix.multiplyMatrices(targetInverse, obj.matrixWorld)
|
|
40
|
-
relMatrix.decompose(c.position, c.quaternion, c.scale)
|
|
41
|
-
scene.add(c)
|
|
42
|
-
clones.push(c)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
if (clones.length === 0) {
|
|
46
|
-
console.error('❌ PresetThumbnail: no renderable objects found', nodeId)
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Combined bounding box across all clones
|
|
51
|
-
const box = new THREE.Box3()
|
|
52
|
-
for (const c of clones) box.expandByObject(c)
|
|
53
|
-
|
|
54
|
-
if (box.isEmpty()) {
|
|
55
|
-
for (const c of clones) scene.remove(c)
|
|
56
|
-
console.error('❌ PresetThumbnail: empty bounding box', nodeId)
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const sphere = new THREE.Sphere()
|
|
61
|
-
box.getBoundingSphere(sphere)
|
|
62
|
-
|
|
63
|
-
// Camera: aspect matches canvas (center-cropped to square after render)
|
|
64
|
-
const { width, height } = gl.domElement
|
|
65
|
-
const camera = new THREE.PerspectiveCamera(CAMERA_FOV, width / height, 0.01, 1000)
|
|
66
|
-
const dir = new THREE.Vector3(-0.5, 0.5, 0.5).normalize()
|
|
67
|
-
const fovRad = (CAMERA_FOV * Math.PI) / 180
|
|
68
|
-
const dist = (sphere.radius / Math.tan(fovRad / 2)) * 1.3
|
|
69
|
-
camera.position.copy(sphere.center).addScaledVector(dir, dist)
|
|
70
|
-
camera.lookAt(sphere.center)
|
|
71
|
-
camera.updateProjectionMatrix()
|
|
72
|
-
|
|
73
|
-
// Hide all scene geometry except the clones — leave lights, cameras, etc. intact
|
|
74
|
-
const cloneSet = new Set<THREE.Object3D>(clones)
|
|
75
|
-
const snapshot = new Map<THREE.Object3D, boolean>()
|
|
76
|
-
scene.traverse((obj) => {
|
|
77
|
-
if (cloneSet.has(obj)) return
|
|
78
|
-
if (
|
|
79
|
-
!(obj instanceof THREE.Mesh || obj instanceof THREE.Line || obj instanceof THREE.Points)
|
|
80
|
-
)
|
|
81
|
-
return
|
|
82
|
-
snapshot.set(obj, obj.visible)
|
|
83
|
-
obj.visible = false
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
gl.render(scene, camera)
|
|
87
|
-
|
|
88
|
-
// Restore visibility and remove clones
|
|
89
|
-
snapshot.forEach((wasVisible, obj) => {
|
|
90
|
-
obj.visible = wasVisible
|
|
91
|
-
})
|
|
92
|
-
for (const c of clones) scene.remove(c)
|
|
93
|
-
|
|
94
|
-
// Center-crop to square and scale to THUMBNAIL_SIZE
|
|
95
|
-
const minDim = Math.min(width, height)
|
|
96
|
-
const sx = Math.round((width - minDim) / 2)
|
|
97
|
-
const sy = Math.round((height - minDim) / 2)
|
|
98
|
-
const offscreen = document.createElement('canvas')
|
|
99
|
-
offscreen.width = THUMBNAIL_SIZE
|
|
100
|
-
offscreen.height = THUMBNAIL_SIZE
|
|
101
|
-
const ctx = offscreen.getContext('2d')!
|
|
102
|
-
ctx.drawImage(gl.domElement, sx, sy, minDim, minDim, 0, 0, THUMBNAIL_SIZE, THUMBNAIL_SIZE)
|
|
103
|
-
|
|
104
|
-
offscreen.toBlob(async (blob) => {
|
|
105
|
-
if (!blob) {
|
|
106
|
-
console.error('❌ PresetThumbnail: failed to create blob')
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
if (!adapter.uploadPresetThumbnail) return
|
|
110
|
-
const thumbnailUrl = await adapter.uploadPresetThumbnail(presetId, blob)
|
|
111
|
-
if (thumbnailUrl) {
|
|
112
|
-
emitter.emit('preset:thumbnail-updated', { presetId, thumbnailUrl })
|
|
113
|
-
}
|
|
114
|
-
}, 'image/png')
|
|
115
|
-
},
|
|
116
|
-
[gl, scene, adapter],
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
emitter.on('preset:generate-thumbnail', generate)
|
|
121
|
-
return () => emitter.off('preset:generate-thumbnail', generate)
|
|
122
|
-
}, [generate])
|
|
123
|
-
|
|
124
|
-
return null
|
|
125
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import type { Point2D, RoofNode, RoofSegmentNode } from '@pascal-app/core'
|
|
4
|
-
import { memo } from 'react'
|
|
5
|
-
import { toSvgX, toSvgY } from '../svg-paths'
|
|
6
|
-
|
|
7
|
-
type FloorplanLineSegment = {
|
|
8
|
-
start: Point2D
|
|
9
|
-
end: Point2D
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type FloorplanRoofSegmentEntry = {
|
|
13
|
-
segment: RoofSegmentNode
|
|
14
|
-
points: string
|
|
15
|
-
ridgeLine: FloorplanLineSegment | null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type FloorplanRoofEntry = {
|
|
19
|
-
roof: RoofNode
|
|
20
|
-
segments: FloorplanRoofSegmentEntry[]
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type FloorplanRoofPalette = {
|
|
24
|
-
roofFill: string
|
|
25
|
-
roofActiveFill: string
|
|
26
|
-
roofSelectedFill: string
|
|
27
|
-
roofStroke: string
|
|
28
|
-
roofActiveStroke: string
|
|
29
|
-
roofSelectedStroke: string
|
|
30
|
-
roofRidgeStroke: string
|
|
31
|
-
roofSelectedRidgeStroke: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
type FloorplanRoofLayerProps = {
|
|
35
|
-
highlightedIdSet: ReadonlySet<string>
|
|
36
|
-
palette: FloorplanRoofPalette
|
|
37
|
-
roofEntries: FloorplanRoofEntry[]
|
|
38
|
-
selectedIdSet: ReadonlySet<string>
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export const FloorplanRoofLayer = memo(function FloorplanRoofLayer({
|
|
42
|
-
highlightedIdSet,
|
|
43
|
-
palette,
|
|
44
|
-
roofEntries,
|
|
45
|
-
selectedIdSet,
|
|
46
|
-
}: FloorplanRoofLayerProps) {
|
|
47
|
-
if (roofEntries.length === 0) {
|
|
48
|
-
return null
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<>
|
|
53
|
-
{roofEntries.map(({ roof, segments }) => {
|
|
54
|
-
const roofSelected = selectedIdSet.has(roof.id)
|
|
55
|
-
const roofHighlighted = highlightedIdSet.has(roof.id)
|
|
56
|
-
const hasSelectedSegment = segments.some(({ segment }) => selectedIdSet.has(segment.id))
|
|
57
|
-
const hasHighlightedSegment = segments.some(({ segment }) =>
|
|
58
|
-
highlightedIdSet.has(segment.id),
|
|
59
|
-
)
|
|
60
|
-
const isRoofActive =
|
|
61
|
-
roofSelected || roofHighlighted || hasSelectedSegment || hasHighlightedSegment
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<g key={roof.id} pointerEvents="none">
|
|
65
|
-
{segments.map(({ points, ridgeLine, segment }) => {
|
|
66
|
-
const isSegmentSelected = selectedIdSet.has(segment.id)
|
|
67
|
-
const isSegmentHighlighted = highlightedIdSet.has(segment.id)
|
|
68
|
-
const isSegmentActive = isSegmentSelected || isSegmentHighlighted
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<g key={segment.id}>
|
|
72
|
-
<polygon
|
|
73
|
-
fill={
|
|
74
|
-
isSegmentActive
|
|
75
|
-
? palette.roofSelectedFill
|
|
76
|
-
: isRoofActive
|
|
77
|
-
? palette.roofActiveFill
|
|
78
|
-
: palette.roofFill
|
|
79
|
-
}
|
|
80
|
-
points={points}
|
|
81
|
-
stroke={
|
|
82
|
-
isSegmentActive
|
|
83
|
-
? palette.roofSelectedStroke
|
|
84
|
-
: isRoofActive
|
|
85
|
-
? palette.roofActiveStroke
|
|
86
|
-
: palette.roofStroke
|
|
87
|
-
}
|
|
88
|
-
strokeWidth={isSegmentActive ? '2.25' : isRoofActive ? '1.75' : '1.1'}
|
|
89
|
-
vectorEffect="non-scaling-stroke"
|
|
90
|
-
/>
|
|
91
|
-
{ridgeLine ? (
|
|
92
|
-
<line
|
|
93
|
-
fill="none"
|
|
94
|
-
stroke={
|
|
95
|
-
isSegmentActive ? palette.roofSelectedRidgeStroke : palette.roofRidgeStroke
|
|
96
|
-
}
|
|
97
|
-
strokeWidth={isSegmentActive ? '2' : '1.4'}
|
|
98
|
-
vectorEffect="non-scaling-stroke"
|
|
99
|
-
x1={toSvgX(ridgeLine.start.x)}
|
|
100
|
-
x2={toSvgX(ridgeLine.end.x)}
|
|
101
|
-
y1={toSvgY(ridgeLine.start.y)}
|
|
102
|
-
y2={toSvgY(ridgeLine.end.y)}
|
|
103
|
-
/>
|
|
104
|
-
) : null}
|
|
105
|
-
</g>
|
|
106
|
-
)
|
|
107
|
-
})}
|
|
108
|
-
</g>
|
|
109
|
-
)
|
|
110
|
-
})}
|
|
111
|
-
</>
|
|
112
|
-
)
|
|
113
|
-
})
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
type BuildingNode,
|
|
5
|
-
emitter,
|
|
6
|
-
type GridEvent,
|
|
7
|
-
sceneRegistry,
|
|
8
|
-
useScene,
|
|
9
|
-
} from '@pascal-app/core'
|
|
10
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
11
|
-
import { useFrame } from '@react-three/fiber'
|
|
12
|
-
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
13
|
-
import * as THREE from 'three'
|
|
14
|
-
import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
|
|
15
|
-
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
16
|
-
import useEditor from '../../../store/use-editor'
|
|
17
|
-
import { CursorSphere } from '../shared/cursor-sphere'
|
|
18
|
-
|
|
19
|
-
export function MoveBuildingContent({ node }: { node: BuildingNode }) {
|
|
20
|
-
const previousGridPosRef = useRef<[number, number] | null>(null)
|
|
21
|
-
|
|
22
|
-
// Stable refs so the effect never needs node in its dependency array
|
|
23
|
-
const nodeIdRef = useRef(node.id)
|
|
24
|
-
const originalPositionRef = useRef<[number, number, number]>([...node.position] as [
|
|
25
|
-
number,
|
|
26
|
-
number,
|
|
27
|
-
number,
|
|
28
|
-
])
|
|
29
|
-
const originalRotationRef = useRef<number>(node.rotation[1] ?? 0)
|
|
30
|
-
const pendingRotationRef = useRef<number>(node.rotation[1] ?? 0)
|
|
31
|
-
|
|
32
|
-
const [cursorWorldPos, setCursorWorldPos] = useState<[number, number, number]>(() => {
|
|
33
|
-
const obj = sceneRegistry.nodes.get(node.id)
|
|
34
|
-
if (obj) {
|
|
35
|
-
const pos = new THREE.Vector3()
|
|
36
|
-
obj.getWorldPosition(pos)
|
|
37
|
-
return [pos.x, pos.y, pos.z]
|
|
38
|
-
}
|
|
39
|
-
return [node.position[0], node.position[1], node.position[2]]
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const exitMoveMode = useCallback(() => {
|
|
43
|
-
useEditor.getState().setMovingNode(null)
|
|
44
|
-
}, [])
|
|
45
|
-
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
const nodeId = nodeIdRef.current
|
|
48
|
-
const originalPosition = originalPositionRef.current
|
|
49
|
-
|
|
50
|
-
useScene.temporal.getState().pause()
|
|
51
|
-
|
|
52
|
-
let wasCommitted = false
|
|
53
|
-
|
|
54
|
-
const onKeyDown = (event: KeyboardEvent) => {
|
|
55
|
-
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const ROTATION_STEP = Math.PI / 2
|
|
60
|
-
let rotationDelta = 0
|
|
61
|
-
if (event.key === 'r' || event.key === 'R') rotationDelta = ROTATION_STEP
|
|
62
|
-
else if (event.key === 't' || event.key === 'T') rotationDelta = -ROTATION_STEP
|
|
63
|
-
|
|
64
|
-
if (rotationDelta !== 0) {
|
|
65
|
-
event.preventDefault()
|
|
66
|
-
sfxEmitter.emit('sfx:item-rotate')
|
|
67
|
-
pendingRotationRef.current += rotationDelta
|
|
68
|
-
|
|
69
|
-
const mesh = sceneRegistry.nodes.get(nodeId)
|
|
70
|
-
if (mesh) mesh.rotation.y = pendingRotationRef.current
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const onGridMove = (event: GridEvent) => {
|
|
75
|
-
const gridX = Math.round(event.position[0] * 2) / 2
|
|
76
|
-
const gridZ = Math.round(event.position[2] * 2) / 2
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
previousGridPosRef.current &&
|
|
80
|
-
(gridX !== previousGridPosRef.current[0] || gridZ !== previousGridPosRef.current[1])
|
|
81
|
-
) {
|
|
82
|
-
sfxEmitter.emit('sfx:grid-snap')
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
previousGridPosRef.current = [gridX, gridZ]
|
|
86
|
-
setCursorWorldPos([gridX, 0, gridZ])
|
|
87
|
-
|
|
88
|
-
// Directly update the Three.js group — no store update during drag
|
|
89
|
-
const mesh = sceneRegistry.nodes.get(nodeId)
|
|
90
|
-
if (mesh) {
|
|
91
|
-
mesh.position.x = gridX
|
|
92
|
-
mesh.position.z = gridZ
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const onGridClick = (event: GridEvent) => {
|
|
97
|
-
const gridX = Math.round(event.position[0] * 2) / 2
|
|
98
|
-
const gridZ = Math.round(event.position[2] * 2) / 2
|
|
99
|
-
|
|
100
|
-
wasCommitted = true
|
|
101
|
-
|
|
102
|
-
useScene.temporal.getState().resume()
|
|
103
|
-
useScene.getState().updateNode(nodeId, {
|
|
104
|
-
position: [gridX, originalPosition[1], gridZ],
|
|
105
|
-
rotation: [0, pendingRotationRef.current, 0],
|
|
106
|
-
})
|
|
107
|
-
useScene.temporal.getState().pause()
|
|
108
|
-
|
|
109
|
-
sfxEmitter.emit('sfx:item-place')
|
|
110
|
-
useViewer.getState().setSelection({ buildingId: nodeId as BuildingNode['id'] })
|
|
111
|
-
exitMoveMode()
|
|
112
|
-
event.nativeEvent?.stopPropagation?.()
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const onCancel = () => {
|
|
116
|
-
// Revert mesh position and rotation immediately
|
|
117
|
-
const mesh = sceneRegistry.nodes.get(nodeId)
|
|
118
|
-
if (mesh) {
|
|
119
|
-
mesh.position.x = originalPosition[0]
|
|
120
|
-
mesh.position.z = originalPosition[2]
|
|
121
|
-
mesh.rotation.y = originalRotationRef.current
|
|
122
|
-
}
|
|
123
|
-
pendingRotationRef.current = originalRotationRef.current
|
|
124
|
-
// Restore building selection
|
|
125
|
-
useViewer.getState().setSelection({ buildingId: nodeId as BuildingNode['id'] })
|
|
126
|
-
useScene.temporal.getState().resume()
|
|
127
|
-
// Tell the keyboard handler we handled this, so it doesn't also clear the selection
|
|
128
|
-
markToolCancelConsumed()
|
|
129
|
-
exitMoveMode()
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
emitter.on('grid:move', onGridMove)
|
|
133
|
-
emitter.on('grid:click', onGridClick)
|
|
134
|
-
emitter.on('tool:cancel', onCancel)
|
|
135
|
-
window.addEventListener('keydown', onKeyDown)
|
|
136
|
-
|
|
137
|
-
return () => {
|
|
138
|
-
if (!wasCommitted) {
|
|
139
|
-
useScene.getState().updateNode(nodeId, {
|
|
140
|
-
position: originalPosition,
|
|
141
|
-
rotation: [0, originalRotationRef.current, 0],
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
useScene.temporal.getState().resume()
|
|
145
|
-
emitter.off('grid:move', onGridMove)
|
|
146
|
-
emitter.off('grid:click', onGridClick)
|
|
147
|
-
emitter.off('tool:cancel', onCancel)
|
|
148
|
-
window.removeEventListener('keydown', onKeyDown)
|
|
149
|
-
}
|
|
150
|
-
}, [exitMoveMode]) // stable — node values captured via refs at mount
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<group>
|
|
154
|
-
<CursorSphere position={cursorWorldPos} showTooltip={false} />
|
|
155
|
-
</group>
|
|
156
|
-
)
|
|
157
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { type CeilingNode, resolveLevelId, useScene } from '@pascal-app/core'
|
|
2
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
3
|
-
import { useCallback } from 'react'
|
|
4
|
-
import { PolygonEditor } from '../shared/polygon-editor'
|
|
5
|
-
|
|
6
|
-
interface CeilingBoundaryEditorProps {
|
|
7
|
-
ceilingId: CeilingNode['id']
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Ceiling boundary editor - allows editing ceiling polygon vertices for a specific ceiling
|
|
12
|
-
* Uses the generic PolygonEditor component
|
|
13
|
-
*/
|
|
14
|
-
export const CeilingBoundaryEditor: React.FC<CeilingBoundaryEditorProps> = ({ ceilingId }) => {
|
|
15
|
-
const ceilingNode = useScene((state) => state.nodes[ceilingId])
|
|
16
|
-
const updateNode = useScene((state) => state.updateNode)
|
|
17
|
-
const setSelection = useViewer((state) => state.setSelection)
|
|
18
|
-
|
|
19
|
-
const ceiling = ceilingNode?.type === 'ceiling' ? (ceilingNode as CeilingNode) : null
|
|
20
|
-
|
|
21
|
-
const handlePolygonChange = useCallback(
|
|
22
|
-
(newPolygon: Array<[number, number]>) => {
|
|
23
|
-
updateNode(ceilingId, { polygon: newPolygon })
|
|
24
|
-
// Re-assert selection so the ceiling stays selected after the edit
|
|
25
|
-
setSelection({ selectedIds: [ceilingId] })
|
|
26
|
-
},
|
|
27
|
-
[ceilingId, updateNode, setSelection],
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
if (!ceiling?.polygon || ceiling.polygon.length < 3) return null
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<PolygonEditor
|
|
34
|
-
allowEdgeMove
|
|
35
|
-
color="#d4d4d4"
|
|
36
|
-
levelId={resolveLevelId(ceiling, useScene.getState().nodes)}
|
|
37
|
-
minVertices={3}
|
|
38
|
-
onPolygonChange={handlePolygonChange}
|
|
39
|
-
polygon={ceiling.polygon}
|
|
40
|
-
surfaceHeight={ceiling.height ?? 2.5}
|
|
41
|
-
/>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { type CeilingNode, resolveLevelId, useScene } from '@pascal-app/core'
|
|
2
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
3
|
-
import { useCallback } from 'react'
|
|
4
|
-
import { PolygonEditor } from '../shared/polygon-editor'
|
|
5
|
-
|
|
6
|
-
interface CeilingHoleEditorProps {
|
|
7
|
-
ceilingId: CeilingNode['id']
|
|
8
|
-
holeIndex: number
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Ceiling hole editor - allows editing a specific hole polygon within a ceiling
|
|
13
|
-
* Uses the generic PolygonEditor component
|
|
14
|
-
*/
|
|
15
|
-
export const CeilingHoleEditor: React.FC<CeilingHoleEditorProps> = ({ ceilingId, holeIndex }) => {
|
|
16
|
-
const ceilingNode = useScene((state) => state.nodes[ceilingId])
|
|
17
|
-
const updateNode = useScene((state) => state.updateNode)
|
|
18
|
-
const setSelection = useViewer((state) => state.setSelection)
|
|
19
|
-
|
|
20
|
-
const ceiling = ceilingNode?.type === 'ceiling' ? (ceilingNode as CeilingNode) : null
|
|
21
|
-
const holes = ceiling?.holes || []
|
|
22
|
-
const hole = holes[holeIndex]
|
|
23
|
-
|
|
24
|
-
const handlePolygonChange = useCallback(
|
|
25
|
-
(newPolygon: Array<[number, number]>) => {
|
|
26
|
-
const updatedHoles = [...holes]
|
|
27
|
-
updatedHoles[holeIndex] = newPolygon
|
|
28
|
-
updateNode(ceilingId, { holes: updatedHoles })
|
|
29
|
-
// Re-assert selection so the ceiling stays selected after the edit
|
|
30
|
-
setSelection({ selectedIds: [ceilingId] })
|
|
31
|
-
},
|
|
32
|
-
[ceilingId, holeIndex, holes, updateNode, setSelection],
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
if (!(ceiling && hole) || hole.length < 3) return null
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<PolygonEditor
|
|
39
|
-
allowEdgeMove
|
|
40
|
-
allowPolygonMove
|
|
41
|
-
color="#ef4444"
|
|
42
|
-
levelId={resolveLevelId(ceiling, useScene.getState().nodes)} // red for holes
|
|
43
|
-
minVertices={3}
|
|
44
|
-
onPolygonChange={handlePolygonChange}
|
|
45
|
-
polygon={hole}
|
|
46
|
-
surfaceHeight={ceiling.height ?? 2.5}
|
|
47
|
-
/>
|
|
48
|
-
)
|
|
49
|
-
}
|