@ifc-lite/viewer 1.11.2 → 1.11.3

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.
@@ -1,6 +1,6 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/browser-KT4JnbMQ.js","assets/index-B9MERyDx.js","assets/index-BoYyWYAu.css"])))=>i.map(i=>d[i]);
2
- import { _ as u, b as S, __tla as __tla_0 } from "./index-B9MERyDx.js";
3
- import { N as j, m as B, __tla as __tla_1 } from "./index-B9MERyDx.js";
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/browser-BVFgLomt.js","assets/index-CDQaZTvV.js","assets/index-BoYyWYAu.css"])))=>i.map(i=>d[i]);
2
+ import { _ as u, b as S, __tla as __tla_0 } from "./index-CDQaZTvV.js";
3
+ import { N as j, m as B, __tla as __tla_1 } from "./index-CDQaZTvV.js";
4
4
  let c, g, L, D, x, R, A;
5
5
  let __tla = Promise.all([
6
6
  (()=>{
@@ -87,7 +87,7 @@ let __tla = Promise.all([
87
87
  function k() {
88
88
  return m || (m = (async ()=>{
89
89
  try {
90
- const e = await u(()=>import("./browser-KT4JnbMQ.js").then((i)=>i.b), __vite__mapDeps([0,1,2])), t = e.default ?? e;
90
+ const e = await u(()=>import("./browser-BVFgLomt.js").then((i)=>i.b), __vite__mapDeps([0,1,2])), t = e.default ?? e;
91
91
  let s;
92
92
  try {
93
93
  s = (await u(()=>import("./esbuild-COv63sf-.js"), [])).default;
@@ -1,4 +1,4 @@
1
- import { _ as c, __tla as __tla_0 } from "./index-B9MERyDx.js";
1
+ import { _ as c, __tla as __tla_0 } from "./index-CDQaZTvV.js";
2
2
  let m;
3
3
  let __tla = Promise.all([
4
4
  (()=>{
@@ -1 +1 @@
1
- import{I as f,a as m}from"./index-B9MERyDx.js";class u{bridge;initialized=!1;constructor(){this.bridge=new f}async init(){this.initialized||(await this.bridge.init(),this.initialized=!0)}isInitialized(){return this.initialized}async processGeometry(s){this.initialized||await this.init(),performance.now();const i=new m(this.bridge.getApi(),s),n=i.collectMeshes(),r=i.getBuildingRotation();performance.now();let e=0,o=0;for(const c of n)e+=c.positions.length/3,o+=c.indices.length/3;return{meshes:n,totalVertices:e,totalTriangles:o,coordinateInfo:{originShift:{x:0,y:0,z:0},originalBounds:{min:{x:0,y:0,z:0},max:{x:0,y:0,z:0}},shiftedBounds:{min:{x:0,y:0,z:0},max:{x:0,y:0,z:0}},hasLargeCoordinates:!1,buildingRotation:r}}}async processGeometryStreaming(s,i){this.initialized||await this.init();const n=performance.now(),r=new m(this.bridge.getApi(),s);let e=0,o=0,a=0;try{for await(const t of r.collectMeshesStreaming(50)){if(t&&typeof t=="object"&&"type"in t&&t.type==="colorUpdate")continue;const l=t;e+=l.length;for(const d of l)o+=d.positions.length/3,a+=d.indices.length/3;i.onBatch?.({meshes:l,progress:{processed:e,total:e,currentType:"processing"}})}}catch(t){throw i.onError?.(t instanceof Error?t:new Error(String(t))),t}const h=performance.now()-n,g={totalMeshes:e,totalVertices:o,totalTriangles:a,parseTimeMs:h*.3,geometryTimeMs:h*.7};return i.onComplete?.(g),g}getApi(){return this.bridge.getApi()}}export{u as WasmBridge};
1
+ import{I as f,a as m}from"./index-CDQaZTvV.js";class u{bridge;initialized=!1;constructor(){this.bridge=new f}async init(){this.initialized||(await this.bridge.init(),this.initialized=!0)}isInitialized(){return this.initialized}async processGeometry(s){this.initialized||await this.init(),performance.now();const i=new m(this.bridge.getApi(),s),n=i.collectMeshes(),r=i.getBuildingRotation();performance.now();let e=0,o=0;for(const c of n)e+=c.positions.length/3,o+=c.indices.length/3;return{meshes:n,totalVertices:e,totalTriangles:o,coordinateInfo:{originShift:{x:0,y:0,z:0},originalBounds:{min:{x:0,y:0,z:0},max:{x:0,y:0,z:0}},shiftedBounds:{min:{x:0,y:0,z:0},max:{x:0,y:0,z:0}},hasLargeCoordinates:!1,buildingRotation:r}}}async processGeometryStreaming(s,i){this.initialized||await this.init();const n=performance.now(),r=new m(this.bridge.getApi(),s);let e=0,o=0,a=0;try{for await(const t of r.collectMeshesStreaming(50)){if(t&&typeof t=="object"&&"type"in t&&t.type==="colorUpdate")continue;const l=t;e+=l.length;for(const d of l)o+=d.positions.length/3,a+=d.indices.length/3;i.onBatch?.({meshes:l,progress:{processed:e,total:e,currentType:"processing"}})}}catch(t){throw i.onError?.(t instanceof Error?t:new Error(String(t))),t}const h=performance.now()-n,g={totalMeshes:e,totalVertices:o,totalTriangles:a,parseTimeMs:h*.3,geometryTimeMs:h*.7};return i.onComplete?.(g),g}getApi(){return this.bridge.getApi()}}export{u as WasmBridge};
package/dist/index.html CHANGED
@@ -44,7 +44,7 @@
44
44
  <meta name="theme-color" content="#7aa2f7">
45
45
  <meta name="msapplication-TileColor" content="#1a1b26">
46
46
  <meta name="msapplication-TileImage" content="/favicon-192x192-cropped.png">
47
- <script type="module" crossorigin src="/assets/index-B9MERyDx.js"></script>
47
+ <script type="module" crossorigin src="/assets/index-CDQaZTvV.js"></script>
48
48
  <link rel="stylesheet" crossorigin href="/assets/index-BoYyWYAu.css">
49
49
  </head>
50
50
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifc-lite/viewer",
3
- "version": "1.11.2",
3
+ "version": "1.11.3",
4
4
  "description": "IFC-Lite viewer application",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -41,24 +41,24 @@
41
41
  "tailwind-merge": "^3.4.0",
42
42
  "tailwindcss": "^4.1.18",
43
43
  "zustand": "^4.4.0",
44
- "@ifc-lite/bcf": "^1.11.2",
45
- "@ifc-lite/cache": "^1.11.2",
46
- "@ifc-lite/data": "^1.11.2",
47
- "@ifc-lite/drawing-2d": "^1.11.2",
48
- "@ifc-lite/encoding": "^1.11.2",
49
- "@ifc-lite/export": "^1.11.2",
50
- "@ifc-lite/geometry": "^1.11.2",
51
- "@ifc-lite/ids": "^1.11.2",
52
- "@ifc-lite/lens": "^1.11.2",
53
- "@ifc-lite/lists": "^1.11.2",
54
- "@ifc-lite/mutations": "^1.11.2",
55
- "@ifc-lite/parser": "^1.11.2",
56
- "@ifc-lite/query": "^1.11.2",
57
- "@ifc-lite/renderer": "^1.11.2",
58
- "@ifc-lite/sandbox": "^1.11.2",
59
- "@ifc-lite/server-client": "^1.11.2",
60
- "@ifc-lite/spatial": "^1.11.2",
61
- "@ifc-lite/wasm": "^1.11.2"
44
+ "@ifc-lite/bcf": "^1.11.3",
45
+ "@ifc-lite/cache": "^1.11.3",
46
+ "@ifc-lite/data": "^1.11.3",
47
+ "@ifc-lite/drawing-2d": "^1.11.3",
48
+ "@ifc-lite/encoding": "^1.11.3",
49
+ "@ifc-lite/export": "^1.11.3",
50
+ "@ifc-lite/geometry": "^1.11.3",
51
+ "@ifc-lite/ids": "^1.11.3",
52
+ "@ifc-lite/lens": "^1.11.3",
53
+ "@ifc-lite/lists": "^1.11.3",
54
+ "@ifc-lite/mutations": "^1.11.3",
55
+ "@ifc-lite/parser": "^1.11.3",
56
+ "@ifc-lite/query": "^1.11.3",
57
+ "@ifc-lite/renderer": "^1.11.3",
58
+ "@ifc-lite/sandbox": "^1.11.3",
59
+ "@ifc-lite/server-client": "^1.11.3",
60
+ "@ifc-lite/spatial": "^1.11.3",
61
+ "@ifc-lite/wasm": "^1.11.3"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@tailwindcss/postcss": "^4.1.18",
@@ -17,7 +17,7 @@ import { GeometryProcessor, GeometryQuality, type MeshData, type CoordinateInfo
17
17
  import { buildSpatialIndex } from '@ifc-lite/spatial';
18
18
  import { type GeometryData, loadGLBToMeshData } from '@ifc-lite/cache';
19
19
 
20
- import { SERVER_URL, USE_SERVER, CACHE_SIZE_THRESHOLD, getDynamicBatchConfig } from '../utils/ifcConfig.js';
20
+ import { SERVER_URL, USE_SERVER, CACHE_SIZE_THRESHOLD, CACHE_MAX_SOURCE_SIZE, getDynamicBatchConfig } from '../utils/ifcConfig.js';
21
21
  import {
22
22
  calculateMeshBounds,
23
23
  createCoordinateInfo,
@@ -324,8 +324,9 @@ export function useIfcLoader() {
324
324
  });
325
325
  };
326
326
 
327
- // Schedule data model parsing to start after geometry begins streaming
328
- setTimeout(startDataModelParsing, 0);
327
+ // Data model parsing is deferred to the 'complete' event (see below).
328
+ // Running it concurrently with geometry streaming steals main-thread cycles
329
+ // from the WASM↔JS bridge, adding ~1-2s to geometry completion time.
329
330
 
330
331
  // Use adaptive processing: sync for small files, streaming for large files
331
332
  let estimatedTotal = 0;
@@ -378,12 +379,14 @@ export function useIfcLoader() {
378
379
  console.log(`[useIfc] Model opened at ${modelOpenMs.toFixed(0)}ms`);
379
380
  break;
380
381
  case 'colorUpdate': {
381
- // Persist parser style/material colors in store (non-overlay path).
382
- updateMeshColors(event.updates);
383
- // Keep local mesh snapshots in sync for cache serialization.
382
+ // Accumulate color updates locally during streaming.
383
+ // We apply them in a single pass at 'complete' instead of
384
+ // calling updateMeshColors() per event (which triggers a
385
+ // React reconciliation each time + O(n) scan over all meshes).
384
386
  for (const [expressId, color] of event.updates) {
385
387
  cumulativeColorUpdates.set(expressId, color);
386
388
  }
389
+ // Keep local mesh snapshots in sync for cache serialization.
387
390
  applyColorUpdatesToMeshes(allMeshes, event.updates);
388
391
  applyColorUpdatesToMeshes(pendingMeshes, event.updates);
389
392
  break;
@@ -447,6 +450,16 @@ export function useIfcLoader() {
447
450
 
448
451
  finalCoordinateInfo = event.coordinateInfo ?? null;
449
452
 
453
+ // Start data model parsing NOW — after geometry streaming is done
454
+ // so the parser doesn't compete with WASM for main-thread CPU.
455
+ startDataModelParsing();
456
+
457
+ // Apply all accumulated color updates in a single store update
458
+ // instead of one updateMeshColors() call per colorUpdate event.
459
+ if (cumulativeColorUpdates.size > 0) {
460
+ updateMeshColors(cumulativeColorUpdates);
461
+ }
462
+
450
463
  // Store captured RTC offset in coordinate info for multi-model alignment
451
464
  if (finalCoordinateInfo && capturedRtcOffset) {
452
465
  finalCoordinateInfo.wasmRtcOffset = capturedRtcOffset;
@@ -483,8 +496,17 @@ export function useIfcLoader() {
483
496
  }
484
497
  }
485
498
 
486
- // Cache the result in the background (for files above threshold)
487
- if (buffer.byteLength >= CACHE_SIZE_THRESHOLD && allMeshes.length > 0 && finalCoordinateInfo) {
499
+ // Cache the result in the background (files between 10 MB and 150 MB).
500
+ // Files above CACHE_MAX_SOURCE_SIZE are not cached because the
501
+ // source buffer is required for on-demand property/quantity
502
+ // extraction, spatial hierarchy elevations, and IFC re-export.
503
+ // Caching without it would silently degrade those features.
504
+ if (
505
+ buffer.byteLength >= CACHE_SIZE_THRESHOLD &&
506
+ buffer.byteLength <= CACHE_MAX_SOURCE_SIZE &&
507
+ allMeshes.length > 0 &&
508
+ finalCoordinateInfo
509
+ ) {
488
510
  // Final safety pass so cache always contains post-style colors.
489
511
  applyColorUpdatesToMeshes(allMeshes, cumulativeColorUpdates);
490
512
  const geometryData: GeometryData = {
@@ -62,27 +62,34 @@ export const createDataSlice: StateCreator<DataSlice, [], [], DataSlice> = (set)
62
62
  setGeometryResult: (geometryResult) => set({ geometryResult }),
63
63
 
64
64
  appendGeometryBatch: (meshes, coordinateInfo) => set((state) => {
65
+ // Incremental totals: O(batch_size) instead of O(total_accumulated) .reduce()
66
+ let batchTriangles = 0;
67
+ let batchVertices = 0;
68
+ for (let i = 0; i < meshes.length; i++) {
69
+ batchTriangles += meshes[i].indices.length / 3;
70
+ batchVertices += meshes[i].positions.length / 3;
71
+ }
72
+
65
73
  if (!state.geometryResult) {
66
- const totalTriangles = meshes.reduce((sum, m) => sum + (m.indices.length / 3), 0);
67
- const totalVertices = meshes.reduce((sum, m) => sum + (m.positions.length / 3), 0);
68
74
  return {
69
75
  geometryResult: {
70
- meshes,
71
- totalTriangles,
72
- totalVertices,
76
+ meshes: meshes.slice(),
77
+ totalTriangles: batchTriangles,
78
+ totalVertices: batchVertices,
73
79
  coordinateInfo: coordinateInfo || getDefaultCoordinateInfo(),
74
80
  },
75
81
  };
76
82
  }
77
- const allMeshes = [...state.geometryResult.meshes, ...meshes];
78
- const totalTriangles = allMeshes.reduce((sum, m) => sum + (m.indices.length / 3), 0);
79
- const totalVertices = allMeshes.reduce((sum, m) => sum + (m.positions.length / 3), 0);
83
+
84
+ // New array reference (required for React/Zustand change detection) but
85
+ // only O(n) pointer copies the expensive part was the .reduce() calls
86
+ // which are now replaced by the incremental counters above.
80
87
  return {
81
88
  geometryResult: {
82
89
  ...state.geometryResult,
83
- meshes: allMeshes,
84
- totalTriangles,
85
- totalVertices,
90
+ meshes: [...state.geometryResult.meshes, ...meshes],
91
+ totalTriangles: state.geometryResult.totalTriangles + batchTriangles,
92
+ totalVertices: state.geometryResult.totalVertices + batchVertices,
86
93
  coordinateInfo: coordinateInfo || state.geometryResult.coordinateInfo,
87
94
  },
88
95
  };
@@ -98,7 +105,8 @@ export const createDataSlice: StateCreator<DataSlice, [], [], DataSlice> = (set)
98
105
  return { pendingMeshColorUpdates: clonedUpdates };
99
106
  }
100
107
 
101
- // Legacy/single mode: update the persisted mesh colors.
108
+ // New array reference so useGeometryStreaming's useEffect detects the change.
109
+ // Only runs once at 'complete' (not per-batch), so O(n) .map() is fine.
102
110
  const updatedMeshes = state.geometryResult.meshes.map(mesh => {
103
111
  const newColor = clonedUpdates.get(mesh.expressId);
104
112
  if (newColor) {
@@ -39,6 +39,13 @@ export const USE_SERVER = SERVER_URL !== '' && import.meta.env.VITE_USE_SERVER =
39
39
  /** Minimum file size to cache (10MB) - smaller files parse quickly anyway */
40
40
  export const CACHE_SIZE_THRESHOLD = 10 * 1024 * 1024;
41
41
 
42
+ /** Maximum file size eligible for caching (150MB).
43
+ * Files above this are not cached at all because the source buffer is required
44
+ * for on-demand property/quantity extraction, spatial hierarchy elevations,
45
+ * and IFC re-export. Caching without it would silently degrade those features,
46
+ * and including it would make the IndexedDB write prohibitively large. */
47
+ export const CACHE_MAX_SOURCE_SIZE = 150 * 1024 * 1024;
48
+
42
49
  /** File size thresholds for various optimizations */
43
50
  export const THRESHOLDS = {
44
51
  /** Use streaming Parquet above this (150MB) */