@ifc-lite/viewer 1.1.7 → 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.
Files changed (164) hide show
  1. package/LICENSE +373 -0
  2. package/dist/apple-touch-icon.png +0 -0
  3. package/dist/assets/Arrow.dom-B0e15b_b.js +20 -0
  4. package/dist/assets/arrow2-bb-jcVEo.js +2 -0
  5. package/dist/assets/arrow2_bg-4Y7xYo54.wasm +0 -0
  6. package/dist/assets/arrow2_bg-BlXl-cSQ.js +1 -0
  7. package/dist/assets/arrow2_bg-BoXCojjR.wasm +0 -0
  8. package/dist/assets/desktop-cache-oPzaWXYE.js +1 -0
  9. package/dist/assets/event-DIOks52T.js +1 -0
  10. package/dist/assets/ifc-cache-BAN4vcd4.js +1 -0
  11. package/dist/assets/ifc-lite_bg-C6kblxf9.wasm +0 -0
  12. package/dist/assets/index-Dgd6vzw_.js +65252 -0
  13. package/dist/assets/index-v3mcCUPN.css +1 -0
  14. package/dist/assets/native-bridge-Ci7NLjlZ.js +111 -0
  15. package/dist/assets/wasm-bridge-Dc82YpdZ.js +1 -0
  16. package/dist/favicon-16x16-cropped.png +0 -0
  17. package/dist/favicon-16x16.png +0 -0
  18. package/dist/favicon-192x192-cropped.png +0 -0
  19. package/dist/favicon-192x192.png +0 -0
  20. package/dist/favicon-32x32-cropped.png +0 -0
  21. package/dist/favicon-32x32.png +0 -0
  22. package/dist/favicon-48x48-cropped.png +0 -0
  23. package/dist/favicon-48x48.png +0 -0
  24. package/dist/favicon-512x512-cropped.png +0 -0
  25. package/dist/favicon-512x512.png +0 -0
  26. package/dist/favicon-64x64-cropped.png +0 -0
  27. package/dist/favicon-64x64.png +0 -0
  28. package/dist/favicon-96x96-cropped.png +0 -0
  29. package/dist/favicon-96x96.png +0 -0
  30. package/dist/favicon-square-512.png +0 -0
  31. package/dist/favicon.ico +0 -0
  32. package/dist/favicon.png +0 -0
  33. package/dist/favicon.svg +3 -0
  34. package/dist/index.html +44 -0
  35. package/dist/logo.png +0 -0
  36. package/dist/manifest.json +48 -0
  37. package/index.html +33 -2
  38. package/package.json +34 -17
  39. package/public/apple-touch-icon.png +0 -0
  40. package/public/favicon-16x16-cropped.png +0 -0
  41. package/public/favicon-16x16.png +0 -0
  42. package/public/favicon-192x192-cropped.png +0 -0
  43. package/public/favicon-192x192.png +0 -0
  44. package/public/favicon-32x32-cropped.png +0 -0
  45. package/public/favicon-32x32.png +0 -0
  46. package/public/favicon-48x48-cropped.png +0 -0
  47. package/public/favicon-48x48.png +0 -0
  48. package/public/favicon-512x512-cropped.png +0 -0
  49. package/public/favicon-512x512.png +0 -0
  50. package/public/favicon-64x64-cropped.png +0 -0
  51. package/public/favicon-64x64.png +0 -0
  52. package/public/favicon-96x96-cropped.png +0 -0
  53. package/public/favicon-96x96.png +0 -0
  54. package/public/favicon-square-512.png +0 -0
  55. package/public/favicon.ico +0 -0
  56. package/public/favicon.png +0 -0
  57. package/public/favicon.svg +3 -0
  58. package/public/logo.png +0 -0
  59. package/public/manifest.json +48 -0
  60. package/src/App.tsx +2 -0
  61. package/src/components/ui/alert.tsx +62 -0
  62. package/src/components/ui/badge.tsx +39 -0
  63. package/src/components/ui/dialog.tsx +120 -0
  64. package/src/components/ui/label.tsx +27 -0
  65. package/src/components/ui/select.tsx +151 -0
  66. package/src/components/ui/switch.tsx +30 -0
  67. package/src/components/ui/table.tsx +120 -0
  68. package/src/components/ui/tabs.tsx +1 -1
  69. package/src/components/viewer/BCFPanel.tsx +1164 -0
  70. package/src/components/viewer/BulkPropertyEditor.tsx +875 -0
  71. package/src/components/viewer/DataConnector.tsx +840 -0
  72. package/src/components/viewer/DrawingSettingsPanel.tsx +536 -0
  73. package/src/components/viewer/EntityContextMenu.tsx +45 -17
  74. package/src/components/viewer/ExportChangesButton.tsx +195 -0
  75. package/src/components/viewer/ExportDialog.tsx +402 -0
  76. package/src/components/viewer/HierarchyPanel.tsx +1132 -218
  77. package/src/components/viewer/IDSPanel.tsx +661 -0
  78. package/src/components/viewer/KeyboardShortcutsDialog.tsx +245 -39
  79. package/src/components/viewer/MainToolbar.tsx +418 -94
  80. package/src/components/viewer/PropertiesPanel.tsx +1355 -91
  81. package/src/components/viewer/PropertyEditor.tsx +611 -0
  82. package/src/components/viewer/Section2DPanel.tsx +3313 -0
  83. package/src/components/viewer/SheetSetupPanel.tsx +502 -0
  84. package/src/components/viewer/StatusBar.tsx +27 -16
  85. package/src/components/viewer/TitleBlockEditor.tsx +437 -0
  86. package/src/components/viewer/ToolOverlays.tsx +935 -127
  87. package/src/components/viewer/ViewerLayout.tsx +40 -11
  88. package/src/components/viewer/Viewport.tsx +1276 -336
  89. package/src/components/viewer/ViewportContainer.tsx +554 -18
  90. package/src/components/viewer/ViewportOverlays.tsx +24 -7
  91. package/src/hooks/useBCF.ts +504 -0
  92. package/src/hooks/useIDS.ts +1065 -0
  93. package/src/hooks/useIfc.ts +1534 -205
  94. package/src/hooks/useIfcCache.ts +279 -0
  95. package/src/hooks/useKeyboardShortcuts.ts +50 -8
  96. package/src/hooks/useModelSelection.ts +61 -0
  97. package/src/hooks/useViewerSelectors.ts +218 -0
  98. package/src/hooks/useWebGPU.ts +80 -0
  99. package/src/index.css +265 -27
  100. package/src/lib/platform.ts +23 -0
  101. package/src/services/cacheService.ts +142 -0
  102. package/src/services/desktop-cache.ts +143 -0
  103. package/src/services/fs-cache.ts +212 -0
  104. package/src/services/ifc-cache.ts +14 -6
  105. package/src/store/constants.ts +85 -0
  106. package/src/store/index.ts +214 -0
  107. package/src/store/slices/bcfSlice.ts +372 -0
  108. package/src/store/slices/cameraSlice.ts +63 -0
  109. package/src/store/slices/dataSlice.test.ts +226 -0
  110. package/src/store/slices/dataSlice.ts +112 -0
  111. package/src/store/slices/drawing2DSlice.ts +340 -0
  112. package/src/store/slices/hoverSlice.ts +40 -0
  113. package/src/store/slices/idsSlice.ts +310 -0
  114. package/src/store/slices/loadingSlice.ts +33 -0
  115. package/src/store/slices/measurementSlice.test.ts +217 -0
  116. package/src/store/slices/measurementSlice.ts +293 -0
  117. package/src/store/slices/modelSlice.test.ts +271 -0
  118. package/src/store/slices/modelSlice.ts +211 -0
  119. package/src/store/slices/mutationSlice.ts +502 -0
  120. package/src/store/slices/sectionSlice.test.ts +125 -0
  121. package/src/store/slices/sectionSlice.ts +58 -0
  122. package/src/store/slices/selectionSlice.test.ts +286 -0
  123. package/src/store/slices/selectionSlice.ts +263 -0
  124. package/src/store/slices/sheetSlice.ts +565 -0
  125. package/src/store/slices/uiSlice.ts +58 -0
  126. package/src/store/slices/visibilitySlice.test.ts +304 -0
  127. package/src/store/slices/visibilitySlice.ts +277 -0
  128. package/src/store/types.test.ts +135 -0
  129. package/src/store/types.ts +248 -0
  130. package/src/store.ts +40 -515
  131. package/src/utils/ifcConfig.ts +82 -0
  132. package/src/utils/localParsingUtils.ts +287 -0
  133. package/src/utils/serverDataModel.ts +783 -0
  134. package/src/utils/spatialHierarchy.ts +283 -0
  135. package/src/utils/viewportUtils.ts +334 -0
  136. package/src/vite-env.d.ts +23 -0
  137. package/src/webgpu-types.d.ts +128 -0
  138. package/src-tauri/Cargo.toml +29 -0
  139. package/src-tauri/build.rs +7 -0
  140. package/src-tauri/capabilities/default.json +18 -0
  141. package/src-tauri/icons/128x128.png +0 -0
  142. package/src-tauri/icons/128x128@2x.png +0 -0
  143. package/src-tauri/icons/32x32.png +0 -0
  144. package/src-tauri/icons/Square107x107Logo.png +0 -0
  145. package/src-tauri/icons/Square142x142Logo.png +0 -0
  146. package/src-tauri/icons/Square150x150Logo.png +0 -0
  147. package/src-tauri/icons/Square284x284Logo.png +0 -0
  148. package/src-tauri/icons/Square30x30Logo.png +0 -0
  149. package/src-tauri/icons/Square310x310Logo.png +0 -0
  150. package/src-tauri/icons/Square44x44Logo.png +0 -0
  151. package/src-tauri/icons/Square71x71Logo.png +0 -0
  152. package/src-tauri/icons/Square89x89Logo.png +0 -0
  153. package/src-tauri/icons/StoreLogo.png +0 -0
  154. package/src-tauri/icons/icon.icns +0 -0
  155. package/src-tauri/icons/icon.ico +0 -0
  156. package/src-tauri/icons/icon.png +0 -0
  157. package/src-tauri/src/lib.rs +21 -0
  158. package/src-tauri/src/main.rs +10 -0
  159. package/src-tauri/tauri.conf.json +39 -0
  160. package/vite.config.ts +174 -26
  161. package/public/ifc-lite_bg.wasm +0 -0
  162. package/public/web-ifc.wasm +0 -0
  163. package/src/components/Viewport.tsx +0 -723
  164. package/src/components/viewer/BoxSelectionOverlay.tsx +0 -53
