@pascal-app/editor 0.6.0 → 0.8.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/package.json +13 -9
- package/src/components/editor/bottom-sheet.tsx +149 -0
- package/src/components/editor/custom-camera-controls.tsx +74 -5
- package/src/components/editor/editor-layout-mobile.tsx +264 -0
- package/src/components/editor/editor-layout-v2.tsx +24 -3
- package/src/components/editor/first-person/build-collider-world.ts +363 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +860 -0
- package/src/components/editor/first-person-controls.tsx +496 -143
- package/src/components/editor/floating-action-menu.tsx +32 -55
- package/src/components/editor/floorplan-background-selection.ts +113 -0
- package/src/components/editor/floorplan-panel.tsx +9861 -3297
- package/src/components/editor/index.tsx +295 -32
- package/src/components/editor/selection-manager.tsx +575 -13
- package/src/components/editor/snapshot-capture-overlay.tsx +465 -0
- package/src/components/editor/thumbnail-generator.tsx +56 -68
- package/src/components/editor/use-floorplan-background-placement.ts +257 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +171 -0
- package/src/components/editor/use-floorplan-scene-data.ts +189 -0
- package/src/components/editor/wall-measurement-label.tsx +267 -36
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +95 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +160 -0
- package/src/components/editor-2d/floorplan-hotkey-handlers.tsx +92 -0
- package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +124 -0
- package/src/components/editor-2d/renderers/floorplan-marquee-layer.tsx +58 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +202 -0
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +113 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +474 -0
- package/src/components/editor-2d/svg-paths.ts +119 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +10 -12
- package/src/components/systems/roof/roof-edit-system.tsx +1 -1
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -0
- package/src/components/systems/zone/zone-system.tsx +0 -0
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +1 -0
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +1 -0
- package/src/components/tools/ceiling/ceiling-tool.tsx +5 -5
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +9 -2
- package/src/components/tools/column/column-tool.tsx +97 -0
- package/src/components/tools/column/move-column-tool.tsx +105 -0
- package/src/components/tools/door/door-tool.tsx +7 -0
- package/src/components/tools/door/move-door-tool.tsx +28 -8
- package/src/components/tools/fence/curve-fence-tool.tsx +4 -5
- package/src/components/tools/fence/fence-drafting.ts +10 -3
- package/src/components/tools/fence/fence-tool.tsx +160 -4
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +139 -25
- package/src/components/tools/fence/move-fence-tool.tsx +111 -40
- package/src/components/tools/item/move-tool.tsx +7 -1
- package/src/components/tools/item/placement-math.ts +32 -5
- package/src/components/tools/item/placement-strategies.ts +110 -31
- package/src/components/tools/item/placement-types.ts +7 -0
- package/src/components/tools/item/use-draft-node.ts +1 -0
- package/src/components/tools/item/use-placement-coordinator.tsx +558 -52
- package/src/components/tools/roof/move-roof-tool.tsx +29 -17
- package/src/components/tools/select/box-select-tool.tsx +12 -17
- package/src/components/tools/shared/polygon-editor.tsx +153 -28
- package/src/components/tools/shared/segment-angle.ts +156 -0
- package/src/components/tools/slab/slab-boundary-editor.tsx +1 -0
- package/src/components/tools/slab/slab-hole-editor.tsx +1 -0
- package/src/components/tools/spawn/move-spawn-tool.tsx +101 -0
- package/src/components/tools/spawn/spawn-tool.tsx +130 -0
- package/src/components/tools/tool-manager.tsx +20 -5
- package/src/components/tools/wall/curve-wall-tool.tsx +8 -6
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +131 -27
- package/src/components/tools/wall/move-wall-tool.tsx +6 -4
- package/src/components/tools/wall/wall-drafting.ts +18 -9
- package/src/components/tools/wall/wall-tool.tsx +136 -4
- package/src/components/tools/window/move-window-tool.tsx +18 -0
- package/src/components/tools/window/window-tool.tsx +5 -0
- package/src/components/tools/zone/zone-tool.tsx +20 -5
- package/src/components/ui/action-menu/camera-actions.tsx +37 -33
- package/src/components/ui/action-menu/control-modes.tsx +34 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
- package/src/components/ui/action-menu/index.tsx +98 -59
- package/src/components/ui/action-menu/structure-tools.tsx +2 -0
- package/src/components/ui/action-menu/view-toggles.tsx +418 -41
- package/src/components/ui/command-palette/editor-commands.tsx +24 -5
- package/src/components/ui/command-palette/index.tsx +4 -255
- package/src/components/ui/controls/material-picker.tsx +154 -164
- package/src/components/ui/controls/slider-control.tsx +66 -18
- package/src/components/ui/floating-level-selector.tsx +286 -55
- package/src/components/ui/helpers/helper-manager.tsx +10 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +2563 -1239
- package/src/components/ui/item-catalog/item-catalog.tsx +96 -187
- package/src/components/ui/level-duplicate-dialog.tsx +113 -0
- package/src/components/ui/panels/ceiling-panel.tsx +3 -28
- package/src/components/ui/panels/column-panel.tsx +759 -0
- package/src/components/ui/panels/door-panel.tsx +989 -290
- package/src/components/ui/panels/fence-panel.tsx +2 -49
- package/src/components/ui/panels/mobile-panel-sheet.tsx +108 -0
- package/src/components/ui/panels/mobile-selection-bar.tsx +100 -0
- package/src/components/ui/panels/node-display.ts +39 -0
- package/src/components/ui/panels/paint-panel.tsx +163 -0
- package/src/components/ui/panels/panel-manager.tsx +208 -28
- package/src/components/ui/panels/panel-wrapper.tsx +48 -39
- package/src/components/ui/panels/reference-panel.tsx +253 -5
- package/src/components/ui/panels/roof-panel.tsx +13 -64
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -25
- package/src/components/ui/panels/slab-panel.tsx +4 -30
- package/src/components/ui/panels/spawn-panel.tsx +161 -0
- package/src/components/ui/panels/stair-panel.tsx +20 -74
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -25
- package/src/components/ui/panels/wall-panel.tsx +10 -8
- package/src/components/ui/panels/window-panel.tsx +668 -139
- package/src/components/ui/primitives/number-input.tsx +1 -1
- package/src/components/ui/primitives/sidebar.tsx +0 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +0 -0
- package/src/components/ui/sidebar/icon-rail.tsx +0 -0
- package/src/components/ui/sidebar/mobile-tab-bar.tsx +46 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +330 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +0 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +0 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +77 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +105 -22
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +0 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +76 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +11 -3
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +10 -5
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +1 -1
- package/src/components/ui/sidebar/tab-bar.tsx +3 -0
- package/src/components/ui/slider.tsx +1 -1
- package/src/components/viewer-overlay.tsx +0 -0
- package/src/components/viewer-zone-system.tsx +0 -0
- package/src/hooks/use-auto-frame.ts +45 -0
- package/src/hooks/use-auto-save.ts +14 -0
- package/src/hooks/use-keyboard.ts +74 -7
- package/src/hooks/use-mobile.ts +12 -12
- package/src/index.tsx +8 -1
- package/src/lib/door-interaction.ts +88 -0
- package/src/lib/floorplan/geometry.ts +263 -0
- package/src/lib/floorplan/index.ts +38 -0
- package/src/lib/floorplan/items.ts +179 -0
- package/src/lib/floorplan/selection-tool.ts +231 -0
- package/src/lib/floorplan/stairs.ts +478 -0
- package/src/lib/floorplan/types.ts +57 -0
- package/src/lib/floorplan/walls.ts +23 -0
- package/src/lib/guide-events.ts +10 -0
- package/src/lib/level-duplication.test.ts +70 -0
- package/src/lib/level-duplication.ts +153 -0
- package/src/lib/local-guide-image.ts +42 -0
- package/src/lib/material-paint.ts +284 -0
- package/src/lib/roof-duplication.ts +214 -0
- package/src/lib/scene-bounds.test.ts +183 -0
- package/src/lib/scene-bounds.ts +169 -0
- package/src/lib/scene.ts +0 -0
- package/src/lib/sfx-bus.ts +2 -0
- package/src/lib/sfx-player.ts +5 -5
- package/src/lib/stair-duplication.ts +126 -0
- package/src/lib/window-interaction.ts +86 -0
- package/src/store/use-editor.tsx +186 -62
- package/tsconfig.json +2 -1
- package/src/components/feedback-dialog.tsx +0 -265
- package/src/components/pascal-radio.tsx +0 -280
- package/src/components/preview-button.tsx +0 -16
- package/src/components/ui/viewer-toolbar.tsx +0 -395
|
@@ -2,27 +2,57 @@ import {
|
|
|
2
2
|
type AnyNode,
|
|
3
3
|
type AnyNodeId,
|
|
4
4
|
type BuildingNode,
|
|
5
|
+
type CeilingNode,
|
|
6
|
+
type ColumnNode,
|
|
5
7
|
emitter,
|
|
8
|
+
type FenceNode,
|
|
9
|
+
getMaterialPresetByRef,
|
|
6
10
|
type ItemNode,
|
|
7
11
|
type NodeEvent,
|
|
8
12
|
type RoofEvent,
|
|
13
|
+
type RoofNode,
|
|
9
14
|
type RoofSegmentEvent,
|
|
10
15
|
resolveLevelId,
|
|
11
|
-
|
|
16
|
+
resolveMaterial,
|
|
17
|
+
type SlabNode,
|
|
12
18
|
type StairEvent,
|
|
13
19
|
type StairNode,
|
|
14
|
-
type StairSurfaceMaterialRole,
|
|
15
20
|
type StairSegmentEvent,
|
|
21
|
+
type StairSurfaceMaterialRole,
|
|
22
|
+
sceneRegistry,
|
|
16
23
|
useScene,
|
|
17
24
|
type WallEvent,
|
|
25
|
+
type WallNode,
|
|
18
26
|
type WallSurfaceSide,
|
|
19
27
|
} from '@pascal-app/core'
|
|
20
28
|
|
|
21
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
applyMaterialPresetToMaterials,
|
|
31
|
+
createMaterial,
|
|
32
|
+
createMaterialFromPresetRef,
|
|
33
|
+
getRoofMaterialArray,
|
|
34
|
+
getStairBodyMaterials,
|
|
35
|
+
getStairRailingMaterial,
|
|
36
|
+
getVisibleWallMaterials,
|
|
37
|
+
useViewer,
|
|
38
|
+
} from '@pascal-app/viewer'
|
|
22
39
|
import { useCallback, useEffect, useRef } from 'react'
|
|
23
|
-
import {
|
|
40
|
+
import { type BufferGeometry, Color, type Material, type Mesh, type Object3D } from 'three'
|
|
41
|
+
import {
|
|
42
|
+
type ActivePaintMaterial,
|
|
43
|
+
buildRoofSurfaceMaterialPatch,
|
|
44
|
+
buildSingleSurfaceMaterialPatch,
|
|
45
|
+
buildStairSurfaceMaterialPatch,
|
|
46
|
+
buildWallSurfaceMaterialPatch,
|
|
47
|
+
hasActivePaintMaterial,
|
|
48
|
+
resolveActivePaintMaterialFromSelection,
|
|
49
|
+
} from '../../lib/material-paint'
|
|
24
50
|
import { sfxEmitter } from '../../lib/sfx-bus'
|
|
25
|
-
import useEditor, {
|
|
51
|
+
import useEditor, {
|
|
52
|
+
type MaterialTargetRole,
|
|
53
|
+
type Phase,
|
|
54
|
+
type StructureLayer,
|
|
55
|
+
} from './../../store/use-editor'
|
|
26
56
|
import { boxSelectHandled } from '../tools/select/box-select-tool'
|
|
27
57
|
|
|
28
58
|
const isNodeInCurrentLevel = (node: AnyNode): boolean => {
|
|
@@ -36,6 +66,7 @@ type SelectableNodeType =
|
|
|
36
66
|
| 'wall'
|
|
37
67
|
| 'fence'
|
|
38
68
|
| 'item'
|
|
69
|
+
| 'column'
|
|
39
70
|
| 'building'
|
|
40
71
|
| 'zone'
|
|
41
72
|
| 'slab'
|
|
@@ -44,6 +75,7 @@ type SelectableNodeType =
|
|
|
44
75
|
| 'roof-segment'
|
|
45
76
|
| 'stair'
|
|
46
77
|
| 'stair-segment'
|
|
78
|
+
| 'spawn'
|
|
47
79
|
| 'window'
|
|
48
80
|
| 'door'
|
|
49
81
|
|
|
@@ -52,6 +84,16 @@ type ModifierKeys = {
|
|
|
52
84
|
ctrl: boolean
|
|
53
85
|
}
|
|
54
86
|
|
|
87
|
+
type PaintPreviewCleanup = () => void
|
|
88
|
+
|
|
89
|
+
type PaintInteraction = {
|
|
90
|
+
key: string
|
|
91
|
+
apply: (() => void) | null
|
|
92
|
+
hoverMode: HoverHighlightMode
|
|
93
|
+
hoveredId: AnyNodeId
|
|
94
|
+
preview: (() => PaintPreviewCleanup | null) | null
|
|
95
|
+
}
|
|
96
|
+
|
|
55
97
|
interface SelectionStrategy {
|
|
56
98
|
types: SelectableNodeType[]
|
|
57
99
|
handleSelect: (node: AnyNode, nativeEvent?: any, modifierKeys?: ModifierKeys) => void
|
|
@@ -175,10 +217,217 @@ function getIntersectionMaterialIndex(
|
|
|
175
217
|
return group?.materialIndex
|
|
176
218
|
}
|
|
177
219
|
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
220
|
+
function getRegisteredNodeObject(nodeId: string): Object3D | null {
|
|
221
|
+
return sceneRegistry.nodes.get(nodeId) ?? null
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function getRegisteredMesh(nodeId: string): Mesh | null {
|
|
225
|
+
const object = getRegisteredNodeObject(nodeId)
|
|
226
|
+
return object && (object as Mesh).isMesh ? (object as Mesh) : null
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function previewMeshMaterial(mesh: Mesh, material: Material | Material[]): PaintPreviewCleanup {
|
|
230
|
+
const previousMaterial = mesh.material
|
|
231
|
+
mesh.material = material
|
|
232
|
+
return () => {
|
|
233
|
+
mesh.material = previousMaterial
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function previewCursor(cursor: string): PaintPreviewCleanup {
|
|
238
|
+
const previousCursor = document.body.style.cursor
|
|
239
|
+
document.body.style.cursor = cursor
|
|
240
|
+
return () => {
|
|
241
|
+
document.body.style.cursor = previousCursor
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function getSingleSurfacePreviewMaterial(material: ActivePaintMaterial): Material | null {
|
|
246
|
+
if (material.materialPreset) {
|
|
247
|
+
return createMaterialFromPresetRef(material.materialPreset)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (material.material) {
|
|
251
|
+
return createMaterial(material.material)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return null
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function applyWallPaintPreview(
|
|
258
|
+
node: WallNode,
|
|
259
|
+
role: WallSurfaceSide,
|
|
260
|
+
material: ActivePaintMaterial,
|
|
261
|
+
): PaintPreviewCleanup | null {
|
|
262
|
+
const mesh = getRegisteredMesh(node.id)
|
|
263
|
+
if (!mesh) return null
|
|
264
|
+
|
|
265
|
+
const previewNode = {
|
|
266
|
+
...node,
|
|
267
|
+
...buildWallSurfaceMaterialPatch(node, role, material.material, material.materialPreset),
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return previewMeshMaterial(mesh, getVisibleWallMaterials(previewNode))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function applyRoofPaintPreview(
|
|
274
|
+
node: RoofNode,
|
|
275
|
+
role: 'top' | 'edge' | 'wall',
|
|
276
|
+
material: ActivePaintMaterial,
|
|
277
|
+
): PaintPreviewCleanup | null {
|
|
278
|
+
const root = getRegisteredNodeObject(node.id)
|
|
279
|
+
const mesh = root?.getObjectByName('merged-roof') as Mesh | undefined
|
|
280
|
+
if (!mesh) return null
|
|
281
|
+
|
|
282
|
+
const previewNode = {
|
|
283
|
+
...node,
|
|
284
|
+
...buildRoofSurfaceMaterialPatch(node, role, material.material, material.materialPreset),
|
|
285
|
+
}
|
|
286
|
+
const previewMaterial = getRoofMaterialArray(previewNode)
|
|
287
|
+
if (!previewMaterial) return null
|
|
288
|
+
|
|
289
|
+
return previewMeshMaterial(mesh, previewMaterial)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function applyStairPaintPreview(
|
|
293
|
+
node: StairNode,
|
|
294
|
+
role: StairSurfaceMaterialRole,
|
|
295
|
+
material: ActivePaintMaterial,
|
|
296
|
+
): PaintPreviewCleanup | null {
|
|
297
|
+
const root = getRegisteredNodeObject(node.id)
|
|
298
|
+
if (!root) return null
|
|
299
|
+
|
|
300
|
+
const previewNode = {
|
|
301
|
+
...node,
|
|
302
|
+
...buildStairSurfaceMaterialPatch(node, role, material.material, material.materialPreset),
|
|
303
|
+
}
|
|
304
|
+
const bodyMaterials = getStairBodyMaterials(previewNode)
|
|
305
|
+
const railingMaterial = getStairRailingMaterial(previewNode)
|
|
306
|
+
const restores: PaintPreviewCleanup[] = []
|
|
307
|
+
|
|
308
|
+
root.traverse((object) => {
|
|
309
|
+
if (!(object as Mesh).isMesh) return
|
|
310
|
+
const mesh = object as Mesh
|
|
311
|
+
if (mesh.name.startsWith('stair-railing')) {
|
|
312
|
+
restores.push(previewMeshMaterial(mesh, railingMaterial))
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
if (Array.isArray(mesh.material) && mesh.material.length === 2) {
|
|
316
|
+
restores.push(previewMeshMaterial(mesh, bodyMaterials))
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
if (mesh.name === 'merged-stair') {
|
|
320
|
+
restores.push(previewMeshMaterial(mesh, bodyMaterials))
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
if (mesh.name.startsWith('stair-side')) {
|
|
324
|
+
restores.push(previewMeshMaterial(mesh, bodyMaterials[1]))
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
if (restores.length === 0) return null
|
|
329
|
+
|
|
330
|
+
return () => {
|
|
331
|
+
for (let index = restores.length - 1; index >= 0; index -= 1) {
|
|
332
|
+
restores[index]?.()
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function applySingleSurfacePaintPreview(
|
|
338
|
+
node: FenceNode | ColumnNode | SlabNode | CeilingNode,
|
|
339
|
+
material: ActivePaintMaterial,
|
|
340
|
+
): PaintPreviewCleanup | null {
|
|
341
|
+
if (node.type === 'ceiling') {
|
|
342
|
+
const root = getRegisteredMesh(node.id)
|
|
343
|
+
const overlay = root?.getObjectByName('ceiling-grid') as Mesh | undefined
|
|
344
|
+
if (!(root && overlay)) return null
|
|
345
|
+
|
|
346
|
+
const previewColor =
|
|
347
|
+
getMaterialPresetByRef(material.materialPreset)?.mapProperties.color ??
|
|
348
|
+
resolveMaterial(material.material).color ??
|
|
349
|
+
'#999999'
|
|
350
|
+
|
|
351
|
+
const previousRootMaterial = root.material
|
|
352
|
+
const previousOverlayMaterial = overlay.material
|
|
353
|
+
const rootPreviewMaterial = Array.isArray(previousRootMaterial)
|
|
354
|
+
? previousRootMaterial.map((entry) => entry.clone())
|
|
355
|
+
: previousRootMaterial.clone()
|
|
356
|
+
const overlayPreviewMaterial = Array.isArray(previousOverlayMaterial)
|
|
357
|
+
? previousOverlayMaterial.map((entry) => entry.clone())
|
|
358
|
+
: previousOverlayMaterial.clone()
|
|
359
|
+
|
|
360
|
+
const applyColor = (input: Material | Material[]) => {
|
|
361
|
+
const materials = Array.isArray(input) ? input : [input]
|
|
362
|
+
for (const entry of materials) {
|
|
363
|
+
const materialWithColor = entry as Material & { color?: Color; needsUpdate?: boolean }
|
|
364
|
+
if (materialWithColor.color instanceof Color) {
|
|
365
|
+
materialWithColor.color = new Color(previewColor)
|
|
366
|
+
}
|
|
367
|
+
materialWithColor.needsUpdate = true
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
applyColor(rootPreviewMaterial)
|
|
372
|
+
applyColor(overlayPreviewMaterial)
|
|
373
|
+
root.material = rootPreviewMaterial
|
|
374
|
+
overlay.material = overlayPreviewMaterial
|
|
375
|
+
|
|
376
|
+
return () => {
|
|
377
|
+
root.material = previousRootMaterial
|
|
378
|
+
overlay.material = previousOverlayMaterial
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const registeredObject = getRegisteredNodeObject(node.id)
|
|
383
|
+
const mesh =
|
|
384
|
+
registeredObject && (registeredObject as Mesh).isMesh ? (registeredObject as Mesh) : null
|
|
385
|
+
|
|
386
|
+
const previewMaterial = getSingleSurfacePreviewMaterial(material)
|
|
387
|
+
if (!previewMaterial) return null
|
|
388
|
+
|
|
389
|
+
if (node.type === 'column') {
|
|
390
|
+
if (!registeredObject) return null
|
|
391
|
+
const restores: PaintPreviewCleanup[] = []
|
|
392
|
+
|
|
393
|
+
registeredObject.traverse((object) => {
|
|
394
|
+
if (!(object as Mesh).isMesh) return
|
|
395
|
+
restores.push(previewMeshMaterial(object as Mesh, previewMaterial))
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
if (restores.length === 0) return null
|
|
399
|
+
return () => {
|
|
400
|
+
for (let index = restores.length - 1; index >= 0; index -= 1) {
|
|
401
|
+
restores[index]?.()
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (!mesh) return null
|
|
407
|
+
|
|
408
|
+
if (node.type === 'slab') {
|
|
409
|
+
const slabMaterial = previewMaterial.clone()
|
|
410
|
+
applyMaterialPresetToMaterials(slabMaterial, getMaterialPresetByRef(material.materialPreset))
|
|
411
|
+
const previewMeshMaterialInput = slabMaterial as Material & {
|
|
412
|
+
alphaMap?: unknown
|
|
413
|
+
depthWrite?: boolean
|
|
414
|
+
needsUpdate?: boolean
|
|
415
|
+
opacity?: number
|
|
416
|
+
side?: number
|
|
417
|
+
transparent?: boolean
|
|
418
|
+
}
|
|
419
|
+
previewMeshMaterialInput.transparent = false
|
|
420
|
+
previewMeshMaterialInput.opacity = 1
|
|
421
|
+
previewMeshMaterialInput.alphaMap = null
|
|
422
|
+
previewMeshMaterialInput.depthWrite = true
|
|
423
|
+
previewMeshMaterialInput.needsUpdate = true
|
|
424
|
+
return previewMeshMaterial(mesh, slabMaterial)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return previewMeshMaterial(mesh, previewMaterial)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function setSelectedMaterialTargetForNode(node: AnyNode, role: MaterialTargetRole | null) {
|
|
182
431
|
if (!role) {
|
|
183
432
|
const currentTarget = useEditor.getState().selectedMaterialTarget
|
|
184
433
|
if (currentTarget?.nodeId !== node.id) {
|
|
@@ -209,6 +458,7 @@ const HIGHLIGHT_PROFILES = {
|
|
|
209
458
|
} as const
|
|
210
459
|
|
|
211
460
|
type HighlightKind = keyof typeof HIGHLIGHT_PROFILES
|
|
461
|
+
type HoverHighlightMode = 'default' | 'delete' | 'paint-ready' | 'paint-disabled'
|
|
212
462
|
|
|
213
463
|
type HighlightableMaterial = Material & {
|
|
214
464
|
color?: Color
|
|
@@ -314,6 +564,7 @@ const SELECTION_STRATEGIES: Record<string, SelectionStrategy> = {
|
|
|
314
564
|
'wall',
|
|
315
565
|
'fence',
|
|
316
566
|
'item',
|
|
567
|
+
'column',
|
|
317
568
|
'zone',
|
|
318
569
|
'slab',
|
|
319
570
|
'ceiling',
|
|
@@ -321,6 +572,7 @@ const SELECTION_STRATEGIES: Record<string, SelectionStrategy> = {
|
|
|
321
572
|
'roof-segment',
|
|
322
573
|
'stair',
|
|
323
574
|
'stair-segment',
|
|
575
|
+
'spawn',
|
|
324
576
|
'window',
|
|
325
577
|
'door',
|
|
326
578
|
],
|
|
@@ -366,12 +618,14 @@ const SELECTION_STRATEGIES: Record<string, SelectionStrategy> = {
|
|
|
366
618
|
if (
|
|
367
619
|
node.type === 'wall' ||
|
|
368
620
|
node.type === 'fence' ||
|
|
621
|
+
node.type === 'column' ||
|
|
369
622
|
node.type === 'slab' ||
|
|
370
623
|
node.type === 'ceiling' ||
|
|
371
624
|
node.type === 'roof' ||
|
|
372
625
|
node.type === 'roof-segment' ||
|
|
373
626
|
node.type === 'stair' ||
|
|
374
|
-
node.type === 'stair-segment'
|
|
627
|
+
node.type === 'stair-segment' ||
|
|
628
|
+
node.type === 'spawn'
|
|
375
629
|
)
|
|
376
630
|
return true
|
|
377
631
|
if (node.type === 'item') {
|
|
@@ -428,12 +682,14 @@ const getSelectionTarget = (node: AnyNode): SelectionTarget | null => {
|
|
|
428
682
|
if (
|
|
429
683
|
node.type === 'wall' ||
|
|
430
684
|
node.type === 'fence' ||
|
|
685
|
+
node.type === 'column' ||
|
|
431
686
|
node.type === 'slab' ||
|
|
432
687
|
node.type === 'ceiling' ||
|
|
433
688
|
node.type === 'roof' ||
|
|
434
689
|
node.type === 'roof-segment' ||
|
|
435
690
|
node.type === 'stair' ||
|
|
436
691
|
node.type === 'stair-segment' ||
|
|
692
|
+
node.type === 'spawn' ||
|
|
437
693
|
node.type === 'window' ||
|
|
438
694
|
node.type === 'door'
|
|
439
695
|
) {
|
|
@@ -475,13 +731,297 @@ export const SelectionManager = () => {
|
|
|
475
731
|
const curvingFence = useEditor((s) => s.curvingFence)
|
|
476
732
|
|
|
477
733
|
useEffect(() => {
|
|
478
|
-
|
|
734
|
+
const nextHoverMode: HoverHighlightMode = mode === 'delete' ? 'delete' : 'default'
|
|
735
|
+
setHoverHighlightMode(nextHoverMode)
|
|
479
736
|
|
|
480
737
|
return () => {
|
|
481
738
|
setHoverHighlightMode('default')
|
|
482
739
|
}
|
|
483
740
|
}, [mode, setHoverHighlightMode])
|
|
484
741
|
|
|
742
|
+
useEffect(() => {
|
|
743
|
+
if (mode !== 'material-paint') return
|
|
744
|
+
if (movingNode || curvingWall) return
|
|
745
|
+
|
|
746
|
+
let activePreview: { key: string; restore: PaintPreviewCleanup } | null = null
|
|
747
|
+
|
|
748
|
+
const clearActivePreview = () => {
|
|
749
|
+
activePreview?.restore()
|
|
750
|
+
activePreview = null
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const resolveActivePaintMaterial = () =>
|
|
754
|
+
useEditor.getState().activePaintMaterial ??
|
|
755
|
+
resolveActivePaintMaterialFromSelection({
|
|
756
|
+
nodes: useScene.getState().nodes,
|
|
757
|
+
selectedId:
|
|
758
|
+
useViewer.getState().selection.selectedIds.length === 1
|
|
759
|
+
? (useViewer.getState().selection.selectedIds[0] ?? null)
|
|
760
|
+
: null,
|
|
761
|
+
selectedMaterialTarget: useEditor.getState().selectedMaterialTarget,
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
const getPaintInteraction = (event: NodeEvent): PaintInteraction | null => {
|
|
765
|
+
const activePaintMaterial = resolveActivePaintMaterial()
|
|
766
|
+
const node = event.node
|
|
767
|
+
|
|
768
|
+
if (!isNodeInCurrentLevel(node)) return null
|
|
769
|
+
|
|
770
|
+
if (node.type === 'wall') {
|
|
771
|
+
const role = resolveWallMaterialTarget(event as WallEvent)
|
|
772
|
+
const compatible = role !== null && hasActivePaintMaterial(activePaintMaterial)
|
|
773
|
+
return {
|
|
774
|
+
key: `wall:${node.id}:${role ?? 'unsupported'}`,
|
|
775
|
+
hoveredId: node.id as AnyNodeId,
|
|
776
|
+
hoverMode:
|
|
777
|
+
compatible && hasActivePaintMaterial(activePaintMaterial) && role
|
|
778
|
+
? 'paint-ready'
|
|
779
|
+
: 'paint-disabled',
|
|
780
|
+
apply:
|
|
781
|
+
compatible && hasActivePaintMaterial(activePaintMaterial)
|
|
782
|
+
? () => {
|
|
783
|
+
useScene
|
|
784
|
+
.getState()
|
|
785
|
+
.updateNode(
|
|
786
|
+
node.id as AnyNodeId,
|
|
787
|
+
buildWallSurfaceMaterialPatch(
|
|
788
|
+
node as WallNode,
|
|
789
|
+
role!,
|
|
790
|
+
activePaintMaterial.material,
|
|
791
|
+
activePaintMaterial.materialPreset,
|
|
792
|
+
),
|
|
793
|
+
)
|
|
794
|
+
}
|
|
795
|
+
: null,
|
|
796
|
+
preview:
|
|
797
|
+
compatible && hasActivePaintMaterial(activePaintMaterial) && role
|
|
798
|
+
? () => applyWallPaintPreview(node as WallNode, role, activePaintMaterial)
|
|
799
|
+
: () => previewCursor('not-allowed'),
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (node.type === 'roof' || node.type === 'roof-segment') {
|
|
804
|
+
const roofNode =
|
|
805
|
+
node.type === 'roof'
|
|
806
|
+
? node
|
|
807
|
+
: node.parentId
|
|
808
|
+
? useScene.getState().nodes[node.parentId as AnyNodeId]
|
|
809
|
+
: null
|
|
810
|
+
if (!roofNode || roofNode.type !== 'roof') return null
|
|
811
|
+
|
|
812
|
+
const role = resolveRoofMaterialTarget(event as RoofEvent | RoofSegmentEvent)
|
|
813
|
+
const compatible = role !== null && hasActivePaintMaterial(activePaintMaterial)
|
|
814
|
+
return {
|
|
815
|
+
key: `roof:${roofNode.id}:${role ?? 'unsupported'}`,
|
|
816
|
+
hoveredId: roofNode.id as AnyNodeId,
|
|
817
|
+
hoverMode:
|
|
818
|
+
compatible && hasActivePaintMaterial(activePaintMaterial) && role
|
|
819
|
+
? 'paint-ready'
|
|
820
|
+
: 'paint-disabled',
|
|
821
|
+
apply:
|
|
822
|
+
compatible && hasActivePaintMaterial(activePaintMaterial)
|
|
823
|
+
? () => {
|
|
824
|
+
useScene
|
|
825
|
+
.getState()
|
|
826
|
+
.updateNode(
|
|
827
|
+
roofNode.id as AnyNodeId,
|
|
828
|
+
buildRoofSurfaceMaterialPatch(
|
|
829
|
+
roofNode as RoofNode,
|
|
830
|
+
role!,
|
|
831
|
+
activePaintMaterial.material,
|
|
832
|
+
activePaintMaterial.materialPreset,
|
|
833
|
+
),
|
|
834
|
+
)
|
|
835
|
+
}
|
|
836
|
+
: null,
|
|
837
|
+
preview:
|
|
838
|
+
compatible && hasActivePaintMaterial(activePaintMaterial) && role
|
|
839
|
+
? () => applyRoofPaintPreview(roofNode as RoofNode, role, activePaintMaterial)
|
|
840
|
+
: () => previewCursor('not-allowed'),
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (node.type === 'stair' || node.type === 'stair-segment') {
|
|
845
|
+
const stairNode =
|
|
846
|
+
node.type === 'stair'
|
|
847
|
+
? node
|
|
848
|
+
: node.parentId
|
|
849
|
+
? useScene.getState().nodes[node.parentId as AnyNodeId]
|
|
850
|
+
: null
|
|
851
|
+
if (!stairNode || stairNode.type !== 'stair') return null
|
|
852
|
+
|
|
853
|
+
const role = resolveStairMaterialTarget(event as StairEvent | StairSegmentEvent)
|
|
854
|
+
const compatible = role !== null && hasActivePaintMaterial(activePaintMaterial)
|
|
855
|
+
return {
|
|
856
|
+
key: `stair:${stairNode.id}:${role ?? 'unsupported'}`,
|
|
857
|
+
hoveredId: stairNode.id as AnyNodeId,
|
|
858
|
+
hoverMode:
|
|
859
|
+
compatible && hasActivePaintMaterial(activePaintMaterial) && role
|
|
860
|
+
? 'paint-ready'
|
|
861
|
+
: 'paint-disabled',
|
|
862
|
+
apply:
|
|
863
|
+
compatible && hasActivePaintMaterial(activePaintMaterial)
|
|
864
|
+
? () => {
|
|
865
|
+
useScene
|
|
866
|
+
.getState()
|
|
867
|
+
.updateNode(
|
|
868
|
+
stairNode.id as AnyNodeId,
|
|
869
|
+
buildStairSurfaceMaterialPatch(
|
|
870
|
+
stairNode as StairNode,
|
|
871
|
+
role!,
|
|
872
|
+
activePaintMaterial.material,
|
|
873
|
+
activePaintMaterial.materialPreset,
|
|
874
|
+
),
|
|
875
|
+
)
|
|
876
|
+
}
|
|
877
|
+
: null,
|
|
878
|
+
preview:
|
|
879
|
+
compatible && hasActivePaintMaterial(activePaintMaterial) && role
|
|
880
|
+
? () => applyStairPaintPreview(stairNode as StairNode, role, activePaintMaterial)
|
|
881
|
+
: () => previewCursor('not-allowed'),
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (
|
|
886
|
+
node.type === 'fence' ||
|
|
887
|
+
node.type === 'column' ||
|
|
888
|
+
node.type === 'slab' ||
|
|
889
|
+
node.type === 'ceiling'
|
|
890
|
+
) {
|
|
891
|
+
const compatible = hasActivePaintMaterial(activePaintMaterial)
|
|
892
|
+
|
|
893
|
+
return {
|
|
894
|
+
key: `${node.type}:${node.id}:surface`,
|
|
895
|
+
hoveredId: node.id as AnyNodeId,
|
|
896
|
+
hoverMode: compatible ? 'paint-ready' : 'paint-disabled',
|
|
897
|
+
apply: compatible
|
|
898
|
+
? () => {
|
|
899
|
+
useScene
|
|
900
|
+
.getState()
|
|
901
|
+
.updateNode(
|
|
902
|
+
node.id as AnyNodeId,
|
|
903
|
+
buildSingleSurfaceMaterialPatch<
|
|
904
|
+
FenceNode | ColumnNode | SlabNode | CeilingNode
|
|
905
|
+
>(activePaintMaterial.material, activePaintMaterial.materialPreset),
|
|
906
|
+
)
|
|
907
|
+
}
|
|
908
|
+
: null,
|
|
909
|
+
preview: compatible
|
|
910
|
+
? () =>
|
|
911
|
+
applySingleSurfacePaintPreview(
|
|
912
|
+
node as FenceNode | ColumnNode | SlabNode | CeilingNode,
|
|
913
|
+
activePaintMaterial,
|
|
914
|
+
)
|
|
915
|
+
: () => previewCursor('not-allowed'),
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const disabledNodeTypes = ['item', 'window', 'door', 'zone']
|
|
920
|
+
if (disabledNodeTypes.includes(node.type)) {
|
|
921
|
+
return {
|
|
922
|
+
key: `${node.type}:${node.id}:unsupported`,
|
|
923
|
+
hoveredId: node.id as AnyNodeId,
|
|
924
|
+
hoverMode: 'paint-disabled',
|
|
925
|
+
apply: null,
|
|
926
|
+
preview: () => previewCursor('not-allowed'),
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return null
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const onEnter = (event: NodeEvent) => {
|
|
934
|
+
if (boxSelectHandled) return
|
|
935
|
+
|
|
936
|
+
const interaction = getPaintInteraction(event)
|
|
937
|
+
if (!interaction) return
|
|
938
|
+
|
|
939
|
+
event.stopPropagation()
|
|
940
|
+
|
|
941
|
+
if (activePreview?.key === interaction.key) {
|
|
942
|
+
return
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
clearActivePreview()
|
|
946
|
+
useViewer.setState({ hoveredId: interaction.hoveredId })
|
|
947
|
+
setHoverHighlightMode(interaction.hoverMode)
|
|
948
|
+
|
|
949
|
+
const restore = interaction.preview?.()
|
|
950
|
+
if (restore) {
|
|
951
|
+
activePreview = { key: interaction.key, restore }
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const onLeave = (event: NodeEvent) => {
|
|
956
|
+
const interaction = getPaintInteraction(event)
|
|
957
|
+
if (!interaction) return
|
|
958
|
+
|
|
959
|
+
if (activePreview?.key !== interaction.key) {
|
|
960
|
+
return
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
clearActivePreview()
|
|
964
|
+
if (useViewer.getState().hoveredId === interaction.hoveredId) {
|
|
965
|
+
useViewer.setState({ hoveredId: null })
|
|
966
|
+
}
|
|
967
|
+
setHoverHighlightMode('default')
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const onClick = (event: NodeEvent) => {
|
|
971
|
+
if (boxSelectHandled) return
|
|
972
|
+
|
|
973
|
+
const interaction = getPaintInteraction(event)
|
|
974
|
+
if (!interaction) return
|
|
975
|
+
|
|
976
|
+
event.stopPropagation()
|
|
977
|
+
|
|
978
|
+
if (!interaction.apply) {
|
|
979
|
+
return
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
interaction.apply()
|
|
983
|
+
if (activePreview?.key === interaction.key) {
|
|
984
|
+
activePreview = null
|
|
985
|
+
} else {
|
|
986
|
+
clearActivePreview()
|
|
987
|
+
}
|
|
988
|
+
setHoverHighlightMode(interaction.hoverMode)
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const allTypes = [
|
|
992
|
+
'wall',
|
|
993
|
+
'fence',
|
|
994
|
+
'item',
|
|
995
|
+
'column',
|
|
996
|
+
'slab',
|
|
997
|
+
'ceiling',
|
|
998
|
+
'roof',
|
|
999
|
+
'roof-segment',
|
|
1000
|
+
'stair',
|
|
1001
|
+
'stair-segment',
|
|
1002
|
+
'window',
|
|
1003
|
+
'door',
|
|
1004
|
+
'zone',
|
|
1005
|
+
] as const
|
|
1006
|
+
|
|
1007
|
+
for (const type of allTypes) {
|
|
1008
|
+
emitter.on(`${type}:enter` as any, onEnter as any)
|
|
1009
|
+
emitter.on(`${type}:leave` as any, onLeave as any)
|
|
1010
|
+
emitter.on(`${type}:click` as any, onClick as any)
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
return () => {
|
|
1014
|
+
for (const type of allTypes) {
|
|
1015
|
+
emitter.off(`${type}:enter` as any, onEnter as any)
|
|
1016
|
+
emitter.off(`${type}:leave` as any, onLeave as any)
|
|
1017
|
+
emitter.off(`${type}:click` as any, onClick as any)
|
|
1018
|
+
}
|
|
1019
|
+
clearActivePreview()
|
|
1020
|
+
useViewer.setState({ hoveredId: null })
|
|
1021
|
+
setHoverHighlightMode('default')
|
|
1022
|
+
}
|
|
1023
|
+
}, [curvingWall, mode, movingNode, setHoverHighlightMode])
|
|
1024
|
+
|
|
485
1025
|
useEffect(() => {
|
|
486
1026
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
487
1027
|
if (event.key === 'Meta') modifierKeysRef.current.meta = true
|
|
@@ -597,6 +1137,14 @@ export const SelectionManager = () => {
|
|
|
597
1137
|
nextMaterialTargetHandled = true
|
|
598
1138
|
}
|
|
599
1139
|
|
|
1140
|
+
if (
|
|
1141
|
+
(node.type === 'fence' || node.type === 'slab' || node.type === 'ceiling') &&
|
|
1142
|
+
nodeToSelect.type === node.type
|
|
1143
|
+
) {
|
|
1144
|
+
setSelectedMaterialTargetForNode(nodeToSelect, 'surface')
|
|
1145
|
+
nextMaterialTargetHandled = true
|
|
1146
|
+
}
|
|
1147
|
+
|
|
600
1148
|
if (!nextMaterialTargetHandled && useEditor.getState().selectedMaterialTarget) {
|
|
601
1149
|
useEditor.getState().setSelectedMaterialTarget(null)
|
|
602
1150
|
}
|
|
@@ -612,6 +1160,7 @@ export const SelectionManager = () => {
|
|
|
612
1160
|
'wall',
|
|
613
1161
|
'fence',
|
|
614
1162
|
'item',
|
|
1163
|
+
'column',
|
|
615
1164
|
'building',
|
|
616
1165
|
'zone',
|
|
617
1166
|
'slab',
|
|
@@ -620,6 +1169,7 @@ export const SelectionManager = () => {
|
|
|
620
1169
|
'roof-segment',
|
|
621
1170
|
'stair',
|
|
622
1171
|
'stair-segment',
|
|
1172
|
+
'spawn',
|
|
623
1173
|
'window',
|
|
624
1174
|
'door',
|
|
625
1175
|
]
|
|
@@ -707,12 +1257,14 @@ export const SelectionManager = () => {
|
|
|
707
1257
|
} else if (
|
|
708
1258
|
node.type === 'wall' ||
|
|
709
1259
|
node.type === 'fence' ||
|
|
1260
|
+
node.type === 'column' ||
|
|
710
1261
|
node.type === 'slab' ||
|
|
711
1262
|
node.type === 'ceiling' ||
|
|
712
1263
|
node.type === 'roof' ||
|
|
713
1264
|
node.type === 'roof-segment' ||
|
|
714
1265
|
node.type === 'stair' ||
|
|
715
1266
|
node.type === 'stair-segment' ||
|
|
1267
|
+
node.type === 'spawn' ||
|
|
716
1268
|
node.type === 'window' ||
|
|
717
1269
|
node.type === 'door'
|
|
718
1270
|
) {
|
|
@@ -758,6 +1310,7 @@ export const SelectionManager = () => {
|
|
|
758
1310
|
'wall',
|
|
759
1311
|
'fence',
|
|
760
1312
|
'item',
|
|
1313
|
+
'column',
|
|
761
1314
|
'building',
|
|
762
1315
|
'slab',
|
|
763
1316
|
'ceiling',
|
|
@@ -765,6 +1318,7 @@ export const SelectionManager = () => {
|
|
|
765
1318
|
'roof-segment',
|
|
766
1319
|
'stair',
|
|
767
1320
|
'stair-segment',
|
|
1321
|
+
'spawn',
|
|
768
1322
|
'window',
|
|
769
1323
|
'door',
|
|
770
1324
|
'zone',
|
|
@@ -830,12 +1384,14 @@ export const SelectionManager = () => {
|
|
|
830
1384
|
'wall',
|
|
831
1385
|
'fence',
|
|
832
1386
|
'item',
|
|
1387
|
+
'column',
|
|
833
1388
|
'slab',
|
|
834
1389
|
'ceiling',
|
|
835
1390
|
'roof',
|
|
836
1391
|
'roof-segment',
|
|
837
1392
|
'stair',
|
|
838
1393
|
'stair-segment',
|
|
1394
|
+
'spawn',
|
|
839
1395
|
'window',
|
|
840
1396
|
'door',
|
|
841
1397
|
'zone',
|
|
@@ -912,7 +1468,12 @@ const SelectionStateSync = () => {
|
|
|
912
1468
|
const selectedNode = useScene.getState().nodes[singleSelectedId as AnyNodeId]
|
|
913
1469
|
if (
|
|
914
1470
|
!selectedNode ||
|
|
915
|
-
(selectedNode.type !== 'wall' &&
|
|
1471
|
+
(selectedNode.type !== 'wall' &&
|
|
1472
|
+
selectedNode.type !== 'fence' &&
|
|
1473
|
+
selectedNode.type !== 'slab' &&
|
|
1474
|
+
selectedNode.type !== 'ceiling' &&
|
|
1475
|
+
selectedNode.type !== 'stair' &&
|
|
1476
|
+
selectedNode.type !== 'roof')
|
|
916
1477
|
) {
|
|
917
1478
|
setSelectedMaterialTarget(null)
|
|
918
1479
|
return
|
|
@@ -1022,7 +1583,8 @@ const SelectionMaterialSync = () => {
|
|
|
1022
1583
|
}, [hoverHighlightMode, hoveredId, previewSelectedIds, selectedIds, syncSelectionMaterials])
|
|
1023
1584
|
|
|
1024
1585
|
useEffect(() => {
|
|
1025
|
-
return useScene.subscribe(() => {
|
|
1586
|
+
return useScene.subscribe((state, prevState) => {
|
|
1587
|
+
if (state.nodes === prevState.nodes) return
|
|
1026
1588
|
syncSelectionMaterials()
|
|
1027
1589
|
})
|
|
1028
1590
|
}, [syncSelectionMaterials])
|