@ifc-lite/viewer 1.11.5 → 1.14.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 (36) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/dist/assets/{Arrow.dom-HFGUoQyp.js → Arrow.dom-CNguvlQi.js} +1 -1
  3. package/dist/assets/{browser-CllJKxsx.js → browser-D6lgLpkA.js} +1 -1
  4. package/dist/assets/{index-CQd80vMv.js → index-BMwpw264.js} +4 -4
  5. package/dist/assets/index-Qp8stcGO.css +1 -0
  6. package/dist/assets/{index-B69WAU-m.js → index-UaDsJsCR.js} +26434 -23023
  7. package/dist/assets/{native-bridge-Bu4SptAa.js → native-bridge-DqELq4X0.js} +1 -1
  8. package/dist/assets/{wasm-bridge-CR2KvcQN.js → wasm-bridge-CVWvHlfH.js} +1 -1
  9. package/dist/index.html +2 -2
  10. package/package.json +19 -19
  11. package/src/App.tsx +2 -0
  12. package/src/components/ui/toast.tsx +121 -0
  13. package/src/components/viewer/BulkPropertyEditor.tsx +8 -1
  14. package/src/components/viewer/DataConnector.tsx +8 -1
  15. package/src/components/viewer/ExportChangesButton.tsx +11 -2
  16. package/src/components/viewer/ExportDialog.tsx +224 -132
  17. package/src/components/viewer/MainToolbar.tsx +9 -2
  18. package/src/components/viewer/PropertiesPanel.tsx +300 -15
  19. package/src/components/viewer/properties/BsddCard.tsx +507 -0
  20. package/src/components/viewer/properties/QuantitySetCard.tsx +1 -0
  21. package/src/components/viewer/useGeometryStreaming.ts +4 -4
  22. package/src/index.css +7 -0
  23. package/src/lib/scripts/templates/bim-globals.d.ts +33 -0
  24. package/src/lib/scripts/templates/create-building.ts +491 -0
  25. package/src/lib/scripts/templates.ts +8 -0
  26. package/src/sdk/adapters/export-adapter.ts +84 -0
  27. package/src/sdk/adapters/lens-adapter.ts +1 -1
  28. package/src/sdk/adapters/model-adapter.ts +8 -0
  29. package/src/sdk/adapters/viewer-adapter.ts +1 -1
  30. package/src/services/bsdd.ts +262 -0
  31. package/src/store/index.ts +2 -2
  32. package/src/store/slices/measurementSlice.test.ts +22 -22
  33. package/src/store/slices/modelSlice.test.ts +2 -0
  34. package/src/store/slices/mutationSlice.ts +155 -1
  35. package/vite.config.ts +7 -0
  36. package/dist/assets/index-BoYyWYAu.css +0 -1
@@ -1,4 +1,4 @@
1
- import { _ as c, __tla as __tla_0 } from "./index-B69WAU-m.js";
1
+ import { _ as c, __tla as __tla_0 } from "./index-UaDsJsCR.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-B69WAU-m.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-UaDsJsCR.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,8 +44,8 @@
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-B69WAU-m.js"></script>
48
- <link rel="stylesheet" crossorigin href="/assets/index-BoYyWYAu.css">
47
+ <script type="module" crossorigin src="/assets/index-UaDsJsCR.js"></script>
48
+ <link rel="stylesheet" crossorigin href="/assets/index-Qp8stcGO.css">
49
49
  </head>
50
50
  <body>
51
51
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifc-lite/viewer",
3
- "version": "1.11.5",
3
+ "version": "1.14.0",
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.5",
45
- "@ifc-lite/cache": "^1.11.5",
46
- "@ifc-lite/data": "^1.11.5",
47
- "@ifc-lite/drawing-2d": "^1.11.5",
48
- "@ifc-lite/encoding": "^1.11.5",
49
- "@ifc-lite/export": "^1.11.5",
50
- "@ifc-lite/geometry": "^1.11.5",
51
- "@ifc-lite/ids": "^1.11.5",
52
- "@ifc-lite/lens": "^1.11.5",
53
- "@ifc-lite/lists": "^1.11.5",
54
- "@ifc-lite/mutations": "^1.11.5",
55
- "@ifc-lite/parser": "^1.11.5",
56
- "@ifc-lite/query": "^1.11.5",
57
- "@ifc-lite/renderer": "^1.11.5",
58
- "@ifc-lite/sandbox": "^1.11.5",
59
- "@ifc-lite/server-client": "^1.11.5",
60
- "@ifc-lite/spatial": "^1.11.5",
61
- "@ifc-lite/wasm": "^1.11.5"
44
+ "@ifc-lite/bcf": "^1.14.0",
45
+ "@ifc-lite/cache": "^1.14.0",
46
+ "@ifc-lite/data": "^1.14.0",
47
+ "@ifc-lite/drawing-2d": "^1.14.0",
48
+ "@ifc-lite/encoding": "^1.14.0",
49
+ "@ifc-lite/export": "^1.14.0",
50
+ "@ifc-lite/geometry": "^1.14.0",
51
+ "@ifc-lite/ids": "^1.14.0",
52
+ "@ifc-lite/lens": "^1.14.0",
53
+ "@ifc-lite/lists": "^1.14.0",
54
+ "@ifc-lite/mutations": "^1.14.0",
55
+ "@ifc-lite/parser": "^1.14.0",
56
+ "@ifc-lite/query": "^1.14.0",
57
+ "@ifc-lite/renderer": "^1.14.0",
58
+ "@ifc-lite/sandbox": "^1.14.0",
59
+ "@ifc-lite/server-client": "^1.14.0",
60
+ "@ifc-lite/spatial": "^1.14.0",
61
+ "@ifc-lite/wasm": "^1.14.0"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@tailwindcss/postcss": "^4.1.18",
package/src/App.tsx CHANGED
@@ -8,11 +8,13 @@
8
8
 