@@ -0,0 +1,287 @@
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
+ * Local parsing utility functions
7
+ * Pure functions for geometry processing, bounds calculation, and batch management
8
+ *
9
+ * Extracted from useIfc.ts for reusability and testability
10
+ */
11
+
12
+ import type { MeshData } from '@ifc-lite/geometry';
13
+
14
+ // ============================================================================
15
+ // Types
16
+ // ============================================================================
17
+
18
+ /**
19
+ * Bounding box for 3D geometry
20
+ */
21
+ export interface Bounds3D {
22
+ min: { x: number; y: number; z: number };
23
+ max: { x: number; y: number; z: number };
24
+ }
25
+
26
+ /**
27
+ * Coordinate information for geometry
28
+ */
29
+ export interface CoordinateInfo {
30
+ originShift: { x: number; y: number; z: number };
31
+ originalBounds: Bounds3D;
32
+ shiftedBounds: Bounds3D;
33
+ /** True if model had large coordinates requiring RTC shift. NOT the same as proper georeferencing via IfcMapConversion. */
34
+ hasLargeCoordinates: boolean;
35
+ }
36
+
37
+ /**
38
+ * Geometry statistics
39
+ */
40
+ export interface GeometryStats {
41
+ totalVertices: number;
42
+ totalTriangles: number;
43
+ }
44
+
45
+ // ============================================================================
46
+ // Bounds Calculation
47
+ // ============================================================================
48
+
49
+ /**
50
+ * Maximum coordinate threshold for valid geometry (10km)
51
+ * Matches CoordinateHandler's NORMAL_COORD_THRESHOLD
52
+ * Coordinates beyond this are likely corrupted or unshifted original coordinates
53
+ */
54
+ export const MAX_VALID_COORD = 10000;
55
+
56
+ /**
57
+ * Create an initial bounds object with infinite values
58
+ * Use this as a starting point for incremental bounds calculation
59
+ */
60
+ export function createEmptyBounds(): Bounds3D {
61
+ return {
62
+ min: { x: Infinity, y: Infinity, z: Infinity },
63
+ max: { x: -Infinity, y: -Infinity, z: -Infinity },
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Update bounds incrementally from a mesh's positions
69
+ * Mutates the bounds object for performance
70
+ *
71
+ * @param bounds - Bounds object to update (mutated)
72
+ * @param positions - Float32Array of vertex positions (x,y,z triplets)
73
+ * @param maxCoord - Maximum valid coordinate value (default: 10km)
74
+ */
75
+ export function updateBoundsFromPositions(
76
+ bounds: Bounds3D,
77
+ positions: Float32Array | number[],
78
+ maxCoord: number = MAX_VALID_COORD
79
+ ): void {
80
+ for (let i = 0; i < positions.length; i += 3) {
81
+ const x = positions[i];
82
+ const y = positions[i + 1];
83
+ const z = positions[i + 2];
84
+ // Filter out corrupted/unshifted vertices (> threshold from origin)
85
+ const isValid = Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) &&
86
+ Math.abs(x) < maxCoord && Math.abs(y) < maxCoord && Math.abs(z) < maxCoord;
87
+ if (isValid) {
88
+ bounds.min.x = Math.min(bounds.min.x, x);
89
+ bounds.min.y = Math.min(bounds.min.y, y);
90
+ bounds.min.z = Math.min(bounds.min.z, z);
91
+ bounds.max.x = Math.max(bounds.max.x, x);
92
+ bounds.max.y = Math.max(bounds.max.y, y);
93
+ bounds.max.z = Math.max(bounds.max.z, z);
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Calculate bounds from an array of meshes
100
+ *
101
+ * @param meshes - Array of mesh data
102
+ * @returns Bounds and geometry statistics
103
+ */
104
+ export function calculateMeshBounds(meshes: MeshData[]): { bounds: Bounds3D; stats: GeometryStats } {
105
+ const bounds = createEmptyBounds();
106
+ let totalVertices = 0;
107
+ let totalTriangles = 0;
108
+
109
+ for (const mesh of meshes) {
110
+ updateBoundsFromPositions(bounds, mesh.positions);
111
+ totalVertices += mesh.positions.length / 3;
112
+ totalTriangles += mesh.indices.length / 3;
113
+ }
114
+
115
+ return {
116
+ bounds,
117
+ stats: { totalVertices, totalTriangles },
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Create coordinate info from bounds
123
+ *
124
+ * @param bounds - Calculated geometry bounds
125
+ * @param originShift - Optional origin shift (defaults to zero)
126
+ * @param hasLargeCoordinates - Whether model had large coordinates requiring RTC shift
127
+ * @returns Coordinate info object with cloned bounds and computed shiftedBounds
128
+ */
129
+ export function createCoordinateInfo(
130
+ bounds: Bounds3D,
131
+ originShift: { x: number; y: number; z: number } = { x: 0, y: 0, z: 0 },
132
+ hasLargeCoordinates: boolean = false
133
+ ): CoordinateInfo {
134
+ // Deep-clone the incoming bounds into originalBounds
135
+ const originalBounds: Bounds3D = {
136
+ min: { x: bounds.min.x, y: bounds.min.y, z: bounds.min.z },
137
+ max: { x: bounds.max.x, y: bounds.max.y, z: bounds.max.z },
138
+ };
139
+
140
+ // Compute shiftedBounds by subtracting originShift from each min/max
141
+ const shiftedBounds: Bounds3D = {
142
+ min: {
143
+ x: bounds.min.x - originShift.x,
144
+ y: bounds.min.y - originShift.y,
145
+ z: bounds.min.z - originShift.z,
146
+ },
147
+ max: {
148
+ x: bounds.max.x - originShift.x,
149
+ y: bounds.max.y - originShift.y,
150
+ z: bounds.max.z - originShift.z,
151
+ },
152
+ };
153
+
154
+ return {
155
+ originShift: { x: originShift.x, y: originShift.y, z: originShift.z },
156
+ originalBounds,
157
+ shiftedBounds,
158
+ hasLargeCoordinates,
159
+ };
160
+ }
161
+
162
+ // ============================================================================
163
+ // Render Throttling
164
+ // ============================================================================
165
+
166
+ /**
167
+ * Calculate render interval based on file size
168
+ * Adaptive throttling: smaller files get more frequent updates, larger files fewer
169
+ *
170
+ * @param fileSizeMB - File size in megabytes
171
+ * @returns Render interval in milliseconds
172
+ */
173
+ export function getRenderIntervalMs(fileSizeMB: number): number {
174
+ if (fileSizeMB > 100) {
175
+ return 200; // Huge files: 5 updates/sec
176
+ } else if (fileSizeMB > 50) {
177
+ return 100; // Large files: 10 updates/sec
178
+ } else if (fileSizeMB > 20) {
179
+ return 75; // Medium files: ~13 updates/sec
180
+ }
181
+ return 50; // Small files: 20 updates/sec
182
+ }
183
+
184
+ /**
185
+ * Calculate server streaming render interval
186
+ *
187
+ * @param fileSizeMB - File size in megabytes
188
+ * @returns Render interval in milliseconds
189
+ */
190
+ export function getServerStreamIntervalMs(fileSizeMB: number): number {
191
+ return fileSizeMB > 100 ? 200 : 100;
192
+ }
193
+
194
+ // ============================================================================
195
+ // Storey Height Calculation
196
+ // ============================================================================
197
+
198
+ /**
199
+ * Calculate storey heights from elevation differences
200
+ * When property data doesn't provide heights, we can infer them from
201
+ * the difference between consecutive storey elevations
202
+ *
203
+ * @param storeyElevations - Map of storey ID to elevation
204
+ * @returns Map of storey ID to calculated height
205
+ */
206
+ export function calculateStoreyHeights(storeyElevations: Map<number, number>): Map<number, number> {
207
+ const heights = new Map<number, number>();
208
+
209
+ if (storeyElevations.size < 2) {
210
+ return heights;
211
+ }
212
+
213
+ const entries = Array.from(storeyElevations.entries()) as Array<[number, number]>;
214
+ const sortedStoreys = entries.sort((a, b) => a[1] - b[1]); // Sort by elevation ascending
215
+
216
+ for (let i = 0; i < sortedStoreys.length - 1; i++) {
217
+ const [storeyId, elevation] = sortedStoreys[i];
218
+ const nextElevation = sortedStoreys[i + 1][1];
219
+ const height = nextElevation - elevation;
220
+ if (height > 0) {
221
+ heights.set(storeyId, height);
222
+ }
223
+ }
224
+
225
+ return heights;
226
+ }
227
+
228
+ // ============================================================================
229
+ // Progress Calculation
230
+ // ============================================================================
231
+
232
+ /**
233
+ * Calculate progress percentage for geometry streaming
234
+ *
235
+ * @param currentMeshes - Number of meshes processed so far
236
+ * @param estimatedTotal - Estimated total meshes (may be 0 or inaccurate)
237
+ * @param basePercent - Base percentage (start of geometry phase)
238
+ * @param maxPercent - Maximum percentage to reach
239
+ * @returns Progress percentage
240
+ */
241
+ export function calculateStreamingProgress(
242
+ currentMeshes: number,
243
+ estimatedTotal: number,
244
+ basePercent: number = 50,
245
+ maxPercent: number = 95
246
+ ): number {
247
+ const denominator = Math.max(estimatedTotal / 10, currentMeshes);
248
+ // Guard against division by zero (both currentMeshes and estimatedTotal are 0)
249
+ if (denominator === 0) {
250
+ return basePercent;
251
+ }
252
+ const progressRange = maxPercent - basePercent;
253
+ return Math.min(maxPercent, basePercent + (currentMeshes / denominator) * progressRange);
254
+ }
255
+
256
+ // ============================================================================
257
+ // Mesh Conversion
258
+ // ============================================================================
259
+
260
+ /**
261
+ * Normalize IFCX mesh color to RGBA format
262
+ *
263
+ * @param color - Optional color array (RGB or RGBA)
264
+ * @returns Normalized RGBA color tuple
265
+ */
266
+ export function normalizeColor(
267
+ color?: [number, number, number, number] | [number, number, number] | number[]
268
+ ): [number, number, number, number] {
269
+ // Return default color if no color provided or array is too short
270
+ if (!color || color.length < 3) {
271
+ return [0.7, 0.7, 0.7, 1.0];
272
+ }
273
+ if (color.length === 4) {
274
+ return color as [number, number, number, number];
275
+ }
276
+ return [color[0], color[1], color[2], 1.0];
277
+ }
278
+
279
+ /**
280
+ * Convert server mesh colors from float [0-1] to byte [0-255]
281
+ *
282
+ * @param floatColors - Float color array [0-1]
283
+ * @returns Uint8Array of byte colors [0-255]
284
+ */
285
+ export function convertFloatColorToBytes(floatColors: number[]): Uint8Array {
286
+ return new Uint8Array(floatColors.map((c: number) => Math.round(c * 255)));
287
+ }