@ifc-lite/viewer 1.17.2 → 1.17.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.
Files changed (62) hide show
  1. package/.turbo/turbo-build.log +30 -29
  2. package/.turbo/turbo-typecheck.log +1 -42
  3. package/CHANGELOG.md +9 -0
  4. package/dist/assets/arrow-DJf2ErbF.js +20 -0
  5. package/dist/assets/basketViewActivator-aojwdomq.js +1 -0
  6. package/dist/assets/bcf-D5-QWGO9.js +281 -0
  7. package/dist/assets/{browser-BDShTXzi.js → browser-CKs-FY1P.js} +1 -1
  8. package/dist/assets/drawing-2d-gWfpdfYe.js +257 -0
  9. package/dist/assets/epsg-index.generated-BjJrt_0S.js +1 -0
  10. package/dist/assets/exporters-C_6J153K.js +79896 -0
  11. package/dist/assets/geometry.worker-Nz9_YIqh.js +1 -0
  12. package/dist/assets/ids-B4jTqB1O.js +1 -0
  13. package/dist/assets/{ifc-lite_bg-FNRmpSvM.wasm → ifc-lite_bg-eSkBTizQ.wasm} +0 -0
  14. package/dist/assets/index-jhBr1wbn.js +101666 -0
  15. package/dist/assets/index-pbE7itQS.css +1 -0
  16. package/dist/assets/lens-CSASnhAL.js +1 -0
  17. package/dist/assets/maplibre-gl-BpvwNKKy.js +811 -0
  18. package/dist/assets/{native-bridge-Crsb7TKz.js → native-bridge-DSIyEYXG.js} +6 -4
  19. package/dist/assets/{arrow2-bb-jcVEo.js → parquet-CEXmQNRO.js} +2 -2
  20. package/dist/assets/sandbox-B79eavQ3.js +5933 -0
  21. package/dist/assets/server-client-D3bUPJJc.js +626 -0
  22. package/dist/assets/wasm-bridge-B0J07fZZ.js +1 -0
  23. package/dist/assets/zip-B-jFFAGa.js +12 -0
  24. package/dist/index.html +11 -2
  25. package/package.json +24 -19
  26. package/src/components/viewer/ExportChangesButton.tsx +18 -3
  27. package/src/components/viewer/ExportDialog.tsx +16 -3
  28. package/src/components/viewer/HierarchyPanel.tsx +6 -6
  29. package/src/components/viewer/PropertiesPanel.tsx +96 -60
  30. package/src/components/viewer/Section2DPanel.tsx +3 -2
  31. package/src/components/viewer/ViewportContainer.tsx +5 -4
  32. package/src/components/viewer/hierarchy/treeDataBuilder.ts +2 -1
  33. package/src/components/viewer/properties/EpsgLookupDialog.tsx +418 -0
  34. package/src/components/viewer/properties/GeoreferencingPanel.tsx +591 -0
  35. package/src/components/viewer/properties/LocationMap.tsx +289 -0
  36. package/src/components/viewer/properties/ModelMetadataPanel.tsx +3 -70
  37. package/src/hooks/bcfIdLookup.ts +13 -11
  38. package/src/hooks/ids/idsColorSystem.ts +3 -8
  39. package/src/hooks/useIDS.ts +31 -16
  40. package/src/hooks/useIfcFederation.ts +2 -2
  41. package/src/lib/geo/kmz-exporter.ts +112 -0
  42. package/src/lib/geo/reproject.ts +244 -0
  43. package/src/lib/lens/adapter.ts +3 -1
  44. package/src/main.tsx +1 -0
  45. package/src/sdk/adapters/export-adapter.ts +14 -1
  46. package/src/sdk/adapters/viewer-adapter.ts +5 -9
  47. package/src/sdk/adapters/visibility-adapter.ts +6 -9
  48. package/src/store/basketVisibleSet.ts +3 -4
  49. package/src/store/globalId.ts +79 -0
  50. package/src/store/index.ts +1 -0
  51. package/src/store/slices/mutationSlice.ts +178 -0
  52. package/src/store/slices/pinboardSlice.ts +4 -8
  53. package/vite.config.ts +17 -0
  54. package/dist/assets/Arrow.dom-BhOg9lpn.js +0 -20
  55. package/dist/assets/arrow2_bg-BlXl-cSQ.js +0 -1
  56. package/dist/assets/basketViewActivator-BRG5DBmM.js +0 -1
  57. package/dist/assets/geometry.worker-kgiT_Qhh.js +0 -1
  58. package/dist/assets/index-B1Ecw4AU.js +0 -189756
  59. package/dist/assets/index-Ba4eoTe7.css +0 -1
  60. package/dist/assets/index-CrgYBjTn.js +0 -229
  61. package/dist/assets/module-6F3E5H7Y-tx0BadV3.js +0 -6
  62. package/dist/assets/wasm-bridge-mJUhb7uk.js +0 -1
