@ifc-lite/viewer 1.27.0 → 1.28.1
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/.turbo/turbo-build.log +35 -42
- package/CHANGELOG.md +74 -0
- package/dist/assets/{basketViewActivator-B3CdrLsb.js → basketViewActivator-Ce38DhXd.js} +8 -8
- package/dist/assets/{bcf-QeHK_Aud.js → bcf-Cv_O3JfD.js} +56 -56
- package/dist/assets/{decode-worker-CgM1iNSK.js → decode-worker-Cjign7Zh.js} +1 -1
- package/dist/assets/{deflate-B-d0SYQM.js → deflate-HbyMq59o.js} +1 -1
- package/dist/assets/drawing-2d-DW98umlt.js +257 -0
- package/dist/assets/e57-source-2wI9jkCA.js +1 -0
- package/dist/assets/{exporters-B4LbZFeT.js → exporters-BuD3XRzB.js} +1309 -1153
- package/dist/assets/geometry.worker-TH3fCCoY.js +1 -0
- package/dist/assets/{geotiff-CrVtDRFq.js → geotiff-B2HA8Bwm.js} +10 -10
- package/dist/assets/{ids-DjsGFN10.js → ids-DYUFMd5f.js} +952 -945
- package/dist/assets/{ifc-lite_bg-DsYUIHm3.wasm → ifc-lite_bg-BEA5DLmg.wasm} +0 -0
- package/dist/assets/index-E9wB0zWt.css +1 -0
- package/dist/assets/{index-COYokSKc.js → index-n5O1QJMM.js} +37877 -38126
- package/dist/assets/{index.es-CY202jA3.js → index.es-BKVIpZgL.js} +9 -9
- package/dist/assets/{jpeg-D4wOkf5h.js → jpeg-C7hjKjPX.js} +1 -1
- package/dist/assets/{jspdf.es.min-DIGb9BHN.js → jspdf.es.min-oWlFc42Y.js} +4 -4
- package/dist/assets/lens-C4p1kQ0p.js +1 -0
- package/dist/assets/{lerc-DmW0_tgf.js → lerc-BfIOGhQz.js} +1 -1
- package/dist/assets/{lzw-oWetY-d6.js → lzw-B0jRuuW5.js} +1 -1
- package/dist/assets/{native-bridge-BX8_tHXE.js → native-bridge-DpB-dtEn.js} +6 -3
- package/dist/assets/{packbits-F8Nkp4NY.js → packbits-DVvBTC39.js} +1 -1
- package/dist/assets/parser.worker-BDsWQ6rc.js +182 -0
- package/dist/assets/{pdf-Dsh3HPZB.js → pdf-dVIqI5ac.js} +10 -10
- package/dist/assets/raw-C0ZJYGmN.js +1 -0
- package/dist/assets/{sandbox-BAC3a-eN.js → sandbox-qpJlrNN0.js} +2962 -2554
- package/dist/assets/server-client-DVZ2huNS.js +719 -0
- package/dist/assets/{webimage-BLV1dgmd.js → webimage-B394g0Tw.js} +1 -1
- package/dist/assets/{xlsx-Bc2HTrjC.js → xlsx-D-oHO76J.js} +8 -8
- package/dist/assets/{zstd-C_1HxVrA.js → zstd-Bf38MwV2.js} +1 -1
- package/dist/index.html +9 -9
- package/package.json +24 -23
- package/src/App.tsx +1 -3
- package/src/components/mcp/playground-dispatcher.ts +3 -0
- package/src/components/mcp/playground-files.ts +33 -1
- package/src/components/viewer/BCFPanel.tsx +1 -16
- package/src/components/viewer/ChatPanel.tsx +11 -46
- package/src/components/viewer/CommandPalette.tsx +6 -1
- package/src/components/viewer/ComparePanel.tsx +420 -0
- package/src/components/viewer/HierarchyPanel.tsx +48 -183
- package/src/components/viewer/IDSPanel.tsx +1 -26
- package/src/components/viewer/MainToolbar.tsx +94 -187
- package/src/components/viewer/MobileToolbar.tsx +1 -9
- package/src/components/viewer/PropertiesPanel.tsx +98 -127
- package/src/components/viewer/ScriptPanel.tsx +8 -34
- package/src/components/viewer/Section2DPanel.tsx +32 -1
- package/src/components/viewer/ViewerLayout.tsx +5 -2
- package/src/components/viewer/Viewport.tsx +3 -0
- package/src/components/viewer/ViewportContainer.tsx +24 -42
- package/src/components/viewer/ViewportOverlays.tsx +1 -4
- package/src/components/viewer/hierarchy/HierarchyNode.tsx +3 -3
- package/src/components/viewer/hierarchy/ifc-icons.ts +9 -0
- package/src/components/viewer/hierarchy/treeDataBuilder.ts +87 -0
- package/src/components/viewer/hierarchy/types.ts +1 -0
- package/src/components/viewer/hierarchy/useHierarchyTree.ts +6 -2
- package/src/components/viewer/properties/MaterialTotalsPanel.tsx +283 -0
- package/src/components/viewer/useGeometryStreaming.ts +0 -2
- package/src/hooks/federationLoadGate.test.ts +12 -2
- package/src/hooks/federationLoadGate.ts +9 -2
- package/src/hooks/ingest/federationAlign.ts +488 -0
- package/src/hooks/ingest/viewerModelIngest.ts +3 -212
- package/src/hooks/useCompare.ts +0 -0
- package/src/hooks/useCompareOverlay.ts +119 -0
- package/src/hooks/useDrawingGeneration.ts +234 -14
- package/src/hooks/useIfc.ts +1 -1
- package/src/hooks/useIfcCache.ts +100 -24
- package/src/hooks/useIfcFederation.ts +42 -811
- package/src/hooks/useIfcLoader.ts +349 -1517
- package/src/hooks/useIfcServer.ts +3 -0
- package/src/hooks/useLens.ts +5 -1
- package/src/hooks/useSymbolicAnnotations.ts +70 -38
- package/src/lib/compare/buildFingerprints.ts +173 -0
- package/src/lib/compare/describeChange.ts +0 -0
- package/src/lib/compare/geometricData.test.ts +54 -0
- package/src/lib/compare/geometricData.ts +37 -0
- package/src/lib/compare/overlay.test.ts +99 -0
- package/src/lib/compare/overlay.ts +91 -0
- package/src/lib/geo/cesium-placement.ts +1 -1
- package/src/lib/geo/reproject.ts +4 -1
- package/src/lib/llm/script-edit-ops.ts +23 -0
- package/src/lib/llm/stream-client.ts +8 -1
- package/src/lib/search/result-export.ts +7 -1
- package/src/sdk/adapters/export-adapter.ts +6 -1
- package/src/services/cacheService.ts +9 -25
- package/src/services/desktop-export.ts +2 -59
- package/src/services/file-dialog.ts +8 -142
- package/src/store/constants.ts +23 -0
- package/src/store/globalId.ts +15 -13
- package/src/store/index.ts +19 -6
- package/src/store/slices/cesiumSlice.ts +8 -1
- package/src/store/slices/compareSlice.ts +96 -0
- package/src/store/slices/drawing2DSlice.ts +8 -0
- package/src/store/slices/lensSlice.ts +8 -0
- package/src/store/slices/visibilitySlice.ts +22 -1
- package/src/store/types.ts +1 -71
- package/src/utils/acquireFileBuffer.test.ts +12 -4
- package/src/utils/ifcConfig.ts +0 -12
- package/src/utils/loadingUtils.ts +32 -0
- package/src/utils/spatialHierarchy.test.ts +53 -1
- package/src/utils/spatialHierarchy.ts +42 -2
- package/src/vite-env.d.ts +2 -0
- package/vite.config.ts +6 -3
- package/DESKTOP_CONTRACT_VERSION +0 -1
- package/dist/assets/drawing-2d-C71b8Ugx.js +0 -257
- package/dist/assets/e57-source-CQHxE8n3.js +0 -1
- package/dist/assets/event-B0kAzHa-.js +0 -1
- package/dist/assets/geometry.worker-BdH-E6NB.js +0 -1
- package/dist/assets/index-ajK6D32J.css +0 -1
- package/dist/assets/lens-PYsLu_MA.js +0 -1
- package/dist/assets/parser.worker-D591Zu_-.js +0 -182
- package/dist/assets/raw-D9iw0tmc.js +0 -1
- package/dist/assets/server-client-Cjwnm7il.js +0 -706
- package/dist/assets/tauri-core-stub-D8Fa-u43.js +0 -1
- package/dist/assets/tauri-dialog-stub-r7Wksg7o.js +0 -1
- package/dist/assets/tauri-fs-stub-BdeRC7aK.js +0 -1
- package/src/components/viewer/DesktopEntitlementBanner.tsx +0 -74
- package/src/components/viewer/SettingsPage.tsx +0 -581
- package/src/hooks/ingest/resolveDataStoreOrAbort.test.ts +0 -61
- package/src/hooks/ingest/resolveDataStoreOrAbort.ts +0 -28
- package/src/hooks/ingest/watchedGeometryStream.test.ts +0 -78
- package/src/hooks/ingest/watchedGeometryStream.ts +0 -76
- package/src/lib/desktop/desktopEntitlementEvents.ts +0 -39
- package/src/lib/desktop-entitlement.ts +0 -43
- package/src/lib/desktop-product.ts +0 -130
- package/src/lib/platform.ts +0 -23
- package/src/services/desktop-cache.ts +0 -186
- package/src/services/desktop-harness.ts +0 -196
- package/src/services/desktop-logger.ts +0 -20
- package/src/services/desktop-native-metadata.ts +0 -230
- package/src/services/desktop-panel-actions.ts +0 -43
- package/src/services/desktop-preferences.ts +0 -44
- package/src/services/fs-cache.ts +0 -212
- package/src/services/tauri-core-stub.ts +0 -7
- package/src/services/tauri-dialog-stub.ts +0 -7
- package/src/services/tauri-fs-stub.ts +0 -7
- package/src/services/tauri-modules.d.ts +0 -50
- package/src/store/slices/desktopEntitlementSlice.ts +0 -86
- package/src/utils/desktopModelSnapshot.ts +0 -358
- package/src/utils/nativeSpatialDataStore.ts +0 -277
- package/src-tauri/Cargo.toml +0 -29
- package/src-tauri/build.rs +0 -7
- package/src-tauri/capabilities/default.json +0 -18
- 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 +0 -21
- package/src-tauri/src/main.rs +0 -10
- package/src-tauri/tauri.conf.json +0 -39
|
@@ -2,16 +2,11 @@
|
|
|
2
2
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { GeometryProcessor, GeometryQuality, type CoordinateInfo, type DynamicBatchConfig, type GeometryResult, type MeshData, type PointCloudAsset } from '@ifc-lite/geometry';
|
|
5
|
+
import { parseIfcx, type IfcDataStore, type PointCloudExtraction } from '@ifc-lite/parser';
|
|
6
|
+
import { type GeometryResult, type MeshData, type PointCloudAsset } from '@ifc-lite/geometry';
|
|
8
7
|
import { loadGLBToMeshData } from '@ifc-lite/cache';
|
|
9
8
|
import type { SchemaVersion } from '../../store/types.js';
|
|
10
|
-
import { calculateMeshBounds,
|
|
11
|
-
import { resolveDataStoreOrAbort } from './resolveDataStoreOrAbort.js';
|
|
12
|
-
import { watchedGeometryStream } from './watchedGeometryStream.js';
|
|
13
|
-
|
|
14
|
-
type RgbaColor = [number, number, number, number];
|
|
9
|
+
import { calculateMeshBounds, createCoordinateInfo, normalizeColor } from '../../utils/localParsingUtils.js';
|
|
15
10
|
|
|
16
11
|
interface RawIfcxMesh {
|
|
17
12
|
expressId?: number;
|
|
@@ -31,40 +26,6 @@ export interface ViewerModelPayload {
|
|
|
31
26
|
schemaVersion: SchemaVersion;
|
|
32
27
|
}
|
|
33
28
|
|
|
34
|
-
export interface StepBatchEvent {
|
|
35
|
-
batchIndex: number;
|
|
36
|
-
estimatedTotal: number;
|
|
37
|
-
totalSoFar: number;
|
|
38
|
-
meshes: MeshData[];
|
|
39
|
-
coordinateInfo?: CoordinateInfo | null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface StepRtcOffsetEvent {
|
|
43
|
-
rtcOffset: { x: number; y: number; z: number };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface StepBufferIngestOptions {
|
|
47
|
-
fileName: string;
|
|
48
|
-
buffer: ArrayBuffer;
|
|
49
|
-
fileSizeMB: number;
|
|
50
|
-
getDynamicBatchSize: (fileSizeMB: number) => number | DynamicBatchConfig;
|
|
51
|
-
onProgress?: (progress: { phase: string; percent: number }) => void;
|
|
52
|
-
onBatch?: (event: StepBatchEvent) => void;
|
|
53
|
-
onColorUpdate?: (updates: Map<number, RgbaColor>) => void;
|
|
54
|
-
onSpatialReady?: (dataStore: IfcDataStore) => void;
|
|
55
|
-
onRtcOffset?: (event: StepRtcOffsetEvent) => void;
|
|
56
|
-
shouldAbort?: () => boolean;
|
|
57
|
-
/** Shared RTC offset from first federated model (IFC Z-up coords).
|
|
58
|
-
* When set, this model uses the same RTC as the first model instead of
|
|
59
|
-
* computing its own, ensuring all models share the same coordinate space. */
|
|
60
|
-
sharedRtcOffset?: { x: number; y: number; z: number };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface StepBufferIngestResult extends ViewerModelPayload {
|
|
64
|
-
allMeshes: MeshData[];
|
|
65
|
-
cumulativeColorUpdates: Map<number, RgbaColor>;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
29
|
export function convertIfcxMeshes(rawMeshes: RawIfcxMesh[]): MeshData[] {
|
|
69
30
|
return rawMeshes.map((mesh) => {
|
|
70
31
|
const positions = mesh.positions instanceof Float32Array ? mesh.positions : new Float32Array(mesh.positions || []);
|
|
@@ -99,16 +60,6 @@ export function createMinimalGlbDataStore(buffer: ArrayBuffer, meshCount: number
|
|
|
99
60
|
} as unknown as IfcDataStore;
|
|
100
61
|
}
|
|
101
62
|
|
|
102
|
-
export function normalizeDataStoreStoreys(dataStore: IfcDataStore): IfcDataStore {
|
|
103
|
-
if (dataStore.spatialHierarchy && dataStore.spatialHierarchy.storeyHeights.size === 0 && dataStore.spatialHierarchy.storeyElevations.size > 1) {
|
|
104
|
-
const calculatedHeights = calculateStoreyHeights(dataStore.spatialHierarchy.storeyElevations);
|
|
105
|
-
for (const [storeyId, height] of calculatedHeights) {
|
|
106
|
-
dataStore.spatialHierarchy.storeyHeights.set(storeyId, height);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return dataStore;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
63
|
export function getMaxExpressId(dataStore: IfcDataStore, meshes: MeshData[]): number {
|
|
113
64
|
const maxExpressIdFromMeshes = meshes.reduce((max, mesh) => Math.max(max, mesh.expressId), 0);
|
|
114
65
|
let maxExpressIdFromEntities = 0;
|
|
@@ -213,163 +164,3 @@ export async function parseGlbViewerModel(buffer: ArrayBuffer): Promise<ViewerMo
|
|
|
213
164
|
schemaVersion: 'IFC4',
|
|
214
165
|
};
|
|
215
166
|
}
|
|
216
|
-
|
|
217
|
-
export async function parseStepBufferViewerModel(options: StepBufferIngestOptions): Promise<StepBufferIngestResult> {
|
|
218
|
-
const geometryProcessor = new GeometryProcessor({ quality: GeometryQuality.Balanced });
|
|
219
|
-
await geometryProcessor.init();
|
|
220
|
-
|
|
221
|
-
const parser = new IfcParser();
|
|
222
|
-
const wasmApi = geometryProcessor.getApi();
|
|
223
|
-
const canShareSource = WorkerParser.isSupported();
|
|
224
|
-
const sharedSource = canShareSource ? new SharedArrayBuffer(options.buffer.byteLength) : null;
|
|
225
|
-
if (sharedSource) {
|
|
226
|
-
new Uint8Array(sharedSource).set(new Uint8Array(options.buffer));
|
|
227
|
-
}
|
|
228
|
-
const geometryWillEmitEntityIndex =
|
|
229
|
-
sharedSource !== null
|
|
230
|
-
&& options.fileSizeMB >= 2
|
|
231
|
-
&& typeof Worker !== 'undefined'
|
|
232
|
-
&& typeof navigator !== 'undefined'
|
|
233
|
-
&& (navigator.hardwareConcurrency ?? 1) > 1;
|
|
234
|
-
let workerParser: WorkerParser | null = null;
|
|
235
|
-
const allMeshes: MeshData[] = [];
|
|
236
|
-
const cumulativeColorUpdates = new Map<number, RgbaColor>();
|
|
237
|
-
let finalCoordinateInfo: CoordinateInfo | null = null;
|
|
238
|
-
let batchIndex = 0;
|
|
239
|
-
let estimatedTotal = 0;
|
|
240
|
-
let capturedRtcOffset: { x: number; y: number; z: number } | null = null;
|
|
241
|
-
|
|
242
|
-
const handleSpatialReady = (partialStore: IfcDataStore) => {
|
|
243
|
-
if (options.shouldAbort?.()) {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
options.onSpatialReady?.(normalizeDataStoreStoreys(partialStore));
|
|
247
|
-
};
|
|
248
|
-
const dataStorePromise = sharedSource
|
|
249
|
-
? (() => {
|
|
250
|
-
workerParser = new WorkerParser();
|
|
251
|
-
return workerParser.parseColumnar(sharedSource, {
|
|
252
|
-
waitForEntityIndex: geometryWillEmitEntityIndex,
|
|
253
|
-
onSpatialReady: handleSpatialReady,
|
|
254
|
-
}).catch((error) => {
|
|
255
|
-
console.warn('[viewerModelIngest] Parser worker failed, falling back to main-thread parse:', error);
|
|
256
|
-
return parser.parseColumnar(options.buffer, {
|
|
257
|
-
wasmApi: wasmApi ?? undefined,
|
|
258
|
-
onSpatialReady: handleSpatialReady,
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
})()
|
|
262
|
-
: parser.parseColumnar(options.buffer, {
|
|
263
|
-
wasmApi: wasmApi ?? undefined,
|
|
264
|
-
onSpatialReady: handleSpatialReady,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
const geometryView = sharedSource ? new Uint8Array(sharedSource) : new Uint8Array(options.buffer);
|
|
268
|
-
const geometryStream = geometryProcessor.processAdaptive(geometryView, {
|
|
269
|
-
sizeThreshold: 2 * 1024 * 1024,
|
|
270
|
-
batchSize: options.getDynamicBatchSize(options.fileSizeMB),
|
|
271
|
-
sharedRtcOffset: options.sharedRtcOffset,
|
|
272
|
-
existingSab: sharedSource ?? undefined,
|
|
273
|
-
onEntityIndex: (ids, starts, lengths) => {
|
|
274
|
-
workerParser?.setEntityIndex(ids, starts, lengths);
|
|
275
|
-
},
|
|
276
|
-
});
|
|
277
|
-
let lastTotalMeshes = 0;
|
|
278
|
-
// The federated/added-model path was missing the size-aware stream watchdog
|
|
279
|
-
// the single-model loader has, so a geometry worker that failed to spawn would
|
|
280
|
-
// hang the load forever on "Processing geometry (N meshes)" instead of
|
|
281
|
-
// surfacing a recoverable error. watchedGeometryStream re-yields each event
|
|
282
|
-
// under that watchdog and bounds iterator teardown on every exit path.
|
|
283
|
-
try {
|
|
284
|
-
for await (const event of watchedGeometryStream(geometryStream, {
|
|
285
|
-
fileName: options.fileName,
|
|
286
|
-
fileSizeMB: options.fileSizeMB,
|
|
287
|
-
shouldAbort: options.shouldAbort,
|
|
288
|
-
getBatchCount: () => batchIndex,
|
|
289
|
-
getLastTotalMeshes: () => lastTotalMeshes,
|
|
290
|
-
})) {
|
|
291
|
-
switch (event.type) {
|
|
292
|
-
case 'start':
|
|
293
|
-
estimatedTotal = event.totalEstimate;
|
|
294
|
-
break;
|
|
295
|
-
case 'colorUpdate':
|
|
296
|
-
for (const [expressId, color] of event.updates) {
|
|
297
|
-
cumulativeColorUpdates.set(expressId, color);
|
|
298
|
-
}
|
|
299
|
-
options.onColorUpdate?.(event.updates);
|
|
300
|
-
break;
|
|
301
|
-
case 'rtcOffset':
|
|
302
|
-
if (event.hasRtc) {
|
|
303
|
-
capturedRtcOffset = event.rtcOffset;
|
|
304
|
-
options.onRtcOffset?.({ rtcOffset: event.rtcOffset });
|
|
305
|
-
}
|
|
306
|
-
break;
|
|
307
|
-
case 'batch':
|
|
308
|
-
batchIndex += 1;
|
|
309
|
-
for (let i = 0; i < event.meshes.length; i++) {
|
|
310
|
-
allMeshes.push(event.meshes[i]);
|
|
311
|
-
}
|
|
312
|
-
finalCoordinateInfo = event.coordinateInfo ?? null;
|
|
313
|
-
lastTotalMeshes = event.totalSoFar;
|
|
314
|
-
options.onBatch?.({
|
|
315
|
-
batchIndex,
|
|
316
|
-
estimatedTotal,
|
|
317
|
-
totalSoFar: event.totalSoFar,
|
|
318
|
-
meshes: event.meshes,
|
|
319
|
-
coordinateInfo: event.coordinateInfo ?? null,
|
|
320
|
-
});
|
|
321
|
-
options.onProgress?.({
|
|
322
|
-
phase: `Processing geometry (${event.totalSoFar} meshes)`,
|
|
323
|
-
percent: 10 + Math.min(80, (allMeshes.length / 1000) * 0.8),
|
|
324
|
-
});
|
|
325
|
-
break;
|
|
326
|
-
case 'complete':
|
|
327
|
-
finalCoordinateInfo = event.coordinateInfo ?? null;
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
} catch (err) {
|
|
332
|
-
// Watchdog stall (or other stream error): the parser worker may be
|
|
333
|
-
// blocked in `waitForEntityIndex`, which only the geometry pre-pass would
|
|
334
|
-
// unblock. Terminate it here so it doesn't leak — the normal path below
|
|
335
|
-
// still awaits it via resolveDataStoreOrAbort. watchedGeometryStream's
|
|
336
|
-
// finally has already bounded teardown of the geometry iterator itself.
|
|
337
|
-
workerParser?.terminate();
|
|
338
|
-
throw err;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// If the load was cancelled, don't await dataStorePromise: a worker parse
|
|
342
|
-
// started with waitForEntityIndex blocks until the geometry pre-pass hands
|
|
343
|
-
// over the entity index, which never happens once the geometry loop has been
|
|
344
|
-
// aborted above. resolveDataStoreOrAbort terminates the worker and throws an
|
|
345
|
-
// AbortError instead of hanging here.
|
|
346
|
-
const dataStore = normalizeDataStoreStoreys(
|
|
347
|
-
await resolveDataStoreOrAbort(dataStorePromise, {
|
|
348
|
-
aborted: options.shouldAbort?.() ?? false,
|
|
349
|
-
terminate: () => workerParser?.terminate(),
|
|
350
|
-
}),
|
|
351
|
-
);
|
|
352
|
-
if (!finalCoordinateInfo) {
|
|
353
|
-
finalCoordinateInfo = createCoordinateInfo(calculateMeshBounds(allMeshes).bounds);
|
|
354
|
-
}
|
|
355
|
-
if (capturedRtcOffset) {
|
|
356
|
-
finalCoordinateInfo.wasmRtcOffset = capturedRtcOffset;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return {
|
|
360
|
-
dataStore,
|
|
361
|
-
geometryResult: {
|
|
362
|
-
meshes: allMeshes,
|
|
363
|
-
totalVertices: allMeshes.reduce((sum, mesh) => sum + mesh.positions.length / 3, 0),
|
|
364
|
-
totalTriangles: allMeshes.reduce((sum, mesh) => sum + mesh.indices.length / 3, 0),
|
|
365
|
-
coordinateInfo: finalCoordinateInfo,
|
|
366
|
-
},
|
|
367
|
-
schemaVersion: dataStore.schemaVersion === 'IFC4X3'
|
|
368
|
-
? 'IFC4X3'
|
|
369
|
-
: dataStore.schemaVersion === 'IFC4'
|
|
370
|
-
? 'IFC4'
|
|
371
|
-
: 'IFC2X3',
|
|
372
|
-
allMeshes,
|
|
373
|
-
cumulativeColorUpdates,
|
|
374
|
-
};
|
|
375
|
-
}
|
|
Binary file
|
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
* Applies the model-comparison result to the 3D viewport (issue #924):
|
|
7
|
+
* per-entity colour overrides (added/modified/deleted/unchanged) plus a hidden
|
|
8
|
+
* set that suppresses the duplicated base-model geometry.
|
|
9
|
+
*
|
|
10
|
+
* Colours go through the same single overlay channel `useLens` uses
|
|
11
|
+
* (`setPendingColorUpdates` → `scene.setColorOverrides` — overlay batches drawn
|
|
12
|
+
* over the original geometry, instant to clear). The hidden set is reconciled
|
|
13
|
+
* with ownership tracking lifted from `useOverlayCompositor`: we remember which
|
|
14
|
+
* ids we hid and whether the user had already hidden them, so teardown only
|
|
15
|
+
* un-hides what we actually contributed.
|
|
16
|
+
*
|
|
17
|
+
* Mounted inside `ComparePanel`, so closing the panel (or clearing the result)
|
|
18
|
+
* restores the scene automatically.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useEffect, useRef } from 'react';
|
|
22
|
+
import { useViewerStore } from '@/store';
|
|
23
|
+
import { buildCompareOverlay } from '@/lib/compare/overlay';
|
|
24
|
+
|
|
25
|
+
type ViewerStore = ReturnType<typeof useViewerStore.getState>;
|
|
26
|
+
|
|
27
|
+
/** Hand the shared colour channel back to its prior owner when compare lets go:
|
|
28
|
+
* if a lens is still active, restore its overlay; otherwise clear. Prevents the
|
|
29
|
+
* compare panel from blanking lens colours on close (an active lens only has
|
|
30
|
+
* its panel hidden, not deactivated). */
|
|
31
|
+
function handBackColorChannel(store: ViewerStore): void {
|
|
32
|
+
const lensColors = store.lensAppliedColors;
|
|
33
|
+
store.setPendingColorUpdates(lensColors && lensColors.size > 0 ? new Map(lensColors) : new Map());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Re-show ids we hid that the user wasn't already hiding, then drop ownership. */
|
|
37
|
+
function restoreOwnedHidden(owned: Map<number, boolean>, store: ViewerStore): void {
|
|
38
|
+
if (owned.size === 0) return;
|
|
39
|
+
const toShow: number[] = [];
|
|
40
|
+
for (const [id, wasHidden] of owned) {
|
|
41
|
+
if (wasHidden === false) toShow.push(id);
|
|
42
|
+
}
|
|
43
|
+
if (toShow.length > 0) store.showEntities(toShow);
|
|
44
|
+
owned.clear();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Reconcile the desired hidden set against what we previously contributed. */
|
|
48
|
+
function reconcileHidden(
|
|
49
|
+
nextHidden: Set<number>,
|
|
50
|
+
ownedRef: { current: Map<number, boolean> },
|
|
51
|
+
store: ViewerStore,
|
|
52
|
+
): void {
|
|
53
|
+
const prev = ownedRef.current;
|
|
54
|
+
const currentlyHidden = store.hiddenEntities ?? new Set<number>();
|
|
55
|
+
|
|
56
|
+
const toShow: number[] = [];
|
|
57
|
+
for (const [id, wasHidden] of prev) {
|
|
58
|
+
if (!nextHidden.has(id) && wasHidden === false) toShow.push(id);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const next = new Map<number, boolean>();
|
|
62
|
+
const toHide: number[] = [];
|
|
63
|
+
for (const id of nextHidden) {
|
|
64
|
+
if (prev.has(id)) {
|
|
65
|
+
next.set(id, prev.get(id)!);
|
|
66
|
+
} else {
|
|
67
|
+
const wasHidden = currentlyHidden.has(id);
|
|
68
|
+
next.set(id, wasHidden);
|
|
69
|
+
if (!wasHidden) toHide.push(id);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (toShow.length > 0) store.showEntities(toShow);
|
|
74
|
+
if (toHide.length > 0) store.hideEntities(toHide);
|
|
75
|
+
ownedRef.current = next;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function useCompareOverlay(): void {
|
|
79
|
+
// global id → "was the user already hiding this when we took over?"
|
|
80
|
+
const ownedHiddenRef = useRef<Map<number, boolean>>(new Map());
|
|
81
|
+
const colorActiveRef = useRef(false);
|
|
82
|
+
|
|
83
|
+
const compareResult = useViewerStore((s) => s.compareResult);
|
|
84
|
+
const showUnchanged = useViewerStore((s) => s.compareShowUnchanged);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const store = useViewerStore.getState();
|
|
88
|
+
|
|
89
|
+
if (!compareResult) {
|
|
90
|
+
if (colorActiveRef.current) {
|
|
91
|
+
handBackColorChannel(store);
|
|
92
|
+
colorActiveRef.current = false;
|
|
93
|
+
}
|
|
94
|
+
restoreOwnedHidden(ownedHiddenRef.current, store);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { colorOverrides, hiddenIds } = buildCompareOverlay(compareResult.diff, showUnchanged);
|
|
99
|
+
reconcileHidden(hiddenIds, ownedHiddenRef, store);
|
|
100
|
+
// Empty map signals the consumer to clear overlays (lens contract).
|
|
101
|
+
store.setPendingColorUpdates(colorOverrides);
|
|
102
|
+
// We own the colour channel whenever a comparison is shown — even an empty
|
|
103
|
+
// override map clobbered any prior lens colours — so teardown must hand the
|
|
104
|
+
// channel back regardless of the map size (don't gate on `.size`).
|
|
105
|
+
colorActiveRef.current = true;
|
|
106
|
+
}, [compareResult, showUnchanged]);
|
|
107
|
+
|
|
108
|
+
// Teardown on unmount (panel closed) — restore the scene we touched.
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
return () => {
|
|
111
|
+
const store = useViewerStore.getState();
|
|
112
|
+
if (colorActiveRef.current) {
|
|
113
|
+
handBackColorChannel(store);
|
|
114
|
+
colorActiveRef.current = false;
|
|
115
|
+
}
|
|
116
|
+
restoreOwnedHidden(ownedHiddenRef.current, store);
|
|
117
|
+
};
|
|
118
|
+
}, []);
|
|
119
|
+
}
|