@ifc-lite/viewer 1.1.6 → 1.5.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/LICENSE +373 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/Arrow.dom-B0e15b_b.js +20 -0
- package/dist/assets/arrow2-bb-jcVEo.js +2 -0
- package/dist/assets/arrow2_bg-4Y7xYo54.wasm +0 -0
- package/dist/assets/arrow2_bg-BlXl-cSQ.js +1 -0
- package/dist/assets/arrow2_bg-BoXCojjR.wasm +0 -0
- package/dist/assets/desktop-cache-oPzaWXYE.js +1 -0
- package/dist/assets/event-DIOks52T.js +1 -0
- package/dist/assets/ifc-cache-BAN4vcd4.js +1 -0
- package/dist/assets/ifc-lite_bg-C6kblxf9.wasm +0 -0
- package/dist/assets/index-Dgd6vzw_.js +65252 -0
- package/dist/assets/index-v3mcCUPN.css +1 -0
- package/dist/assets/native-bridge-Ci7NLjlZ.js +111 -0
- package/dist/assets/wasm-bridge-Dc82YpdZ.js +1 -0
- package/dist/favicon-16x16-cropped.png +0 -0
- package/dist/favicon-16x16.png +0 -0
- package/dist/favicon-192x192-cropped.png +0 -0
- package/dist/favicon-192x192.png +0 -0
- package/dist/favicon-32x32-cropped.png +0 -0
- package/dist/favicon-32x32.png +0 -0
- package/dist/favicon-48x48-cropped.png +0 -0
- package/dist/favicon-48x48.png +0 -0
- package/dist/favicon-512x512-cropped.png +0 -0
- package/dist/favicon-512x512.png +0 -0
- package/dist/favicon-64x64-cropped.png +0 -0
- package/dist/favicon-64x64.png +0 -0
- package/dist/favicon-96x96-cropped.png +0 -0
- package/dist/favicon-96x96.png +0 -0
- package/dist/favicon-square-512.png +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +3 -0
- package/dist/index.html +44 -0
- package/dist/logo.png +0 -0
- package/dist/manifest.json +48 -0
- package/index.html +33 -2
- package/package.json +34 -17
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16-cropped.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-192x192-cropped.png +0 -0
- package/public/favicon-192x192.png +0 -0
- package/public/favicon-32x32-cropped.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon-48x48-cropped.png +0 -0
- package/public/favicon-48x48.png +0 -0
- package/public/favicon-512x512-cropped.png +0 -0
- package/public/favicon-512x512.png +0 -0
- package/public/favicon-64x64-cropped.png +0 -0
- package/public/favicon-64x64.png +0 -0
- package/public/favicon-96x96-cropped.png +0 -0
- package/public/favicon-96x96.png +0 -0
- package/public/favicon-square-512.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +3 -0
- package/public/logo.png +0 -0
- package/public/manifest.json +48 -0
- package/src/App.tsx +2 -0
- package/src/components/ui/alert.tsx +62 -0
- package/src/components/ui/badge.tsx +39 -0
- package/src/components/ui/dialog.tsx +120 -0
- package/src/components/ui/label.tsx +27 -0
- package/src/components/ui/select.tsx +151 -0
- package/src/components/ui/switch.tsx +30 -0
- package/src/components/ui/table.tsx +120 -0
- package/src/components/ui/tabs.tsx +1 -1
- package/src/components/viewer/BCFPanel.tsx +1164 -0
- package/src/components/viewer/BulkPropertyEditor.tsx +875 -0
- package/src/components/viewer/DataConnector.tsx +840 -0
- package/src/components/viewer/DrawingSettingsPanel.tsx +536 -0
- package/src/components/viewer/EntityContextMenu.tsx +45 -17
- package/src/components/viewer/ExportChangesButton.tsx +195 -0
- package/src/components/viewer/ExportDialog.tsx +402 -0
- package/src/components/viewer/HierarchyPanel.tsx +1132 -218
- package/src/components/viewer/IDSPanel.tsx +661 -0
- package/src/components/viewer/KeyboardShortcutsDialog.tsx +245 -39
- package/src/components/viewer/MainToolbar.tsx +418 -94
- package/src/components/viewer/PropertiesPanel.tsx +1355 -91
- package/src/components/viewer/PropertyEditor.tsx +611 -0
- package/src/components/viewer/Section2DPanel.tsx +3313 -0
- package/src/components/viewer/SheetSetupPanel.tsx +502 -0
- package/src/components/viewer/StatusBar.tsx +27 -16
- package/src/components/viewer/TitleBlockEditor.tsx +437 -0
- package/src/components/viewer/ToolOverlays.tsx +935 -127
- package/src/components/viewer/ViewerLayout.tsx +40 -11
- package/src/components/viewer/Viewport.tsx +1276 -336
- package/src/components/viewer/ViewportContainer.tsx +554 -18
- package/src/components/viewer/ViewportOverlays.tsx +24 -7
- package/src/hooks/useBCF.ts +504 -0
- package/src/hooks/useIDS.ts +1065 -0
- package/src/hooks/useIfc.ts +1534 -205
- package/src/hooks/useIfcCache.ts +279 -0
- package/src/hooks/useKeyboardShortcuts.ts +50 -8
- package/src/hooks/useModelSelection.ts +61 -0
- package/src/hooks/useViewerSelectors.ts +218 -0
- package/src/hooks/useWebGPU.ts +80 -0
- package/src/index.css +265 -27
- package/src/lib/platform.ts +23 -0
- package/src/services/cacheService.ts +142 -0
- package/src/services/desktop-cache.ts +143 -0
- package/src/services/fs-cache.ts +212 -0
- package/src/services/ifc-cache.ts +14 -6
- package/src/store/constants.ts +85 -0
- package/src/store/index.ts +214 -0
- package/src/store/slices/bcfSlice.ts +372 -0
- package/src/store/slices/cameraSlice.ts +63 -0
- package/src/store/slices/dataSlice.test.ts +226 -0
- package/src/store/slices/dataSlice.ts +112 -0
- package/src/store/slices/drawing2DSlice.ts +340 -0
- package/src/store/slices/hoverSlice.ts +40 -0
- package/src/store/slices/idsSlice.ts +310 -0
- package/src/store/slices/loadingSlice.ts +33 -0
- package/src/store/slices/measurementSlice.test.ts +217 -0
- package/src/store/slices/measurementSlice.ts +293 -0
- package/src/store/slices/modelSlice.test.ts +271 -0
- package/src/store/slices/modelSlice.ts +211 -0
- package/src/store/slices/mutationSlice.ts +502 -0
- package/src/store/slices/sectionSlice.test.ts +125 -0
- package/src/store/slices/sectionSlice.ts +58 -0
- package/src/store/slices/selectionSlice.test.ts +286 -0
- package/src/store/slices/selectionSlice.ts +263 -0
- package/src/store/slices/sheetSlice.ts +565 -0
- package/src/store/slices/uiSlice.ts +58 -0
- package/src/store/slices/visibilitySlice.test.ts +304 -0
- package/src/store/slices/visibilitySlice.ts +277 -0
- package/src/store/types.test.ts +135 -0
- package/src/store/types.ts +248 -0
- package/src/store.ts +40 -515
- package/src/utils/ifcConfig.ts +82 -0
- package/src/utils/localParsingUtils.ts +287 -0
- package/src/utils/serverDataModel.ts +783 -0
- package/src/utils/spatialHierarchy.ts +283 -0
- package/src/utils/viewportUtils.ts +334 -0
- package/src/vite-env.d.ts +23 -0
- package/src/webgpu-types.d.ts +128 -0
- package/src-tauri/Cargo.toml +29 -0
- package/src-tauri/build.rs +7 -0
- package/src-tauri/capabilities/default.json +18 -0
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/src/lib.rs +21 -0
- package/src-tauri/src/main.rs +10 -0
- package/src-tauri/tauri.conf.json +39 -0
- package/vite.config.ts +174 -26
- package/public/ifc-lite_bg.wasm +0 -0
- package/public/web-ifc.wasm +0 -0
- package/src/components/Viewport.tsx +0 -723
- package/src/components/viewer/BoxSelectionOverlay.tsx +0 -53
package/src/store.ts
CHANGED
|
@@ -3,520 +3,45 @@
|
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Re-export from modular store for backward compatibility
|
|
7
|
+
*
|
|
8
|
+
* The store has been refactored into domain-specific slices:
|
|
9
|
+
* - loadingSlice: Loading, progress, error state
|
|
10
|
+
* - selectionSlice: Entity and storey selection
|
|
11
|
+
* - visibilitySlice: Hidden/isolated entities, type visibility
|
|
12
|
+
* - uiSlice: Panel state, theme, mobile detection
|
|
13
|
+
* - hoverSlice: Hover and context menu state
|
|
14
|
+
* - cameraSlice: Camera rotation and callbacks
|
|
15
|
+
* - sectionSlice: Section plane state
|
|
16
|
+
* - measurementSlice: Measurements, snapping, edge lock
|
|
17
|
+
* - dataSlice: IFC data and geometry
|
|
18
|
+
*
|
|
19
|
+
* See apps/viewer/src/store/ for the modular implementation.
|
|
7
20
|
*/
|
|
8
21
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Hover state
|
|
37
|
-
export interface HoverState {
|
|
38
|
-
entityId: number | null;
|
|
39
|
-
screenX: number;
|
|
40
|
-
screenY: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Context menu state
|
|
44
|
-
export interface ContextMenuState {
|
|
45
|
-
isOpen: boolean;
|
|
46
|
-
entityId: number | null;
|
|
47
|
-
screenX: number;
|
|
48
|
-
screenY: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Box selection state
|
|
52
|
-
export interface BoxSelectState {
|
|
53
|
-
isSelecting: boolean;
|
|
54
|
-
startX: number;
|
|
55
|
-
startY: number;
|
|
56
|
-
currentX: number;
|
|
57
|
-
currentY: number;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface ViewerState {
|
|
61
|
-
// Loading state
|
|
62
|
-
loading: boolean;
|
|
63
|
-
progress: { phase: string; percent: number } | null;
|
|
64
|
-
error: string | null;
|
|
65
|
-
|
|
66
|
-
// Data
|
|
67
|
-
ifcDataStore: IfcDataStore | null;
|
|
68
|
-
geometryResult: GeometryResult | null;
|
|
69
|
-
|
|
70
|
-
// Selection
|
|
71
|
-
selectedEntityId: number | null;
|
|
72
|
-
selectedEntityIds: Set<number>; // Multi-selection support
|
|
73
|
-
selectedStorey: number | null;
|
|
74
|
-
|
|
75
|
-
// Visibility
|
|
76
|
-
hiddenEntities: Set<number>;
|
|
77
|
-
isolatedEntities: Set<number> | null; // null = show all, Set = only show these
|
|
78
|
-
|
|
79
|
-
// UI State
|
|
80
|
-
leftPanelCollapsed: boolean;
|
|
81
|
-
rightPanelCollapsed: boolean;
|
|
82
|
-
activeTool: string;
|
|
83
|
-
theme: 'light' | 'dark';
|
|
84
|
-
isMobile: boolean;
|
|
85
|
-
hoverTooltipsEnabled: boolean;
|
|
86
|
-
|
|
87
|
-
// Hover state
|
|
88
|
-
hoverState: HoverState;
|
|
89
|
-
|
|
90
|
-
// Context menu state
|
|
91
|
-
contextMenu: ContextMenuState;
|
|
92
|
-
|
|
93
|
-
// Box selection state
|
|
94
|
-
boxSelect: BoxSelectState;
|
|
95
|
-
|
|
96
|
-
// Measurement state
|
|
97
|
-
measurements: Measurement[];
|
|
98
|
-
pendingMeasurePoint: MeasurePoint | null;
|
|
99
|
-
|
|
100
|
-
// Section plane state
|
|
101
|
-
sectionPlane: SectionPlane;
|
|
102
|
-
|
|
103
|
-
// Camera state (for ViewCube sync)
|
|
104
|
-
cameraRotation: { azimuth: number; elevation: number };
|
|
105
|
-
cameraCallbacks: {
|
|
106
|
-
setPresetView?: (view: 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right') => void;
|
|
107
|
-
fitAll?: () => void;
|
|
108
|
-
home?: () => void; // Reset to isometric view
|
|
109
|
-
zoomIn?: () => void;
|
|
110
|
-
zoomOut?: () => void;
|
|
111
|
-
frameSelection?: () => void; // Center view on selected element (F key)
|
|
112
|
-
orbit?: (deltaX: number, deltaY: number) => void; // Orbit camera by delta
|
|
113
|
-
};
|
|
114
|
-
// Direct callback for real-time ViewCube updates (bypasses React state)
|
|
115
|
-
onCameraRotationChange: ((rotation: { azimuth: number; elevation: number }) => void) | null;
|
|
116
|
-
// Direct callback for real-time scale bar updates (bypasses React state)
|
|
117
|
-
onScaleChange: ((scale: number) => void) | null;
|
|
118
|
-
|
|
119
|
-
// Actions
|
|
120
|
-
setLoading: (loading: boolean) => void;
|
|
121
|
-
setProgress: (progress: { phase: string; percent: number } | null) => void;
|
|
122
|
-
setError: (error: string | null) => void;
|
|
123
|
-
setIfcDataStore: (result: IfcDataStore | null) => void;
|
|
124
|
-
setGeometryResult: (result: GeometryResult | null) => void;
|
|
125
|
-
appendGeometryBatch: (meshes: GeometryResult['meshes'], coordinateInfo?: CoordinateInfo) => void;
|
|
126
|
-
updateCoordinateInfo: (coordinateInfo: CoordinateInfo) => void;
|
|
127
|
-
setSelectedEntityId: (id: number | null) => void;
|
|
128
|
-
setSelectedStorey: (id: number | null) => void;
|
|
129
|
-
setLeftPanelCollapsed: (collapsed: boolean) => void;
|
|
130
|
-
setRightPanelCollapsed: (collapsed: boolean) => void;
|
|
131
|
-
setActiveTool: (tool: string) => void;
|
|
132
|
-
setTheme: (theme: 'light' | 'dark') => void;
|
|
133
|
-
toggleTheme: () => void;
|
|
134
|
-
setIsMobile: (isMobile: boolean) => void;
|
|
135
|
-
toggleHoverTooltips: () => void;
|
|
136
|
-
|
|
137
|
-
// Camera actions
|
|
138
|
-
setCameraRotation: (rotation: { azimuth: number; elevation: number }) => void;
|
|
139
|
-
setCameraCallbacks: (callbacks: ViewerState['cameraCallbacks']) => void;
|
|
140
|
-
setOnCameraRotationChange: (callback: ((rotation: { azimuth: number; elevation: number }) => void) | null) => void;
|
|
141
|
-
// Call this for real-time updates (uses callback if available, skips state)
|
|
142
|
-
updateCameraRotationRealtime: (rotation: { azimuth: number; elevation: number }) => void;
|
|
143
|
-
setOnScaleChange: (callback: ((scale: number) => void) | null) => void;
|
|
144
|
-
updateScaleRealtime: (scale: number) => void;
|
|
145
|
-
|
|
146
|
-
// Visibility actions
|
|
147
|
-
hideEntity: (id: number) => void;
|
|
148
|
-
hideEntities: (ids: number[]) => void;
|
|
149
|
-
showEntity: (id: number) => void;
|
|
150
|
-
showEntities: (ids: number[]) => void;
|
|
151
|
-
toggleEntityVisibility: (id: number) => void;
|
|
152
|
-
isolateEntity: (id: number) => void;
|
|
153
|
-
isolateEntities: (ids: number[]) => void;
|
|
154
|
-
clearIsolation: () => void;
|
|
155
|
-
showAll: () => void;
|
|
156
|
-
isEntityVisible: (id: number) => boolean;
|
|
157
|
-
|
|
158
|
-
// Multi-selection actions
|
|
159
|
-
addToSelection: (id: number) => void;
|
|
160
|
-
removeFromSelection: (id: number) => void;
|
|
161
|
-
toggleSelection: (id: number) => void;
|
|
162
|
-
setSelectedEntityIds: (ids: number[]) => void;
|
|
163
|
-
clearSelection: () => void;
|
|
164
|
-
|
|
165
|
-
// Hover actions
|
|
166
|
-
setHoverState: (state: HoverState) => void;
|
|
167
|
-
clearHover: () => void;
|
|
168
|
-
|
|
169
|
-
// Context menu actions
|
|
170
|
-
openContextMenu: (entityId: number | null, screenX: number, screenY: number) => void;
|
|
171
|
-
closeContextMenu: () => void;
|
|
172
|
-
|
|
173
|
-
// Box selection actions
|
|
174
|
-
startBoxSelect: (startX: number, startY: number) => void;
|
|
175
|
-
updateBoxSelect: (currentX: number, currentY: number) => void;
|
|
176
|
-
endBoxSelect: () => void;
|
|
177
|
-
cancelBoxSelect: () => void;
|
|
178
|
-
|
|
179
|
-
// Measurement actions
|
|
180
|
-
addMeasurePoint: (point: MeasurePoint) => void;
|
|
181
|
-
completeMeasurement: (endPoint: MeasurePoint) => void;
|
|
182
|
-
deleteMeasurement: (id: string) => void;
|
|
183
|
-
clearMeasurements: () => void;
|
|
184
|
-
|
|
185
|
-
// Section plane actions
|
|
186
|
-
setSectionPlaneAxis: (axis: 'x' | 'y' | 'z') => void;
|
|
187
|
-
setSectionPlanePosition: (position: number) => void;
|
|
188
|
-
toggleSectionPlane: () => void;
|
|
189
|
-
flipSectionPlane: () => void;
|
|
190
|
-
resetSectionPlane: () => void;
|
|
191
|
-
|
|
192
|
-
// Reset all viewer state (called when loading new file)
|
|
193
|
-
resetViewerState: () => void;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export const useViewerStore = create<ViewerState>((set, get) => ({
|
|
197
|
-
loading: false,
|
|
198
|
-
progress: null,
|
|
199
|
-
error: null,
|
|
200
|
-
ifcDataStore: null,
|
|
201
|
-
geometryResult: null,
|
|
202
|
-
selectedEntityId: null,
|
|
203
|
-
selectedEntityIds: new Set(),
|
|
204
|
-
selectedStorey: null,
|
|
205
|
-
hiddenEntities: new Set(),
|
|
206
|
-
isolatedEntities: null,
|
|
207
|
-
leftPanelCollapsed: false,
|
|
208
|
-
rightPanelCollapsed: false,
|
|
209
|
-
activeTool: 'select',
|
|
210
|
-
theme: 'dark',
|
|
211
|
-
isMobile: false,
|
|
212
|
-
hoverTooltipsEnabled: false,
|
|
213
|
-
hoverState: { entityId: null, screenX: 0, screenY: 0 },
|
|
214
|
-
contextMenu: { isOpen: false, entityId: null, screenX: 0, screenY: 0 },
|
|
215
|
-
boxSelect: { isSelecting: false, startX: 0, startY: 0, currentX: 0, currentY: 0 },
|
|
216
|
-
measurements: [],
|
|
217
|
-
pendingMeasurePoint: null,
|
|
218
|
-
sectionPlane: { axis: 'y', position: 50, enabled: false },
|
|
219
|
-
cameraRotation: { azimuth: 45, elevation: 25 },
|
|
220
|
-
cameraCallbacks: {},
|
|
221
|
-
onCameraRotationChange: null,
|
|
222
|
-
onScaleChange: null,
|
|
223
|
-
|
|
224
|
-
setLoading: (loading) => set({ loading }),
|
|
225
|
-
setProgress: (progress) => set({ progress }),
|
|
226
|
-
setError: (error) => set({ error }),
|
|
227
|
-
setIfcDataStore: (ifcDataStore) => set({ ifcDataStore }),
|
|
228
|
-
setGeometryResult: (geometryResult) => set({ geometryResult }),
|
|
229
|
-
appendGeometryBatch: (meshes, coordinateInfo) => set((state) => {
|
|
230
|
-
if (!state.geometryResult) {
|
|
231
|
-
const totalTriangles = meshes.reduce((sum, m) => sum + (m.indices.length / 3), 0);
|
|
232
|
-
const totalVertices = meshes.reduce((sum, m) => sum + (m.positions.length / 3), 0);
|
|
233
|
-
return {
|
|
234
|
-
geometryResult: {
|
|
235
|
-
meshes,
|
|
236
|
-
totalTriangles,
|
|
237
|
-
totalVertices,
|
|
238
|
-
coordinateInfo: coordinateInfo || {
|
|
239
|
-
originShift: { x: 0, y: 0, z: 0 },
|
|
240
|
-
originalBounds: { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 } },
|
|
241
|
-
shiftedBounds: { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 } },
|
|
242
|
-
isGeoReferenced: false,
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
const allMeshes = [...state.geometryResult.meshes, ...meshes];
|
|
248
|
-
const totalTriangles = allMeshes.reduce((sum, m) => sum + (m.indices.length / 3), 0);
|
|
249
|
-
const totalVertices = allMeshes.reduce((sum, m) => sum + (m.positions.length / 3), 0);
|
|
250
|
-
return {
|
|
251
|
-
geometryResult: {
|
|
252
|
-
...state.geometryResult,
|
|
253
|
-
meshes: allMeshes,
|
|
254
|
-
totalTriangles,
|
|
255
|
-
totalVertices,
|
|
256
|
-
coordinateInfo: coordinateInfo || state.geometryResult.coordinateInfo,
|
|
257
|
-
},
|
|
258
|
-
};
|
|
259
|
-
}),
|
|
260
|
-
updateCoordinateInfo: (coordinateInfo) => set((state) => {
|
|
261
|
-
if (!state.geometryResult) return {};
|
|
262
|
-
return {
|
|
263
|
-
geometryResult: {
|
|
264
|
-
...state.geometryResult,
|
|
265
|
-
coordinateInfo,
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
|
-
}),
|
|
269
|
-
setSelectedEntityId: (selectedEntityId) => set({ selectedEntityId }),
|
|
270
|
-
setSelectedStorey: (selectedStorey) => set({ selectedStorey }),
|
|
271
|
-
setLeftPanelCollapsed: (leftPanelCollapsed) => set({ leftPanelCollapsed }),
|
|
272
|
-
setRightPanelCollapsed: (rightPanelCollapsed) => set({ rightPanelCollapsed }),
|
|
273
|
-
setActiveTool: (activeTool) => set({ activeTool }),
|
|
274
|
-
setTheme: (theme) => {
|
|
275
|
-
document.documentElement.classList.toggle('dark', theme === 'dark');
|
|
276
|
-
set({ theme });
|
|
277
|
-
},
|
|
278
|
-
toggleTheme: () => {
|
|
279
|
-
const newTheme = get().theme === 'dark' ? 'light' : 'dark';
|
|
280
|
-
document.documentElement.classList.toggle('dark', newTheme === 'dark');
|
|
281
|
-
set({ theme: newTheme });
|
|
282
|
-
},
|
|
283
|
-
setIsMobile: (isMobile) => set({ isMobile }),
|
|
284
|
-
toggleHoverTooltips: () => set((state) => ({ hoverTooltipsEnabled: !state.hoverTooltipsEnabled })),
|
|
285
|
-
|
|
286
|
-
// Camera actions
|
|
287
|
-
setCameraRotation: (cameraRotation) => set({ cameraRotation }),
|
|
288
|
-
setCameraCallbacks: (cameraCallbacks) => set({ cameraCallbacks }),
|
|
289
|
-
setOnCameraRotationChange: (onCameraRotationChange) => set({ onCameraRotationChange }),
|
|
290
|
-
updateCameraRotationRealtime: (rotation) => {
|
|
291
|
-
const callback = get().onCameraRotationChange;
|
|
292
|
-
if (callback) {
|
|
293
|
-
// Use direct callback - no React state update, no re-renders
|
|
294
|
-
callback(rotation);
|
|
295
|
-
}
|
|
296
|
-
// Don't update store state during real-time updates
|
|
297
|
-
},
|
|
298
|
-
setOnScaleChange: (onScaleChange) => set({ onScaleChange }),
|
|
299
|
-
updateScaleRealtime: (scale) => {
|
|
300
|
-
const callback = get().onScaleChange;
|
|
301
|
-
if (callback) {
|
|
302
|
-
// Use direct callback - no React state update, no re-renders
|
|
303
|
-
callback(scale);
|
|
304
|
-
}
|
|
305
|
-
// Don't update store state during real-time updates
|
|
306
|
-
},
|
|
307
|
-
|
|
308
|
-
// Visibility actions
|
|
309
|
-
hideEntity: (id) => set((state) => {
|
|
310
|
-
// Toggle hide: if already hidden, show it; otherwise hide it
|
|
311
|
-
const newHidden = new Set(state.hiddenEntities);
|
|
312
|
-
if (newHidden.has(id)) {
|
|
313
|
-
newHidden.delete(id);
|
|
314
|
-
} else {
|
|
315
|
-
newHidden.add(id);
|
|
316
|
-
}
|
|
317
|
-
return { hiddenEntities: newHidden };
|
|
318
|
-
}),
|
|
319
|
-
hideEntities: (ids) => set((state) => {
|
|
320
|
-
// Toggle hide for each entity: if already hidden, show it; otherwise hide it
|
|
321
|
-
const newHidden = new Set(state.hiddenEntities);
|
|
322
|
-
ids.forEach(id => {
|
|
323
|
-
if (newHidden.has(id)) {
|
|
324
|
-
newHidden.delete(id);
|
|
325
|
-
} else {
|
|
326
|
-
newHidden.add(id);
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
return { hiddenEntities: newHidden };
|
|
330
|
-
}),
|
|
331
|
-
showEntity: (id) => set((state) => {
|
|
332
|
-
const newHidden = new Set(state.hiddenEntities);
|
|
333
|
-
newHidden.delete(id);
|
|
334
|
-
return { hiddenEntities: newHidden };
|
|
335
|
-
}),
|
|
336
|
-
showEntities: (ids) => set((state) => {
|
|
337
|
-
const newHidden = new Set(state.hiddenEntities);
|
|
338
|
-
ids.forEach(id => newHidden.delete(id));
|
|
339
|
-
return { hiddenEntities: newHidden };
|
|
340
|
-
}),
|
|
341
|
-
toggleEntityVisibility: (id) => set((state) => {
|
|
342
|
-
const newHidden = new Set(state.hiddenEntities);
|
|
343
|
-
if (newHidden.has(id)) {
|
|
344
|
-
newHidden.delete(id);
|
|
345
|
-
} else {
|
|
346
|
-
newHidden.add(id);
|
|
347
|
-
}
|
|
348
|
-
return { hiddenEntities: newHidden };
|
|
349
|
-
}),
|
|
350
|
-
isolateEntity: (id) => set((state) => {
|
|
351
|
-
// Toggle isolate: if this entity is already the only isolated one, clear isolation
|
|
352
|
-
// Otherwise, isolate it (and unhide it for good UX)
|
|
353
|
-
const isAlreadyIsolated = state.isolatedEntities !== null &&
|
|
354
|
-
state.isolatedEntities.size === 1 &&
|
|
355
|
-
state.isolatedEntities.has(id);
|
|
356
|
-
|
|
357
|
-
if (isAlreadyIsolated) {
|
|
358
|
-
// Toggle off: clear isolation
|
|
359
|
-
return { isolatedEntities: null };
|
|
360
|
-
} else {
|
|
361
|
-
// Toggle on: isolate this entity (and unhide it)
|
|
362
|
-
const newHidden = new Set(state.hiddenEntities);
|
|
363
|
-
newHidden.delete(id);
|
|
364
|
-
return {
|
|
365
|
-
isolatedEntities: new Set([id]),
|
|
366
|
-
hiddenEntities: newHidden,
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
}),
|
|
370
|
-
isolateEntities: (ids) => set((state) => {
|
|
371
|
-
// Toggle isolate: if these exact entities are already isolated, clear isolation
|
|
372
|
-
// Otherwise, isolate them (and unhide them for good UX)
|
|
373
|
-
const idsSet = new Set(ids);
|
|
374
|
-
const isAlreadyIsolated = state.isolatedEntities !== null &&
|
|
375
|
-
state.isolatedEntities.size === idsSet.size &&
|
|
376
|
-
ids.every(id => state.isolatedEntities!.has(id));
|
|
377
|
-
|
|
378
|
-
if (isAlreadyIsolated) {
|
|
379
|
-
// Toggle off: clear isolation
|
|
380
|
-
return { isolatedEntities: null };
|
|
381
|
-
} else {
|
|
382
|
-
// Toggle on: isolate these entities (and unhide them)
|
|
383
|
-
const newHidden = new Set(state.hiddenEntities);
|
|
384
|
-
ids.forEach(id => newHidden.delete(id));
|
|
385
|
-
return {
|
|
386
|
-
isolatedEntities: idsSet,
|
|
387
|
-
hiddenEntities: newHidden,
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
}),
|
|
391
|
-
clearIsolation: () => set({ isolatedEntities: null }),
|
|
392
|
-
showAll: () => set({ hiddenEntities: new Set(), isolatedEntities: null }),
|
|
393
|
-
isEntityVisible: (id) => {
|
|
394
|
-
const state = get();
|
|
395
|
-
if (state.hiddenEntities.has(id)) return false;
|
|
396
|
-
if (state.isolatedEntities !== null && !state.isolatedEntities.has(id)) return false;
|
|
397
|
-
return true;
|
|
398
|
-
},
|
|
399
|
-
|
|
400
|
-
// Multi-selection actions
|
|
401
|
-
addToSelection: (id) => set((state) => {
|
|
402
|
-
const newSelection = new Set(state.selectedEntityIds);
|
|
403
|
-
newSelection.add(id);
|
|
404
|
-
return { selectedEntityIds: newSelection, selectedEntityId: id };
|
|
405
|
-
}),
|
|
406
|
-
removeFromSelection: (id) => set((state) => {
|
|
407
|
-
const newSelection = new Set(state.selectedEntityIds);
|
|
408
|
-
newSelection.delete(id);
|
|
409
|
-
const remaining = Array.from(newSelection);
|
|
410
|
-
return {
|
|
411
|
-
selectedEntityIds: newSelection,
|
|
412
|
-
selectedEntityId: remaining.length > 0 ? remaining[remaining.length - 1] : null,
|
|
413
|
-
};
|
|
414
|
-
}),
|
|
415
|
-
toggleSelection: (id) => set((state) => {
|
|
416
|
-
const newSelection = new Set(state.selectedEntityIds);
|
|
417
|
-
if (newSelection.has(id)) {
|
|
418
|
-
newSelection.delete(id);
|
|
419
|
-
} else {
|
|
420
|
-
newSelection.add(id);
|
|
421
|
-
}
|
|
422
|
-
const remaining = Array.from(newSelection);
|
|
423
|
-
return {
|
|
424
|
-
selectedEntityIds: newSelection,
|
|
425
|
-
selectedEntityId: remaining.length > 0 ? remaining[remaining.length - 1] : null,
|
|
426
|
-
};
|
|
427
|
-
}),
|
|
428
|
-
setSelectedEntityIds: (ids) => set({
|
|
429
|
-
selectedEntityIds: new Set(ids),
|
|
430
|
-
selectedEntityId: ids.length > 0 ? ids[ids.length - 1] : null,
|
|
431
|
-
}),
|
|
432
|
-
clearSelection: () => set({
|
|
433
|
-
selectedEntityIds: new Set(),
|
|
434
|
-
selectedEntityId: null,
|
|
435
|
-
}),
|
|
436
|
-
|
|
437
|
-
// Hover actions
|
|
438
|
-
setHoverState: (hoverState) => set({ hoverState }),
|
|
439
|
-
clearHover: () => set({ hoverState: { entityId: null, screenX: 0, screenY: 0 } }),
|
|
440
|
-
|
|
441
|
-
// Context menu actions
|
|
442
|
-
openContextMenu: (entityId, screenX, screenY) => set({
|
|
443
|
-
contextMenu: { isOpen: true, entityId, screenX, screenY },
|
|
444
|
-
}),
|
|
445
|
-
closeContextMenu: () => set({
|
|
446
|
-
contextMenu: { isOpen: false, entityId: null, screenX: 0, screenY: 0 },
|
|
447
|
-
}),
|
|
448
|
-
|
|
449
|
-
// Box selection actions
|
|
450
|
-
startBoxSelect: (startX, startY) => set({
|
|
451
|
-
boxSelect: { isSelecting: true, startX, startY, currentX: startX, currentY: startY },
|
|
452
|
-
}),
|
|
453
|
-
updateBoxSelect: (currentX, currentY) => set((state) => ({
|
|
454
|
-
boxSelect: { ...state.boxSelect, currentX, currentY },
|
|
455
|
-
})),
|
|
456
|
-
endBoxSelect: () => set({
|
|
457
|
-
boxSelect: { isSelecting: false, startX: 0, startY: 0, currentX: 0, currentY: 0 },
|
|
458
|
-
}),
|
|
459
|
-
cancelBoxSelect: () => set({
|
|
460
|
-
boxSelect: { isSelecting: false, startX: 0, startY: 0, currentX: 0, currentY: 0 },
|
|
461
|
-
}),
|
|
462
|
-
|
|
463
|
-
// Measurement actions
|
|
464
|
-
addMeasurePoint: (point) => set({ pendingMeasurePoint: point }),
|
|
465
|
-
completeMeasurement: (endPoint) => set((state) => {
|
|
466
|
-
if (!state.pendingMeasurePoint) return {};
|
|
467
|
-
const start = state.pendingMeasurePoint;
|
|
468
|
-
const distance = Math.sqrt(
|
|
469
|
-
Math.pow(endPoint.x - start.x, 2) +
|
|
470
|
-
Math.pow(endPoint.y - start.y, 2) +
|
|
471
|
-
Math.pow(endPoint.z - start.z, 2)
|
|
472
|
-
);
|
|
473
|
-
const measurement: Measurement = {
|
|
474
|
-
id: `m-${Date.now()}`,
|
|
475
|
-
start,
|
|
476
|
-
end: endPoint,
|
|
477
|
-
distance,
|
|
478
|
-
};
|
|
479
|
-
return {
|
|
480
|
-
measurements: [...state.measurements, measurement],
|
|
481
|
-
pendingMeasurePoint: null,
|
|
482
|
-
};
|
|
483
|
-
}),
|
|
484
|
-
deleteMeasurement: (id) => set((state) => ({
|
|
485
|
-
measurements: state.measurements.filter((m) => m.id !== id),
|
|
486
|
-
})),
|
|
487
|
-
clearMeasurements: () => set({ measurements: [], pendingMeasurePoint: null }),
|
|
488
|
-
|
|
489
|
-
// Section plane actions
|
|
490
|
-
setSectionPlaneAxis: (axis) => set((state) => ({
|
|
491
|
-
sectionPlane: { ...state.sectionPlane, axis },
|
|
492
|
-
})),
|
|
493
|
-
setSectionPlanePosition: (position) => set((state) => ({
|
|
494
|
-
sectionPlane: { ...state.sectionPlane, position },
|
|
495
|
-
})),
|
|
496
|
-
toggleSectionPlane: () => set((state) => ({
|
|
497
|
-
sectionPlane: { ...state.sectionPlane, enabled: !state.sectionPlane.enabled },
|
|
498
|
-
})),
|
|
499
|
-
flipSectionPlane: () => set((state) => ({
|
|
500
|
-
sectionPlane: { ...state.sectionPlane, position: 100 - state.sectionPlane.position },
|
|
501
|
-
})),
|
|
502
|
-
resetSectionPlane: () => set({
|
|
503
|
-
sectionPlane: { axis: 'y', position: 50, enabled: false },
|
|
504
|
-
}),
|
|
505
|
-
|
|
506
|
-
// Reset all viewer state when loading new file
|
|
507
|
-
resetViewerState: () => set({
|
|
508
|
-
selectedEntityId: null,
|
|
509
|
-
selectedEntityIds: new Set(),
|
|
510
|
-
selectedStorey: null,
|
|
511
|
-
hiddenEntities: new Set(),
|
|
512
|
-
isolatedEntities: null,
|
|
513
|
-
hoverState: { entityId: null, screenX: 0, screenY: 0 },
|
|
514
|
-
contextMenu: { isOpen: false, entityId: null, screenX: 0, screenY: 0 },
|
|
515
|
-
boxSelect: { isSelecting: false, startX: 0, startY: 0, currentX: 0, currentY: 0 },
|
|
516
|
-
measurements: [],
|
|
517
|
-
pendingMeasurePoint: null,
|
|
518
|
-
sectionPlane: { axis: 'y', position: 50, enabled: false },
|
|
519
|
-
cameraRotation: { azimuth: 45, elevation: 25 },
|
|
520
|
-
activeTool: 'select',
|
|
521
|
-
}),
|
|
522
|
-
}));
|
|
22
|
+
// Re-export everything from the modular store
|
|
23
|
+
export { useViewerStore } from './store/index.js';
|
|
24
|
+
export type { ViewerState } from './store/index.js';
|
|
25
|
+
|
|
26
|
+
// Re-export types for backward compatibility
|
|
27
|
+
export type {
|
|
28
|
+
MeasurePoint,
|
|
29
|
+
Measurement,
|
|
30
|
+
ActiveMeasurement,
|
|
31
|
+
EdgeLockState,
|
|
32
|
+
SectionPlaneAxis,
|
|
33
|
+
SectionPlane,
|
|
34
|
+
HoverState,
|
|
35
|
+
ContextMenuState,
|
|
36
|
+
SnapVisualization,
|
|
37
|
+
TypeVisibility,
|
|
38
|
+
CameraRotation,
|
|
39
|
+
CameraCallbacks,
|
|
40
|
+
// Multi-model federation types
|
|
41
|
+
EntityRef,
|
|
42
|
+
SchemaVersion,
|
|
43
|
+
FederatedModel,
|
|
44
|
+
} from './store/types.js';
|
|
45
|
+
|
|
46
|
+
// Re-export utility functions for multi-model federation
|
|
47
|
+
export { entityRefToString, stringToEntityRef, entityRefEquals, isIfcxDataStore } from './store/types.js';
|
|
@@ -0,0 +1,82 @@
|
|
|
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 loading configuration constants and utilities
|
|
7
|
+
* Extracted from useIfc.ts for reusability
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { DynamicBatchConfig } from '@ifc-lite/geometry';
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Server Configuration
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/** IFC server URL - only set via environment variable, no default (pure client-side by default) */
|
|
17
|
+
export const SERVER_URL = import.meta.env.VITE_IFC_SERVER_URL || import.meta.env.VITE_SERVER_URL || '';
|
|
18
|
+
|
|
19
|
+
/** Enable server parsing - only if server URL is explicitly configured */
|
|
20
|
+
export const USE_SERVER = SERVER_URL !== '' && import.meta.env.VITE_USE_SERVER !== 'false';
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// File Size Thresholds (in bytes unless noted)
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
/** Minimum file size to cache (10MB) - smaller files parse quickly anyway */
|
|
27
|
+
export const CACHE_SIZE_THRESHOLD = 10 * 1024 * 1024;
|
|
28
|
+
|
|
29
|
+
/** File size thresholds for various optimizations */
|
|
30
|
+
export const THRESHOLDS = {
|
|
31
|
+
/** Use streaming Parquet above this (150MB) */
|
|
32
|
+
STREAMING_MB: 150,
|
|
33
|
+
/** Use Parquet vs JSON above this (10MB) */
|
|
34
|
+
PARQUET_MB: 10,
|
|
35
|
+
/** Large file threshold affecting batch sizing (50MB) */
|
|
36
|
+
LARGE_FILE_MB: 50,
|
|
37
|
+
/** Huge file threshold for aggressive batching (100MB) */
|
|
38
|
+
HUGE_FILE_MB: 100,
|
|
39
|
+
/** Don't cache files smaller than this (10MB) */
|
|
40
|
+
CACHE_MIN_MB: 10,
|
|
41
|
+
} as const;
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Platform Detection
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
/** Detect if running in Tauri (desktop) environment */
|
|
48
|
+
export const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window;
|
|
49
|
+
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// Dynamic Batch Configuration
|
|
52
|
+
// ============================================================================
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Calculate dynamic batch config based on file size
|
|
56
|
+
* Larger files get larger batches for better throughput
|
|
57
|
+
*
|
|
58
|
+
* @param fileSizeMB - File size in megabytes
|
|
59
|
+
* @returns Batch configuration for geometry processing
|
|
60
|
+
*/
|
|
61
|
+
export function getDynamicBatchConfig(fileSizeMB: number): DynamicBatchConfig {
|
|
62
|
+
if (fileSizeMB < 10) {
|
|
63
|
+
// Small files: smaller batches for responsiveness
|
|
64
|
+
return { initialBatchSize: 50, maxBatchSize: 200, fileSizeMB };
|
|
65
|
+
} else if (fileSizeMB < 50) {
|
|
66
|
+
// Medium files: balanced batching
|
|
67
|
+
return { initialBatchSize: 100, maxBatchSize: 500, fileSizeMB };
|
|
68
|
+
} else if (fileSizeMB < 100) {
|
|
69
|
+
// Large files: larger batches for throughput
|
|
70
|
+
return { initialBatchSize: 100, maxBatchSize: 1000, fileSizeMB };
|
|
71
|
+
} else {
|
|
72
|
+
// Huge files (100MB+): aggressive batching for maximum throughput
|
|
73
|
+
return { initialBatchSize: 100, maxBatchSize: 3000, fileSizeMB };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Convert bytes to megabytes
|
|
79
|
+
*/
|
|
80
|
+
export function bytesToMB(bytes: number): number {
|
|
81
|
+
return bytes / (1024 * 1024);
|
|
82
|
+
}
|