@pascal-app/editor 1.0.3 → 1.0.5
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/README.md +106 -0
- package/package.json +42 -38
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +307 -5
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
- package/src/components/editor/construction-reveal-audio.tsx +40 -0
- package/src/components/editor/custom-camera-controls.tsx +275 -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 +37 -26
- package/src/components/editor/editor-layout-v2.tsx +132 -59
- package/src/components/editor/export-manager.tsx +47 -26
- package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
- package/src/components/editor/first-person/build-collider-world.ts +28 -7
- package/src/components/editor/first-person/ground-holes.test.ts +128 -0
- package/src/components/editor/first-person/ground-holes.ts +130 -0
- package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
- package/src/components/editor/first-person/mechanism-targets.ts +88 -0
- package/src/components/editor/first-person-controls.tsx +220 -9
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +466 -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-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +414 -363
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +56 -2
- package/src/components/editor/group-move-3d.ts +2 -0
- package/src/components/editor/group-rotate-handle.tsx +2 -0
- package/src/components/editor/group-transform-shared.test.ts +460 -0
- 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 +409 -197
- 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/riser-diagram-panel.tsx +14 -7
- 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 +233 -0
- package/src/components/editor/room-labels.test.ts +850 -0
- package/src/components/editor/room-labels.tsx +1890 -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 +474 -419
- package/src/components/editor/slab-hole-highlights.tsx +5 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
- package/src/components/editor/snapshot-capture.test.ts +119 -60
- package/src/components/editor/snapshot-capture.ts +36 -5
- package/src/components/editor/thumbnail-generator.test.ts +654 -0
- package/src/components/editor/thumbnail-generator.tsx +1103 -223
- package/src/components/editor/use-floorplan-background-placement.ts +34 -12
- 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 +663 -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.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +343 -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 +8 -4
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- 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-registered-tool-layer.tsx +2 -2
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
- 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 +319 -233
- package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
- package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/point-ask/bubble.tsx +538 -0
- package/src/components/point-ask/hover-chip.tsx +91 -0
- package/src/components/point-ask/mode-pill.tsx +92 -0
- package/src/components/point-ask/optional-motion.ts +22 -0
- package/src/components/point-ask/outline-corners.tsx +93 -0
- package/src/components/point-ask/pins.tsx +711 -0
- package/src/components/point-ask/point-ask-layer.tsx +83 -0
- package/src/components/point-ask/point-ask-styles.ts +105 -0
- package/src/components/point-ask/region-marquee-styles.ts +15 -0
- package/src/components/point-ask/region-marquee.tsx +64 -0
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- 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/roof/roof-edit-system.tsx +1 -0
- 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/distribution/connection-compatibility.test.ts +34 -0
- package/src/components/tools/distribution/connection-compatibility.ts +43 -0
- package/src/components/tools/distribution/distribution-public.ts +9 -0
- package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
- package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
- package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
- package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
- package/src/components/tools/distribution/draw-alignment.ts +35 -0
- package/src/components/tools/distribution/ports.test.ts +56 -0
- package/src/components/tools/distribution/ports.ts +378 -0
- package/src/components/tools/distribution/run-cursor.test.ts +135 -0
- package/src/components/tools/distribution/run-cursor.ts +36 -0
- package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
- package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
- package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
- package/src/components/tools/distribution/run-port-snap.ts +25 -0
- package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
- package/src/components/tools/distribution/run-screen-direction.ts +30 -0
- package/src/components/tools/elevator/elevator-tool.tsx +3 -6
- package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
- package/src/components/tools/item/placement-history.test.tsx +512 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +11 -10
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- 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 +148 -95
- 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/item-surface-move.test.ts +62 -63
- package/src/components/tools/registry/item-surface-move.ts +2 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
- package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
- 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/tool-manager.tsx +59 -24
- package/src/components/tools/wall/wall-drafting.test.ts +175 -47
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +9 -1
- package/src/components/ui/action-menu/camera-actions.tsx +9 -4
- package/src/components/ui/action-menu/control-modes.tsx +15 -42
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
- package/src/components/ui/action-menu/index.tsx +56 -33
- package/src/components/ui/action-menu/measurement-control.tsx +14 -19
- package/src/components/ui/action-menu/placement.ts +20 -0
- package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
- package/src/components/ui/action-menu/view-toggles.tsx +47 -42
- package/src/components/ui/build-panel/build-panel.tsx +242 -0
- package/src/components/ui/command-palette/editor-commands.tsx +7 -42
- package/src/components/ui/command-palette/index.tsx +50 -39
- package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
- package/src/components/ui/controls/action-button.tsx +5 -2
- 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/metric-control.tsx +1 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/controls/slider-control.tsx +1 -1
- package/src/components/ui/controls/toggle-control.tsx +3 -3
- package/src/components/ui/controls/tool-options-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +159 -59
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
- package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
- package/src/components/ui/helpers/helper-manager.tsx +331 -14
- package/src/components/ui/helpers/item-helper.tsx +17 -1
- 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 +28 -22
- 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 +4 -4
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
- 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 +161 -5
- 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 +130 -62
- package/src/components/ui/panels/parametric-field-control.tsx +3 -3
- package/src/components/ui/panels/parametric-inspector.tsx +7 -7
- 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 +790 -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/badge.tsx +8 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +3 -1
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +164 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +8 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
- package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
- 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 +181 -223
- 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 +13 -37
- 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 +2 -4
- package/src/components/ui/sidebar/tab-bar.tsx +14 -4
- 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-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +191 -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 +215 -43
- package/src/hooks/use-auto-save.ts +60 -9
- package/src/hooks/use-ceiling-events.test.ts +123 -118
- package/src/hooks/use-grid-events.test.ts +105 -105
- package/src/hooks/use-keyboard.test.ts +243 -19
- package/src/hooks/use-keyboard.ts +118 -83
- 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 +71 -0
- package/src/hooks/use-placement-notice.ts +73 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +312 -58
- package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
- package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
- package/src/lib/add-storey.test.ts +177 -0
- package/src/lib/add-storey.ts +59 -0
- package/src/lib/below-grade.test.tsx +171 -0
- package/src/lib/below-grade.ts +133 -0
- package/src/lib/camera-facing.test.ts +24 -0
- package/src/lib/camera-facing.ts +24 -0
- 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/direct-manipulation.ts +2 -0
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +55 -0
- package/src/lib/editor-views.ts +178 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/export-texture-utils.ts +2 -4
- 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 +284 -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 +363 -27
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -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/pdfkit-standard-fonts.d.ts +5 -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 +124 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -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 +230 -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 +157 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
- package/src/lib/floorplan/site-plan/geometry.ts +142 -0
- package/src/lib/floorplan/site-plan/index.ts +55 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +287 -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 +70 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
- package/src/lib/follow-pascal-exterior.test.ts +171 -0
- package/src/lib/follow-pascal-exterior.ts +39 -0
- package/src/lib/follow-pascal-presentation.test.ts +203 -0
- package/src/lib/follow-pascal-presentation.ts +129 -0
- package/src/lib/follow-pascal-sight.ts +84 -0
- package/src/lib/follow-pascal-targets.test.ts +406 -0
- package/src/lib/follow-pascal-targets.ts +198 -0
- package/src/lib/follow-pascal.test.ts +350 -0
- package/src/lib/follow-pascal.ts +284 -0
- package/src/lib/fresh-planar-placement.test.ts +148 -0
- package/src/lib/fresh-planar-placement.ts +114 -37
- package/src/lib/geometry-script/author.ts +157 -0
- package/src/lib/geometry-script/client.ts +159 -0
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +13 -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 +636 -126
- package/src/lib/glb-export.ts +241 -120
- package/src/lib/history.test.ts +18 -7
- 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 +22 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- 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 +187 -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 +392 -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/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -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 +201 -0
- package/src/lib/orbit-floor-clearance.ts +154 -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 +122 -0
- package/src/lib/paint-pass-through.ts +78 -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 +267 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +16 -7
- 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 +65 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
- package/src/lib/point-ask/__fixtures__/house.ts +81 -0
- package/src/lib/point-ask/ask-created.test.ts +195 -0
- package/src/lib/point-ask/ask-flow.test.ts +380 -0
- package/src/lib/point-ask/build-context.test.ts +173 -0
- package/src/lib/point-ask/build-context.ts +94 -0
- package/src/lib/point-ask/capture-crop.ts +129 -0
- package/src/lib/point-ask/choreography.ts +69 -0
- package/src/lib/point-ask/compare.test.ts +145 -0
- package/src/lib/point-ask/compare.ts +73 -0
- package/src/lib/point-ask/describe-target.test.ts +165 -0
- package/src/lib/point-ask/describe-target.ts +234 -0
- package/src/lib/point-ask/dictation.ts +79 -0
- package/src/lib/point-ask/edge-marker.test.ts +61 -0
- package/src/lib/point-ask/edge-marker.ts +27 -0
- package/src/lib/point-ask/frame-loop.ts +25 -0
- package/src/lib/point-ask/history-marks.test.ts +94 -0
- package/src/lib/point-ask/history-marks.ts +39 -0
- package/src/lib/point-ask/host-api.ts +32 -0
- package/src/lib/point-ask/outline-box.test.ts +30 -0
- package/src/lib/point-ask/outline-box.ts +11 -0
- package/src/lib/point-ask/overlay-metrics.ts +6 -0
- package/src/lib/point-ask/pick.test.ts +220 -0
- package/src/lib/point-ask/pick.ts +121 -0
- package/src/lib/point-ask/pin-machine.test.ts +494 -0
- package/src/lib/point-ask/pin-machine.ts +217 -0
- package/src/lib/point-ask/placement.test.ts +203 -0
- package/src/lib/point-ask/placement.ts +141 -0
- package/src/lib/point-ask/pointer-tracker.ts +34 -0
- package/src/lib/point-ask/projector.ts +83 -0
- package/src/lib/point-ask/region.test.ts +157 -0
- package/src/lib/point-ask/region.ts +136 -0
- package/src/lib/point-ask/retry.test.ts +217 -0
- package/src/lib/point-ask/retry.ts +37 -0
- package/src/lib/point-ask/scene-commit.test.ts +312 -0
- package/src/lib/point-ask/scene-commit.ts +32 -0
- package/src/lib/point-ask/session.ts +1391 -0
- package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
- package/src/lib/point-ask/spring-keyframes.ts +38 -0
- package/src/lib/point-ask/spring.test.ts +63 -0
- package/src/lib/point-ask/spring.ts +107 -0
- package/src/lib/point-ask/suggestions.test.ts +80 -0
- package/src/lib/point-ask/suggestions.ts +125 -0
- package/src/lib/point-ask/target-scope.test.ts +622 -0
- package/src/lib/point-ask/target-scope.ts +338 -0
- package/src/lib/point-ask/types.ts +48 -0
- package/src/lib/portable-export.test.ts +34 -1
- package/src/lib/portable-export.ts +119 -53
- package/src/lib/print-feature-thickness.ts +10 -1
- package/src/lib/print-roof-solids.test.ts +39 -1
- package/src/lib/print-roof-solids.ts +43 -10
- package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
- 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 +265 -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 +1017 -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 +610 -1
- package/src/lib/scene-clipboard.ts +269 -58
- 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 +54 -75
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sfx-bus.ts +13 -0
- package/src/lib/sfx-player.test.ts +70 -9
- package/src/lib/sfx-player.ts +6 -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/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +214 -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 +580 -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-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -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-changes.ts +35 -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-audio.test.ts +146 -0
- package/src/store/use-audio.tsx +127 -3
- package/src/store/use-construction-reveal.test.ts +173 -0
- package/src/store/use-construction-reveal.ts +145 -0
- package/src/store/use-corner-snap-hint.test.ts +24 -0
- package/src/store/use-corner-snap-hint.ts +47 -0
- 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-follow-pascal.test.ts +62 -0
- package/src/store/use-follow-pascal.ts +53 -0
- package/src/store/use-point-ask.ts +247 -0
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +33 -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 +201 -0
- package/src/store/view-layout.ts +199 -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/render-current-markup.tsx +16 -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/tools/stair/stair-click-guard.test.ts +0 -58
- package/src/components/tools/stair/stair-click-guard.ts +0 -72
- package/src/components/tools/stair/stair-defaults.ts +0 -18
- package/src/components/tools/stair/stair-tool.tsx +0 -634
- 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
|
@@ -23,12 +23,20 @@ import {
|
|
|
23
23
|
resolveBuildingForLevel,
|
|
24
24
|
resolveSelectionProxyId,
|
|
25
25
|
resumeSceneHistory,
|
|
26
|
+
type SlabNode,
|
|
26
27
|
useInteractive,
|
|
27
28
|
useLiveNodeOverrides,
|
|
28
29
|
useLiveTransforms,
|
|
29
30
|
useScene,
|
|
30
31
|
} from '@pascal-app/core'
|
|
31
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
beginPerfAction,
|
|
34
|
+
cancelPerfAction,
|
|
35
|
+
commitPerfAction,
|
|
36
|
+
useRevealPlanActive,
|
|
37
|
+
useRevealWaiting,
|
|
38
|
+
useViewer,
|
|
39
|
+
} from '@pascal-app/viewer'
|
|
32
40
|
import {
|
|
33
41
|
type ComponentProps,
|
|
34
42
|
memo,
|
|
@@ -43,6 +51,14 @@ import {
|
|
|
43
51
|
} from 'react'
|
|
44
52
|
import { useShallow } from 'zustand/react/shallow'
|
|
45
53
|
import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
|
|
54
|
+
import {
|
|
55
|
+
hoverRoomFromHit,
|
|
56
|
+
resolvePlanRoomHit,
|
|
57
|
+
roomPickingEnabled,
|
|
58
|
+
selectionModifiersFromEvent,
|
|
59
|
+
useRoomRecords,
|
|
60
|
+
} from '../../../hooks/use-selected-room'
|
|
61
|
+
import { useCeilingEditSession } from '../../../lib/ceiling-edit-session'
|
|
46
62
|
import { ROTATE_HANDLE_DRAG_LABEL } from '../../../lib/contextual-help'
|
|
47
63
|
import {
|
|
48
64
|
canDirectRotateNode,
|
|
@@ -53,6 +69,7 @@ import {
|
|
|
53
69
|
snapDirectRotationDelta,
|
|
54
70
|
} from '../../../lib/direct-manipulation'
|
|
55
71
|
import { createEditorApi } from '../../../lib/editor-api'
|
|
72
|
+
import { isRoomOwnedPlate, roomOwnedPlateDrillTarget } from '../../../lib/floor-footprints'
|
|
56
73
|
import {
|
|
57
74
|
type FloorplanAnnotationVisibility,
|
|
58
75
|
filterFloorplanAnnotationGeometry,
|
|
@@ -86,7 +103,15 @@ import {
|
|
|
86
103
|
isIdle,
|
|
87
104
|
tangentReshapeScope,
|
|
88
105
|
} from '../../../lib/interaction/scope'
|
|
89
|
-
import {
|
|
106
|
+
import { runFloorplanWallPush, WALL_PUSH_AFFORDANCE } from '../../../lib/room-handle-drag'
|
|
107
|
+
import { selectRoom, selectRoomFromHit } from '../../../lib/room-selection-commands'
|
|
108
|
+
import { clickZoneArea, hoverZoneArea } from '../../../lib/room-zone-routing'
|
|
109
|
+
import {
|
|
110
|
+
emitCanvasNodeSelection,
|
|
111
|
+
enterBuildingFromPlanHit,
|
|
112
|
+
resolveCanvasBuildingId,
|
|
113
|
+
} from '../../../lib/selection-routing'
|
|
114
|
+
import { createSessionWrites, type SessionWrites } from '../../../lib/session-writes'
|
|
90
115
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
91
116
|
import { clearSurfacePlanSnapFeedback } from '../../../lib/surface-plan-snap'
|
|
92
117
|
import { paintZoneMembership } from '../../../lib/units'
|
|
@@ -101,8 +126,13 @@ import useInteractionScope, {
|
|
|
101
126
|
useMovingNode,
|
|
102
127
|
} from '../../../store/use-interaction-scope'
|
|
103
128
|
import { expandSessionSelectionForNode } from '../../../store/use-session-groups'
|
|
129
|
+
import { isViewVisible, VIEW_2D } from '../../../store/view-layout'
|
|
130
|
+
import { FloorRegionControls2D } from '../../editor/floor-region-controls'
|
|
104
131
|
import { startGroupPickUp } from '../../editor/group-actions'
|
|
105
132
|
import { classifyParticipant } from '../../editor/group-transform-shared'
|
|
133
|
+
import { RoomHandleDragPreview2D, RoomHandles2D } from '../../editor/room-handles-2d'
|
|
134
|
+
import { RoomHighlight2D } from '../../editor/room-highlight'
|
|
135
|
+
import { RoomLabels2D } from '../../editor/room-labels'
|
|
106
136
|
import { suppressBoxSelectForPointer } from '../../tools/select/box-select-state'
|
|
107
137
|
import {
|
|
108
138
|
FloorplanGroupSelectionBox,
|
|
@@ -120,6 +150,7 @@ import {
|
|
|
120
150
|
resolveSvgAnnotationCollisions,
|
|
121
151
|
svgAnnotationLabelId,
|
|
122
152
|
} from './floorplan-annotation-layout'
|
|
153
|
+
import { FloorplanDimensionEditOverlay } from './floorplan-dimension-edit-overlay'
|
|
123
154
|
import { FloorplanDimensionRenderer } from './floorplan-dimension-renderer'
|
|
124
155
|
import { FloorplanGeometryRenderer } from './floorplan-geometry-renderer'
|
|
125
156
|
import {
|
|
@@ -127,6 +158,7 @@ import {
|
|
|
127
158
|
resolveFloorplanLabelAngle,
|
|
128
159
|
updateSvgFloorplanLabelOrientations,
|
|
129
160
|
} from './floorplan-label-angle'
|
|
161
|
+
import { useFloorplanReveal } from './floorplan-reveal'
|
|
130
162
|
|
|
131
163
|
/**
|
|
132
164
|
* Registry-driven floor-plan layer.
|
|
@@ -179,22 +211,19 @@ const ScaleAwareFloorplanGroupSelectionBox = memo(function ScaleAwareFloorplanGr
|
|
|
179
211
|
return <FloorplanGroupSelectionBox {...props} unitsPerPixel={unitsPerPixel} />
|
|
180
212
|
})
|
|
181
213
|
|
|
182
|
-
/**
|
|
183
|
-
* Snapshot of node fields captured at drag-start, used by the single-undo
|
|
184
|
-
* dance to revert untracked before re-applying as a single tracked
|
|
185
|
-
* change. The dispatcher only knows about the `affectedIds` the
|
|
186
|
-
* affordance declares; it captures whatever fields exist on each node by
|
|
187
|
-
* cloning the full record minus the registry-managed `id` / `type`.
|
|
188
|
-
*/
|
|
189
|
-
type NodeSnapshot = { id: AnyNodeId; data: Record<string, unknown> }
|
|
190
|
-
|
|
191
214
|
type ActiveDrag = {
|
|
192
215
|
pointerId: number
|
|
193
216
|
captureTarget: Element
|
|
194
217
|
/** Key for the visual `active` flag — e.g. `${nodeId}:${endpoint}`. */
|
|
195
218
|
handleId: string
|
|
196
219
|
session: FloorplanAffordanceSession
|
|
197
|
-
|
|
220
|
+
/**
|
|
221
|
+
* What the session itself wrote to the scene during `apply()` (legacy
|
|
222
|
+
* sessions only; override-only sessions write nothing). Cancel and commit
|
|
223
|
+
* take back exactly these writes, so a collaborator's edit to the same node
|
|
224
|
+
* mid-drag survives.
|
|
225
|
+
*/
|
|
226
|
+
writes: SessionWrites
|
|
198
227
|
historyPaused: boolean
|
|
199
228
|
/**
|
|
200
229
|
* Last plan point handed to `session.apply` (the grab point until the first
|
|
@@ -218,7 +247,6 @@ type ActiveDrag = {
|
|
|
218
247
|
}
|
|
219
248
|
|
|
220
249
|
type FloorplanAffordanceCancelEffects = {
|
|
221
|
-
restoreSnapshots: (snapshots: NodeSnapshot[]) => void
|
|
222
250
|
resumeHistory: () => void
|
|
223
251
|
clearPreview: (id: AnyNodeId) => void
|
|
224
252
|
clearSnapFeedback: () => void
|
|
@@ -242,7 +270,7 @@ export function cancelFloorplanAffordanceDrag(
|
|
|
242
270
|
drag.captureTarget.releasePointerCapture?.(drag.pointerId)
|
|
243
271
|
}
|
|
244
272
|
|
|
245
|
-
|
|
273
|
+
drag.writes.revert()
|
|
246
274
|
if (drag.historyPaused) {
|
|
247
275
|
effects.resumeHistory()
|
|
248
276
|
drag.historyPaused = false
|
|
@@ -378,6 +406,7 @@ type CacheEntry = {
|
|
|
378
406
|
deps: NodeDeps
|
|
379
407
|
base: FloorplanGeometry | null
|
|
380
408
|
overlay: FloorplanGeometry | null
|
|
409
|
+
handles: FloorplanGeometry | null
|
|
381
410
|
node: AnyNode
|
|
382
411
|
}
|
|
383
412
|
|
|
@@ -433,7 +462,7 @@ type FloorplanLevelDataHook = (args: {
|
|
|
433
462
|
nodes: Record<string, AnyNode>
|
|
434
463
|
}) => unknown
|
|
435
464
|
|
|
436
|
-
type FloorplanRenderPass = 'base' | 'overlay'
|
|
465
|
+
type FloorplanRenderPass = 'base' | 'overlay' | 'handles'
|
|
437
466
|
|
|
438
467
|
const POINTER_CURSOR_STYLE = { cursor: 'pointer' } as const
|
|
439
468
|
// Group members advertise the drag-to-move-the-selection gesture.
|
|
@@ -441,18 +470,16 @@ const MOVE_CURSOR_STYLE = { cursor: 'move' } as const
|
|
|
441
470
|
const NO_POINTER_EVENTS_STYLE = { pointerEvents: 'none' } as const
|
|
442
471
|
|
|
443
472
|
export function isFloorplanOpeningPlacementState({
|
|
444
|
-
phase,
|
|
445
473
|
mode,
|
|
446
474
|
tool,
|
|
447
475
|
movingNodeHasWallOpeningPlacement,
|
|
448
476
|
}: {
|
|
449
|
-
phase: string
|
|
450
477
|
mode: string
|
|
451
478
|
tool: string | null
|
|
452
479
|
movingNodeHasWallOpeningPlacement: boolean
|
|
453
480
|
}): boolean {
|
|
454
481
|
return (
|
|
455
|
-
(
|
|
482
|
+
(mode === 'build' && (tool === 'door' || tool === 'window')) ||
|
|
456
483
|
movingNodeHasWallOpeningPlacement
|
|
457
484
|
)
|
|
458
485
|
}
|
|
@@ -471,12 +498,11 @@ export function floorplanEntryYieldsToTool(state: {
|
|
|
471
498
|
}
|
|
472
499
|
|
|
473
500
|
function floorplanEntryYieldsToToolNow(): boolean {
|
|
474
|
-
const {
|
|
501
|
+
const { mode, tool } = useEditor.getState()
|
|
475
502
|
const movingNode = getMovingNode()
|
|
476
503
|
return floorplanEntryYieldsToTool({
|
|
477
504
|
mode,
|
|
478
505
|
openingPlacement: isFloorplanOpeningPlacementState({
|
|
479
|
-
phase,
|
|
480
506
|
mode,
|
|
481
507
|
tool,
|
|
482
508
|
movingNodeHasWallOpeningPlacement:
|
|
@@ -486,21 +512,6 @@ function floorplanEntryYieldsToToolNow(): boolean {
|
|
|
486
512
|
})
|
|
487
513
|
}
|
|
488
514
|
|
|
489
|
-
function snapshotNode(node: AnyNode): NodeSnapshot {
|
|
490
|
-
// Shallow-clone every non-id, non-type field. Arrays / vec tuples are
|
|
491
|
-
// deep-cloned to detach from the live store reference.
|
|
492
|
-
const data: Record<string, unknown> = {}
|
|
493
|
-
for (const [key, value] of Object.entries(node)) {
|
|
494
|
-
if (key === 'id' || key === 'type' || key === 'object' || key === 'parentId') continue
|
|
495
|
-
data[key] = Array.isArray(value) ? [...(value as unknown[])] : value
|
|
496
|
-
}
|
|
497
|
-
return { id: node.id, data }
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function snapshotsToUpdates(snapshots: NodeSnapshot[]) {
|
|
501
|
-
return snapshots.map((s) => ({ id: s.id, data: s.data }))
|
|
502
|
-
}
|
|
503
|
-
|
|
504
515
|
// Stable empty sentinel used by per-entry builders while the floor plan is
|
|
505
516
|
// hidden; committed scene edits still flow through `useScene`.
|
|
506
517
|
const EMPTY_LIVE_OVERRIDES: Map<string, LiveNodeOverrides> = new Map()
|
|
@@ -564,6 +575,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
564
575
|
}, [selectedLevelId, ambientBuildingSourceId, nodes])
|
|
565
576
|
|
|
566
577
|
const levelId = selectedLevelId ?? ambientLevelId
|
|
578
|
+
useRoomRecords(levelId)
|
|
567
579
|
const isAmbient = !selectedLevelId && !!ambientLevelId
|
|
568
580
|
const activeBuildingId = useMemo(
|
|
569
581
|
() => (levelId ? resolveBuildingForLevel(levelId as AnyNodeId, nodes) : null),
|
|
@@ -584,19 +596,17 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
584
596
|
// `handleClickStop` / `handleSelect`, so the tool never sees it. Pass
|
|
585
597
|
// clicks through in that case (`floorplanEntryYieldsToToolNow`).
|
|
586
598
|
const editorMode = useEditor((s) => s.mode)
|
|
587
|
-
const structureLayer = useEditor((s) => s.structureLayer)
|
|
588
599
|
const floorplanSelectionTool = useEditor((s) => s.floorplanSelectionTool)
|
|
589
600
|
const endpointReshape = useEndpointReshape()
|
|
590
601
|
const isMarqueeSelectionActive =
|
|
591
602
|
editorMode === 'select' &&
|
|
592
603
|
floorplanSelectionTool === 'marquee' &&
|
|
593
|
-
structureLayer !== 'zones' &&
|
|
594
604
|
!movingNode &&
|
|
595
605
|
!endpointReshape
|
|
596
606
|
// While the floor plan is not on screen (pure 3D view), per-entry live
|
|
597
607
|
// selectors freeze to `undefined` so drag publishes do not re-render the
|
|
598
608
|
// hidden floor-plan tree.
|
|
599
|
-
const floorplanVisible = useEditor((s) => s
|
|
609
|
+
const floorplanVisible = useEditor((s) => isViewVisible(s, VIEW_2D))
|
|
600
610
|
const drawingType = useDrawingView((s) => s.drawingType)
|
|
601
611
|
const annotationVisibility = useFloorplanAnnotationVisibility((s) => s.visibility)
|
|
602
612
|
const wallDimensionReference = useFloorplanAnnotationVisibility((s) => s.wallDimensionReference)
|
|
@@ -697,6 +707,17 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
697
707
|
swallowNextClick(200)
|
|
698
708
|
return
|
|
699
709
|
}
|
|
710
|
+
// A zone's fill or label takes the same rule as its pill (`clickZoneArea`).
|
|
711
|
+
if (clickedNode?.type === 'zone') {
|
|
712
|
+
clickZoneArea(clickedNode.id, {
|
|
713
|
+
meta: false,
|
|
714
|
+
ctrl: false,
|
|
715
|
+
shift: options.shouldToggle,
|
|
716
|
+
alt: options.isolateMember,
|
|
717
|
+
})
|
|
718
|
+
swallowNextClick(200)
|
|
719
|
+
return
|
|
720
|
+
}
|
|
700
721
|
const currentSelectedIds = useViewer.getState().selection.selectedIds
|
|
701
722
|
let nextSelectedIds: string[]
|
|
702
723
|
if (options.shouldToggle) {
|
|
@@ -754,13 +775,13 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
754
775
|
)
|
|
755
776
|
|
|
756
777
|
const handleClickStop = useCallback((event: React.MouseEvent<SVGGElement>) => {
|
|
757
|
-
if (floorplanEntryYieldsToToolNow()) return
|
|
778
|
+
if (floorplanEntryYieldsToToolNow() || !isIdle(useInteractionScope.getState().scope)) return
|
|
758
779
|
event.stopPropagation()
|
|
759
780
|
}, [])
|
|
760
781
|
|
|
761
782
|
const startDirectMoveDrag = useCallback(
|
|
762
783
|
(id: AnyNodeId, event: ReactPointerEvent<SVGGElement>): boolean => {
|
|
763
|
-
if (event.button !== 0) return false
|
|
784
|
+
if (event.button !== 0 || event.altKey) return false
|
|
764
785
|
|
|
765
786
|
const node = useScene.getState().nodes[id]
|
|
766
787
|
if (!node || !isRegistryMovable(node.type)) return false
|
|
@@ -1015,7 +1036,35 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1015
1036
|
// the stores at event time. Commit clears the interaction scope before
|
|
1016
1037
|
// React paints the next frame; a render-time `undefined` handler leaves
|
|
1017
1038
|
// a short dead zone where the first post-placement selection is lost.
|
|
1018
|
-
if (floorplanEntryYieldsToToolNow()) return
|
|
1039
|
+
if (floorplanEntryYieldsToToolNow() || !isIdle(useInteractionScope.getState().scope)) return
|
|
1040
|
+
if (event.button === 0 && enterBuildingFromPlanHit(id)) {
|
|
1041
|
+
event.stopPropagation()
|
|
1042
|
+
swallowNextClick(200)
|
|
1043
|
+
return
|
|
1044
|
+
}
|
|
1045
|
+
const point = clientToPlan(event.clientX, event.clientY)
|
|
1046
|
+
if (
|
|
1047
|
+
event.button === 0 &&
|
|
1048
|
+
point &&
|
|
1049
|
+
roomPickingEnabled() &&
|
|
1050
|
+
selectRoomFromHit(resolvePlanRoomHit(id, point), selectionModifiersFromEvent(event), id)
|
|
1051
|
+
) {
|
|
1052
|
+
event.stopPropagation()
|
|
1053
|
+
swallowNextClick(200)
|
|
1054
|
+
return
|
|
1055
|
+
}
|
|
1056
|
+
// A room-owned plate past its room: the same routing as the 3D click.
|
|
1057
|
+
const plate = useScene.getState().nodes[id]
|
|
1058
|
+
if (event.button === 0 && point && roomPickingEnabled() && isRoomOwnedPlate(plate)) {
|
|
1059
|
+
const footprint = roomOwnedPlateDrillTarget(useScene.getState().nodes, plate as SlabNode)
|
|
1060
|
+
if (footprint) handleSelect(footprint.id as AnyNodeId, event)
|
|
1061
|
+
else {
|
|
1062
|
+
const hit = resolvePlanRoomHit(id, point)
|
|
1063
|
+
if (hit) selectRoom(hit)
|
|
1064
|
+
event.stopPropagation()
|
|
1065
|
+
}
|
|
1066
|
+
return
|
|
1067
|
+
}
|
|
1019
1068
|
if (startDirectMoveDrag(id, event)) return
|
|
1020
1069
|
if (startDirectRotateDrag(id, event)) return
|
|
1021
1070
|
if (startGroupMoveDrag(id, event)) return
|
|
@@ -1241,6 +1290,16 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1241
1290
|
if (event.button !== 0) return
|
|
1242
1291
|
if (movingNode) return
|
|
1243
1292
|
|
|
1293
|
+
// A wall push arrow runs the same push drag as the 3D arrows (its own
|
|
1294
|
+
// gesture, preview and commit), not a registry affordance session.
|
|
1295
|
+
if (affordance === WALL_PUSH_AFFORDANCE) {
|
|
1296
|
+
event.preventDefault()
|
|
1297
|
+
event.stopPropagation()
|
|
1298
|
+
suppressBoxSelectForPointer(event)
|
|
1299
|
+
runFloorplanWallPush(payload, event.clientX, event.clientY)
|
|
1300
|
+
return
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1244
1303
|
const sceneNodes = useScene.getState().nodes
|
|
1245
1304
|
const node = sceneNodes[nodeId]
|
|
1246
1305
|
if (!node) return
|
|
@@ -1266,13 +1325,9 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1266
1325
|
sceneApi: createSceneApi(useScene),
|
|
1267
1326
|
})
|
|
1268
1327
|
|
|
1269
|
-
const snapshots: NodeSnapshot[] = []
|
|
1270
|
-
for (const id of session.affectedIds) {
|
|
1271
|
-
const n = sceneNodes[id]
|
|
1272
|
-
if (n) snapshots.push(snapshotNode(n))
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
1328
|
pauseSceneHistory(useScene)
|
|
1329
|
+
const writes = createSessionWrites()
|
|
1330
|
+
writes.watch(session.affectedIds)
|
|
1276
1331
|
|
|
1277
1332
|
// Rotation readout setup. The wedge radius tracks the grab distance
|
|
1278
1333
|
// from the pivot (≈ the handle's orbit), nudged inward so the swept
|
|
@@ -1304,7 +1359,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1304
1359
|
captureTarget,
|
|
1305
1360
|
handleId,
|
|
1306
1361
|
session,
|
|
1307
|
-
|
|
1362
|
+
writes,
|
|
1308
1363
|
historyPaused: true,
|
|
1309
1364
|
lastPlanPoint: initialPlanPoint,
|
|
1310
1365
|
rotation,
|
|
@@ -1337,8 +1392,6 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1337
1392
|
cancelFloorplanAffordanceDrag(
|
|
1338
1393
|
dragRef,
|
|
1339
1394
|
{
|
|
1340
|
-
restoreSnapshots: (snapshots) =>
|
|
1341
|
-
useScene.getState().updateNodes(snapshotsToUpdates(snapshots)),
|
|
1342
1395
|
resumeHistory: () => resumeSceneHistory(useScene),
|
|
1343
1396
|
clearPreview: (id) => {
|
|
1344
1397
|
useLiveNodeOverrides.getState().clear(id)
|
|
@@ -1364,15 +1417,17 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1364
1417
|
if (!planPoint) return
|
|
1365
1418
|
|
|
1366
1419
|
drag.lastPlanPoint = planPoint
|
|
1367
|
-
drag.
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1420
|
+
drag.writes.record(() =>
|
|
1421
|
+
drag.session.apply({
|
|
1422
|
+
planPoint,
|
|
1423
|
+
modifiers: {
|
|
1424
|
+
shiftKey: event.shiftKey,
|
|
1425
|
+
altKey: event.altKey,
|
|
1426
|
+
ctrlKey: event.ctrlKey,
|
|
1427
|
+
metaKey: event.metaKey,
|
|
1428
|
+
},
|
|
1429
|
+
}),
|
|
1430
|
+
)
|
|
1376
1431
|
|
|
1377
1432
|
// Live rotation readout. Sweep from the bearing at grab to the
|
|
1378
1433
|
// current pointer bearing around the pivot — the same measurement
|
|
@@ -1409,11 +1464,11 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1409
1464
|
// Sessions with a `commit` hook own their atomic write (e.g.
|
|
1410
1465
|
// affordances that publish to `useLiveNodeOverrides` during
|
|
1411
1466
|
// `apply()` and never touch scene mid-drag). Mirrors the move
|
|
1412
|
-
// overlay's `session.commit` path —
|
|
1413
|
-
//
|
|
1414
|
-
//
|
|
1467
|
+
// overlay's `session.commit` path — take back what the session wrote
|
|
1468
|
+
// (nothing, for those), resume history, then let the session do the
|
|
1469
|
+
// tracked write.
|
|
1415
1470
|
if (commitValid && drag.session.commit) {
|
|
1416
|
-
|
|
1471
|
+
drag.writes.revert()
|
|
1417
1472
|
if (drag.historyPaused) {
|
|
1418
1473
|
resumeSceneHistory(useScene)
|
|
1419
1474
|
drag.historyPaused = false
|
|
@@ -1430,37 +1485,23 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1430
1485
|
}
|
|
1431
1486
|
|
|
1432
1487
|
// Legacy compatibility for sessions that still wrote preview state into
|
|
1433
|
-
// `useScene` during `apply()`:
|
|
1434
|
-
//
|
|
1435
|
-
// `commit()` hook and preview through live overrides/transforms
|
|
1436
|
-
const
|
|
1437
|
-
const
|
|
1438
|
-
for (const snap of drag.snapshots) {
|
|
1439
|
-
const current = sceneNodes[snap.id]
|
|
1440
|
-
if (!current) continue
|
|
1441
|
-
const data: Record<string, unknown> = {}
|
|
1442
|
-
let changed = false
|
|
1443
|
-
for (const [key, before] of Object.entries(snap.data)) {
|
|
1444
|
-
const after = (current as unknown as Record<string, unknown>)[key]
|
|
1445
|
-
if (!deepEqual(before, after)) {
|
|
1446
|
-
data[key] = Array.isArray(after) ? [...(after as unknown[])] : after
|
|
1447
|
-
changed = true
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
if (changed) finalUpdates.push({ id: snap.id, data })
|
|
1451
|
-
}
|
|
1488
|
+
// `useScene` during `apply()`: the fields they wrote are re-applied as
|
|
1489
|
+
// one tracked change after the untracked revert. New sessions should
|
|
1490
|
+
// provide a `commit()` hook and preview through live overrides/transforms.
|
|
1491
|
+
const changes = drag.writes.changes()
|
|
1492
|
+
const changed = changes.create.length + changes.update.length + changes.delete.length > 0
|
|
1452
1493
|
|
|
1453
|
-
if (commitValid &&
|
|
1494
|
+
if (commitValid && changed) {
|
|
1454
1495
|
// Legacy single-undo dance (mirrors the old 3D move-endpoint-tool):
|
|
1455
|
-
// 1.
|
|
1496
|
+
// 1. Take back the session's writes while history is still paused.
|
|
1456
1497
|
// 2. Resume history.
|
|
1457
|
-
// 3. Re-apply
|
|
1458
|
-
|
|
1498
|
+
// 3. Re-apply them — recorded as one tracked change.
|
|
1499
|
+
drag.writes.revert()
|
|
1459
1500
|
if (drag.historyPaused) {
|
|
1460
1501
|
resumeSceneHistory(useScene)
|
|
1461
1502
|
drag.historyPaused = false
|
|
1462
1503
|
}
|
|
1463
|
-
useScene.getState().
|
|
1504
|
+
useScene.getState().applyNodeChanges(changes)
|
|
1464
1505
|
commitPerfAction()
|
|
1465
1506
|
sfxEmitter.emit('sfx:structure-build')
|
|
1466
1507
|
} else {
|
|
@@ -1468,7 +1509,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1468
1509
|
// resume without committing. Also clear any live overrides
|
|
1469
1510
|
// the session published (no-op when the session writes to
|
|
1470
1511
|
// scene directly).
|
|
1471
|
-
|
|
1512
|
+
drag.writes.revert()
|
|
1472
1513
|
if (drag.historyPaused) {
|
|
1473
1514
|
resumeSceneHistory(useScene)
|
|
1474
1515
|
drag.historyPaused = false
|
|
@@ -1504,15 +1545,17 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1504
1545
|
) {
|
|
1505
1546
|
return
|
|
1506
1547
|
}
|
|
1507
|
-
drag.
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1548
|
+
drag.writes.record(() =>
|
|
1549
|
+
drag.session.apply({
|
|
1550
|
+
planPoint: drag.lastPlanPoint,
|
|
1551
|
+
modifiers: {
|
|
1552
|
+
shiftKey: event.shiftKey,
|
|
1553
|
+
altKey: event.altKey,
|
|
1554
|
+
ctrlKey: event.ctrlKey,
|
|
1555
|
+
metaKey: event.metaKey,
|
|
1556
|
+
},
|
|
1557
|
+
}),
|
|
1558
|
+
)
|
|
1516
1559
|
}
|
|
1517
1560
|
|
|
1518
1561
|
window.addEventListener('pointermove', onPointerMove)
|
|
@@ -1542,6 +1585,48 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1542
1585
|
|
|
1543
1586
|
const palette = renderCtx?.palette
|
|
1544
1587
|
|
|
1588
|
+
const renderEntries = (pass: FloorplanRenderPass) =>
|
|
1589
|
+
entries.map((entry) => (
|
|
1590
|
+
<FloorplanRegistryEntry
|
|
1591
|
+
activeDragId={handleIdForNode(activeDragId, entry.id)}
|
|
1592
|
+
annotationVisibility={annotationVisibility}
|
|
1593
|
+
floorplanMode={floorplanMode}
|
|
1594
|
+
floorplanVisible={floorplanVisible}
|
|
1595
|
+
geometryCacheRef={geometryCacheRef}
|
|
1596
|
+
hatchPatternId={renderCtx?.hatchPatternId}
|
|
1597
|
+
hoveredHandleId={handleIdForNode(hoveredHandleId, entry.id)}
|
|
1598
|
+
interactiveElevators={interactiveElevators}
|
|
1599
|
+
isMarqueeSelectionActive={isMarqueeSelectionActive}
|
|
1600
|
+
key={`${pass}-${entry.id}`}
|
|
1601
|
+
levelDataCacheRef={levelDataCacheRef}
|
|
1602
|
+
levelNodeIdsByType={floorplanData.levelNodeIdsByType}
|
|
1603
|
+
moving={movingNode?.id === entry.id}
|
|
1604
|
+
node={entry.node}
|
|
1605
|
+
nodeId={entry.id}
|
|
1606
|
+
nodes={nodes}
|
|
1607
|
+
onClickStop={handleClickStop}
|
|
1608
|
+
onEntryPointerDown={handleEntryPointerDown}
|
|
1609
|
+
onGroupMovePointerDown={handleGroupMoveHandlePointerDown}
|
|
1610
|
+
onHandleHoverChange={setHoveredHandleId}
|
|
1611
|
+
onHandleDoubleClick={commitAffordanceAction}
|
|
1612
|
+
onHandlePointerDown={startAffordanceDrag}
|
|
1613
|
+
palette={palette}
|
|
1614
|
+
unitFocus={unitFocus}
|
|
1615
|
+
pass={pass}
|
|
1616
|
+
sceneRotationDeg={sceneRotationDeg}
|
|
1617
|
+
setMovingNode={setMovingNode}
|
|
1618
|
+
setMovingNodeOrigin={setMovingNodeOrigin}
|
|
1619
|
+
siblingEpoch={entry.dependsOnSiblingInputs ? (siblingEpochs.get(entry.id) ?? 0) : 0}
|
|
1620
|
+
unit={unit}
|
|
1621
|
+
metricNotation={metricNotation}
|
|
1622
|
+
wallDimensionReference={effectiveWallDimensionReference}
|
|
1623
|
+
visibilityRootId={
|
|
1624
|
+
entry.visibilityRootId ?? (entry.ctxOverrides ? undefined : (levelId as AnyNodeId))
|
|
1625
|
+
}
|
|
1626
|
+
ctxOverrides={entry.ctxOverrides}
|
|
1627
|
+
/>
|
|
1628
|
+
))
|
|
1629
|
+
|
|
1545
1630
|
return (
|
|
1546
1631
|
// The outer wrapper stops `click` events that escape an entry's
|
|
1547
1632
|
// `onClick={handleClickStop}`. The base+overlay split means
|
|
@@ -1567,48 +1652,10 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1567
1652
|
strokes, hatches). Lower-rank kinds (zones) paint first so
|
|
1568
1653
|
higher-rank kinds (slabs, then walls / items / shelves) layer
|
|
1569
1654
|
on top in the expected document-order z-stack. */}
|
|
1570
|
-
<g className="floorplan-registry-base">
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
annotationVisibility={annotationVisibility}
|
|
1575
|
-
floorplanMode={floorplanMode}
|
|
1576
|
-
floorplanVisible={floorplanVisible}
|
|
1577
|
-
geometryCacheRef={geometryCacheRef}
|
|
1578
|
-
hatchPatternId={renderCtx?.hatchPatternId}
|
|
1579
|
-
hoveredHandleId={handleIdForNode(hoveredHandleId, entry.id)}
|
|
1580
|
-
interactiveElevators={interactiveElevators}
|
|
1581
|
-
isMarqueeSelectionActive={isMarqueeSelectionActive}
|
|
1582
|
-
key={`base-${entry.id}`}
|
|
1583
|
-
levelDataCacheRef={levelDataCacheRef}
|
|
1584
|
-
levelNodeIdsByType={floorplanData.levelNodeIdsByType}
|
|
1585
|
-
moving={movingNode?.id === entry.id}
|
|
1586
|
-
node={entry.node}
|
|
1587
|
-
nodeId={entry.id}
|
|
1588
|
-
nodes={nodes}
|
|
1589
|
-
onClickStop={handleClickStop}
|
|
1590
|
-
onEntryPointerDown={handleEntryPointerDown}
|
|
1591
|
-
onGroupMovePointerDown={handleGroupMoveHandlePointerDown}
|
|
1592
|
-
onHandleHoverChange={setHoveredHandleId}
|
|
1593
|
-
onHandleDoubleClick={commitAffordanceAction}
|
|
1594
|
-
onHandlePointerDown={startAffordanceDrag}
|
|
1595
|
-
palette={palette}
|
|
1596
|
-
unitFocus={unitFocus}
|
|
1597
|
-
pass="base"
|
|
1598
|
-
sceneRotationDeg={sceneRotationDeg}
|
|
1599
|
-
setMovingNode={setMovingNode}
|
|
1600
|
-
setMovingNodeOrigin={setMovingNodeOrigin}
|
|
1601
|
-
siblingEpoch={entry.dependsOnSiblingInputs ? (siblingEpochs.get(entry.id) ?? 0) : 0}
|
|
1602
|
-
unit={unit}
|
|
1603
|
-
metricNotation={metricNotation}
|
|
1604
|
-
wallDimensionReference={effectiveWallDimensionReference}
|
|
1605
|
-
visibilityRootId={
|
|
1606
|
-
entry.visibilityRootId ?? (entry.ctxOverrides ? undefined : (levelId as AnyNodeId))
|
|
1607
|
-
}
|
|
1608
|
-
ctxOverrides={entry.ctxOverrides}
|
|
1609
|
-
/>
|
|
1610
|
-
))}
|
|
1611
|
-
</g>
|
|
1655
|
+
<g className="floorplan-registry-base">{renderEntries('base')}</g>
|
|
1656
|
+
<RoomHighlight2D levelId={levelId} />
|
|
1657
|
+
<RoomLabels2D levelId={levelId} />
|
|
1658
|
+
<FloorRegionControls2D levelId={levelId} />
|
|
1612
1659
|
{/* Overlay pass — interactive handles (vertex / midpoint / edge /
|
|
1613
1660
|
move) and labels (text / dimensions). Painted after every base
|
|
1614
1661
|
entry so polygon-editor chrome on a selected slab stays above
|
|
@@ -1616,49 +1663,16 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1616
1663
|
slab + wall geometry sitting on top of the zone. Each overlay
|
|
1617
1664
|
still routes through the same selection-handling `<g>` so a
|
|
1618
1665
|
click on a zone's name selects the zone. */}
|
|
1619
|
-
<g className="floorplan-registry-overlay">
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
floorplanVisible={floorplanVisible}
|
|
1626
|
-
geometryCacheRef={geometryCacheRef}
|
|
1627
|
-
hatchPatternId={renderCtx?.hatchPatternId}
|
|
1628
|
-
hoveredHandleId={handleIdForNode(hoveredHandleId, entry.id)}
|
|
1629
|
-
interactiveElevators={interactiveElevators}
|
|
1630
|
-
isMarqueeSelectionActive={isMarqueeSelectionActive}
|
|
1631
|
-
key={`overlay-${entry.id}`}
|
|
1632
|
-
levelDataCacheRef={levelDataCacheRef}
|
|
1633
|
-
levelNodeIdsByType={floorplanData.levelNodeIdsByType}
|
|
1634
|
-
moving={movingNode?.id === entry.id}
|
|
1635
|
-
node={entry.node}
|
|
1636
|
-
nodeId={entry.id}
|
|
1637
|
-
nodes={nodes}
|
|
1638
|
-
onClickStop={handleClickStop}
|
|
1639
|
-
onEntryPointerDown={handleEntryPointerDown}
|
|
1640
|
-
onGroupMovePointerDown={handleGroupMoveHandlePointerDown}
|
|
1641
|
-
onHandleHoverChange={setHoveredHandleId}
|
|
1642
|
-
onHandleDoubleClick={commitAffordanceAction}
|
|
1643
|
-
onHandlePointerDown={startAffordanceDrag}
|
|
1644
|
-
palette={palette}
|
|
1645
|
-
unitFocus={unitFocus}
|
|
1646
|
-
pass="overlay"
|
|
1647
|
-
sceneRotationDeg={sceneRotationDeg}
|
|
1648
|
-
setMovingNode={setMovingNode}
|
|
1649
|
-
setMovingNodeOrigin={setMovingNodeOrigin}
|
|
1650
|
-
siblingEpoch={entry.dependsOnSiblingInputs ? (siblingEpochs.get(entry.id) ?? 0) : 0}
|
|
1651
|
-
unit={unit}
|
|
1652
|
-
metricNotation={metricNotation}
|
|
1653
|
-
wallDimensionReference={effectiveWallDimensionReference}
|
|
1654
|
-
visibilityRootId={
|
|
1655
|
-
entry.visibilityRootId ?? (entry.ctxOverrides ? undefined : (levelId as AnyNodeId))
|
|
1656
|
-
}
|
|
1657
|
-
ctxOverrides={entry.ctxOverrides}
|
|
1658
|
-
/>
|
|
1659
|
-
))}
|
|
1660
|
-
</g>
|
|
1666
|
+
<g className="floorplan-registry-overlay">{renderEntries('overlay')}</g>
|
|
1667
|
+
{/* Handles pass — every grabbable handle above every node's overlay,
|
|
1668
|
+
so a neighbour's outline never covers a selected wall's corner. */}
|
|
1669
|
+
<g className="floorplan-registry-handles">{renderEntries('handles')}</g>
|
|
1670
|
+
<RoomHandles2D levelId={levelId} />
|
|
1671
|
+
<RoomHandleDragPreview2D levelId={levelId} />
|
|
1661
1672
|
<FloorplanAnnotationLayoutResolver active={floorplanVisible} />
|
|
1673
|
+
{/* Click-to-type dimension labels. Portals its HTML input to
|
|
1674
|
+
document.body, so mounting it inside the SVG tree is safe. */}
|
|
1675
|
+
<FloorplanDimensionEditOverlay />
|
|
1662
1676
|
{/* Dashed group bbox — shows what a group drag carries along while a
|
|
1663
1677
|
multi-selection exists, rides the live delta mid-drag, and doubles
|
|
1664
1678
|
as the group's whole-area drag handle. */}
|
|
@@ -2021,18 +2035,37 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2021
2035
|
wallDimensionReference,
|
|
2022
2036
|
visibilityRootId,
|
|
2023
2037
|
}: FloorplanRegistryEntryProps): React.ReactElement | null {
|
|
2038
|
+
// A node an agent just built is drawn on its construction-reveal turn, as in 3D.
|
|
2039
|
+
const revealWaiting = useRevealWaiting(nodeId)
|
|
2040
|
+
const revealActive = useRevealPlanActive(nodeId)
|
|
2041
|
+
const entryRef = useRef<SVGGElement>(null)
|
|
2024
2042
|
const selected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
2025
2043
|
const highlighted = useViewer((state) => state.previewSelectedIds.includes(nodeId))
|
|
2026
|
-
const
|
|
2044
|
+
const multiSelected = useViewer(
|
|
2027
2045
|
(state) =>
|
|
2028
2046
|
state.selection.selectedIds.length > 1 && state.selection.selectedIds.includes(nodeId),
|
|
2029
2047
|
)
|
|
2048
|
+
// A ceiling's vertex / edge handles belong to its Edit ceiling session.
|
|
2049
|
+
const ceilingWithoutSession = useCeilingEditSession(
|
|
2050
|
+
(state) => node.type === 'ceiling' && state.session?.ceilingId !== nodeId,
|
|
2051
|
+
)
|
|
2052
|
+
const suppressHandles = multiSelected || ceilingWithoutSession
|
|
2030
2053
|
const selectedLevelId = useViewer((state) => state.selection.levelId)
|
|
2031
2054
|
const selectionProxyId = resolveSelectionProxyId(
|
|
2032
2055
|
node,
|
|
2033
2056
|
nodes as Record<string, AnyNode | undefined>,
|
|
2034
2057
|
)
|
|
2035
|
-
const
|
|
2058
|
+
const phase = useEditor((state) => state.phase)
|
|
2059
|
+
const hoverTargetId =
|
|
2060
|
+
phase === 'site' ? (resolveCanvasBuildingId(node, nodes) ?? selectionProxyId) : selectionProxyId
|
|
2061
|
+
// What a chat chip points at (`pointedIds`) lights as a hover does, as it does in 3D: the node
|
|
2062
|
+
// itself, or what a hover on it would light.
|
|
2063
|
+
const hovered = useViewer(
|
|
2064
|
+
(state) =>
|
|
2065
|
+
state.hoveredId === hoverTargetId ||
|
|
2066
|
+
state.pointedIds.includes(nodeId) ||
|
|
2067
|
+
state.pointedIds.includes(hoverTargetId),
|
|
2068
|
+
)
|
|
2036
2069
|
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
2037
2070
|
const referencedAnnotationRole = useViewer((state) =>
|
|
2038
2071
|
floorplanEntryReferencedAnnotationRole(node, nodes, new Set(state.selection.selectedIds)),
|
|
@@ -2041,7 +2074,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2041
2074
|
state.activeRotateNodeId === nodeId ? nodeId : null,
|
|
2042
2075
|
)
|
|
2043
2076
|
const groupMoveCursor =
|
|
2044
|
-
|
|
2077
|
+
multiSelected && classifyParticipant(node, selectedLevelId, nodes) !== null
|
|
2045
2078
|
const live = useLiveTransforms((s) => (floorplanVisible ? s.transforms.get(nodeId) : undefined))
|
|
2046
2079
|
const liveOverride = useLiveNodeOverrides((s) =>
|
|
2047
2080
|
floorplanVisible ? s.overrides.get(nodeId) : undefined,
|
|
@@ -2066,25 +2099,55 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2066
2099
|
|
|
2067
2100
|
// Mirror the sidebar tree nodes' hover wiring — `useViewer.hoveredId` drives
|
|
2068
2101
|
// the highlight halo in 3D as well as registry floor-plan hover strokes.
|
|
2069
|
-
const handlePointerEnter = useCallback(
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
currentNode
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
)
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2102
|
+
const handlePointerEnter = useCallback(
|
|
2103
|
+
(event: ReactPointerEvent<SVGGElement>) => {
|
|
2104
|
+
if (floorplanEntryYieldsToToolNow() || !isIdle(useInteractionScope.getState().scope)) return
|
|
2105
|
+
const currentNode = useScene.getState().nodes[nodeId]
|
|
2106
|
+
if (currentNode && useEditor.getState().phase === 'site') {
|
|
2107
|
+
const buildingId = resolveCanvasBuildingId(currentNode, useScene.getState().nodes)
|
|
2108
|
+
if (buildingId) {
|
|
2109
|
+
setHoveredId(buildingId)
|
|
2110
|
+
return
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
if (currentNode?.type === 'zone' && roomPickingEnabled()) {
|
|
2114
|
+
hoverZoneArea(nodeId, selectionModifiersFromEvent(event))
|
|
2115
|
+
return
|
|
2116
|
+
}
|
|
2117
|
+
const point = clientToPlan(event.clientX, event.clientY)
|
|
2118
|
+
if (
|
|
2119
|
+
point &&
|
|
2120
|
+
roomPickingEnabled() &&
|
|
2121
|
+
hoverRoomFromHit(
|
|
2122
|
+
resolvePlanRoomHit(nodeId, point),
|
|
2123
|
+
selectionModifiersFromEvent(event),
|
|
2124
|
+
nodeId,
|
|
2125
|
+
)
|
|
2126
|
+
)
|
|
2127
|
+
return
|
|
2128
|
+
setHoveredId(
|
|
2129
|
+
currentNode
|
|
2130
|
+
? resolveSelectionProxyId(
|
|
2131
|
+
currentNode,
|
|
2132
|
+
useScene.getState().nodes as Record<string, AnyNode | undefined>,
|
|
2133
|
+
)
|
|
2134
|
+
: nodeId,
|
|
2135
|
+
)
|
|
2136
|
+
},
|
|
2137
|
+
[nodeId, setHoveredId],
|
|
2138
|
+
)
|
|
2080
2139
|
|
|
2081
2140
|
const handlePointerLeave = useCallback(() => {
|
|
2141
|
+
useEditor.getState().setHoveredRoom(null)
|
|
2082
2142
|
const node = useScene.getState().nodes[nodeId]
|
|
2083
2143
|
const targetId = node
|
|
2084
|
-
?
|
|
2085
|
-
|
|
2086
|
-
useScene.getState().nodes
|
|
2087
|
-
|
|
2144
|
+
? useEditor.getState().phase === 'site'
|
|
2145
|
+
? (resolveCanvasBuildingId(node, useScene.getState().nodes) ??
|
|
2146
|
+
resolveSelectionProxyId(node, useScene.getState().nodes))
|
|
2147
|
+
: resolveSelectionProxyId(
|
|
2148
|
+
node,
|
|
2149
|
+
useScene.getState().nodes as Record<string, AnyNode | undefined>,
|
|
2150
|
+
)
|
|
2088
2151
|
: nodeId
|
|
2089
2152
|
if (useViewer.getState().hoveredId === targetId) setHoveredId(null)
|
|
2090
2153
|
}, [nodeId, setHoveredId])
|
|
@@ -2160,7 +2223,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2160
2223
|
wallDimensionReference,
|
|
2161
2224
|
visibilityRootId,
|
|
2162
2225
|
})
|
|
2163
|
-
const rawGeometry = cacheEntry ?
|
|
2226
|
+
const rawGeometry = cacheEntry ? cacheEntry[pass] : null
|
|
2164
2227
|
const visibleGeometry = rawGeometry
|
|
2165
2228
|
? filterFloorplanAnnotationGeometry(rawGeometry, presentationVisibility)
|
|
2166
2229
|
: null
|
|
@@ -2168,10 +2231,11 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2168
2231
|
// dimension chrome (all of which live in the overlay pass) while keeping
|
|
2169
2232
|
// its highlighted body geometry.
|
|
2170
2233
|
const geometry =
|
|
2171
|
-
visibleGeometry && suppressHandles && pass
|
|
2234
|
+
visibleGeometry && suppressHandles && pass !== 'base'
|
|
2172
2235
|
? stripHandleChrome(visibleGeometry)
|
|
2173
2236
|
: visibleGeometry
|
|
2174
|
-
|
|
2237
|
+
useFloorplanReveal(nodeId, entryRef, cacheEntry, revealWaiting, revealActive)
|
|
2238
|
+
if (revealWaiting || !geometry) return null
|
|
2175
2239
|
|
|
2176
2240
|
const entryClick = isMarqueeSelectionActive ? undefined : onClickStop
|
|
2177
2241
|
const entryPointerDown = isMarqueeSelectionActive ? undefined : handlePointerDown
|
|
@@ -2180,9 +2244,11 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2180
2244
|
<g
|
|
2181
2245
|
className="floorplan-registry-entry"
|
|
2182
2246
|
data-node-id={nodeId}
|
|
2247
|
+
ref={entryRef}
|
|
2183
2248
|
onClick={entryClick}
|
|
2184
2249
|
onPointerDown={entryPointerDown}
|
|
2185
2250
|
onPointerEnter={handlePointerEnter}
|
|
2251
|
+
onPointerMove={handlePointerEnter}
|
|
2186
2252
|
onPointerLeave={handlePointerLeave}
|
|
2187
2253
|
style={groupMoveCursor ? MOVE_CURSOR_STYLE : POINTER_CURSOR_STYLE}
|
|
2188
2254
|
>
|
|
@@ -2401,6 +2467,7 @@ export function buildFloorplanEntryGeometry({
|
|
|
2401
2467
|
selected,
|
|
2402
2468
|
unit,
|
|
2403
2469
|
metricNotation,
|
|
2470
|
+
roomLabelOverlay: true,
|
|
2404
2471
|
wallDimensionReference,
|
|
2405
2472
|
highlighted,
|
|
2406
2473
|
hovered,
|
|
@@ -2426,6 +2493,7 @@ export function buildFloorplanEntryGeometry({
|
|
|
2426
2493
|
automaticDimensions,
|
|
2427
2494
|
metricNotation,
|
|
2428
2495
|
purpose: 'edit',
|
|
2496
|
+
roomLabelOverlay: true,
|
|
2429
2497
|
wallDimensionReference,
|
|
2430
2498
|
}),
|
|
2431
2499
|
viewState: palette
|
|
@@ -2467,10 +2535,13 @@ export function buildFloorplanEntryGeometry({
|
|
|
2467
2535
|
transform: ctxOverrides.outputTransform,
|
|
2468
2536
|
}
|
|
2469
2537
|
: unprojectedGeometry
|
|
2470
|
-
const { base, overlay } = geometry
|
|
2538
|
+
const { base, overlay: overlayWithHandles } = geometry
|
|
2471
2539
|
? splitFloorplanOverlay(geometry)
|
|
2472
2540
|
: { base: null, overlay: null }
|
|
2473
|
-
const
|
|
2541
|
+
const { rest: overlay, handles } = overlayWithHandles
|
|
2542
|
+
? splitFloorplanHandles(overlayWithHandles)
|
|
2543
|
+
: { rest: null, handles: null }
|
|
2544
|
+
const entry: CacheEntry = { deps, base, overlay, handles, node: effectiveNode }
|
|
2474
2545
|
geometryCache.set(nodeId, entry)
|
|
2475
2546
|
return entry
|
|
2476
2547
|
}
|
|
@@ -2619,6 +2690,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2619
2690
|
}
|
|
2620
2691
|
case 'endpoint-handle': {
|
|
2621
2692
|
if (!palette) return <></>
|
|
2693
|
+
const endpointUnitsPerPixel = g.screenSized ? unitsPerPixel : handleUnitsPerPixel
|
|
2622
2694
|
const handleId = makeHandleId(nodeId, g.payload)
|
|
2623
2695
|
const doubleClickAffordance = floorplanHandleDoubleClickAffordance(g)
|
|
2624
2696
|
const isHovered = hoveredHandleId === handleId
|
|
@@ -2644,10 +2716,10 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2644
2716
|
: palette.endpointHandleFill
|
|
2645
2717
|
const outerRadius =
|
|
2646
2718
|
(isActive ? ENDPOINT_HANDLE_ACTIVE_RADIUS_PX : ENDPOINT_HANDLE_SELECTED_RADIUS_PX) *
|
|
2647
|
-
|
|
2719
|
+
endpointUnitsPerPixel
|
|
2648
2720
|
const dotRadius =
|
|
2649
2721
|
(isActive ? ENDPOINT_HANDLE_ACTIVE_DOT_RADIUS_PX : ENDPOINT_HANDLE_DOT_RADIUS_PX) *
|
|
2650
|
-
|
|
2722
|
+
endpointUnitsPerPixel
|
|
2651
2723
|
return (
|
|
2652
2724
|
<g
|
|
2653
2725
|
key={keyHint}
|
|
@@ -2663,7 +2735,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2663
2735
|
r={outerRadius}
|
|
2664
2736
|
stroke={hoverStroke}
|
|
2665
2737
|
strokeOpacity={isActive ? 0.24 : 0.16}
|
|
2666
|
-
strokeWidth={ENDPOINT_HOVER_GLOW_STROKE_WIDTH_PX *
|
|
2738
|
+
strokeWidth={ENDPOINT_HOVER_GLOW_STROKE_WIDTH_PX * endpointUnitsPerPixel}
|
|
2667
2739
|
style={{ opacity: isHovered || isActive ? 1 : 0, transition: HOVER_TRANSITION }}
|
|
2668
2740
|
vectorEffect="non-scaling-stroke"
|
|
2669
2741
|
/>
|
|
@@ -2675,7 +2747,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2675
2747
|
r={outerRadius}
|
|
2676
2748
|
stroke={hoverStroke}
|
|
2677
2749
|
strokeOpacity={isActive ? 0.72 : 0.52}
|
|
2678
|
-
strokeWidth={ENDPOINT_HOVER_RING_STROKE_WIDTH_PX *
|
|
2750
|
+
strokeWidth={ENDPOINT_HOVER_RING_STROKE_WIDTH_PX * endpointUnitsPerPixel}
|
|
2679
2751
|
style={{ opacity: isHovered || isActive ? 1 : 0, transition: HOVER_TRANSITION }}
|
|
2680
2752
|
vectorEffect="non-scaling-stroke"
|
|
2681
2753
|
/>
|
|
@@ -2923,12 +2995,13 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2923
2995
|
// body (matches the 3D `HANDLE_OFFSET`).
|
|
2924
2996
|
const bi = 0.03 // base inset
|
|
2925
2997
|
const arrowD = `M ${bi},${-sh} L ${bi + sl},${-sh} L ${bi + sl},${-hh} L ${bi + sl + hl},0 L ${bi + sl},${hh} L ${bi + sl},${sh} L ${bi},${sh} Z`
|
|
2926
|
-
// Indigo palette to match the 3D
|
|
2998
|
+
// Indigo palette to match the 3D wall push arrows
|
|
2927
2999
|
// (`ARROW_COLOR` / `ARROW_HOVER_COLOR`) and the corner-sphere
|
|
2928
3000
|
// accent in `floating-action-menu.tsx`.
|
|
2929
3001
|
const fill = isHovered ? '#a5b4fc' : '#8381ed'
|
|
2930
3002
|
const angleDeg = (g.angle * 180) / Math.PI
|
|
2931
|
-
const cursor =
|
|
3003
|
+
const cursor =
|
|
3004
|
+
g.affordance === WALL_PUSH_AFFORDANCE ? 'grab' : g.affordance ? 'ew-resize' : 'move'
|
|
2932
3005
|
const affordance = g.affordance
|
|
2933
3006
|
const payload = g.payload
|
|
2934
3007
|
// No hover-grow: a scaling transform would enlarge the hit area
|
|
@@ -3515,6 +3588,42 @@ export function splitFloorplanOverlay(g: FloorplanGeometry): {
|
|
|
3515
3588
|
return { base: g, overlay: null }
|
|
3516
3589
|
}
|
|
3517
3590
|
|
|
3591
|
+
/** The kinds a pointer grabs: they paint and hit-test above every node's outline. */
|
|
3592
|
+
const HANDLE_KINDS = new Set<FloorplanGeometry['kind']>([
|
|
3593
|
+
'endpoint-handle',
|
|
3594
|
+
'midpoint-handle',
|
|
3595
|
+
'edge-handle',
|
|
3596
|
+
'move-handle',
|
|
3597
|
+
'move-arrow',
|
|
3598
|
+
'rotate-arrow',
|
|
3599
|
+
])
|
|
3600
|
+
|
|
3601
|
+
/**
|
|
3602
|
+
* Splits an overlay tree into its grabbable handles and the rest (labels,
|
|
3603
|
+
* dimensions, outlines). The handles render in a pass of their own after
|
|
3604
|
+
* every node's overlay: an outline painted later (a neighbouring wall's body)
|
|
3605
|
+
* would otherwise cover a selected wall's corner handle and take its press —
|
|
3606
|
+
* the 2D twin of the 3D handle hit priority.
|
|
3607
|
+
*/
|
|
3608
|
+
export function splitFloorplanHandles(g: FloorplanGeometry): {
|
|
3609
|
+
rest: FloorplanGeometry | null
|
|
3610
|
+
handles: FloorplanGeometry | null
|
|
3611
|
+
} {
|
|
3612
|
+
if (HANDLE_KINDS.has(g.kind)) return { rest: null, handles: g }
|
|
3613
|
+
if (g.kind !== 'group') return { rest: g, handles: null }
|
|
3614
|
+
const rest: FloorplanGeometry[] = []
|
|
3615
|
+
const handles: FloorplanGeometry[] = []
|
|
3616
|
+
for (const child of g.children) {
|
|
3617
|
+
const split = splitFloorplanHandles(child)
|
|
3618
|
+
if (split.rest) rest.push(split.rest)
|
|
3619
|
+
if (split.handles) handles.push(split.handles)
|
|
3620
|
+
}
|
|
3621
|
+
return {
|
|
3622
|
+
rest: rest.length ? { ...g, children: rest } : null,
|
|
3623
|
+
handles: handles.length ? { ...g, children: handles } : null,
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3518
3627
|
/**
|
|
3519
3628
|
* Per-node edit chrome hidden while a multi-selection is active: the group is
|
|
3520
3629
|
* manipulated as one rigid piece (drag to move, R/T to rotate), so individual
|
|
@@ -3703,29 +3812,6 @@ function depsValueEqual(a: unknown, b: unknown): boolean {
|
|
|
3703
3812
|
*/
|
|
3704
3813
|
export { floorplanLayerRank }
|
|
3705
3814
|
|
|
3706
|
-
function deepEqual(a: unknown, b: unknown): boolean {
|
|
3707
|
-
if (a === b) return true
|
|
3708
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
3709
|
-
if (a.length !== b.length) return false
|
|
3710
|
-
for (let i = 0; i < a.length; i++) {
|
|
3711
|
-
if (!deepEqual(a[i], b[i])) return false
|
|
3712
|
-
}
|
|
3713
|
-
return true
|
|
3714
|
-
}
|
|
3715
|
-
if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {
|
|
3716
|
-
const aKeys = Object.keys(a as Record<string, unknown>)
|
|
3717
|
-
const bKeys = Object.keys(b as Record<string, unknown>)
|
|
3718
|
-
if (aKeys.length !== bKeys.length) return false
|
|
3719
|
-
for (const key of aKeys) {
|
|
3720
|
-
if (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) {
|
|
3721
|
-
return false
|
|
3722
|
-
}
|
|
3723
|
-
}
|
|
3724
|
-
return true
|
|
3725
|
-
}
|
|
3726
|
-
return false
|
|
3727
|
-
}
|
|
3728
|
-
|
|
3729
3815
|
const ROTATION_WEDGE_COLOR = '#8381ed'
|
|
3730
3816
|
const ROTATION_WEDGE_SEGMENTS = 48
|
|
3731
3817
|
|