@ifc-lite/viewer 1.13.0 → 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 +24 -0
- package/dist/assets/{Arrow.dom-VW5W1XFO.js → Arrow.dom-CNguvlQi.js} +1 -1
- package/dist/assets/{browser-C6mwD6n0.js → browser-D6lgLpkA.js} +1 -1
- package/dist/assets/{index-DQE23JyT.js → index-BMwpw264.js} +4 -4
- package/dist/assets/index-Qp8stcGO.css +1 -0
- package/dist/assets/{index-BzoX4cQC.js → index-UaDsJsCR.js} +24458 -22069
- package/dist/assets/{native-bridge-BibEEmFV.js → native-bridge-DqELq4X0.js} +1 -1
- package/dist/assets/{wasm-bridge-CYzUd3Io.js → wasm-bridge-CVWvHlfH.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +19 -19
- package/src/components/viewer/BulkPropertyEditor.tsx +8 -1
- package/src/components/viewer/DataConnector.tsx +8 -1
- package/src/components/viewer/ExportChangesButton.tsx +8 -1
- package/src/components/viewer/ExportDialog.tsx +8 -1
- package/src/components/viewer/PropertiesPanel.tsx +209 -15
- package/src/components/viewer/properties/BsddCard.tsx +507 -0
- package/src/components/viewer/properties/QuantitySetCard.tsx +1 -0
- 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/model-adapter.ts +8 -0
- package/src/services/bsdd.ts +262 -0
- package/src/store/index.ts +2 -2
- package/src/store/slices/mutationSlice.ts +155 -1
- package/vite.config.ts +7 -0
- package/dist/assets/index-Cx134arv.css +0 -1
|
@@ -0,0 +1,262 @@
|
|
|
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
|
+
* bSDD (buildingSMART Data Dictionary) API client.
|
|
7
|
+
*
|
|
8
|
+
* Fetches IFC class definitions, property sets, and properties from the
|
|
9
|
+
* bSDD REST API so that users can discover schema-conform properties
|
|
10
|
+
* for a selected IFC entity type and add them in one click.
|
|
11
|
+
*
|
|
12
|
+
* API docs: https://app.swaggerhub.com/apis/buildingSMART/Dictionaries/v1
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Proxy through our own origin to avoid CORS issues.
|
|
16
|
+
// In dev Vite proxies /api/bsdd → https://api.bsdd.buildingsmart.org,
|
|
17
|
+
// in production Vercel rewrites do the same.
|
|
18
|
+
const BSDD_API = '/api/bsdd';
|
|
19
|
+
const IFC_DICTIONARY_URI =
|
|
20
|
+
'https://identifier.buildingsmart.org/uri/buildingsmart/ifc/4.3';
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Types
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
export interface BsddClassProperty {
|
|
27
|
+
/** Property name, e.g. "IsExternal" */
|
|
28
|
+
name: string;
|
|
29
|
+
/** URI of the property definition */
|
|
30
|
+
uri: string;
|
|
31
|
+
/** Human-readable description */
|
|
32
|
+
description: string | null;
|
|
33
|
+
/** bSDD data type, e.g. "Boolean", "Real", "String" */
|
|
34
|
+
dataType: string | null;
|
|
35
|
+
/** Name of the property set this property belongs to */
|
|
36
|
+
propertySet: string | null;
|
|
37
|
+
/** Allowed values (enum constraints) */
|
|
38
|
+
allowedValues: Array<{ uri?: string; value: string; description?: string }> | null;
|
|
39
|
+
/** Units */
|
|
40
|
+
units: string[] | null;
|
|
41
|
+
/** Whether this is from the IFC standard dictionary */
|
|
42
|
+
isIfcStandard: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BsddClassInfo {
|
|
46
|
+
/** Class URI */
|
|
47
|
+
uri: string;
|
|
48
|
+
/** IFC entity code, e.g. "IfcWall" */
|
|
49
|
+
code: string;
|
|
50
|
+
/** Human-readable name */
|
|
51
|
+
name: string;
|
|
52
|
+
/** Description / definition */
|
|
53
|
+
definition: string | null;
|
|
54
|
+
/** Parent class URI */
|
|
55
|
+
parentClassUri: string | null;
|
|
56
|
+
/** Properties defined for this class */
|
|
57
|
+
classProperties: BsddClassProperty[];
|
|
58
|
+
/** Related IFC entity names */
|
|
59
|
+
relatedIfcEntityNames: string[] | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface BsddSearchResult {
|
|
63
|
+
uri: string;
|
|
64
|
+
code: string;
|
|
65
|
+
name: string;
|
|
66
|
+
definition: string | null;
|
|
67
|
+
dictionaryUri: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// In-memory cache (keyed by class URI)
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
const classCache = new Map<string, { data: BsddClassInfo; ts: number }>();
|
|
75
|
+
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
76
|
+
|
|
77
|
+
function getCached(key: string): BsddClassInfo | null {
|
|
78
|
+
const entry = classCache.get(key);
|
|
79
|
+
if (entry && Date.now() - entry.ts < CACHE_TTL_MS) return entry.data;
|
|
80
|
+
if (entry) classCache.delete(key);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function setCache(key: string, data: BsddClassInfo) {
|
|
85
|
+
classCache.set(key, { data, ts: Date.now() });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// API helpers
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
async function fetchJson<T>(url: string): Promise<T> {
|
|
93
|
+
const res = await fetch(url, {
|
|
94
|
+
headers: { Accept: 'application/json' },
|
|
95
|
+
});
|
|
96
|
+
if (!res.ok) {
|
|
97
|
+
throw new Error(`bSDD API ${res.status}: ${res.statusText}`);
|
|
98
|
+
}
|
|
99
|
+
return res.json() as Promise<T>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Public API
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build the bSDD class URI for an IFC entity type.
|
|
108
|
+
* e.g. "IfcWall" -> "https://identifier.buildingsmart.org/uri/buildingsmart/ifc/4.3/class/IfcWall"
|
|
109
|
+
*/
|
|
110
|
+
export function ifcClassUri(ifcType: string): string {
|
|
111
|
+
// Use the type name as-is. IFC parsers typically produce PascalCase
|
|
112
|
+
// names (e.g. "IfcWall") which match the bSDD URI scheme directly.
|
|
113
|
+
// Previous best-effort lowercasing corrupted multi-word names like
|
|
114
|
+
// IFCWALLSTANDARDCASE → "IfcWallstandardcase", so we no longer attempt
|
|
115
|
+
// case normalisation — the bSDD API will simply 404 for unknown names
|
|
116
|
+
// and we handle that gracefully.
|
|
117
|
+
return `${IFC_DICTIONARY_URI}/class/${ifcType}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Fetch full class info (including properties) for an IFC entity type.
|
|
122
|
+
*
|
|
123
|
+
* Uses the `/api/Class/v1` endpoint with `IncludeClassProperties=true`
|
|
124
|
+
* (PascalCase parameter names per the bSDD OpenAPI spec).
|
|
125
|
+
* Falls back to the paginated `/api/Class/Properties/v1` endpoint when
|
|
126
|
+
* the inline property list comes back empty.
|
|
127
|
+
*/
|
|
128
|
+
export async function fetchClassInfo(
|
|
129
|
+
ifcType: string,
|
|
130
|
+
): Promise<BsddClassInfo | null> {
|
|
131
|
+
const uri = ifcClassUri(ifcType);
|
|
132
|
+
const cached = getCached(uri);
|
|
133
|
+
if (cached) return cached;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
// Parameter names must be PascalCase per the bSDD OpenAPI spec
|
|
137
|
+
const raw = await fetchJson<Record<string, unknown>>(
|
|
138
|
+
`${BSDD_API}/api/Class/v1?Uri=${encodeURIComponent(uri)}&IncludeClassProperties=true&IncludeClassRelations=true`,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
let info = mapClassResponse(raw, true);
|
|
142
|
+
|
|
143
|
+
// Fallback: if inline classProperties came back empty, try the
|
|
144
|
+
// dedicated paginated properties endpoint
|
|
145
|
+
if (info.classProperties.length === 0) {
|
|
146
|
+
const propsRaw = await fetchJson<Record<string, unknown>>(
|
|
147
|
+
`${BSDD_API}/api/Class/Properties/v1?ClassUri=${encodeURIComponent(uri)}`,
|
|
148
|
+
).catch(() => null);
|
|
149
|
+
|
|
150
|
+
if (propsRaw) {
|
|
151
|
+
const propsList = propsRaw.classProperties as Array<Record<string, unknown>> | undefined;
|
|
152
|
+
if (propsList && propsList.length > 0) {
|
|
153
|
+
info = {
|
|
154
|
+
...info,
|
|
155
|
+
classProperties: propsList.map((p) => ({
|
|
156
|
+
name: String(p.name ?? p.propertyCode ?? ''),
|
|
157
|
+
uri: String(p.propertyUri ?? p.uri ?? ''),
|
|
158
|
+
description: p.description ? String(p.description) : null,
|
|
159
|
+
dataType: p.dataType ? String(p.dataType) : null,
|
|
160
|
+
propertySet: p.propertySet ? String(p.propertySet) : null,
|
|
161
|
+
allowedValues: Array.isArray(p.allowedValues)
|
|
162
|
+
? p.allowedValues.map((v: Record<string, unknown>) => ({
|
|
163
|
+
uri: v.uri ? String(v.uri) : undefined,
|
|
164
|
+
value: String(v.value ?? ''),
|
|
165
|
+
description: v.description ? String(v.description) : undefined,
|
|
166
|
+
}))
|
|
167
|
+
: null,
|
|
168
|
+
units: Array.isArray(p.units) ? (p.units as string[]) : null,
|
|
169
|
+
isIfcStandard: true,
|
|
170
|
+
})),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
setCache(uri, info);
|
|
177
|
+
return info;
|
|
178
|
+
} catch {
|
|
179
|
+
// Silently return null – bSDD may not have data for every type
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Search bSDD for classes related to a given IFC entity type across all
|
|
186
|
+
* dictionaries (not just the IFC dictionary).
|
|
187
|
+
*
|
|
188
|
+
* Uses `/api/Class/Search/v1` with a RelatedIfcEntities filter.
|
|
189
|
+
* Returns lightweight results. Call `fetchClassInfo` on a specific result
|
|
190
|
+
* to get full properties.
|
|
191
|
+
*/
|
|
192
|
+
export async function searchRelatedClasses(
|
|
193
|
+
ifcType: string,
|
|
194
|
+
): Promise<BsddSearchResult[]> {
|
|
195
|
+
try {
|
|
196
|
+
const raw = await fetchJson<{
|
|
197
|
+
classes?: Array<Record<string, unknown>>;
|
|
198
|
+
}>(
|
|
199
|
+
`${BSDD_API}/api/Class/Search/v1?SearchText=${encodeURIComponent(ifcType)}&RelatedIfcEntities=${encodeURIComponent(ifcType)}`,
|
|
200
|
+
);
|
|
201
|
+
return (raw.classes ?? []).map((c) => ({
|
|
202
|
+
uri: String(c.uri ?? ''),
|
|
203
|
+
code: String(c.code ?? c.name ?? ''),
|
|
204
|
+
name: String(c.name ?? ''),
|
|
205
|
+
definition: c.definition ? String(c.definition) : null,
|
|
206
|
+
dictionaryUri: String(c.dictionaryUri ?? ''),
|
|
207
|
+
}));
|
|
208
|
+
} catch {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// Response mapping
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
function mapClassResponse(
|
|
218
|
+
raw: Record<string, unknown>,
|
|
219
|
+
isIfcStandard: boolean,
|
|
220
|
+
): BsddClassInfo {
|
|
221
|
+
const props = raw.classProperties as Array<Record<string, unknown>> | undefined;
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
uri: String(raw.uri ?? ''),
|
|
225
|
+
code: String(raw.code ?? raw.name ?? ''),
|
|
226
|
+
name: String(raw.name ?? ''),
|
|
227
|
+
definition: raw.definition ? String(raw.definition) : null,
|
|
228
|
+
parentClassUri: raw.parentClassReference
|
|
229
|
+
? String((raw.parentClassReference as Record<string, unknown>).uri ?? '')
|
|
230
|
+
: null,
|
|
231
|
+
relatedIfcEntityNames: raw.relatedIfcEntityNames as string[] | null,
|
|
232
|
+
classProperties: (props ?? []).map((p) => ({
|
|
233
|
+
name: String(p.name ?? p.propertyCode ?? ''),
|
|
234
|
+
uri: String(p.propertyUri ?? p.uri ?? ''),
|
|
235
|
+
description: p.description ? String(p.description) : null,
|
|
236
|
+
dataType: p.dataType ? String(p.dataType) : null,
|
|
237
|
+
propertySet: p.propertySet ? String(p.propertySet) : null,
|
|
238
|
+
allowedValues: Array.isArray(p.allowedValues)
|
|
239
|
+
? p.allowedValues.map((v: Record<string, unknown>) => ({
|
|
240
|
+
uri: v.uri ? String(v.uri) : undefined,
|
|
241
|
+
value: String(v.value ?? ''),
|
|
242
|
+
description: v.description ? String(v.description) : undefined,
|
|
243
|
+
}))
|
|
244
|
+
: null,
|
|
245
|
+
units: Array.isArray(p.units) ? (p.units as string[]) : null,
|
|
246
|
+
isIfcStandard,
|
|
247
|
+
})),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Map bSDD dataType string to a human-friendly label.
|
|
253
|
+
*/
|
|
254
|
+
export function bsddDataTypeLabel(dt: string | null): string {
|
|
255
|
+
if (!dt) return 'String';
|
|
256
|
+
const lower = dt.toLowerCase();
|
|
257
|
+
if (lower === 'boolean') return 'Boolean';
|
|
258
|
+
if (lower === 'real' || lower === 'number') return 'Real';
|
|
259
|
+
if (lower === 'integer') return 'Integer';
|
|
260
|
+
if (lower === 'string' || lower === 'character') return 'String';
|
|
261
|
+
return dt;
|
|
262
|
+
}
|
package/src/store/index.ts
CHANGED
|
@@ -275,8 +275,8 @@ export const useViewerStore = create<ViewerState>()((...args) => ({
|
|
|
275
275
|
basketPresentationVisible: false,
|
|
276
276
|
hierarchyBasketSelection: new Set<string>(),
|
|
277
277
|
|
|
278
|
-
// Script - reset execution state but keep saved scripts and
|
|
279
|
-
|
|
278
|
+
// Script - reset execution state but keep saved scripts, editor content, and panel visibility
|
|
279
|
+
// (scripts that create-and-load a model should not close the panel)
|
|
280
280
|
scriptExecutionState: 'idle' as const,
|
|
281
281
|
scriptLastResult: null,
|
|
282
282
|
scriptLastError: null,
|
|
@@ -10,7 +10,7 @@ import { type StateCreator } from 'zustand';
|
|
|
10
10
|
import type { ViewerState } from '../index.js';
|
|
11
11
|
import type { MutablePropertyView } from '@ifc-lite/mutations';
|
|
12
12
|
import type { Mutation, ChangeSet, PropertyValue } from '@ifc-lite/mutations';
|
|
13
|
-
import { PropertyValueType } from '@ifc-lite/data';
|
|
13
|
+
import { PropertyValueType, QuantityType } from '@ifc-lite/data';
|
|
14
14
|
|
|
15
15
|
export interface MutationSlice {
|
|
16
16
|
// State
|
|
@@ -68,6 +68,35 @@ export interface MutationSlice {
|
|
|
68
68
|
psetName: string
|
|
69
69
|
) => Mutation | null;
|
|
70
70
|
|
|
71
|
+
// Actions - Quantity Mutations
|
|
72
|
+
/** Set a quantity value */
|
|
73
|
+
setQuantity: (
|
|
74
|
+
modelId: string,
|
|
75
|
+
entityId: number,
|
|
76
|
+
qsetName: string,
|
|
77
|
+
quantName: string,
|
|
78
|
+
value: number,
|
|
79
|
+
quantityType?: QuantityType,
|
|
80
|
+
unit?: string
|
|
81
|
+
) => Mutation | null;
|
|
82
|
+
/** Create a new quantity set */
|
|
83
|
+
createQuantitySet: (
|
|
84
|
+
modelId: string,
|
|
85
|
+
entityId: number,
|
|
86
|
+
qsetName: string,
|
|
87
|
+
quantities: Array<{ name: string; value: number; quantityType: QuantityType; unit?: string }>
|
|
88
|
+
) => Mutation | null;
|
|
89
|
+
|
|
90
|
+
// Actions - Attribute Mutations
|
|
91
|
+
/** Set an entity attribute value */
|
|
92
|
+
setAttribute: (
|
|
93
|
+
modelId: string,
|
|
94
|
+
entityId: number,
|
|
95
|
+
attrName: string,
|
|
96
|
+
value: string,
|
|
97
|
+
oldValue?: string
|
|
98
|
+
) => Mutation | null;
|
|
99
|
+
|
|
71
100
|
// Actions - Undo/Redo
|
|
72
101
|
/** Undo last mutation for a model */
|
|
73
102
|
undo: (modelId: string) => void;
|
|
@@ -266,6 +295,92 @@ export const createMutationSlice: StateCreator<
|
|
|
266
295
|
return mutation;
|
|
267
296
|
},
|
|
268
297
|
|
|
298
|
+
// Quantity Mutations
|
|
299
|
+
setQuantity: (modelId, entityId, qsetName, quantName, value, quantityType = QuantityType.Count, unit) => {
|
|
300
|
+
const view = get().mutationViews.get(modelId);
|
|
301
|
+
if (!view) return null;
|
|
302
|
+
|
|
303
|
+
const mutation = view.setQuantity(entityId, qsetName, quantName, value, quantityType, unit);
|
|
304
|
+
|
|
305
|
+
set((state) => {
|
|
306
|
+
const newUndoStacks = new Map(state.undoStacks);
|
|
307
|
+
const stack = newUndoStacks.get(modelId) || [];
|
|
308
|
+
newUndoStacks.set(modelId, [...stack, mutation]);
|
|
309
|
+
|
|
310
|
+
const newRedoStacks = new Map(state.redoStacks);
|
|
311
|
+
newRedoStacks.set(modelId, []);
|
|
312
|
+
|
|
313
|
+
const newDirty = new Set(state.dirtyModels);
|
|
314
|
+
newDirty.add(modelId);
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
undoStacks: newUndoStacks,
|
|
318
|
+
redoStacks: newRedoStacks,
|
|
319
|
+
dirtyModels: newDirty,
|
|
320
|
+
mutationVersion: state.mutationVersion + 1,
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return mutation;
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
createQuantitySet: (modelId, entityId, qsetName, quantities) => {
|
|
328
|
+
const view = get().mutationViews.get(modelId);
|
|
329
|
+
if (!view) return null;
|
|
330
|
+
|
|
331
|
+
const mutation = view.createQuantitySet(entityId, qsetName, quantities);
|
|
332
|
+
|
|
333
|
+
set((state) => {
|
|
334
|
+
const newUndoStacks = new Map(state.undoStacks);
|
|
335
|
+
const stack = newUndoStacks.get(modelId) || [];
|
|
336
|
+
newUndoStacks.set(modelId, [...stack, mutation]);
|
|
337
|
+
|
|
338
|
+
const newRedoStacks = new Map(state.redoStacks);
|
|
339
|
+
newRedoStacks.set(modelId, []);
|
|
340
|
+
|
|
341
|
+
const newDirty = new Set(state.dirtyModels);
|
|
342
|
+
newDirty.add(modelId);
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
undoStacks: newUndoStacks,
|
|
346
|
+
redoStacks: newRedoStacks,
|
|
347
|
+
dirtyModels: newDirty,
|
|
348
|
+
mutationVersion: state.mutationVersion + 1,
|
|
349
|
+
};
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
return mutation;
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
// Attribute Mutations
|
|
356
|
+
setAttribute: (modelId, entityId, attrName, value, oldValue) => {
|
|
357
|
+
const view = get().mutationViews.get(modelId);
|
|
358
|
+
if (!view) return null;
|
|
359
|
+
|
|
360
|
+
const mutation = view.setAttribute(entityId, attrName, value, oldValue);
|
|
361
|
+
|
|
362
|
+
set((state) => {
|
|
363
|
+
const newUndoStacks = new Map(state.undoStacks);
|
|
364
|
+
const stack = newUndoStacks.get(modelId) || [];
|
|
365
|
+
newUndoStacks.set(modelId, [...stack, mutation]);
|
|
366
|
+
|
|
367
|
+
const newRedoStacks = new Map(state.redoStacks);
|
|
368
|
+
newRedoStacks.set(modelId, []);
|
|
369
|
+
|
|
370
|
+
const newDirty = new Set(state.dirtyModels);
|
|
371
|
+
newDirty.add(modelId);
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
undoStacks: newUndoStacks,
|
|
375
|
+
redoStacks: newRedoStacks,
|
|
376
|
+
dirtyModels: newDirty,
|
|
377
|
+
mutationVersion: state.mutationVersion + 1,
|
|
378
|
+
};
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
return mutation;
|
|
382
|
+
},
|
|
383
|
+
|
|
269
384
|
// Undo/Redo
|
|
270
385
|
undo: (modelId) => {
|
|
271
386
|
const state = get();
|
|
@@ -303,6 +418,29 @@ export const createMutationSlice: StateCreator<
|
|
|
303
418
|
true // skipHistory
|
|
304
419
|
);
|
|
305
420
|
}
|
|
421
|
+
} else if (mutation.type === 'CREATE_QUANTITY') {
|
|
422
|
+
// Undo creation: remove the quantity mutation
|
|
423
|
+
view.removeQuantityMutation(mutation.entityId, mutation.psetName!, mutation.propName);
|
|
424
|
+
} else if (mutation.type === 'UPDATE_QUANTITY') {
|
|
425
|
+
if (mutation.psetName && mutation.propName && mutation.oldValue !== undefined && mutation.oldValue !== null) {
|
|
426
|
+
view.setQuantity(
|
|
427
|
+
mutation.entityId,
|
|
428
|
+
mutation.psetName,
|
|
429
|
+
mutation.propName,
|
|
430
|
+
Number(mutation.oldValue),
|
|
431
|
+
undefined,
|
|
432
|
+
undefined,
|
|
433
|
+
true // skipHistory
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
} else if (mutation.type === 'UPDATE_ATTRIBUTE') {
|
|
437
|
+
if (mutation.attributeName) {
|
|
438
|
+
if (mutation.oldValue !== undefined && mutation.oldValue !== null) {
|
|
439
|
+
view.setAttribute(mutation.entityId, mutation.attributeName, String(mutation.oldValue), undefined, true);
|
|
440
|
+
} else {
|
|
441
|
+
view.removeAttributeMutation(mutation.entityId, mutation.attributeName);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
306
444
|
}
|
|
307
445
|
|
|
308
446
|
set((s) => {
|
|
@@ -347,6 +485,22 @@ export const createMutationSlice: StateCreator<
|
|
|
347
485
|
if (mutation.psetName && mutation.propName) {
|
|
348
486
|
view.deleteProperty(mutation.entityId, mutation.psetName, mutation.propName, true);
|
|
349
487
|
}
|
|
488
|
+
} else if (mutation.type === 'CREATE_QUANTITY' || mutation.type === 'UPDATE_QUANTITY') {
|
|
489
|
+
if (mutation.psetName && mutation.propName && mutation.newValue !== undefined) {
|
|
490
|
+
view.setQuantity(
|
|
491
|
+
mutation.entityId,
|
|
492
|
+
mutation.psetName,
|
|
493
|
+
mutation.propName,
|
|
494
|
+
Number(mutation.newValue),
|
|
495
|
+
undefined,
|
|
496
|
+
undefined,
|
|
497
|
+
true // skipHistory
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
} else if (mutation.type === 'UPDATE_ATTRIBUTE') {
|
|
501
|
+
if (mutation.attributeName && mutation.newValue !== undefined) {
|
|
502
|
+
view.setAttribute(mutation.entityId, mutation.attributeName, String(mutation.newValue), undefined, true);
|
|
503
|
+
}
|
|
350
504
|
}
|
|
351
505
|
|
|
352
506
|
set((s) => {
|
package/vite.config.ts
CHANGED
|
@@ -201,6 +201,13 @@ export default defineConfig({
|
|
|
201
201
|
fs: {
|
|
202
202
|
allow: ['../..'],
|
|
203
203
|
},
|
|
204
|
+
proxy: {
|
|
205
|
+
'/api/bsdd': {
|
|
206
|
+
target: 'https://api.bsdd.buildingsmart.org',
|
|
207
|
+
changeOrigin: true,
|
|
208
|
+
rewrite: (p) => p.replace(/^\/api\/bsdd/, ''),
|
|
209
|
+
},
|
|
210
|
+
},
|
|
204
211
|
},
|
|
205
212
|
build: {
|
|
206
213
|
target: 'esnext',
|