@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
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyNode,
|
|
3
|
+
type AnyNodeId,
|
|
4
|
+
getScaledDimensions,
|
|
5
|
+
type ItemNode,
|
|
6
|
+
type LevelNode,
|
|
7
|
+
useLiveTransforms,
|
|
8
|
+
} from '@pascal-app/core'
|
|
9
|
+
import { getRotatedRectanglePolygon, rotatePlanVector } from './geometry'
|
|
10
|
+
import type { FloorplanItemEntry, FloorplanNodeTransform, LevelDescendantMap } from './types'
|
|
11
|
+
|
|
12
|
+
export function collectLevelDescendants(
|
|
13
|
+
levelNode: LevelNode,
|
|
14
|
+
nodes: Record<string, AnyNode>,
|
|
15
|
+
): AnyNode[] {
|
|
16
|
+
const descendants: AnyNode[] = []
|
|
17
|
+
const stack = [...levelNode.children].reverse() as AnyNodeId[]
|
|
18
|
+
|
|
19
|
+
while (stack.length > 0) {
|
|
20
|
+
const nodeId = stack.pop()
|
|
21
|
+
if (!nodeId) {
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const node = nodes[nodeId]
|
|
26
|
+
if (!node) {
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
descendants.push(node)
|
|
31
|
+
|
|
32
|
+
if ('children' in node && Array.isArray(node.children) && node.children.length > 0) {
|
|
33
|
+
for (let index = node.children.length - 1; index >= 0; index -= 1) {
|
|
34
|
+
stack.push(node.children[index] as AnyNodeId)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return descendants
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getItemFloorplanTransform(
|
|
43
|
+
item: ItemNode,
|
|
44
|
+
nodeById: LevelDescendantMap,
|
|
45
|
+
cache: Map<string, FloorplanNodeTransform | null>,
|
|
46
|
+
): FloorplanNodeTransform | null {
|
|
47
|
+
const cached = cache.get(item.id)
|
|
48
|
+
if (cached !== undefined) {
|
|
49
|
+
return cached
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const localRotation = item.rotation[1] ?? 0
|
|
53
|
+
let result: FloorplanNodeTransform | null = null
|
|
54
|
+
const itemMetadata =
|
|
55
|
+
typeof item.metadata === 'object' && item.metadata !== null && !Array.isArray(item.metadata)
|
|
56
|
+
? (item.metadata as Record<string, unknown>)
|
|
57
|
+
: null
|
|
58
|
+
|
|
59
|
+
if (itemMetadata?.isTransient === true) {
|
|
60
|
+
const live = useLiveTransforms.getState().get(item.id)
|
|
61
|
+
if (live) {
|
|
62
|
+
result = {
|
|
63
|
+
position: {
|
|
64
|
+
x: live.position[0],
|
|
65
|
+
y: live.position[2],
|
|
66
|
+
},
|
|
67
|
+
rotation: live.rotation,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
cache.set(item.id, result)
|
|
71
|
+
return result
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (item.parentId) {
|
|
76
|
+
const parentNode = nodeById.get(item.parentId as AnyNodeId)
|
|
77
|
+
|
|
78
|
+
if (parentNode?.type === 'wall') {
|
|
79
|
+
const wallRotation = -Math.atan2(
|
|
80
|
+
parentNode.end[1] - parentNode.start[1],
|
|
81
|
+
parentNode.end[0] - parentNode.start[0],
|
|
82
|
+
)
|
|
83
|
+
const wallLocalZ =
|
|
84
|
+
item.asset.attachTo === 'wall-side'
|
|
85
|
+
? ((parentNode.thickness ?? 0.1) / 2) * (item.side === 'back' ? -1 : 1)
|
|
86
|
+
: item.position[2]
|
|
87
|
+
const [offsetX, offsetY] = rotatePlanVector(item.position[0], wallLocalZ, wallRotation)
|
|
88
|
+
|
|
89
|
+
result = {
|
|
90
|
+
position: {
|
|
91
|
+
x: parentNode.start[0] + offsetX,
|
|
92
|
+
y: parentNode.start[1] + offsetY,
|
|
93
|
+
},
|
|
94
|
+
rotation: wallRotation + localRotation,
|
|
95
|
+
}
|
|
96
|
+
} else if (parentNode?.type === 'item') {
|
|
97
|
+
const parentTransform = getItemFloorplanTransform(parentNode, nodeById, cache)
|
|
98
|
+
if (parentTransform) {
|
|
99
|
+
const [offsetX, offsetY] = rotatePlanVector(
|
|
100
|
+
item.position[0],
|
|
101
|
+
item.position[2],
|
|
102
|
+
parentTransform.rotation,
|
|
103
|
+
)
|
|
104
|
+
result = {
|
|
105
|
+
position: {
|
|
106
|
+
x: parentTransform.position.x + offsetX,
|
|
107
|
+
y: parentTransform.position.y + offsetY,
|
|
108
|
+
},
|
|
109
|
+
rotation: parentTransform.rotation + localRotation,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
result = {
|
|
114
|
+
position: { x: item.position[0], y: item.position[2] },
|
|
115
|
+
rotation: localRotation,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
result = {
|
|
120
|
+
position: { x: item.position[0], y: item.position[2] },
|
|
121
|
+
rotation: localRotation,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
cache.set(item.id, result)
|
|
126
|
+
return result
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function buildFloorplanItemEntry(
|
|
130
|
+
item: ItemNode,
|
|
131
|
+
nodeById: LevelDescendantMap,
|
|
132
|
+
cache: Map<string, FloorplanNodeTransform | null>,
|
|
133
|
+
): FloorplanItemEntry | null {
|
|
134
|
+
const transform = getItemFloorplanTransform(item, nodeById, cache)
|
|
135
|
+
if (!transform) {
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Polygon is derived purely from `dimensions` — the same source of truth the
|
|
140
|
+
// editor uses for placement / collision. Previously we ran a per-frame
|
|
141
|
+
// convex-hull / minimum-area-rect pass over the loaded mesh's vertices to
|
|
142
|
+
// produce a tighter polygon, but that's expensive and disagrees with what
|
|
143
|
+
// the user sees on the 3D side (their dimensions are intentionally the
|
|
144
|
+
// bounding box, sometimes hand-tuned).
|
|
145
|
+
const dimensionPolygon = getItemDimensionPolygon(item, transform)
|
|
146
|
+
const [width, , depth] = getScaledDimensions(item)
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
dimensionPolygon,
|
|
150
|
+
item,
|
|
151
|
+
polygon: dimensionPolygon,
|
|
152
|
+
usesRealMesh: false,
|
|
153
|
+
center: transform.position,
|
|
154
|
+
rotation: transform.rotation,
|
|
155
|
+
width,
|
|
156
|
+
depth,
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
type Point = {
|
|
161
|
+
x: number
|
|
162
|
+
y: number
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getItemDimensionPolygon(item: ItemNode, transform: FloorplanNodeTransform): Point[] {
|
|
166
|
+
const [width, , depth] = getScaledDimensions(item)
|
|
167
|
+
const centerLocalZ = item.asset.attachTo === 'wall-side' ? -depth / 2 : 0
|
|
168
|
+
const [offsetX, offsetY] = rotatePlanVector(0, centerLocalZ, transform.rotation)
|
|
169
|
+
|
|
170
|
+
return getRotatedRectanglePolygon(
|
|
171
|
+
{
|
|
172
|
+
x: transform.position.x + offsetX,
|
|
173
|
+
y: transform.position.y + offsetY,
|
|
174
|
+
},
|
|
175
|
+
width,
|
|
176
|
+
depth,
|
|
177
|
+
transform.rotation,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CeilingNode,
|
|
3
|
+
DoorNode,
|
|
4
|
+
ItemNode,
|
|
5
|
+
Point2D,
|
|
6
|
+
RoofNode,
|
|
7
|
+
RoofSegmentNode,
|
|
8
|
+
SlabNode,
|
|
9
|
+
StairNode,
|
|
10
|
+
WallNode,
|
|
11
|
+
WindowNode,
|
|
12
|
+
} from '@pascal-app/core'
|
|
13
|
+
import {
|
|
14
|
+
doesPolygonIntersectSelectionBounds,
|
|
15
|
+
getDistanceToWallSegment,
|
|
16
|
+
isPointInsidePolygon,
|
|
17
|
+
isPointInsidePolygonWithHoles,
|
|
18
|
+
} from './geometry'
|
|
19
|
+
import type { FloorplanSelectionBounds } from './types'
|
|
20
|
+
|
|
21
|
+
type OpeningNode = WindowNode | DoorNode
|
|
22
|
+
|
|
23
|
+
type OpeningPolygonEntry = {
|
|
24
|
+
opening: OpeningNode
|
|
25
|
+
polygon: Point2D[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ItemEntry = {
|
|
29
|
+
item: ItemNode
|
|
30
|
+
polygon: Point2D[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type StairEntry = {
|
|
34
|
+
hitPolygons: Point2D[][]
|
|
35
|
+
stair: StairNode
|
|
36
|
+
segments: Array<{ polygon: Point2D[] }>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type WallEntry = {
|
|
40
|
+
wall: WallNode
|
|
41
|
+
polygon: Point2D[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type SlabEntry = {
|
|
45
|
+
slab: SlabNode
|
|
46
|
+
polygon: Point2D[]
|
|
47
|
+
holes: Point2D[][]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type CeilingEntry = {
|
|
51
|
+
ceiling: CeilingNode
|
|
52
|
+
polygon: Point2D[]
|
|
53
|
+
holes: Point2D[][]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type RoofEntry = {
|
|
57
|
+
roof: RoofNode
|
|
58
|
+
segments: Array<{
|
|
59
|
+
polygon: Point2D[]
|
|
60
|
+
segment: RoofSegmentNode
|
|
61
|
+
}>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type FloorplanSelectionToolContext = {
|
|
65
|
+
point: Point2D
|
|
66
|
+
phase: 'site' | 'structure' | 'furnish'
|
|
67
|
+
isItemContextActive: boolean
|
|
68
|
+
items: ItemEntry[]
|
|
69
|
+
openings: OpeningPolygonEntry[]
|
|
70
|
+
stairs: StairEntry[]
|
|
71
|
+
walls: WallEntry[]
|
|
72
|
+
slabs: SlabEntry[]
|
|
73
|
+
ceilings: CeilingEntry[]
|
|
74
|
+
roofs: RoofEntry[]
|
|
75
|
+
openingHitTolerance: number
|
|
76
|
+
wallHitTolerance: number
|
|
77
|
+
getOpeningCenterLine: (polygon: Point2D[]) => { start: Point2D; end: Point2D } | null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getItemHitId(context: FloorplanSelectionToolContext) {
|
|
81
|
+
if (!context.isItemContextActive) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const itemHit = context.items.find(({ polygon }) => isPointInsidePolygon(context.point, polygon))
|
|
86
|
+
return itemHit?.item.id ?? null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getStairHitPolygons(stair: StairEntry) {
|
|
90
|
+
return stair.hitPolygons.length > 0
|
|
91
|
+
? stair.hitPolygons
|
|
92
|
+
: stair.segments.map(({ polygon }) => polygon)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getFloorplanHitNodeId(context: FloorplanSelectionToolContext) {
|
|
96
|
+
if (context.phase === 'structure') {
|
|
97
|
+
const openingHit = context.openings.find(({ polygon }) => {
|
|
98
|
+
if (isPointInsidePolygon(context.point, polygon)) {
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const centerLine = context.getOpeningCenterLine(polygon)
|
|
103
|
+
if (!centerLine) {
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
getDistanceToWallSegment(
|
|
109
|
+
context.point,
|
|
110
|
+
[centerLine.start.x, centerLine.start.y],
|
|
111
|
+
[centerLine.end.x, centerLine.end.y],
|
|
112
|
+
) <= context.openingHitTolerance
|
|
113
|
+
)
|
|
114
|
+
})
|
|
115
|
+
if (openingHit) {
|
|
116
|
+
return openingHit.opening.id
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const stairHit = context.stairs.find((stair) =>
|
|
120
|
+
getStairHitPolygons(stair).some((polygon) => isPointInsidePolygon(context.point, polygon)),
|
|
121
|
+
)
|
|
122
|
+
if (stairHit) {
|
|
123
|
+
return stairHit.stair.id
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const wallHit = context.walls.find(
|
|
127
|
+
({ wall, polygon }) =>
|
|
128
|
+
isPointInsidePolygon(context.point, polygon) ||
|
|
129
|
+
getDistanceToWallSegment(context.point, wall.start, wall.end) <= context.wallHitTolerance,
|
|
130
|
+
)
|
|
131
|
+
if (wallHit) {
|
|
132
|
+
return wallHit.wall.id
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const roofHit = context.roofs.find(({ segments }) =>
|
|
136
|
+
segments.some(({ polygon }) => isPointInsidePolygon(context.point, polygon)),
|
|
137
|
+
)
|
|
138
|
+
if (roofHit) {
|
|
139
|
+
return roofHit.roof.id
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const ceilingHit = context.ceilings.find(({ polygon, holes }) =>
|
|
143
|
+
isPointInsidePolygonWithHoles(context.point, polygon, holes),
|
|
144
|
+
)
|
|
145
|
+
if (ceilingHit) {
|
|
146
|
+
return ceilingHit.ceiling.id
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const slabHit = context.slabs.find(({ polygon, holes }) =>
|
|
150
|
+
isPointInsidePolygonWithHoles(context.point, polygon, holes),
|
|
151
|
+
)
|
|
152
|
+
if (slabHit) {
|
|
153
|
+
return slabHit.slab.id
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return getItemHitId(context)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
type FloorplanSelectionBoundsContext = {
|
|
161
|
+
bounds: FloorplanSelectionBounds
|
|
162
|
+
phase: 'site' | 'structure' | 'furnish'
|
|
163
|
+
isItemContextActive: boolean
|
|
164
|
+
items: ItemEntry[]
|
|
165
|
+
walls: WallEntry[]
|
|
166
|
+
openings: OpeningPolygonEntry[]
|
|
167
|
+
slabs: SlabEntry[]
|
|
168
|
+
ceilings: CeilingEntry[]
|
|
169
|
+
stairs: StairEntry[]
|
|
170
|
+
roofs: RoofEntry[]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function getFloorplanSelectionIdsInBounds({
|
|
174
|
+
bounds,
|
|
175
|
+
phase,
|
|
176
|
+
isItemContextActive,
|
|
177
|
+
items,
|
|
178
|
+
walls,
|
|
179
|
+
openings,
|
|
180
|
+
slabs,
|
|
181
|
+
ceilings,
|
|
182
|
+
stairs,
|
|
183
|
+
roofs,
|
|
184
|
+
}: FloorplanSelectionBoundsContext) {
|
|
185
|
+
const itemIds = isItemContextActive
|
|
186
|
+
? items
|
|
187
|
+
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
|
188
|
+
.map(({ item }) => item.id)
|
|
189
|
+
: []
|
|
190
|
+
|
|
191
|
+
if (phase !== 'structure') {
|
|
192
|
+
return itemIds
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const wallIds = walls
|
|
196
|
+
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
|
197
|
+
.map(({ wall }) => wall.id)
|
|
198
|
+
const openingIds = openings
|
|
199
|
+
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
|
200
|
+
.map(({ opening }) => opening.id)
|
|
201
|
+
const slabIds = slabs
|
|
202
|
+
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
|
203
|
+
.map(({ slab }) => slab.id)
|
|
204
|
+
const ceilingIds = ceilings
|
|
205
|
+
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
|
206
|
+
.map(({ ceiling }) => ceiling.id)
|
|
207
|
+
const stairIds = stairs
|
|
208
|
+
.filter((stair) =>
|
|
209
|
+
getStairHitPolygons(stair).some((polygon) =>
|
|
210
|
+
doesPolygonIntersectSelectionBounds(polygon, bounds),
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
.map(({ stair }) => stair.id)
|
|
214
|
+
const roofIds = roofs
|
|
215
|
+
.filter(({ segments }) =>
|
|
216
|
+
segments.some(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds)),
|
|
217
|
+
)
|
|
218
|
+
.map(({ roof }) => roof.id)
|
|
219
|
+
|
|
220
|
+
return Array.from(
|
|
221
|
+
new Set([
|
|
222
|
+
...itemIds,
|
|
223
|
+
...wallIds,
|
|
224
|
+
...openingIds,
|
|
225
|
+
...slabIds,
|
|
226
|
+
...ceilingIds,
|
|
227
|
+
...stairIds,
|
|
228
|
+
...roofIds,
|
|
229
|
+
]),
|
|
230
|
+
)
|
|
231
|
+
}
|