9
9
  import { ViewerLayout } from './components/viewer/ViewerLayout';
10
10
  import { BimProvider } from './sdk/BimProvider';
11
+ import { Toaster } from './components/ui/toast';
11
12
 
12
13
  export function App() {
13
14
  return (
14
15
  <BimProvider>
15
16
  <ViewerLayout />
17
+ <Toaster />
16
18
  </BimProvider>
17
19
  );
18
20
  }
@@ -0,0 +1,121 @@
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
+ * Lightweight toast notification system.
7
+ * Usage:
8
+ * import { toast } from '@/components/ui/toast';
9
+ * toast.success('Exported 42 entities');
10
+ * toast.error('Export failed');
11
+ */
12
+
13
+ import { useState, useEffect, useCallback, useRef } from 'react';
14
+ import { Check, AlertCircle, Download, X } from 'lucide-react';
15
+ import { cn } from '@/lib/utils';
16
+
17
+ // ─── Store (vanilla, framework-agnostic) ─────────────────────────────────
18
+
19
+ interface Toast {
20
+ id: number;
21
+ type: 'success' | 'error' | 'info';
22
+ message: string;
23
+ }
24
+
25
+ type Listener = () => void;
26
+
27
+ let nextId = 0;
28
+ let toasts: Toast[] = [];
29
+ const listeners = new Set<Listener>();
30
+
31
+ function notify() {
32
+ for (const l of listeners) l();
33
+ }
34
+
35
+ function addToast(type: Toast['type'], message: string, durationMs = 3000) {
36
+ const id = nextId++;
37
+ toasts = [...toasts, { id, type, message }];
38
+ notify();
39
+ setTimeout(() => {
40
+ toasts = toasts.filter((t) => t.id !== id);
41
+ notify();
42
+ }, durationMs);
43
+ }
44
+
45
+ function dismiss(id: number) {
46
+ toasts = toasts.filter((t) => t.id !== id);
47
+ notify();
48
+ }
49
+
50
+ /** Imperative toast API */
51
+ export const toast = {
52
+ success: (message: string) => addToast('success', message, 3000),
53
+ error: (message: string) => addToast('error', message, 5000),
54
+ info: (message: string) => addToast('info', message, 3000),
55
+ };
56
+
57
+ // ─── React Component ──────────────────────────────────────────────────────
58
+
59
+ function useToasts(): Toast[] {
60
+ const [, setTick] = useState(0);
61
+ const tickRef = useRef(0);
62
+
63
+ useEffect(() => {
64
+ const listener = () => {
65
+ tickRef.current++;
66
+ setTick(tickRef.current);
67
+ };
68
+ listeners.add(listener);
69
+ return () => { listeners.delete(listener); };
70
+ }, []);
71
+
72
+ return toasts;
73
+ }
74
+
75
+ const iconMap = {
76
+ success: Check,
77
+ error: AlertCircle,
78
+ info: Download,
79
+ };
80
+
81
+ const colorMap = {
82
+ success: 'border-emerald-500/50 bg-emerald-50 dark:bg-emerald-950/80 text-emerald-800 dark:text-emerald-200',
83
+ error: 'border-red-500/50 bg-red-50 dark:bg-red-950/80 text-red-800 dark:text-red-200',
84
+ info: 'border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200',
85
+ };
86
+
87
+ /** Mount this once at the app root (e.g. in App.tsx) */
88
+ export function Toaster() {
89
+ const items = useToasts();
90
+
91
+ const handleDismiss = useCallback((id: number) => dismiss(id), []);
92
+
93
+ if (items.length === 0) return null;
94
+
95
+ return (
96
+ <div className="fixed bottom-4 right-4 z-[9999] flex flex-col gap-2 pointer-events-none max-w-sm">
97
+ {items.map((t) => {
98
+ const Icon = iconMap[t.type];
99
+ return (
100
+ <div
101
+ key={t.id}
102
+ className={cn(
103
+ 'pointer-events-auto flex items-center gap-2 border-2 px-3 py-2 shadow-lg',
104
+ 'animate-in slide-in-from-bottom-2 fade-in-0 duration-200',
105
+ colorMap[t.type],
106
+ )}
107
+ >
108
+ <Icon className="h-4 w-4 shrink-0" />
109
+ <span className="text-xs font-medium flex-1 min-w-0">{t.message}</span>
110
+ <button
111
+ onClick={() => handleDismiss(t.id)}
112
+ className="shrink-0 p-0.5 rounded-sm hover:bg-black/10 dark:hover:bg-white/10"
113
+ >
114
+ <X className="h-3 w-3" />
115
+ </button>
116
+ </div>
117
+ );
118
+ })}
119
+ </div>
120
+ );
121
+ }
@@ -61,7 +61,7 @@ import {
61
61
  type BulkQueryPreview,
62
62
  type BulkQueryResult,
63
63
  } from '@ifc-lite/mutations';
