@ifc-lite/viewer 1.7.0 → 1.9.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 (95) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/dist/assets/{Arrow.dom-BGPQieQQ.js → Arrow.dom-CusgkT03.js} +1 -1
  3. package/dist/assets/browser-BXNIkE8a.js +694 -0
  4. package/dist/assets/emscripten-module-BTRCZGcB.wasm +0 -0
  5. package/dist/assets/emscripten-module-CGIn_cMh.wasm +0 -0
  6. package/dist/assets/emscripten-module-DYvzWiHh.wasm +0 -0
  7. package/dist/assets/emscripten-module-NWak2PoB.wasm +0 -0
  8. package/dist/assets/emscripten-module.browser-CY5t0Vfq.js +1 -0
  9. package/dist/assets/esbuild-COv63sf-.js +1 -0
  10. package/dist/assets/esbuild-Cpd5nU_H.wasm +0 -0
  11. package/dist/assets/ffi-DlhRHxHv.js +1 -0
  12. package/dist/assets/index-6Mr3byM-.js +216 -0
  13. package/dist/assets/index-CGbokkQ9.css +1 -0
  14. package/dist/assets/index-huvR-kGC.js +98305 -0
  15. package/dist/assets/module-6F3E5H7Y-tx0BadV3.js +6 -0
  16. package/dist/assets/{native-bridge-DD0SNyQ5.js → native-bridge-DsHOKdgD.js} +1 -1
  17. package/dist/assets/{wasm-bridge-D54YMO7X.js → wasm-bridge-Bd73HXn-.js} +1 -1
  18. package/dist/index.html +12 -3
  19. package/index.html +10 -1
  20. package/package.json +30 -21
  21. package/src/App.tsx +6 -1
  22. package/src/components/ui/dialog.tsx +8 -6
  23. package/src/components/viewer/CodeEditor.tsx +309 -0
  24. package/src/components/viewer/CommandPalette.tsx +597 -0
  25. package/src/components/viewer/Drawing2DCanvas.tsx +364 -1
  26. package/src/components/viewer/EntityContextMenu.tsx +47 -20
  27. package/src/components/viewer/ExportDialog.tsx +166 -17
  28. package/src/components/viewer/HierarchyPanel.tsx +3 -1
  29. package/src/components/viewer/LensPanel.tsx +848 -85
  30. package/src/components/viewer/MainToolbar.tsx +145 -84
  31. package/src/components/viewer/ScriptPanel.tsx +416 -0
  32. package/src/components/viewer/Section2DPanel.tsx +269 -29
  33. package/src/components/viewer/TextAnnotationEditor.tsx +112 -0
  34. package/src/components/viewer/ViewerLayout.tsx +63 -11
  35. package/src/components/viewer/Viewport.tsx +58 -23
  36. package/src/components/viewer/ViewportContainer.tsx +2 -0
  37. package/src/components/viewer/hierarchy/HierarchyNode.tsx +1 -1
  38. package/src/components/viewer/hierarchy/types.ts +1 -1
  39. package/src/components/viewer/lists/ListResultsTable.tsx +53 -19
  40. package/src/components/viewer/tools/cloudPathGenerator.test.ts +118 -0
  41. package/src/components/viewer/tools/cloudPathGenerator.ts +275 -0
  42. package/src/components/viewer/tools/computePolygonArea.test.ts +165 -0
  43. package/src/components/viewer/tools/computePolygonArea.ts +72 -0
  44. package/src/components/viewer/useGeometryStreaming.ts +25 -5
  45. package/src/hooks/ids/idsExportService.ts +1 -1
  46. package/src/hooks/useAnnotation2D.ts +551 -0
  47. package/src/hooks/useDrawingExport.ts +83 -1
  48. package/src/hooks/useKeyboardShortcuts.ts +114 -14
  49. package/src/hooks/useLens.ts +40 -55
  50. package/src/hooks/useLensDiscovery.ts +46 -0
  51. package/src/hooks/useModelSelection.ts +5 -22
  52. package/src/hooks/useSandbox.ts +113 -0
  53. package/src/index.css +7 -1
  54. package/src/lib/lens/adapter.ts +127 -1
  55. package/src/lib/lists/columnToAutoColor.ts +33 -0
  56. package/src/lib/recent-files.ts +122 -0
  57. package/src/lib/scripts/persistence.ts +132 -0
  58. package/src/lib/scripts/templates/bim-globals.d.ts +111 -0
  59. package/src/lib/scripts/templates/data-quality-audit.ts +149 -0
  60. package/src/lib/scripts/templates/envelope-check.ts +164 -0
  61. package/src/lib/scripts/templates/federation-compare.ts +189 -0
  62. package/src/lib/scripts/templates/fire-safety-check.ts +161 -0
  63. package/src/lib/scripts/templates/mep-equipment-schedule.ts +175 -0
  64. package/src/lib/scripts/templates/quantity-takeoff.ts +145 -0
  65. package/src/lib/scripts/templates/reset-view.ts +6 -0
  66. package/src/lib/scripts/templates/space-validation.ts +189 -0
  67. package/src/lib/scripts/templates/tsconfig.json +13 -0
  68. package/src/lib/scripts/templates.ts +86 -0
  69. package/src/sdk/BimProvider.tsx +50 -0
  70. package/src/sdk/adapters/export-adapter.ts +283 -0
  71. package/src/sdk/adapters/lens-adapter.ts +44 -0
  72. package/src/sdk/adapters/model-adapter.ts +32 -0
  73. package/src/sdk/adapters/model-compat.ts +80 -0
  74. package/src/sdk/adapters/mutate-adapter.ts +45 -0
  75. package/src/sdk/adapters/query-adapter.ts +241 -0
  76. package/src/sdk/adapters/selection-adapter.ts +29 -0
  77. package/src/sdk/adapters/spatial-adapter.ts +37 -0
  78. package/src/sdk/adapters/types.ts +11 -0
  79. package/src/sdk/adapters/viewer-adapter.ts +103 -0
  80. package/src/sdk/adapters/visibility-adapter.ts +61 -0
  81. package/src/sdk/local-backend.ts +144 -0
  82. package/src/sdk/useBimHost.ts +69 -0
  83. package/src/store/constants.ts +10 -2
  84. package/src/store/index.ts +28 -2
  85. package/src/store/resolveEntityRef.ts +44 -0
  86. package/src/store/slices/drawing2DSlice.ts +321 -0
  87. package/src/store/slices/lensSlice.ts +46 -4
  88. package/src/store/slices/pinboardSlice.ts +171 -42
  89. package/src/store/slices/scriptSlice.ts +218 -0
  90. package/src/store/slices/uiSlice.ts +2 -0
  91. package/src/store.ts +3 -0
  92. package/tsconfig.json +5 -2
  93. package/vite.config.ts +8 -0
  94. package/dist/assets/index-dgdgiQ9p.js +0 -75456
  95. package/dist/assets/index-yTqs8kgX.css +0 -1
