@ifc-lite/viewer 1.8.0 → 1.9.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 (67) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/assets/{Arrow.dom-CwcRxist.js → Arrow.dom-CusgkT03.js} +1 -1
  3. package/dist/assets/browser-BXNIkE8a.js +694 -0
  4. package/dist/assets/emscripten-module-BTRCZGcB.wasm +0 -0
  5. package/dist/assets/emscripten-module-CGIn_cMh.wasm +0 -0
  6. package/dist/assets/emscripten-module-DYvzWiHh.wasm +0 -0
  7. package/dist/assets/emscripten-module-NWak2PoB.wasm +0 -0
  8. package/dist/assets/emscripten-module.browser-CY5t0Vfq.js +1 -0
  9. package/dist/assets/esbuild-COv63sf-.js +1 -0
  10. package/dist/assets/esbuild-Cpd5nU_H.wasm +0 -0
  11. package/dist/assets/ffi-DlhRHxHv.js +1 -0
  12. package/dist/assets/index-6Mr3byM-.js +216 -0
  13. package/dist/assets/index-CGbokkQ9.css +1 -0
  14. package/dist/assets/{index-BSANf7-H.js → index-huvR-kGC.js} +50271 -30761
  15. package/dist/assets/module-6F3E5H7Y-tx0BadV3.js +6 -0
  16. package/dist/assets/{native-bridge-5LbrYh3R.js → native-bridge-DsHOKdgD.js} +1 -1
  17. package/dist/assets/{wasm-bridge-CgpLtj1h.js → wasm-bridge-Bd73HXn-.js} +1 -1
  18. package/dist/index.html +12 -3
  19. package/index.html +10 -1
  20. package/package.json +30 -21
  21. package/src/App.tsx +6 -1
  22. package/src/components/ui/dialog.tsx +8 -6
  23. package/src/components/viewer/CodeEditor.tsx +309 -0
  24. package/src/components/viewer/CommandPalette.tsx +597 -0
  25. package/src/components/viewer/MainToolbar.tsx +31 -3
  26. package/src/components/viewer/ScriptPanel.tsx +416 -0
  27. package/src/components/viewer/ViewerLayout.tsx +63 -11
  28. package/src/components/viewer/Viewport.tsx +1 -0
  29. package/src/components/viewer/useGeometryStreaming.ts +13 -1
  30. package/src/hooks/useKeyboardShortcuts.ts +1 -0
  31. package/src/hooks/useLens.ts +2 -1
  32. package/src/hooks/useSandbox.ts +113 -0
  33. package/src/lib/recent-files.ts +122 -0
  34. package/src/lib/scripts/persistence.ts +132 -0
  35. package/src/lib/scripts/templates/bim-globals.d.ts +111 -0
  36. package/src/lib/scripts/templates/data-quality-audit.ts +149 -0
  37. package/src/lib/scripts/templates/envelope-check.ts +164 -0
  38. package/src/lib/scripts/templates/federation-compare.ts +189 -0
  39. package/src/lib/scripts/templates/fire-safety-check.ts +161 -0
  40. package/src/lib/scripts/templates/mep-equipment-schedule.ts +175 -0
  41. package/src/lib/scripts/templates/quantity-takeoff.ts +145 -0
  42. package/src/lib/scripts/templates/reset-view.ts +6 -0
  43. package/src/lib/scripts/templates/space-validation.ts +189 -0
  44. package/src/lib/scripts/templates/tsconfig.json +13 -0
  45. package/src/lib/scripts/templates.ts +86 -0
  46. package/src/sdk/BimProvider.tsx +50 -0
  47. package/src/sdk/adapters/export-adapter.ts +283 -0
  48. package/src/sdk/adapters/lens-adapter.ts +44 -0
  49. package/src/sdk/adapters/model-adapter.ts +32 -0
  50. package/src/sdk/adapters/model-compat.ts +80 -0
  51. package/src/sdk/adapters/mutate-adapter.ts +45 -0
  52. package/src/sdk/adapters/query-adapter.ts +241 -0
  53. package/src/sdk/adapters/selection-adapter.ts +29 -0
  54. package/src/sdk/adapters/spatial-adapter.ts +37 -0
  55. package/src/sdk/adapters/types.ts +11 -0
  56. package/src/sdk/adapters/viewer-adapter.ts +103 -0
  57. package/src/sdk/adapters/visibility-adapter.ts +61 -0
  58. package/src/sdk/local-backend.ts +144 -0
  59. package/src/sdk/useBimHost.ts +69 -0
  60. package/src/store/constants.ts +10 -2
  61. package/src/store/index.ts +14 -1
  62. package/src/store/slices/pinboardSlice.ts +37 -41
  63. package/src/store/slices/scriptSlice.ts +218 -0
  64. package/src/store/slices/uiSlice.ts +2 -0
  65. package/tsconfig.json +5 -2
  66. package/vite.config.ts +8 -0
  67. package/dist/assets/index-7WoQ-qVC.css +0 -1
