@pascal-app/editor 1.0.0-beta.4 → 1.0.0
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/alignment-3d-guide-layer.tsx +12 -3
- package/src/components/editor/bake-exporter.tsx +1 -0
- 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 +17 -5
- package/src/components/editor/elevation-3d-guide-layer.tsx +3 -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 +182 -57
- package/src/components/editor/floating-action-menu.tsx +55 -55
- package/src/components/editor/floorplan-background-selection.test.ts +31 -4
- package/src/components/editor/floorplan-background-selection.ts +27 -7
- package/src/components/editor/floorplan-panel.tsx +81 -57
- package/src/components/editor/grid.tsx +12 -7
- package/src/components/editor/group-actions.ts +34 -50
- package/src/components/editor/group-floating-action-menu.tsx +30 -15
- 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/linear-resize-drag.ts +67 -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 +18 -1
- package/src/components/editor/index.tsx +252 -46
- package/src/components/editor/measurement-pill.tsx +12 -12
- package/src/components/editor/node-action-menu.tsx +42 -1
- package/src/components/editor/node-arrow-handles.tsx +47 -58
- package/src/components/editor/opening-guides-3d-layer.tsx +13 -4
- package/src/components/editor/selection-manager.tsx +132 -47
- package/src/components/editor/site-edge-labels.tsx +2 -1
- package/src/components/editor/snapshot-capture-overlay.tsx +309 -41
- 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 -232
- package/src/components/editor/use-floorplan-background-placement.ts +53 -5
- package/src/components/editor/wall-measurement-label.tsx +3 -2
- package/src/components/editor/wall-move-side-handles.tsx +299 -7
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +23 -16
- 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-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 +204 -114
- 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/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/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 +494 -0
- package/src/components/tools/item/placement-strategies.ts +192 -24
- 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 +6 -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 +26 -4
- package/src/components/tools/item/use-placement-coordinator.tsx +447 -180
- package/src/components/tools/registered-tool-install-lifecycle.test.tsx +256 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +10 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +306 -110
- 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 +63 -8
- 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 +45 -8
- package/src/components/tools/site/terrain-brush-cursor.tsx +2 -1
- 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 +527 -4
- package/src/components/tools/wall/wall-drafting.ts +55 -462
- 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 +12 -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/scene-material-list.tsx +6 -5
- package/src/components/ui/controls/segmented-control.tsx +8 -1
- package/src/components/ui/controls/slider-control.tsx +19 -3
- 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 +23 -6
- 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 +263 -0
- package/src/components/ui/panels/multi-selection-panel.tsx +78 -26
- 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/panel-wrapper.tsx +228 -12
- 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 +29 -162
- package/src/components/ui/panels/reference-panel.tsx +31 -1
- 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/items-panel/function-tree-panel.tsx +7 -2
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +390 -36
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +31 -23
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +140 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +109 -0
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +19 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +128 -46
- 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/tree-node.test.ts +8 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +22 -7
- package/src/components/ui/sidebar/tab-bar.tsx +9 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +17 -2
- package/src/components/viewer/floorplan-compass-button.tsx +50 -0
- package/src/components/viewer/floorplan-preview-geometry.test.ts +107 -0
- package/src/components/viewer/floorplan-preview-geometry.ts +281 -0
- package/src/components/viewer/floorplan-preview-navigation.test.ts +43 -0
- package/src/components/viewer/floorplan-preview-navigation.ts +62 -0
- package/src/components/viewer/floorplan-preview.tsx +1069 -0
- package/src/components/viewer/use-viewer-camera-navigation-sync.ts +140 -0
- package/src/components/viewer/viewer-scene-header.tsx +2 -1
- package/src/components/viewer/viewer-stage-modes.test.ts +38 -0
- package/src/components/viewer/viewer-stage-modes.ts +49 -0
- package/src/components/viewer/viewer-stage-switcher.tsx +95 -0
- package/src/components/viewer/viewer-stage.test.tsx +47 -0
- package/src/components/viewer/viewer-stage.tsx +221 -0
- package/src/components/viewer-overlay.tsx +13 -30
- 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 +122 -1
- package/src/hooks/use-auto-save.ts +119 -13
- package/src/hooks/use-ceiling-events.test.ts +122 -0
- 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 +164 -63
- package/src/hooks/use-registered-tool-enabled.ts +20 -0
- package/src/hooks/use-save-shortcut.ts +33 -0
- package/src/index.tsx +109 -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/contextual-help.test.ts +8 -0
- package/src/lib/contextual-help.ts +8 -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/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 +397 -1
- package/src/lib/floorplan/floorplan-export.tsx +111 -16
- 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.test.ts +47 -0
- package/src/lib/floorplan/floorplan-readonly.ts +85 -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/glb-export.test.ts +1169 -21
- package/src/lib/glb-export.ts +629 -101
- package/src/lib/grid-event-presentation.ts +16 -0
- package/src/lib/history.test.ts +523 -77
- package/src/lib/history.ts +133 -14
- package/src/lib/host-tree-children.test.ts +27 -0
- package/src/lib/host-tree-children.ts +79 -0
- package/src/lib/inspector-card-mode.test.ts +92 -0
- package/src/lib/inspector-card-mode.ts +60 -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-print-export.test.ts +484 -0
- package/src/lib/level-print-export.ts +526 -0
- 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/measurement-parser.ts +3 -0
- package/src/lib/measurements.test.ts +4 -0
- package/src/lib/model-export.ts +29 -0
- package/src/lib/paint-preview-owner.ts +71 -0
- package/src/lib/paint-scope.test.ts +58 -2
- package/src/lib/paint-scope.ts +8 -2
- 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 +366 -0
- package/src/lib/portable-export.ts +969 -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 +330 -0
- package/src/lib/print-shell-compiler-manifold-worker.ts +187 -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 +30 -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.ts +22 -8
- package/src/lib/selection-routing.test.ts +125 -6
- package/src/lib/selection-routing.ts +34 -1
- package/src/lib/session-groups.test.ts +88 -0
- package/src/lib/session-groups.ts +201 -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/structured-clone-fallback.test.ts +45 -0
- package/src/lib/structured-clone-fallback.ts +24 -0
- package/src/lib/usdz-export.ts +38 -0
- 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-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-session-groups.ts +103 -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,
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
resolveDirectManipulationNode,
|
|
47
48
|
resolveDirectRotationDragDelta,
|
|
48
49
|
resolveDirectRotationPatch,
|
|
50
|
+
shouldStartDirectMoveDrag,
|
|
49
51
|
snapDirectRotationDelta,
|
|
50
52
|
} from '../../../lib/direct-manipulation'
|
|
51
53
|
import { createEditorApi } from '../../../lib/editor-api'
|
|
@@ -67,6 +69,10 @@ import {
|
|
|
67
69
|
resolveFloorplanAnnotationVisibility,
|
|
68
70
|
resolveFloorplanWallDimensionReference,
|
|
69
71
|
} from '../../../lib/floorplan/floorplan-mode'
|
|
72
|
+
import {
|
|
73
|
+
buildFloorplanContext,
|
|
74
|
+
floorplanLayerRank,
|
|
75
|
+
} from '../../../lib/floorplan/floorplan-readonly'
|
|
70
76
|
import { clientToPlan } from '../../../lib/floorplan/plan-coords'
|
|
71
77
|
import {
|
|
72
78
|
type ActiveInteractionScope,
|
|
@@ -91,6 +97,7 @@ import useInteractionScope, {
|
|
|
91
97
|
useEndpointReshape,
|
|
92
98
|
useMovingNode,
|
|
93
99
|
} from '../../../store/use-interaction-scope'
|
|
100
|
+
import { expandSessionSelectionForNode } from '../../../store/use-session-groups'
|
|
94
101
|
import { startGroupPickUp } from '../../editor/group-actions'
|
|
95
102
|
import { classifyParticipant } from '../../editor/group-transform-shared'
|
|
96
103
|
import { suppressBoxSelectForPointer } from '../../tools/select/box-select-state'
|
|
@@ -241,6 +248,7 @@ export function cancelFloorplanAffordanceDrag(
|
|
|
241
248
|
for (const id of drag.session.affectedIds) effects.clearPreview(id)
|
|
242
249
|
effects.endReshapeScope(drag)
|
|
243
250
|
effects.clearDragFeedback?.()
|
|
251
|
+
cancelPerfAction()
|
|
244
252
|
return true
|
|
245
253
|
}
|
|
246
254
|
|
|
@@ -305,6 +313,14 @@ export function floorplanAffordanceReshapeScope(
|
|
|
305
313
|
return null
|
|
306
314
|
}
|
|
307
315
|
|
|
316
|
+
function floorplanAffordancePerfAction(node: AnyNode, affordance: string): string {
|
|
317
|
+
if (affordance.includes('endpoint')) return `drag:${node.type}-endpoint`
|
|
318
|
+
if (affordance.includes('resize')) return 'drag:resize'
|
|
319
|
+
if (affordance.includes('rotate')) return 'drag:rotate'
|
|
320
|
+
if (affordance.includes('move')) return 'drag:move'
|
|
321
|
+
return 'drag:reshape'
|
|
322
|
+
}
|
|
323
|
+
|
|
308
324
|
/**
|
|
309
325
|
* Transient live-rotation readout state. Rebuilt each pointer-move while a
|
|
310
326
|
* rotate-arrow is dragged and cleared on release. World-plan coords.
|
|
@@ -323,6 +339,8 @@ type FloorplanEntryDescriptor = {
|
|
|
323
339
|
node: AnyNode
|
|
324
340
|
dependsOnSiblingInputs: boolean
|
|
325
341
|
ctxOverrides?: FloorplanContextOverrides
|
|
342
|
+
scopeRank?: number
|
|
343
|
+
visibilityRootId?: AnyNodeId
|
|
326
344
|
}
|
|
327
345
|
|
|
328
346
|
type NodeDeps = {
|
|
@@ -342,6 +360,7 @@ type NodeDeps = {
|
|
|
342
360
|
committedNodes: Record<string, AnyNode> | null
|
|
343
361
|
dependencyNodes: AnyNode[]
|
|
344
362
|
interactiveElevators: unknown
|
|
363
|
+
ctxOverrides: FloorplanContextOverrides | undefined
|
|
345
364
|
}
|
|
346
365
|
|
|
347
366
|
type CacheEntry = {
|
|
@@ -362,6 +381,40 @@ type FloorplanContextOverrides = {
|
|
|
362
381
|
children: AnyNode[]
|
|
363
382
|
siblings: AnyNode[]
|
|
364
383
|
parent: AnyNode | null
|
|
384
|
+
outputTransform?: { translate?: FloorplanPoint; rotate?: number }
|
|
385
|
+
trackAllNodes?: boolean
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function collectDirectFloorplanScopeNodes(
|
|
389
|
+
nodes: Record<string, AnyNode>,
|
|
390
|
+
parent: AnyNode,
|
|
391
|
+
scope: FloorplanScope,
|
|
392
|
+
): AnyNode[] {
|
|
393
|
+
const scopedKinds = new Set(kindsWithFloorplanScope(scope))
|
|
394
|
+
const declaredChildren = new Set(
|
|
395
|
+
Array.isArray((parent as { children?: AnyNodeId[] }).children)
|
|
396
|
+
? (parent as { children: AnyNodeId[] }).children
|
|
397
|
+
: [],
|
|
398
|
+
)
|
|
399
|
+
return Object.values(nodes).filter(
|
|
400
|
+
(node) =>
|
|
401
|
+
scopedKinds.has(node.type) && (node.parentId === parent.id || declaredChildren.has(node.id)),
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function siteToFloorplanTransform(
|
|
406
|
+
buildingPosition: readonly [number, number, number],
|
|
407
|
+
buildingRotationY: number,
|
|
408
|
+
): { translate: FloorplanPoint; rotate: number } {
|
|
409
|
+
const cos = Math.cos(buildingRotationY)
|
|
410
|
+
const sin = Math.sin(buildingRotationY)
|
|
411
|
+
return {
|
|
412
|
+
translate: [
|
|
413
|
+
-buildingPosition[0] * cos + buildingPosition[2] * sin,
|
|
414
|
+
-buildingPosition[0] * sin - buildingPosition[2] * cos,
|
|
415
|
+
],
|
|
416
|
+
rotate: buildingRotationY,
|
|
417
|
+
}
|
|
365
418
|
}
|
|
366
419
|
|
|
367
420
|
type FloorplanLevelDataHook = (args: {
|
|
@@ -472,6 +525,13 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
472
525
|
|
|
473
526
|
const levelId = selectedLevelId ?? ambientLevelId
|
|
474
527
|
const isAmbient = !selectedLevelId && !!ambientLevelId
|
|
528
|
+
const activeBuildingId = useMemo(
|
|
529
|
+
() => (levelId ? resolveBuildingForLevel(levelId as AnyNodeId, nodes) : null),
|
|
530
|
+
[levelId, nodes],
|
|
531
|
+
)
|
|
532
|
+
const activeBuildingLiveTransform = useLiveTransforms((state) =>
|
|
533
|
+
activeBuildingId ? state.transforms.get(activeBuildingId) : undefined,
|
|
534
|
+
)
|
|
475
535
|
const renderCtx = useFloorplanStaticRender()
|
|
476
536
|
const sceneRotationDeg = renderCtx?.getSceneRotationDeg() ?? 0
|
|
477
537
|
const setMovingNode = useEditor((s) => s.setMovingNode)
|
|
@@ -589,13 +649,19 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
589
649
|
}, [bumpAffectedSiblingEpochs])
|
|
590
650
|
|
|
591
651
|
const applyEntrySelection = useCallback(
|
|
592
|
-
(id: AnyNodeId, shouldToggle: boolean) => {
|
|
652
|
+
(id: AnyNodeId, options: { shouldToggle: boolean; isolateMember: boolean }) => {
|
|
593
653
|
const currentSelectedIds = useViewer.getState().selection.selectedIds
|
|
594
|
-
|
|
595
|
-
|
|
654
|
+
let nextSelectedIds: string[]
|
|
655
|
+
if (options.shouldToggle) {
|
|
656
|
+
nextSelectedIds = currentSelectedIds.includes(id)
|
|
596
657
|
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
597
658
|
: [...currentSelectedIds, id]
|
|
598
|
-
|
|
659
|
+
} else if (options.isolateMember) {
|
|
660
|
+
nextSelectedIds = [id]
|
|
661
|
+
} else {
|
|
662
|
+
const expanded = expandSessionSelectionForNode(id)
|
|
663
|
+
nextSelectedIds = expanded && expanded.length > 1 ? expanded : [id]
|
|
664
|
+
}
|
|
599
665
|
setSelection({ selectedIds: nextSelectedIds })
|
|
600
666
|
if (nextSelectedIds.length === 1 && nextSelectedIds[0] === id) {
|
|
601
667
|
const node = useScene.getState().nodes[id]
|
|
@@ -618,7 +684,10 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
618
684
|
(id: AnyNodeId, event: React.PointerEvent<SVGGElement>) => {
|
|
619
685
|
if (event.button !== 0) return
|
|
620
686
|
event.stopPropagation()
|
|
621
|
-
applyEntrySelection(id,
|
|
687
|
+
applyEntrySelection(id, {
|
|
688
|
+
shouldToggle: event.metaKey || event.ctrlKey || event.shiftKey,
|
|
689
|
+
isolateMember: event.altKey && !(event.metaKey || event.ctrlKey || event.shiftKey),
|
|
690
|
+
})
|
|
622
691
|
},
|
|
623
692
|
[applyEntrySelection],
|
|
624
693
|
)
|
|
@@ -630,16 +699,24 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
630
699
|
|
|
631
700
|
const startDirectMoveDrag = useCallback(
|
|
632
701
|
(id: AnyNodeId, event: ReactPointerEvent<SVGGElement>): boolean => {
|
|
633
|
-
if (event.button !== 0
|
|
702
|
+
if (event.button !== 0) return false
|
|
634
703
|
|
|
635
704
|
const node = useScene.getState().nodes[id]
|
|
636
705
|
if (!node || !isRegistryMovable(node.type)) return false
|
|
637
|
-
// Sole selection only: per-node direct manipulation stands down for a
|
|
638
|
-
// multi-selection (the group session owns plain drags there, and Cmd is
|
|
639
|
-
// the selection-toggle key — a wobbly Cmd+click must not yank one
|
|
640
|
-
// member out of the group).
|
|
641
706
|
const currentSelectedIds = useViewer.getState().selection.selectedIds
|
|
642
|
-
|
|
707
|
+
const allowPlainDrag = nodeRegistry.get(node.type)?.capabilities?.movable?.directDrag === true
|
|
708
|
+
const commandModifier = event.metaKey || event.ctrlKey
|
|
709
|
+
if (
|
|
710
|
+
!shouldStartDirectMoveDrag({
|
|
711
|
+
allowPlainDrag,
|
|
712
|
+
commandModifier,
|
|
713
|
+
handleOwnsPointer: false,
|
|
714
|
+
nodeId: id,
|
|
715
|
+
selectedIds: currentSelectedIds,
|
|
716
|
+
})
|
|
717
|
+
) {
|
|
718
|
+
return false
|
|
719
|
+
}
|
|
643
720
|
|
|
644
721
|
event.preventDefault()
|
|
645
722
|
event.stopPropagation()
|
|
@@ -691,7 +768,10 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
691
768
|
if (endEvent.pointerId !== pointerId) return
|
|
692
769
|
cleanup()
|
|
693
770
|
if (!engaged) {
|
|
694
|
-
applyEntrySelection(id,
|
|
771
|
+
applyEntrySelection(id, {
|
|
772
|
+
shouldToggle: commandModifier,
|
|
773
|
+
isolateMember: false,
|
|
774
|
+
})
|
|
695
775
|
}
|
|
696
776
|
}
|
|
697
777
|
|
|
@@ -911,7 +991,12 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
911
991
|
|
|
912
992
|
collectLevelDataKind(levelId as AnyNodeId)
|
|
913
993
|
|
|
914
|
-
const pushEntry = (
|
|
994
|
+
const pushEntry = (
|
|
995
|
+
id: AnyNodeId,
|
|
996
|
+
node: AnyNode,
|
|
997
|
+
ctxOverrides?: FloorplanContextOverrides,
|
|
998
|
+
options?: { scopeRank?: number; visibilityRootId?: AnyNodeId },
|
|
999
|
+
) => {
|
|
915
1000
|
if (!isNodeKindEnabled(node.type, installedPlugins)) return
|
|
916
1001
|
const drawingNode = resolveNodeForDrawingType(node, nodes, drawingType)
|
|
917
1002
|
if (!drawingNode) return
|
|
@@ -924,6 +1009,8 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
924
1009
|
)
|
|
925
1010
|
const descriptor: FloorplanEntryDescriptor = { id, node: drawingNode, dependsOnSiblingInputs }
|
|
926
1011
|
if (ctxOverrides) descriptor.ctxOverrides = ctxOverrides
|
|
1012
|
+
if (options?.scopeRank !== undefined) descriptor.scopeRank = options.scopeRank
|
|
1013
|
+
if (options?.visibilityRootId) descriptor.visibilityRootId = options.visibilityRootId
|
|
927
1014
|
out.push(descriptor)
|
|
928
1015
|
}
|
|
929
1016
|
|
|
@@ -959,13 +1046,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
959
1046
|
// as siblings of the level, not under it — the `visit(levelId)` DFS
|
|
960
1047
|
// above doesn't reach them. Walk every node of those kinds whose
|
|
961
1048
|
// parent matches the active level's building, and synthesise a
|
|
962
|
-
// `GeometryContext` whose `parent` is the active level
|
|
963
|
-
// builders that gate on the current floor — e.g. elevator service
|
|
964
|
-
// range — keep working). Pure registry-driven dispatch: no kind
|
|
965
|
-
// name appears in this file.
|
|
966
|
-
const activeBuildingId = activeLevelNode
|
|
967
|
-
? resolveBuildingForLevel(levelId as AnyNodeId, nodes)
|
|
968
|
-
: null
|
|
1049
|
+
// `GeometryContext` whose `parent` is the active level.
|
|
969
1050
|
if (activeLevelNode && activeBuildingId) {
|
|
970
1051
|
const buildingScopedKinds = kindsWithFloorplanScope('building')
|
|
971
1052
|
const buildingScopedKindSet = new Set(buildingScopedKinds)
|
|
@@ -982,6 +1063,45 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
982
1063
|
}
|
|
983
1064
|
}
|
|
984
1065
|
|
|
1066
|
+
// Site-scoped kinds are semantic children of the Site and therefore
|
|
1067
|
+
// outside both the active-level DFS and the building sibling scan.
|
|
1068
|
+
// Their builders stay site-local; wrap their output in the inverse active
|
|
1069
|
+
// building transform so it aligns with this building-local plan.
|
|
1070
|
+
const activeBuildingNode = activeBuildingId ? nodes[activeBuildingId] : undefined
|
|
1071
|
+
const activeSiteNode =
|
|
1072
|
+
activeBuildingNode?.type === 'building' && activeBuildingNode.parentId
|
|
1073
|
+
? nodes[activeBuildingNode.parentId as AnyNodeId]
|
|
1074
|
+
: undefined
|
|
1075
|
+
if (activeSiteNode?.type === 'site' && activeBuildingNode?.type === 'building') {
|
|
1076
|
+
const siteScopedNodes = collectDirectFloorplanScopeNodes(nodes, activeSiteNode, 'site')
|
|
1077
|
+
const siteProjection = siteToFloorplanTransform(
|
|
1078
|
+
activeBuildingLiveTransform?.position ?? activeBuildingNode.position,
|
|
1079
|
+
activeBuildingLiveTransform?.rotation ?? activeBuildingNode.rotation[1],
|
|
1080
|
+
)
|
|
1081
|
+
for (const node of siteScopedNodes) {
|
|
1082
|
+
const children = Array.isArray((node as { children?: AnyNodeId[] }).children)
|
|
1083
|
+
? (node as { children: AnyNodeId[] }).children
|
|
1084
|
+
.map((id) => nodes[id])
|
|
1085
|
+
.filter((child): child is AnyNode => child !== undefined)
|
|
1086
|
+
: []
|
|
1087
|
+
const siblings = siteScopedNodes.filter(
|
|
1088
|
+
(candidate) => candidate.id !== node.id && candidate.type === node.type,
|
|
1089
|
+
)
|
|
1090
|
+
pushEntry(
|
|
1091
|
+
node.id,
|
|
1092
|
+
node,
|
|
1093
|
+
{
|
|
1094
|
+
children,
|
|
1095
|
+
siblings,
|
|
1096
|
+
parent: activeSiteNode,
|
|
1097
|
+
outputTransform: siteProjection,
|
|
1098
|
+
trackAllNodes: true,
|
|
1099
|
+
},
|
|
1100
|
+
{ scopeRank: -1, visibilityRootId: activeSiteNode.id },
|
|
1101
|
+
)
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
985
1105
|
// Stable z-order sort. SVG renders in document order — later siblings
|
|
986
1106
|
// paint on top of earlier ones — so anything that should sit *under*
|
|
987
1107
|
// other floor-plan geometry has to come first in the entries array.
|
|
@@ -989,7 +1109,11 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
989
1109
|
// all belong on top of them. Within a layer bucket we preserve the
|
|
990
1110
|
// DFS visit order (stable sort) so siblings keep their relative
|
|
991
1111
|
// priority.
|
|
992
|
-
out.sort(
|
|
1112
|
+
out.sort(
|
|
1113
|
+
(a, b) =>
|
|
1114
|
+
(a.scopeRank ?? 0) - (b.scopeRank ?? 0) ||
|
|
1115
|
+
floorplanLayerRank(a.node.type) - floorplanLayerRank(b.node.type),
|
|
1116
|
+
)
|
|
993
1117
|
const entryIds = new Set(out.map((entry) => entry.id))
|
|
994
1118
|
for (const id of geometryCacheRef.current.keys()) {
|
|
995
1119
|
if (!entryIds.has(id as AnyNodeId)) geometryCacheRef.current.delete(id)
|
|
@@ -998,7 +1122,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
998
1122
|
if (!levelNodeIdsByType.has(type)) levelDataCacheRef.current.delete(type)
|
|
999
1123
|
}
|
|
1000
1124
|
return { entries: out, levelNodeIdsByType }
|
|
1001
|
-
}, [drawingType, installedPlugins, levelId, nodes])
|
|
1125
|
+
}, [activeBuildingId, activeBuildingLiveTransform, drawingType, installedPlugins, levelId, nodes])
|
|
1002
1126
|
|
|
1003
1127
|
// ── Generic 2D affordance dispatch ─────────────────────────────────
|
|
1004
1128
|
//
|
|
@@ -1030,6 +1154,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1030
1154
|
nodes: sceneNodes,
|
|
1031
1155
|
initialPlanPoint,
|
|
1032
1156
|
gridSnapStep: useEditor.getState().gridSnapStep,
|
|
1157
|
+
sceneApi: createSceneApi(useScene),
|
|
1033
1158
|
})
|
|
1034
1159
|
if (!(session.commit && session.canCommit())) return
|
|
1035
1160
|
session.commit()
|
|
@@ -1067,12 +1192,14 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1067
1192
|
event.stopPropagation()
|
|
1068
1193
|
suppressBoxSelectForPointer(event)
|
|
1069
1194
|
|
|
1195
|
+
beginPerfAction(floorplanAffordancePerfAction(node, affordance), `${node.type}:${node.id}`)
|
|
1070
1196
|
const session = handler.start({
|
|
1071
1197
|
node,
|
|
1072
1198
|
payload,
|
|
1073
1199
|
nodes: sceneNodes,
|
|
1074
1200
|
initialPlanPoint,
|
|
1075
1201
|
gridSnapStep: useEditor.getState().gridSnapStep,
|
|
1202
|
+
sceneApi: createSceneApi(useScene),
|
|
1076
1203
|
})
|
|
1077
1204
|
|
|
1078
1205
|
const snapshots: NodeSnapshot[] = []
|
|
@@ -1228,6 +1355,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1228
1355
|
drag.historyPaused = false
|
|
1229
1356
|
}
|
|
1230
1357
|
drag.session.commit()
|
|
1358
|
+
commitPerfAction()
|
|
1231
1359
|
sfxEmitter.emit('sfx:structure-build')
|
|
1232
1360
|
clearSurfacePlanSnapFeedback()
|
|
1233
1361
|
endReshapeScope(drag)
|
|
@@ -1269,6 +1397,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1269
1397
|
drag.historyPaused = false
|
|
1270
1398
|
}
|
|
1271
1399
|
useScene.getState().updateNodes(finalUpdates)
|
|
1400
|
+
commitPerfAction()
|
|
1272
1401
|
sfxEmitter.emit('sfx:structure-build')
|
|
1273
1402
|
} else {
|
|
1274
1403
|
// Either no net change or canCommit() rejected — revert and
|
|
@@ -1282,6 +1411,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1282
1411
|
}
|
|
1283
1412
|
const overrides = useLiveNodeOverrides.getState()
|
|
1284
1413
|
for (const id of drag.session.affectedIds) overrides.clear(id)
|
|
1414
|
+
cancelPerfAction()
|
|
1285
1415
|
}
|
|
1286
1416
|
|
|
1287
1417
|
clearSurfacePlanSnapFeedback()
|
|
@@ -1407,7 +1537,9 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1407
1537
|
unit={unit}
|
|
1408
1538
|
metricNotation={metricNotation}
|
|
1409
1539
|
wallDimensionReference={effectiveWallDimensionReference}
|
|
1410
|
-
visibilityRootId={
|
|
1540
|
+
visibilityRootId={
|
|
1541
|
+
entry.visibilityRootId ?? (entry.ctxOverrides ? undefined : (levelId as AnyNodeId))
|
|
1542
|
+
}
|
|
1411
1543
|
ctxOverrides={entry.ctxOverrides}
|
|
1412
1544
|
/>
|
|
1413
1545
|
))}
|
|
@@ -1453,7 +1585,9 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1453
1585
|
unit={unit}
|
|
1454
1586
|
metricNotation={metricNotation}
|
|
1455
1587
|
wallDimensionReference={effectiveWallDimensionReference}
|
|
1456
|
-
visibilityRootId={
|
|
1588
|
+
visibilityRootId={
|
|
1589
|
+
entry.visibilityRootId ?? (entry.ctxOverrides ? undefined : (levelId as AnyNodeId))
|
|
1590
|
+
}
|
|
1457
1591
|
ctxOverrides={entry.ctxOverrides}
|
|
1458
1592
|
/>
|
|
1459
1593
|
))}
|
|
@@ -1925,12 +2059,12 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
1925
2059
|
// the body-drag gesture — the whole selection slides, not one member.
|
|
1926
2060
|
if (onGroupMovePointerDown(nodeId, event)) return
|
|
1927
2061
|
sfxEmitter.emit('sfx:item-pick')
|
|
1928
|
-
|
|
2062
|
+
createEditorApi().engageMove(currentNode)
|
|
1929
2063
|
// Claim 2D ownership of this move at the source. `setMovingNode`
|
|
1930
2064
|
// resets the origin to null, so this must follow it.
|
|
1931
2065
|
setMovingNodeOrigin('2d')
|
|
1932
2066
|
},
|
|
1933
|
-
[nodeId, onGroupMovePointerDown,
|
|
2067
|
+
[nodeId, onGroupMovePointerDown, setMovingNodeOrigin],
|
|
1934
2068
|
)
|
|
1935
2069
|
|
|
1936
2070
|
const cacheEntry = buildFloorplanEntryGeometry({
|
|
@@ -2060,7 +2194,7 @@ function floorplanEntryReferencedAnnotationRole(
|
|
|
2060
2194
|
return dependencyIds.some((id) => selectedIds.has(id)) ? role : undefined
|
|
2061
2195
|
}
|
|
2062
2196
|
|
|
2063
|
-
function buildFloorplanEntryGeometry({
|
|
2197
|
+
export function buildFloorplanEntryGeometry({
|
|
2064
2198
|
automaticDimensions,
|
|
2065
2199
|
ctxOverrides,
|
|
2066
2200
|
geometryCache,
|
|
@@ -2105,6 +2239,7 @@ function buildFloorplanEntryGeometry({
|
|
|
2105
2239
|
const deps: NodeDeps = {
|
|
2106
2240
|
automaticDimensions,
|
|
2107
2241
|
node,
|
|
2242
|
+
ctxOverrides,
|
|
2108
2243
|
live,
|
|
2109
2244
|
unit,
|
|
2110
2245
|
metricNotation,
|
|
@@ -2118,7 +2253,7 @@ function buildFloorplanEntryGeometry({
|
|
|
2118
2253
|
siblingEpoch: dependsOnSiblingInputs ? siblingEpoch : 0,
|
|
2119
2254
|
// Sibling-dependent kinds (wall miters, opening cuts) read other nodes'
|
|
2120
2255
|
// committed state via `ctx`, so committed sibling edits still invalidate.
|
|
2121
|
-
committedNodes: dependsOnSiblingInputs ? nodes : null,
|
|
2256
|
+
committedNodes: dependsOnSiblingInputs || ctxOverrides?.trackAllNodes ? nodes : null,
|
|
2122
2257
|
dependencyNodes,
|
|
2123
2258
|
interactiveElevators,
|
|
2124
2259
|
}
|
|
@@ -2143,7 +2278,12 @@ function buildFloorplanEntryGeometry({
|
|
|
2143
2278
|
: r,
|
|
2144
2279
|
} as AnyNode
|
|
2145
2280
|
}
|
|
2146
|
-
if (
|
|
2281
|
+
if (
|
|
2282
|
+
(def.capabilities?.floorPlaced ||
|
|
2283
|
+
def.floorplanScope === 'building' ||
|
|
2284
|
+
def.floorplanScope === 'site') &&
|
|
2285
|
+
hasPosition
|
|
2286
|
+
) {
|
|
2147
2287
|
return applyPositionLiveTransform(sourceNode, live)
|
|
2148
2288
|
}
|
|
2149
2289
|
if (sourceNode.type === 'slab' || sourceNode.type === 'ceiling' || sourceNode.type === 'zone') {
|
|
@@ -2238,10 +2378,18 @@ function buildFloorplanEntryGeometry({
|
|
|
2238
2378
|
const taggedContextualGeometry = withFloorplanGeometryMetadata(contextualGeometry, {
|
|
2239
2379
|
annotationRole: 'contextual-dimension',
|
|
2240
2380
|
})
|
|
2241
|
-
const
|
|
2381
|
+
const unprojectedGeometry =
|
|
2242
2382
|
modelGeometry && taggedContextualGeometry
|
|
2243
2383
|
? { kind: 'group' as const, children: [modelGeometry, taggedContextualGeometry] }
|
|
2244
2384
|
: (modelGeometry ?? taggedContextualGeometry)
|
|
2385
|
+
const geometry =
|
|
2386
|
+
unprojectedGeometry && ctxOverrides?.outputTransform
|
|
2387
|
+
? {
|
|
2388
|
+
kind: 'group' as const,
|
|
2389
|
+
children: [unprojectedGeometry],
|
|
2390
|
+
transform: ctxOverrides.outputTransform,
|
|
2391
|
+
}
|
|
2392
|
+
: unprojectedGeometry
|
|
2245
2393
|
const { base, overlay } = geometry
|
|
2246
2394
|
? splitFloorplanOverlay(geometry)
|
|
2247
2395
|
: { base: null, overlay: null }
|
|
@@ -2804,6 +2952,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2804
2952
|
case 'midpoint-handle': {
|
|
2805
2953
|
if (!palette) return <></>
|
|
2806
2954
|
const handleId = makeHandleId(nodeId, g.payload)
|
|
2955
|
+
const isAction = g.activation === 'action'
|
|
2807
2956
|
const isHovered = hoveredHandleId === handleId
|
|
2808
2957
|
const isActive = activeDragId === handleId
|
|
2809
2958
|
const stroke = palette.endpointHandleStroke
|
|
@@ -2816,7 +2965,12 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2816
2965
|
return (
|
|
2817
2966
|
<g
|
|
2818
2967
|
key={keyHint}
|
|
2819
|
-
onClick={(
|
|
2968
|
+
onClick={(event) => {
|
|
2969
|
+
event.stopPropagation()
|
|
2970
|
+
if (!isAction) return
|
|
2971
|
+
event.preventDefault()
|
|
2972
|
+
onHandleDoubleClick(g.affordance, g.payload, event)
|
|
2973
|
+
}}
|
|
2820
2974
|
onPointerEnter={() => onHandleHoverChange(handleId)}
|
|
2821
2975
|
onPointerLeave={() => onHandleHoverChange(null)}
|
|
2822
2976
|
>
|
|
@@ -2873,9 +3027,17 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2873
3027
|
cx={g.point[0]}
|
|
2874
3028
|
cy={g.point[1]}
|
|
2875
3029
|
fill="transparent"
|
|
2876
|
-
onPointerDown={(
|
|
2877
|
-
|
|
2878
|
-
|
|
3030
|
+
onPointerDown={(event) => {
|
|
3031
|
+
if (isAction) {
|
|
3032
|
+
event.stopPropagation()
|
|
3033
|
+
return
|
|
3034
|
+
}
|
|
3035
|
+
onHandlePointerDown(
|
|
3036
|
+
g.affordance,
|
|
3037
|
+
g.payload,
|
|
3038
|
+
event as ReactPointerEvent<SVGGElement>,
|
|
3039
|
+
)
|
|
3040
|
+
}}
|
|
2879
3041
|
pointerEvents="all"
|
|
2880
3042
|
r={radius + unitsPerPixel * 2}
|
|
2881
3043
|
stroke="transparent"
|
|
@@ -3124,12 +3286,15 @@ export function isFloorplanNodeVisible(node: AnyNode, liveOverride?: LiveNodeOve
|
|
|
3124
3286
|
return (node as { visible?: boolean }).visible !== false
|
|
3125
3287
|
}
|
|
3126
3288
|
|
|
3127
|
-
function isFloorplanHierarchyVisible(
|
|
3289
|
+
export function isFloorplanHierarchyVisible(
|
|
3128
3290
|
node: AnyNode,
|
|
3129
3291
|
nodes: Record<string, AnyNode>,
|
|
3130
3292
|
liveOverrides: Map<string, LiveNodeOverrides>,
|
|
3131
3293
|
rootId: AnyNodeId,
|
|
3132
3294
|
): boolean {
|
|
3295
|
+
const root = nodes[rootId]
|
|
3296
|
+
if (root && !isFloorplanNodeVisible(root, liveOverrides.get(root.id))) return false
|
|
3297
|
+
|
|
3133
3298
|
let current: AnyNode | undefined = node
|
|
3134
3299
|
const seen = new Set<AnyNodeId>()
|
|
3135
3300
|
while (current) {
|
|
@@ -3144,73 +3309,7 @@ function isFloorplanHierarchyVisible(
|
|
|
3144
3309
|
return true
|
|
3145
3310
|
}
|
|
3146
3311
|
|
|
3147
|
-
export
|
|
3148
|
-
node: AnyNode,
|
|
3149
|
-
nodes: Record<string, AnyNode>,
|
|
3150
|
-
viewState: {
|
|
3151
|
-
automaticDimensions?: boolean
|
|
3152
|
-
selected: boolean
|
|
3153
|
-
unit: 'metric' | 'imperial'
|
|
3154
|
-
metricNotation?: 'meters' | 'millimeters'
|
|
3155
|
-
purpose?: 'edit' | 'document'
|
|
3156
|
-
wallDimensionReference?: FloorplanWallDimensionReference
|
|
3157
|
-
highlighted: boolean
|
|
3158
|
-
hovered: boolean
|
|
3159
|
-
moving: boolean
|
|
3160
|
-
palette: FloorplanPalette | undefined
|
|
3161
|
-
},
|
|
3162
|
-
levelData?: unknown,
|
|
3163
|
-
): GeometryContext {
|
|
3164
|
-
const resolve = <N = AnyNode>(id: AnyNodeId): N | undefined => nodes[id] as N | undefined
|
|
3165
|
-
|
|
3166
|
-
const childIds = (node as unknown as { children?: AnyNodeId[] }).children
|
|
3167
|
-
const children: AnyNode[] = Array.isArray(childIds)
|
|
3168
|
-
? childIds.map((cid) => nodes[cid]).filter((n): n is AnyNode => n !== undefined)
|
|
3169
|
-
: []
|
|
3170
|
-
|
|
3171
|
-
const parentId = node.parentId as AnyNodeId | null
|
|
3172
|
-
const parent: AnyNode | null = parentId ? (nodes[parentId] ?? null) : null
|
|
3173
|
-
|
|
3174
|
-
let siblings: AnyNode[] = []
|
|
3175
|
-
if (parent) {
|
|
3176
|
-
const parentChildIds = (parent as unknown as { children?: AnyNodeId[] }).children
|
|
3177
|
-
if (Array.isArray(parentChildIds)) {
|
|
3178
|
-
for (const sid of parentChildIds) {
|
|
3179
|
-
if (sid === node.id) continue
|
|
3180
|
-
const s = nodes[sid]
|
|
3181
|
-
if (s && s.type === node.type) siblings.push(s)
|
|
3182
|
-
}
|
|
3183
|
-
} else {
|
|
3184
|
-
siblings = Object.values(nodes).filter(
|
|
3185
|
-
(n) => n !== node && n.type === node.type && n.parentId === parentId,
|
|
3186
|
-
)
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3189
|
-
|
|
3190
|
-
return {
|
|
3191
|
-
resolve,
|
|
3192
|
-
children,
|
|
3193
|
-
siblings,
|
|
3194
|
-
parent,
|
|
3195
|
-
levelData,
|
|
3196
|
-
extensions: createFloorplanContextExtensions({
|
|
3197
|
-
automaticDimensions: viewState.automaticDimensions,
|
|
3198
|
-
metricNotation: viewState.metricNotation ?? 'meters',
|
|
3199
|
-
purpose: viewState.purpose ?? 'edit',
|
|
3200
|
-
wallDimensionReference: viewState.wallDimensionReference,
|
|
3201
|
-
}),
|
|
3202
|
-
viewState: viewState.palette
|
|
3203
|
-
? {
|
|
3204
|
-
selected: viewState.selected,
|
|
3205
|
-
unit: viewState.unit,
|
|
3206
|
-
highlighted: viewState.highlighted,
|
|
3207
|
-
hovered: viewState.hovered,
|
|
3208
|
-
moving: viewState.moving,
|
|
3209
|
-
palette: viewState.palette,
|
|
3210
|
-
}
|
|
3211
|
-
: undefined,
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3312
|
+
export const buildContext = buildFloorplanContext
|
|
3214
3313
|
|
|
3215
3314
|
export function collectFloorplanLinkedLevelNodes(
|
|
3216
3315
|
nodes: Record<string, AnyNode>,
|
|
@@ -3475,6 +3574,7 @@ function nodeDepsEqual(a: NodeDeps, b: NodeDeps): boolean {
|
|
|
3475
3574
|
'liveOverride',
|
|
3476
3575
|
'palette',
|
|
3477
3576
|
'siblingEpoch',
|
|
3577
|
+
'ctxOverrides',
|
|
3478
3578
|
'committedNodes',
|
|
3479
3579
|
'dependencyNodes',
|
|
3480
3580
|
'interactiveElevators',
|
|
@@ -3512,17 +3612,7 @@ function depsValueEqual(a: unknown, b: unknown): boolean {
|
|
|
3512
3612
|
* Sort is stable in modern JS engines, so siblings within the same
|
|
3513
3613
|
* bucket keep their DFS order (= scene tree order).
|
|
3514
3614
|
*/
|
|
3515
|
-
export
|
|
3516
|
-
switch (type) {
|
|
3517
|
-
case 'zone':
|
|
3518
|
-
return 0
|
|
3519
|
-
case 'slab':
|
|
3520
|
-
case 'ceiling':
|
|
3521
|
-
return 1
|
|
3522
|
-
default:
|
|
3523
|
-
return 2
|
|
3524
|
-
}
|
|
3525
|
-
}
|
|
3615
|
+
export { floorplanLayerRank }
|
|
3526
3616
|
|
|
3527
3617
|
function deepEqual(a: unknown, b: unknown): boolean {
|
|
3528
3618
|
if (a === b) return true
|