@@ -0,0 +1,165 @@
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
+ import { describe, it } from 'node:test';
6
+ import assert from 'node:assert';
7
+ import {
8
+ computePolygonArea,
9
+ computePolygonPerimeter,
10
+ computePolygonCentroid,
11
+ formatArea,
12
+ } from './computePolygonArea.js';
13
+
14
+ describe('computePolygonArea', () => {
15
+ it('returns 0 for fewer than 3 points', () => {
16
+ assert.strictEqual(computePolygonArea([]), 0);
17
+ assert.strictEqual(computePolygonArea([{ x: 0, y: 0 }]), 0);
18
+ assert.strictEqual(computePolygonArea([{ x: 0, y: 0 }, { x: 1, y: 0 }]), 0);
19
+ });
20
+
21
+ it('computes area of a unit square', () => {
22
+ const square = [
23
+ { x: 0, y: 0 },
24
+ { x: 1, y: 0 },
25
+ { x: 1, y: 1 },
26
+ { x: 0, y: 1 },
27
+ ];
28
+ assert.strictEqual(computePolygonArea(square), 1);
29
+ });
30
+
31
+ it('computes area of a right triangle', () => {
32
+ const triangle = [
33
+ { x: 0, y: 0 },
34
+ { x: 4, y: 0 },
35
+ { x: 0, y: 3 },
36
+ ];
37
+ assert.strictEqual(computePolygonArea(triangle), 6);
38
+ });
39
+
40
+ it('computes area of a rectangle', () => {
41
+ const rect = [
42
+ { x: 0, y: 0 },
43
+ { x: 5, y: 0 },
44
+ { x: 5, y: 3 },
45
+ { x: 0, y: 3 },
46
+ ];
47
+ assert.strictEqual(computePolygonArea(rect), 15);
48
+ });
49
+
50
+ it('returns positive area regardless of winding direction', () => {
51
+ // Counter-clockwise
52
+ const ccw = [
53
+ { x: 0, y: 0 },
54
+ { x: 0, y: 2 },
55
+ { x: 2, y: 2 },
56
+ { x: 2, y: 0 },
57
+ ];
58
+ assert.strictEqual(computePolygonArea(ccw), 4);
59
+ });
60
+
61
+ it('computes area of an irregular polygon', () => {
62
+ // L-shaped polygon (6 vertices)
63
+ const lShape = [
64
+ { x: 0, y: 0 },
65
+ { x: 3, y: 0 },
66
+ { x: 3, y: 1 },
67
+ { x: 1, y: 1 },
68
+ { x: 1, y: 2 },
69
+ { x: 0, y: 2 },
70
+ ];
71
+ // Area: 3*1 + 1*1 = 4
72
+ assert.strictEqual(computePolygonArea(lShape), 4);
73
+ });
74
+ });
75
+
76
+ describe('computePolygonPerimeter', () => {
77
+ it('returns 0 for fewer than 2 points', () => {
78
+ assert.strictEqual(computePolygonPerimeter([]), 0);
79
+ assert.strictEqual(computePolygonPerimeter([{ x: 0, y: 0 }]), 0);
80
+ });
81
+
82
+ it('computes perimeter of a unit square', () => {
83
+ const square = [
84
+ { x: 0, y: 0 },
85
+ { x: 1, y: 0 },
86
+ { x: 1, y: 1 },
87
+ { x: 0, y: 1 },
88
+ ];
89
+ assert.strictEqual(computePolygonPerimeter(square), 4);
90
+ });
91
+
92
+ it('computes perimeter of a 3-4-5 right triangle', () => {
93
+ const triangle = [
94
+ { x: 0, y: 0 },
95
+ { x: 4, y: 0 },
96
+ { x: 0, y: 3 },
97
+ ];
98
+ assert.strictEqual(computePolygonPerimeter(triangle), 12);
99
+ });
100
+
101
+ it('computes perimeter of a rectangle', () => {
102
+ const rect = [
103
+ { x: 0, y: 0 },
104
+ { x: 5, y: 0 },
105
+ { x: 5, y: 3 },
106
+ { x: 0, y: 3 },
107
+ ];
108
+ assert.strictEqual(computePolygonPerimeter(rect), 16);
109
+ });
110
+ });
111
+
112
+ describe('computePolygonCentroid', () => {
113
+ it('returns origin for empty polygon', () => {
114
+ const c = computePolygonCentroid([]);
115
+ assert.strictEqual(c.x, 0);
116
+ assert.strictEqual(c.y, 0);
117
+ });
118
+
119
+ it('returns the point for a single point', () => {
120
+ const c = computePolygonCentroid([{ x: 3, y: 7 }]);
121
+ assert.strictEqual(c.x, 3);
122
+ assert.strictEqual(c.y, 7);
123
+ });
124
+
125
+ it('computes centroid of a unit square at origin', () => {
126
+ const square = [
127
+ { x: 0, y: 0 },
128
+ { x: 2, y: 0 },
129
+ { x: 2, y: 2 },
130
+ { x: 0, y: 2 },
131
+ ];
132
+ const c = computePolygonCentroid(square);
133
+ assert.strictEqual(c.x, 1);
134
+ assert.strictEqual(c.y, 1);
135
+ });
136
+
137
+ it('computes centroid of a triangle', () => {
138
+ const triangle = [
139
+ { x: 0, y: 0 },
140
+ { x: 6, y: 0 },
141
+ { x: 0, y: 6 },
142
+ ];
143
+ const c = computePolygonCentroid(triangle);
144
+ assert.strictEqual(c.x, 2);
145
+ assert.strictEqual(c.y, 2);
146
+ });
147
+ });
148
+
149
+ describe('formatArea', () => {
150
+ it('formats small areas in cm²', () => {
151
+ assert.strictEqual(formatArea(0.005), '50.0 cm²');
152
+ assert.strictEqual(formatArea(0.001), '10.0 cm²');
153
+ });
154
+
155
+ it('formats medium areas in m²', () => {
156
+ assert.strictEqual(formatArea(1), '1.00 m²');
157
+ assert.strictEqual(formatArea(25.5), '25.50 m²');
158
+ assert.strictEqual(formatArea(100), '100.00 m²');
159
+ });
160
+
161
+ it('formats large areas in hectares', () => {
162
+ assert.strictEqual(formatArea(10000), '1.00 ha');
163
+ assert.strictEqual(formatArea(50000), '5.00 ha');
164
+ });
165
+ });
@@ -0,0 +1,72 @@
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
+ * Polygon area and perimeter computation utilities for 2D annotations.
7
+ * Uses the shoelace formula for area calculation.
8
+ */
9
+
10
+ interface Point2D {
11
+ x: number;
12
+ y: number;
13
+ }
14
+
15
+ /**
16
+ * Compute the signed area of a simple polygon using the shoelace formula.
17
+ * Returns absolute value (always positive).
18
+ */
19
+ export function computePolygonArea(points: Point2D[]): number {
20
+ if (points.length < 3) return 0;
21
+ let area = 0;
22
+ const n = points.length;
23
+ for (let i = 0; i < n; i++) {
24
+ const j = (i + 1) % n;
25
+ area += points[i].x * points[j].y;
26
+ area -= points[j].x * points[i].y;
27
+ }
28
+ return Math.abs(area) / 2;
29
+ }
30
+
31
+ /**
32
+ * Compute the perimeter of a closed polygon.
33
+ */
34
+ export function computePolygonPerimeter(points: Point2D[]): number {
35
+ if (points.length < 2) return 0;
36
+ let perimeter = 0;
37
+ const n = points.length;
38
+ for (let i = 0; i < n; i++) {
39
+ const j = (i + 1) % n;
40
+ const dx = points[j].x - points[i].x;
41
+ const dy = points[j].y - points[i].y;
42
+ perimeter += Math.sqrt(dx * dx + dy * dy);
43
+ }
44
+ return perimeter;
45
+ }
46
+
47
+ /**
48
+ * Compute the centroid (geometric center) of a polygon.
49
+ */
50
+ export function computePolygonCentroid(points: Point2D[]): Point2D {
51
+ if (points.length === 0) return { x: 0, y: 0 };
52
+ let cx = 0;
53
+ let cy = 0;
54
+ for (const p of points) {
55
+ cx += p.x;
56
+ cy += p.y;
57
+ }
58
+ return { x: cx / points.length, y: cy / points.length };
59
+ }
60
+
61
+ /**
62
+ * Format an area value for display with appropriate units.
63
+ */
64
+ export function formatArea(squareMeters: number): string {
65
+ if (squareMeters < 0.01) {
66
+ return `${(squareMeters * 10000).toFixed(1)} cm²`;
67
+ } else if (squareMeters < 10000) {
68
+ return `${squareMeters.toFixed(2)} m²`;
69
+ } else {
70
+ return `${(squareMeters / 10000).toFixed(2)} ha`;
71
+ }
72
+ }
@@ -20,6 +20,8 @@ export interface UseGeometryStreamingParams {
20
20
  geometryBoundsRef: MutableRefObject<{ min: { x: number; y: number; z: number }; max: { x: number; y: number; z: number } }>;
21
21
  pendingColorUpdates: Map<number, [number, number, number, number]> | null;
22
22
  clearPendingColorUpdates: () => void;
23
+ // Clear color ref — color update renders must preserve theme background
24
+ clearColorRef: MutableRefObject<[number, number, number, number]>;
23
25
  }
