@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.
- package/CHANGELOG.md +88 -0
- package/dist/assets/{Arrow.dom-BGPQieQQ.js → Arrow.dom-CusgkT03.js} +1 -1
- package/dist/assets/browser-BXNIkE8a.js +694 -0
- package/dist/assets/emscripten-module-BTRCZGcB.wasm +0 -0
- package/dist/assets/emscripten-module-CGIn_cMh.wasm +0 -0
- package/dist/assets/emscripten-module-DYvzWiHh.wasm +0 -0
- package/dist/assets/emscripten-module-NWak2PoB.wasm +0 -0
- package/dist/assets/emscripten-module.browser-CY5t0Vfq.js +1 -0
- package/dist/assets/esbuild-COv63sf-.js +1 -0
- package/dist/assets/esbuild-Cpd5nU_H.wasm +0 -0
- package/dist/assets/ffi-DlhRHxHv.js +1 -0
- package/dist/assets/index-6Mr3byM-.js +216 -0
- package/dist/assets/index-CGbokkQ9.css +1 -0
- package/dist/assets/index-huvR-kGC.js +98305 -0
- package/dist/assets/module-6F3E5H7Y-tx0BadV3.js +6 -0
- package/dist/assets/{native-bridge-DD0SNyQ5.js → native-bridge-DsHOKdgD.js} +1 -1
- package/dist/assets/{wasm-bridge-D54YMO7X.js → wasm-bridge-Bd73HXn-.js} +1 -1
- package/dist/index.html +12 -3
- package/index.html +10 -1
- package/package.json +30 -21
- package/src/App.tsx +6 -1
- package/src/components/ui/dialog.tsx +8 -6
- package/src/components/viewer/CodeEditor.tsx +309 -0
- package/src/components/viewer/CommandPalette.tsx +597 -0
- package/src/components/viewer/Drawing2DCanvas.tsx +364 -1
- package/src/components/viewer/EntityContextMenu.tsx +47 -20
- package/src/components/viewer/ExportDialog.tsx +166 -17
- package/src/components/viewer/HierarchyPanel.tsx +3 -1
- package/src/components/viewer/LensPanel.tsx +848 -85
- package/src/components/viewer/MainToolbar.tsx +145 -84
- package/src/components/viewer/ScriptPanel.tsx +416 -0
- package/src/components/viewer/Section2DPanel.tsx +269 -29
- package/src/components/viewer/TextAnnotationEditor.tsx +112 -0
- package/src/components/viewer/ViewerLayout.tsx +63 -11
- package/src/components/viewer/Viewport.tsx +58 -23
- package/src/components/viewer/ViewportContainer.tsx +2 -0
- package/src/components/viewer/hierarchy/HierarchyNode.tsx +1 -1
- package/src/components/viewer/hierarchy/types.ts +1 -1
- package/src/components/viewer/lists/ListResultsTable.tsx +53 -19
- package/src/components/viewer/tools/cloudPathGenerator.test.ts +118 -0
- package/src/components/viewer/tools/cloudPathGenerator.ts +275 -0
- package/src/components/viewer/tools/computePolygonArea.test.ts +165 -0
- package/src/components/viewer/tools/computePolygonArea.ts +72 -0
- package/src/components/viewer/useGeometryStreaming.ts +25 -5
- package/src/hooks/ids/idsExportService.ts +1 -1
- package/src/hooks/useAnnotation2D.ts +551 -0
- package/src/hooks/useDrawingExport.ts +83 -1
- package/src/hooks/useKeyboardShortcuts.ts +114 -14
- package/src/hooks/useLens.ts +40 -55
- package/src/hooks/useLensDiscovery.ts +46 -0
- package/src/hooks/useModelSelection.ts +5 -22
- package/src/hooks/useSandbox.ts +113 -0
- package/src/index.css +7 -1
- package/src/lib/lens/adapter.ts +127 -1
- package/src/lib/lists/columnToAutoColor.ts +33 -0
- package/src/lib/recent-files.ts +122 -0
- package/src/lib/scripts/persistence.ts +132 -0
- package/src/lib/scripts/templates/bim-globals.d.ts +111 -0
- package/src/lib/scripts/templates/data-quality-audit.ts +149 -0
- package/src/lib/scripts/templates/envelope-check.ts +164 -0
- package/src/lib/scripts/templates/federation-compare.ts +189 -0
- package/src/lib/scripts/templates/fire-safety-check.ts +161 -0
- package/src/lib/scripts/templates/mep-equipment-schedule.ts +175 -0
- package/src/lib/scripts/templates/quantity-takeoff.ts +145 -0
- package/src/lib/scripts/templates/reset-view.ts +6 -0
- package/src/lib/scripts/templates/space-validation.ts +189 -0
- package/src/lib/scripts/templates/tsconfig.json +13 -0
- package/src/lib/scripts/templates.ts +86 -0
- package/src/sdk/BimProvider.tsx +50 -0
- package/src/sdk/adapters/export-adapter.ts +283 -0
- package/src/sdk/adapters/lens-adapter.ts +44 -0
- package/src/sdk/adapters/model-adapter.ts +32 -0
- package/src/sdk/adapters/model-compat.ts +80 -0
- package/src/sdk/adapters/mutate-adapter.ts +45 -0
- package/src/sdk/adapters/query-adapter.ts +241 -0
- package/src/sdk/adapters/selection-adapter.ts +29 -0
- package/src/sdk/adapters/spatial-adapter.ts +37 -0
- package/src/sdk/adapters/types.ts +11 -0
- package/src/sdk/adapters/viewer-adapter.ts +103 -0
- package/src/sdk/adapters/visibility-adapter.ts +61 -0
- package/src/sdk/local-backend.ts +144 -0
- package/src/sdk/useBimHost.ts +69 -0
- package/src/store/constants.ts +10 -2
- package/src/store/index.ts +28 -2
- package/src/store/resolveEntityRef.ts +44 -0
- package/src/store/slices/drawing2DSlice.ts +321 -0
- package/src/store/slices/lensSlice.ts +46 -4
- package/src/store/slices/pinboardSlice.ts +171 -42
- package/src/store/slices/scriptSlice.ts +218 -0
- package/src/store/slices/uiSlice.ts +2 -0
- package/src/store.ts +3 -0
- package/tsconfig.json +5 -2
- package/vite.config.ts +8 -0
- package/dist/assets/index-dgdgiQ9p.js +0 -75456
- 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
|
|
376
|
-
//
|
|
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 (
|
|
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
|
-
|
|
392
|
-
|
|
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">▴▾</span></th>
|
|
334
|
-
<th class="col-type" onclick="sortTable(${i}, 1)">IFC
|
|
334
|
+
<th class="col-type" onclick="sortTable(${i}, 1)">IFC Class <span class="sort-icon">▴▾</span></th>
|
|
335
335
|
<th class="col-name" onclick="sortTable(${i}, 2)">Name <span class="sort-icon">▴▾</span></th>
|
|
336
336
|
<th class="col-globalid" onclick="sortTable(${i}, 3)">GlobalId <span class="sort-icon">▴▾</span></th>
|
|
337
337
|
<th class="col-expressid" onclick="sortTable(${i}, 4)">ID <span class="sort-icon">▴▾</span></th>
|