@@ -783,6 +783,7 @@ export function Viewport({ geometry, coordinateInfo, computedIsolatedIds, modelI
783
783
  geometryBoundsRef,
784
784
  pendingColorUpdates,
785
785
  clearPendingColorUpdates,
786
+ clearColorRef,
786
787
  });
787
788
 
788
789
  useRenderUpdates({
@@ -20,6 +20,8 @@ export interface UseGeometryStreamingParams {
20
20
  geometryBoundsRef: MutableRefObject<{ min: { x: number; y: number; z: number }; max: { x: number; y: number; z: number } }>;
21
21
  pendingColorUpdates: Map<number, [number, number, number, number]> | null;
22
22
  clearPendingColorUpdates: () => void;
23
+ // Clear color ref — color update renders must preserve theme background
24
+ clearColorRef: MutableRefObject<[number, number, number, number]>;
23
25
  }
24
26
 
25
27
  export function useGeometryStreaming(params: UseGeometryStreamingParams): void {
@@ -32,6 +34,7 @@ export function useGeometryStreaming(params: UseGeometryStreamingParams): void {
32
34
  geometryBoundsRef,
33
35
  pendingColorUpdates,
34
36
  clearPendingColorUpdates,
37
+ clearColorRef,
35
38
  } = params;
36
39
 
37
40
  // Track processed meshes for incremental updates
@@ -397,7 +400,16 @@ export function useGeometryStreaming(params: UseGeometryStreamingParams): void {
397
400
  // Non-empty map = set color overrides
398
401
  scene.setColorOverrides(pendingColorUpdates, device, pipeline);
399
402
  }
400
- renderer.render();
403
+ // Re-render with current theme background — render() without options
404
+ // defaults to black background. Do NOT pass hiddenIds/isolatedIds here:
405
+ // visibility filtering causes partial batches which write depth only for
406
+ // visible elements, but overlay batches cover all geometry. Without
407
+ // filtering, all original batches write depth for every entity, ensuring
408
+ // depthCompare 'equal' matches exactly for the overlay pass.
409
+ // The next render from useRenderUpdates will apply the correct visibility.
410
+ renderer.render({
411
+ clearColor: clearColorRef.current,
412
+ });
401
413
  clearPendingColorUpdates();
402
414
  }
403
415
  }, [pendingColorUpdates, isInitialized, clearPendingColorUpdates]);
@@ -279,5 +279,6 @@ export const KEYBOARD_SHORTCUTS = [
279
279
  { key: '1-6', description: 'Preset views', category: 'Camera' },
280
280
  { key: 'T', description: 'Toggle theme', category: 'UI' },
281
281
  { key: 'Esc', description: 'Reset all (clear selection, basket, isolation)', category: 'Selection' },
282
+ { key: 'Ctrl+K', description: 'Command palette', category: 'UI' },
282
283
  { key: '?', description: 'Show info panel', category: 'Help' },
283
284
  ] as const;
@@ -24,6 +24,7 @@
24
24
 
25
25
  import { useEffect, useRef, useMemo } from 'react';