@@ -11,6 +11,13 @@ import type { ViewerState } from '../index.js';
11
11
  import type { MutablePropertyView } from '@ifc-lite/mutations';
12
12
  import type { Mutation, ChangeSet, PropertyValue } from '@ifc-lite/mutations';
13
13
  import { PropertyValueType, QuantityType } from '@ifc-lite/data';
14
+ import type { MapConversion, ProjectedCRS } from '@ifc-lite/parser';
15
+
16
+ /** Tracks georeferencing field mutations per model */
17
+ export interface GeorefMutationData {
18
+ projectedCRS?: Partial<ProjectedCRS>;
19
+ mapConversion?: Partial<MapConversion>;
20
+ }
14
21
 
15
22
  export interface MutationSlice {
16
23
  // State
@@ -28,6 +35,26 @@ export interface MutationSlice {
28
35
  dirtyModels: Set<string>;
29
36
  /** Version counter to trigger re-renders when mutations change */
30
37
  mutationVersion: number;
38
+ /** Georeferencing mutations per model */
39
+ georefMutations: Map<string, GeorefMutationData>;
40
+
41
+ // Actions - Georeferencing Mutations
42
+ /** Set a georeferencing field value */
43
+ setGeorefField: (
44
+ modelId: string,
45
+ entity: 'projectedCRS' | 'mapConversion',
46
+ field: string,
47
+ value: string | number,
48
+ oldValue?: string | number
49
+ ) => void;
50
+ /** Set multiple georeferencing field values atomically */
51
+ setGeorefFields: (
52
+ modelId: string,
53
+ entity: 'projectedCRS' | 'mapConversion',
54
+ fields: Array<{ field: string; value: string | number; oldValue?: string | number }>
55
+ ) => void;
56
+ /** Get merged georef mutations for a model */
57
+ getGeorefMutations: (modelId: string) => GeorefMutationData | undefined;
31
58
 
32
59
  // Actions - Mutation View Management
33
60
  /** Get or create mutation view for a model */
@@ -154,6 +181,60 @@ export const createMutationSlice: StateCreator<
154
181
  redoStacks: new Map(),
155
182
  dirtyModels: new Set(),
156
183
  mutationVersion: 0,
184
+ georefMutations: new Map(),
185
+
186
+ // Georeferencing Mutations
187
+ setGeorefField: (modelId, entity, field, value, oldValue) => {
188
+ get().setGeorefFields(modelId, entity, [{ field, value, oldValue }]);
189
+ },
190
+
191
+ setGeorefFields: (modelId, entity, fields) => {
192
+ if (fields.length === 0) return;
193
+ set((state) => {
194
+ const newGeorefMuts = new Map(state.georefMutations);
195
+ const modelMuts = { ...(newGeorefMuts.get(modelId) || {}) };
196
+ const entityMuts = { ...(modelMuts[entity] || {}) } as Record<string, unknown>;
197
+ for (const entry of fields) {
198
+ entityMuts[entry.field] = entry.value;
199
+ }
200
+ newGeorefMuts.set(modelId, { ...modelMuts, [entity]: entityMuts });
201
+
202
+ // Track undo
203
+ const newUndoStacks = new Map(state.undoStacks);
204
+ const stack = newUndoStacks.get(modelId) || [];
205
+ const nextMutations: Mutation[] = fields.map(entry => ({
206
+ id: `mut_georef_${entity}_${entry.field}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
207
+ type: 'UPDATE_ATTRIBUTE',
208
+ timestamp: Date.now(),
209
+ modelId,
210
+ entityId: 0, // georef entities don't map to a specific element
211
+ attributeName: `georef.${entity}.${entry.field}`,
212
+ oldValue: entry.oldValue,
213
+ newValue: entry.value,
214
+ propName: entry.field,
215
+ psetName: entity,
216
+ }));
217
+ newUndoStacks.set(modelId, [...stack, ...nextMutations]);
218
+
219
+ const newRedoStacks = new Map(state.redoStacks);
220
+ newRedoStacks.set(modelId, []);
221
+
222
+ const newDirty = new Set(state.dirtyModels);
223
+ newDirty.add(modelId);
224
+
225
+ return {
226
+ georefMutations: newGeorefMuts,
227
+ undoStacks: newUndoStacks,
228
+ redoStacks: newRedoStacks,
229
+ dirtyModels: newDirty,
230
+ mutationVersion: state.mutationVersion + 1,
231
+ };
232
+ });
233
+ },
234
+
235
+ getGeorefMutations: (modelId) => {
236
+ return get().georefMutations.get(modelId);
237
+ },
157
238
 
158
239
  // Mutation View Management
159
240
  getMutationView: (modelId) => {
@@ -388,6 +469,48 @@ export const createMutationSlice: StateCreator<
388
469
  if (undoStack.length === 0) return;
389
470
 
390
471
  const mutation = undoStack[undoStack.length - 1];
472
+
473
+ // Handle georef mutations directly on georefMutations map
474
+ if (mutation.type === 'UPDATE_ATTRIBUTE' && mutation.attributeName?.startsWith('georef.')) {
475
+ const parts = mutation.attributeName.split('.');
476
+ const entity = parts[1] as 'projectedCRS' | 'mapConversion';
477
+ const field = parts[2];
478
+ set((s) => {
479
+ const newGeorefMuts = new Map(s.georefMutations);
480
+ const modelMuts = { ...(newGeorefMuts.get(modelId) || {}) };
481
+ const entityMuts = { ...(modelMuts[entity] || {}) } as Record<string, unknown>;
482
+ if (mutation.oldValue !== undefined && mutation.oldValue !== null) {
483
+ entityMuts[field] = mutation.oldValue;
484
+ } else {
485
+ delete entityMuts[field];
486
+ }
487
+ if (Object.keys(entityMuts).length === 0) {
488
+ delete modelMuts[entity];
489
+ } else {
490
+ modelMuts[entity] = entityMuts as typeof modelMuts[typeof entity];
491
+ }
492
+ if (Object.keys(modelMuts).length === 0) {
493
+ newGeorefMuts.delete(modelId);
494
+ } else {
495
+ newGeorefMuts.set(modelId, modelMuts);
496
+ }
497
+
498
+ const newUndoStacks = new Map(s.undoStacks);
499
+ newUndoStacks.set(modelId, undoStack.slice(0, -1));
500
+ const newRedoStacks = new Map(s.redoStacks);
501
+ const redoStack = newRedoStacks.get(modelId) || [];
502
+ newRedoStacks.set(modelId, [...redoStack, mutation]);
503
+
504
+ return {
505
+ georefMutations: newGeorefMuts,
506
+ undoStacks: newUndoStacks,
507
+ redoStacks: newRedoStacks,
508
+ mutationVersion: s.mutationVersion + 1,
509
+ };
510
+ });
511
+ return;
512
+ }
513
+
391
514
  const view = state.mutationViews.get(modelId);
392
515
  if (!view) return;
393
516
 
@@ -465,6 +588,48 @@ export const createMutationSlice: StateCreator<
465
588
  if (redoStack.length === 0) return;
466
589
 
467
590
  const mutation = redoStack[redoStack.length - 1];
591
+
592
+ // Handle georef mutations directly
593
+ if (mutation.type === 'UPDATE_ATTRIBUTE' && mutation.attributeName?.startsWith('georef.')) {
594
+ const parts = mutation.attributeName.split('.');
595
+ const entity = parts[1] as 'projectedCRS' | 'mapConversion';
596
+ const field = parts[2];
597
+ set((s) => {
598
+ const newGeorefMuts = new Map(s.georefMutations);
599
+ const modelMuts = { ...(newGeorefMuts.get(modelId) || {}) };
600
+ const entityMuts = { ...(modelMuts[entity] || {}) } as Record<string, unknown>;
601
+ if (mutation.newValue !== undefined && mutation.newValue !== null) {
602
+ entityMuts[field] = mutation.newValue;
603
+ } else {
604
+ delete entityMuts[field];
605
+ }
606
+ if (Object.keys(entityMuts).length === 0) {
607
+ delete modelMuts[entity];
608
+ } else {
609
+ modelMuts[entity] = entityMuts as typeof modelMuts[typeof entity];
610
+ }
611
+ if (Object.keys(modelMuts).length === 0) {
612
+ newGeorefMuts.delete(modelId);
613
+ } else {
614
+ newGeorefMuts.set(modelId, modelMuts);
615
+ }
616
+
617
+ const newRedoStacks = new Map(s.redoStacks);
618
+ newRedoStacks.set(modelId, redoStack.slice(0, -1));
619
+ const newUndoStacks = new Map(s.undoStacks);
620
+ const undoStack = newUndoStacks.get(modelId) || [];
621
+ newUndoStacks.set(modelId, [...undoStack, mutation]);
622
+
623
+ return {
624
+ georefMutations: newGeorefMuts,
625
+ undoStacks: newUndoStacks,
626
+ redoStacks: newRedoStacks,
627
+ mutationVersion: s.mutationVersion + 1,
628
+ };
629
+ });
630
+ return;
631
+ }
632
+
468
633
  const view = state.mutationViews.get(modelId);
469
634
  if (!view) return;
470
635
 
@@ -606,6 +771,14 @@ export const createMutationSlice: StateCreator<
606
771
  for (const view of get().mutationViews.values()) {
607
772
  count += view.getModifiedEntityCount();
608
773
  }
774
+ // Include models with georef-only edits
775
+ for (const [modelId, gm] of get().georefMutations) {
776
+ const hasGeoref = (gm.projectedCRS && Object.keys(gm.projectedCRS).length > 0)
777
+ || (gm.mapConversion && Object.keys(gm.mapConversion).length > 0);
778
+ if (hasGeoref && !get().mutationViews.has(modelId)) {
779
+ count += 1; // count the model as having modifications
780
+ }
781
+ }
609
782
  return count;
610
783
  },
611
784
 
@@ -626,10 +799,14 @@ export const createMutationSlice: StateCreator<
626
799
  const newDirty = new Set(state.dirtyModels);
627
800
  newDirty.delete(modelId);
628
801
 
802
+ const newGeorefMuts = new Map(state.georefMutations);
803
+ newGeorefMuts.delete(modelId);
804
+
629
805
  return {
630
806
  undoStacks: newUndoStacks,
631
807
  redoStacks: newRedoStacks,
632
808
  dirtyModels: newDirty,
809
+ georefMutations: newGeorefMuts,
633
810
  mutationVersion: state.mutationVersion + 1,
634
811
  };
635
812
  });
@@ -644,6 +821,7 @@ export const createMutationSlice: StateCreator<
644
821
  undoStacks: new Map(),
645
822
  redoStacks: new Map(),
646
823
  dirtyModels: new Set(),
824
+ georefMutations: new Map(),
647
825
  mutationVersion: state.mutationVersion + 1,
648
826
  }));
649
827
  },
@@ -20,6 +20,7 @@ import type { StateCreator } from 'zustand';
20
20
  import type { Drawing2D } from '@ifc-lite/drawing-2d';
21
21
  import type { CameraCallbacks, CameraViewpoint, EntityRef, SectionPlane } from '../types.js';
22
22
  import { entityRefToString, stringToEntityRef } from '../types.js';
23
+ import { toGlobalIdForRef } from '../globalId.js';
23
24
 
24
25
  export type BasketSource = 'selection' | 'visible' | 'hierarchy' | 'manual';
25
26
 
@@ -148,17 +149,14 @@ function basketToGlobalIds(
148
149
  const globalIds = new Set<number>();
149
150
  for (const str of basketEntities) {
150
151
  const ref = stringToEntityRef(str);
151
- const model = models.get(ref.modelId);
152
- const offset = model?.idOffset ?? 0;
153
- globalIds.add(ref.expressId + offset);
152
+ globalIds.add(toGlobalIdForRef(models, ref));
154
153
  }
155
154
  return globalIds;
156
155
  }
157
156
 
158
157
  /** Compute a single EntityRef's global ID */
159
158
  function refToGlobalId(ref: EntityRef, models: Map<string, { idOffset: number }>): number {
160
- const model = models.get(ref.modelId);
161
- return ref.expressId + (model?.idOffset ?? 0);
159
+ return toGlobalIdForRef(models, ref);
162
160
  }
163
161
 
164
162
  function refsToEntityKeySet(refs: EntityRef[]): Set<string> {
@@ -195,9 +193,7 @@ function computeBasketVisibility(
195
193
  }
196
194
  const hiddenEntities = new Set<number>(currentHidden);
197
195
  for (const ref of unhideRefs) {
198
- const model = models.get(ref.modelId);
199
- const offset = model?.idOffset ?? 0;
200
- hiddenEntities.delete(ref.expressId + offset);
196
+ hiddenEntities.delete(toGlobalIdForRef(models, ref));
201
197
  }
202
198
  return { isolatedEntities, hiddenEntities };
203
199
  }
package/vite.config.ts CHANGED
@@ -275,6 +275,23 @@ export default defineConfig({
275
275
  build: {
276
276
  target: 'esnext',
277
277
  chunkSizeWarningLimit: 6000,
278
+ rollupOptions: {
279
+ output: {
280
+ manualChunks(id) {
281
+ if (id.includes('/packages/sandbox/')) return 'sandbox';
282
+ if (id.includes('/packages/export/')) return 'exporters';
283
+ if (id.includes('/packages/server-client/')) return 'server-client';
284
+ if (id.includes('/packages/bcf/')) return 'bcf';
285
+ if (id.includes('/packages/ids/')) return 'ids';
286
+ if (id.includes('/packages/lens/')) return 'lens';
287
+ if (id.includes('/packages/drawing-2d/')) return 'drawing-2d';
288
+ if (id.includes('/node_modules/jszip/')) return 'zip';
289
+ if (id.includes('/node_modules/apache-arrow/')) return 'arrow';
290
+ if (id.includes('/node_modules/parquet-wasm/')) return 'parquet';
291
+ return undefined;
292
+ },
293
+ },
294
+ },
278
295
  },
279
296
  optimizeDeps: {
280
297
  exclude: [