@ifc-lite/viewer 1.28.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 +34 -41
- package/CHANGELOG.md +10 -0
- package/dist/assets/{basketViewActivator-BNRDNuUJ.js → basketViewActivator-Ce38DhXd.js} +7 -7
- package/dist/assets/{bcf-DCwCuP7n.js → bcf-Cv_O3JfD.js} +1 -1
- package/dist/assets/{deflate-DNGgs8Ur.js → deflate-HbyMq59o.js} +1 -1
- package/dist/assets/drawing-2d-DW98umlt.js +257 -0
- package/dist/assets/{exporters-B9v81gi9.js → exporters-BuD3XRzB.js} +463 -416
- package/dist/assets/geometry.worker-TH3fCCoY.js +1 -0
- package/dist/assets/{geotiff-D-YCLS4g.js → geotiff-B2HA8Bwm.js} +10 -10
- package/dist/assets/{ids-CCpq-5d3.js → ids-DYUFMd5f.js} +4 -4
- package/dist/assets/{ifc-lite_bg-DbgS5EUA.wasm → ifc-lite_bg-BEA5DLmg.wasm} +0 -0
- package/dist/assets/index-E9wB0zWt.css +1 -0
- package/dist/assets/{index-Bgb3_Pu_.js → index-n5O1QJMM.js} +36808 -39415
- package/dist/assets/{index.es-CWfqZyyr.js → index.es-BKVIpZgL.js} +8 -8
- package/dist/assets/{jpeg-DGOAeUqU.js → jpeg-C7hjKjPX.js} +1 -1
- package/dist/assets/{jspdf.es.min-XPLU2Wkq.js → jspdf.es.min-oWlFc42Y.js} +4 -4
- package/dist/assets/{lerc-1PMSCHwX.js → lerc-BfIOGhQz.js} +1 -1
- package/dist/assets/{lzw-C65U9lNM.js → lzw-B0jRuuW5.js} +1 -1
- package/dist/assets/{native-bridge-XxXos6yI.js → native-bridge-DpB-dtEn.js} +5 -2
- package/dist/assets/{packbits-BdMWXC3m.js → packbits-DVvBTC39.js} +1 -1
- package/dist/assets/{parser.worker-Ddwo3_06.js → parser.worker-BDsWQ6rc.js} +1 -1
- package/dist/assets/{pdf-CRwaZf3s.js → pdf-dVIqI5ac.js} +9 -9
- package/dist/assets/raw-C0ZJYGmN.js +1 -0
- package/dist/assets/{sandbox-0sDo3g3m.js → sandbox-qpJlrNN0.js} +8 -8
- package/dist/assets/{server-client-cTCJ-853.js → server-client-DVZ2huNS.js} +1 -1
- package/dist/assets/{webimage-BtakWX7W.js → webimage-B394g0Tw.js} +1 -1
- package/dist/assets/{xlsx-B1YOg2QB.js → xlsx-D-oHO76J.js} +7 -7
- package/dist/assets/{zstd-CmwsbxmM.js → zstd-Bf38MwV2.js} +1 -1
- package/dist/index.html +8 -8
- package/package.json +5 -5
- package/src/App.tsx +1 -3
- package/src/components/viewer/BCFPanel.tsx +1 -16
- package/src/components/viewer/ChatPanel.tsx +11 -46
- package/src/components/viewer/HierarchyPanel.tsx +2 -176
- package/src/components/viewer/IDSPanel.tsx +1 -26
- package/src/components/viewer/MainToolbar.tsx +75 -185
- package/src/components/viewer/MobileToolbar.tsx +1 -9
- package/src/components/viewer/PropertiesPanel.tsx +28 -126
- package/src/components/viewer/ScriptPanel.tsx +8 -34
- package/src/components/viewer/Section2DPanel.tsx +32 -1
- package/src/components/viewer/ViewerLayout.tsx +0 -2
- package/src/components/viewer/ViewportContainer.tsx +24 -42
- package/src/components/viewer/ViewportOverlays.tsx +1 -4
- package/src/components/viewer/useGeometryStreaming.ts +0 -2
- package/src/hooks/ingest/federationAlign.ts +7 -0
- package/src/hooks/useDrawingGeneration.ts +211 -13
- package/src/hooks/useIfcCache.ts +94 -41
- package/src/hooks/useIfcFederation.ts +2 -3
- package/src/hooks/useIfcLoader.ts +10 -1051
- 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/index.ts +3 -5
- package/src/store/slices/drawing2DSlice.ts +8 -0
- package/src/store/slices/visibilitySlice.ts +22 -1
- package/src/store/types.ts +1 -71
- package/src/utils/ifcConfig.ts +0 -12
- package/vite.config.ts +6 -3
- package/DESKTOP_CONTRACT_VERSION +0 -1
- package/dist/assets/drawing-2d-D0dDf6Lh.js +0 -257
- package/dist/assets/event-B0kAzHa-.js +0 -1
- package/dist/assets/geometry.worker-Bpa3115V.js +0 -1
- package/dist/assets/index-BtbXFKsX.css +0 -1
- package/dist/assets/raw-CJgQdyuZ.js +0 -1
- 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/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 -359
- 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
|
@@ -3,16 +3,11 @@
|
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Dynamically loads the appropriate cache implementation based on platform:
|
|
8
|
-
* - Tauri (desktop): Uses native filesystem via desktop-cache.ts
|
|
9
|
-
* - Web: Uses IndexedDB via ifc-cache.ts
|
|
6
|
+
* Cache service — IndexedDB-backed cache (ifc-cache.ts) for the web build.
|
|
10
7
|
*
|
|
11
8
|
* Extracted from useIfc.ts for reusability and testability
|
|
12
9
|
*/
|
|
13
10
|
|
|
14
|
-
import { isTauri } from '../utils/ifcConfig.js';
|
|
15
|
-
|
|
16
11
|
// ============================================================================
|
|
17
12
|
// Types
|
|
18
13
|
// ============================================================================
|
|
@@ -65,29 +60,18 @@ export interface ICacheService {
|
|
|
65
60
|
let cacheService: ICacheService | null = null;
|
|
66
61
|
|
|
67
62
|
/**
|
|
68
|
-
* Get the cache service
|
|
69
|
-
* Lazily loads the
|
|
63
|
+
* Get the cache service.
|
|
64
|
+
* Lazily loads the IndexedDB implementation.
|
|
70
65
|
*/
|
|
71
66
|
export async function getCacheService(): Promise<ICacheService> {
|
|
72
67
|
if (cacheService) return cacheService;
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
deleteCached: mod.deleteCached,
|
|
81
|
-
};
|
|
82
|
-
} else {
|
|
83
|
-
// Web: Use IndexedDB
|
|
84
|
-
const mod = await import('./ifc-cache.js');
|
|
85
|
-
cacheService = {
|
|
86
|
-
getCached: mod.getCached,
|
|
87
|
-
setCached: mod.setCached,
|
|
88
|
-
deleteCached: mod.deleteCached,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
69
|
+
const mod = await import('./ifc-cache.js');
|
|
70
|
+
cacheService = {
|
|
71
|
+
getCached: mod.getCached,
|
|
72
|
+
setCached: mod.setCached,
|
|
73
|
+
deleteCached: mod.deleteCached,
|
|
74
|
+
};
|
|
91
75
|
|
|
92
76
|
return cacheService;
|
|
93
77
|
}
|
|
@@ -2,34 +2,8 @@
|
|
|
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 {
|
|
5
|
+
import { type IfcDataStore } from '@ifc-lite/parser';
|
|
6
6
|
import { getViewerStoreApi } from '@/store';
|
|
7
|
-
import { toast } from '@/components/ui/toast';
|
|
8
|
-
import { readNativeFile } from '@/services/file-dialog';
|
|
9
|
-
|
|
10
|
-
const exportHydrationByModel = new Map<string, Promise<IfcDataStore | null>>();
|
|
11
|
-
|
|
12
|
-
function isDesktopRuntime(): boolean {
|
|
13
|
-
// `globalThis` and `Window` aren't structurally compatible per TS, so
|
|
14
|
-
// route through `unknown` first — the cast is intentional.
|
|
15
|
-
const win = globalThis as unknown as Window & { __TAURI_INTERNALS__?: { invoke?: unknown } };
|
|
16
|
-
return typeof win.__TAURI_INTERNALS__?.invoke === 'function';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function hasFullStepSource(dataStore: IfcDataStore | null | undefined): dataStore is IfcDataStore {
|
|
20
|
-
return Boolean(dataStore?.source?.length && dataStore.entityIndex?.byId?.size);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function toExactArrayBuffer(bytes: Uint8Array): ArrayBuffer {
|
|
24
|
-
if (
|
|
25
|
-
bytes.buffer instanceof ArrayBuffer
|
|
26
|
-
&& bytes.byteOffset === 0
|
|
27
|
-
&& bytes.byteLength === bytes.buffer.byteLength
|
|
28
|
-
) {
|
|
29
|
-
return bytes.buffer;
|
|
30
|
-
}
|
|
31
|
-
return bytes.slice().buffer;
|
|
32
|
-
}
|
|
33
7
|
|
|
34
8
|
export async function ensureModelExportReady(modelId: string): Promise<IfcDataStore | null> {
|
|
35
9
|
const store = getViewerStoreApi();
|
|
@@ -44,36 +18,5 @@ export async function ensureModelExportReady(modelId: string): Promise<IfcDataSt
|
|
|
44
18
|
return null;
|
|
45
19
|
}
|
|
46
20
|
|
|
47
|
-
|
|
48
|
-
return model.ifcDataStore;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!isDesktopRuntime() || !model.nativeMetadata?.filePath) {
|
|
52
|
-
return model.ifcDataStore;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const pending = exportHydrationByModel.get(modelId);
|
|
56
|
-
if (pending) {
|
|
57
|
-
return pending;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const hydrationPromise = (async () => {
|
|
61
|
-
toast.info(`Preparing ${model.name} for IFC export...`);
|
|
62
|
-
const bytes = await readNativeFile(model.nativeMetadata!.filePath);
|
|
63
|
-
const parser = new IfcParser();
|
|
64
|
-
const hydratedStore = await parser.parseColumnar(toExactArrayBuffer(bytes));
|
|
65
|
-
|
|
66
|
-
store.getState().updateModel(modelId, {
|
|
67
|
-
ifcDataStore: hydratedStore,
|
|
68
|
-
schemaVersion: hydratedStore.schemaVersion,
|
|
69
|
-
metadataLoadState: 'complete',
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return hydratedStore;
|
|
73
|
-
})().finally(() => {
|
|
74
|
-
exportHydrationByModel.delete(modelId);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
exportHydrationByModel.set(modelId, hydrationPromise);
|
|
78
|
-
return hydrationPromise;
|
|
21
|
+
return model.ifcDataStore;
|
|
79
22
|
}
|
|
@@ -2,151 +2,17 @@
|
|
|
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
|
-
type InvokeFn = <T>(cmd: string, args?: Record<string, unknown>) => Promise<T>;
|
|
6
|
-
|
|
7
|
-
export interface NativeFileHandle {
|
|
8
|
-
path: string;
|
|
9
|
-
name: string;
|
|
10
|
-
size: number;
|
|
11
|
-
modifiedMs?: number | null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
5
|
export interface GenericFileDialogOptions {
|
|
15
6
|
title?: string;
|
|
16
7
|
filters?: Array<{ name: string; extensions: string[] }>;
|
|
17
8
|
}
|
|
18
9
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
async function loadDialogModule(): Promise<{
|
|
29
|
-
open: (options?: {
|
|
30
|
-
multiple?: boolean;
|
|
31
|
-
directory?: boolean;
|
|
32
|
-
filters?: Array<{ name: string; extensions: string[] }>;
|
|
33
|
-
title?: string;
|
|
34
|
-
}) => Promise<string | string[] | null>;
|
|
35
|
-
} | null> {
|
|
36
|
-
try {
|
|
37
|
-
return await import('@tauri-apps/plugin-dialog');
|
|
38
|
-
} catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function loadFsModule(): Promise<{
|
|
44
|
-
stat: (path: string) => Promise<{ size: number }>;
|
|
45
|
-
} | null> {
|
|
46
|
-
try {
|
|
47
|
-
return await import('@tauri-apps/plugin-fs');
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function getInvoke(): Promise<InvokeFn> {
|
|
54
|
-
const win = globalThis as unknown as { __TAURI_INTERNALS__?: { invoke: InvokeFn } };
|
|
55
|
-
if (win.__TAURI_INTERNALS__?.invoke) {
|
|
56
|
-
return win.__TAURI_INTERNALS__.invoke;
|
|
57
|
-
}
|
|
58
|
-
const moduleInvoke = await loadInvokeFromTauriModule();
|
|
59
|
-
if (moduleInvoke) {
|
|
60
|
-
return moduleInvoke;
|
|
61
|
-
}
|
|
62
|
-
throw new Error('Tauri API not available');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function openIfcFileDialog(): Promise<NativeFileHandle | null> {
|
|
66
|
-
try {
|
|
67
|
-
const invoke = await getInvoke();
|
|
68
|
-
return await invoke<NativeFileHandle | null>('open_ifc_file');
|
|
69
|
-
} catch {
|
|
70
|
-
// Expected in browser builds — fall through to plugin fallback, then browser file input.
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const dialog = await loadDialogModule();
|
|
75
|
-
const fs = await loadFsModule();
|
|
76
|
-
if (!dialog || !fs) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const selected = await dialog.open({
|
|
81
|
-
multiple: false,
|
|
82
|
-
directory: false,
|
|
83
|
-
title: 'Open IFC, Mesh or Point Cloud File',
|
|
84
|
-
filters: [
|
|
85
|
-
{ name: 'IFC Files', extensions: ['ifc', 'ifczip', 'ifcxml', 'ifcx'] },
|
|
86
|
-
{ name: 'Mesh Files', extensions: ['glb'] },
|
|
87
|
-
{ name: 'Point Clouds', extensions: ['las', 'laz', 'ply', 'pcd', 'e57'] },
|
|
88
|
-
{ name: 'All Files', extensions: ['*'] },
|
|
89
|
-
],
|
|
90
|
-
});
|
|
91
|
-
if (!selected || Array.isArray(selected)) {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const metadata = await fs.stat(selected);
|
|
96
|
-
const normalizedPath = selected.toString();
|
|
97
|
-
const pathSegments = normalizedPath.split(/[\\/]/);
|
|
98
|
-
const name = pathSegments[pathSegments.length - 1] || 'unknown.ifc';
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
path: normalizedPath,
|
|
102
|
-
name,
|
|
103
|
-
size: metadata.size,
|
|
104
|
-
};
|
|
105
|
-
} catch {
|
|
106
|
-
// No Tauri plugin available — caller falls back to browser <input type="file">.
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export async function readNativeFile(path: string): Promise<Uint8Array> {
|
|
112
|
-
try {
|
|
113
|
-
const invoke = await getInvoke();
|
|
114
|
-
const bytes = await invoke<number[]>('read_native_file', { path });
|
|
115
|
-
return Uint8Array.from(bytes);
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.warn('[FileDialog] Falling back to plugin-fs read for native file:', error);
|
|
118
|
-
const fs = await import('@tauri-apps/plugin-fs');
|
|
119
|
-
return fs.readFile(path);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export async function openGenericFileDialog(options: GenericFileDialogOptions = {}): Promise<File | null> {
|
|
124
|
-
try {
|
|
125
|
-
const dialog = await loadDialogModule();
|
|
126
|
-
if (!dialog) {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const selected = await dialog.open({
|
|
131
|
-
multiple: false,
|
|
132
|
-
directory: false,
|
|
133
|
-
title: options.title,
|
|
134
|
-
filters: options.filters,
|
|
135
|
-
});
|
|
136
|
-
if (!selected || Array.isArray(selected)) {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const normalizedPath = selected.toString();
|
|
141
|
-
const bytes = await readNativeFile(normalizedPath);
|
|
142
|
-
const pathSegments = normalizedPath.split(/[\\/]/);
|
|
143
|
-
const name = pathSegments[pathSegments.length - 1] || 'document';
|
|
144
|
-
// Slice to a fresh ArrayBuffer view — TS5+ narrows `Uint8Array` to
|
|
145
|
-
// `Uint8Array<ArrayBufferLike>` which `BlobPart` doesn't accept.
|
|
146
|
-
const blobPart = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength).slice();
|
|
147
|
-
return new File([blobPart], name, { type: 'application/octet-stream' });
|
|
148
|
-
} catch (error) {
|
|
149
|
-
console.warn('[FileDialog] Failed to open generic native file dialog:', error);
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
10
|
+
/**
|
|
11
|
+
* Browser-only build: there is no native OS file dialog, so this resolves to
|
|
12
|
+
* `null` and callers fall back to a browser `<input type="file">`. (ifc-lite no
|
|
13
|
+
* longer ships a desktop app; third parties building their own desktop shell on
|
|
14
|
+
* the published packages supply native file access in their own host layer.)
|
|
15
|
+
*/
|
|
16
|
+
export async function openGenericFileDialog(_options: GenericFileDialogOptions = {}): Promise<null> {
|
|
17
|
+
return null;
|
|
152
18
|
}
|
package/src/store/constants.ts
CHANGED
|
@@ -222,6 +222,29 @@ export function getPersistedTypeVisibility(): TypeVisibility {
|
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* The 3D view mode for the Model/Types switch (#957 follow-up).
|
|
227
|
+
* 'model' — show placed occurrences (the default; the building as designed).
|
|
228
|
+
* 'types' — show the type-library shapes (each IfcTypeProduct's
|
|
229
|
+
* RepresentationMap at its MappingOrigin), hiding occurrences.
|
|
230
|
+
* Orphan type geometry (a type with no occurrence, e.g. annex-E showcase files)
|
|
231
|
+
* shows in BOTH modes since it is the only geometry the file has.
|
|
232
|
+
*/
|
|
233
|
+
export type TypeViewMode = 'model' | 'types';
|
|
234
|
+
|
|
235
|
+
export const TYPE_VIEW_MODE_STORAGE_KEY = 'ifc-lite-type-view-mode';
|
|
236
|
+
export const TYPE_VIEW_MODE_DEFAULT: TypeViewMode = 'model';
|
|
237
|
+
|
|
238
|
+
/** Resolve the persisted Model/Types view mode (read fresh, like type visibility). */
|
|
239
|
+
export function getPersistedTypeViewMode(): TypeViewMode {
|
|
240
|
+
if (typeof window === 'undefined') return TYPE_VIEW_MODE_DEFAULT;
|
|
241
|
+
try {
|
|
242
|
+
return localStorage.getItem(TYPE_VIEW_MODE_STORAGE_KEY) === 'types' ? 'types' : 'model';
|
|
243
|
+
} catch {
|
|
244
|
+
return TYPE_VIEW_MODE_DEFAULT;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
225
248
|
// ============================================================================
|
|
226
249
|
// Data Defaults
|
|
227
250
|
// ============================================================================
|
package/src/store/index.ts
CHANGED
|
@@ -38,7 +38,6 @@ import { createCompareSlice, type CompareSlice } from './slices/compareSlice.js'
|
|
|
38
38
|
import { createScriptSlice, type ScriptSlice } from './slices/scriptSlice.js';
|
|
39
39
|
import { createChatSlice, type ChatSlice } from './slices/chatSlice.js';
|
|
40
40
|
import { createCesiumSlice, type CesiumSlice } from './slices/cesiumSlice.js';
|
|
41
|
-
import { createDesktopEntitlementSlice, type DesktopEntitlementSlice } from './slices/desktopEntitlementSlice.js';
|
|
42
41
|
import { createScheduleSlice, type ScheduleSlice } from './slices/scheduleSlice.js';
|
|
43
42
|
import { createPlaybackSlice, type PlaybackSlice } from './slices/playbackSlice.js';
|
|
44
43
|
import { createOverlaySlice, type OverlaySlice } from './slices/overlaySlice.js';
|
|
@@ -51,7 +50,7 @@ import { createPointCloudSlice, type PointCloudSlice, POINT_CLOUD_DEFAULTS } fro
|
|
|
51
50
|
import { invalidateVisibleBasketCache } from './basketVisibleSet.js';
|
|
52
51
|
|
|
53
52
|
// Import constants for reset function
|
|
54
|
-
import { CAMERA_DEFAULTS, SECTION_PLANE_DEFAULTS, UI_DEFAULTS, getPersistedTypeVisibility } from './constants.js';
|
|
53
|
+
import { CAMERA_DEFAULTS, SECTION_PLANE_DEFAULTS, UI_DEFAULTS, getPersistedTypeVisibility, getPersistedTypeViewMode } from './constants.js';
|
|
55
54
|
|
|
56
55
|
// Re-export types for consumers
|
|
57
56
|
export type * from './types.js';
|
|
@@ -93,7 +92,6 @@ export type { ScriptSlice } from './slices/scriptSlice.js';
|
|
|
93
92
|
|
|
94
93
|
// Re-export Chat types
|
|
95
94
|
export type { ChatSlice } from './slices/chatSlice.js';
|
|
96
|
-
export type { DesktopEntitlementSlice } from './slices/desktopEntitlementSlice.js';
|
|
97
95
|
|
|
98
96
|
// Re-export Cesium types
|
|
99
97
|
export type { CesiumSlice, CesiumDataSource, CesiumPlacementDraft } from './slices/cesiumSlice.js';
|
|
@@ -137,7 +135,6 @@ export type ViewerState = LoadingSlice &
|
|
|
137
135
|
ScriptSlice &
|
|
138
136
|
ChatSlice &
|
|
139
137
|
CesiumSlice &
|
|
140
|
-
DesktopEntitlementSlice &
|
|
141
138
|
ScheduleSlice &
|
|
142
139
|
PlaybackSlice &
|
|
143
140
|
OverlaySlice &
|
|
@@ -189,7 +186,6 @@ const createViewerStore = () => create<ViewerState>()((...args) => ({
|
|
|
189
186
|
...createScriptSlice(...args),
|
|
190
187
|
...createChatSlice(...args),
|
|
191
188
|
...createCesiumSlice(...args),
|
|
192
|
-
...createDesktopEntitlementSlice(...args),
|
|
193
189
|
...createScheduleSlice(...args),
|
|
194
190
|
...createPlaybackSlice(...args),
|
|
195
191
|
...createOverlaySlice(...args),
|
|
@@ -225,6 +221,7 @@ const createViewerStore = () => create<ViewerState>()((...args) => ({
|
|
|
225
221
|
// Re-read persisted toggles on every file load so a new model never
|
|
226
222
|
// reverts the user's visibility choices (e.g. "Show Annotations").
|
|
227
223
|
typeVisibility: getPersistedTypeVisibility(),
|
|
224
|
+
typeViewMode: getPersistedTypeViewMode(),
|
|
228
225
|
|
|
229
226
|
// Visibility (multi-model)
|
|
230
227
|
hiddenEntitiesByModel: new Map(),
|
|
@@ -330,6 +327,7 @@ const createViewerStore = () => create<ViewerState>()((...args) => ({
|
|
|
330
327
|
scale: 100,
|
|
331
328
|
useSymbolicRepresentations: false,
|
|
332
329
|
showIfcAnnotations: true,
|
|
330
|
+
showConstructionProjection: false,
|
|
333
331
|
},
|
|
334
332
|
// Graphic overrides (keep presets, reset active and custom)
|
|
335
333
|
activePresetId: 'preset-3d-colors',
|
|
@@ -98,6 +98,13 @@ export interface Drawing2DState {
|
|
|
98
98
|
* to the IfcAnnotation text feature).
|
|
99
99
|
*/
|
|
100
100
|
showIfcAnnotations: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Construction projection (issue #979): project geometry beyond the cut
|
|
103
|
+
* as reference lines — thin solid for the visible floor side, dashed for
|
|
104
|
+
* overhead elements (beams, roofs, eaves). Plan ('down') sections only.
|
|
105
|
+
* Off by default; the section view stays cut-only until enabled.
|
|
106
|
+
*/
|
|
107
|
+
showConstructionProjection: boolean;
|
|
101
108
|
};
|
|
102
109
|
/** Available graphic override presets */
|
|
103
110
|
graphicOverridePresets: GraphicOverridePreset[];
|
|
@@ -244,6 +251,7 @@ const getDefaultDisplayOptions = (): Drawing2DState['drawing2DDisplayOptions'] =
|
|
|
244
251
|
scale: 100, // 1:100 default
|
|
245
252
|
useSymbolicRepresentations: false, // Default to section cut (Body geometry)
|
|
246
253
|
showIfcAnnotations: true, // Mirror the 3D Class Visibility default
|
|
254
|
+
showConstructionProjection: false, // Optional reference projection (issue #979), off by default
|
|
247
255
|
});
|
|
248
256
|
|
|
249
257
|
const getDefaultState = (): Drawing2DState => ({
|
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
|
|
12
12
|
import type { StateCreator } from 'zustand';
|
|
13
13
|
import type { TypeVisibility, EntityRef } from '../types.js';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getPersistedTypeVisibility,
|
|
16
|
+
TYPE_VISIBILITY_STORAGE_KEYS,
|
|
17
|
+
TYPE_VISIBILITY_SEMANTIC_DEFAULTS,
|
|
18
|
+
getPersistedTypeViewMode,
|
|
19
|
+
TYPE_VIEW_MODE_STORAGE_KEY,
|
|
20
|
+
type TypeViewMode,
|
|
21
|
+
} from '../constants.js';
|
|
15
22
|
|
|
16
23
|
export interface VisibilitySlice {
|
|
17
24
|
// State (legacy - single model)
|
|
@@ -20,6 +27,9 @@ export interface VisibilitySlice {
|
|
|
20
27
|
/** Class-level filter (from Class tab type-group clicks) — independent of isolatedEntities */
|
|
21
28
|
classFilter: { ids: Set<number>; label: string } | null;
|
|
22
29
|
typeVisibility: TypeVisibility;
|
|
30
|
+
/** 3D view mode for the Model/Types switch (#957 follow-up). 'model' shows
|
|
31
|
+
* placed occurrences (default); 'types' shows the type-library shapes. */
|
|
32
|
+
typeViewMode: TypeViewMode;
|
|
23
33
|
|
|
24
34
|
// State (multi-model)
|
|
25
35
|
/** Hidden entities per model */
|
|
@@ -46,6 +56,8 @@ export interface VisibilitySlice {
|
|
|
46
56
|
toggleTypeVisibility: (type: 'spaces' | 'openings' | 'site' | 'ifcAnnotations' | 'ifcGrid') => void;
|
|
47
57
|
/** Restore every type-visibility toggle to its semantic default (and persist). */
|
|
48
58
|
resetTypeVisibility: () => void;
|
|
59
|
+
/** Set the Model/Types 3D view mode (and persist). */
|
|
60
|
+
setTypeViewMode: (mode: TypeViewMode) => void;
|
|
49
61
|
/** Set all hidden entities at once (for BCF viewpoint application) */
|
|
50
62
|
setHiddenEntities: (ids: Set<number>) => void;
|
|
51
63
|
/** Set all isolated entities at once (for BCF viewpoint with defaultVisibility=false) */
|
|
@@ -79,6 +91,7 @@ export const createVisibilitySlice: StateCreator<VisibilitySlice, [], [], Visibi
|
|
|
79
91
|
classFilter: null,
|
|
80
92
|
// Read persisted toggles fresh so the user's choices survive reloads.
|
|
81
93
|
typeVisibility: getPersistedTypeVisibility(),
|
|
94
|
+
typeViewMode: getPersistedTypeViewMode(),
|
|
82
95
|
|
|
83
96
|
// Initial state (multi-model)
|
|
84
97
|
hiddenEntitiesByModel: new Map(),
|
|
@@ -221,6 +234,14 @@ export const createVisibilitySlice: StateCreator<VisibilitySlice, [], [], Visibi
|
|
|
221
234
|
return { typeVisibility: { ...TYPE_VISIBILITY_SEMANTIC_DEFAULTS } };
|
|
222
235
|
}),
|
|
223
236
|
|
|
237
|
+
setTypeViewMode: (mode) => set(() => {
|
|
238
|
+
if (typeof window !== 'undefined') {
|
|
239
|
+
try { localStorage.setItem(TYPE_VIEW_MODE_STORAGE_KEY, mode); }
|
|
240
|
+
catch { /* private-mode storage rejection — non-fatal */ }
|
|
241
|
+
}
|
|
242
|
+
return { typeViewMode: mode };
|
|
243
|
+
}),
|
|
244
|
+
|
|
224
245
|
// Actions (multi-model)
|
|
225
246
|
hideEntityInModel: (modelId, expressId) => set((state) => {
|
|
226
247
|
const newMap = new Map(state.hiddenEntitiesByModel);
|
package/src/store/types.ts
CHANGED
|
@@ -298,75 +298,7 @@ export type MetadataLoadState =
|
|
|
298
298
|
| 'complete'
|
|
299
299
|
| 'error';
|
|
300
300
|
|
|
301
|
-
export
|
|
302
|
-
name: string;
|
|
303
|
-
value: string | number | boolean | null;
|
|
304
|
-
type?: number;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export interface NativeMetadataPropertySet {
|
|
308
|
-
name: string;
|
|
309
|
-
globalId?: string;
|
|
310
|
-
properties: NativeMetadataProperty[];
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export interface NativeMetadataQuantity {
|
|
314
|
-
name: string;
|
|
315
|
-
value: number;
|
|
316
|
-
type?: number;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export interface NativeMetadataQuantitySet {
|
|
320
|
-
name: string;
|
|
321
|
-
quantities: NativeMetadataQuantity[];
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
export interface NativeMetadataEntitySummary {
|
|
325
|
-
expressId: number;
|
|
326
|
-
type: string;
|
|
327
|
-
name: string;
|
|
328
|
-
globalId?: string | null;
|
|
329
|
-
kind: 'spatial' | 'element';
|
|
330
|
-
hasChildren: boolean;
|
|
331
|
-
elementCount?: number;
|
|
332
|
-
elevation?: number | null;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export interface NativeMetadataSpatialNode extends NativeMetadataEntitySummary {
|
|
336
|
-
children: NativeMetadataSpatialNode[];
|
|
337
|
-
elements: NativeMetadataEntitySummary[];
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
export interface NativeMetadataSpatialInfo {
|
|
341
|
-
storeyId?: number | null;
|
|
342
|
-
storeyName?: string | null;
|
|
343
|
-
elevation?: number | null;
|
|
344
|
-
height?: number | null;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export interface NativeMetadataEntityDetails {
|
|
348
|
-
summary: NativeMetadataEntitySummary;
|
|
349
|
-
typeSummary?: NativeMetadataEntitySummary | null;
|
|
350
|
-
spatial?: NativeMetadataSpatialInfo | null;
|
|
351
|
-
properties: NativeMetadataPropertySet[];
|
|
352
|
-
quantities: NativeMetadataQuantitySet[];
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
export interface NativeMetadataSnapshot {
|
|
356
|
-
mode: 'desktop-lazy';
|
|
357
|
-
cacheKey: string;
|
|
358
|
-
filePath: string;
|
|
359
|
-
schemaVersion: SchemaVersion;
|
|
360
|
-
entityCount: number;
|
|
361
|
-
spatialTree: NativeMetadataSpatialNode | null;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export type ModelSourceFile = File | {
|
|
365
|
-
path: string;
|
|
366
|
-
name: string;
|
|
367
|
-
size: number;
|
|
368
|
-
modifiedMs?: number | null;
|
|
369
|
-
};
|
|
301
|
+
export type ModelSourceFile = File;
|
|
370
302
|
|
|
371
303
|
/** Complete model container for federation */
|
|
372
304
|
export interface FederatedModel {
|
|
@@ -406,8 +338,6 @@ export interface FederatedModel {
|
|
|
406
338
|
metadataLoadState?: MetadataLoadState;
|
|
407
339
|
/** True once the model is visibly interactive in the viewport. */
|
|
408
340
|
interactiveReady?: boolean;
|
|
409
|
-
/** Optional sparse desktop metadata snapshot for huge native loads. */
|
|
410
|
-
nativeMetadata?: NativeMetadataSnapshot | null;
|
|
411
341
|
/** Cache state for the current load session. */
|
|
412
342
|
cacheState?: 'none' | 'hit' | 'miss' | 'writing';
|
|
413
343
|
/** Optional load error for this model. */
|
package/src/utils/ifcConfig.ts
CHANGED
|
@@ -46,9 +46,6 @@ export const CACHE_SIZE_THRESHOLD = 10 * 1024 * 1024;
|
|
|
46
46
|
* and including it would make the IndexedDB write prohibitively large. */
|
|
47
47
|
export const CACHE_MAX_SOURCE_SIZE = 150 * 1024 * 1024;
|
|
48
48
|
|
|
49
|
-
/** Route desktop IFCs above this threshold through the bounded-memory path. */
|
|
50
|
-
export const HUGE_NATIVE_FILE_THRESHOLD = 128 * 1024 * 1024;
|
|
51
|
-
|
|
52
49
|
/**
|
|
53
50
|
* File size at which the browser-File-API entry path streams directly into a
|
|
54
51
|
* `SharedArrayBuffer` instead of going through `await file.arrayBuffer()`.
|
|
@@ -87,15 +84,6 @@ export const THRESHOLDS = {
|
|
|
87
84
|
CACHE_MIN_MB: 10,
|
|
88
85
|
} as const;
|
|
89
86
|
|
|
90
|
-
// ============================================================================
|
|
91
|
-
// Platform Detection
|
|
92
|
-
// ============================================================================
|
|
93
|
-
|
|
94
|
-
/** Detect if running in Tauri (desktop) environment */
|
|
95
|
-
export function isTauri(): boolean {
|
|
96
|
-
return typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
87
|
// ============================================================================
|
|
100
88
|
// Dynamic Batch Configuration
|
|
101
89
|
// ============================================================================
|
package/vite.config.ts
CHANGED
|
@@ -267,9 +267,6 @@ export default defineConfig({
|
|
|
267
267
|
'@ifc-lite/encoding': path.resolve(__dirname, '../../packages/encoding/src'),
|
|
268
268
|
'@ifc-lite/ids': path.resolve(__dirname, '../../packages/ids/src'),
|
|
269
269
|
'@ifc-lite/lists': path.resolve(__dirname, '../../packages/lists/src'),
|
|
270
|
-
'@tauri-apps/api/core': path.resolve(__dirname, './src/services/tauri-core-stub.ts'),
|
|
271
|
-
'@tauri-apps/plugin-dialog': path.resolve(__dirname, './src/services/tauri-dialog-stub.ts'),
|
|
272
|
-
'@tauri-apps/plugin-fs': path.resolve(__dirname, './src/services/tauri-fs-stub.ts'),
|
|
273
270
|
},
|
|
274
271
|
},
|
|
275
272
|
server: {
|
|
@@ -301,6 +298,12 @@ export default defineConfig({
|
|
|
301
298
|
target: 'esnext',
|
|
302
299
|
chunkSizeWarningLimit: 6000,
|
|
303
300
|
rollupOptions: {
|
|
301
|
+
// @ifc-lite/geometry's NativeBridge does a dynamic `import('@tauri-apps/api/event')`
|
|
302
|
+
// (under isTauri(), never reached on web). Rollup still resolves it
|
|
303
|
+
// statically, so externalize it to prevent a build failure. ifc-lite no
|
|
304
|
+
// longer ships a desktop app; downstream desktop builders supply
|
|
305
|
+
// @tauri-apps in their own host layer.
|
|
306
|
+
external: ['@tauri-apps/api/event'],
|
|
304
307
|
output: {
|
|
305
308
|
manualChunks(id) {
|
|
306
309
|
if (id.includes('/packages/sandbox/')) return 'sandbox';
|
package/DESKTOP_CONTRACT_VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2
|