@pascal-app/editor 0.9.2 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -7
- package/src/components/editor/bake-exporter.tsx +3 -1
- package/src/components/editor/bake-thumbnail.tsx +95 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
- package/src/components/editor/custom-camera-controls.tsx +307 -212
- package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
- package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +66 -2
- package/src/components/editor/first-person/build-collider-world.ts +20 -0
- package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
- package/src/components/editor/first-person/terrain-collider.ts +212 -0
- package/src/components/editor/first-person-controls.tsx +295 -132
- package/src/components/editor/floating-action-menu.tsx +59 -65
- package/src/components/editor/floorplan-background-selection.test.ts +16 -2
- package/src/components/editor/floorplan-background-selection.ts +4 -18
- package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
- package/src/components/editor/floorplan-camera-sync.ts +213 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
- package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
- package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
- package/src/components/editor/floorplan-panel.tsx +931 -861
- package/src/components/editor/group-actions.ts +155 -15
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-rotate-handle.tsx +1 -1
- package/src/components/editor/group-selection-box-3d.tsx +1 -1
- package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
- package/src/components/editor/handles/handle-drag-history.ts +14 -0
- package/src/components/editor/handles/preview-overrides.test.ts +26 -0
- package/src/components/editor/handles/preview-overrides.ts +13 -0
- package/src/components/editor/handles/resize-snap.test.ts +52 -0
- package/src/components/editor/handles/resize-snap.ts +23 -0
- package/src/components/editor/handles/use-handle-drag.ts +17 -7
- package/src/components/editor/index.tsx +42 -9
- package/src/components/editor/measurement-pill.tsx +6 -1
- package/src/components/editor/node-arrow-handles.tsx +51 -15
- package/src/components/editor/quick-measurement-card.tsx +9 -3
- package/src/components/editor/quick-measurement-hud.tsx +7 -1
- package/src/components/editor/selection-manager.tsx +3 -1
- package/src/components/editor/site-edge-labels.tsx +24 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -1
- package/src/components/editor/thumbnail-generator.tsx +66 -294
- package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
- package/src/components/editor/use-mesh-settle-epoch.ts +1 -0
- package/src/components/editor/wall-measurement-label.tsx +8 -16
- package/src/components/editor/wall-move-side-handles.tsx +193 -18
- package/src/components/editor/wall-opening-highlights.tsx +63 -32
- package/src/components/editor/wall-snap-beacon-layer.tsx +6 -4
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +39 -8
- package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +47 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +21 -5
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +93 -59
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +27 -0
- package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +75 -1
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +92 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +93 -28
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +279 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +621 -281
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +12 -7
- package/src/components/tools/fence/fence-drafting.ts +27 -0
- package/src/components/tools/item/use-draft-node.ts +131 -72
- package/src/components/tools/item/use-placement-coordinator.tsx +52 -4
- package/src/components/tools/registry/move-registry-node-tool.tsx +75 -4
- package/src/components/tools/roof/roof-tool.tsx +16 -0
- package/src/components/tools/shared/floor-stack-preview.ts +4 -0
- package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
- package/src/components/tools/shared/pointer-support-cap.ts +273 -0
- package/src/components/tools/site/site-boundary-editor.tsx +13 -6
- package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
- package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
- package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
- package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
- package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
- package/src/components/tools/stair/stair-click-guard.ts +72 -0
- package/src/components/tools/stair/stair-tool.tsx +98 -47
- package/src/components/tools/tool-manager.tsx +67 -9
- package/src/components/tools/wall/wall-drafting.test.ts +118 -1
- package/src/components/tools/wall/wall-drafting.ts +128 -9
- package/src/components/tools/wall/wall-snap-geometry.test.ts +8 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +70 -3
- package/src/components/tools/zone/zone-tool.tsx +16 -0
- package/src/components/ui/action-menu/control-modes.tsx +4 -1
- package/src/components/ui/action-menu/measurement-control.tsx +107 -28
- package/src/components/ui/command-palette/editor-commands.tsx +14 -1
- package/src/components/ui/controls/material-paint-panel.tsx +7 -1
- package/src/components/ui/controls/material-picker.tsx +86 -7
- package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
- package/src/components/ui/floating-level-selector.tsx +73 -19
- package/src/components/ui/helpers/helper-manager.tsx +46 -0
- package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
- package/src/components/ui/panels/panel-manager.tsx +14 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -2
- package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
- package/src/components/ui/panels/selection-breakdown.ts +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +14 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +16 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +2 -0
- package/src/components/viewer/viewer-controls-bar.tsx +466 -0
- package/src/components/viewer/viewer-scene-header.tsx +235 -0
- package/src/components/viewer-overlay.tsx +20 -655
- package/src/components/walkthrough-hud.tsx +111 -0
- package/src/hooks/use-grid-events.ts +24 -0
- package/src/hooks/use-keyboard.ts +53 -30
- package/src/index.tsx +137 -4
- package/src/lib/active-placement-surface.test.ts +24 -0
- package/src/lib/active-placement-surface.ts +8 -1
- package/src/lib/camera-pose.test.ts +237 -0
- package/src/lib/camera-pose.ts +172 -0
- package/src/lib/direct-manipulation.test.ts +83 -0
- package/src/lib/direct-manipulation.ts +9 -0
- package/src/lib/door-interaction.ts +1 -1
- package/src/lib/elevation-guides.test.ts +107 -0
- package/src/lib/elevation-guides.ts +236 -0
- package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
- package/src/lib/floorplan/annotation-visibility.ts +133 -0
- package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
- package/src/lib/floorplan/drawing-coordination.ts +13 -0
- package/src/lib/floorplan/floorplan-export.test.ts +319 -0
- package/src/lib/floorplan/floorplan-export.tsx +745 -131
- package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
- package/src/lib/floorplan/floorplan-extension.ts +181 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
- package/src/lib/floorplan/floorplan-mode.ts +73 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
- package/src/lib/floorplan/geometry.ts +2 -3
- package/src/lib/fresh-planar-placement.test.ts +44 -1
- package/src/lib/fresh-planar-placement.ts +21 -4
- package/src/lib/glb-export.test.ts +116 -1
- package/src/lib/glb-export.ts +204 -7
- package/src/lib/ground-surface.ts +95 -0
- package/src/lib/history.test.ts +96 -0
- package/src/lib/history.ts +22 -0
- package/src/lib/interaction/overlay-policy.test.ts +2 -1
- package/src/lib/interaction/scope.ts +40 -8
- package/src/lib/level-duplication.test.ts +2 -1
- package/src/lib/measurements.test.ts +5 -0
- package/src/lib/measurements.ts +12 -1
- package/src/lib/quick-action-feedback.ts +33 -0
- package/src/lib/quick-action-nodes.test.ts +94 -0
- package/src/lib/quick-action-nodes.ts +47 -0
- package/src/lib/scene-clipboard.test.ts +130 -1
- package/src/lib/scene-clipboard.ts +237 -13
- package/src/lib/selection-routing.test.ts +16 -1
- package/src/lib/selection-routing.ts +5 -0
- package/src/lib/sfx-bus.ts +33 -2
- package/src/lib/sfx-player.test.ts +148 -6
- package/src/lib/sfx-player.ts +156 -8
- package/src/lib/site-boundary.test.ts +53 -0
- package/src/lib/site-boundary.ts +27 -0
- package/src/lib/snapping-mode.test.ts +29 -2
- package/src/lib/snapping-mode.ts +15 -3
- package/src/lib/stair-duplication.test.ts +103 -0
- package/src/lib/stair-duplication.ts +57 -85
- package/src/lib/stair-levels.ts +2 -0
- package/src/lib/terrain-sculpt.test.ts +429 -0
- package/src/lib/terrain-sculpt.ts +301 -0
- package/src/lib/terrain-verb-color.test.ts +51 -0
- package/src/lib/terrain-verb-color.ts +58 -0
- package/src/lib/touch-gesture-priority.test.ts +45 -0
- package/src/lib/touch-gesture-priority.ts +30 -0
- package/src/lib/window-interaction.ts +1 -1
- package/src/store/camera-pose-store.test.ts +29 -0
- package/src/store/camera-pose-store.ts +27 -0
- package/src/store/live-draft-preview-stores.test.ts +136 -0
- package/src/store/navigation-sync-pose-store.test.ts +34 -0
- package/src/store/navigation-sync-pose-store.ts +27 -0
- package/src/store/terrain-sculpt-mode.test.ts +223 -0
- package/src/store/use-delete-confirmation.ts +27 -0
- package/src/store/use-drawing-view.test.ts +23 -0
- package/src/store/use-drawing-view.ts +84 -0
- package/src/store/use-editor.tsx +184 -15
- package/src/store/use-elevation-guides.ts +40 -0
- package/src/store/use-fence-curve-draft.ts +38 -8
- package/src/store/use-first-person-hud.ts +40 -0
- package/src/store/use-floorplan-annotation-visibility.ts +60 -0
- package/src/store/use-floorplan-draft-preview.ts +54 -4
- package/src/store/use-floorplan-mode.ts +110 -0
- package/src/store/use-interaction-scope.test.ts +23 -3
- package/src/store/use-interaction-scope.ts +8 -0
- package/src/store/use-measurement-draft.test.ts +29 -0
- package/src/store/use-measurement-draft.ts +33 -6
- package/src/store/use-path-draft-preview.ts +90 -0
- package/src/store/use-stair-build-preview.ts +12 -2
- package/src/lib/floorplan/selection-tool.ts +0 -271
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyNode,
|
|
3
|
+
type AnyNodeId,
|
|
4
|
+
getWallBaseElevationForNodes,
|
|
5
|
+
getWallEffectiveHeightForNodes,
|
|
6
|
+
resolveCeilingHeight,
|
|
7
|
+
} from '@pascal-app/core'
|
|
8
|
+
import useElevationGuides from '../store/use-elevation-guides'
|
|
9
|
+
|
|
10
|
+
export const ELEVATION_ALIGNMENT_THRESHOLD_M = 0.08
|
|
11
|
+
const GUIDE_MATCH_EPSILON_M = 1e-4
|
|
12
|
+
|
|
13
|
+
export type ElevationGuideSource = {
|
|
14
|
+
nodeId: string
|
|
15
|
+
levelId: string | null | undefined
|
|
16
|
+
anchor: readonly [number, number]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ElevationSnapTarget = {
|
|
20
|
+
id: string
|
|
21
|
+
elevation: number
|
|
22
|
+
anchor: readonly [number, number]
|
|
23
|
+
label: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ElevationSnapMatch = {
|
|
27
|
+
target: ElevationSnapTarget
|
|
28
|
+
elevation: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function polygonCenter(polygon: ReadonlyArray<readonly [number, number]>): [number, number] {
|
|
32
|
+
if (polygon.length === 0) return [0, 0]
|
|
33
|
+
let x = 0
|
|
34
|
+
let z = 0
|
|
35
|
+
for (const point of polygon) {
|
|
36
|
+
x += point[0]
|
|
37
|
+
z += point[1]
|
|
38
|
+
}
|
|
39
|
+
return [x / polygon.length, z / polygon.length]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function segmentCenter(
|
|
43
|
+
start: readonly [number, number],
|
|
44
|
+
end: readonly [number, number],
|
|
45
|
+
): [number, number] {
|
|
46
|
+
return [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function fenceBaseElevation(node: AnyNode, nodes: Record<string, AnyNode>): number {
|
|
50
|
+
if (node.type !== 'fence') return 0
|
|
51
|
+
const host = node.supportSlabId ? nodes[node.supportSlabId as AnyNodeId] : undefined
|
|
52
|
+
const hostElevation =
|
|
53
|
+
host?.type === 'slab' && (host.parentId ?? null) === (node.parentId ?? null)
|
|
54
|
+
? (host.elevation ?? 0)
|
|
55
|
+
: 0
|
|
56
|
+
return hostElevation + (node.supportOffset ?? 0)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Structural Y datums on the source node's level. This is editor-runtime
|
|
61
|
+
* collection over authoritative vertical resolvers; matching remains a pure
|
|
62
|
+
* scalar operation in {@link resolveElevationSnapMatch}.
|
|
63
|
+
*/
|
|
64
|
+
export function collectElevationSnapTargets(
|
|
65
|
+
source: ElevationGuideSource,
|
|
66
|
+
nodes: Record<string, AnyNode>,
|
|
67
|
+
): ElevationSnapTarget[] {
|
|
68
|
+
if (!source.levelId) return []
|
|
69
|
+
|
|
70
|
+
const targets: ElevationSnapTarget[] = [
|
|
71
|
+
{
|
|
72
|
+
id: `${source.levelId}:level`,
|
|
73
|
+
elevation: 0,
|
|
74
|
+
anchor: source.anchor,
|
|
75
|
+
label: 'Level',
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
const level = nodes[source.levelId as AnyNodeId]
|
|
79
|
+
if (level?.type !== 'level') return targets
|
|
80
|
+
|
|
81
|
+
for (const childId of level.children) {
|
|
82
|
+
if (childId === source.nodeId) continue
|
|
83
|
+
const node = nodes[childId as AnyNodeId]
|
|
84
|
+
if (!node) continue
|
|
85
|
+
|
|
86
|
+
if (node.type === 'slab') {
|
|
87
|
+
const center = polygonCenter(node.polygon)
|
|
88
|
+
const top = node.elevation ?? 0.05
|
|
89
|
+
targets.push({
|
|
90
|
+
id: `${node.id}:top`,
|
|
91
|
+
elevation: top,
|
|
92
|
+
anchor: center,
|
|
93
|
+
label: 'Slab top',
|
|
94
|
+
})
|
|
95
|
+
if (!node.recessed) {
|
|
96
|
+
targets.push({
|
|
97
|
+
id: `${node.id}:base`,
|
|
98
|
+
elevation: top - (node.thickness ?? 0.05),
|
|
99
|
+
anchor: center,
|
|
100
|
+
label: 'Slab underside',
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (node.type === 'ceiling') {
|
|
107
|
+
targets.push({
|
|
108
|
+
id: `${node.id}:ceiling`,
|
|
109
|
+
elevation: resolveCeilingHeight(node, nodes as Record<AnyNodeId, AnyNode>),
|
|
110
|
+
anchor: polygonCenter(node.polygon),
|
|
111
|
+
label: 'Ceiling',
|
|
112
|
+
})
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (node.type === 'wall') {
|
|
117
|
+
const base = getWallBaseElevationForNodes(node, nodes)
|
|
118
|
+
const center = segmentCenter(node.start, node.end)
|
|
119
|
+
targets.push({
|
|
120
|
+
id: `${node.id}:base`,
|
|
121
|
+
elevation: base,
|
|
122
|
+
anchor: center,
|
|
123
|
+
label: 'Wall base',
|
|
124
|
+
})
|
|
125
|
+
targets.push({
|
|
126
|
+
id: `${node.id}:top`,
|
|
127
|
+
elevation: base + getWallEffectiveHeightForNodes(node, nodes),
|
|
128
|
+
anchor: center,
|
|
129
|
+
label: 'Wall top',
|
|
130
|
+
})
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (node.type === 'fence') {
|
|
135
|
+
const base = fenceBaseElevation(node, nodes)
|
|
136
|
+
const center = segmentCenter(node.start, node.end)
|
|
137
|
+
targets.push({
|
|
138
|
+
id: `${node.id}:base`,
|
|
139
|
+
elevation: base,
|
|
140
|
+
anchor: center,
|
|
141
|
+
label: 'Fence base',
|
|
142
|
+
})
|
|
143
|
+
targets.push({
|
|
144
|
+
id: `${node.id}:top`,
|
|
145
|
+
elevation: base + (node.height ?? 1.8),
|
|
146
|
+
anchor: center,
|
|
147
|
+
label: 'Fence top',
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return targets
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function resolveElevationSnapMatch(
|
|
156
|
+
proposedElevation: number,
|
|
157
|
+
sourceAnchor: readonly [number, number],
|
|
158
|
+
targets: readonly ElevationSnapTarget[],
|
|
159
|
+
threshold = ELEVATION_ALIGNMENT_THRESHOLD_M,
|
|
160
|
+
): ElevationSnapMatch | null {
|
|
161
|
+
let best: ElevationSnapTarget | null = null
|
|
162
|
+
let bestDelta = Number.POSITIVE_INFINITY
|
|
163
|
+
let bestPlanDistance = Number.POSITIVE_INFINITY
|
|
164
|
+
|
|
165
|
+
for (const target of targets) {
|
|
166
|
+
const delta = Math.abs(target.elevation - proposedElevation)
|
|
167
|
+
if (delta > threshold) continue
|
|
168
|
+
const dx = target.anchor[0] - sourceAnchor[0]
|
|
169
|
+
const dz = target.anchor[1] - sourceAnchor[1]
|
|
170
|
+
const planDistance = dx * dx + dz * dz
|
|
171
|
+
if (
|
|
172
|
+
delta < bestDelta - 1e-9 ||
|
|
173
|
+
(Math.abs(delta - bestDelta) <= 1e-9 && planDistance < bestPlanDistance)
|
|
174
|
+
) {
|
|
175
|
+
best = target
|
|
176
|
+
bestDelta = delta
|
|
177
|
+
bestPlanDistance = planDistance
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return best ? { target: best, elevation: best.elevation } : null
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function resolveStructuralElevationSnap(
|
|
185
|
+
source: ElevationGuideSource,
|
|
186
|
+
proposedElevation: number,
|
|
187
|
+
nodes: Record<string, AnyNode>,
|
|
188
|
+
): number {
|
|
189
|
+
return (
|
|
190
|
+
resolveElevationSnapMatch(
|
|
191
|
+
proposedElevation,
|
|
192
|
+
source.anchor,
|
|
193
|
+
collectElevationSnapTargets(source, nodes),
|
|
194
|
+
)?.elevation ?? proposedElevation
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function publishStructuralElevationGuide(
|
|
199
|
+
source: ElevationGuideSource,
|
|
200
|
+
elevation: number,
|
|
201
|
+
nodes: Record<string, AnyNode>,
|
|
202
|
+
): void {
|
|
203
|
+
if (!source.levelId) {
|
|
204
|
+
clearStructuralElevationGuide(source.nodeId)
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const match = resolveElevationSnapMatch(
|
|
209
|
+
elevation,
|
|
210
|
+
source.anchor,
|
|
211
|
+
collectElevationSnapTargets(source, nodes),
|
|
212
|
+
GUIDE_MATCH_EPSILON_M,
|
|
213
|
+
)
|
|
214
|
+
if (!match) {
|
|
215
|
+
clearStructuralElevationGuide(source.nodeId)
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const dx = match.target.anchor[0] - source.anchor[0]
|
|
220
|
+
const dz = match.target.anchor[1] - source.anchor[1]
|
|
221
|
+
const length = Math.hypot(dx, dz)
|
|
222
|
+
const direction: [number, number] = length > 1e-6 ? [dx / length, dz / length] : [1, 0]
|
|
223
|
+
|
|
224
|
+
useElevationGuides.getState().publish({
|
|
225
|
+
ownerId: source.nodeId,
|
|
226
|
+
levelId: source.levelId,
|
|
227
|
+
center: source.anchor,
|
|
228
|
+
direction,
|
|
229
|
+
elevation: match.elevation,
|
|
230
|
+
label: match.target.label,
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function clearStructuralElevationGuide(ownerId: string): void {
|
|
235
|
+
useElevationGuides.getState().clear(ownerId)
|
|
236
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import type { FloorplanGeometry } from '@pascal-app/core'
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
5
|
+
filterFloorplanAnnotationGeometry,
|
|
6
|
+
normalizeFloorplanAnnotationVisibility,
|
|
7
|
+
} from './annotation-visibility'
|
|
8
|
+
import { floorplanGeometryMetadata, withFloorplanGeometryMetadata } from './floorplan-extension'
|
|
9
|
+
|
|
10
|
+
describe('floor-plan annotation visibility', () => {
|
|
11
|
+
test('fills missing persisted categories with visible defaults', () => {
|
|
12
|
+
expect(normalizeFloorplanAnnotationVisibility({ measurements: false })).toEqual({
|
|
13
|
+
...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
14
|
+
measurements: false,
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('removes automatic dimension primitives without removing plan geometry', () => {
|
|
19
|
+
const line = { kind: 'line', x1: 0, y1: 0, x2: 2, y2: 0 } satisfies FloorplanGeometry
|
|
20
|
+
const geometry = {
|
|
21
|
+
kind: 'group',
|
|
22
|
+
children: [
|
|
23
|
+
line,
|
|
24
|
+
{
|
|
25
|
+
kind: 'dimension-string',
|
|
26
|
+
segments: [{ start: [0, 0], end: [2, 0], text: '2.00m' }],
|
|
27
|
+
offsetNormal: [0, 1],
|
|
28
|
+
offsetDistance: 0.3,
|
|
29
|
+
extensionOvershoot: 0.1,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
} satisfies FloorplanGeometry
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
filterFloorplanAnnotationGeometry(geometry, {
|
|
36
|
+
...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
37
|
+
automaticDimensions: false,
|
|
38
|
+
}),
|
|
39
|
+
).toEqual({ kind: 'group', children: [line] })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('removes a complete curved automatic dimension group', () => {
|
|
43
|
+
const curvedDimension = {
|
|
44
|
+
kind: 'group',
|
|
45
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'automatic-dimension' }),
|
|
46
|
+
children: [
|
|
47
|
+
{ kind: 'line', x1: 0, y1: 0, x2: 2, y2: 2 },
|
|
48
|
+
{ kind: 'dimension-label', cx: 1, cy: 1, text: 'R 2m', angle: 0 },
|
|
49
|
+
],
|
|
50
|
+
} satisfies FloorplanGeometry
|
|
51
|
+
|
|
52
|
+
expect(
|
|
53
|
+
filterFloorplanAnnotationGeometry(curvedDimension, {
|
|
54
|
+
...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
55
|
+
automaticDimensions: false,
|
|
56
|
+
}),
|
|
57
|
+
).toBeNull()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('removes only the opening mark from door geometry', () => {
|
|
61
|
+
const body = {
|
|
62
|
+
kind: 'polygon',
|
|
63
|
+
points: [
|
|
64
|
+
[0, 0],
|
|
65
|
+
[1, 0],
|
|
66
|
+
[1, 0.1],
|
|
67
|
+
],
|
|
68
|
+
} satisfies FloorplanGeometry
|
|
69
|
+
const mark = {
|
|
70
|
+
kind: 'group',
|
|
71
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'opening-mark' }),
|
|
72
|
+
children: [
|
|
73
|
+
{ kind: 'line', x1: 0.5, y1: 0, x2: 0.5, y2: 0.5 },
|
|
74
|
+
{ kind: 'rect', x: 0.3, y: 0.5, width: 0.4, height: 0.3 },
|
|
75
|
+
{ kind: 'text', x: 0.5, y: 0.65, text: '101', fontSize: 0.15, upright: true },
|
|
76
|
+
],
|
|
77
|
+
} satisfies FloorplanGeometry
|
|
78
|
+
const geometry = { kind: 'group', children: [body, mark] } satisfies FloorplanGeometry
|
|
79
|
+
|
|
80
|
+
expect(
|
|
81
|
+
filterFloorplanAnnotationGeometry(geometry, {
|
|
82
|
+
...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
83
|
+
openingMarks: false,
|
|
84
|
+
}),
|
|
85
|
+
).toEqual({ kind: 'group', children: [body] })
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('hides manual dimensions and measurements independently', () => {
|
|
89
|
+
const manualDimension = {
|
|
90
|
+
kind: 'text',
|
|
91
|
+
x: 0,
|
|
92
|
+
y: 0,
|
|
93
|
+
text: 'Annotation',
|
|
94
|
+
fontSize: 0.15,
|
|
95
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'manual-dimension' }),
|
|
96
|
+
} satisfies FloorplanGeometry
|
|
97
|
+
const measurement = {
|
|
98
|
+
...manualDimension,
|
|
99
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'measurement' }),
|
|
100
|
+
} satisfies FloorplanGeometry
|
|
101
|
+
|
|
102
|
+
expect(
|
|
103
|
+
filterFloorplanAnnotationGeometry(manualDimension, {
|
|
104
|
+
...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
105
|
+
manualDimensions: false,
|
|
106
|
+
}),
|
|
107
|
+
).toBeNull()
|
|
108
|
+
expect(
|
|
109
|
+
filterFloorplanAnnotationGeometry(
|
|
110
|
+
{
|
|
111
|
+
kind: 'group',
|
|
112
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'manual-dimension' }),
|
|
113
|
+
children: [
|
|
114
|
+
{
|
|
115
|
+
kind: 'dimension',
|
|
116
|
+
start: [0, 0],
|
|
117
|
+
end: [1, 0],
|
|
118
|
+
offsetNormal: [0, 1],
|
|
119
|
+
offsetDistance: 0.5,
|
|
120
|
+
extensionOvershoot: 0.1,
|
|
121
|
+
text: '1m',
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
127
|
+
automaticDimensions: false,
|
|
128
|
+
},
|
|
129
|
+
),
|
|
130
|
+
).not.toBeNull()
|
|
131
|
+
expect(
|
|
132
|
+
filterFloorplanAnnotationGeometry(measurement, {
|
|
133
|
+
...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
134
|
+
measurements: false,
|
|
135
|
+
}),
|
|
136
|
+
).toBeNull()
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('keeps a contextual dimension when automatic dimensions are hidden', () => {
|
|
140
|
+
const contextualDimension = {
|
|
141
|
+
kind: 'group',
|
|
142
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'contextual-dimension' }),
|
|
143
|
+
children: [
|
|
144
|
+
{
|
|
145
|
+
kind: 'dimension',
|
|
146
|
+
start: [0, 0],
|
|
147
|
+
end: [2, 0],
|
|
148
|
+
offsetNormal: [0, 1],
|
|
149
|
+
offsetDistance: 0.3,
|
|
150
|
+
extensionOvershoot: 0.08,
|
|
151
|
+
text: '2m',
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
} satisfies FloorplanGeometry
|
|
155
|
+
|
|
156
|
+
expect(
|
|
157
|
+
filterFloorplanAnnotationGeometry(contextualDimension, {
|
|
158
|
+
...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
159
|
+
automaticDimensions: false,
|
|
160
|
+
contextualDimensions: true,
|
|
161
|
+
}),
|
|
162
|
+
).toEqual(contextualDimension)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('removes a nested automatic wall string beside a contextual dimension', () => {
|
|
166
|
+
const wallBody = {
|
|
167
|
+
kind: 'polygon',
|
|
168
|
+
points: [
|
|
169
|
+
[0, 0],
|
|
170
|
+
[0.2, 0],
|
|
171
|
+
[0.2, 6],
|
|
172
|
+
[0, 6],
|
|
173
|
+
],
|
|
174
|
+
} satisfies FloorplanGeometry
|
|
175
|
+
const automaticDimension = {
|
|
176
|
+
kind: 'dimension-string',
|
|
177
|
+
segments: [{ start: [0, 0], end: [0, 18], text: '18m' }],
|
|
178
|
+
offsetNormal: [1, 0],
|
|
179
|
+
offsetDistance: 0.55,
|
|
180
|
+
extensionOvershoot: 0.12,
|
|
181
|
+
} satisfies FloorplanGeometry
|
|
182
|
+
const contextualDimension = withFloorplanGeometryMetadata(
|
|
183
|
+
{
|
|
184
|
+
kind: 'dimension',
|
|
185
|
+
start: [0, 0],
|
|
186
|
+
end: [0, 6],
|
|
187
|
+
offsetNormal: [1, 0],
|
|
188
|
+
offsetDistance: 0.34,
|
|
189
|
+
extensionOvershoot: 0.08,
|
|
190
|
+
text: '6m',
|
|
191
|
+
} satisfies FloorplanGeometry,
|
|
192
|
+
{ annotationRole: 'contextual-dimension' },
|
|
193
|
+
)
|
|
194
|
+
const geometry = {
|
|
195
|
+
kind: 'group',
|
|
196
|
+
children: [
|
|
197
|
+
{
|
|
198
|
+
kind: 'group',
|
|
199
|
+
children: [wallBody, automaticDimension],
|
|
200
|
+
},
|
|
201
|
+
contextualDimension,
|
|
202
|
+
],
|
|
203
|
+
} satisfies FloorplanGeometry
|
|
204
|
+
|
|
205
|
+
expect(
|
|
206
|
+
filterFloorplanAnnotationGeometry(geometry, {
|
|
207
|
+
...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
208
|
+
automaticDimensions: false,
|
|
209
|
+
contextualDimensions: true,
|
|
210
|
+
}),
|
|
211
|
+
).toEqual({
|
|
212
|
+
kind: 'group',
|
|
213
|
+
children: [{ kind: 'group', children: [wallBody] }, contextualDimension],
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('hides structural grids and only the center marks within column geometry', () => {
|
|
218
|
+
const centerMark = {
|
|
219
|
+
kind: 'line',
|
|
220
|
+
x1: -0.1,
|
|
221
|
+
y1: -0.1,
|
|
222
|
+
x2: 0.1,
|
|
223
|
+
y2: 0.1,
|
|
224
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'column-center' }),
|
|
225
|
+
} satisfies FloorplanGeometry
|
|
226
|
+
const gridReference = {
|
|
227
|
+
kind: 'text',
|
|
228
|
+
x: 0,
|
|
229
|
+
y: 0.3,
|
|
230
|
+
text: 'B-2',
|
|
231
|
+
fontSize: 0.13,
|
|
232
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'column-center' }),
|
|
233
|
+
} satisfies FloorplanGeometry
|
|
234
|
+
const footprint = {
|
|
235
|
+
kind: 'rect',
|
|
236
|
+
x: -0.2,
|
|
237
|
+
y: -0.2,
|
|
238
|
+
width: 0.4,
|
|
239
|
+
height: 0.4,
|
|
240
|
+
} satisfies FloorplanGeometry
|
|
241
|
+
const visibility = {
|
|
242
|
+
...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
|
|
243
|
+
structuralGrids: false,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
expect(
|
|
247
|
+
filterFloorplanAnnotationGeometry(
|
|
248
|
+
{
|
|
249
|
+
kind: 'group',
|
|
250
|
+
children: [footprint, centerMark, gridReference],
|
|
251
|
+
},
|
|
252
|
+
visibility,
|
|
253
|
+
),
|
|
254
|
+
).toEqual({ kind: 'group', children: [footprint] })
|
|
255
|
+
expect(
|
|
256
|
+
filterFloorplanAnnotationGeometry(
|
|
257
|
+
{
|
|
258
|
+
kind: 'group',
|
|
259
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'structural-grid' }),
|
|
260
|
+
children: [footprint],
|
|
261
|
+
},
|
|
262
|
+
visibility,
|
|
263
|
+
),
|
|
264
|
+
).toBeNull()
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
test('hides room labels without removing the room footprint', () => {
|
|
268
|
+
const footprint = {
|
|
269
|
+
kind: 'polygon',
|
|
270
|
+
points: [
|
|
271
|
+
[0, 0],
|
|
272
|
+
[4, 0],
|
|
273
|
+
[4, 3],
|
|
274
|
+
],
|
|
275
|
+
} satisfies FloorplanGeometry
|
|
276
|
+
const roomName = {
|
|
277
|
+
kind: 'text',
|
|
278
|
+
x: 2,
|
|
279
|
+
y: 1.5,
|
|
280
|
+
text: 'Office',
|
|
281
|
+
fontSize: 0.2,
|
|
282
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'room-label' }),
|
|
283
|
+
} satisfies FloorplanGeometry
|
|
284
|
+
|
|
285
|
+
expect(
|
|
286
|
+
filterFloorplanAnnotationGeometry(
|
|
287
|
+
{ kind: 'group', children: [footprint, roomName] },
|
|
288
|
+
{ ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY, roomLabels: false },
|
|
289
|
+
),
|
|
290
|
+
).toEqual({ kind: 'group', children: [footprint] })
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
test('hides stair notes and break lines without removing stair geometry', () => {
|
|
294
|
+
const footprint = {
|
|
295
|
+
kind: 'polygon',
|
|
296
|
+
points: [
|
|
297
|
+
[0, 0],
|
|
298
|
+
[1, 0],
|
|
299
|
+
[1, 3],
|
|
300
|
+
[0, 3],
|
|
301
|
+
],
|
|
302
|
+
} satisfies FloorplanGeometry
|
|
303
|
+
const direction = {
|
|
304
|
+
kind: 'text',
|
|
305
|
+
x: 0.5,
|
|
306
|
+
y: 0.5,
|
|
307
|
+
text: 'UP',
|
|
308
|
+
fontSize: 0.16,
|
|
309
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'stair-annotation' }),
|
|
310
|
+
} satisfies FloorplanGeometry
|
|
311
|
+
const breakLine = {
|
|
312
|
+
kind: 'polyline',
|
|
313
|
+
points: [
|
|
314
|
+
[0, 2],
|
|
315
|
+
[1, 2],
|
|
316
|
+
],
|
|
317
|
+
metadata: floorplanGeometryMetadata({ annotationRole: 'stair-annotation' }),
|
|
318
|
+
} satisfies FloorplanGeometry
|
|
319
|
+
|
|
320
|
+
expect(
|
|
321
|
+
filterFloorplanAnnotationGeometry(
|
|
322
|
+
{ kind: 'group', children: [footprint, direction, breakLine] },
|
|
323
|
+
{ ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY, stairAnnotations: false },
|
|
324
|
+
),
|
|
325
|
+
).toEqual({ kind: 'group', children: [footprint] })
|
|
326
|
+
})
|
|
327
|
+
})
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { FloorplanGeometry } from '@pascal-app/core'
|
|
2
|
+
import { type FloorplanAnnotationRole, readFloorplanGeometryMetadata } from './floorplan-extension'
|
|
3
|
+
|
|
4
|
+
export type FloorplanAnnotationCategory =
|
|
5
|
+
| 'automaticDimensions'
|
|
6
|
+
| 'contextualDimensions'
|
|
7
|
+
| 'manualDimensions'
|
|
8
|
+
| 'measurements'
|
|
9
|
+
| 'openingMarks'
|
|
10
|
+
| 'structuralGrids'
|
|
11
|
+
| 'roomLabels'
|
|
12
|
+
| 'stairAnnotations'
|
|
13
|
+
|
|
14
|
+
export type FloorplanAnnotationVisibility = Record<FloorplanAnnotationCategory, boolean>
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY: FloorplanAnnotationVisibility = {
|
|
17
|
+
automaticDimensions: true,
|
|
18
|
+
contextualDimensions: false,
|
|
19
|
+
manualDimensions: true,
|
|
20
|
+
measurements: true,
|
|
21
|
+
openingMarks: true,
|
|
22
|
+
structuralGrids: true,
|
|
23
|
+
roomLabels: true,
|
|
24
|
+
stairAnnotations: true,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function revealFloorplanAnnotationRole(
|
|
28
|
+
visibility: FloorplanAnnotationVisibility,
|
|
29
|
+
role: FloorplanAnnotationRole,
|
|
30
|
+
): FloorplanAnnotationVisibility {
|
|
31
|
+
const category = annotationCategoryForRole(role)
|
|
32
|
+
return visibility[category] ? visibility : { ...visibility, [category]: true }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function normalizeFloorplanAnnotationVisibility(
|
|
36
|
+
value: unknown,
|
|
37
|
+
): FloorplanAnnotationVisibility {
|
|
38
|
+
if (!value || typeof value !== 'object') return { ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY }
|
|
39
|
+
const persisted = value as Partial<Record<FloorplanAnnotationCategory, unknown>>
|
|
40
|
+
return {
|
|
41
|
+
automaticDimensions:
|
|
42
|
+
typeof persisted.automaticDimensions === 'boolean'
|
|
43
|
+
? persisted.automaticDimensions
|
|
44
|
+
: DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY.automaticDimensions,
|
|
45
|
+
contextualDimensions:
|
|
46
|
+
typeof persisted.contextualDimensions === 'boolean'
|
|
47
|
+
? persisted.contextualDimensions
|
|
48
|
+
: DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY.contextualDimensions,
|
|
49
|
+
manualDimensions:
|
|
50
|
+
typeof persisted.manualDimensions === 'boolean'
|
|
51
|
+
? persisted.manualDimensions
|
|
52
|
+
: DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY.manualDimensions,
|
|
53
|
+
measurements:
|
|
54
|
+
typeof persisted.measurements === 'boolean'
|
|
55
|
+
? persisted.measurements
|
|
56
|
+
: DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY.measurements,
|
|
57
|
+
openingMarks:
|
|
58
|
+
typeof persisted.openingMarks === 'boolean'
|
|
59
|
+
? persisted.openingMarks
|
|
60
|
+
: DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY.openingMarks,
|
|
61
|
+
structuralGrids:
|
|
62
|
+
typeof persisted.structuralGrids === 'boolean'
|
|
63
|
+
? persisted.structuralGrids
|
|
64
|
+
: DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY.structuralGrids,
|
|
65
|
+
roomLabels:
|
|
66
|
+
typeof persisted.roomLabels === 'boolean'
|
|
67
|
+
? persisted.roomLabels
|
|
68
|
+
: DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY.roomLabels,
|
|
69
|
+
stairAnnotations:
|
|
70
|
+
typeof persisted.stairAnnotations === 'boolean'
|
|
71
|
+
? persisted.stairAnnotations
|
|
72
|
+
: DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY.stairAnnotations,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function filterFloorplanAnnotationGeometry(
|
|
77
|
+
geometry: FloorplanGeometry,
|
|
78
|
+
visibility: FloorplanAnnotationVisibility,
|
|
79
|
+
inheritedRole?: FloorplanAnnotationRole,
|
|
80
|
+
): FloorplanGeometry | null {
|
|
81
|
+
const role = readFloorplanGeometryMetadata(geometry).annotationRole ?? inheritedRole
|
|
82
|
+
if (role && !isAnnotationRoleVisible(role, visibility)) return null
|
|
83
|
+
if (
|
|
84
|
+
!visibility.automaticDimensions &&
|
|
85
|
+
(role === undefined || role === 'automatic-dimension') &&
|
|
86
|
+
(geometry.kind === 'dimension' ||
|
|
87
|
+
geometry.kind === 'dimension-string' ||
|
|
88
|
+
geometry.kind === 'dimension-label' ||
|
|
89
|
+
geometry.kind === 'equal-spacing-badge')
|
|
90
|
+
) {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
if (geometry.kind !== 'group') return geometry
|
|
94
|
+
|
|
95
|
+
const children = geometry.children
|
|
96
|
+
.map((child) => filterFloorplanAnnotationGeometry(child, visibility, role))
|
|
97
|
+
.filter((child): child is FloorplanGeometry => child !== null)
|
|
98
|
+
if (children.length === 0) return null
|
|
99
|
+
const childrenUnchanged =
|
|
100
|
+
children.length === geometry.children.length &&
|
|
101
|
+
children.every((child, index) => child === geometry.children[index])
|
|
102
|
+
if (childrenUnchanged) return geometry
|
|
103
|
+
return { ...geometry, children }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isAnnotationRoleVisible(
|
|
107
|
+
role: FloorplanAnnotationRole,
|
|
108
|
+
visibility: FloorplanAnnotationVisibility,
|
|
109
|
+
): boolean {
|
|
110
|
+
return visibility[annotationCategoryForRole(role)]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function annotationCategoryForRole(role: FloorplanAnnotationRole): FloorplanAnnotationCategory {
|
|
114
|
+
switch (role) {
|
|
115
|
+
case 'automatic-dimension':
|
|
116
|
+
return 'automaticDimensions'
|
|
117
|
+
case 'contextual-dimension':
|
|
118
|
+
return 'contextualDimensions'
|
|
119
|
+
case 'manual-dimension':
|
|
120
|
+
return 'manualDimensions'
|
|
121
|
+
case 'measurement':
|
|
122
|
+
return 'measurements'
|
|
123
|
+
case 'opening-mark':
|
|
124
|
+
return 'openingMarks'
|
|
125
|
+
case 'structural-grid':
|
|
126
|
+
case 'column-center':
|
|
127
|
+
return 'structuralGrids'
|
|
128
|
+
case 'room-label':
|
|
129
|
+
return 'roomLabels'
|
|
130
|
+
case 'stair-annotation':
|
|
131
|
+
return 'stairAnnotations'
|
|
132
|
+
}
|
|
133
|
+
}
|