24
26
 
25
27
  export function useGeometryStreaming(params: UseGeometryStreamingParams): void {
@@ -32,6 +34,7 @@ export function useGeometryStreaming(params: UseGeometryStreamingParams): void {
32
34
  geometryBoundsRef,
33
35
  pendingColorUpdates,
34
36
  clearPendingColorUpdates,
37
+ clearColorRef,
35
38
  } = params;
36
39
 
37
40
  // Track processed meshes for incremental updates
@@ -372,10 +375,12 @@ export function useGeometryStreaming(params: UseGeometryStreamingParams): void {
372
375
  prevIsStreamingRef.current = isStreaming;
373
376
  }, [isStreaming, isInitialized]);
374
377
 
375
- // Apply pending color updates to WebGPU scene
376
- // Note: Color updates may arrive before viewport is initialized, so we wait
378
+ // Apply pending color updates as overlay batches (lens coloring).
379
+ // Uses scene.setColorOverrides() which builds overlay batches rendered on top
380
+ // of original geometry via depthCompare 'equal'. Original batches are NEVER
381
+ // modified, so clearing lens is instant (no batch rebuild).
377
382
  useEffect(() => {
378
- if (!pendingColorUpdates || pendingColorUpdates.size === 0) return;
383
+ if (pendingColorUpdates === null) return;
379
384
 
380
385
  // Wait until viewport is initialized before applying color updates
381
386
  if (!isInitialized) return;
@@ -388,8 +393,23 @@ export function useGeometryStreaming(params: UseGeometryStreamingParams): void {
388
393
  const scene = renderer.getScene();
389
394
 
390
395
  if (device && pipeline) {
391
- scene.updateMeshColors(pendingColorUpdates, device, pipeline);
392
- renderer.render();
396
+ if (pendingColorUpdates.size === 0) {
397
+ // Empty map = clear overrides (lens deactivated)
398
+ scene.clearColorOverrides();
399
+ } else {
400
+ // Non-empty map = set color overrides
401
+ scene.setColorOverrides(pendingColorUpdates, device, pipeline);
402
+ }
403
+ // Re-render with current theme background — render() without options
404
+ // defaults to black background. Do NOT pass hiddenIds/isolatedIds here:
405
+ // visibility filtering causes partial batches which write depth only for
406
+ // visible elements, but overlay batches cover all geometry. Without
407
+ // filtering, all original batches write depth for every entity, ensuring
408
+ // depthCompare 'equal' matches exactly for the overlay pass.
409
+ // The next render from useRenderUpdates will apply the correct visibility.
410
+ renderer.render({
411
+ clearColor: clearColorRef.current,
412
+ });
393
413
  clearPendingColorUpdates();
394
414
  }
395
415
  }, [pendingColorUpdates, isInitialized, clearPendingColorUpdates]);
@@ -331,7 +331,7 @@ export function buildReportHTML(report: IDSValidationReport, locale: SupportedLo
331
331
  <thead>
332
332
  <tr>
333
333
  <th class="col-status" onclick="sortTable(${i}, 0)">Status <span class="sort-icon">&#x25B4;&#x25BE;</span></th>
334
- <th class="col-type" onclick="sortTable(${i}, 1)">IFC Type <span class="sort-icon">&#x25B4;&#x25BE;</span></th>
334
+ <th class="col-type" onclick="sortTable(${i}, 1)">IFC Class <span class="sort-icon">&#x25B4;&#x25BE;</span></th>
335
335
  <th class="col-name" onclick="sortTable(${i}, 2)">Name <span class="sort-icon">&#x25B4;&#x25BE;</span></th>
336
336
  <th class="col-globalid" onclick="sortTable(${i}, 3)">GlobalId <span class="sort-icon">&#x25B4;&#x25BE;</span></th>
337
337
  <th class="col-expressid" onclick="sortTable(${i}, 4)">ID <span class="sort-icon">&#x25B4;&#x25BE;</span></th>