@pascal-app/editor 1.0.0-beta.5 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bunfig.toml +4 -0
- package/package.json +15 -8
- package/scripts/generate-print-golden-house.ts +89 -0
- package/src/components/editor/bake-exporter.tsx +5 -0
- package/src/components/editor/bake-thumbnail.tsx +4 -1
- package/src/components/editor/camera-dragging-lifecycle.test.ts +41 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +10 -2
- package/src/components/editor/capture-camera-rig.tsx +59 -0
- package/src/components/editor/custom-camera-controls.tsx +20 -11
- package/src/components/editor/editor-layout-mobile.tsx +6 -6
- package/src/components/editor/editor-layout-v2.tsx +9 -2
- package/src/components/editor/export-manager.tsx +175 -33
- package/src/components/editor/first-person/build-collider-world.test.ts +30 -0
- package/src/components/editor/first-person-controls.tsx +180 -12
- package/src/components/editor/floating-action-menu.tsx +55 -51
- package/src/components/editor/floorplan-panel.tsx +99 -15
- package/src/components/editor/grid.tsx +12 -7
- package/src/components/editor/group-actions.ts +34 -50
- package/src/components/editor/group-move-3d.ts +51 -21
- package/src/components/editor/group-transform-shared.test.ts +78 -0
- package/src/components/editor/group-transform-shared.ts +74 -7
- package/src/components/editor/handles/handle-arrow-raycast.test.ts +86 -0
- package/src/components/editor/handles/handle-arrow.tsx +109 -48
- package/src/components/editor/handles/handle-placement.test.ts +111 -0
- package/src/components/editor/handles/handle-placement.ts +69 -0
- package/src/components/editor/handles/linear-resize-drag.ts +78 -0
- package/src/components/editor/handles/resize-snap.test.ts +46 -0
- package/src/components/editor/handles/resize-snap.ts +10 -1
- package/src/components/editor/handles/use-handle-drag.ts +23 -3
- package/src/components/editor/index.tsx +151 -24
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +67 -119
- package/src/components/editor/selection-manager.tsx +153 -42
- package/src/components/editor/snapshot-capture-overlay.tsx +298 -39
- package/src/components/editor/snapshot-capture.test.ts +422 -0
- package/src/components/editor/snapshot-capture.ts +145 -0
- package/src/components/editor/thumbnail-generator.tsx +295 -239
- package/src/components/editor/use-floorplan-background-placement.ts +53 -5
- package/src/components/editor/wall-move-side-handles.tsx +299 -7
- package/src/components/editor-2d/floorplan-group-move.tsx +67 -33
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +3 -1
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +12 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +129 -38
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +14 -1
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +16 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +2 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +52 -3
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +196 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +226 -35
- package/src/components/procedural-items/inspector.tsx +223 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +402 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.ts +371 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +347 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +281 -305
- package/src/components/systems/ceiling/ceiling-system.tsx +3 -1
- package/src/components/systems/roof/roof-edit-system.test.tsx +120 -0
- package/src/components/systems/roof/roof-edit-system.tsx +10 -19
- package/src/components/systems/selection-affordance-manager.tsx +59 -9
- package/src/components/systems/selection-affordance-services.ts +19 -0
- package/src/components/systems/stair/stair-edit-system.tsx +5 -2
- package/src/components/tools/fence/fence-drafting.test.ts +100 -0
- package/src/components/tools/fence/fence-drafting.ts +6 -25
- package/src/components/tools/item/draft-creation.test.ts +12 -0
- package/src/components/tools/item/draft-creation.ts +10 -0
- package/src/components/tools/item/face-host-commit.test.ts +65 -0
- package/src/components/tools/item/face-host-commit.ts +56 -0
- package/src/components/tools/item/face-host-preview.test.ts +77 -0
- package/src/components/tools/item/face-host-preview.ts +82 -0
- package/src/components/tools/item/item-surface-placement.test.ts +175 -0
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +210 -0
- package/src/components/tools/item/move-tool.tsx +4 -3
- package/src/components/tools/item/placement-math.test.ts +11 -1
- package/src/components/tools/item/placement-math.ts +0 -20
- package/src/components/tools/item/placement-strategies.test.ts +331 -4
- package/src/components/tools/item/placement-strategies.ts +304 -171
- package/src/components/tools/item/placement-surface.test.ts +61 -0
- package/src/components/tools/item/placement-surface.ts +24 -0
- package/src/components/tools/item/placement-types.ts +10 -0
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +257 -0
- package/src/components/tools/item/test-face-host.ts +121 -0
- package/src/components/tools/item/use-draft-node.test.tsx +336 -0
- package/src/components/tools/item/use-draft-node.ts +116 -44
- package/src/components/tools/item/use-placement-coordinator.tsx +672 -256
- package/src/components/tools/registered-tool-install-lifecycle.test.tsx +256 -0
- package/src/components/tools/registry/item-surface-child-frame.test.ts +90 -0
- package/src/components/tools/registry/item-surface-move.test.ts +501 -0
- package/src/components/tools/registry/item-surface-move.ts +441 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +257 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +10 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +505 -164
- package/src/components/tools/registry-tool-context.tsx +30 -0
- package/src/components/tools/select/box-select-tool.tsx +3 -1
- package/src/components/tools/select/marquee-footprint.test.ts +53 -0
- package/src/components/tools/select/marquee-footprint.ts +29 -0
- package/src/components/tools/shared/placement-box.tsx +57 -7
- package/src/components/tools/shared/placement-dimension-guides.tsx +121 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +319 -17
- package/src/components/tools/shared/pointer-support-cap.ts +56 -17
- package/src/components/tools/shared/shelf-stickiness.ts +54 -0
- package/src/components/tools/shared/surface-hit.test.ts +56 -0
- package/src/components/tools/shared/surface-hit.ts +23 -0
- package/src/components/tools/shared/surface-rejection.test.tsx +106 -0
- package/src/components/tools/shared/surface-rejection.tsx +29 -0
- package/src/components/tools/site/terrain-sculpt-grid.tsx +9 -5
- package/src/components/tools/site/terrain-sculpt-tool.tsx +1 -1
- package/src/components/tools/stair/stair-tool.tsx +127 -74
- package/src/components/tools/tool-manager.tsx +25 -8
- package/src/components/tools/wall/wall-drafting.test.ts +320 -2
- package/src/components/tools/wall/wall-drafting.ts +23 -114
- package/src/components/tools/zone/zone-tool.tsx +13 -4
- package/src/components/ui/action-menu/control-modes.tsx +9 -6
- package/src/components/ui/action-menu/index.tsx +11 -1
- package/src/components/ui/command-palette/editor-commands.tsx +63 -13
- package/src/components/ui/command-palette/index.tsx +7 -2
- package/src/components/ui/controls/material-paint-panel.tsx +6 -4
- package/src/components/ui/controls/material-picker.tsx +4 -4
- package/src/components/ui/controls/metric-control.tsx +29 -13
- package/src/components/ui/controls/scene-material-list.tsx +6 -5
- package/src/components/ui/controls/segmented-control.tsx +8 -1
- package/src/components/ui/controls/slider-control.tsx +44 -21
- package/src/components/ui/controls/toggle-control.tsx +18 -5
- package/src/components/ui/controls/tool-options-panel.tsx +126 -0
- package/src/components/ui/floating-level-selector.tsx +103 -10
- package/src/components/ui/helpers/contextual-helper-panel.tsx +9 -0
- package/src/components/ui/helpers/helper-manager.tsx +47 -11
- package/src/components/ui/helpers/item-helper.tsx +1 -1
- package/src/components/ui/helpers/registered-tool-helper.tsx +21 -0
- package/src/components/ui/panels/homogeneous-selection.test.ts +80 -0
- package/src/components/ui/panels/homogeneous-selection.ts +47 -0
- package/src/components/ui/panels/mobile-selection-bar.tsx +11 -5
- package/src/components/ui/panels/multi-field-value.test.ts +209 -0
- package/src/components/ui/panels/multi-field-value.ts +163 -0
- package/src/components/ui/panels/multi-height-mode.tsx +185 -0
- package/src/components/ui/panels/multi-parametric-inspector.tsx +266 -0
- package/src/components/ui/panels/multi-selection-panel.tsx +48 -37
- package/src/components/ui/panels/node-display.ts +4 -0
- package/src/components/ui/panels/panel-manager.tsx +77 -3
- package/src/components/ui/panels/parametric-field-control.tsx +173 -0
- package/src/components/ui/panels/parametric-field-utils.ts +18 -0
- package/src/components/ui/panels/parametric-inspector.tsx +35 -164
- package/src/components/ui/panels/reference-panel.tsx +34 -4
- package/src/components/ui/primitives/checkbox.tsx +34 -0
- package/src/components/ui/primitives/shortcut-token.tsx +23 -1
- package/src/components/ui/scene-loader.tsx +35 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +502 -31
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +20 -23
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +167 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +128 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +8 -1
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +19 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +222 -50
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +2 -7
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +28 -11
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +3 -8
- package/src/components/ui/sidebar/panels/site-panel/tree-node.test.ts +8 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +20 -7
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +224 -0
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +22 -0
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +24 -7
- package/src/components/ui/sidebar/tab-bar.tsx +2 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +17 -2
- package/src/components/ui/snap-target-badge.tsx +2 -0
- package/src/components/viewer/viewer-scene-header.tsx +5 -1
- package/src/components/viewer/viewer-stage.tsx +4 -1
- package/src/components/viewer/viewer-units-panel.tsx +116 -0
- package/src/components/viewer-overlay.tsx +1 -22
- package/src/components/viewer-zone-system.tsx +6 -2
- package/src/components/walkthrough-hud.tsx +19 -10
- package/src/hooks/use-auto-save.test.ts +73 -1
- package/src/hooks/use-auto-save.ts +76 -5
- package/src/hooks/use-ceiling-events.test.ts +144 -0
- package/src/hooks/use-ceiling-events.ts +5 -1
- package/src/hooks/use-drag-action.ts +17 -3
- package/src/hooks/use-grid-events.test.ts +240 -0
- package/src/hooks/use-grid-events.ts +190 -11
- package/src/hooks/use-keyboard.test.ts +244 -0
- package/src/hooks/use-keyboard.ts +132 -63
- package/src/hooks/use-registered-tool-enabled.ts +20 -0
- package/src/hooks/use-save-shortcut.ts +33 -0
- package/src/index.tsx +87 -4
- package/src/lib/active-placement-surface.test.ts +16 -0
- package/src/lib/active-placement-surface.ts +14 -0
- package/src/lib/ceiling-surface-raycast.test.ts +64 -0
- package/src/lib/ceiling-surface-raycast.ts +12 -0
- package/src/lib/contextual-help-extension.ts +17 -0
- package/src/lib/continuation.test.ts +11 -0
- package/src/lib/continuation.ts +8 -1
- package/src/lib/direct-manipulation.test.ts +132 -0
- package/src/lib/direct-manipulation.ts +38 -1
- package/src/lib/editor-api.ts +6 -3
- package/src/lib/elevation-guides.test.ts +28 -1
- package/src/lib/elevation-guides.ts +22 -7
- package/src/lib/export-texture-utils.test.ts +167 -0
- package/src/lib/export-texture-utils.ts +121 -0
- package/src/lib/export-user-data.test.ts +48 -0
- package/src/lib/export-user-data.ts +69 -0
- package/src/lib/floating-menu-scale.test.ts +29 -0
- package/src/lib/floating-menu-scale.ts +14 -0
- package/src/lib/floorplan/apply-alignment.test.ts +47 -2
- package/src/lib/floorplan/apply-alignment.ts +10 -1
- package/src/lib/floorplan/floorplan-export.test.ts +536 -2
- package/src/lib/floorplan/floorplan-export.tsx +120 -19
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +26 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +32 -24
- package/src/lib/floorplan/floorplan-readonly.ts +5 -0
- package/src/lib/floorplan-grid-event-point.test.ts +29 -0
- package/src/lib/floorplan-grid-event-point.ts +13 -0
- package/src/lib/fresh-planar-placement.ts +13 -5
- package/src/lib/glb-export-serialize.test.ts +280 -0
- package/src/lib/glb-export.test.ts +1375 -21
- package/src/lib/glb-export.ts +781 -106
- package/src/lib/grid-event-presentation.ts +16 -0
- package/src/lib/history.test.ts +528 -147
- package/src/lib/history.ts +81 -7
- package/src/lib/host-tree-children.test.ts +27 -0
- package/src/lib/host-tree-children.ts +79 -0
- package/src/lib/interaction/hot-set.test.ts +10 -1
- package/src/lib/interaction/hot-set.ts +3 -1
- package/src/lib/interaction/overlay-policy.test.ts +33 -1
- package/src/lib/interaction/overlay-policy.ts +16 -0
- package/src/lib/interaction/registered-drafting.ts +36 -0
- package/src/lib/interaction/scope.ts +31 -0
- package/src/lib/level-duplication.test.ts +73 -0
- package/src/lib/level-duplication.ts +32 -2
- package/src/lib/level-print-export.test.ts +484 -0
- package/src/lib/level-print-export.ts +526 -0
- package/src/lib/linear-display.test.ts +59 -0
- package/src/lib/linear-display.ts +39 -0
- package/src/lib/local-guide-image.test.ts +24 -0
- package/src/lib/local-guide-image.ts +34 -1
- package/src/lib/local-project-presentation-persistence.test.ts +302 -0
- package/src/lib/local-project-presentation-persistence.ts +335 -0
- package/src/lib/material-paint.ts +2 -0
- package/src/lib/model-export.ts +29 -0
- package/src/lib/node-action-movement.ts +21 -0
- package/src/lib/paint-preview-owner.ts +71 -0
- package/src/lib/planar-cursor-placement.test.ts +178 -1
- package/src/lib/planar-cursor-placement.ts +61 -5
- package/src/lib/plugin-panels.test.ts +92 -1
- package/src/lib/plugin-panels.ts +48 -0
- package/src/lib/portable-export.test.ts +455 -0
- package/src/lib/portable-export.ts +1042 -0
- package/src/lib/print-3mf.test.ts +86 -0
- package/src/lib/print-3mf.ts +161 -0
- package/src/lib/print-content-scope.test.ts +107 -0
- package/src/lib/print-content-scope.ts +90 -0
- package/src/lib/print-export.test.ts +294 -0
- package/src/lib/print-export.ts +735 -0
- package/src/lib/print-feature-thickness.test.ts +147 -0
- package/src/lib/print-feature-thickness.ts +180 -0
- package/src/lib/print-golden-house.test-fixture.ts +352 -0
- package/src/lib/print-golden-house.test.ts +249 -0
- package/src/lib/print-roof-solids.test.ts +133 -0
- package/src/lib/print-roof-solids.ts +630 -0
- package/src/lib/print-shell-compiler-baseline.test.ts +419 -0
- package/src/lib/print-shell-compiler-baseline.ts +225 -0
- package/src/lib/print-shell-compiler-manifold-core.ts +379 -0
- package/src/lib/print-shell-compiler-manifold-worker.ts +200 -0
- package/src/lib/print-shell-compiler-manifold.worker.ts +20 -0
- package/src/lib/print-shell-compiler-mesh-data.ts +32 -0
- package/src/lib/print-shell-compiler-protocol.ts +38 -0
- package/src/lib/print-shell-compiler.ts +273 -0
- package/src/lib/print-wall-solids.test.ts +194 -0
- package/src/lib/print-wall-solids.ts +328 -0
- package/src/lib/rigid-plan-svg-transform.test.ts +35 -0
- package/src/lib/rigid-plan-svg-transform.ts +24 -0
- package/src/lib/scene-clipboard.ts +1 -1
- package/src/lib/scene.ts +22 -8
- package/src/lib/selection-routing.test.ts +105 -1
- package/src/lib/selection-routing.ts +19 -0
- package/src/lib/send-to-app.test.ts +108 -0
- package/src/lib/send-to-app.ts +121 -0
- package/src/lib/sfx/movement-tick.ts +37 -0
- package/src/lib/sfx-bus.ts +4 -0
- package/src/lib/sfx-player.ts +8 -0
- package/src/lib/snapping-mode.test.ts +32 -4
- package/src/lib/snapping-mode.ts +11 -7
- package/src/lib/surface-attachment.ts +77 -0
- package/src/lib/units.ts +254 -0
- package/src/lib/usdz-export.ts +65 -0
- package/src/lib/use-linear-display.ts +8 -32
- package/src/lib/walkthrough-pointer-lock.ts +47 -0
- package/src/store/live-draft-preview-stores.test.ts +3 -0
- package/src/store/terrain-sculpt-mode.test.ts +3 -6
- package/src/store/tool-mode.test.ts +87 -0
- package/src/store/use-camera-hint-focus.ts +48 -0
- package/src/store/use-editor.tsx +299 -188
- package/src/store/use-floorplan-draft-preview.ts +9 -0
- package/src/store/use-handle-group.ts +4 -0
- package/src/store/use-interaction-scope.test.ts +23 -1
- package/src/store/use-interaction-scope.ts +63 -2
- package/src/store/use-placement-preview.ts +56 -3
- package/src/store/use-send-to-app.ts +21 -0
- package/src/components/tools/roof/roof-tool.tsx +0 -656
- package/src/components/ui/helpers/roof-helper.tsx +0 -14
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type FloorplanGeometry,
|
|
11
11
|
type FloorplanPalette,
|
|
12
12
|
type FloorplanPoint,
|
|
13
|
+
type FloorplanScope,
|
|
13
14
|
type GeometryContext,
|
|
14
15
|
isNodeKindEnabled,
|
|
15
16
|
isRegistryMovable,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
useLiveTransforms,
|
|
27
28
|
useScene,
|
|
28
29
|
} from '@pascal-app/core'
|
|
29
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
30
|
+
import { beginPerfAction, cancelPerfAction, commitPerfAction, useViewer } from '@pascal-app/viewer'
|
|
30
31
|
import {
|
|
31
32
|
type ComponentProps,
|
|
32
33
|
memo,
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
useRef,
|
|
40
41
|
useState,
|
|
41
42
|
} from 'react'
|
|
43
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
42
44
|
import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
|
|
43
45
|
import { ROTATE_HANDLE_DRAG_LABEL } from '../../../lib/contextual-help'
|
|
44
46
|
import {
|
|
@@ -46,6 +48,7 @@ import {
|
|
|
46
48
|
resolveDirectManipulationNode,
|
|
47
49
|
resolveDirectRotationDragDelta,
|
|
48
50
|
resolveDirectRotationPatch,
|
|
51
|
+
shouldStartDirectMoveDrag,
|
|
49
52
|
snapDirectRotationDelta,
|
|
50
53
|
} from '../../../lib/direct-manipulation'
|
|
51
54
|
import { createEditorApi } from '../../../lib/editor-api'
|
|
@@ -85,6 +88,7 @@ import {
|
|
|
85
88
|
import { emitCanvasNodeSelection } from '../../../lib/selection-routing'
|
|
86
89
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
87
90
|
import { clearSurfacePlanSnapFeedback } from '../../../lib/surface-plan-snap'
|
|
91
|
+
import { paintZoneMembership } from '../../../lib/units'
|
|
88
92
|
import useDirectManipulationFeedback from '../../../store/use-direct-manipulation-feedback'
|
|
89
93
|
import useDrawingView from '../../../store/use-drawing-view'
|
|
90
94
|
import useEditor, { isAngleSnapActive } from '../../../store/use-editor'
|
|
@@ -246,6 +250,7 @@ export function cancelFloorplanAffordanceDrag(
|
|
|
246
250
|
for (const id of drag.session.affectedIds) effects.clearPreview(id)
|
|
247
251
|
effects.endReshapeScope(drag)
|
|
248
252
|
effects.clearDragFeedback?.()
|
|
253
|
+
cancelPerfAction()
|
|
249
254
|
return true
|
|
250
255
|
}
|
|
251
256
|
|
|
@@ -310,6 +315,14 @@ export function floorplanAffordanceReshapeScope(
|
|
|
310
315
|
return null
|
|
311
316
|
}
|
|
312
317
|
|
|
318
|
+
function floorplanAffordancePerfAction(node: AnyNode, affordance: string): string {
|
|
319
|
+
if (affordance.includes('endpoint')) return `drag:${node.type}-endpoint`
|
|
320
|
+
if (affordance.includes('resize')) return 'drag:resize'
|
|
321
|
+
if (affordance.includes('rotate')) return 'drag:rotate'
|
|
322
|
+
if (affordance.includes('move')) return 'drag:move'
|
|
323
|
+
return 'drag:reshape'
|
|
324
|
+
}
|
|
325
|
+
|
|
313
326
|
/**
|
|
314
327
|
* Transient live-rotation readout state. Rebuilt each pointer-move while a
|
|
315
328
|
* rotate-arrow is dragged and cleared on release. World-plan coords.
|
|
@@ -328,6 +341,15 @@ type FloorplanEntryDescriptor = {
|
|
|
328
341
|
node: AnyNode
|
|
329
342
|
dependsOnSiblingInputs: boolean
|
|
330
343
|
ctxOverrides?: FloorplanContextOverrides
|
|
344
|
+
scopeRank?: number
|
|
345
|
+
visibilityRootId?: AnyNodeId
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** The focused unit and its members; one stable object per focus change so
|
|
349
|
+
* cached zone geometry invalidates exactly when the focus does. */
|
|
350
|
+
export type FloorplanUnitFocus = {
|
|
351
|
+
focusedUnitId: string
|
|
352
|
+
focusedUnitMemberIds: readonly string[]
|
|
331
353
|
}
|
|
332
354
|
|
|
333
355
|
type NodeDeps = {
|
|
@@ -343,10 +365,12 @@ type NodeDeps = {
|
|
|
343
365
|
moving: boolean
|
|
344
366
|
liveOverride: LiveNodeOverrides | undefined
|
|
345
367
|
palette: FloorplanPalette | undefined
|
|
368
|
+
unitFocus: FloorplanUnitFocus | undefined
|
|
346
369
|
siblingEpoch: number
|
|
347
370
|
committedNodes: Record<string, AnyNode> | null
|
|
348
371
|
dependencyNodes: AnyNode[]
|
|
349
372
|
interactiveElevators: unknown
|
|
373
|
+
ctxOverrides: FloorplanContextOverrides | undefined
|
|
350
374
|
}
|
|
351
375
|
|
|
352
376
|
type CacheEntry = {
|
|
@@ -367,6 +391,40 @@ type FloorplanContextOverrides = {
|
|
|
367
391
|
children: AnyNode[]
|
|
368
392
|
siblings: AnyNode[]
|
|
369
393
|
parent: AnyNode | null
|
|
394
|
+
outputTransform?: { translate?: FloorplanPoint; rotate?: number }
|
|
395
|
+
trackAllNodes?: boolean
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function collectDirectFloorplanScopeNodes(
|
|
399
|
+
nodes: Record<string, AnyNode>,
|
|
400
|
+
parent: AnyNode,
|
|
401
|
+
scope: FloorplanScope,
|
|
402
|
+
): AnyNode[] {
|
|
403
|
+
const scopedKinds = new Set(kindsWithFloorplanScope(scope))
|
|
404
|
+
const declaredChildren = new Set(
|
|
405
|
+
Array.isArray((parent as { children?: AnyNodeId[] }).children)
|
|
406
|
+
? (parent as { children: AnyNodeId[] }).children
|
|
407
|
+
: [],
|
|
408
|
+
)
|
|
409
|
+
return Object.values(nodes).filter(
|
|
410
|
+
(node) =>
|
|
411
|
+
scopedKinds.has(node.type) && (node.parentId === parent.id || declaredChildren.has(node.id)),
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function siteToFloorplanTransform(
|
|
416
|
+
buildingPosition: readonly [number, number, number],
|
|
417
|
+
buildingRotationY: number,
|
|
418
|
+
): { translate: FloorplanPoint; rotate: number } {
|
|
419
|
+
const cos = Math.cos(buildingRotationY)
|
|
420
|
+
const sin = Math.sin(buildingRotationY)
|
|
421
|
+
return {
|
|
422
|
+
translate: [
|
|
423
|
+
-buildingPosition[0] * cos + buildingPosition[2] * sin,
|
|
424
|
+
-buildingPosition[0] * sin - buildingPosition[2] * cos,
|
|
425
|
+
],
|
|
426
|
+
rotate: buildingRotationY,
|
|
427
|
+
}
|
|
370
428
|
}
|
|
371
429
|
|
|
372
430
|
type FloorplanLevelDataHook = (args: {
|
|
@@ -428,6 +486,7 @@ function snapshotsToUpdates(snapshots: NodeSnapshot[]) {
|
|
|
428
486
|
// Stable empty sentinel used by per-entry builders while the floor plan is
|
|
429
487
|
// hidden; committed scene edits still flow through `useScene`.
|
|
430
488
|
const EMPTY_LIVE_OVERRIDES: Map<string, LiveNodeOverrides> = new Map()
|
|
489
|
+
const NO_MEMBER_IDS: readonly string[] = []
|
|
431
490
|
|
|
432
491
|
export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
433
492
|
const selectedLevelId = useViewer((s) => s.selection.levelId)
|
|
@@ -436,6 +495,17 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
436
495
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
437
496
|
const setSelection = useViewer((s) => s.setSelection)
|
|
438
497
|
const nodes = useScene((s) => s.nodes)
|
|
498
|
+
const focusedUnitId = useViewer((s) => s.focusedUnitId)
|
|
499
|
+
const focusedUnitMemberIds = useScene(
|
|
500
|
+
useShallow((s) => {
|
|
501
|
+
const focusedUnit = focusedUnitId ? s.nodes[focusedUnitId] : undefined
|
|
502
|
+
return focusedUnit?.type === 'unit' ? focusedUnit.members : NO_MEMBER_IDS
|
|
503
|
+
}),
|
|
504
|
+
)
|
|
505
|
+
const unitFocus = useMemo<FloorplanUnitFocus | undefined>(
|
|
506
|
+
() => (focusedUnitId ? { focusedUnitId, focusedUnitMemberIds } : undefined),
|
|
507
|
+
[focusedUnitId, focusedUnitMemberIds],
|
|
508
|
+
)
|
|
439
509
|
const installedPlugins = useScene((s) => s.installedPlugins)
|
|
440
510
|
const movingNode = useMovingNode()
|
|
441
511
|
// When a building is being moved, its explicit selection may be
|
|
@@ -477,6 +547,13 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
477
547
|
|
|
478
548
|
const levelId = selectedLevelId ?? ambientLevelId
|
|
479
549
|
const isAmbient = !selectedLevelId && !!ambientLevelId
|
|
550
|
+
const activeBuildingId = useMemo(
|
|
551
|
+
() => (levelId ? resolveBuildingForLevel(levelId as AnyNodeId, nodes) : null),
|
|
552
|
+
[levelId, nodes],
|
|
553
|
+
)
|
|
554
|
+
const activeBuildingLiveTransform = useLiveTransforms((state) =>
|
|
555
|
+
activeBuildingId ? state.transforms.get(activeBuildingId) : undefined,
|
|
556
|
+
)
|
|
480
557
|
const renderCtx = useFloorplanStaticRender()
|
|
481
558
|
const sceneRotationDeg = renderCtx?.getSceneRotationDeg() ?? 0
|
|
482
559
|
const setMovingNode = useEditor((s) => s.setMovingNode)
|
|
@@ -595,6 +672,13 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
595
672
|
|
|
596
673
|
const applyEntrySelection = useCallback(
|
|
597
674
|
(id: AnyNodeId, options: { shouldToggle: boolean; isolateMember: boolean }) => {
|
|
675
|
+
const focusedUnitId = useViewer.getState().focusedUnitId
|
|
676
|
+
const clickedNode = useScene.getState().nodes[id]
|
|
677
|
+
if (focusedUnitId && clickedNode?.type === 'zone') {
|
|
678
|
+
paintZoneMembership(focusedUnitId, clickedNode.id)
|
|
679
|
+
swallowNextClick(200)
|
|
680
|
+
return
|
|
681
|
+
}
|
|
598
682
|
const currentSelectedIds = useViewer.getState().selection.selectedIds
|
|
599
683
|
let nextSelectedIds: string[]
|
|
600
684
|
if (options.shouldToggle) {
|
|
@@ -644,16 +728,24 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
644
728
|
|
|
645
729
|
const startDirectMoveDrag = useCallback(
|
|
646
730
|
(id: AnyNodeId, event: ReactPointerEvent<SVGGElement>): boolean => {
|
|
647
|
-
if (event.button !== 0
|
|
731
|
+
if (event.button !== 0) return false
|
|
648
732
|
|
|
649
733
|
const node = useScene.getState().nodes[id]
|
|
650
734
|
if (!node || !isRegistryMovable(node.type)) return false
|
|
651
|
-
// Sole selection only: per-node direct manipulation stands down for a
|
|
652
|
-
// multi-selection (the group session owns plain drags there, and Cmd is
|
|
653
|
-
// the selection-toggle key — a wobbly Cmd+click must not yank one
|
|
654
|
-
// member out of the group).
|
|
655
735
|
const currentSelectedIds = useViewer.getState().selection.selectedIds
|
|
656
|
-
|
|
736
|
+
const allowPlainDrag = nodeRegistry.get(node.type)?.capabilities?.movable?.directDrag === true
|
|
737
|
+
const commandModifier = event.metaKey || event.ctrlKey
|
|
738
|
+
if (
|
|
739
|
+
!shouldStartDirectMoveDrag({
|
|
740
|
+
allowPlainDrag,
|
|
741
|
+
commandModifier,
|
|
742
|
+
handleOwnsPointer: false,
|
|
743
|
+
nodeId: id,
|
|
744
|
+
selectedIds: currentSelectedIds,
|
|
745
|
+
})
|
|
746
|
+
) {
|
|
747
|
+
return false
|
|
748
|
+
}
|
|
657
749
|
|
|
658
750
|
event.preventDefault()
|
|
659
751
|
event.stopPropagation()
|
|
@@ -705,9 +797,8 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
705
797
|
if (endEvent.pointerId !== pointerId) return
|
|
706
798
|
cleanup()
|
|
707
799
|
if (!engaged) {
|
|
708
|
-
// Cmd/Ctrl+click without drag: toggle member (options object, not bare boolean).
|
|
709
800
|
applyEntrySelection(id, {
|
|
710
|
-
shouldToggle:
|
|
801
|
+
shouldToggle: commandModifier,
|
|
711
802
|
isolateMember: false,
|
|
712
803
|
})
|
|
713
804
|
}
|
|
@@ -929,7 +1020,12 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
929
1020
|
|
|
930
1021
|
collectLevelDataKind(levelId as AnyNodeId)
|
|
931
1022
|
|
|
932
|
-
const pushEntry = (
|
|
1023
|
+
const pushEntry = (
|
|
1024
|
+
id: AnyNodeId,
|
|
1025
|
+
node: AnyNode,
|
|
1026
|
+
ctxOverrides?: FloorplanContextOverrides,
|
|
1027
|
+
options?: { scopeRank?: number; visibilityRootId?: AnyNodeId },
|
|
1028
|
+
) => {
|
|
933
1029
|
if (!isNodeKindEnabled(node.type, installedPlugins)) return
|
|
934
1030
|
const drawingNode = resolveNodeForDrawingType(node, nodes, drawingType)
|
|
935
1031
|
if (!drawingNode) return
|
|
@@ -942,6 +1038,8 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
942
1038
|
)
|
|
943
1039
|
const descriptor: FloorplanEntryDescriptor = { id, node: drawingNode, dependsOnSiblingInputs }
|
|
944
1040
|
if (ctxOverrides) descriptor.ctxOverrides = ctxOverrides
|
|
1041
|
+
if (options?.scopeRank !== undefined) descriptor.scopeRank = options.scopeRank
|
|
1042
|
+
if (options?.visibilityRootId) descriptor.visibilityRootId = options.visibilityRootId
|
|
945
1043
|
out.push(descriptor)
|
|
946
1044
|
}
|
|
947
1045
|
|
|
@@ -977,13 +1075,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
977
1075
|
// as siblings of the level, not under it — the `visit(levelId)` DFS
|
|
978
1076
|
// above doesn't reach them. Walk every node of those kinds whose
|
|
979
1077
|
// parent matches the active level's building, and synthesise a
|
|
980
|
-
// `GeometryContext` whose `parent` is the active level
|
|
981
|
-
// builders that gate on the current floor — e.g. elevator service
|
|
982
|
-
// range — keep working). Pure registry-driven dispatch: no kind
|
|
983
|
-
// name appears in this file.
|
|
984
|
-
const activeBuildingId = activeLevelNode
|
|
985
|
-
? resolveBuildingForLevel(levelId as AnyNodeId, nodes)
|
|
986
|
-
: null
|
|
1078
|
+
// `GeometryContext` whose `parent` is the active level.
|
|
987
1079
|
if (activeLevelNode && activeBuildingId) {
|
|
988
1080
|
const buildingScopedKinds = kindsWithFloorplanScope('building')
|
|
989
1081
|
const buildingScopedKindSet = new Set(buildingScopedKinds)
|
|
@@ -1000,6 +1092,45 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1000
1092
|
}
|
|
1001
1093
|
}
|
|
1002
1094
|
|
|
1095
|
+
// Site-scoped kinds are semantic children of the Site and therefore
|
|
1096
|
+
// outside both the active-level DFS and the building sibling scan.
|
|
1097
|
+
// Their builders stay site-local; wrap their output in the inverse active
|
|
1098
|
+
// building transform so it aligns with this building-local plan.
|
|
1099
|
+
const activeBuildingNode = activeBuildingId ? nodes[activeBuildingId] : undefined
|
|
1100
|
+
const activeSiteNode =
|
|
1101
|
+
activeBuildingNode?.type === 'building' && activeBuildingNode.parentId
|
|
1102
|
+
? nodes[activeBuildingNode.parentId as AnyNodeId]
|
|
1103
|
+
: undefined
|
|
1104
|
+
if (activeSiteNode?.type === 'site' && activeBuildingNode?.type === 'building') {
|
|
1105
|
+
const siteScopedNodes = collectDirectFloorplanScopeNodes(nodes, activeSiteNode, 'site')
|
|
1106
|
+
const siteProjection = siteToFloorplanTransform(
|
|
1107
|
+
activeBuildingLiveTransform?.position ?? activeBuildingNode.position,
|
|
1108
|
+
activeBuildingLiveTransform?.rotation ?? activeBuildingNode.rotation[1],
|
|
1109
|
+
)
|
|
1110
|
+
for (const node of siteScopedNodes) {
|
|
1111
|
+
const children = Array.isArray((node as { children?: AnyNodeId[] }).children)
|
|
1112
|
+
? (node as { children: AnyNodeId[] }).children
|
|
1113
|
+
.map((id) => nodes[id])
|
|
1114
|
+
.filter((child): child is AnyNode => child !== undefined)
|
|
1115
|
+
: []
|
|
1116
|
+
const siblings = siteScopedNodes.filter(
|
|
1117
|
+
(candidate) => candidate.id !== node.id && candidate.type === node.type,
|
|
1118
|
+
)
|
|
1119
|
+
pushEntry(
|
|
1120
|
+
node.id,
|
|
1121
|
+
node,
|
|
1122
|
+
{
|
|
1123
|
+
children,
|
|
1124
|
+
siblings,
|
|
1125
|
+
parent: activeSiteNode,
|
|
1126
|
+
outputTransform: siteProjection,
|
|
1127
|
+
trackAllNodes: true,
|
|
1128
|
+
},
|
|
1129
|
+
{ scopeRank: -1, visibilityRootId: activeSiteNode.id },
|
|
1130
|
+
)
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1003
1134
|
// Stable z-order sort. SVG renders in document order — later siblings
|
|
1004
1135
|
// paint on top of earlier ones — so anything that should sit *under*
|
|
1005
1136
|
// other floor-plan geometry has to come first in the entries array.
|
|
@@ -1007,7 +1138,11 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1007
1138
|
// all belong on top of them. Within a layer bucket we preserve the
|
|
1008
1139
|
// DFS visit order (stable sort) so siblings keep their relative
|
|
1009
1140
|
// priority.
|
|
1010
|
-
out.sort(
|
|
1141
|
+
out.sort(
|
|
1142
|
+
(a, b) =>
|
|
1143
|
+
(a.scopeRank ?? 0) - (b.scopeRank ?? 0) ||
|
|
1144
|
+
floorplanLayerRank(a.node.type) - floorplanLayerRank(b.node.type),
|
|
1145
|
+
)
|
|
1011
1146
|
const entryIds = new Set(out.map((entry) => entry.id))
|
|
1012
1147
|
for (const id of geometryCacheRef.current.keys()) {
|
|
1013
1148
|
if (!entryIds.has(id as AnyNodeId)) geometryCacheRef.current.delete(id)
|
|
@@ -1016,7 +1151,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1016
1151
|
if (!levelNodeIdsByType.has(type)) levelDataCacheRef.current.delete(type)
|
|
1017
1152
|
}
|
|
1018
1153
|
return { entries: out, levelNodeIdsByType }
|
|
1019
|
-
}, [drawingType, installedPlugins, levelId, nodes])
|
|
1154
|
+
}, [activeBuildingId, activeBuildingLiveTransform, drawingType, installedPlugins, levelId, nodes])
|
|
1020
1155
|
|
|
1021
1156
|
// ── Generic 2D affordance dispatch ─────────────────────────────────
|
|
1022
1157
|
//
|
|
@@ -1048,6 +1183,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1048
1183
|
nodes: sceneNodes,
|
|
1049
1184
|
initialPlanPoint,
|
|
1050
1185
|
gridSnapStep: useEditor.getState().gridSnapStep,
|
|
1186
|
+
sceneApi: createSceneApi(useScene),
|
|
1051
1187
|
})
|
|
1052
1188
|
if (!(session.commit && session.canCommit())) return
|
|
1053
1189
|
session.commit()
|
|
@@ -1085,12 +1221,14 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1085
1221
|
event.stopPropagation()
|
|
1086
1222
|
suppressBoxSelectForPointer(event)
|
|
1087
1223
|
|
|
1224
|
+
beginPerfAction(floorplanAffordancePerfAction(node, affordance), `${node.type}:${node.id}`)
|
|
1088
1225
|
const session = handler.start({
|
|
1089
1226
|
node,
|
|
1090
1227
|
payload,
|
|
1091
1228
|
nodes: sceneNodes,
|
|
1092
1229
|
initialPlanPoint,
|
|
1093
1230
|
gridSnapStep: useEditor.getState().gridSnapStep,
|
|
1231
|
+
sceneApi: createSceneApi(useScene),
|
|
1094
1232
|
})
|
|
1095
1233
|
|
|
1096
1234
|
const snapshots: NodeSnapshot[] = []
|
|
@@ -1246,6 +1384,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1246
1384
|
drag.historyPaused = false
|
|
1247
1385
|
}
|
|
1248
1386
|
drag.session.commit()
|
|
1387
|
+
commitPerfAction()
|
|
1249
1388
|
sfxEmitter.emit('sfx:structure-build')
|
|
1250
1389
|
clearSurfacePlanSnapFeedback()
|
|
1251
1390
|
endReshapeScope(drag)
|
|
@@ -1287,6 +1426,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1287
1426
|
drag.historyPaused = false
|
|
1288
1427
|
}
|
|
1289
1428
|
useScene.getState().updateNodes(finalUpdates)
|
|
1429
|
+
commitPerfAction()
|
|
1290
1430
|
sfxEmitter.emit('sfx:structure-build')
|
|
1291
1431
|
} else {
|
|
1292
1432
|
// Either no net change or canCommit() rejected — revert and
|
|
@@ -1300,6 +1440,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1300
1440
|
}
|
|
1301
1441
|
const overrides = useLiveNodeOverrides.getState()
|
|
1302
1442
|
for (const id of drag.session.affectedIds) overrides.clear(id)
|
|
1443
|
+
cancelPerfAction()
|
|
1303
1444
|
}
|
|
1304
1445
|
|
|
1305
1446
|
clearSurfacePlanSnapFeedback()
|
|
@@ -1417,6 +1558,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1417
1558
|
onHandleDoubleClick={commitAffordanceAction}
|
|
1418
1559
|
onHandlePointerDown={startAffordanceDrag}
|
|
1419
1560
|
palette={palette}
|
|
1561
|
+
unitFocus={unitFocus}
|
|
1420
1562
|
pass="base"
|
|
1421
1563
|
sceneRotationDeg={sceneRotationDeg}
|
|
1422
1564
|
setMovingNode={setMovingNode}
|
|
@@ -1425,7 +1567,9 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1425
1567
|
unit={unit}
|
|
1426
1568
|
metricNotation={metricNotation}
|
|
1427
1569
|
wallDimensionReference={effectiveWallDimensionReference}
|
|
1428
|
-
visibilityRootId={
|
|
1570
|
+
visibilityRootId={
|
|
1571
|
+
entry.visibilityRootId ?? (entry.ctxOverrides ? undefined : (levelId as AnyNodeId))
|
|
1572
|
+
}
|
|
1429
1573
|
ctxOverrides={entry.ctxOverrides}
|
|
1430
1574
|
/>
|
|
1431
1575
|
))}
|
|
@@ -1463,6 +1607,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1463
1607
|
onHandleDoubleClick={commitAffordanceAction}
|
|
1464
1608
|
onHandlePointerDown={startAffordanceDrag}
|
|
1465
1609
|
palette={palette}
|
|
1610
|
+
unitFocus={unitFocus}
|
|
1466
1611
|
pass="overlay"
|
|
1467
1612
|
sceneRotationDeg={sceneRotationDeg}
|
|
1468
1613
|
setMovingNode={setMovingNode}
|
|
@@ -1471,7 +1616,9 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1471
1616
|
unit={unit}
|
|
1472
1617
|
metricNotation={metricNotation}
|
|
1473
1618
|
wallDimensionReference={effectiveWallDimensionReference}
|
|
1474
|
-
visibilityRootId={
|
|
1619
|
+
visibilityRootId={
|
|
1620
|
+
entry.visibilityRootId ?? (entry.ctxOverrides ? undefined : (levelId as AnyNodeId))
|
|
1621
|
+
}
|
|
1475
1622
|
ctxOverrides={entry.ctxOverrides}
|
|
1476
1623
|
/>
|
|
1477
1624
|
))}
|
|
@@ -1792,6 +1939,7 @@ type FloorplanRegistryEntryProps = {
|
|
|
1792
1939
|
rotationPivot?: FloorplanPoint,
|
|
1793
1940
|
) => void
|
|
1794
1941
|
palette: FloorplanPalette | undefined
|
|
1942
|
+
unitFocus: FloorplanUnitFocus | undefined
|
|
1795
1943
|
pass: FloorplanRenderPass
|
|
1796
1944
|
sceneRotationDeg: number
|
|
1797
1945
|
setMovingNode: ReturnType<typeof useEditor.getState>['setMovingNode']
|
|
@@ -1827,6 +1975,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
1827
1975
|
onHandleDoubleClick,
|
|
1828
1976
|
onHandlePointerDown,
|
|
1829
1977
|
palette,
|
|
1978
|
+
unitFocus,
|
|
1830
1979
|
pass,
|
|
1831
1980
|
sceneRotationDeg,
|
|
1832
1981
|
setMovingNode,
|
|
@@ -1851,7 +2000,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
1851
2000
|
const hovered = useViewer((state) => state.hoveredId === selectionProxyId)
|
|
1852
2001
|
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
1853
2002
|
const referencedAnnotationRole = useViewer((state) =>
|
|
1854
|
-
floorplanEntryReferencedAnnotationRole(node, new Set(state.selection.selectedIds)),
|
|
2003
|
+
floorplanEntryReferencedAnnotationRole(node, nodes, new Set(state.selection.selectedIds)),
|
|
1855
2004
|
)
|
|
1856
2005
|
const activeRotateNodeId = useDirectManipulationFeedback((state) =>
|
|
1857
2006
|
state.activeRotateNodeId === nodeId ? nodeId : null,
|
|
@@ -1943,12 +2092,12 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
1943
2092
|
// the body-drag gesture — the whole selection slides, not one member.
|
|
1944
2093
|
if (onGroupMovePointerDown(nodeId, event)) return
|
|
1945
2094
|
sfxEmitter.emit('sfx:item-pick')
|
|
1946
|
-
|
|
2095
|
+
createEditorApi().engageMove(currentNode)
|
|
1947
2096
|
// Claim 2D ownership of this move at the source. `setMovingNode`
|
|
1948
2097
|
// resets the origin to null, so this must follow it.
|
|
1949
2098
|
setMovingNodeOrigin('2d')
|
|
1950
2099
|
},
|
|
1951
|
-
[nodeId, onGroupMovePointerDown,
|
|
2100
|
+
[nodeId, onGroupMovePointerDown, setMovingNodeOrigin],
|
|
1952
2101
|
)
|
|
1953
2102
|
|
|
1954
2103
|
const cacheEntry = buildFloorplanEntryGeometry({
|
|
@@ -1968,6 +2117,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
1968
2117
|
nodeId,
|
|
1969
2118
|
nodes,
|
|
1970
2119
|
palette,
|
|
2120
|
+
unitFocus,
|
|
1971
2121
|
selected,
|
|
1972
2122
|
siblingEpoch,
|
|
1973
2123
|
unit,
|
|
@@ -2037,6 +2187,7 @@ type BuildFloorplanEntryGeometryArgs = {
|
|
|
2037
2187
|
nodeId: AnyNodeId
|
|
2038
2188
|
nodes: Record<string, AnyNode>
|
|
2039
2189
|
palette: FloorplanPalette | undefined
|
|
2190
|
+
unitFocus?: FloorplanUnitFocus
|
|
2040
2191
|
selected: boolean
|
|
2041
2192
|
siblingEpoch: number
|
|
2042
2193
|
unit: 'metric' | 'imperial'
|
|
@@ -2051,7 +2202,7 @@ export function collectFloorplanDependencyNodes(
|
|
|
2051
2202
|
nodes: Record<string, AnyNode>,
|
|
2052
2203
|
liveOverrides?: Map<string, LiveNodeOverrides>,
|
|
2053
2204
|
): AnyNode[] {
|
|
2054
|
-
return (def.floorplanDependencies?.(node) ?? []).flatMap((id) => {
|
|
2205
|
+
return (def.floorplanDependencies?.(node, nodes) ?? []).flatMap((id) => {
|
|
2055
2206
|
const dependency = nodes[id]
|
|
2056
2207
|
if (!dependency) return []
|
|
2057
2208
|
const dependencyOverride = liveOverrides?.get(dependency.id)
|
|
@@ -2068,17 +2219,18 @@ export function collectFloorplanDependencyNodes(
|
|
|
2068
2219
|
|
|
2069
2220
|
function floorplanEntryReferencedAnnotationRole(
|
|
2070
2221
|
node: AnyNode,
|
|
2222
|
+
nodes: Record<string, AnyNode>,
|
|
2071
2223
|
selectedIds: ReadonlySet<string>,
|
|
2072
2224
|
): FloorplanAnnotationRole | undefined {
|
|
2073
2225
|
if (selectedIds.size === 0) return undefined
|
|
2074
2226
|
const definition = nodeRegistry.get(node.type)
|
|
2075
2227
|
const role = getFloorplanNodeExtension(definition)?.referencedSelectionAnnotationRole
|
|
2076
2228
|
if (!role) return undefined
|
|
2077
|
-
const dependencyIds = definition?.floorplanDependencies?.(node) ?? []
|
|
2229
|
+
const dependencyIds = definition?.floorplanDependencies?.(node, nodes) ?? []
|
|
2078
2230
|
return dependencyIds.some((id) => selectedIds.has(id)) ? role : undefined
|
|
2079
2231
|
}
|
|
2080
2232
|
|
|
2081
|
-
function buildFloorplanEntryGeometry({
|
|
2233
|
+
export function buildFloorplanEntryGeometry({
|
|
2082
2234
|
automaticDimensions,
|
|
2083
2235
|
ctxOverrides,
|
|
2084
2236
|
geometryCache,
|
|
@@ -2095,6 +2247,7 @@ function buildFloorplanEntryGeometry({
|
|
|
2095
2247
|
nodeId,
|
|
2096
2248
|
nodes,
|
|
2097
2249
|
palette,
|
|
2250
|
+
unitFocus,
|
|
2098
2251
|
selected,
|
|
2099
2252
|
siblingEpoch,
|
|
2100
2253
|
unit,
|
|
@@ -2123,6 +2276,7 @@ function buildFloorplanEntryGeometry({
|
|
|
2123
2276
|
const deps: NodeDeps = {
|
|
2124
2277
|
automaticDimensions,
|
|
2125
2278
|
node,
|
|
2279
|
+
ctxOverrides,
|
|
2126
2280
|
live,
|
|
2127
2281
|
unit,
|
|
2128
2282
|
metricNotation,
|
|
@@ -2133,10 +2287,11 @@ function buildFloorplanEntryGeometry({
|
|
|
2133
2287
|
moving,
|
|
2134
2288
|
liveOverride,
|
|
2135
2289
|
palette,
|
|
2290
|
+
unitFocus,
|
|
2136
2291
|
siblingEpoch: dependsOnSiblingInputs ? siblingEpoch : 0,
|
|
2137
2292
|
// Sibling-dependent kinds (wall miters, opening cuts) read other nodes'
|
|
2138
2293
|
// committed state via `ctx`, so committed sibling edits still invalidate.
|
|
2139
|
-
committedNodes: dependsOnSiblingInputs ? nodes : null,
|
|
2294
|
+
committedNodes: dependsOnSiblingInputs || ctxOverrides?.trackAllNodes ? nodes : null,
|
|
2140
2295
|
dependencyNodes,
|
|
2141
2296
|
interactiveElevators,
|
|
2142
2297
|
}
|
|
@@ -2161,7 +2316,12 @@ function buildFloorplanEntryGeometry({
|
|
|
2161
2316
|
: r,
|
|
2162
2317
|
} as AnyNode
|
|
2163
2318
|
}
|
|
2164
|
-
if (
|
|
2319
|
+
if (
|
|
2320
|
+
(def.capabilities?.floorPlaced ||
|
|
2321
|
+
def.floorplanScope === 'building' ||
|
|
2322
|
+
def.floorplanScope === 'site') &&
|
|
2323
|
+
hasPosition
|
|
2324
|
+
) {
|
|
2165
2325
|
return applyPositionLiveTransform(sourceNode, live)
|
|
2166
2326
|
}
|
|
2167
2327
|
if (sourceNode.type === 'slab' || sourceNode.type === 'ceiling' || sourceNode.type === 'zone') {
|
|
@@ -2210,6 +2370,8 @@ function buildFloorplanEntryGeometry({
|
|
|
2210
2370
|
highlighted,
|
|
2211
2371
|
hovered,
|
|
2212
2372
|
moving,
|
|
2373
|
+
focusedUnitId: unitFocus?.focusedUnitId,
|
|
2374
|
+
focusedUnitMemberIds: unitFocus?.focusedUnitMemberIds,
|
|
2213
2375
|
palette,
|
|
2214
2376
|
}
|
|
2215
2377
|
const resolveContextNode = <N = AnyNode>(rid: AnyNodeId): N | undefined => {
|
|
@@ -2238,6 +2400,8 @@ function buildFloorplanEntryGeometry({
|
|
|
2238
2400
|
highlighted,
|
|
2239
2401
|
hovered,
|
|
2240
2402
|
moving,
|
|
2403
|
+
focusedUnitId: unitFocus?.focusedUnitId,
|
|
2404
|
+
focusedUnitMemberIds: unitFocus?.focusedUnitMemberIds,
|
|
2241
2405
|
palette,
|
|
2242
2406
|
}
|
|
2243
2407
|
: undefined,
|
|
@@ -2256,10 +2420,18 @@ function buildFloorplanEntryGeometry({
|
|
|
2256
2420
|
const taggedContextualGeometry = withFloorplanGeometryMetadata(contextualGeometry, {
|
|
2257
2421
|
annotationRole: 'contextual-dimension',
|
|
2258
2422
|
})
|
|
2259
|
-
const
|
|
2423
|
+
const unprojectedGeometry =
|
|
2260
2424
|
modelGeometry && taggedContextualGeometry
|
|
2261
2425
|
? { kind: 'group' as const, children: [modelGeometry, taggedContextualGeometry] }
|
|
2262
2426
|
: (modelGeometry ?? taggedContextualGeometry)
|
|
2427
|
+
const geometry =
|
|
2428
|
+
unprojectedGeometry && ctxOverrides?.outputTransform
|
|
2429
|
+
? {
|
|
2430
|
+
kind: 'group' as const,
|
|
2431
|
+
children: [unprojectedGeometry],
|
|
2432
|
+
transform: ctxOverrides.outputTransform,
|
|
2433
|
+
}
|
|
2434
|
+
: unprojectedGeometry
|
|
2263
2435
|
const { base, overlay } = geometry
|
|
2264
2436
|
? splitFloorplanOverlay(geometry)
|
|
2265
2437
|
: { base: null, overlay: null }
|
|
@@ -2822,6 +2994,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2822
2994
|
case 'midpoint-handle': {
|
|
2823
2995
|
if (!palette) return <></>
|
|
2824
2996
|
const handleId = makeHandleId(nodeId, g.payload)
|
|
2997
|
+
const isAction = g.activation === 'action'
|
|
2825
2998
|
const isHovered = hoveredHandleId === handleId
|
|
2826
2999
|
const isActive = activeDragId === handleId
|
|
2827
3000
|
const stroke = palette.endpointHandleStroke
|
|
@@ -2834,7 +3007,12 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2834
3007
|
return (
|
|
2835
3008
|
<g
|
|
2836
3009
|
key={keyHint}
|
|
2837
|
-
onClick={(
|
|
3010
|
+
onClick={(event) => {
|
|
3011
|
+
event.stopPropagation()
|
|
3012
|
+
if (!isAction) return
|
|
3013
|
+
event.preventDefault()
|
|
3014
|
+
onHandleDoubleClick(g.affordance, g.payload, event)
|
|
3015
|
+
}}
|
|
2838
3016
|
onPointerEnter={() => onHandleHoverChange(handleId)}
|
|
2839
3017
|
onPointerLeave={() => onHandleHoverChange(null)}
|
|
2840
3018
|
>
|
|
@@ -2891,9 +3069,17 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2891
3069
|
cx={g.point[0]}
|
|
2892
3070
|
cy={g.point[1]}
|
|
2893
3071
|
fill="transparent"
|
|
2894
|
-
onPointerDown={(
|
|
2895
|
-
|
|
2896
|
-
|
|
3072
|
+
onPointerDown={(event) => {
|
|
3073
|
+
if (isAction) {
|
|
3074
|
+
event.stopPropagation()
|
|
3075
|
+
return
|
|
3076
|
+
}
|
|
3077
|
+
onHandlePointerDown(
|
|
3078
|
+
g.affordance,
|
|
3079
|
+
g.payload,
|
|
3080
|
+
event as ReactPointerEvent<SVGGElement>,
|
|
3081
|
+
)
|
|
3082
|
+
}}
|
|
2897
3083
|
pointerEvents="all"
|
|
2898
3084
|
r={radius + unitsPerPixel * 2}
|
|
2899
3085
|
stroke="transparent"
|
|
@@ -3142,12 +3328,15 @@ export function isFloorplanNodeVisible(node: AnyNode, liveOverride?: LiveNodeOve
|
|
|
3142
3328
|
return (node as { visible?: boolean }).visible !== false
|
|
3143
3329
|
}
|
|
3144
3330
|
|
|
3145
|
-
function isFloorplanHierarchyVisible(
|
|
3331
|
+
export function isFloorplanHierarchyVisible(
|
|
3146
3332
|
node: AnyNode,
|
|
3147
3333
|
nodes: Record<string, AnyNode>,
|
|
3148
3334
|
liveOverrides: Map<string, LiveNodeOverrides>,
|
|
3149
3335
|
rootId: AnyNodeId,
|
|
3150
3336
|
): boolean {
|
|
3337
|
+
const root = nodes[rootId]
|
|
3338
|
+
if (root && !isFloorplanNodeVisible(root, liveOverrides.get(root.id))) return false
|
|
3339
|
+
|
|
3151
3340
|
let current: AnyNode | undefined = node
|
|
3152
3341
|
const seen = new Set<AnyNodeId>()
|
|
3153
3342
|
while (current) {
|
|
@@ -3426,7 +3615,9 @@ function nodeDepsEqual(a: NodeDeps, b: NodeDeps): boolean {
|
|
|
3426
3615
|
'moving',
|
|
3427
3616
|
'liveOverride',
|
|
3428
3617
|
'palette',
|
|
3618
|
+
'unitFocus',
|
|
3429
3619
|
'siblingEpoch',
|
|
3620
|
+
'ctxOverrides',
|
|
3430
3621
|
'committedNodes',
|
|
3431
3622
|
'dependencyNodes',
|
|
3432
3623
|
'interactiveElevators',
|