26
26
  import { evaluateLens, evaluateAutoColorLens, rgbaToHex, isGhostColor } from '@ifc-lite/lens';
27
+ import type { AutoColorEvaluationResult } from '@ifc-lite/lens';
27
28
  import { useViewerStore } from '@/store';
28
29
  import { createLensDataProvider } from '@/lib/lens';
29
30
  import { useLensDiscovery } from './useLensDiscovery';
@@ -95,7 +96,7 @@ export function useLens() {
95
96
 
96
97
  // Store auto-color legend entries for UI display
97
98
  if (isAutoColor && 'legend' in result) {
98
- useViewerStore.getState().setLensAutoColorLegend(result.legend);
99
+ useViewerStore.getState().setLensAutoColorLegend((result as AutoColorEvaluationResult).legend);
99
100
  } else {
100
101
  useViewerStore.getState().setLensAutoColorLegend([]);
101
102
  }
@@ -0,0 +1,113 @@
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
+ * useSandbox — React hook for executing scripts in a QuickJS sandbox.
7
+ *
8
+ * Creates a fresh sandbox context per execution for full isolation.
9
+ * The WASM module is cached across the session (cheap to reuse),
10
+ * but each script runs in a clean context with no leaked state.
11
+ */
12
+
13
+ import { useCallback, useEffect, useRef } from 'react';
14
+ import { useBim } from '../sdk/BimProvider.js';
15
+ import { useViewerStore } from '../store/index.js';
16
+ import type { Sandbox, ScriptResult, SandboxConfig } from '@ifc-lite/sandbox';
17
+
18
+ /** Type guard for ScriptError shape (has logs + durationMs) */
19
+ function isScriptError(err: unknown): err is { message: string; logs: Array<{ level: string; args: unknown[]; timestamp: number }>; durationMs: number } {
20
+ return (
21
+ err !== null &&
22
+ typeof err === 'object' &&
23
+ 'logs' in err &&
24
+ Array.isArray((err as Record<string, unknown>).logs) &&
25
+ 'durationMs' in err &&
26
+ typeof (err as Record<string, unknown>).durationMs === 'number'
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Hook that provides a sandbox execution interface.
32
+ *
33
+ * Each execute() call creates a fresh QuickJS context for full isolation —
34
+ * scripts cannot leak global state between runs. The WASM module itself
35
+ * is cached (loaded once per app lifetime, ~1ms context creation overhead).
36
+ */
37
+ export function useSandbox(config?: SandboxConfig) {
38
+ const bim = useBim();
39
+ const activeSandboxRef = useRef<Sandbox | null>(null);
40
+
41
+ const setExecutionState = useViewerStore((s) => s.setScriptExecutionState);
42
+ const setResult = useViewerStore((s) => s.setScriptResult);
43
+ const setError = useViewerStore((s) => s.setScriptError);
44
+
45
+ /** Execute a script in an isolated sandbox context */
46
+ const execute = useCallback(async (code: string): Promise<ScriptResult | null> => {
47
+ setExecutionState('running');
48
+ setError(null);
49
+
50
+ let sandbox: Sandbox | null = null;
51
+ try {
52
+ // Create a fresh sandbox for every execution — full isolation
53
+ const { createSandbox } = await import('@ifc-lite/sandbox');
54
+ sandbox = await createSandbox(bim, {
55
+ permissions: { model: true, query: true, viewer: true, mutate: true, lens: true, export: true, ...config?.permissions },
56
+ limits: { timeoutMs: 30_000, ...config?.limits },
57
+ });
58
+ activeSandboxRef.current = sandbox;
59
+
60
+ const result = await sandbox.eval(code);
61
+ setResult({
62
+ value: result.value,
63
+ logs: result.logs,
64
+ durationMs: result.durationMs,
65
+ });
66
+ return result;
67
+ } catch (err: unknown) {
68
+ const message = err instanceof Error ? err.message : String(err);
69
+ setError(message);
70
+
71
+ // If the error is a ScriptError with captured logs, preserve them
72
+ if (isScriptError(err)) {
73
+ setResult({
74
+ value: undefined,
75
+ logs: err.logs as ScriptResult['logs'],
76
+ durationMs: err.durationMs,
77
+ });
78
+ }
79
+ return null;
80
+ } finally {
81
+ // Always dispose the sandbox after execution
82
+ if (sandbox) {
83
+ sandbox.dispose();
84
+ }
85
+ if (activeSandboxRef.current === sandbox) {
86
+ activeSandboxRef.current = null;
87
+ }
88
+ }
89
+ }, [bim, config?.permissions, config?.limits, setExecutionState, setResult, setError]);
90
+
91
+ /** Reset clears any active sandbox (no-op if none running) */
92
+ const reset = useCallback(() => {
93
+ if (activeSandboxRef.current) {
94
+ activeSandboxRef.current.dispose();
95
+ activeSandboxRef.current = null;
96
+ }
97
+ setExecutionState('idle');
98
+ setResult(null);
99
+ setError(null);
100
+ }, [setExecutionState, setResult, setError]);
101
+
102
+ // Cleanup on unmount
103
+ useEffect(() => {
104
+ return () => {
105
+ if (activeSandboxRef.current) {
106
+ activeSandboxRef.current.dispose();
107
+ activeSandboxRef.current = null;
108
+ }
109
+ };
110
+ }, []);
111
+
112
+ return { execute, reset };
113
+ }
@@ -0,0 +1,122 @@
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
+ * Track recently opened model files via localStorage + IndexedDB.
7
+ *
8
+ * localStorage: metadata (name, size, timestamp) — for palette display.
9
+ * IndexedDB: actual file blobs — so recent files can be loaded instantly
10
+ * without the user re-selecting them from the file picker.
11
+ *
12
+ * Shared between MainToolbar (writes) and CommandPalette (reads).
13
+ */
14
+
15
+ const KEY = 'ifc-lite:recent-files';
16
+ const DB_NAME = 'ifc-lite-file-cache';
17
+ const DB_VERSION = 1;
18
+ const STORE_NAME = 'files';
19
+ const MAX_CACHED_FILES = 5;
20
+ /** Max file size to cache (150 MB) — avoids filling IndexedDB quota */
21
+ const MAX_CACHE_SIZE = 150 * 1024 * 1024;
22
+
23
+ export interface RecentFileEntry {
24
+ name: string;
25
+ size: number;
26
+ timestamp: number;
27
+ }
28
+
29
+ // ── localStorage (metadata) ─────────────────────────────────────────────
30
+
31
+ export function getRecentFiles(): RecentFileEntry[] {
32
+ try { return JSON.parse(localStorage.getItem(KEY) ?? '[]'); }
33
+ catch { return []; }
34
+ }
35
+
36
+ export function recordRecentFiles(files: { name: string; size: number }[]) {
37
+ try {
38
+ const names = new Set(files.map(f => f.name));
39
+ const existing = getRecentFiles().filter(f => !names.has(f.name));
40
+ const entries: RecentFileEntry[] = files.map(f => ({
41
+ name: f.name,
42
+ size: f.size,
43
+ timestamp: Date.now(),
44
+ }));
45
+ localStorage.setItem(KEY, JSON.stringify([...entries, ...existing].slice(0, 10)));
46
+ } catch { /* noop */ }
47
+ }
48
+
49
+ /** Format bytes into human-readable size */
50
+ export function formatFileSize(bytes: number): string {
51
+ if (bytes < 1024) return `${bytes} B`;
52
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
53
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
54
+ }
55
+
56
+ // ── IndexedDB (file blob cache) ─────────────────────────────────────────
57
+
58
+ function openDB(): Promise<IDBDatabase> {
59
+ return new Promise((resolve, reject) => {
60
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
61
+ req.onupgradeneeded = () => {
62
+ const db = req.result;
63
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
64
+ db.createObjectStore(STORE_NAME, { keyPath: 'name' });
65
+ }
66
+ };
67
+ req.onsuccess = () => resolve(req.result);
68
+ req.onerror = () => reject(req.error);
69
+ });
70
+ }
71
+
72
+ /** Cache file blobs in IndexedDB for instant reload from palette. */
73
+ export async function cacheFileBlobs(files: File[]): Promise<void> {
74
+ try {
75
+ const db = await openDB();
76
+ const tx = db.transaction(STORE_NAME, 'readwrite');
77
+ const store = tx.objectStore(STORE_NAME);
78
+
79
+ for (const file of files) {
80
+ if (file.size > MAX_CACHE_SIZE) continue; // skip oversized files
81
+ const blob = await file.arrayBuffer();
82
+ store.put({ name: file.name, blob, size: file.size, type: file.type, timestamp: Date.now() });
83
+ }
84
+
85
+ // Evict old entries beyond MAX_CACHED_FILES
86
+ const allReq = store.getAll();
87
+ allReq.onsuccess = () => {
88
+ const all = allReq.result as { name: string; timestamp: number }[];
89
+ if (all.length > MAX_CACHED_FILES) {
90
+ all.sort((a, b) => b.timestamp - a.timestamp);
91
+ for (let i = MAX_CACHED_FILES; i < all.length; i++) {
92
+ store.delete(all[i].name);
93
+ }
94
+ }
95
+ };
96
+
97
+ await new Promise<void>((resolve, reject) => {
98
+ tx.oncomplete = () => resolve();
99
+ tx.onerror = () => reject(tx.error);
100
+ });
101
+ db.close();
102
+ } catch { /* IndexedDB unavailable — degrade gracefully */ }
103
+ }
104
+
105
+ /** Retrieve a cached file blob and reconstruct a File object. */
106
+ export async function getCachedFile(name: string): Promise<File | null> {
107
+ try {
108
+ const db = await openDB();
109
+ const tx = db.transaction(STORE_NAME, 'readonly');
110
+ const store = tx.objectStore(STORE_NAME);
111
+ const req = store.get(name);
112
+ const result = await new Promise<{ name: string; blob: ArrayBuffer; size: number; type: string } | undefined>((resolve, reject) => {
113
+ req.onsuccess = () => resolve(req.result);
114
+ req.onerror = () => reject(req.error);
115
+ });
116
+ db.close();
117
+ if (!result) return null;
118
+ return new File([result.blob], result.name, { type: result.type || 'application/octet-stream' });
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
@@ -0,0 +1,132 @@
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
+ * Persistence for user scripts via localStorage.
7
+ *
8
+ * Uses a versioned schema so future additions (tags, description, etc.)
9
+ * can be migrated without data loss.
10
+ */
11
+
12
+ /** Current schema version */
13
+ const SCHEMA_VERSION = 1;
14
+
15
+ export interface SavedScript {
16
+ id: string;
17
+ name: string;
18
+ code: string;
19
+ createdAt: number;
20
+ updatedAt: number;
21
+ version: number;
22
+ }
23
+
24
+ /** Stored wrapper with schema version for migration */
25
+ interface StoredScripts {
26
+ schemaVersion: number;
27
+ scripts: SavedScript[];
28
+ }
29
+
30
+ const STORAGE_KEY = 'ifc-lite-scripts';
31
+
32
+ /** Maximum scripts allowed (prevents storage exhaustion) */
33
+ const MAX_SCRIPTS = 500;
34
+
35
+ /** Maximum code size per script in characters (~100KB) */
36
+ const MAX_SCRIPT_SIZE = 100_000;
37
+
38
+ export function loadSavedScripts(): SavedScript[] {
39
+ try {
40
+ const raw = localStorage.getItem(STORAGE_KEY);
41
+ if (!raw) return [];
42
+
43
+ const parsed: unknown = JSON.parse(raw);
44
+
45
+ // Handle legacy format (bare array without schema version)
46
+ if (Array.isArray(parsed)) {
47
+ return migrateFromLegacy(parsed);
48
+ }
49
+
50
+ // Versioned format — validate structure
51
+ if (
52
+ typeof parsed === 'object' &&
53
+ parsed !== null &&
54
+ 'schemaVersion' in parsed &&
55
+ 'scripts' in parsed &&
56
+ Array.isArray((parsed as StoredScripts).scripts)
57
+ ) {
58
+ return (parsed as StoredScripts).scripts;
59
+ }
60
+
61
+ return [];
62
+ } catch {
63
+ return [];
64
+ }
65
+ }
66
+
67
+ /** Migrate from the original unversioned format — discards corrupted entries */
68
+ function migrateFromLegacy(scripts: unknown[]): SavedScript[] {
69
+ const migrated: SavedScript[] = [];
70
+ for (const s of scripts) {
71
+ if (s === null || typeof s !== 'object') continue;
72
+ const script = s as Record<string, unknown>;
73
+
74
+ // Validate essential fields — discard garbage values from String()/Number() coercion
75
+ const id = typeof script.id === 'string' && script.id.length > 0 ? script.id : crypto.randomUUID();
76
+ const name = typeof script.name === 'string' && script.name.length > 0 ? script.name : 'Untitled';
77
+ const code = typeof script.code === 'string' ? script.code : '';
78
+ const createdAt = typeof script.createdAt === 'number' && isFinite(script.createdAt) ? script.createdAt : Date.now();
79
+ const updatedAt = typeof script.updatedAt === 'number' && isFinite(script.updatedAt) ? script.updatedAt : Date.now();
80
+
81
+ migrated.push({ id, name, code, createdAt, updatedAt, version: SCHEMA_VERSION });
82
+ }
83
+ // Save in new format
84
+ saveScripts(migrated);
85
+ return migrated;
86
+ }
87
+
88
+ export type SaveResult =
89
+ | { ok: true }
90
+ | { ok: false; reason: 'quota_exceeded' | 'serialization_error' | 'unknown'; message: string };
91
+
92
+ export function saveScripts(scripts: SavedScript[]): SaveResult {
93
+ const stored: StoredScripts = {
94
+ schemaVersion: SCHEMA_VERSION,
95
+ scripts,
96
+ };
97
+
98
+ try {
99
+ const json = JSON.stringify(stored);
100
+ localStorage.setItem(STORAGE_KEY, json);
101
+ return { ok: true };
102
+ } catch (err: unknown) {
103
+ if (err instanceof DOMException && err.name === 'QuotaExceededError') {
104
+ console.warn('[Scripts] localStorage quota exceeded. Consider deleting unused scripts.');
105
+ return { ok: false, reason: 'quota_exceeded', message: 'Storage quota exceeded. Delete unused scripts to free space.' };
106
+ }
107
+ if (err instanceof TypeError) {
108
+ console.warn('[Scripts] Failed to serialize scripts:', err.message);
109
+ return { ok: false, reason: 'serialization_error', message: err.message };
110
+ }
111
+ console.warn('[Scripts] Failed to save scripts to localStorage');
112
+ return { ok: false, reason: 'unknown', message: String(err) };
113
+ }
114
+ }
115
+
116
+ /** Validate a script name — returns sanitized name or null if invalid */
117
+ export function validateScriptName(name: string): string | null {
118
+ const trimmed = name.trim();
119
+ if (trimmed.length === 0) return null;
120
+ if (trimmed.length > 100) return trimmed.slice(0, 100);
121
+ return trimmed;
122
+ }
123
+
124
+ /** Check if adding another script is within limits */
125
+ export function canCreateScript(currentCount: number): boolean {
126
+ return currentCount < MAX_SCRIPTS;
127
+ }
128
+
129
+ /** Check if script code is within size limits */
130
+ export function isScriptWithinSizeLimit(code: string): boolean {
131
+ return code.length <= MAX_SCRIPT_SIZE;
132
+ }
@@ -0,0 +1,111 @@
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
+ * AUTO-GENERATED — do not edit by hand.
7
+ * Run: npx tsx scripts/generate-bim-globals.ts
8
+ *
9
+ * Type declarations for the sandbox `bim` global.
10
+ * Generated from NAMESPACE_SCHEMAS in bridge-schema.ts.
11
+ */
12
+
13
+ // ── Entity types ────────────────────────────────────────────────────────
14
+
15
+ interface BimEntity {
16
+ ref: { modelId: string; expressId: number };
17
+ name: string; Name: string;
18
+ type: string; Type: string;
19
+ globalId: string; GlobalId: string;
20
+ description: string; Description: string;
21
+ objectType: string; ObjectType: string;
22
+ }
23
+
24
+ interface BimPropertySet {
25
+ name: string;
26
+ properties: Array<{ name: string; value: string | number | boolean | null }>;
27
+ }
28
+
29
+ interface BimQuantitySet {
30
+ name: string;
31
+ quantities: Array<{ name: string; value: number | null }>;
32
+ }
33
+
34
+ interface BimModelInfo {
35
+ id: string;
36
+ name: string;
37
+ schemaVersion: string;
38
+ entityCount: number;
39
+ fileSize: number;
40
+ }
41
+
42
+ // ── Namespace declarations ──────────────────────────────────────────────
43
+
44
+ declare const bim: {
45
+ /** Model operations */
46
+ model: {
47
+ /** List loaded models */
48
+ list(): BimModelInfo[];
49
+ /** Get active model */
50
+ active(): BimModelInfo | null;
51
+ /** Get active model ID */
52
+ activeId(): string | null;
53
+ };
54
+ /** Query entities */
55
+ query: {
56
+ /** Get all entities */
57
+ all(): BimEntity[];
58
+ /** Filter by IFC type e.g. 'IfcWall' */
59
+ byType(...types: string[]): BimEntity[];
60
+ /** Get entity by model ID and express ID */
61
+ entity(modelId: string, expressId: number): BimEntity | null;
62
+ /** Get all IfcPropertySet data for an entity */
63
+ properties(entity: BimEntity): BimPropertySet[];
64
+ /** Get all IfcElementQuantity data for an entity */
65
+ quantities(entity: BimEntity): BimQuantitySet[];
66
+ };
67
+ /** Viewer control */
68
+ viewer: {
69
+ /** Colorize entities e.g. '#ff0000' */
70
+ colorize(entities: BimEntity[], color: string): void;
71
+ /** Batch colorize with [{entities, color}] */
72
+ colorizeAll(batches: Array<{ entities: BimEntity[]; color: string }>): void;
73
+ /** Hide entities */
74
+ hide(entities: BimEntity[]): void;
75
+ /** Show entities */
76
+ show(entities: BimEntity[]): void;
77
+ /** Isolate entities */
78
+ isolate(entities: BimEntity[]): void;
79
+ /** Select entities */
80
+ select(entities: BimEntity[]): void;
81
+ /** Fly camera to entities */
82
+ flyTo(entities: BimEntity[]): void;
83
+ /** Reset all colors */
84
+ resetColors(): void;
85
+ /** Reset all visibility */
86
+ resetVisibility(): void;
87
+ };
88
+ /** Property editing */
89
+ mutate: {
90
+ /** Set a property value */
91
+ setProperty(entity: unknown, psetName: string, propName: string, value: unknown): void;
92
+ /** Delete a property */
93
+ deleteProperty(entity: unknown, psetName: string, propName: string): void;
94
+ /** Undo last mutation */
95
+ undo(modelId: string): void;
96
+ /** Redo undone mutation */
97
+ redo(modelId: string): void;
98
+ };
99
+ /** Lens visualization */
100
+ lens: {
101
+ /** Get built-in lens presets */
102
+ presets(): unknown[];
103
+ };
104
+ /** Data export */
105
+ export: {
106
+ /** Export entities to CSV string */
107
+ csv(entities: BimEntity[], options: { columns: string[]; filename?: string; separator?: string }): string;
108
+ /** Export entities to JSON array */
109
+ json(entities: BimEntity[], columns: string[]): Record<string, unknown>[];
110
+ };
111
+ };