@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { emitter, sceneRegistry
|
|
3
|
+
import { emitter, sceneRegistry } from '@pascal-app/core'
|
|
4
4
|
import { SSGI_PARAMS, snapLevelsToTruePositions, useViewer } from '@pascal-app/viewer'
|
|
5
5
|
import type { CameraControls } from '@react-three/drei'
|
|
6
6
|
import { useThree } from '@react-three/fiber'
|
|
@@ -28,7 +28,6 @@ import { EDITOR_LAYER } from '../../lib/constants'
|
|
|
28
28
|
|
|
29
29
|
const THUMBNAIL_WIDTH = 1920
|
|
30
30
|
const THUMBNAIL_HEIGHT = 1080
|
|
31
|
-
const AUTO_SAVE_DELAY = 10_000
|
|
32
31
|
|
|
33
32
|
export interface SnapshotCameraData {
|
|
34
33
|
position: [number, number, number]
|
|
@@ -49,8 +48,6 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
49
48
|
const mainCamera = useThree((state) => state.camera)
|
|
50
49
|
const controls = useThree((state) => state.controls) as CameraControls | null
|
|
51
50
|
const isGenerating = useRef(false)
|
|
52
|
-
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
53
|
-
const pendingAutoRef = useRef(false)
|
|
54
51
|
const onThumbnailCaptureRef = useRef(onThumbnailCapture)
|
|
55
52
|
|
|
56
53
|
const thumbnailCameraRef = useRef<THREE.PerspectiveCamera | null>(null)
|
|
@@ -61,7 +58,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
61
58
|
onThumbnailCaptureRef.current = onThumbnailCapture
|
|
62
59
|
}, [onThumbnailCapture])
|
|
63
60
|
|
|
64
|
-
// Build the thumbnail camera, SSGI pipeline, and render target once
|
|
61
|
+
// Build the thumbnail camera, SSGI pipeline, and render target once — reused on every capture.
|
|
65
62
|
useEffect(() => {
|
|
66
63
|
const cam = new THREE.PerspectiveCamera(60, THUMBNAIL_WIDTH / THUMBNAIL_HEIGHT, 0.1, 1000)
|
|
67
64
|
cam.layers.disable(EDITOR_LAYER)
|
|
@@ -75,7 +72,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
75
72
|
if (!mounted) return
|
|
76
73
|
|
|
77
74
|
// pass() handles MRT internally for all material types, including custom
|
|
78
|
-
// shaders
|
|
75
|
+
// shaders — unlike renderer.setMRT() which crashes on non-NodeMaterials.
|
|
79
76
|
// pass() also respects camera.layers, so EDITOR_LAYER objects are filtered.
|
|
80
77
|
const scenePass = pass(scene, cam)
|
|
81
78
|
scenePass.setMRT(
|
|
@@ -117,15 +114,15 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
117
114
|
const ao = (denoisePass as any).r
|
|
118
115
|
const finalOutput = vec4(scenePassColor.rgb.mul(ao), scenePassColor.a)
|
|
119
116
|
|
|
120
|
-
// FXAA requires a texture node as input
|
|
121
|
-
// into an intermediate RT so FXAA can sample it with
|
|
117
|
+
// FXAA requires a texture node as input; convertToTexture renders finalOutput
|
|
118
|
+
// into an intermediate RT so FXAA can sample it with neighbour UV offsets.
|
|
122
119
|
const aaOutput = fxaa(convertToTexture(finalOutput))
|
|
123
120
|
|
|
124
121
|
const pipeline = new RenderPipeline(gl as unknown as WebGPURenderer)
|
|
125
122
|
pipeline.outputNode = aaOutput
|
|
126
123
|
pipelineRef.current = pipeline
|
|
127
124
|
|
|
128
|
-
// Dedicated render target
|
|
125
|
+
// Dedicated render target — pipeline outputs here instead of the canvas,
|
|
129
126
|
// so R3F's main render loop can never overwrite our capture.
|
|
130
127
|
const { width, height } = gl.domElement
|
|
131
128
|
renderTargetRef.current = new RenderTarget(width, height, { depthBuffer: true })
|
|
@@ -176,7 +173,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
176
173
|
thumbnailCamera.aspect = width / height
|
|
177
174
|
thumbnailCamera.updateProjectionMatrix()
|
|
178
175
|
|
|
179
|
-
// Capture camera data for snapshot storage
|
|
176
|
+
// Capture camera data for snapshot storage
|
|
180
177
|
const pos = mainCamera.position
|
|
181
178
|
let tgt: [number, number, number] | null = null
|
|
182
179
|
if (controls && 'getTarget' in controls) {
|
|
@@ -192,7 +189,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
192
189
|
...(isOrtho && { zoom: (mainCamera as THREE.OrthographicCamera).zoom }),
|
|
193
190
|
}
|
|
194
191
|
|
|
195
|
-
// For auto-save
|
|
192
|
+
// For auto-save: snap levels to stacked positions and reset levelMode
|
|
196
193
|
let restoreLevelMode: (() => void) | null = null
|
|
197
194
|
let restoreLevels: () => void = () => {}
|
|
198
195
|
if (snapLevels) {
|
|
@@ -205,7 +202,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
205
202
|
}
|
|
206
203
|
|
|
207
204
|
// Hide scan and guide nodes directly so they are excluded from the
|
|
208
|
-
// thumbnail regardless of whether ScanSystem
|
|
205
|
+
// thumbnail regardless of whether ScanSystem/GuideSystem listeners are
|
|
209
206
|
// registered. Returns a function that restores the original visibility.
|
|
210
207
|
const restoreNodeVisibility = (() => {
|
|
211
208
|
const saved = new Map<THREE.Object3D, boolean>()
|
|
@@ -231,7 +228,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
231
228
|
if (pipelineRef.current && renderTargetRef.current) {
|
|
232
229
|
const rt = renderTargetRef.current
|
|
233
230
|
|
|
234
|
-
// Resize RT if the canvas dimensions changed
|
|
231
|
+
// Resize RT if the canvas dimensions changed
|
|
235
232
|
if (rt.width !== width || rt.height !== height) {
|
|
236
233
|
rt.setSize(width, height)
|
|
237
234
|
}
|
|
@@ -248,7 +245,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
248
245
|
emitter.emit('thumbnail:after-capture', undefined)
|
|
249
246
|
|
|
250
247
|
// Restore level positions, levelMode, and node visibility immediately after the
|
|
251
|
-
// render
|
|
248
|
+
// render — before the async GPU readback.
|
|
252
249
|
restoreLevels()
|
|
253
250
|
restoreLevelMode?.()
|
|
254
251
|
restoreNodeVisibility()
|
|
@@ -265,18 +262,49 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
265
262
|
)) as Uint8Array
|
|
266
263
|
|
|
267
264
|
const actualBytesPerRow = width * 4
|
|
265
|
+
const tightTotal = actualBytesPerRow * height
|
|
268
266
|
const paddedBytesPerRow = Math.ceil(actualBytesPerRow / 256) * 256
|
|
267
|
+
// Two readback shapes to handle:
|
|
268
|
+
// - WebGPU (`copyTextureToBuffer`): top-down + 256-byte row padding
|
|
269
|
+
// when width*4 isn't already a multiple of 256.
|
|
270
|
+
// - WebGL2 fallback (iOS Chrome, etc.): tightly-packed but bottom-up
|
|
271
|
+
// (OpenGL framebuffer convention).
|
|
272
|
+
// `isWebGPURenderer` lies — it stays true even when the renderer
|
|
273
|
+
// falls back to the WebGL backend. Inspect the actual backend
|
|
274
|
+
// instead (presence of a GPU device, or backend constructor name).
|
|
275
|
+
const backend = (renderer as any).backend
|
|
276
|
+
const isWebGPU =
|
|
277
|
+
!!backend?.device ||
|
|
278
|
+
backend?.isWebGPUBackend === true ||
|
|
279
|
+
backend?.constructor?.name === 'WebGPUBackend'
|
|
269
280
|
let tightPixels: Uint8ClampedArray
|
|
270
|
-
if (
|
|
271
|
-
|
|
281
|
+
if (isWebGPU) {
|
|
282
|
+
// WebGPU: depad rows if needed; orientation is already top-down.
|
|
283
|
+
if (paddedBytesPerRow === actualBytesPerRow) {
|
|
284
|
+
tightPixels = new Uint8ClampedArray(
|
|
285
|
+
pixels.buffer,
|
|
286
|
+
pixels.byteOffset,
|
|
287
|
+
Math.min(pixels.byteLength, tightTotal),
|
|
288
|
+
)
|
|
289
|
+
} else {
|
|
290
|
+
tightPixels = new Uint8ClampedArray(tightTotal)
|
|
291
|
+
for (let row = 0; row < height; row++) {
|
|
292
|
+
tightPixels.set(
|
|
293
|
+
pixels.subarray(
|
|
294
|
+
row * paddedBytesPerRow,
|
|
295
|
+
row * paddedBytesPerRow + actualBytesPerRow,
|
|
296
|
+
),
|
|
297
|
+
row * actualBytesPerRow,
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
272
301
|
} else {
|
|
273
|
-
|
|
302
|
+
// WebGL2: tight buffer in bottom-up order — flip rows.
|
|
303
|
+
tightPixels = new Uint8ClampedArray(tightTotal)
|
|
274
304
|
for (let row = 0; row < height; row++) {
|
|
305
|
+
const srcStart = (height - 1 - row) * actualBytesPerRow
|
|
275
306
|
tightPixels.set(
|
|
276
|
-
pixels.subarray(
|
|
277
|
-
row * paddedBytesPerRow,
|
|
278
|
-
row * paddedBytesPerRow + actualBytesPerRow,
|
|
279
|
-
),
|
|
307
|
+
pixels.subarray(srcStart, srcStart + actualBytesPerRow),
|
|
280
308
|
row * actualBytesPerRow,
|
|
281
309
|
)
|
|
282
310
|
}
|
|
@@ -308,6 +336,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
308
336
|
offscreen.getContext('2d')!.drawImage(srcCanvas, sx, sy, outW, outH, 0, 0, outW, outH)
|
|
309
337
|
blob = await offscreen.convertToBlob({ type: 'image/png' })
|
|
310
338
|
} else {
|
|
339
|
+
// Standard: center-crop to 1920×1080 aspect ratio
|
|
311
340
|
const srcAspect = width / height
|
|
312
341
|
const dstAspect = THUMBNAIL_WIDTH / THUMBNAIL_HEIGHT
|
|
313
342
|
let sx = 0,
|
|
@@ -333,7 +362,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
333
362
|
if (captureMode !== undefined) cameraData.captureMode = captureMode
|
|
334
363
|
cameraData.resolution = { w: outW, h: outH }
|
|
335
364
|
} else {
|
|
336
|
-
// Fallback: plain render directly to the canvas
|
|
365
|
+
// Fallback: plain render directly to the canvas
|
|
337
366
|
emitter.emit('thumbnail:before-capture', undefined)
|
|
338
367
|
gl.render(scene, thumbnailCamera)
|
|
339
368
|
emitter.emit('thumbnail:after-capture', undefined)
|
|
@@ -419,10 +448,11 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
419
448
|
)
|
|
420
449
|
|
|
421
450
|
// Thumbnail request via emitter. Two call shapes:
|
|
422
|
-
// - user-driven capture: `{ projectId, captureMode, cropRegion }
|
|
451
|
+
// - user-driven capture: `{ projectId, captureMode, cropRegion }` — captures
|
|
423
452
|
// the current pose with the supplied crop.
|
|
424
|
-
// - auto-save
|
|
425
|
-
// their true positions first for a consistent auto-thumbnail angle.
|
|
453
|
+
// - host-driven auto-save: `{ projectId, snapLevels: true }` — snaps levels
|
|
454
|
+
// to their true positions first for a consistent auto-thumbnail angle.
|
|
455
|
+
// The caller owns policy (when to fire, whether the tab is visible).
|
|
426
456
|
useEffect(() => {
|
|
427
457
|
if (!onThumbnailCapture) return
|
|
428
458
|
|
|
@@ -438,49 +468,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
438
468
|
return () => emitter.off('camera-controls:generate-thumbnail', handleGenerateThumbnail)
|
|
439
469
|
}, [generate, onThumbnailCapture])
|
|
440
470
|
|
|
441
|
-
//
|
|
442
|
-
// community host-side autosave hook is not part of this repo.
|
|
443
|
-
useEffect(() => {
|
|
444
|
-
if (!onThumbnailCapture) return
|
|
445
|
-
|
|
446
|
-
const triggerNow = () => {
|
|
447
|
-
void generate(true)
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const scheduleOrDefer = () => {
|
|
451
|
-
if (document.visibilityState === 'visible') {
|
|
452
|
-
triggerNow()
|
|
453
|
-
} else {
|
|
454
|
-
pendingAutoRef.current = true
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const onSceneChange = () => {
|
|
459
|
-
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current)
|
|
460
|
-
debounceTimerRef.current = setTimeout(scheduleOrDefer, AUTO_SAVE_DELAY)
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const onVisibilityChange = () => {
|
|
464
|
-
if (document.visibilityState === 'visible' && pendingAutoRef.current) {
|
|
465
|
-
pendingAutoRef.current = false
|
|
466
|
-
triggerNow()
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const unsubscribe = useScene.subscribe((state, prevState) => {
|
|
471
|
-
if (state.nodes !== prevState.nodes) onSceneChange()
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
document.addEventListener('visibilitychange', onVisibilityChange)
|
|
475
|
-
|
|
476
|
-
return () => {
|
|
477
|
-
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current)
|
|
478
|
-
unsubscribe()
|
|
479
|
-
document.removeEventListener('visibilitychange', onVisibilityChange)
|
|
480
|
-
}
|
|
481
|
-
}, [generate, onThumbnailCapture])
|
|
482
|
-
|
|
483
|
-
// Go-to-camera: animate camera to a saved snapshot position or target.
|
|
471
|
+
// Go-to-camera: animate camera to a saved snapshot position/target
|
|
484
472
|
useEffect(() => {
|
|
485
473
|
const handler = ({
|
|
486
474
|
position,
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { emitter, type FenceNode, isCurvedWall, type WallNode } from '@pascal-app/core'
|
|
4
|
+
import { type MouseEvent as ReactMouseEvent, useCallback } from 'react'
|
|
5
|
+
import { getPlanPointDistance } from '../../lib/floorplan'
|
|
6
|
+
import { snapFenceDraftPoint } from '../tools/fence/fence-drafting'
|
|
7
|
+
import type { WallPlanPoint } from '../tools/wall/wall-drafting'
|
|
8
|
+
|
|
9
|
+
type UseFloorplanBackgroundPlacementArgs = {
|
|
10
|
+
activePolygonDraftPoints: WallPlanPoint[]
|
|
11
|
+
ceilingDraftPoints: WallPlanPoint[]
|
|
12
|
+
clearFencePlacementDraft: () => void
|
|
13
|
+
clearRoofPlacementDraft: () => void
|
|
14
|
+
emitFloorplanGridEvent: (
|
|
15
|
+
type: 'click' | 'double-click' | 'move',
|
|
16
|
+
planPoint: WallPlanPoint,
|
|
17
|
+
event: ReactMouseEvent<SVGSVGElement>,
|
|
18
|
+
) => WallPlanPoint
|
|
19
|
+
fenceDraftStart: WallPlanPoint | null
|
|
20
|
+
fences: FenceNode[]
|
|
21
|
+
findClosestWallPoint: (
|
|
22
|
+
point: WallPlanPoint,
|
|
23
|
+
walls: WallNode[],
|
|
24
|
+
options?: { canUseWall?: (wall: WallNode) => boolean },
|
|
25
|
+
) => {
|
|
26
|
+
normal: [number, number, number]
|
|
27
|
+
point: WallPlanPoint
|
|
28
|
+
t: number
|
|
29
|
+
wall: WallNode
|
|
30
|
+
} | null
|
|
31
|
+
floorplanOpeningLocalY: number
|
|
32
|
+
getSnappedFloorplanPoint: (point: WallPlanPoint) => WallPlanPoint
|
|
33
|
+
handleCeilingPlacementPoint: (point: WallPlanPoint) => void
|
|
34
|
+
handleSlabPlacementPoint: (point: WallPlanPoint) => void
|
|
35
|
+
handleWallPlacementPoint: (point: WallPlanPoint) => void
|
|
36
|
+
handleZonePlacementPoint: (point: WallPlanPoint) => void
|
|
37
|
+
isCeilingBuildActive: boolean
|
|
38
|
+
isFenceBuildActive: boolean
|
|
39
|
+
isFloorplanGridInteractionActive: boolean
|
|
40
|
+
isOpeningPlacementActive: boolean
|
|
41
|
+
isPolygonBuildActive: boolean
|
|
42
|
+
isRoofBuildActive: boolean
|
|
43
|
+
isWallBuildActive: boolean
|
|
44
|
+
isZoneBuildActive: boolean
|
|
45
|
+
roofDraftStart: WallPlanPoint | null
|
|
46
|
+
setCursorPoint: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
47
|
+
setFenceDraftEnd: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
48
|
+
setFenceDraftStart: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
49
|
+
setRoofDraftEnd: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
50
|
+
setRoofDraftStart: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
51
|
+
shiftPressed: boolean
|
|
52
|
+
snapWallDraftPoint: (args: {
|
|
53
|
+
point: WallPlanPoint
|
|
54
|
+
walls: WallNode[]
|
|
55
|
+
start?: WallPlanPoint
|
|
56
|
+
angleSnap: boolean
|
|
57
|
+
}) => WallPlanPoint
|
|
58
|
+
snapPolygonDraftPoint: (args: {
|
|
59
|
+
point: WallPlanPoint
|
|
60
|
+
start?: WallPlanPoint
|
|
61
|
+
angleSnap: boolean
|
|
62
|
+
}) => WallPlanPoint
|
|
63
|
+
toPoint2D: (point: WallPlanPoint) => { x: number; y: number }
|
|
64
|
+
walls: WallNode[]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function useFloorplanBackgroundPlacement({
|
|
68
|
+
activePolygonDraftPoints,
|
|
69
|
+
ceilingDraftPoints,
|
|
70
|
+
clearFencePlacementDraft,
|
|
71
|
+
clearRoofPlacementDraft,
|
|
72
|
+
emitFloorplanGridEvent,
|
|
73
|
+
fenceDraftStart,
|
|
74
|
+
fences,
|
|
75
|
+
findClosestWallPoint,
|
|
76
|
+
floorplanOpeningLocalY,
|
|
77
|
+
getSnappedFloorplanPoint,
|
|
78
|
+
handleCeilingPlacementPoint,
|
|
79
|
+
handleSlabPlacementPoint,
|
|
80
|
+
handleWallPlacementPoint,
|
|
81
|
+
handleZonePlacementPoint,
|
|
82
|
+
isCeilingBuildActive,
|
|
83
|
+
isFenceBuildActive,
|
|
84
|
+
isFloorplanGridInteractionActive,
|
|
85
|
+
isOpeningPlacementActive,
|
|
86
|
+
isPolygonBuildActive,
|
|
87
|
+
isRoofBuildActive,
|
|
88
|
+
isWallBuildActive,
|
|
89
|
+
isZoneBuildActive,
|
|
90
|
+
roofDraftStart,
|
|
91
|
+
setCursorPoint,
|
|
92
|
+
setFenceDraftEnd,
|
|
93
|
+
setFenceDraftStart,
|
|
94
|
+
setRoofDraftEnd,
|
|
95
|
+
setRoofDraftStart,
|
|
96
|
+
shiftPressed,
|
|
97
|
+
snapWallDraftPoint,
|
|
98
|
+
snapPolygonDraftPoint,
|
|
99
|
+
toPoint2D,
|
|
100
|
+
walls,
|
|
101
|
+
}: UseFloorplanBackgroundPlacementArgs) {
|
|
102
|
+
const handleBackgroundPlacementClick = useCallback(
|
|
103
|
+
(
|
|
104
|
+
planPoint: WallPlanPoint,
|
|
105
|
+
event: ReactMouseEvent<SVGSVGElement>,
|
|
106
|
+
draftStart: WallPlanPoint | null,
|
|
107
|
+
) => {
|
|
108
|
+
if (isOpeningPlacementActive) {
|
|
109
|
+
const closest = findClosestWallPoint(planPoint, walls, {
|
|
110
|
+
canUseWall: (wall) => !isCurvedWall(wall),
|
|
111
|
+
})
|
|
112
|
+
if (closest) {
|
|
113
|
+
const dx = closest.wall.end[0] - closest.wall.start[0]
|
|
114
|
+
const dz = closest.wall.end[1] - closest.wall.start[1]
|
|
115
|
+
const length = Math.sqrt(dx * dx + dz * dz)
|
|
116
|
+
const distance = closest.t * length
|
|
117
|
+
|
|
118
|
+
emitter.emit('wall:click', {
|
|
119
|
+
node: closest.wall,
|
|
120
|
+
point: { x: closest.point[0], y: 0, z: closest.point[1] },
|
|
121
|
+
localPosition: [distance, floorplanOpeningLocalY, 0],
|
|
122
|
+
normal: closest.normal,
|
|
123
|
+
stopPropagation: () => {},
|
|
124
|
+
} as any)
|
|
125
|
+
}
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (isCeilingBuildActive) {
|
|
130
|
+
emitFloorplanGridEvent('click', planPoint, event)
|
|
131
|
+
|
|
132
|
+
const snappedPoint = snapPolygonDraftPoint({
|
|
133
|
+
point: planPoint,
|
|
134
|
+
start: ceilingDraftPoints[ceilingDraftPoints.length - 1],
|
|
135
|
+
angleSnap: ceilingDraftPoints.length > 0 && !shiftPressed,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
handleCeilingPlacementPoint(snappedPoint)
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (isRoofBuildActive) {
|
|
143
|
+
const snappedPoint = getSnappedFloorplanPoint(planPoint)
|
|
144
|
+
emitFloorplanGridEvent('click', snappedPoint, event)
|
|
145
|
+
setCursorPoint(snappedPoint)
|
|
146
|
+
|
|
147
|
+
if (roofDraftStart) {
|
|
148
|
+
clearRoofPlacementDraft()
|
|
149
|
+
} else {
|
|
150
|
+
setRoofDraftStart(snappedPoint)
|
|
151
|
+
setRoofDraftEnd(snappedPoint)
|
|
152
|
+
}
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (isFenceBuildActive) {
|
|
157
|
+
emitFloorplanGridEvent('click', planPoint, event)
|
|
158
|
+
|
|
159
|
+
const snappedPoint = snapFenceDraftPoint({
|
|
160
|
+
point: planPoint,
|
|
161
|
+
walls,
|
|
162
|
+
fences,
|
|
163
|
+
start: fenceDraftStart ?? undefined,
|
|
164
|
+
angleSnap: Boolean(fenceDraftStart) && !shiftPressed,
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
setCursorPoint(snappedPoint)
|
|
168
|
+
|
|
169
|
+
if (!fenceDraftStart) {
|
|
170
|
+
setFenceDraftStart(snappedPoint)
|
|
171
|
+
setFenceDraftEnd(snappedPoint)
|
|
172
|
+
} else if (
|
|
173
|
+
getPlanPointDistance(toPoint2D(fenceDraftStart), toPoint2D(snappedPoint)) >= 0.01
|
|
174
|
+
) {
|
|
175
|
+
clearFencePlacementDraft()
|
|
176
|
+
} else {
|
|
177
|
+
setFenceDraftEnd(snappedPoint)
|
|
178
|
+
}
|
|
179
|
+
return true
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (isFloorplanGridInteractionActive) {
|
|
183
|
+
const snappedPoint = emitFloorplanGridEvent('click', planPoint, event)
|
|
184
|
+
setCursorPoint(snappedPoint)
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (isPolygonBuildActive) {
|
|
189
|
+
const snappedPoint = snapPolygonDraftPoint({
|
|
190
|
+
point: planPoint,
|
|
191
|
+
start: activePolygonDraftPoints[activePolygonDraftPoints.length - 1],
|
|
192
|
+
angleSnap: activePolygonDraftPoints.length > 0 && !shiftPressed,
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
if (isZoneBuildActive) {
|
|
196
|
+
handleZonePlacementPoint(snappedPoint)
|
|
197
|
+
} else {
|
|
198
|
+
handleSlabPlacementPoint(snappedPoint)
|
|
199
|
+
}
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!isWallBuildActive) {
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const snappedPoint = snapWallDraftPoint({
|
|
208
|
+
point: planPoint,
|
|
209
|
+
walls,
|
|
210
|
+
start: draftStart ?? undefined,
|
|
211
|
+
angleSnap: Boolean(draftStart) && !shiftPressed,
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
handleWallPlacementPoint(snappedPoint)
|
|
215
|
+
return true
|
|
216
|
+
},
|
|
217
|
+
[
|
|
218
|
+
activePolygonDraftPoints,
|
|
219
|
+
ceilingDraftPoints,
|
|
220
|
+
clearFencePlacementDraft,
|
|
221
|
+
clearRoofPlacementDraft,
|
|
222
|
+
emitFloorplanGridEvent,
|
|
223
|
+
fenceDraftStart,
|
|
224
|
+
fences,
|
|
225
|
+
findClosestWallPoint,
|
|
226
|
+
floorplanOpeningLocalY,
|
|
227
|
+
getSnappedFloorplanPoint,
|
|
228
|
+
handleCeilingPlacementPoint,
|
|
229
|
+
handleSlabPlacementPoint,
|
|
230
|
+
handleZonePlacementPoint,
|
|
231
|
+
isCeilingBuildActive,
|
|
232
|
+
isFenceBuildActive,
|
|
233
|
+
isFloorplanGridInteractionActive,
|
|
234
|
+
isOpeningPlacementActive,
|
|
235
|
+
isPolygonBuildActive,
|
|
236
|
+
isRoofBuildActive,
|
|
237
|
+
isWallBuildActive,
|
|
238
|
+
isZoneBuildActive,
|
|
239
|
+
roofDraftStart,
|
|
240
|
+
setCursorPoint,
|
|
241
|
+
setFenceDraftEnd,
|
|
242
|
+
setFenceDraftStart,
|
|
243
|
+
setRoofDraftEnd,
|
|
244
|
+
setRoofDraftStart,
|
|
245
|
+
shiftPressed,
|
|
246
|
+
snapWallDraftPoint,
|
|
247
|
+
snapPolygonDraftPoint,
|
|
248
|
+
toPoint2D,
|
|
249
|
+
walls,
|
|
250
|
+
handleWallPlacementPoint,
|
|
251
|
+
],
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
handleBackgroundPlacementClick,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AnyNode,
|
|
5
|
+
CeilingNode,
|
|
6
|
+
DoorNode,
|
|
7
|
+
ItemNode,
|
|
8
|
+
Point2D,
|
|
9
|
+
RoofNode,
|
|
10
|
+
RoofSegmentNode,
|
|
11
|
+
SlabNode,
|
|
12
|
+
StairNode,
|
|
13
|
+
StairSegmentNode,
|
|
14
|
+
WallNode,
|
|
15
|
+
WindowNode,
|
|
16
|
+
} from '@pascal-app/core'
|
|
17
|
+
import { useCallback } from 'react'
|
|
18
|
+
import {
|
|
19
|
+
getFloorplanHitNodeId,
|
|
20
|
+
getFloorplanSelectionIdsInBounds,
|
|
21
|
+
} from '../../lib/floorplan/selection-tool'
|
|
22
|
+
import type { FloorplanSelectionBounds } from '../../lib/floorplan/types'
|
|
23
|
+
import type { WallPlanPoint } from '../tools/wall/wall-drafting'
|
|
24
|
+
|
|
25
|
+
type OpeningNode = WindowNode | DoorNode
|
|
26
|
+
|
|
27
|
+
type WallPolygonEntry = {
|
|
28
|
+
wall: WallNode
|
|
29
|
+
polygon: Point2D[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type OpeningPolygonEntry = {
|
|
33
|
+
opening: OpeningNode
|
|
34
|
+
polygon: Point2D[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type SlabPolygonEntry = {
|
|
38
|
+
slab: SlabNode
|
|
39
|
+
polygon: Point2D[]
|
|
40
|
+
holes: Point2D[][]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type CeilingPolygonEntry = {
|
|
44
|
+
ceiling: CeilingNode
|
|
45
|
+
polygon: Point2D[]
|
|
46
|
+
holes: Point2D[][]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type FloorplanRoofEntry = {
|
|
50
|
+
roof: RoofNode
|
|
51
|
+
segments: Array<{
|
|
52
|
+
polygon: Point2D[]
|
|
53
|
+
segment: RoofSegmentNode
|
|
54
|
+
}>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type FloorplanItemEntry = {
|
|
58
|
+
item: ItemNode
|
|
59
|
+
polygon: Point2D[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type FloorplanStairSegmentEntry = {
|
|
63
|
+
polygon: Point2D[]
|
|
64
|
+
segment: StairSegmentNode | AnyNode
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type FloorplanStairEntry = {
|
|
68
|
+
hitPolygons: Point2D[][]
|
|
69
|
+
stair: StairNode
|
|
70
|
+
segments: FloorplanStairSegmentEntry[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type UseFloorplanHitTestingArgs = {
|
|
74
|
+
ceilingPolygons: CeilingPolygonEntry[]
|
|
75
|
+
displaySlabPolygons: SlabPolygonEntry[]
|
|
76
|
+
displayWallPolygons: WallPolygonEntry[]
|
|
77
|
+
floorplanItemEntries: FloorplanItemEntry[]
|
|
78
|
+
floorplanOpeningHitTolerance: number
|
|
79
|
+
floorplanRoofEntries: FloorplanRoofEntry[]
|
|
80
|
+
floorplanStairEntries: FloorplanStairEntry[]
|
|
81
|
+
floorplanWallHitTolerance: number
|
|
82
|
+
getOpeningCenterLine: (polygon: Point2D[]) => { start: Point2D; end: Point2D } | null
|
|
83
|
+
isFloorplanItemContextActive: boolean
|
|
84
|
+
openingsPolygons: OpeningPolygonEntry[]
|
|
85
|
+
phase: 'site' | 'structure' | 'furnish'
|
|
86
|
+
toPoint2D: (point: WallPlanPoint) => Point2D
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function useFloorplanHitTesting({
|
|
90
|
+
ceilingPolygons,
|
|
91
|
+
displaySlabPolygons,
|
|
92
|
+
displayWallPolygons,
|
|
93
|
+
floorplanItemEntries,
|
|
94
|
+
floorplanOpeningHitTolerance,
|
|
95
|
+
floorplanRoofEntries,
|
|
96
|
+
floorplanStairEntries,
|
|
97
|
+
floorplanWallHitTolerance,
|
|
98
|
+
getOpeningCenterLine,
|
|
99
|
+
isFloorplanItemContextActive,
|
|
100
|
+
openingsPolygons,
|
|
101
|
+
phase,
|
|
102
|
+
toPoint2D,
|
|
103
|
+
}: UseFloorplanHitTestingArgs) {
|
|
104
|
+
const getFloorplanHitIdAtPoint = useCallback(
|
|
105
|
+
(planPoint: WallPlanPoint) => {
|
|
106
|
+
const point = toPoint2D(planPoint)
|
|
107
|
+
return getFloorplanHitNodeId({
|
|
108
|
+
point,
|
|
109
|
+
ceilings: ceilingPolygons,
|
|
110
|
+
phase,
|
|
111
|
+
isItemContextActive: isFloorplanItemContextActive,
|
|
112
|
+
items: floorplanItemEntries,
|
|
113
|
+
openings: openingsPolygons,
|
|
114
|
+
roofs: floorplanRoofEntries,
|
|
115
|
+
stairs: floorplanStairEntries,
|
|
116
|
+
walls: displayWallPolygons,
|
|
117
|
+
slabs: displaySlabPolygons,
|
|
118
|
+
openingHitTolerance: floorplanOpeningHitTolerance,
|
|
119
|
+
wallHitTolerance: floorplanWallHitTolerance,
|
|
120
|
+
getOpeningCenterLine,
|
|
121
|
+
})
|
|
122
|
+
},
|
|
123
|
+
[
|
|
124
|
+
ceilingPolygons,
|
|
125
|
+
displaySlabPolygons,
|
|
126
|
+
displayWallPolygons,
|
|
127
|
+
floorplanItemEntries,
|
|
128
|
+
floorplanOpeningHitTolerance,
|
|
129
|
+
floorplanRoofEntries,
|
|
130
|
+
floorplanStairEntries,
|
|
131
|
+
floorplanWallHitTolerance,
|
|
132
|
+
getOpeningCenterLine,
|
|
133
|
+
isFloorplanItemContextActive,
|
|
134
|
+
openingsPolygons,
|
|
135
|
+
phase,
|
|
136
|
+
toPoint2D,
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
const getFloorplanSelectionIdsInBoundsForArea = useCallback(
|
|
141
|
+
(bounds: FloorplanSelectionBounds) =>
|
|
142
|
+
getFloorplanSelectionIdsInBounds({
|
|
143
|
+
bounds,
|
|
144
|
+
ceilings: ceilingPolygons,
|
|
145
|
+
phase,
|
|
146
|
+
isItemContextActive: isFloorplanItemContextActive,
|
|
147
|
+
items: floorplanItemEntries,
|
|
148
|
+
walls: displayWallPolygons,
|
|
149
|
+
openings: openingsPolygons,
|
|
150
|
+
roofs: floorplanRoofEntries,
|
|
151
|
+
slabs: displaySlabPolygons,
|
|
152
|
+
stairs: floorplanStairEntries,
|
|
153
|
+
}),
|
|
154
|
+
[
|
|
155
|
+
ceilingPolygons,
|
|
156
|
+
displaySlabPolygons,
|
|
157
|
+
displayWallPolygons,
|
|
158
|
+
floorplanItemEntries,
|
|
159
|
+
floorplanRoofEntries,
|
|
160
|
+
floorplanStairEntries,
|
|
161
|
+
isFloorplanItemContextActive,
|
|
162
|
+
openingsPolygons,
|
|
163
|
+
phase,
|
|
164
|
+
],
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
getFloorplanHitIdAtPoint,
|
|
169
|
+
getFloorplanSelectionIdsInBounds: getFloorplanSelectionIdsInBoundsForArea,
|
|
170
|
+
}
|
|
171
|
+
}
|