@ifc-lite/viewer 1.6.0 → 1.7.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/CHANGELOG.md +78 -0
- package/dist/assets/{Arrow.dom-BjDQoB2M.js → Arrow.dom-BGPQieQQ.js} +1 -1
- package/dist/assets/ifc-lite_bg-DyIN_nBM.wasm +0 -0
- package/dist/assets/{index-YBtrHPu3.js → index-dgdgiQ9p.js} +40212 -30008
- package/dist/assets/index-yTqs8kgX.css +1 -0
- package/dist/assets/{native-bridge-CULtTDX3.js → native-bridge-DD0SNyQ5.js} +1 -1
- package/dist/assets/{wasm-bridge-CjL-lSak.js → wasm-bridge-D54YMO7X.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +18 -15
- package/src/components/viewer/BCFPanel.tsx +7 -789
- package/src/components/viewer/Drawing2DCanvas.tsx +1048 -0
- package/src/components/viewer/DrawingSettingsPanel.tsx +3 -3
- package/src/components/viewer/HierarchyPanel.tsx +110 -842
- package/src/components/viewer/IDSExportDialog.tsx +281 -0
- package/src/components/viewer/IDSPanel.tsx +126 -17
- package/src/components/viewer/KeyboardShortcutsDialog.tsx +9 -0
- package/src/components/viewer/LensPanel.tsx +603 -0
- package/src/components/viewer/MainToolbar.tsx +188 -21
- package/src/components/viewer/PropertiesPanel.tsx +171 -663
- package/src/components/viewer/PropertyEditor.tsx +866 -77
- package/src/components/viewer/Section2DPanel.tsx +76 -2648
- package/src/components/viewer/ToolOverlays.tsx +3 -1097
- package/src/components/viewer/ViewerLayout.tsx +132 -45
- package/src/components/viewer/Viewport.tsx +237 -1659
- package/src/components/viewer/ViewportContainer.tsx +11 -3
- package/src/components/viewer/bcf/BCFCreateTopicForm.tsx +134 -0
- package/src/components/viewer/bcf/BCFTopicDetail.tsx +388 -0
- package/src/components/viewer/bcf/BCFTopicList.tsx +239 -0
- package/src/components/viewer/bcf/bcfHelpers.tsx +109 -0
- package/src/components/viewer/hierarchy/HierarchyNode.tsx +328 -0
- package/src/components/viewer/hierarchy/treeDataBuilder.ts +464 -0
- package/src/components/viewer/hierarchy/types.ts +54 -0
- package/src/components/viewer/hierarchy/useHierarchyTree.ts +280 -0
- package/src/components/viewer/lists/ListBuilder.tsx +486 -0
- package/src/components/viewer/lists/ListPanel.tsx +540 -0
- package/src/components/viewer/lists/ListResultsTable.tsx +193 -0
- package/src/components/viewer/properties/ClassificationCard.tsx +70 -0
- package/src/components/viewer/properties/CoordinateDisplay.tsx +49 -0
- package/src/components/viewer/properties/DocumentCard.tsx +89 -0
- package/src/components/viewer/properties/MaterialCard.tsx +201 -0
- package/src/components/viewer/properties/ModelMetadataPanel.tsx +335 -0
- package/src/components/viewer/properties/PropertySetCard.tsx +132 -0
- package/src/components/viewer/properties/QuantitySetCard.tsx +79 -0
- package/src/components/viewer/properties/RelationshipsCard.tsx +100 -0
- package/src/components/viewer/properties/encodingUtils.ts +29 -0
- package/src/components/viewer/tools/MeasurePanel.tsx +218 -0
- package/src/components/viewer/tools/MeasurementVisuals.tsx +644 -0
- package/src/components/viewer/tools/SectionPanel.tsx +183 -0
- package/src/components/viewer/tools/SectionVisualization.tsx +78 -0
- package/src/components/viewer/tools/formatDistance.ts +18 -0
- package/src/components/viewer/tools/sectionConstants.ts +14 -0
- package/src/components/viewer/useAnimationLoop.ts +166 -0
- package/src/components/viewer/useGeometryStreaming.ts +398 -0
- package/src/components/viewer/useKeyboardControls.ts +221 -0
- package/src/components/viewer/useMouseControls.ts +1009 -0
- package/src/components/viewer/useRenderUpdates.ts +165 -0
- package/src/components/viewer/useTouchControls.ts +245 -0
- package/src/hooks/ids/idsColorSystem.ts +125 -0
- package/src/hooks/ids/idsDataAccessor.ts +237 -0
- package/src/hooks/ids/idsExportService.ts +444 -0
- package/src/hooks/useBCF.ts +7 -0
- package/src/hooks/useDrawingExport.ts +627 -0
- package/src/hooks/useDrawingGeneration.ts +627 -0
- package/src/hooks/useFloorplanView.ts +108 -0
- package/src/hooks/useIDS.ts +270 -463
- package/src/hooks/useIfc.ts +26 -1628
- package/src/hooks/useIfcFederation.ts +803 -0
- package/src/hooks/useIfcLoader.ts +508 -0
- package/src/hooks/useIfcServer.ts +465 -0
- package/src/hooks/useKeyboardShortcuts.ts +1 -1
- package/src/hooks/useLens.ts +129 -0
- package/src/hooks/useMeasure2D.ts +365 -0
- package/src/hooks/useViewControls.ts +218 -0
- package/src/lib/ifc4-pset-definitions.test.ts +161 -0
- package/src/lib/ifc4-pset-definitions.ts +621 -0
- package/src/lib/ifc4-qto-definitions.ts +315 -0
- package/src/lib/lens/adapter.ts +138 -0
- package/src/lib/lens/index.ts +5 -0
- package/src/lib/lists/adapter.ts +69 -0
- package/src/lib/lists/index.ts +28 -0
- package/src/lib/lists/persistence.ts +64 -0
- package/src/services/fs-cache.ts +1 -1
- package/src/services/tauri-modules.d.ts +25 -0
- package/src/store/index.ts +38 -2
- package/src/store/slices/cameraSlice.ts +14 -1
- package/src/store/slices/dataSlice.ts +14 -1
- package/src/store/slices/lensSlice.ts +184 -0
- package/src/store/slices/listSlice.ts +74 -0
- package/src/store/slices/pinboardSlice.ts +114 -0
- package/src/store/types.ts +5 -0
- package/src/utils/ifcConfig.ts +16 -3
- package/src/utils/serverDataModel.ts +64 -101
- package/src/vite-env.d.ts +3 -0
- package/dist/assets/ifc-lite_bg-C6kblxf9.wasm +0 -0
- package/dist/assets/index-v3mcCUPN.css +0 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* IFC string encoding/decoding utilities and property value parsing.
|
|
7
|
+
*
|
|
8
|
+
* Core logic lives in @ifc-lite/encoding; this file re-exports it and adds
|
|
9
|
+
* viewer-specific types (PropertySet/QuantitySet with mutation tracking).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Re-export core encoding functions from the package
|
|
13
|
+
export { decodeIfcString, parsePropertyValue } from '@ifc-lite/encoding';
|
|
14
|
+
export type { ParsedPropertyValue } from '@ifc-lite/encoding';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Viewer-specific Types (with mutation tracking for property editing UI)
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
export interface PropertySet {
|
|
21
|
+
name: string;
|
|
22
|
+
properties: Array<{ name: string; value: unknown; isMutated?: boolean }>;
|
|
23
|
+
isNewPset?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface QuantitySet {
|
|
27
|
+
name: string;
|
|
28
|
+
quantities: Array<{ name: string; value: number; type: number }>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Measure tool panel UI (measurement list, controls)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useCallback, useState, useEffect } from 'react';
|
|
10
|
+
import { X, Trash2, Ruler, ChevronDown } from 'lucide-react';
|
|
11
|
+
import { Button } from '@/components/ui/button';
|
|
12
|
+
import { useViewerStore, type Measurement } from '@/store';
|
|
13
|
+
import { MeasurementOverlays } from './MeasurementVisuals';
|
|
14
|
+
import { formatDistance } from './formatDistance';
|
|
15
|
+
|
|
16
|
+
export function MeasureOverlay() {
|
|
17
|
+
const measurements = useViewerStore((s) => s.measurements);
|
|
18
|
+
const pendingMeasurePoint = useViewerStore((s) => s.pendingMeasurePoint);
|
|
19
|
+
const activeMeasurement = useViewerStore((s) => s.activeMeasurement);
|
|
20
|
+
const snapTarget = useViewerStore((s) => s.snapTarget);
|
|
21
|
+
const snapVisualization = useViewerStore((s) => s.snapVisualization);
|
|
22
|
+
const snapEnabled = useViewerStore((s) => s.snapEnabled);
|
|
23
|
+
const measurementConstraintEdge = useViewerStore((s) => s.measurementConstraintEdge);
|
|
24
|
+
const toggleSnap = useViewerStore((s) => s.toggleSnap);
|
|
25
|
+
const deleteMeasurement = useViewerStore((s) => s.deleteMeasurement);
|
|
26
|
+
const clearMeasurements = useViewerStore((s) => s.clearMeasurements);
|
|
27
|
+
const setActiveTool = useViewerStore((s) => s.setActiveTool);
|
|
28
|
+
const projectToScreen = useViewerStore((s) => s.cameraCallbacks.projectToScreen);
|
|
29
|
+
|
|
30
|
+
// Track cursor position in ref (no re-renders on mouse move)
|
|
31
|
+
const cursorPosRef = React.useRef<{ x: number; y: number } | null>(null);
|
|
32
|
+
// Only update snap indicator position when snap target changes (not on every cursor move)
|
|
33
|
+
const [snapIndicatorPos, setSnapIndicatorPos] = useState<{ x: number; y: number } | null>(null);
|
|
34
|
+
// Panel collapsed by default for minimal UI
|
|
35
|
+
const [isPanelCollapsed, setIsPanelCollapsed] = useState(true);
|
|
36
|
+
// Ref to the overlay container for coordinate conversion
|
|
37
|
+
const overlayRef = React.useRef<HTMLDivElement>(null);
|
|
38
|
+
|
|
39
|
+
// Update cursor position in ref (no re-renders)
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
42
|
+
// Convert page coords to overlay-relative coords for consistent SVG positioning
|
|
43
|
+
const container = overlayRef.current?.parentElement;
|
|
44
|
+
if (container) {
|
|
45
|
+
const rect = container.getBoundingClientRect();
|
|
46
|
+
cursorPosRef.current = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
47
|
+
} else {
|
|
48
|
+
cursorPosRef.current = { x: e.clientX, y: e.clientY };
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
53
|
+
return () => {
|
|
54
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
55
|
+
};
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
// Update snap indicator position when snap target changes
|
|
59
|
+
// Cursor position is stored in ref (no re-renders on mouse move)
|
|
60
|
+
// Snap target changes already trigger re-renders, so indicator will update frequently enough
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (snapTarget && cursorPosRef.current) {
|
|
63
|
+
setSnapIndicatorPos(cursorPosRef.current);
|
|
64
|
+
} else {
|
|
65
|
+
setSnapIndicatorPos(null);
|
|
66
|
+
}
|
|
67
|
+
}, [snapTarget]);
|
|
68
|
+
|
|
69
|
+
const handleClear = useCallback(() => {
|
|
70
|
+
clearMeasurements();
|
|
71
|
+
}, [clearMeasurements]);
|
|
72
|
+
|
|
73
|
+
const handleDeleteMeasurement = useCallback((id: string) => {
|
|
74
|
+
deleteMeasurement(id);
|
|
75
|
+
}, [deleteMeasurement]);
|
|
76
|
+
|
|
77
|
+
const togglePanel = useCallback(() => {
|
|
78
|
+
setIsPanelCollapsed(prev => !prev);
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
const handleClose = useCallback(() => {
|
|
82
|
+
setActiveTool('select');
|
|
83
|
+
}, [setActiveTool]);
|
|
84
|
+
|
|
85
|
+
// Calculate total distance
|
|
86
|
+
const totalDistance = measurements.reduce((sum, m) => sum + m.distance, 0);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<>
|
|
90
|
+
{/* Hidden ref element for coordinate calculation */}
|
|
91
|
+
<div ref={overlayRef} className="absolute top-0 left-0 w-0 h-0" />
|
|
92
|
+
|
|
93
|
+
{/* Compact Measure Tool Panel */}
|
|
94
|
+
<div className="pointer-events-auto absolute top-4 left-1/2 -translate-x-1/2 bg-background/95 backdrop-blur-sm rounded-lg border shadow-lg z-30">
|
|
95
|
+
{/* Header - always visible */}
|
|
96
|
+
<div className="flex items-center justify-between gap-2 p-2">
|
|
97
|
+
<button
|
|
98
|
+
onClick={togglePanel}
|
|
99
|
+
className="flex items-center gap-2 hover:bg-accent/50 rounded px-2 py-1 transition-colors"
|
|
100
|
+
>
|
|
101
|
+
<Ruler className="h-4 w-4 text-primary" />
|
|
102
|
+
<span className="font-medium text-sm">Measure</span>
|
|
103
|
+
{measurements.length > 0 && !isPanelCollapsed && (
|
|
104
|
+
<span className="text-xs text-muted-foreground">({measurements.length})</span>
|
|
105
|
+
)}
|
|
106
|
+
<ChevronDown className={`h-3 w-3 transition-transform ${isPanelCollapsed ? '-rotate-90' : ''}`} />
|
|
107
|
+
</button>
|
|
108
|
+
<div className="flex items-center gap-1">
|
|
109
|
+
{measurements.length > 0 && (
|
|
110
|
+
<Button variant="ghost" size="icon-sm" onClick={handleClear} title="Clear all">
|
|
111
|
+
<Trash2 className="h-3 w-3" />
|
|
112
|
+
</Button>
|
|
113
|
+
)}
|
|
114
|
+
<Button variant="ghost" size="icon-sm" onClick={handleClose} title="Close">
|
|
115
|
+
<X className="h-3 w-3" />
|
|
116
|
+
</Button>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* Expandable content */}
|
|
121
|
+
{!isPanelCollapsed && (
|
|
122
|
+
<div className="border-t px-2 pb-2 min-w-56">
|
|
123
|
+
{measurements.length > 0 ? (
|
|
124
|
+
<div className="space-y-1 mt-2">
|
|
125
|
+
{measurements.map((m, i) => (
|
|
126
|
+
<MeasurementItem
|
|
127
|
+
key={m.id}
|
|
128
|
+
measurement={m}
|
|
129
|
+
index={i}
|
|
130
|
+
onDelete={handleDeleteMeasurement}
|
|
131
|
+
/>
|
|
132
|
+
))}
|
|
133
|
+
{measurements.length > 1 && (
|
|
134
|
+
<div className="flex items-center justify-between border-t pt-1 mt-1 text-xs font-medium">
|
|
135
|
+
<span>Total</span>
|
|
136
|
+
<span className="font-mono">{formatDistance(totalDistance)}</span>
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
) : (
|
|
141
|
+
<div className="text-center py-2 text-muted-foreground text-xs">
|
|
142
|
+
No measurements
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{/* Instruction hint - brutalist style with snap-colored shadow */}
|
|
150
|
+
<div
|
|
151
|
+
className="pointer-events-auto absolute bottom-16 left-1/2 -translate-x-1/2 z-30 bg-zinc-900 dark:bg-zinc-100 text-zinc-100 dark:text-zinc-900 px-3 py-1.5 border-2 border-zinc-900 dark:border-zinc-100 transition-shadow duration-150"
|
|
152
|
+
style={{
|
|
153
|
+
boxShadow: snapTarget
|
|
154
|
+
? `4px 4px 0px 0px ${
|
|
155
|
+
snapTarget.type === 'vertex' ? '#FFEB3B' :
|
|
156
|
+
snapTarget.type === 'edge' ? '#FF9800' :
|
|
157
|
+
snapTarget.type === 'face' ? '#03A9F4' : '#00BCD4'
|
|
158
|
+
}`
|
|
159
|
+
: '3px 3px 0px 0px rgba(0,0,0,0.3)'
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
<span className="font-mono text-xs uppercase tracking-wide">
|
|
163
|
+
{activeMeasurement ? 'Release to complete' : 'Drag to measure'}
|
|
164
|
+
</span>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{/* Snap toggle - brutalist style */}
|
|
168
|
+
<div className="pointer-events-auto absolute bottom-4 left-1/2 -translate-x-1/2 z-30">
|
|
169
|
+
<button
|
|
170
|
+
onClick={toggleSnap}
|
|
171
|
+
className={`px-2 py-1 font-mono text-[10px] uppercase tracking-wider border-2 transition-colors ${
|
|
172
|
+
snapEnabled
|
|
173
|
+
? 'bg-primary text-primary-foreground border-primary'
|
|
174
|
+
: 'bg-zinc-100 dark:bg-zinc-900 text-zinc-500 border-zinc-300 dark:border-zinc-700'
|
|
175
|
+
}`}
|
|
176
|
+
title="Toggle snap (S key)"
|
|
177
|
+
>
|
|
178
|
+
Snap {snapEnabled ? 'On' : 'Off'}
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* Render measurement lines, labels, and snap indicators */}
|
|
183
|
+
<MeasurementOverlays
|
|
184
|
+
measurements={measurements}
|
|
185
|
+
pending={pendingMeasurePoint}
|
|
186
|
+
activeMeasurement={activeMeasurement}
|
|
187
|
+
snapTarget={snapTarget}
|
|
188
|
+
snapVisualization={snapVisualization}
|
|
189
|
+
hoverPosition={snapIndicatorPos}
|
|
190
|
+
projectToScreen={projectToScreen}
|
|
191
|
+
constraintEdge={measurementConstraintEdge}
|
|
192
|
+
/>
|
|
193
|
+
</>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface MeasurementItemProps {
|
|
198
|
+
measurement: Measurement;
|
|
199
|
+
index: number;
|
|
200
|
+
onDelete: (id: string) => void;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function MeasurementItem({ measurement, index, onDelete }: MeasurementItemProps) {
|
|
204
|
+
return (
|
|
205
|
+
<div className="flex items-center justify-between bg-muted/50 rounded px-2 py-0.5 text-xs">
|
|
206
|
+
<span className="text-muted-foreground text-xs">#{index + 1}</span>
|
|
207
|
+
<span className="font-mono font-medium">{formatDistance(measurement.distance)}</span>
|
|
208
|
+
<Button
|
|
209
|
+
variant="ghost"
|
|
210
|
+
size="icon-sm"
|
|
211
|
+
className="h-4 w-4 hover:bg-destructive/20"
|
|
212
|
+
onClick={() => onDelete(measurement.id)}
|
|
213
|
+
>
|
|
214
|
+
<X className="h-2.5 w-2.5" />
|
|
215
|
+
</Button>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|