@ifc-lite/cli 0.41.0 → 0.42.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/README.md +12 -1
- package/dist/commands/diff-engine.d.ts.map +1 -1
- package/dist/commands/diff-engine.js +9 -3
- package/dist/commands/diff-engine.js.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +24 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/export-rust-formats.d.ts.map +1 -1
- package/dist/commands/export-rust-formats.js +37 -41
- package/dist/commands/export-rust-formats.js.map +1 -1
- package/dist/commands/extract-entities.d.ts.map +1 -1
- package/dist/commands/extract-entities.js +7 -11
- package/dist/commands/extract-entities.js.map +1 -1
- package/dist/commands/gym.d.ts.map +1 -1
- package/dist/commands/gym.js +2 -10
- package/dist/commands/gym.js.map +1 -1
- package/dist/commands/mutate.d.ts +6 -0
- package/dist/commands/mutate.d.ts.map +1 -1
- package/dist/commands/mutate.js +16 -19
- package/dist/commands/mutate.js.map +1 -1
- package/dist/commands/mutation-base.d.ts +4 -0
- package/dist/commands/mutation-base.d.ts.map +1 -0
- package/dist/commands/mutation-base.js +11 -0
- package/dist/commands/mutation-base.js.map +1 -0
- package/dist/commands/step-args.d.ts +0 -119
- package/dist/commands/step-args.d.ts.map +1 -1
- package/dist/commands/step-args.js +54 -49
- package/dist/commands/step-args.js.map +1 -1
- package/dist/commands/storey-selection.d.ts.map +1 -1
- package/dist/commands/storey-selection.js +16 -9
- package/dist/commands/storey-selection.js.map +1 -1
- package/dist/commands/subset-relations.d.ts +0 -76
- package/dist/commands/subset-relations.d.ts.map +1 -1
- package/dist/commands/subset-relations.js +88 -24
- package/dist/commands/subset-relations.js.map +1 -1
- package/dist/commands/test-fixtures/type-mutation.d.ts +18 -0
- package/dist/commands/test-fixtures/type-mutation.d.ts.map +1 -0
- package/dist/commands/test-fixtures/type-mutation.js +45 -0
- package/dist/commands/test-fixtures/type-mutation.js.map +1 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +6 -0
- package/dist/commands/validate.js.map +1 -1
- package/dist/diagnostics.d.ts +33 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +113 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/headless-backend.d.ts +29 -77
- package/dist/headless-backend.d.ts.map +1 -1
- package/dist/headless-backend.js +54 -536
- package/dist/headless-backend.js.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +1 -0
- package/dist/help.js.map +1 -1
- package/dist/index.js +51 -82
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +7 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +34 -50
- package/dist/loader.js.map +1 -1
- package/package.json +21 -20
- package/dist/query-overlay-relations.d.ts +0 -9
- package/dist/query-overlay-relations.d.ts.map +0 -1
- package/dist/query-overlay-relations.js +0 -78
- package/dist/query-overlay-relations.js.map +0 -1
- package/dist/query-overlay.d.ts +0 -46
- package/dist/query-overlay.d.ts.map +0 -1
- package/dist/query-overlay.js +0 -141
- package/dist/query-overlay.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless-backend.d.ts","sourceRoot":"","sources":["../src/headless-backend.ts"],"names":[],"mappings":"AAIA,
|
|
1
|
+
{"version":3,"file":"headless-backend.d.ts","sourceRoot":"","sources":["../src/headless-backend.ts"],"names":[],"mappings":"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"}
|
package/dist/headless-backend.js
CHANGED
|
@@ -1,50 +1,18 @@
|
|
|
1
1
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
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
|
-
import {
|
|
4
|
+
import { resolveLiveOwnerHistoryId } from '@ifc-lite/sdk';
|
|
5
|
+
import { HeadlessBackend as SharedHeadlessBackend, createQueryAdapter } from '@ifc-lite/sdk/headless';
|
|
6
|
+
import { expandTypes, isQueryableObjectType } from '@ifc-lite/parser';
|
|
7
|
+
import { normalizeBooleanValue } from '@ifc-lite/query';
|
|
8
|
+
import { addDoorToStore, addWindowToStore, resolveSpatialAnchor, generateSpaces, listStoreys } from '@ifc-lite/create';
|
|
5
9
|
import { createStoreAuthoring } from './headless-backend-store-authoring.js';
|
|
6
|
-
import { MutablePropertyView, StoreEditor, storeHasSourceEntity } from '@ifc-lite/mutations';
|
|
7
|
-
import { addDoorToStore, applyStylesInStore, addWindowToStore, resolveSpatialAnchor, generateSpaces, listStoreys, } from '@ifc-lite/create';
|
|
8
|
-
import { EntityNode, findPropertyInSets, findQuantityInSets, normalizeBooleanValue, matchesPropertyFilter } from '@ifc-lite/query';
|
|
9
|
-
import { extractAllEntityAttributes, extractClassificationsOnDemand, extractMaterialsOnDemand, extractPropertiesOnDemand, extractQuantitiesOnDemand, extractTypePropertiesOnDemand, extractDocumentsOnDemand, extractRelationshipsOnDemand, extractExactRelatedIds, expandTypes, QUERY_REL_TYPE_MAP, isQueryableObjectType, } from '@ifc-lite/parser';
|
|
10
|
-
import { escapeCsvCell, exportToStep, StepExporter } from '@ifc-lite/export';
|
|
11
10
|
import { createStructuralAdapter } from './headless-backend-structural.js';
|
|
12
11
|
import { createScheduleAdapter } from './headless-backend-schedule.js';
|
|
13
12
|
import { exportHbjson, exportDfjson } from './energy-export.js';
|
|
14
|
-
|
|
15
|
-
import { applyParsedEntityOverrides, overlayEntityData, overlayProperties, overlayQuantities } from './query-overlay.js';
|
|
16
|
-
import { iterateEffectiveEntityIds } from '@ifc-lite/mutations';
|
|
17
|
-
// `expandTypes` used to be defined here; it now comes from `@ifc-lite/parser`,
|
|
18
|
-
// shared with the other query backends (see `query-backend-maps.ts`). Re-exported
|
|
19
|
-
// so this module's consumers are unaffected by where it lives.
|
|
20
|
-
export { expandTypes };
|
|
21
|
-
const MODEL_ID = 'default';
|
|
22
|
-
/**
|
|
23
|
-
* Strip a real IFC source-file extension from a filename-derived model name.
|
|
24
|
-
* Only applied to the `modelName` fallback: an explicitly supplied export name
|
|
25
|
-
* is a display name where a trailing dotted segment is meaningful (`Tower.v2`).
|
|
26
|
-
*/
|
|
27
|
-
function stripIfcExtension(name) {
|
|
28
|
-
return name.replace(/\.(ifc|ifcx|ifczip)$/i, '');
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Which classes an unfiltered query answers with.
|
|
32
|
-
*
|
|
33
|
-
* Thin alias: the predicate is schema logic and lives in `@ifc-lite/parser`, so
|
|
34
|
-
* the CLI and MCP backends cannot drift apart on it. Kept as a named export
|
|
35
|
-
* here because both packages already publish it under this name.
|
|
36
|
-
*/
|
|
13
|
+
export { expandTypes, normalizeBooleanValue };
|
|
37
14
|
export const isProductType = isQueryableObjectType;
|
|
38
|
-
|
|
39
|
-
* Normalize boolean-like values for comparison.
|
|
40
|
-
* IFC STEP files store booleans as .T./.F., but users pass true/false.
|
|
41
|
-
*
|
|
42
|
-
* Re-exported from `@ifc-lite/query` for existing importers of this module;
|
|
43
|
-
* the actual comparison the query filter runs lives there too
|
|
44
|
-
* (`compareFilterValue`), shared with the viewer and MCP `QueryBackendMethods`
|
|
45
|
-
* implementations so the three can't drift apart on `where()` semantics again.
|
|
46
|
-
*/
|
|
47
|
-
export { normalizeBooleanValue };
|
|
15
|
+
const MODEL_ID = 'default';
|
|
48
16
|
export function normalizePropertyValue(value) {
|
|
49
17
|
if (value == null)
|
|
50
18
|
return null;
|
|
@@ -63,13 +31,12 @@ export function normalizePropertyValue(value) {
|
|
|
63
31
|
}
|
|
64
32
|
}
|
|
65
33
|
export class HeadlessBackend {
|
|
34
|
+
dataStore;
|
|
66
35
|
model;
|
|
67
36
|
query;
|
|
68
37
|
selection;
|
|
69
|
-
visibility;
|
|
70
|
-
viewer;
|
|
71
38
|
mutate;
|
|
72
|
-
|
|
39
|
+
style;
|
|
73
40
|
spatial;
|
|
74
41
|
export;
|
|
75
42
|
lens;
|
|
@@ -77,70 +44,57 @@ export class HeadlessBackend {
|
|
|
77
44
|
schedule;
|
|
78
45
|
structural;
|
|
79
46
|
cost;
|
|
47
|
+
store;
|
|
80
48
|
spaces;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
this.
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.mutate = this.createMutateAdapter();
|
|
101
|
-
this.store = this.createStoreAdapter();
|
|
102
|
-
this.spatial = this.createSpatialAdapter();
|
|
103
|
-
this.export = this.createExportAdapter();
|
|
104
|
-
this.lens = this.createLensAdapter();
|
|
105
|
-
this.files = this.createFilesAdapter();
|
|
106
|
-
this.schedule = createScheduleAdapter(this.dataStore, modelId => this.assertKnownModelId(modelId));
|
|
107
|
-
this.structural = createStructuralAdapter(this.dataStore, modelId => this.assertKnownModelId(modelId));
|
|
108
|
-
this.cost = createCostBackend(modelId => {
|
|
109
|
-
if (modelId)
|
|
110
|
-
this.assertKnownModelId(modelId);
|
|
111
|
-
// Lazily created overlay (#4857): a `bim.store.addCost*` authored
|
|
112
|
-
// entity, or any pending edit, is only visible to `bim.cost` once this
|
|
113
|
-
// is non-null, and it stays null until the first mutating call.
|
|
114
|
-
return { modelId: MODEL_ID, store: this.dataStore, mutationView: this.mutationView ?? undefined };
|
|
49
|
+
viewer;
|
|
50
|
+
visibility;
|
|
51
|
+
shared;
|
|
52
|
+
constructor(dataStore, modelName) {
|
|
53
|
+
this.dataStore = dataStore;
|
|
54
|
+
this.shared = new SharedHeadlessBackend(dataStore, modelName, MODEL_ID, {}, modelName === MODEL_ID ? [] : [modelName]);
|
|
55
|
+
this.model = this.shared.model;
|
|
56
|
+
this.selection = this.shared.selection;
|
|
57
|
+
this.mutate = this.shared.mutate;
|
|
58
|
+
this.style = this.shared.style;
|
|
59
|
+
this.spatial = this.shared.spatial;
|
|
60
|
+
this.lens = this.shared.lens;
|
|
61
|
+
this.files = this.shared.files;
|
|
62
|
+
this.cost = this.shared.cost;
|
|
63
|
+
this.viewer = this.shared.viewer;
|
|
64
|
+
this.visibility = this.shared.visibility;
|
|
65
|
+
this.query = createQueryAdapter(dataStore, MODEL_ID, () => this.shared.pendingOverlay(), () => this.getMutationView(), {
|
|
66
|
+
paginationError: TypeError,
|
|
67
|
+
normalizeTypePropertyValue: normalizePropertyValue,
|
|
115
68
|
});
|
|
69
|
+
this.store = this.createStoreAdapter();
|
|
70
|
+
this.schedule = createScheduleAdapter(dataStore, id => this.assertKnownModelId(id));
|
|
71
|
+
this.structural = createStructuralAdapter(dataStore, id => this.assertKnownModelId(id));
|
|
116
72
|
this.spaces = this.createSpacesAdapter();
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
73
|
+
// Explicit display names retain dots; only the filename-derived fallback loses its IFC suffix.
|
|
74
|
+
const fallbackName = modelName.replace(/\.(ifc|ifcx|ifczip)$/i, '');
|
|
75
|
+
this.export = {
|
|
76
|
+
...this.shared.export,
|
|
77
|
+
hbjson: name => exportHbjson(dataStore, this.mutationView, name ?? fallbackName),
|
|
78
|
+
dfjson: name => exportDfjson(dataStore, this.mutationView, name ?? fallbackName),
|
|
79
|
+
};
|
|
122
80
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
81
|
+
get mutationView() { return this.shared.getMutationView(); }
|
|
82
|
+
getMutationView() { return this.shared.getMutationView(); }
|
|
83
|
+
getOrCreateMutationView() { return this.shared.getOrCreateMutationView(); }
|
|
84
|
+
getOrCreateStoreEditor() { return this.shared.ensureEditor(); }
|
|
85
|
+
assertKnownModelId(modelId) { this.shared.assertKnownModelId(modelId); }
|
|
86
|
+
tableAccess(modelId) {
|
|
87
|
+
if (modelId)
|
|
88
|
+
this.assertKnownModelId(modelId);
|
|
89
|
+
return { entities: this.dataStore.entities, mutationView: this.getOrCreateMutationView(), strings: this.dataStore.strings ?? null };
|
|
126
90
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (this.acceptsModelId(modelId))
|
|
130
|
-
return;
|
|
131
|
-
throw new Error(`Unknown modelId '${modelId}': this backend answers for ${this.acceptedModelIdList()}`);
|
|
91
|
+
subscribe(event, handler) {
|
|
92
|
+
return this.shared.subscribe(event, handler);
|
|
132
93
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// land in the overlay this backend's export adapter already reads.
|
|
138
|
-
applyColors: (batches, options) => applyStylesInStore(this.getOrCreateStoreEditor(), this.dataStore, batches.map(batch => ({
|
|
139
|
-
products: batch.refs.map(r => r.expressId),
|
|
140
|
-
color: batch.color,
|
|
141
|
-
name: batch.name,
|
|
142
|
-
})), options),
|
|
143
|
-
};
|
|
94
|
+
attachStreamingAdapters(viewer, visibility) {
|
|
95
|
+
this.shared.attachStreamingAdapters(viewer, visibility);
|
|
96
|
+
this.viewer = viewer;
|
|
97
|
+
this.visibility = visibility;
|
|
144
98
|
}
|
|
145
99
|
createSpacesAdapter() {
|
|
146
100
|
return {
|
|
@@ -155,306 +109,6 @@ export class HeadlessBackend {
|
|
|
155
109
|
},
|
|
156
110
|
};
|
|
157
111
|
}
|
|
158
|
-
subscribe(_event, _handler) {
|
|
159
|
-
return () => { };
|
|
160
|
-
}
|
|
161
|
-
createModelAdapter() {
|
|
162
|
-
const store = this.dataStore;
|
|
163
|
-
const name = this.modelName;
|
|
164
|
-
return {
|
|
165
|
-
list() {
|
|
166
|
-
return [{
|
|
167
|
-
id: MODEL_ID,
|
|
168
|
-
name,
|
|
169
|
-
schema: store.schemaVersion,
|
|
170
|
-
schemaVersion: store.schemaVersion,
|
|
171
|
-
entityCount: store.entityCount,
|
|
172
|
-
fileSize: store.fileSize,
|
|
173
|
-
loadedAt: Date.now(),
|
|
174
|
-
}];
|
|
175
|
-
},
|
|
176
|
-
activeId() { return MODEL_ID; },
|
|
177
|
-
loadIfc() { },
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
createQueryAdapter() {
|
|
181
|
-
const store = this.dataStore;
|
|
182
|
-
// Lazy — a read-only session's `related()` stays on the store-only path.
|
|
183
|
-
const getMutationView = () => this.mutationView;
|
|
184
|
-
function getEntityData(ref) {
|
|
185
|
-
const overlay = overlayEntityData(getMutationView(), ref, store.schemaVersion);
|
|
186
|
-
if (overlay !== undefined)
|
|
187
|
-
return overlay;
|
|
188
|
-
// @raw-entity-enumeration-ok overlayEntityData already handles deleted and created ids; this is source membership for one remaining ref
|
|
189
|
-
if (!store.entityIndex.byId.has(ref.expressId))
|
|
190
|
-
return null; // not parsed either
|
|
191
|
-
const node = new EntityNode(store, ref.expressId);
|
|
192
|
-
const type = node.type;
|
|
193
|
-
if (!type || type === 'Unknown')
|
|
194
|
-
return null;
|
|
195
|
-
return applyParsedEntityOverrides(getMutationView(), ref.expressId, type, store.schemaVersion, {
|
|
196
|
-
ref,
|
|
197
|
-
globalId: node.globalId,
|
|
198
|
-
name: node.name,
|
|
199
|
-
type,
|
|
200
|
-
description: node.description,
|
|
201
|
-
objectType: node.objectType,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
function getProperties(ref) {
|
|
205
|
-
const overlay = overlayProperties(getMutationView(), ref);
|
|
206
|
-
if (overlay !== undefined)
|
|
207
|
-
return overlay;
|
|
208
|
-
const node = new EntityNode(store, ref.expressId);
|
|
209
|
-
return node.properties().map((pset) => ({
|
|
210
|
-
name: pset.name,
|
|
211
|
-
globalId: pset.globalId,
|
|
212
|
-
properties: pset.properties.map((p) => ({
|
|
213
|
-
name: p.name,
|
|
214
|
-
type: p.type,
|
|
215
|
-
value: p.value,
|
|
216
|
-
})),
|
|
217
|
-
}));
|
|
218
|
-
}
|
|
219
|
-
function getQuantities(ref) {
|
|
220
|
-
const overlay = overlayQuantities(getMutationView(), ref);
|
|
221
|
-
if (overlay !== undefined)
|
|
222
|
-
return overlay;
|
|
223
|
-
const node = new EntityNode(store, ref.expressId);
|
|
224
|
-
return node.quantities().map(qset => ({
|
|
225
|
-
name: qset.name,
|
|
226
|
-
quantities: qset.quantities.map(q => ({
|
|
227
|
-
name: q.name,
|
|
228
|
-
type: q.type,
|
|
229
|
-
value: q.value,
|
|
230
|
-
})),
|
|
231
|
-
}));
|
|
232
|
-
}
|
|
233
|
-
return {
|
|
234
|
-
entities(descriptor) {
|
|
235
|
-
const results = [];
|
|
236
|
-
const view = getMutationView();
|
|
237
|
-
const types = descriptor.types && descriptor.types.length > 0
|
|
238
|
-
? expandTypes(descriptor.types, store.schemaVersion) : undefined;
|
|
239
|
-
for (const { expressId, type, overlayCreated } of iterateEffectiveEntityIds(store, view, types)) {
|
|
240
|
-
if (!types && !isProductType(type))
|
|
241
|
-
continue;
|
|
242
|
-
const ref = { modelId: MODEL_ID, expressId };
|
|
243
|
-
if (overlayCreated) {
|
|
244
|
-
const created = overlayEntityData(view, ref, store.schemaVersion);
|
|
245
|
-
if (created)
|
|
246
|
-
results.push(created);
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
const node = new EntityNode(store, expressId);
|
|
250
|
-
results.push({
|
|
251
|
-
ref,
|
|
252
|
-
globalId: node.globalId,
|
|
253
|
-
name: node.name,
|
|
254
|
-
type: view?.getEntityTypeMutation(expressId)?.newType ?? node.type,
|
|
255
|
-
description: node.description,
|
|
256
|
-
objectType: node.objectType,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
let filtered = results;
|
|
260
|
-
if (descriptor.filters && descriptor.filters.length > 0) {
|
|
261
|
-
const propsCache = new Map();
|
|
262
|
-
const getCachedProps = (ref) => {
|
|
263
|
-
let cached = propsCache.get(ref.expressId);
|
|
264
|
-
if (!cached) {
|
|
265
|
-
cached = getProperties(ref);
|
|
266
|
-
propsCache.set(ref.expressId, cached);
|
|
267
|
-
}
|
|
268
|
-
return cached;
|
|
269
|
-
};
|
|
270
|
-
// A `Qto_` filter (or any psetName with no matching property set)
|
|
271
|
-
// falls back to quantity sets — see `matchesPropertyFilter` in
|
|
272
|
-
// `@ifc-lite/query`'s `property-filter-match.ts`. Cached the same
|
|
273
|
-
// way as `propsCache`; only populated on the fallback path since
|
|
274
|
-
// most filters resolve from properties alone.
|
|
275
|
-
const qsetsCache = new Map();
|
|
276
|
-
const getCachedQuantities = (ref) => {
|
|
277
|
-
let cached = qsetsCache.get(ref.expressId);
|
|
278
|
-
if (!cached) {
|
|
279
|
-
cached = getQuantities(ref);
|
|
280
|
-
qsetsCache.set(ref.expressId, cached);
|
|
281
|
-
}
|
|
282
|
-
return cached;
|
|
283
|
-
};
|
|
284
|
-
for (const filter of descriptor.filters) {
|
|
285
|
-
filtered = filtered.filter(entity => {
|
|
286
|
-
const props = getCachedProps(entity.ref);
|
|
287
|
-
return matchesPropertyFilter(props, filter, getCachedQuantities(entity.ref));
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
// `!= null` alone lets a NaN offset/limit through (neither null nor
|
|
292
|
-
// undefined); a bare `> 0` then silently drops it (every NaN
|
|
293
|
-
// comparison is false) instead of rejecting it, and by the same
|
|
294
|
-
// reasoning silently ignored a deliberate `limit: 0`. Reject
|
|
295
|
-
// non-finite/negative values loudly instead of quietly serving the
|
|
296
|
-
// wrong slice. The CLI's own `--limit`/`--offset` flags are validated
|
|
297
|
-
// before reaching this descriptor, so this guards direct SDK callers
|
|
298
|
-
// (`@ifc-lite/mcp`'s parallel `backend-query.ts` ported the identical
|
|
299
|
-
// fix independently — same defect shape, own tests and release
|
|
300
|
-
// cadence).
|
|
301
|
-
if (descriptor.offset != null) {
|
|
302
|
-
if (!Number.isFinite(descriptor.offset) || descriptor.offset < 0) {
|
|
303
|
-
throw new TypeError(`Invalid offset: ${descriptor.offset} (must be a non-negative finite number)`);
|
|
304
|
-
}
|
|
305
|
-
if (descriptor.offset > 0)
|
|
306
|
-
filtered = filtered.slice(descriptor.offset);
|
|
307
|
-
}
|
|
308
|
-
if (descriptor.limit != null) {
|
|
309
|
-
if (!Number.isFinite(descriptor.limit) || descriptor.limit < 0) {
|
|
310
|
-
throw new TypeError(`Invalid limit: ${descriptor.limit} (must be a non-negative finite number)`);
|
|
311
|
-
}
|
|
312
|
-
filtered = filtered.slice(0, descriptor.limit);
|
|
313
|
-
}
|
|
314
|
-
return filtered;
|
|
315
|
-
},
|
|
316
|
-
// Headless contexts have no interactive viewer filter, so there is never
|
|
317
|
-
// an "active filter" to report (issue #1107).
|
|
318
|
-
entitiesMatchingActiveFilter: () => null,
|
|
319
|
-
entityData: getEntityData,
|
|
320
|
-
attributes(ref) {
|
|
321
|
-
return extractAllEntityAttributes(store, ref.expressId);
|
|
322
|
-
},
|
|
323
|
-
properties: getProperties,
|
|
324
|
-
quantities: getQuantities,
|
|
325
|
-
classifications(ref) {
|
|
326
|
-
return extractClassificationsOnDemand(store, ref.expressId);
|
|
327
|
-
},
|
|
328
|
-
materials(ref) {
|
|
329
|
-
return extractMaterialsOnDemand(store, ref.expressId);
|
|
330
|
-
},
|
|
331
|
-
typeProperties(ref) {
|
|
332
|
-
const info = extractTypePropertiesOnDemand(store, ref.expressId);
|
|
333
|
-
if (!info)
|
|
334
|
-
return null;
|
|
335
|
-
return {
|
|
336
|
-
typeName: info.typeName,
|
|
337
|
-
typeId: info.typeId,
|
|
338
|
-
properties: info.properties.map((pset) => ({
|
|
339
|
-
name: pset.name,
|
|
340
|
-
globalId: pset.globalId,
|
|
341
|
-
properties: pset.properties.map((prop) => ({
|
|
342
|
-
name: prop.name,
|
|
343
|
-
type: prop.type,
|
|
344
|
-
value: normalizePropertyValue(prop.value),
|
|
345
|
-
})),
|
|
346
|
-
})),
|
|
347
|
-
};
|
|
348
|
-
},
|
|
349
|
-
documents(ref) {
|
|
350
|
-
return extractDocumentsOnDemand(store, ref.expressId);
|
|
351
|
-
},
|
|
352
|
-
relationships(ref) {
|
|
353
|
-
const result = extractRelationshipsOnDemand(store, ref.expressId);
|
|
354
|
-
const view = getMutationView();
|
|
355
|
-
if (!view)
|
|
356
|
-
return result;
|
|
357
|
-
return foldQueuedRelationshipData(store, view, result, ref, getEntityData);
|
|
358
|
-
},
|
|
359
|
-
// Folds queued `IfcRel…` creates in (query-overlay-relations.ts, mirrors #2014).
|
|
360
|
-
related(ref, relType, direction) {
|
|
361
|
-
const relEnum = QUERY_REL_TYPE_MAP[relType];
|
|
362
|
-
if (relEnum === undefined)
|
|
363
|
-
return [];
|
|
364
|
-
const view = getMutationView();
|
|
365
|
-
if (view?.isDeleted(ref.expressId))
|
|
366
|
-
return []; // deleted relates to nothing
|
|
367
|
-
const out = [];
|
|
368
|
-
const seen = new Set();
|
|
369
|
-
const take = (id) => {
|
|
370
|
-
if (view?.isDeleted(id) || seen.has(id))
|
|
371
|
-
return;
|
|
372
|
-
seen.add(id);
|
|
373
|
-
out.push(id);
|
|
374
|
-
};
|
|
375
|
-
const superseded = view ? supersededRelationshipIds(store, view) : new Set();
|
|
376
|
-
const isDeleted = view ? (id) => view.isDeleted(id) || superseded.has(id) : () => false;
|
|
377
|
-
for (const id of extractExactRelatedIds(store, ref.expressId, relType, direction, isDeleted))
|
|
378
|
-
take(id);
|
|
379
|
-
if (view)
|
|
380
|
-
for (const t of foldQueuedRelated(store, view, relType, direction, ref.expressId))
|
|
381
|
-
take(t);
|
|
382
|
-
return out.map((expressId) => ({ modelId: ref.modelId, expressId }));
|
|
383
|
-
},
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
createSelectionAdapter() {
|
|
387
|
-
let selection = [];
|
|
388
|
-
return {
|
|
389
|
-
get() { return selection; },
|
|
390
|
-
set(refs) { selection = refs; },
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
createVisibilityAdapter() {
|
|
394
|
-
return {
|
|
395
|
-
hide() { },
|
|
396
|
-
show() { },
|
|
397
|
-
isolate() { },
|
|
398
|
-
reset() { },
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
createViewerAdapter() {
|
|
402
|
-
return {
|
|
403
|
-
colorize() { },
|
|
404
|
-
colorizeAll() { },
|
|
405
|
-
resetColors() { },
|
|
406
|
-
flyTo() { },
|
|
407
|
-
setSection() { },
|
|
408
|
-
getSection() { return null; },
|
|
409
|
-
setCamera() { },
|
|
410
|
-
getCamera() { return { mode: 'perspective' }; },
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
createMutateAdapter() {
|
|
414
|
-
return createHeadlessMutateAdapter(() => this.getOrCreateMutationView(), createEffectiveEntityCheck({
|
|
415
|
-
acceptedModelIds: this.acceptedModelIds,
|
|
416
|
-
// Both halves of the source index (byId + deferred property atoms, #5222).
|
|
417
|
-
hasSourceEntity: id => storeHasSourceEntity(this.dataStore, id),
|
|
418
|
-
overlay: () => this.mutationView,
|
|
419
|
-
}));
|
|
420
|
-
}
|
|
421
|
-
/** Inspect an existing overlay without allocating a StoreEditor (#7372). */
|
|
422
|
-
getMutationView() { return this.mutationView; }
|
|
423
|
-
/**
|
|
424
|
-
* The overlay every write goes through, created on first use by
|
|
425
|
-
* `getOrCreateStoreEditor` so its extractors are wired exactly once.
|
|
426
|
-
*/
|
|
427
|
-
getOrCreateMutationView() {
|
|
428
|
-
this.getOrCreateStoreEditor();
|
|
429
|
-
// Non-null immediately after: both fields are assigned together and never cleared.
|
|
430
|
-
return this.mutationView;
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* The bulk entity-table access `table.joinByKey` needs to reuse
|
|
434
|
-
* `@ifc-lite/mutations`' `csv-match.ts` tag/property index (#5167, #5230)
|
|
435
|
-
* rather than re-implement it. Same lazily-created overlay as `bim.cost`.
|
|
436
|
-
*/
|
|
437
|
-
tableAccess(modelId) {
|
|
438
|
-
if (modelId)
|
|
439
|
-
this.assertKnownModelId(modelId);
|
|
440
|
-
return { entities: this.dataStore.entities, mutationView: this.getOrCreateMutationView(), strings: this.dataStore.strings ?? null };
|
|
441
|
-
}
|
|
442
|
-
getOrCreateStoreEditor() {
|
|
443
|
-
if (this.storeEditor)
|
|
444
|
-
return this.storeEditor;
|
|
445
|
-
this.mutationView = new MutablePropertyView(this.dataStore.properties || null, MODEL_ID);
|
|
446
|
-
// Give the overlay a base to merge against — the columnar parser serves
|
|
447
|
-
// properties on demand, so without this `getForEntity` would answer with
|
|
448
|
-
// only the one edited pset and `StepExporter` would drop every sibling
|
|
449
|
-
// property on save. Same wiring as `packages/mcp/src/headless-backend.ts`
|
|
450
|
-
// and `apps/viewer/src/utils/configureMutationView.ts` (#2000, #2004).
|
|
451
|
-
if (this.dataStore.source?.length > 0) {
|
|
452
|
-
this.mutationView.setOnDemandExtractor((entityId) => extractPropertiesOnDemand(this.dataStore, entityId));
|
|
453
|
-
this.mutationView.setQuantityExtractor((entityId) => extractQuantitiesOnDemand(this.dataStore, entityId));
|
|
454
|
-
}
|
|
455
|
-
this.storeEditor = new StoreEditor(this.dataStore, this.mutationView);
|
|
456
|
-
return this.storeEditor;
|
|
457
|
-
}
|
|
458
112
|
createStoreAdapter() {
|
|
459
113
|
const get = () => this.getOrCreateStoreEditor();
|
|
460
114
|
const dataStore = () => this.dataStore;
|
|
@@ -502,141 +156,5 @@ export class HeadlessBackend {
|
|
|
502
156
|
}),
|
|
503
157
|
};
|
|
504
158
|
}
|
|
505
|
-
createSpatialAdapter() {
|
|
506
|
-
return {
|
|
507
|
-
queryBounds() { return []; },
|
|
508
|
-
raycast() { return []; },
|
|
509
|
-
queryFrustum() { return []; },
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
createExportAdapter() {
|
|
513
|
-
const store = this.dataStore;
|
|
514
|
-
const modelName = this.modelName;
|
|
515
|
-
const queryAdapter = this.query;
|
|
516
|
-
/**
|
|
517
|
-
* RFC 4180 quoting + the CWE-1236 formula-injection guard, delegated to
|
|
518
|
-
* `@ifc-lite/export`'s single escaper. The copy that used to live here
|
|
519
|
-
* tested the trigger anchored at offset 0, so a BOM/ZWSP/LRM/NBSP/U+2028
|
|
520
|
-
* in front of `=` walked past it.
|
|
521
|
-
*/
|
|
522
|
-
function escapeCsv(value, sep) {
|
|
523
|
-
return escapeCsvCell(value, { delimiter: sep });
|
|
524
|
-
}
|
|
525
|
-
function resolveColumn(data, col, props, qsets) {
|
|
526
|
-
if (col === 'Name' || col === 'name')
|
|
527
|
-
return data.name;
|
|
528
|
-
if (col === 'Type' || col === 'type')
|
|
529
|
-
return data.type;
|
|
530
|
-
if (col === 'GlobalId' || col === 'globalId')
|
|
531
|
-
return data.globalId;
|
|
532
|
-
if (col === 'Description' || col === 'description')
|
|
533
|
-
return data.description;
|
|
534
|
-
if (col === 'ObjectType' || col === 'objectType')
|
|
535
|
-
return data.objectType;
|
|
536
|
-
const dotIdx = col.indexOf('.');
|
|
537
|
-
if (dotIdx > 0) {
|
|
538
|
-
const setName = col.slice(0, dotIdx);
|
|
539
|
-
const valueName = col.slice(dotIdx + 1);
|
|
540
|
-
if (props) {
|
|
541
|
-
const prop = findPropertyInSets(props, setName, valueName);
|
|
542
|
-
if (prop?.value != null)
|
|
543
|
-
return String(prop.value);
|
|
544
|
-
}
|
|
545
|
-
if (qsets) {
|
|
546
|
-
const qty = findQuantityInSets(qsets, setName, valueName);
|
|
547
|
-
if (qty?.value != null)
|
|
548
|
-
return String(qty.value);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
return '';
|
|
552
|
-
}
|
|
553
|
-
return {
|
|
554
|
-
csv(refs, options) {
|
|
555
|
-
const entityRefs = refs;
|
|
556
|
-
const opts = options;
|
|
557
|
-
const columns = opts.columns;
|
|
558
|
-
const sep = opts.separator ?? ',';
|
|
559
|
-
const hasDotColumns = columns.some(c => c.indexOf('.') > 0);
|
|
560
|
-
const rows = [columns];
|
|
561
|
-
for (const ref of entityRefs) {
|
|
562
|
-
const data = queryAdapter.entityData(ref);
|
|
563
|
-
if (!data)
|
|
564
|
-
continue;
|
|
565
|
-
const props = hasDotColumns ? queryAdapter.properties(ref) : null;
|
|
566
|
-
const qsets = hasDotColumns ? queryAdapter.quantities(ref) : null;
|
|
567
|
-
rows.push(columns.map(col => resolveColumn(data, col, props, qsets)));
|
|
568
|
-
}
|
|
569
|
-
return rows.map(r => r.map(cell => escapeCsv(cell, sep)).join(sep)).join('\n');
|
|
570
|
-
},
|
|
571
|
-
json(refs, columns) {
|
|
572
|
-
const entityRefs = refs;
|
|
573
|
-
const cols = columns;
|
|
574
|
-
const hasDotColumns = cols.some(c => c.indexOf('.') > 0);
|
|
575
|
-
const result = [];
|
|
576
|
-
for (const ref of entityRefs) {
|
|
577
|
-
const data = queryAdapter.entityData(ref);
|
|
578
|
-
if (!data)
|
|
579
|
-
continue;
|
|
580
|
-
const props = hasDotColumns ? queryAdapter.properties(ref) : null;
|
|
581
|
-
const qsets = hasDotColumns ? queryAdapter.quantities(ref) : null;
|
|
582
|
-
const row = {};
|
|
583
|
-
for (const col of cols) {
|
|
584
|
-
const val = resolveColumn(data, col, props, qsets);
|
|
585
|
-
row[col] = val || null;
|
|
586
|
-
}
|
|
587
|
-
result.push(row);
|
|
588
|
-
}
|
|
589
|
-
return result;
|
|
590
|
-
},
|
|
591
|
-
ifc: (refs, options) => {
|
|
592
|
-
const opts = (options ?? {});
|
|
593
|
-
const schema = opts.schema ?? store.schemaVersion ?? 'IFC4';
|
|
594
|
-
const exportOpts = { schema };
|
|
595
|
-
// `undefined` is the only "no isolation filter": an empty list is a filter
|
|
596
|
-
// that matched nothing, refused above in `ExportNamespace.ifc` (#4738).
|
|
597
|
-
if (refs != null) {
|
|
598
|
-
const isolatedIds = new Set(refs.map(r => r.expressId));
|
|
599
|
-
exportOpts.visibleOnly = true;
|
|
600
|
-
exportOpts.isolatedEntityIds = isolatedIds;
|
|
601
|
-
exportOpts.hiddenEntityIds = new Set();
|
|
602
|
-
}
|
|
603
|
-
// Route through StepExporter directly so any bim.store.* / bim.mutate.*
|
|
604
|
-
// overlay state on this backend's MutablePropertyView is included.
|
|
605
|
-
if (this.mutationView) {
|
|
606
|
-
const exporter = new StepExporter(store, this.mutationView);
|
|
607
|
-
const result = exporter.export({ schema, ...exportOpts });
|
|
608
|
-
return new TextDecoder().decode(result.content);
|
|
609
|
-
}
|
|
610
|
-
return exportToStep(store, exportOpts);
|
|
611
|
-
},
|
|
612
|
-
// Both energy formats apply the mutation view — see energy-export.ts
|
|
613
|
-
// (issues #1908, #1344). An explicit `name` is a display/model name, so
|
|
614
|
-
// it is kept verbatim (dotted identifiers like `Tower.v2` are valid);
|
|
615
|
-
// only the `modelName` fallback is a filename, so only it gets a real
|
|
616
|
-
// IFC extension stripped.
|
|
617
|
-
hbjson: (name) => exportHbjson(store, this.mutationView, name ?? stripIfcExtension(modelName)),
|
|
618
|
-
dfjson: (name) => exportDfjson(store, this.mutationView, name ?? stripIfcExtension(modelName)),
|
|
619
|
-
download(_content, _filename, _mimeType) {
|
|
620
|
-
/* no-op — CLI writes to stdout/file directly */
|
|
621
|
-
},
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
createLensAdapter() {
|
|
625
|
-
return {
|
|
626
|
-
presets() { return []; },
|
|
627
|
-
create() { return null; },
|
|
628
|
-
activate() { },
|
|
629
|
-
deactivate() { },
|
|
630
|
-
getActive() { return null; },
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
createFilesAdapter() {
|
|
634
|
-
return {
|
|
635
|
-
list() { return []; },
|
|
636
|
-
text() { return null; },
|
|
637
|
-
csv() { return null; },
|
|
638
|
-
csvColumns() { return []; },
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
159
|
}
|
|
642
160
|
//# sourceMappingURL=headless-backend.js.map
|