64
- import { extractPropertiesOnDemand, type IfcDataStore } from '@ifc-lite/parser';
64
+ import { extractPropertiesOnDemand, extractQuantitiesOnDemand, type IfcDataStore } from '@ifc-lite/parser';
65
65
 
66
66
  // Common IFC type enum IDs (from IFC schema)
67
67
  // These correspond to the typeEnum values in EntityTable
@@ -245,6 +245,13 @@ export function BulkPropertyEditor({ trigger }: BulkPropertyEditorProps) {
245
245
  });
246
246
  }
247
247
 
248
+ // Set up on-demand quantity extraction if the data store supports it
249
+ if (dataStore.onDemandQuantityMap && dataStore.source?.length > 0) {
250
+ mutationView.setQuantityExtractor((entityId: number) => {
251
+ return extractQuantitiesOnDemand(dataStore as IfcDataStore, entityId);
252
+ });
253
+ }
254
+
248
255
  // Register the mutation view
249
256
  registerMutationView(selectedModelId, mutationView);
250
257
  }, [selectedModel, selectedModelId, getMutationView, registerMutationView]);
@@ -71,7 +71,7 @@ import {
71
71
  type MatchResult,
72
72
  type ImportStats,
73
73
  } from '@ifc-lite/mutations';
74
- import { extractPropertiesOnDemand, type IfcDataStore } from '@ifc-lite/parser';
74
+ import { extractPropertiesOnDemand, extractQuantitiesOnDemand, type IfcDataStore } from '@ifc-lite/parser';
75
75
 
76
76
  type MatchType = 'globalId' | 'expressId' | 'name' | 'property';
77
77
 
@@ -187,6 +187,13 @@ export function DataConnector({ trigger }: DataConnectorProps) {
187
187
  });
188
188
  }
189
189
 
190
+ // Set up on-demand quantity extraction if the data store supports it
191
+ if (dataStore.onDemandQuantityMap && dataStore.source?.length > 0) {
192
+ mutationView.setQuantityExtractor((entityId: number) => {
193
+ return extractQuantitiesOnDemand(dataStore as IfcDataStore, entityId);
194
+ });
195
+ }
196
+
190
197
  // Register the mutation view
191
198
  registerMutationView(selectedModelId, mutationView);
192
199
  }, [selectedModel, selectedModelId, getMutationView, registerMutationView]);
@@ -15,7 +15,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
15
15
  import { useViewerStore } from '@/store';
16
16
  import { StepExporter } from '@ifc-lite/export';
17
17
  import { MutablePropertyView } from '@ifc-lite/mutations';
18
- import { extractPropertiesOnDemand, type IfcDataStore } from '@ifc-lite/parser';
18
+ import { extractPropertiesOnDemand, extractQuantitiesOnDemand, type IfcDataStore } from '@ifc-lite/parser';
19
+ import { toast } from '@/components/ui/toast';
19
20
 
20
21
  interface ExportChangesButtonProps {
21
22
  /** Optional custom class name */
@@ -86,6 +87,13 @@ export function ExportChangesButton({ className }: ExportChangesButtonProps) {
86
87
  });
87
88
  }
88
89
 
90
+ // Set up on-demand quantity extraction
91
+ if (dataStore.onDemandQuantityMap && dataStore.source?.length > 0) {
92
+ mutationView.setQuantityExtractor((entityId: number) => {
93
+ return extractQuantitiesOnDemand(dataStore as IfcDataStore, entityId);
94
+ });
95
+ }
96
+
89
97
  registerMutationView(modelInfo.id, mutationView);
90
98
  }, [modelInfo, getMutationView, registerMutationView]);
91
99
 
@@ -147,11 +155,12 @@ export function ExportChangesButton({ className }: ExportChangesButtonProps) {
147
155
  // Reset status after 2 seconds
148
156
  setTimeout(() => setExportStatus('idle'), 2000);
149
157
 
150
- console.log(`[ExportChangesButton] Exported ${result.stats.entityCount} entities (${result.stats.modifiedEntityCount} modified)`);
158
+ toast.success(`Exported ${result.stats.entityCount} entities (${result.stats.modifiedEntityCount} modified)`);
151
159
  } catch (error) {
152
160
  console.error('[ExportChangesButton] Export failed:', error);
153
161
  setExportStatus('error');
154
162
  setTimeout(() => setExportStatus('idle'), 3000);
163
+ toast.error(`Export failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
155
164
  } finally {
156
165
  setIsExporting(false);
157
166
  }