@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
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getSelectableKinds,
|
|
12
12
|
type ItemNode,
|
|
13
13
|
isRegistrySelectable,
|
|
14
|
+
isSelectionHighlightEnabled,
|
|
14
15
|
type NodeEvent,
|
|
15
16
|
nodeRegistry,
|
|
16
17
|
type RoofEvent,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
createMaterial,
|
|
32
33
|
createMaterialFromPresetRef,
|
|
33
34
|
getRoofMaterialArray,
|
|
35
|
+
registerMaterialCacheCleanup,
|
|
34
36
|
useViewer,
|
|
35
37
|
} from '@pascal-app/viewer'
|
|
36
38
|
import { useThree } from '@react-three/fiber'
|
|
@@ -39,10 +41,14 @@ import { type BufferGeometry, Color, type Material, type Mesh, type Object3D, Ve
|
|
|
39
41
|
import {
|
|
40
42
|
canDirectMoveNode,
|
|
41
43
|
canDirectRotateNode,
|
|
44
|
+
pointerEventHitsEditorHandle,
|
|
45
|
+
resolveDirectManipulationNode,
|
|
42
46
|
resolveDirectRotationDragDelta,
|
|
43
47
|
resolveDirectRotationPatch,
|
|
48
|
+
shouldStartDirectMoveDrag,
|
|
44
49
|
} from '../../lib/direct-manipulation'
|
|
45
50
|
import { createEditorApi } from '../../lib/editor-api'
|
|
51
|
+
import { selectionEnabled } from '../../lib/interaction/scope'
|
|
46
52
|
import {
|
|
47
53
|
type ActivePaintMaterial,
|
|
48
54
|
buildRoofSegmentSurfaceMaterialPatch,
|
|
@@ -50,6 +56,11 @@ import {
|
|
|
50
56
|
hasActivePaintMaterial,
|
|
51
57
|
resolveActivePaintMaterialFromSelection,
|
|
52
58
|
} from '../../lib/material-paint'
|
|
59
|
+
import {
|
|
60
|
+
combinePaintPreviews,
|
|
61
|
+
createPaintPreviewOwner,
|
|
62
|
+
type PaintPreviewCleanup,
|
|
63
|
+
} from '../../lib/paint-preview-owner'
|
|
53
64
|
import {
|
|
54
65
|
availablePaintScopes,
|
|
55
66
|
commitPaintScopeFanout,
|
|
@@ -70,6 +81,12 @@ import {
|
|
|
70
81
|
shouldPreserveSelectedRoofHostTarget,
|
|
71
82
|
} from '../../lib/selection-routing'
|
|
72
83
|
import { emitDeleteSFX, sfxEmitter } from '../../lib/sfx-bus'
|
|
84
|
+
import {
|
|
85
|
+
cancelPendingZonePaint,
|
|
86
|
+
paintZoneMembership,
|
|
87
|
+
zoneAtLevelPoint,
|
|
88
|
+
zoneAtWorldPoint,
|
|
89
|
+
} from '../../lib/units'
|
|
73
90
|
import useDirectManipulationFeedback from '../../store/use-direct-manipulation-feedback'
|
|
74
91
|
import useEditor, { type MaterialTargetRole } from './../../store/use-editor'
|
|
75
92
|
import useInteractionScope, {
|
|
@@ -112,8 +129,6 @@ type SelectableNodeType =
|
|
|
112
129
|
| 'window'
|
|
113
130
|
| 'door'
|
|
114
131
|
|
|
115
|
-
type PaintPreviewCleanup = () => void
|
|
116
|
-
|
|
117
132
|
type PaintInteraction = {
|
|
118
133
|
key: string
|
|
119
134
|
apply: (() => void) | null
|
|
@@ -805,6 +820,7 @@ export const SelectionManager = () => {
|
|
|
805
820
|
if (mode !== 'material-paint') return
|
|
806
821
|
if (movingNode || isCurveReshape) return
|
|
807
822
|
|
|
823
|
+
const previewOwner = createPaintPreviewOwner()
|
|
808
824
|
let activePreview: { key: string; restore: PaintPreviewCleanup } | null = null
|
|
809
825
|
// The last hover event, replayed when the application scope cycles so the
|
|
810
826
|
// preview + chip update under a stationary cursor (Shift fires no pointer move).
|
|
@@ -826,7 +842,7 @@ export const SelectionManager = () => {
|
|
|
826
842
|
selectedMaterialTarget: useEditor.getState().selectedMaterialTarget,
|
|
827
843
|
})
|
|
828
844
|
|
|
829
|
-
const
|
|
845
|
+
const resolvePaintInteraction = (event: NodeEvent): PaintInteraction | null => {
|
|
830
846
|
const eraser = useEditor.getState().paintEraser
|
|
831
847
|
const activePaintMaterial = resolveActivePaintMaterial()
|
|
832
848
|
const node = event.node
|
|
@@ -949,27 +965,29 @@ export const SelectionManager = () => {
|
|
|
949
965
|
// paint capability builds the preview; restores combine.
|
|
950
966
|
const restores: PaintPreviewCleanup[] = []
|
|
951
967
|
const sceneNodes = useScene.getState().nodes
|
|
952
|
-
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
968
|
+
try {
|
|
969
|
+
for (const target of scopeTargets) {
|
|
970
|
+
const targetNode = sceneNodes[target.nodeId]
|
|
971
|
+
const targetRoot = getRegisteredNodeObject(target.nodeId)
|
|
972
|
+
const targetCap = targetNode
|
|
973
|
+
? nodeRegistry.get(targetNode.type)?.capabilities?.paint
|
|
974
|
+
: null
|
|
975
|
+
if (!(targetNode && targetRoot && targetCap)) continue
|
|
976
|
+
const restore = targetCap.applyPreview({
|
|
977
|
+
node: targetNode,
|
|
978
|
+
role: target.role,
|
|
979
|
+
material: paintSpec.material,
|
|
980
|
+
materialPreset: paintSpec.materialPreset,
|
|
981
|
+
root: targetRoot,
|
|
982
|
+
})
|
|
983
|
+
if (restore) restores.push(restore)
|
|
984
|
+
}
|
|
985
|
+
} catch (error) {
|
|
986
|
+
combinePaintPreviews(restores)()
|
|
987
|
+
throw error
|
|
967
988
|
}
|
|
968
989
|
if (restores.length === 0) return null
|
|
969
|
-
return ()
|
|
970
|
-
for (let index = restores.length - 1; index >= 0; index -= 1)
|
|
971
|
-
restores[index]?.()
|
|
972
|
-
}
|
|
990
|
+
return combinePaintPreviews(restores)
|
|
973
991
|
}
|
|
974
992
|
: () => previewCursor('not-allowed'),
|
|
975
993
|
}
|
|
@@ -1069,6 +1087,9 @@ export const SelectionManager = () => {
|
|
|
1069
1087
|
return null
|
|
1070
1088
|
}
|
|
1071
1089
|
|
|
1090
|
+
const getPaintInteraction = (event: NodeEvent) =>
|
|
1091
|
+
previewOwner.wrap(resolvePaintInteraction(event))
|
|
1092
|
+
|
|
1072
1093
|
const onEnter = (event: NodeEvent) => {
|
|
1073
1094
|
// A host-driven drag (handle resize/rotate) sets `inputDragging`.
|
|
1074
1095
|
// useNodeEvents now emits hover events during such a drag so surface
|
|
@@ -1239,8 +1260,10 @@ export const SelectionManager = () => {
|
|
|
1239
1260
|
if (movingNode || isCurveReshape) return
|
|
1240
1261
|
|
|
1241
1262
|
const onPointerDown = (event: NodeEvent) => {
|
|
1263
|
+
if (!selectionEnabled(useInteractionScope.getState().scope)) return
|
|
1242
1264
|
const pointer = pointerEventFromNodeEvent(event)
|
|
1243
1265
|
if (pointer.button !== 0) return
|
|
1266
|
+
const handleOwnsPointer = pointerEventHitsEditorHandle(event.nativeEvent)
|
|
1244
1267
|
|
|
1245
1268
|
// Plain press on a transformable member of a multi-selection arms the
|
|
1246
1269
|
// group move — dragging slides the whole selection on the ground plane
|
|
@@ -1248,6 +1271,7 @@ export const SelectionManager = () => {
|
|
|
1248
1271
|
// group-move gizmo cross). A plain click (no drag) still falls through
|
|
1249
1272
|
// to the normal click handling, which collapses to the pressed node.
|
|
1250
1273
|
if (
|
|
1274
|
+
!handleOwnsPointer &&
|
|
1251
1275
|
!(pointer.shiftKey || pointer.altKey || isCommandModifier(pointer)) &&
|
|
1252
1276
|
armGroupMove3d({
|
|
1253
1277
|
nodeId: event.node.id as AnyNodeId,
|
|
@@ -1263,8 +1287,6 @@ export const SelectionManager = () => {
|
|
|
1263
1287
|
return
|
|
1264
1288
|
}
|
|
1265
1289
|
|
|
1266
|
-
if (!isCommandModifier(pointer)) return
|
|
1267
|
-
|
|
1268
1290
|
const eventNode = useScene.getState().nodes[event.node.id as AnyNodeId] ?? event.node
|
|
1269
1291
|
const node = resolveCanvasSelectionNode({
|
|
1270
1292
|
node: eventNode,
|
|
@@ -1272,18 +1294,26 @@ export const SelectionManager = () => {
|
|
|
1272
1294
|
selectedIds: useViewer.getState().selection.selectedIds,
|
|
1273
1295
|
})
|
|
1274
1296
|
if (!canDirectMoveNode(node)) return
|
|
1275
|
-
// Sole selection only: per-node direct manipulation stands down for a
|
|
1276
|
-
// multi-selection (the group sessions own plain drags there, and Cmd is
|
|
1277
|
-
// the selection-toggle key — a wobbly Cmd+click must not yank one
|
|
1278
|
-
// member out of the group).
|
|
1279
1297
|
const currentSelectedIds = useViewer.getState().selection.selectedIds
|
|
1280
|
-
|
|
1298
|
+
const allowPlainDrag = nodeRegistry.get(node.type)?.capabilities?.movable?.directDrag === true
|
|
1299
|
+
if (
|
|
1300
|
+
!shouldStartDirectMoveDrag({
|
|
1301
|
+
allowPlainDrag,
|
|
1302
|
+
commandModifier: isCommandModifier(pointer),
|
|
1303
|
+
handleOwnsPointer,
|
|
1304
|
+
nodeId: node.id,
|
|
1305
|
+
selectedIds: currentSelectedIds,
|
|
1306
|
+
})
|
|
1307
|
+
) {
|
|
1308
|
+
return
|
|
1309
|
+
}
|
|
1281
1310
|
|
|
1282
1311
|
const startX = pointer.clientX
|
|
1283
1312
|
const startY = pointer.clientY
|
|
1284
1313
|
const pointerId = pointer.pointerId
|
|
1285
1314
|
const pointerTarget = pointer.target instanceof EventTarget ? pointer.target : null
|
|
1286
1315
|
let engaged = false
|
|
1316
|
+
let engagedTargetId: AnyNodeId | null = null
|
|
1287
1317
|
|
|
1288
1318
|
const cleanup = () => {
|
|
1289
1319
|
window.removeEventListener('pointermove', onMove)
|
|
@@ -1305,8 +1335,9 @@ export const SelectionManager = () => {
|
|
|
1305
1335
|
useViewer.getState().setInputDragging(true)
|
|
1306
1336
|
swallowNextClick()
|
|
1307
1337
|
createEditorApi().engageMoveDrag(node)
|
|
1338
|
+
engagedTargetId = (getMovingNode()?.id as AnyNodeId | undefined) ?? null
|
|
1308
1339
|
requestAnimationFrame(() => {
|
|
1309
|
-
if (getMovingNode()
|
|
1340
|
+
if (!getMovingNode()) return
|
|
1310
1341
|
pointerTarget?.dispatchEvent(
|
|
1311
1342
|
new PointerEvent('pointermove', {
|
|
1312
1343
|
altKey: moveEvent.altKey,
|
|
@@ -1330,7 +1361,7 @@ export const SelectionManager = () => {
|
|
|
1330
1361
|
if (engaged) {
|
|
1331
1362
|
requestAnimationFrame(() => {
|
|
1332
1363
|
const editor = useEditor.getState()
|
|
1333
|
-
if (getMovingNode()?.id !==
|
|
1364
|
+
if (getMovingNode()?.id !== engagedTargetId || !editor.placementDragMode) return
|
|
1334
1365
|
editor.setMovingNode(null)
|
|
1335
1366
|
})
|
|
1336
1367
|
}
|
|
@@ -1392,7 +1423,7 @@ export const SelectionManager = () => {
|
|
|
1392
1423
|
if (key === prevKey) return
|
|
1393
1424
|
prevKey = key
|
|
1394
1425
|
let wantsMove = false
|
|
1395
|
-
if (hoveredId && !getMovingNode()) {
|
|
1426
|
+
if (hoveredId && !getMovingNode() && selectionEnabled(useInteractionScope.getState().scope)) {
|
|
1396
1427
|
if (sole === hoveredId) {
|
|
1397
1428
|
const node = useScene.getState().nodes[sole as AnyNodeId]
|
|
1398
1429
|
wantsMove = !!node && canDirectMoveNode(node)
|
|
@@ -1557,6 +1588,7 @@ export const SelectionManager = () => {
|
|
|
1557
1588
|
// body click so only the reshape tool handles the release. (Scoped to
|
|
1558
1589
|
// `endpoint`: hole-edit relies on node clicks to exit, just below.)
|
|
1559
1590
|
const activeScope = useInteractionScope.getState().scope
|
|
1591
|
+
if (activeScope.kind === 'mesh-editing') return
|
|
1560
1592
|
if (activeScope.kind === 'reshaping' && activeScope.reshape === 'endpoint') return
|
|
1561
1593
|
|
|
1562
1594
|
if (dispatchSceneAction(event.node, getEventObject(event))) {
|
|
@@ -1583,6 +1615,23 @@ export const SelectionManager = () => {
|
|
|
1583
1615
|
// the click falls through to the item underneath.
|
|
1584
1616
|
if (node.type === 'ceiling' && !event.viaHandle) return
|
|
1585
1617
|
|
|
1618
|
+
// Unit focus turns clicks inside a zone into the paint gesture:
|
|
1619
|
+
// membership toggles, the zone stays unselected, focus stays.
|
|
1620
|
+
const focusedUnitId = useViewer.getState().focusedUnitId
|
|
1621
|
+
if (focusedUnitId) {
|
|
1622
|
+
const zone =
|
|
1623
|
+
node.type === 'zone' ? node : zoneAtWorldPoint(event.position[0], event.position[2])
|
|
1624
|
+
if (zone) {
|
|
1625
|
+
event.stopPropagation()
|
|
1626
|
+
clickHandledRef.current = true
|
|
1627
|
+
setTimeout(() => {
|
|
1628
|
+
clickHandledRef.current = false
|
|
1629
|
+
}, 50)
|
|
1630
|
+
paintZoneMembership(focusedUnitId, zone.id)
|
|
1631
|
+
return
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1586
1635
|
let currentPhase = useEditor.getState().phase
|
|
1587
1636
|
let currentStructureLayer = useEditor.getState().structureLayer
|
|
1588
1637
|
const selectedIdsBeforeRouting = useViewer.getState().selection.selectedIds
|
|
@@ -1655,9 +1704,16 @@ export const SelectionManager = () => {
|
|
|
1655
1704
|
const hasModifier = nativeEvent.shiftKey || isCommandModifier(nativeEvent)
|
|
1656
1705
|
const isAlreadySole =
|
|
1657
1706
|
selectedIdsBeforeRouting.length === 1 && selectedIdsBeforeRouting[0] === nodeToSelect.id
|
|
1658
|
-
if (
|
|
1707
|
+
if (
|
|
1708
|
+
useEditor.getState().mode !== 'delete' &&
|
|
1709
|
+
!hasModifier &&
|
|
1710
|
+
isAlreadySole &&
|
|
1711
|
+
!getMovingNode() &&
|
|
1712
|
+
canDirectMoveNode(nodeToSelect)
|
|
1713
|
+
) {
|
|
1659
1714
|
sfxEmitter.emit('sfx:item-pick')
|
|
1660
|
-
|
|
1715
|
+
const moveTarget = resolveDirectManipulationNode(nodeToSelect, useScene.getState().nodes)
|
|
1716
|
+
useEditor.getState().setMovingNode(moveTarget as never)
|
|
1661
1717
|
useViewer.getState().setSelection({ selectedIds: [] })
|
|
1662
1718
|
return
|
|
1663
1719
|
}
|
|
@@ -1773,8 +1829,17 @@ export const SelectionManager = () => {
|
|
|
1773
1829
|
const onGridClick = (event: GridEvent) => {
|
|
1774
1830
|
if (clickHandledRef.current) return
|
|
1775
1831
|
if (boxSelectHandled) return
|
|
1832
|
+
if (useInteractionScope.getState().scope.kind === 'mesh-editing') return
|
|
1776
1833
|
const nativeEvent = event.nativeEvent
|
|
1777
1834
|
if (nativeEvent?.metaKey || nativeEvent?.ctrlKey || nativeEvent?.shiftKey) return
|
|
1835
|
+
// Unit focus: a ground click inside a zone paints it; elsewhere it
|
|
1836
|
+
// leaves focus and the unit selection alone.
|
|
1837
|
+
const focusedUnitId = useViewer.getState().focusedUnitId
|
|
1838
|
+
if (focusedUnitId) {
|
|
1839
|
+
const zone = zoneAtLevelPoint(event.localPosition[0], event.localPosition[2])
|
|
1840
|
+
if (zone) paintZoneMembership(focusedUnitId, zone.id)
|
|
1841
|
+
return
|
|
1842
|
+
}
|
|
1778
1843
|
const { phase, structureLayer } = useEditor.getState()
|
|
1779
1844
|
const activeStrategy = SELECTION_STRATEGIES[phase]
|
|
1780
1845
|
if (activeStrategy) activeStrategy.handleDeselect()
|
|
@@ -1804,6 +1869,7 @@ export const SelectionManager = () => {
|
|
|
1804
1869
|
if (movingNode || isCurveReshape) return
|
|
1805
1870
|
|
|
1806
1871
|
const onEnter = (event: NodeEvent) => {
|
|
1872
|
+
if (useInteractionScope.getState().scope.kind === 'mesh-editing') return
|
|
1807
1873
|
// A host-driven drag (handle resize/rotate, box-select) sets
|
|
1808
1874
|
// `inputDragging`. useNodeEvents still emits hover events during it so
|
|
1809
1875
|
// surface move tools keep tracking — but the select-hover outline must
|
|
@@ -1852,6 +1918,7 @@ export const SelectionManager = () => {
|
|
|
1852
1918
|
}
|
|
1853
1919
|
|
|
1854
1920
|
const onDoubleClick = (event: NodeEvent) => {
|
|
1921
|
+
if (useInteractionScope.getState().scope.kind === 'mesh-editing') return
|
|
1855
1922
|
let node = resolveCanvasSelectionNode({
|
|
1856
1923
|
node: resolveSelectModeNodeTarget(event),
|
|
1857
1924
|
nodes: useScene.getState().nodes,
|
|
@@ -1883,6 +1950,24 @@ export const SelectionManager = () => {
|
|
|
1883
1950
|
}
|
|
1884
1951
|
}
|
|
1885
1952
|
|
|
1953
|
+
// While a unit is focused a double-click inside a zone selects that
|
|
1954
|
+
// zone (focus stays); the two clicks before it cancel each other's paint.
|
|
1955
|
+
if (useViewer.getState().focusedUnitId) {
|
|
1956
|
+
const zone =
|
|
1957
|
+
node.type === 'zone' ? node : zoneAtWorldPoint(event.position[0], event.position[2])
|
|
1958
|
+
if (zone) {
|
|
1959
|
+
event.stopPropagation()
|
|
1960
|
+
cancelPendingZonePaint(zone.id)
|
|
1961
|
+
SELECTION_STRATEGIES.structure?.handleSelect(
|
|
1962
|
+
zone,
|
|
1963
|
+
event.nativeEvent,
|
|
1964
|
+
modifierKeysRef.current,
|
|
1965
|
+
[],
|
|
1966
|
+
)
|
|
1967
|
+
return
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1886
1971
|
if (node.type === 'zone') {
|
|
1887
1972
|
return
|
|
1888
1973
|
}
|
|
@@ -2119,6 +2204,7 @@ const SelectionMaterialSync = () => {
|
|
|
2119
2204
|
const previewSelectedIds = useViewer((s) => s.previewSelectedIds)
|
|
2120
2205
|
const hoveredId = useViewer((s) => s.hoveredId)
|
|
2121
2206
|
const hoverHighlightMode = useViewer((s) => s.hoverHighlightMode)
|
|
2207
|
+
const registryVersion = useRegistryVersion()
|
|
2122
2208
|
const geometryRevision = useViewer((s) => s.geometryRevision)
|
|
2123
2209
|
const activeHighlightKindsRef = useRef(new Map<string, HighlightKind>())
|
|
2124
2210
|
const highlightedMaterialsRef = useRef(
|
|
@@ -2141,6 +2227,10 @@ const SelectionMaterialSync = () => {
|
|
|
2141
2227
|
continue
|
|
2142
2228
|
}
|
|
2143
2229
|
|
|
2230
|
+
if (node && !isSelectionHighlightEnabled(node.type)) {
|
|
2231
|
+
continue
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2144
2234
|
const rootObject = sceneRegistry.nodes.get(id)
|
|
2145
2235
|
if (!rootObject) {
|
|
2146
2236
|
continue
|
|
@@ -2196,6 +2286,7 @@ const SelectionMaterialSync = () => {
|
|
|
2196
2286
|
}, [])
|
|
2197
2287
|
|
|
2198
2288
|
useEffect(() => {
|
|
2289
|
+
void registryVersion
|
|
2199
2290
|
void geometryRevision
|
|
2200
2291
|
const nextHighlightKinds = new Map<string, HighlightKind>()
|
|
2201
2292
|
|
|
@@ -2211,6 +2302,7 @@ const SelectionMaterialSync = () => {
|
|
|
2211
2302
|
syncSelectionMaterials()
|
|
2212
2303
|
}, [
|
|
2213
2304
|
geometryRevision,
|
|
2305
|
+
registryVersion,
|
|
2214
2306
|
hoverHighlightMode,
|
|
2215
2307
|
hoveredId,
|
|
2216
2308
|
previewSelectedIds,
|
|
@@ -2251,7 +2343,7 @@ const SelectionMaterialSync = () => {
|
|
|
2251
2343
|
}, [])
|
|
2252
2344
|
|
|
2253
2345
|
useEffect(() => {
|
|
2254
|
-
|
|
2346
|
+
const clearHighlights = () => {
|
|
2255
2347
|
for (const [mesh, entry] of highlightedMaterialsRef.current.entries()) {
|
|
2256
2348
|
if (mesh.material === entry.highlightedMaterial) {
|
|
2257
2349
|
mesh.material = entry.originalMaterial
|
|
@@ -2261,6 +2353,11 @@ const SelectionMaterialSync = () => {
|
|
|
2261
2353
|
|
|
2262
2354
|
highlightedMaterialsRef.current.clear()
|
|
2263
2355
|
}
|
|
2356
|
+
const unsubscribe = registerMaterialCacheCleanup(clearHighlights)
|
|
2357
|
+
return () => {
|
|
2358
|
+
unsubscribe()
|
|
2359
|
+
clearHighlights()
|
|
2360
|
+
}
|
|
2264
2361
|
}, [])
|
|
2265
2362
|
|
|
2266
2363
|
return null
|
|
@@ -2272,11 +2369,13 @@ const EditorOutlinerSync = () => {
|
|
|
2272
2369
|
const previewSelectedIds = useViewer((s) => s.previewSelectedIds)
|
|
2273
2370
|
const hoveredId = useViewer((s) => s.hoveredId)
|
|
2274
2371
|
const geometryRevision = useViewer((s) => s.geometryRevision)
|
|
2372
|
+
const registryVersion = useRegistryVersion()
|
|
2275
2373
|
const outliner = useViewer((s) => s.outliner)
|
|
2276
2374
|
const nodes = useScene((s) => s.nodes)
|
|
2277
2375
|
|
|
2278
2376
|
useEffect(() => {
|
|
2279
2377
|
void geometryRevision
|
|
2378
|
+
void registryVersion
|
|
2280
2379
|
let idsToHighlight: string[] = []
|
|
2281
2380
|
|
|
2282
2381
|
// 1. Determine what should be highlighted based on Phase
|
|
@@ -2311,7 +2410,8 @@ const EditorOutlinerSync = () => {
|
|
|
2311
2410
|
// 2. Sync with the imperative outliner arrays (mutate in place to keep references)
|
|
2312
2411
|
outliner.selectedObjects.length = 0
|
|
2313
2412
|
for (const id of idsToHighlight) {
|
|
2314
|
-
|
|
2413
|
+
const node = nodes[id as AnyNodeId]
|
|
2414
|
+
if (!(node && isSelectionHighlightEnabled(node.type))) continue
|
|
2315
2415
|
const obj = sceneRegistry.nodes.get(id)
|
|
2316
2416
|
if (obj?.parent) outliner.selectedObjects.push(obj)
|
|
2317
2417
|
}
|
|
@@ -2322,14 +2422,25 @@ const EditorOutlinerSync = () => {
|
|
|
2322
2422
|
useViewer.setState({ hoveredId: null })
|
|
2323
2423
|
} else {
|
|
2324
2424
|
const hoveredNode = nodes[hoveredId as AnyNodeId]
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2425
|
+
if (hoveredNode && isSelectionHighlightEnabled(hoveredNode.type)) {
|
|
2426
|
+
const obj =
|
|
2427
|
+
hoveredNode.type === 'roof-segment'
|
|
2428
|
+
? (getHoveredRoofSegmentOutlineProxy(hoveredId) ?? sceneRegistry.nodes.get(hoveredId))
|
|
2429
|
+
: sceneRegistry.nodes.get(hoveredId)
|
|
2430
|
+
if (obj?.parent) outliner.hoveredObjects.push(obj)
|
|
2431
|
+
}
|
|
2330
2432
|
}
|
|
2331
2433
|
}
|
|
2332
|
-
}, [
|
|
2434
|
+
}, [
|
|
2435
|
+
geometryRevision,
|
|
2436
|
+
registryVersion,
|
|
2437
|
+
phase,
|
|
2438
|
+
previewSelectedIds,
|
|
2439
|
+
selection,
|
|
2440
|
+
hoveredId,
|
|
2441
|
+
outliner,
|
|
2442
|
+
nodes,
|
|
2443
|
+
])
|
|
2333
2444
|
|
|
2334
2445
|
return null
|
|
2335
2446
|
}
|