@ifc-lite/viewer 1.14.2 → 1.14.4
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 +35 -0
- package/dist/assets/{Arrow.dom-CSgnLhN4.js → Arrow.dom-_vGzMMKs.js} +1 -1
- package/dist/assets/basketViewActivator-BZcoCL3V.js +1 -0
- package/dist/assets/{browser-qSKWrKQW.js → browser-Czmf34bo.js} +1 -1
- package/dist/assets/ifc-lite_bg-DyBKoGgk.wasm +0 -0
- package/dist/assets/index-CMQ_Dgkr.css +1 -0
- package/dist/assets/index-D7nEDctQ.js +229 -0
- package/dist/assets/{index-4Y4XaV8N.js → index-DX-Qf5fA.js} +72669 -61673
- package/dist/assets/{native-bridge-CSFDsEkg.js → native-bridge-DAOWftxE.js} +1 -1
- package/dist/assets/{wasm-bridge-Zf90ysEm.js → wasm-bridge-D7jYpn8a.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +21 -20
- package/src/App.tsx +17 -1
- package/src/components/viewer/BasketPresentationDock.tsx +8 -4
- package/src/components/viewer/ChatPanel.tsx +1402 -0
- package/src/components/viewer/CodeEditor.tsx +70 -4
- package/src/components/viewer/CommandPalette.tsx +1 -0
- package/src/components/viewer/HierarchyPanel.tsx +28 -13
- package/src/components/viewer/MainToolbar.tsx +113 -95
- package/src/components/viewer/ScriptPanel.tsx +351 -184
- package/src/components/viewer/UpgradePage.tsx +69 -0
- package/src/components/viewer/Viewport.tsx +23 -0
- package/src/components/viewer/chat/ChatMessage.tsx +144 -0
- package/src/components/viewer/chat/ExecutableCodeBlock.tsx +416 -0
- package/src/components/viewer/chat/ModelSelector.tsx +102 -0
- package/src/components/viewer/chat/renderTextContent.test.ts +23 -0
- package/src/components/viewer/chat/renderTextContent.ts +19 -0
- package/src/components/viewer/hierarchy/HierarchyNode.tsx +10 -3
- package/src/components/viewer/hierarchy/treeDataBuilder.test.ts +126 -0
- package/src/components/viewer/hierarchy/treeDataBuilder.ts +139 -38
- package/src/components/viewer/hierarchy/types.ts +6 -1
- package/src/components/viewer/hierarchy/useHierarchyTree.ts +27 -12
- package/src/hooks/useIfcCache.ts +1 -2
- package/src/hooks/useSandbox.ts +122 -6
- package/src/index.css +10 -0
- package/src/lib/attachments.ts +46 -0
- package/src/lib/llm/ClerkChatSync.tsx +74 -0
- package/src/lib/llm/clerk-auth.ts +62 -0
- package/src/lib/llm/code-extractor.ts +50 -0
- package/src/lib/llm/context-builder.test.ts +18 -0
- package/src/lib/llm/context-builder.ts +305 -0
- package/src/lib/llm/free-models.test.ts +118 -0
- package/src/lib/llm/message-capabilities.test.ts +131 -0
- package/src/lib/llm/message-capabilities.ts +94 -0
- package/src/lib/llm/models.ts +197 -0
- package/src/lib/llm/repair-loop.test.ts +91 -0
- package/src/lib/llm/repair-loop.ts +76 -0
- package/src/lib/llm/script-diagnostics.ts +445 -0
- package/src/lib/llm/script-edit-ops.test.ts +399 -0
- package/src/lib/llm/script-edit-ops.ts +954 -0
- package/src/lib/llm/script-preflight.test.ts +513 -0
- package/src/lib/llm/script-preflight.ts +990 -0
- package/src/lib/llm/script-preservation.test.ts +128 -0
- package/src/lib/llm/script-preservation.ts +152 -0
- package/src/lib/llm/stream-client.test.ts +97 -0
- package/src/lib/llm/stream-client.ts +410 -0
- package/src/lib/llm/system-prompt.test.ts +181 -0
- package/src/lib/llm/system-prompt.ts +665 -0
- package/src/lib/llm/types.ts +150 -0
- package/src/lib/scripts/templates/bim-globals.d.ts +226 -7
- package/src/lib/scripts/templates/create-building.ts +12 -12
- package/src/main.tsx +10 -1
- package/src/sdk/adapters/export-adapter.test.ts +24 -0
- package/src/sdk/adapters/export-adapter.ts +40 -16
- package/src/sdk/adapters/files-adapter.ts +39 -0
- package/src/sdk/adapters/model-compat.ts +1 -1
- package/src/sdk/adapters/mutate-adapter.ts +20 -6
- package/src/sdk/adapters/mutation-view.ts +112 -0
- package/src/sdk/adapters/query-adapter.ts +100 -4
- package/src/sdk/local-backend.ts +4 -0
- package/src/store/index.ts +15 -1
- package/src/store/slices/chatSlice.test.ts +325 -0
- package/src/store/slices/chatSlice.ts +468 -0
- package/src/store/slices/scriptSlice.test.ts +75 -0
- package/src/store/slices/scriptSlice.ts +256 -9
- package/src/vite-env.d.ts +10 -0
- package/vite.config.ts +21 -2
- package/dist/assets/ifc-lite_bg-BOvNXJA_.wasm +0 -0
- package/dist/assets/index-ByrFvN5A.css +0 -1
- package/dist/assets/index-CN7qDq7G.js +0 -216
|
@@ -6,7 +6,8 @@ import type { StoreApi } from './types.js';
|
|
|
6
6
|
import type { EntityRef, EntityData, PropertySetData, QuantitySetData, ExportBackendMethods } from '@ifc-lite/sdk';
|
|
7
7
|
import { EntityNode } from '@ifc-lite/query';
|
|
8
8
|
import { StepExporter, type StepExportOptions } from '@ifc-lite/export';
|
|
9
|
-
import { getModelForRef } from './model-compat.js';
|
|
9
|
+
import { getModelForRef, LEGACY_MODEL_ID } from './model-compat.js';
|
|
10
|
+
import { applyAttributeMutationsToEntityData, getMutationViewForModel } from './mutation-view.js';
|
|
10
11
|
|
|
11
12
|
/** Options for CSV export */
|
|
12
13
|
interface CsvOptions {
|
|
@@ -78,6 +79,28 @@ function normalizeRefs(raw: unknown[]): EntityRef[] {
|
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
export function resolveVisibilityFilterSets(
|
|
83
|
+
state: StoreApi['getState'] extends () => infer T ? T : never,
|
|
84
|
+
modelId: string,
|
|
85
|
+
selectedExpressIds: Set<number>,
|
|
86
|
+
entityCount: number,
|
|
87
|
+
): { visibleOnly: boolean; hiddenEntityIds: Set<number>; isolatedEntityIds: Set<number> | null } {
|
|
88
|
+
const shouldLimitToSelection = selectedExpressIds.size < entityCount;
|
|
89
|
+
const isLegacyModel = state.models.size === 0 && (modelId === LEGACY_MODEL_ID || modelId === 'legacy');
|
|
90
|
+
const modelHidden = state.hiddenEntitiesByModel.get(modelId) ?? (isLegacyModel ? state.hiddenEntities : undefined);
|
|
91
|
+
const modelIsolated = state.isolatedEntitiesByModel.get(modelId) ?? (isLegacyModel ? state.isolatedEntities : null);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
visibleOnly: shouldLimitToSelection,
|
|
95
|
+
hiddenEntityIds: shouldLimitToSelection
|
|
96
|
+
? new Set<number>()
|
|
97
|
+
: new Set<number>(modelHidden ?? []),
|
|
98
|
+
isolatedEntityIds: shouldLimitToSelection
|
|
99
|
+
? selectedExpressIds
|
|
100
|
+
: modelIsolated,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
81
104
|
/**
|
|
82
105
|
* Escape a CSV cell value — wrap in quotes if it contains the separator,
|
|
83
106
|
* double-quotes, or newlines.
|
|
@@ -104,14 +127,14 @@ export function createExportAdapter(store: StoreApi): ExportBackendMethods {
|
|
|
104
127
|
if (!model?.ifcDataStore) return null;
|
|
105
128
|
|
|
106
129
|
const node = new EntityNode(model.ifcDataStore, ref.expressId);
|
|
107
|
-
return {
|
|
130
|
+
return applyAttributeMutationsToEntityData(store, ref.modelId, ref.expressId, {
|
|
108
131
|
ref,
|
|
109
132
|
globalId: node.globalId,
|
|
110
133
|
name: node.name,
|
|
111
134
|
type: node.type,
|
|
112
135
|
description: node.description,
|
|
113
136
|
objectType: node.objectType,
|
|
114
|
-
};
|
|
137
|
+
});
|
|
115
138
|
}
|
|
116
139
|
|
|
117
140
|
/** Resolve property sets for an entity */
|
|
@@ -317,19 +340,20 @@ export function createExportAdapter(store: StoreApi): ExportBackendMethods {
|
|
|
317
340
|
|
|
318
341
|
const options = candidateOptions;
|
|
319
342
|
const selectedExpressIds = new Set(refs.map(ref => ref.expressId));
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const isolatedEntityIds =
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
343
|
+
const visibilityFilters = resolveVisibilityFilterSets(
|
|
344
|
+
state,
|
|
345
|
+
modelId,
|
|
346
|
+
selectedExpressIds,
|
|
347
|
+
model.ifcDataStore.entityCount,
|
|
348
|
+
);
|
|
349
|
+
const visibleOnly = options.visibleOnly === true || visibilityFilters.visibleOnly;
|
|
350
|
+
const hiddenEntityIds = visibleOnly ? visibilityFilters.hiddenEntityIds : new Set<number>();
|
|
351
|
+
const isolatedEntityIds = visibleOnly ? visibilityFilters.isolatedEntityIds : null;
|
|
352
|
+
|
|
353
|
+
const exporter = new StepExporter(
|
|
354
|
+
model.ifcDataStore,
|
|
355
|
+
options.includeMutations === false ? undefined : getMutationViewForModel(store, modelId) ?? undefined,
|
|
356
|
+
);
|
|
333
357
|
const exportOptions: StepExportOptions = {
|
|
334
358
|
schema: options.schema ?? model.ifcDataStore.schemaVersion,
|
|
335
359
|
includeGeometry: true,
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
import type { FileAttachmentInfo, FilesBackendMethods } from '@ifc-lite/sdk';
|
|
6
|
+
import { collectActiveFileAttachments } from '@/lib/attachments';
|
|
7
|
+
import type { StoreApi } from './types.js';
|
|
8
|
+
|
|
9
|
+
function getAttachments(store: StoreApi) {
|
|
10
|
+
const state = store.getState();
|
|
11
|
+
return collectActiveFileAttachments(state.chatMessages, state.chatAttachments);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createFilesAdapter(store: StoreApi): FilesBackendMethods {
|
|
15
|
+
return {
|
|
16
|
+
list(): FileAttachmentInfo[] {
|
|
17
|
+
return getAttachments(store).map((attachment) => ({
|
|
18
|
+
name: attachment.name,
|
|
19
|
+
type: attachment.type,
|
|
20
|
+
size: attachment.size,
|
|
21
|
+
rowCount: attachment.csvData?.length,
|
|
22
|
+
columns: attachment.csvColumns,
|
|
23
|
+
hasTextContent: typeof attachment.textContent === 'string',
|
|
24
|
+
}));
|
|
25
|
+
},
|
|
26
|
+
text(name: string): string | null {
|
|
27
|
+
const attachment = getAttachments(store).find((item) => item.name === name);
|
|
28
|
+
return attachment?.textContent ?? null;
|
|
29
|
+
},
|
|
30
|
+
csv(name: string): Record<string, string>[] | null {
|
|
31
|
+
const attachment = getAttachments(store).find((item) => item.name === name);
|
|
32
|
+
return attachment?.csvData ?? null;
|
|
33
|
+
},
|
|
34
|
+
csvColumns(name: string): string[] {
|
|
35
|
+
const attachment = getAttachments(store).find((item) => item.name === name);
|
|
36
|
+
return attachment?.csvColumns ?? [];
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -42,7 +42,7 @@ export function getModelForRef(state: ViewerState, modelId: string): ModelLike |
|
|
|
42
42
|
if (model) return model;
|
|
43
43
|
|
|
44
44
|
// Legacy single-model fallback
|
|
45
|
-
if (modelId === LEGACY_MODEL_ID && state.models.size === 0 && state.ifcDataStore) {
|
|
45
|
+
if ((modelId === LEGACY_MODEL_ID || modelId === 'legacy') && state.models.size === 0 && state.ifcDataStore) {
|
|
46
46
|
return buildLegacyModel(state.ifcDataStore);
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -4,31 +4,45 @@
|
|
|
4
4
|
|
|
5
5
|
import type { EntityRef, MutateBackendMethods } from '@ifc-lite/sdk';
|
|
6
6
|
import type { StoreApi } from './types.js';
|
|
7
|
+
import { getOrCreateMutationView, normalizeMutationModelId } from './mutation-view.js';
|
|
7
8
|
|
|
8
9
|
export function createMutateAdapter(store: StoreApi): MutateBackendMethods {
|
|
9
10
|
return {
|
|
10
11
|
setProperty(ref: EntityRef, psetName: string, propName: string, value: string | number | boolean) {
|
|
11
12
|
const state = store.getState();
|
|
12
|
-
state
|
|
13
|
+
const normalizedModelId = normalizeMutationModelId(state, ref.modelId);
|
|
14
|
+
if (!getOrCreateMutationView(store, ref.modelId)) return undefined;
|
|
15
|
+
state.setProperty?.(normalizedModelId, ref.expressId, psetName, propName, value);
|
|
16
|
+
return undefined;
|
|
17
|
+
},
|
|
18
|
+
setAttribute(ref: EntityRef, attrName: string, value: string) {
|
|
19
|
+
const state = store.getState();
|
|
20
|
+
const normalizedModelId = normalizeMutationModelId(state, ref.modelId);
|
|
21
|
+
if (!getOrCreateMutationView(store, ref.modelId)) return undefined;
|
|
22
|
+
state.setAttribute?.(normalizedModelId, ref.expressId, attrName, value);
|
|
13
23
|
return undefined;
|
|
14
24
|
},
|
|
15
25
|
deleteProperty(ref: EntityRef, psetName: string, propName: string) {
|
|
16
26
|
const state = store.getState();
|
|
17
|
-
state
|
|
27
|
+
const normalizedModelId = normalizeMutationModelId(state, ref.modelId);
|
|
28
|
+
if (!getOrCreateMutationView(store, ref.modelId)) return undefined;
|
|
29
|
+
state.deleteProperty?.(normalizedModelId, ref.expressId, psetName, propName);
|
|
18
30
|
return undefined;
|
|
19
31
|
},
|
|
20
32
|
undo(modelId: string) {
|
|
21
33
|
const state = store.getState();
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
const normalizedModelId = normalizeMutationModelId(state, modelId);
|
|
35
|
+
if (state.canUndo?.(normalizedModelId)) {
|
|
36
|
+
state.undo?.(normalizedModelId);
|
|
24
37
|
return true;
|
|
25
38
|
}
|
|
26
39
|
return false;
|
|
27
40
|
},
|
|
28
41
|
redo(modelId: string) {
|
|
29
42
|
const state = store.getState();
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
const normalizedModelId = normalizeMutationModelId(state, modelId);
|
|
44
|
+
if (state.canRedo?.(normalizedModelId)) {
|
|
45
|
+
state.redo?.(normalizedModelId);
|
|
32
46
|
return true;
|
|
33
47
|
}
|
|
34
48
|
return false;
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
import { MutablePropertyView } from '@ifc-lite/mutations';
|
|
6
|
+
import { extractPropertiesOnDemand, extractQuantitiesOnDemand } from '@ifc-lite/parser';
|
|
7
|
+
import type { EntityAttributeData, EntityData } from '@ifc-lite/sdk';
|
|
8
|
+
import type { ViewerState } from '../../store/index.js';
|
|
9
|
+
import { getModelForRef, LEGACY_MODEL_ID } from './model-compat.js';
|
|
10
|
+
import type { StoreApi } from './types.js';
|
|
11
|
+
|
|
12
|
+
export const LEGACY_MUTATION_MODEL_ID = '__legacy__';
|
|
13
|
+
|
|
14
|
+
function isLegacyMutationRef(state: ViewerState, modelId: string): boolean {
|
|
15
|
+
return state.models.size === 0 && (modelId === 'legacy' || modelId === LEGACY_MODEL_ID || modelId === LEGACY_MUTATION_MODEL_ID);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function normalizeMutationModelId(state: ViewerState, modelId: string): string {
|
|
19
|
+
if (isLegacyMutationRef(state, modelId)) {
|
|
20
|
+
return LEGACY_MUTATION_MODEL_ID;
|
|
21
|
+
}
|
|
22
|
+
return modelId;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getMutationViewForModel(store: StoreApi, modelId: string): MutablePropertyView | null {
|
|
26
|
+
const state = store.getState();
|
|
27
|
+
return state.getMutationView?.(normalizeMutationModelId(state, modelId)) ?? null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getOrCreateMutationView(store: StoreApi, modelId: string): MutablePropertyView | null {
|
|
31
|
+
const state = store.getState();
|
|
32
|
+
const normalizedModelId = normalizeMutationModelId(state, modelId);
|
|
33
|
+
const existing = state.getMutationView?.(normalizedModelId);
|
|
34
|
+
if (existing) return existing;
|
|
35
|
+
|
|
36
|
+
const modelRefId = isLegacyMutationRef(state, modelId) ? LEGACY_MODEL_ID : modelId;
|
|
37
|
+
const model = getModelForRef(state, modelRefId);
|
|
38
|
+
const dataStore = model?.ifcDataStore;
|
|
39
|
+
if (!dataStore) return null;
|
|
40
|
+
|
|
41
|
+
const mutationView = new MutablePropertyView(dataStore.properties || null, normalizedModelId);
|
|
42
|
+
|
|
43
|
+
if (dataStore.onDemandPropertyMap && dataStore.source?.length) {
|
|
44
|
+
mutationView.setOnDemandExtractor((entityId: number) => (
|
|
45
|
+
extractPropertiesOnDemand(dataStore, entityId)
|
|
46
|
+
));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (dataStore.onDemandQuantityMap && dataStore.source?.length) {
|
|
50
|
+
mutationView.setQuantityExtractor((entityId: number) => (
|
|
51
|
+
extractQuantitiesOnDemand(dataStore, entityId)
|
|
52
|
+
));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
state.registerMutationView?.(normalizedModelId, mutationView);
|
|
56
|
+
return mutationView;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function applyAttributeMutationsToEntityData(
|
|
60
|
+
store: StoreApi,
|
|
61
|
+
modelId: string,
|
|
62
|
+
expressId: number,
|
|
63
|
+
data: EntityData,
|
|
64
|
+
): EntityData {
|
|
65
|
+
const mutationView = getMutationViewForModel(store, modelId);
|
|
66
|
+
if (!mutationView) return data;
|
|
67
|
+
|
|
68
|
+
const mutations = mutationView.getAttributeMutationsForEntity(expressId);
|
|
69
|
+
if (mutations.length === 0) return data;
|
|
70
|
+
|
|
71
|
+
const next = { ...data };
|
|
72
|
+
for (const mutation of mutations) {
|
|
73
|
+
switch (mutation.name) {
|
|
74
|
+
case 'GlobalId':
|
|
75
|
+
next.globalId = mutation.value;
|
|
76
|
+
break;
|
|
77
|
+
case 'Name':
|
|
78
|
+
next.name = mutation.value;
|
|
79
|
+
break;
|
|
80
|
+
case 'Description':
|
|
81
|
+
next.description = mutation.value;
|
|
82
|
+
break;
|
|
83
|
+
case 'ObjectType':
|
|
84
|
+
next.objectType = mutation.value;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return next;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function mergeAttributeMutations(
|
|
92
|
+
baseAttributes: EntityAttributeData[],
|
|
93
|
+
store: StoreApi,
|
|
94
|
+
modelId: string,
|
|
95
|
+
expressId: number,
|
|
96
|
+
): EntityAttributeData[] {
|
|
97
|
+
const mutationView = getMutationViewForModel(store, modelId);
|
|
98
|
+
if (!mutationView) return baseAttributes;
|
|
99
|
+
|
|
100
|
+
const mutations = mutationView.getAttributeMutationsForEntity(expressId);
|
|
101
|
+
if (mutations.length === 0) return baseAttributes;
|
|
102
|
+
|
|
103
|
+
const merged = new Map<string, string>();
|
|
104
|
+
for (const attr of baseAttributes) {
|
|
105
|
+
merged.set(attr.name, attr.value);
|
|
106
|
+
}
|
|
107
|
+
for (const mutation of mutations) {
|
|
108
|
+
merged.set(mutation.name, mutation.value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return [...merged.entries()].map(([name, value]) => ({ name, value }));
|
|
112
|
+
}
|
|
@@ -5,8 +5,14 @@
|
|
|
5
5
|
import type {
|
|
6
6
|
EntityRef,
|
|
7
7
|
EntityData,
|
|
8
|
+
EntityAttributeData,
|
|
8
9
|
PropertySetData,
|
|
9
10
|
QuantitySetData,
|
|
11
|
+
ClassificationData,
|
|
12
|
+
MaterialData,
|
|
13
|
+
TypePropertiesData,
|
|
14
|
+
DocumentData,
|
|
15
|
+
EntityRelationshipsData,
|
|
10
16
|
QueryDescriptor,
|
|
11
17
|
QueryBackendMethods,
|
|
12
18
|
} from '@ifc-lite/sdk';
|
|
@@ -14,6 +20,15 @@ import type { StoreApi } from './types.js';
|
|
|
14
20
|
import { EntityNode } from '@ifc-lite/query';
|
|
15
21
|
import { RelationshipType, IfcTypeEnum, IfcTypeEnumFromString } from '@ifc-lite/data';
|
|
16
22
|
import { getModelForRef, getAllModelEntries } from './model-compat.js';
|
|
23
|
+
import {
|
|
24
|
+
extractAllEntityAttributes,
|
|
25
|
+
extractClassificationsOnDemand,
|
|
26
|
+
extractMaterialsOnDemand,
|
|
27
|
+
extractTypePropertiesOnDemand,
|
|
28
|
+
extractDocumentsOnDemand,
|
|
29
|
+
extractRelationshipsOnDemand,
|
|
30
|
+
} from '@ifc-lite/parser';
|
|
31
|
+
import { applyAttributeMutationsToEntityData, mergeAttributeMutations } from './mutation-view.js';
|
|
17
32
|
|
|
18
33
|
/** Map IFC relationship entity names to internal RelationshipType enum.
|
|
19
34
|
* Keys use proper IFC schema names (e.g. IfcRelAggregates, not "Aggregates"). */
|
|
@@ -86,6 +101,18 @@ function isProductType(type: string): boolean {
|
|
|
86
101
|
return true;
|
|
87
102
|
}
|
|
88
103
|
|
|
104
|
+
function normalizePropertyValue(value: unknown): string | number | boolean | null {
|
|
105
|
+
if (value == null) return null;
|
|
106
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
return JSON.stringify(value);
|
|
111
|
+
} catch {
|
|
112
|
+
return String(value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
89
116
|
export function createQueryAdapter(store: StoreApi): QueryBackendMethods {
|
|
90
117
|
function getEntityData(ref: EntityRef): EntityData | null {
|
|
91
118
|
const state = store.getState();
|
|
@@ -93,14 +120,14 @@ export function createQueryAdapter(store: StoreApi): QueryBackendMethods {
|
|
|
93
120
|
if (!model?.ifcDataStore) return null;
|
|
94
121
|
|
|
95
122
|
const node = new EntityNode(model.ifcDataStore, ref.expressId);
|
|
96
|
-
return {
|
|
123
|
+
return applyAttributeMutationsToEntityData(store, ref.modelId, ref.expressId, {
|
|
97
124
|
ref,
|
|
98
125
|
globalId: node.globalId,
|
|
99
126
|
name: node.name,
|
|
100
127
|
type: node.type,
|
|
101
128
|
description: node.description,
|
|
102
129
|
objectType: node.objectType,
|
|
103
|
-
};
|
|
130
|
+
});
|
|
104
131
|
}
|
|
105
132
|
|
|
106
133
|
function getProperties(ref: EntityRef): PropertySetData[] {
|
|
@@ -120,6 +147,18 @@ export function createQueryAdapter(store: StoreApi): QueryBackendMethods {
|
|
|
120
147
|
}));
|
|
121
148
|
}
|
|
122
149
|
|
|
150
|
+
function getAttributes(ref: EntityRef): EntityAttributeData[] {
|
|
151
|
+
const state = store.getState();
|
|
152
|
+
const model = getModelForRef(state, ref.modelId);
|
|
153
|
+
if (!model?.ifcDataStore) return [];
|
|
154
|
+
return mergeAttributeMutations(
|
|
155
|
+
extractAllEntityAttributes(model.ifcDataStore, ref.expressId),
|
|
156
|
+
store,
|
|
157
|
+
ref.modelId,
|
|
158
|
+
ref.expressId,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
123
162
|
function getQuantities(ref: EntityRef): QuantitySetData[] {
|
|
124
163
|
const state = store.getState();
|
|
125
164
|
const model = getModelForRef(state, ref.modelId);
|
|
@@ -136,6 +175,57 @@ export function createQueryAdapter(store: StoreApi): QueryBackendMethods {
|
|
|
136
175
|
}));
|
|
137
176
|
}
|
|
138
177
|
|
|
178
|
+
function getClassifications(ref: EntityRef): ClassificationData[] {
|
|
179
|
+
const state = store.getState();
|
|
180
|
+
const model = getModelForRef(state, ref.modelId);
|
|
181
|
+
if (!model?.ifcDataStore) return [];
|
|
182
|
+
return extractClassificationsOnDemand(model.ifcDataStore, ref.expressId);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getMaterials(ref: EntityRef): MaterialData | null {
|
|
186
|
+
const state = store.getState();
|
|
187
|
+
const model = getModelForRef(state, ref.modelId);
|
|
188
|
+
if (!model?.ifcDataStore) return null;
|
|
189
|
+
return extractMaterialsOnDemand(model.ifcDataStore, ref.expressId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getTypeProperties(ref: EntityRef): TypePropertiesData | null {
|
|
193
|
+
const state = store.getState();
|
|
194
|
+
const model = getModelForRef(state, ref.modelId);
|
|
195
|
+
if (!model?.ifcDataStore) return null;
|
|
196
|
+
const info = extractTypePropertiesOnDemand(model.ifcDataStore, ref.expressId);
|
|
197
|
+
if (!info) return null;
|
|
198
|
+
return {
|
|
199
|
+
typeName: info.typeName,
|
|
200
|
+
typeId: info.typeId,
|
|
201
|
+
properties: info.properties.map((pset) => ({
|
|
202
|
+
name: pset.name,
|
|
203
|
+
globalId: pset.globalId,
|
|
204
|
+
properties: pset.properties.map((prop) => ({
|
|
205
|
+
name: prop.name,
|
|
206
|
+
type: prop.type,
|
|
207
|
+
value: normalizePropertyValue(prop.value),
|
|
208
|
+
})),
|
|
209
|
+
})),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getDocuments(ref: EntityRef): DocumentData[] {
|
|
214
|
+
const state = store.getState();
|
|
215
|
+
const model = getModelForRef(state, ref.modelId);
|
|
216
|
+
if (!model?.ifcDataStore) return [];
|
|
217
|
+
return extractDocumentsOnDemand(model.ifcDataStore, ref.expressId);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function getRelationships(ref: EntityRef): EntityRelationshipsData {
|
|
221
|
+
const state = store.getState();
|
|
222
|
+
const model = getModelForRef(state, ref.modelId);
|
|
223
|
+
if (!model?.ifcDataStore) {
|
|
224
|
+
return { voids: [], fills: [], groups: [], connections: [] };
|
|
225
|
+
}
|
|
226
|
+
return extractRelationshipsOnDemand(model.ifcDataStore, ref.expressId);
|
|
227
|
+
}
|
|
228
|
+
|
|
139
229
|
function queryEntities(descriptor: QueryDescriptor): EntityData[] {
|
|
140
230
|
const state = store.getState();
|
|
141
231
|
const results: EntityData[] = [];
|
|
@@ -167,14 +257,14 @@ export function createQueryAdapter(store: StoreApi): QueryBackendMethods {
|
|
|
167
257
|
for (const expressId of entityIds) {
|
|
168
258
|
if (expressId === 0) continue;
|
|
169
259
|
const node = new EntityNode(model.ifcDataStore, expressId);
|
|
170
|
-
results.push({
|
|
260
|
+
results.push(applyAttributeMutationsToEntityData(store, modelId, expressId, {
|
|
171
261
|
ref: { modelId, expressId },
|
|
172
262
|
globalId: node.globalId,
|
|
173
263
|
name: node.name,
|
|
174
264
|
type: node.type,
|
|
175
265
|
description: node.description,
|
|
176
266
|
objectType: node.objectType,
|
|
177
|
-
});
|
|
267
|
+
}));
|
|
178
268
|
}
|
|
179
269
|
}
|
|
180
270
|
|
|
@@ -226,8 +316,14 @@ export function createQueryAdapter(store: StoreApi): QueryBackendMethods {
|
|
|
226
316
|
return {
|
|
227
317
|
entities: queryEntities,
|
|
228
318
|
entityData: getEntityData,
|
|
319
|
+
attributes: getAttributes,
|
|
229
320
|
properties: getProperties,
|
|
230
321
|
quantities: getQuantities,
|
|
322
|
+
classifications: getClassifications,
|
|
323
|
+
materials: getMaterials,
|
|
324
|
+
typeProperties: getTypeProperties,
|
|
325
|
+
documents: getDocuments,
|
|
326
|
+
relationships: getRelationships,
|
|
231
327
|
related(ref: EntityRef, relType: string, direction: 'forward' | 'inverse') {
|
|
232
328
|
const state = store.getState();
|
|
233
329
|
const model = getModelForRef(state, ref.modelId);
|
package/src/sdk/local-backend.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
SpatialBackendMethods,
|
|
22
22
|
ExportBackendMethods,
|
|
23
23
|
LensBackendMethods,
|
|
24
|
+
FilesBackendMethods,
|
|
24
25
|
} from '@ifc-lite/sdk';
|
|
25
26
|
import type { StoreApi } from './adapters/types.js';
|
|
26
27
|
import { LEGACY_MODEL_ID } from './adapters/model-compat.js';
|
|
@@ -33,6 +34,7 @@ import { createMutateAdapter } from './adapters/mutate-adapter.js';
|
|
|
33
34
|
import { createSpatialAdapter } from './adapters/spatial-adapter.js';
|
|
34
35
|
import { createLensAdapter } from './adapters/lens-adapter.js';
|
|
35
36
|
import { createExportAdapter } from './adapters/export-adapter.js';
|
|
37
|
+
import { createFilesAdapter } from './adapters/files-adapter.js';
|
|
36
38
|
|
|
37
39
|
export class LocalBackend implements BimBackend {
|
|
38
40
|
readonly model: ModelBackendMethods;
|
|
@@ -44,6 +46,7 @@ export class LocalBackend implements BimBackend {
|
|
|
44
46
|
readonly spatial: SpatialBackendMethods;
|
|
45
47
|
readonly export: ExportBackendMethods;
|
|
46
48
|
readonly lens: LensBackendMethods;
|
|
49
|
+
readonly files: FilesBackendMethods;
|
|
47
50
|
|
|
48
51
|
private store: StoreApi;
|
|
49
52
|
|
|
@@ -58,6 +61,7 @@ export class LocalBackend implements BimBackend {
|
|
|
58
61
|
this.spatial = createSpatialAdapter(store);
|
|
59
62
|
this.lens = createLensAdapter(store);
|
|
60
63
|
this.export = createExportAdapter(store);
|
|
64
|
+
this.files = createFilesAdapter(store);
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
subscribe(event: BimEventType, handler: (data: unknown) => void): () => void {
|
package/src/store/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { createListSlice, type ListSlice } from './slices/listSlice.js';
|
|
|
31
31
|
import { createPinboardSlice, type PinboardSlice } from './slices/pinboardSlice.js';
|
|
32
32
|
import { createLensSlice, type LensSlice } from './slices/lensSlice.js';
|
|
33
33
|
import { createScriptSlice, type ScriptSlice } from './slices/scriptSlice.js';
|
|
34
|
+
import { createChatSlice, type ChatSlice } from './slices/chatSlice.js';
|
|
34
35
|
import { invalidateVisibleBasketCache } from './basketVisibleSet.js';
|
|
35
36
|
|
|
36
37
|
// Import constants for reset function
|
|
@@ -72,6 +73,9 @@ export type { LensSlice, Lens, LensRule, LensCriteria } from './slices/lensSlice
|
|
|
72
73
|
// Re-export Script types
|
|
73
74
|
export type { ScriptSlice } from './slices/scriptSlice.js';
|
|
74
75
|
|
|
76
|
+
// Re-export Chat types
|
|
77
|
+
export type { ChatSlice } from './slices/chatSlice.js';
|
|
78
|
+
|
|
75
79
|
// Combined store type
|
|
76
80
|
export type ViewerState = LoadingSlice &
|
|
77
81
|
SelectionSlice &
|
|
@@ -91,7 +95,8 @@ export type ViewerState = LoadingSlice &
|
|
|
91
95
|
ListSlice &
|
|
92
96
|
PinboardSlice &
|
|
93
97
|
LensSlice &
|
|
94
|
-
ScriptSlice &
|
|
98
|
+
ScriptSlice &
|
|
99
|
+
ChatSlice & {
|
|
95
100
|
resetViewerState: () => void;
|
|
96
101
|
};
|
|
97
102
|
|
|
@@ -119,6 +124,7 @@ export const useViewerStore = create<ViewerState>()((...args) => ({
|
|
|
119
124
|
...createPinboardSlice(...args),
|
|
120
125
|
...createLensSlice(...args),
|
|
121
126
|
...createScriptSlice(...args),
|
|
127
|
+
...createChatSlice(...args),
|
|
122
128
|
|
|
123
129
|
// Reset all viewer state when loading new file
|
|
124
130
|
// Note: Does NOT clear models - use clearAllModels() for that
|
|
@@ -280,6 +286,8 @@ export const useViewerStore = create<ViewerState>()((...args) => ({
|
|
|
280
286
|
scriptExecutionState: 'idle' as const,
|
|
281
287
|
scriptLastResult: null,
|
|
282
288
|
scriptLastError: null,
|
|
289
|
+
scriptLastDiagnostics: [],
|
|
290
|
+
scriptAssistantTurnSnapshot: null,
|
|
283
291
|
scriptDeleteConfirmId: null,
|
|
284
292
|
|
|
285
293
|
// Lens - deactivate but keep saved lenses
|
|
@@ -289,6 +297,12 @@ export const useViewerStore = create<ViewerState>()((...args) => ({
|
|
|
289
297
|
lensHiddenIds: new Set<number>(),
|
|
290
298
|
lensRuleCounts: new Map<string, number>(),
|
|
291
299
|
lensRuleEntityIds: new Map<string, number[]>(),
|
|
300
|
+
|
|
301
|
+
// Chat - keep messages and panel visible, reset streaming state
|
|
302
|
+
chatStatus: 'idle' as const,
|
|
303
|
+
chatStreamingContent: '',
|
|
304
|
+
chatError: null,
|
|
305
|
+
chatAbortController: null,
|
|
292
306
|
});
|
|
293
307
|
},
|
|
294
308
|
}));
|