@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.
- package/CHANGELOG.md +91 -0
- package/dist/assets/{Arrow.dom-HFGUoQyp.js → Arrow.dom-CNguvlQi.js} +1 -1
- package/dist/assets/{browser-CllJKxsx.js → browser-D6lgLpkA.js} +1 -1
- package/dist/assets/{index-CQd80vMv.js → index-BMwpw264.js} +4 -4
- package/dist/assets/index-Qp8stcGO.css +1 -0
- package/dist/assets/{index-B69WAU-m.js → index-UaDsJsCR.js} +26434 -23023
- package/dist/assets/{native-bridge-Bu4SptAa.js → native-bridge-DqELq4X0.js} +1 -1
- package/dist/assets/{wasm-bridge-CR2KvcQN.js → wasm-bridge-CVWvHlfH.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +19 -19
- package/src/App.tsx +2 -0
- package/src/components/ui/toast.tsx +121 -0
- package/src/components/viewer/BulkPropertyEditor.tsx +8 -1
- package/src/components/viewer/DataConnector.tsx +8 -1
- package/src/components/viewer/ExportChangesButton.tsx +11 -2
- package/src/components/viewer/ExportDialog.tsx +224 -132
- package/src/components/viewer/MainToolbar.tsx +9 -2
- package/src/components/viewer/PropertiesPanel.tsx +300 -15
- package/src/components/viewer/properties/BsddCard.tsx +507 -0
- package/src/components/viewer/properties/QuantitySetCard.tsx +1 -0
- package/src/components/viewer/useGeometryStreaming.ts +4 -4
- package/src/index.css +7 -0
- package/src/lib/scripts/templates/bim-globals.d.ts +33 -0
- package/src/lib/scripts/templates/create-building.ts +491 -0
- package/src/lib/scripts/templates.ts +8 -0
- package/src/sdk/adapters/export-adapter.ts +84 -0
- package/src/sdk/adapters/lens-adapter.ts +1 -1
- package/src/sdk/adapters/model-adapter.ts +8 -0
- package/src/sdk/adapters/viewer-adapter.ts +1 -1
- package/src/services/bsdd.ts +262 -0
- package/src/store/index.ts +2 -2
- package/src/store/slices/measurementSlice.test.ts +22 -22
- package/src/store/slices/modelSlice.test.ts +2 -0
- package/src/store/slices/mutationSlice.ts +155 -1
- package/vite.config.ts +7 -0
- package/dist/assets/index-BoYyWYAu.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{I as f,a as m}from"./index-
|
|
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-
|
|
48
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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.
|
|
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.
|
|
45
|
-
"@ifc-lite/cache": "^1.
|
|
46
|
-
"@ifc-lite/data": "^1.
|
|
47
|
-
"@ifc-lite/drawing-2d": "^1.
|
|
48
|
-
"@ifc-lite/encoding": "^1.
|
|
49
|
-
"@ifc-lite/export": "^1.
|
|
50
|
-
"@ifc-lite/geometry": "^1.
|
|
51
|
-
"@ifc-lite/ids": "^1.
|
|
52
|
-
"@ifc-lite/lens": "^1.
|
|
53
|
-
"@ifc-lite/lists": "^1.
|
|
54
|
-
"@ifc-lite/mutations": "^1.
|
|
55
|
-
"@ifc-lite/parser": "^1.
|
|
56
|
-
"@ifc-lite/query": "^1.
|
|
57
|
-
"@ifc-lite/renderer": "^1.
|
|
58
|
-
"@ifc-lite/sandbox": "^1.
|
|
59
|
-
"@ifc-lite/server-client": "^1.
|
|
60
|
-
"@ifc-lite/spatial": "^1.
|
|
61
|
-
"@ifc-lite/wasm": "^1.
|
|
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
|
-
|
|
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
|
}
|