@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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
4
|
+
import { Html } from '@react-three/drei'
|
|
5
|
+
import { memo, useEffect, useLayoutEffect, useMemo } from 'react'
|
|
6
|
+
import { BufferGeometry, Float32BufferAttribute, Line as ThreeLine } from 'three'
|
|
7
|
+
import { LineBasicNodeMaterial } from 'three/webgpu'
|
|
8
|
+
import { EDITOR_LAYER } from '../../lib/constants'
|
|
9
|
+
import useOpeningGuides, {
|
|
10
|
+
type OpeningGuide3D,
|
|
11
|
+
type OpeningGuideVec3,
|
|
12
|
+
} from '../../store/use-opening-guides'
|
|
13
|
+
import { formatMeasurement } from './measurement-pill'
|
|
14
|
+
|
|
15
|
+
const DIMENSION_COLOR = 0x81_8c_f8 // indigo — a neutral measurement
|
|
16
|
+
const ALIGN_COLOR = 0xef_44_44 // red — a snapped alignment (matches the 2D guide accent)
|
|
17
|
+
const DIMENSION_PILL = '#6366f1'
|
|
18
|
+
const BADGE_PILL = '#ec4899' // pink — matches the 2D equal-spacing badge
|
|
19
|
+
|
|
20
|
+
// Shared depth-test-off materials so the guides read on top of the wall and
|
|
21
|
+
// don't rebuild GPU buffers as guides churn during a drag.
|
|
22
|
+
const dimensionMaterial = new LineBasicNodeMaterial({
|
|
23
|
+
color: DIMENSION_COLOR,
|
|
24
|
+
depthTest: false,
|
|
25
|
+
depthWrite: false,
|
|
26
|
+
toneMapped: false,
|
|
27
|
+
transparent: true,
|
|
28
|
+
})
|
|
29
|
+
const alignMaterial = new LineBasicNodeMaterial({
|
|
30
|
+
color: ALIGN_COLOR,
|
|
31
|
+
depthTest: false,
|
|
32
|
+
depthWrite: false,
|
|
33
|
+
toneMapped: false,
|
|
34
|
+
transparent: true,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const mid = (a: OpeningGuideVec3, b: OpeningGuideVec3): OpeningGuideVec3 => [
|
|
38
|
+
(a[0] + b[0]) / 2,
|
|
39
|
+
(a[1] + b[1]) / 2,
|
|
40
|
+
(a[2] + b[2]) / 2,
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Wall-plane proximity / alignment guides for the 3D editor — the spatial twin
|
|
45
|
+
* of the floor-plan placement dimensions + equal-spacing badges. Subscribes to
|
|
46
|
+
* `useOpeningGuides` (published by the door/window move, placement, and resize
|
|
47
|
+
* interactions each drag tick) and draws sill/head + edge-proximity dimensions, a sill-alignment line, and
|
|
48
|
+
* equal-spacing badges. Coordinates are already in the move tool's render frame
|
|
49
|
+
* (the producer reuses the cursor's `wallLocalToWorld`, so they share the cursor's
|
|
50
|
+
* building-local frame), so this layer mounts beside `Alignment3DGuideLayer` and
|
|
51
|
+
* renders them as-is.
|
|
52
|
+
*/
|
|
53
|
+
export const OpeningGuides3DLayer = memo(function OpeningGuides3DLayer() {
|
|
54
|
+
const guides = useOpeningGuides((s) => s.guides)
|
|
55
|
+
const unit = useViewer((s) => s.unit)
|
|
56
|
+
if (guides.length === 0) return null
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
{guides.map((guide) => (
|
|
60
|
+
<OpeningGuide guide={guide} key={guide.id} unit={unit} />
|
|
61
|
+
))}
|
|
62
|
+
</>
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
function OpeningGuide({ guide, unit }: { guide: OpeningGuide3D; unit: 'metric' | 'imperial' }) {
|
|
67
|
+
if (guide.kind === 'badge') {
|
|
68
|
+
return (
|
|
69
|
+
<Html
|
|
70
|
+
center
|
|
71
|
+
position={guide.at}
|
|
72
|
+
style={{ pointerEvents: 'none', userSelect: 'none' }}
|
|
73
|
+
zIndexRange={[20, 0]}
|
|
74
|
+
>
|
|
75
|
+
<div
|
|
76
|
+
className="whitespace-nowrap rounded-[3px] px-[5px] py-[2px] font-sans font-semibold text-[11px] text-white"
|
|
77
|
+
style={{ backgroundColor: BADGE_PILL }}
|
|
78
|
+
>
|
|
79
|
+
{`= ${formatMeasurement(guide.value, unit)}`}
|
|
80
|
+
</div>
|
|
81
|
+
</Html>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const material = guide.kind === 'align-line' ? alignMaterial : dimensionMaterial
|
|
86
|
+
return (
|
|
87
|
+
<>
|
|
88
|
+
<GuideSegment from={guide.from} material={material} to={guide.to} />
|
|
89
|
+
{guide.kind === 'dimension' ? (
|
|
90
|
+
<Html
|
|
91
|
+
center
|
|
92
|
+
position={mid(guide.from, guide.to)}
|
|
93
|
+
style={{ pointerEvents: 'none', userSelect: 'none' }}
|
|
94
|
+
zIndexRange={[20, 0]}
|
|
95
|
+
>
|
|
96
|
+
<div
|
|
97
|
+
className="whitespace-nowrap rounded-[3px] px-[5px] py-[2px] font-medium font-sans text-[11px] text-white"
|
|
98
|
+
style={{ backgroundColor: DIMENSION_PILL }}
|
|
99
|
+
>
|
|
100
|
+
{formatMeasurement(guide.value, unit)}
|
|
101
|
+
</div>
|
|
102
|
+
</Html>
|
|
103
|
+
) : null}
|
|
104
|
+
</>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function GuideSegment({
|
|
109
|
+
from,
|
|
110
|
+
to,
|
|
111
|
+
material,
|
|
112
|
+
}: {
|
|
113
|
+
from: OpeningGuideVec3
|
|
114
|
+
to: OpeningGuideVec3
|
|
115
|
+
material: LineBasicNodeMaterial
|
|
116
|
+
}) {
|
|
117
|
+
// Build the THREE.Line once with a preallocated 2-point position buffer and
|
|
118
|
+
// mount it via <primitive> (the intrinsic <line> JSX element collides with
|
|
119
|
+
// React's SVG <line>). `material` is a module-level constant, so this memo
|
|
120
|
+
// runs exactly once per mounted slot; subsequent drag ticks mutate the
|
|
121
|
+
// existing buffer in place via the layout effect below rather than rebuilding
|
|
122
|
+
// the geometry, line, and GPU buffer every frame.
|
|
123
|
+
const { line, position } = useMemo(() => {
|
|
124
|
+
const position = new Float32BufferAttribute(new Float32Array(6), 3)
|
|
125
|
+
const geometry = new BufferGeometry()
|
|
126
|
+
geometry.setAttribute('position', position)
|
|
127
|
+
const line = new ThreeLine(geometry, material)
|
|
128
|
+
line.frustumCulled = false
|
|
129
|
+
line.layers.set(EDITOR_LAYER)
|
|
130
|
+
line.renderOrder = 1000
|
|
131
|
+
return { line, position }
|
|
132
|
+
}, [material])
|
|
133
|
+
|
|
134
|
+
const [fx, fy, fz] = from
|
|
135
|
+
const [tx, ty, tz] = to
|
|
136
|
+
useLayoutEffect(() => {
|
|
137
|
+
position.setXYZ(0, fx, fy, fz)
|
|
138
|
+
position.setXYZ(1, tx, ty, tz)
|
|
139
|
+
position.needsUpdate = true
|
|
140
|
+
}, [position, fx, fy, fz, tx, ty, tz])
|
|
141
|
+
|
|
142
|
+
useEffect(() => () => line.geometry.dispose(), [line])
|
|
143
|
+
return <primitive object={line} />
|
|
144
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { QuickMeasurementMetric, QuickMeasurementReport } from '@pascal-app/core'
|
|
4
|
+
import { Crosshair, MapPin } from 'lucide-react'
|
|
5
|
+
import { memo } from 'react'
|
|
6
|
+
import { formatAreaLabel, formatLinearMeasurement, formatVolumeLabel } from '../../lib/measurements'
|
|
7
|
+
|
|
8
|
+
function formatMetric(metric: QuickMeasurementMetric, unit: 'metric' | 'imperial'): string {
|
|
9
|
+
if (metric.quantity === 'area') return formatAreaLabel(metric.value, unit, 2)
|
|
10
|
+
if (metric.quantity === 'volume') return formatVolumeLabel(metric.value, unit, 2)
|
|
11
|
+
return formatLinearMeasurement(metric.value, unit)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const QuickMeasurementCard = memo(function QuickMeasurementCard({
|
|
15
|
+
report,
|
|
16
|
+
unit,
|
|
17
|
+
lensState,
|
|
18
|
+
}: {
|
|
19
|
+
report: QuickMeasurementReport
|
|
20
|
+
unit: 'metric' | 'imperial'
|
|
21
|
+
lensState: 'live' | 'pinned'
|
|
22
|
+
}) {
|
|
23
|
+
const pinned = lensState === 'pinned'
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
aria-label={`${pinned ? 'Pinned' : 'Live'} ${report.kindLabel.toLowerCase()} measurements`}
|
|
28
|
+
className="w-full min-w-0 overflow-hidden rounded-lg border border-border/45 bg-background/96 text-foreground shadow-elevation-3 backdrop-blur-xl"
|
|
29
|
+
data-quick-measure-card
|
|
30
|
+
data-quick-measure-state={lensState}
|
|
31
|
+
role="status"
|
|
32
|
+
>
|
|
33
|
+
<div className="flex items-center gap-2 border-border/60 border-b px-3 py-2">
|
|
34
|
+
{pinned ? (
|
|
35
|
+
<MapPin aria-hidden="true" className="h-3.5 w-3.5 shrink-0 text-foreground" />
|
|
36
|
+
) : (
|
|
37
|
+
<Crosshair aria-hidden="true" className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
38
|
+
)}
|
|
39
|
+
<div className="min-w-0">
|
|
40
|
+
<div className="truncate font-medium text-xs leading-tight">{report.title}</div>
|
|
41
|
+
<div className="text-[10px] text-muted-foreground leading-tight">{report.kindLabel}</div>
|
|
42
|
+
</div>
|
|
43
|
+
<span className="ml-auto shrink-0 rounded-full bg-muted px-2 py-0.5 font-medium text-[10px] text-muted-foreground">
|
|
44
|
+
{pinned ? 'Pinned' : 'Live lens'}
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div className="grid grid-cols-[repeat(auto-fit,minmax(6.5rem,1fr))] gap-1.5 p-2">
|
|
49
|
+
{report.metrics.map((metric) => (
|
|
50
|
+
<div className="min-w-0 rounded-md bg-muted/60 px-2.5 py-1.5" key={metric.key}>
|
|
51
|
+
<div className="flex items-baseline gap-1 text-[10px] text-muted-foreground">
|
|
52
|
+
<span className="font-medium text-foreground/80">{metric.abbreviation}</span>
|
|
53
|
+
<span className="truncate">{metric.label}</span>
|
|
54
|
+
</div>
|
|
55
|
+
<div className="mt-0.5 truncate font-mono font-medium text-xs tabular-nums">
|
|
56
|
+
{formatMetric(metric, unit)}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
))}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 border-border/60 border-t px-3 py-1.5 text-[10px] text-muted-foreground leading-tight">
|
|
63
|
+
{report.note ? <span className="min-w-48 flex-1">{report.note}</span> : <span />}
|
|
64
|
+
<span className="ml-auto shrink-0 text-foreground/70">
|
|
65
|
+
{pinned ? 'Click another surface to replace' : 'Click surface to pin'}
|
|
66
|
+
</span>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
4
|
+
import useEditor from '../../store/use-editor'
|
|
5
|
+
import {
|
|
6
|
+
selectQuickMeasurementHudEntry,
|
|
7
|
+
useQuickMeasurementHud,
|
|
8
|
+
} from '../../store/use-quick-measurement-hud'
|
|
9
|
+
import { QuickMeasurementCard } from './quick-measurement-card'
|
|
10
|
+
|
|
11
|
+
export function QuickMeasurementHud() {
|
|
12
|
+
const viewMode = useEditor((state) => state.viewMode)
|
|
13
|
+
const entry = useQuickMeasurementHud((state) => selectQuickMeasurementHudEntry(state, viewMode))
|
|
14
|
+
const unit = useViewer((state) => state.unit)
|
|
15
|
+
|
|
16
|
+
if (!entry) return null
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className="pointer-events-none absolute inset-x-3 top-3 z-40 flex justify-center"
|
|
21
|
+
data-quick-measure-hud
|
|
22
|
+
>
|
|
23
|
+
<div className="w-full max-w-[34rem]">
|
|
24
|
+
<QuickMeasurementCard lensState={entry.lensState} report={entry.report} unit={unit} />
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type AnyNodeId, buildRiserDiagram, useScene } from '@pascal-app/core'
|
|
4
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
5
|
+
import { X } from 'lucide-react'
|
|
6
|
+
import { useMemo } from 'react'
|
|
7
|
+
import useEditor from '../../store/use-editor'
|
|
8
|
+
|
|
9
|
+
const WASTE_COLOR = '#0ea5e9'
|
|
10
|
+
const VENT_COLOR = '#a855f7'
|
|
11
|
+
const MARKER_COLOR = '#1e293b'
|
|
12
|
+
const PADDING = 32
|
|
13
|
+
/** Meters → SVG units. The iso projection is in meters; scale up so a
|
|
14
|
+
* typical house drain (a few meters) fills the panel. */
|
|
15
|
+
const SCALE = 90
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* DWV riser diagram — the plumbing isometric drawn from the scene's
|
|
19
|
+
* drain/waste/vent nodes. Read-only; toggled from the view controls.
|
|
20
|
+
* Vertical stacks read vertical, sloped drains lean at 30°, with size +
|
|
21
|
+
* vent-termination annotations, matching the permit-drawing convention.
|
|
22
|
+
* Clicking a line/marker selects its node in 3D.
|
|
23
|
+
*/
|
|
24
|
+
export function RiserDiagramPanel() {
|
|
25
|
+
const isOpen = useEditor((s) => s.isRiserOpen)
|
|
26
|
+
// Only the open flag lives here. The whole-scene subscription that drives
|
|
27
|
+
// the diagram lives in the child, mounted only while the panel is open —
|
|
28
|
+
// so a closed panel doesn't re-render on every scene mutation.
|
|
29
|
+
if (!isOpen) return null
|
|
30
|
+
return <RiserDiagramContent />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function RiserDiagramContent() {
|
|
34
|
+
const setRiserOpen = useEditor((s) => s.setRiserOpen)
|
|
35
|
+
const nodes = useScene((s) => s.nodes)
|
|
36
|
+
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
37
|
+
|
|
38
|
+
const diagram = useMemo(() => buildRiserDiagram(nodes), [nodes])
|
|
39
|
+
|
|
40
|
+
const select = (nodeId: AnyNodeId) => useViewer.getState().setSelection({ selectedIds: [nodeId] })
|
|
41
|
+
|
|
42
|
+
const width = diagram ? (diagram.bounds.maxX - diagram.bounds.minX) * SCALE + PADDING * 2 : 320
|
|
43
|
+
const height = diagram ? (diagram.bounds.maxY - diagram.bounds.minY) * SCALE + PADDING * 2 : 200
|
|
44
|
+
const tx = diagram ? -diagram.bounds.minX * SCALE + PADDING : 0
|
|
45
|
+
const ty = diagram ? -diagram.bounds.minY * SCALE + PADDING : 0
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="dark pointer-events-auto absolute top-4 right-4 z-30 flex max-h-[80vh] w-[26rem] flex-col overflow-hidden rounded-2xl border border-border/40 bg-background/95 text-foreground shadow-lg backdrop-blur-xl">
|
|
49
|
+
<div className="flex items-center justify-between border-border/40 border-b px-4 py-2.5">
|
|
50
|
+
<div className="flex flex-col">
|
|
51
|
+
<span className="font-medium text-sm">Riser Diagram</span>
|
|
52
|
+
<span className="text-muted-foreground text-xs">DWV plumbing isometric</span>
|
|
53
|
+
</div>
|
|
54
|
+
<button
|
|
55
|
+
className="flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-white/10"
|
|
56
|
+
onClick={() => setRiserOpen(false)}
|
|
57
|
+
>
|
|
58
|
+
<X className="h-4 w-4 text-muted-foreground" />
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div className="flex items-center gap-3 border-border/40 border-b px-4 py-2 text-xs">
|
|
63
|
+
<span className="flex items-center gap-1.5">
|
|
64
|
+
<span className="h-0.5 w-4" style={{ background: WASTE_COLOR }} /> Waste
|
|
65
|
+
</span>
|
|
66
|
+
<span className="flex items-center gap-1.5">
|
|
67
|
+
<span className="h-0 w-4 border-t-2 border-dashed" style={{ borderColor: VENT_COLOR }} />{' '}
|
|
68
|
+
Vent
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className="overflow-auto p-2">
|
|
73
|
+
{diagram ? (
|
|
74
|
+
<svg
|
|
75
|
+
height={Math.max(height, 120)}
|
|
76
|
+
role="img"
|
|
77
|
+
aria-label="DWV riser diagram"
|
|
78
|
+
viewBox={`0 0 ${Math.max(width, 200)} ${Math.max(height, 120)}`}
|
|
79
|
+
width="100%"
|
|
80
|
+
>
|
|
81
|
+
<g transform={`translate(${tx}, ${ty})`}>
|
|
82
|
+
{diagram.lines.map((line, i) => {
|
|
83
|
+
const isSel = selectedIds.includes(line.nodeId)
|
|
84
|
+
const color = line.system === 'waste' ? WASTE_COLOR : VENT_COLOR
|
|
85
|
+
return (
|
|
86
|
+
<g key={`${line.nodeId}-${i}`}>
|
|
87
|
+
<line
|
|
88
|
+
className="cursor-pointer"
|
|
89
|
+
onClick={() => select(line.nodeId)}
|
|
90
|
+
stroke={color}
|
|
91
|
+
strokeDasharray={line.system === 'vent' ? '5 4' : undefined}
|
|
92
|
+
strokeLinecap="round"
|
|
93
|
+
strokeWidth={(line.vertical ? 3.5 : 2.5) + (isSel ? 2 : 0)}
|
|
94
|
+
x1={line.from[0] * SCALE}
|
|
95
|
+
x2={line.to[0] * SCALE}
|
|
96
|
+
y1={line.from[1] * SCALE}
|
|
97
|
+
y2={line.to[1] * SCALE}
|
|
98
|
+
/>
|
|
99
|
+
<text
|
|
100
|
+
fill={color}
|
|
101
|
+
fontSize={9}
|
|
102
|
+
x={((line.from[0] + line.to[0]) / 2) * SCALE + 4}
|
|
103
|
+
y={((line.from[1] + line.to[1]) / 2) * SCALE - 3}
|
|
104
|
+
>
|
|
105
|
+
{line.diameter}"
|
|
106
|
+
</text>
|
|
107
|
+
</g>
|
|
108
|
+
)
|
|
109
|
+
})}
|
|
110
|
+
{diagram.markers.map((marker, i) => (
|
|
111
|
+
<g
|
|
112
|
+
className="cursor-pointer"
|
|
113
|
+
key={`${marker.nodeId}-${i}`}
|
|
114
|
+
onClick={() => select(marker.nodeId)}
|
|
115
|
+
transform={`translate(${marker.point[0] * SCALE}, ${marker.point[1] * SCALE})`}
|
|
116
|
+
>
|
|
117
|
+
{marker.kind === 'vent-termination' ? (
|
|
118
|
+
<path d="M -5 0 L 0 -7 L 5 0" fill="none" stroke={VENT_COLOR} strokeWidth={2} />
|
|
119
|
+
) : (
|
|
120
|
+
<circle fill={MARKER_COLOR} r={3} stroke={MARKER_COLOR} strokeWidth={1.5} />
|
|
121
|
+
)}
|
|
122
|
+
<text fill={MARKER_COLOR} fontSize={9} x={8} y={3}>
|
|
123
|
+
{marker.label}
|
|
124
|
+
</text>
|
|
125
|
+
</g>
|
|
126
|
+
))}
|
|
127
|
+
</g>
|
|
128
|
+
</svg>
|
|
129
|
+
) : (
|
|
130
|
+
<div className="flex h-32 items-center justify-center px-6 text-center text-muted-foreground text-sm">
|
|
131
|
+
No drain, waste, or vent pipes yet. Draw plumbing to see the riser diagram.
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
)
|
|
137
|
+
}
|