@ifc-lite/export 3.1.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/anonymize-export.d.ts.map +1 -1
- package/dist/anonymize-export.js +108 -8
- package/dist/anonymize-export.js.map +1 -1
- package/dist/anonymize-placement.js +4 -4
- package/dist/anonymize-scrub.d.ts +6 -0
- package/dist/anonymize-scrub.d.ts.map +1 -1
- package/dist/anonymize-scrub.js +8 -4
- package/dist/anonymize-scrub.js.map +1 -1
- package/dist/anonymize-types.d.ts +30 -7
- package/dist/anonymize-types.d.ts.map +1 -1
- package/dist/declared-property-type.d.ts +15 -0
- package/dist/declared-property-type.d.ts.map +1 -1
- package/dist/declared-property-type.js +43 -0
- package/dist/declared-property-type.js.map +1 -1
- package/dist/georef-closure.d.ts +55 -0
- package/dist/georef-closure.d.ts.map +1 -0
- package/dist/georef-closure.js +95 -0
- package/dist/georef-closure.js.map +1 -0
- package/dist/glb.d.ts.map +1 -1
- package/dist/glb.js +26 -2
- package/dist/glb.js.map +1 -1
- package/dist/ifc5-classification.d.ts +36 -0
- package/dist/ifc5-classification.d.ts.map +1 -0
- package/dist/ifc5-classification.js +46 -0
- package/dist/ifc5-classification.js.map +1 -0
- package/dist/ifc5-export-helpers.d.ts +32 -0
- package/dist/ifc5-export-helpers.d.ts.map +1 -0
- package/dist/ifc5-export-helpers.js +84 -0
- package/dist/ifc5-export-helpers.js.map +1 -0
- package/dist/ifc5-exporter.d.ts.map +1 -1
- package/dist/ifc5-exporter.js +14 -86
- package/dist/ifc5-exporter.js.map +1 -1
- package/dist/ifc5-material.d.ts +38 -0
- package/dist/ifc5-material.d.ts.map +1 -0
- package/dist/ifc5-material.js +32 -0
- package/dist/ifc5-material.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/lod0-generator.d.ts +16 -0
- package/dist/lod0-generator.d.ts.map +1 -1
- package/dist/lod0-generator.js +33 -1
- package/dist/lod0-generator.js.map +1 -1
- package/dist/merged-context.d.ts +100 -0
- package/dist/merged-context.d.ts.map +1 -0
- package/dist/merged-context.js +211 -0
- package/dist/merged-context.js.map +1 -0
- package/dist/merged-empty-containers.d.ts +63 -0
- package/dist/merged-empty-containers.d.ts.map +1 -0
- package/dist/merged-empty-containers.js +354 -0
- package/dist/merged-empty-containers.js.map +1 -0
- package/dist/merged-exporter.d.ts +28 -67
- package/dist/merged-exporter.d.ts.map +1 -1
- package/dist/merged-exporter.js +134 -258
- package/dist/merged-exporter.js.map +1 -1
- package/dist/merged-guid.d.ts +48 -0
- package/dist/merged-guid.d.ts.map +1 -0
- package/dist/merged-guid.js +128 -0
- package/dist/merged-guid.js.map +1 -0
- package/dist/merged-rel-aggregates.d.ts +63 -0
- package/dist/merged-rel-aggregates.d.ts.map +1 -0
- package/dist/merged-rel-aggregates.js +122 -0
- package/dist/merged-rel-aggregates.js.map +1 -0
- package/dist/merged-remap.d.ts +18 -0
- package/dist/merged-remap.d.ts.map +1 -0
- package/dist/merged-remap.js +66 -0
- package/dist/merged-remap.js.map +1 -0
- package/dist/merged-subcontext.d.ts +41 -0
- package/dist/merged-subcontext.d.ts.map +1 -0
- package/dist/merged-subcontext.js +99 -0
- package/dist/merged-subcontext.js.map +1 -0
- package/dist/parquet-exporter-ondemand.d.ts +34 -0
- package/dist/parquet-exporter-ondemand.d.ts.map +1 -0
- package/dist/parquet-exporter-ondemand.js +89 -0
- package/dist/parquet-exporter-ondemand.js.map +1 -0
- package/dist/parquet-exporter.d.ts +1 -29
- package/dist/parquet-exporter.d.ts.map +1 -1
- package/dist/parquet-exporter.js +59 -81
- package/dist/parquet-exporter.js.map +1 -1
- package/dist/parquet-type-strings.d.ts +11 -0
- package/dist/parquet-type-strings.d.ts.map +1 -0
- package/dist/parquet-type-strings.js +40 -0
- package/dist/parquet-type-strings.js.map +1 -0
- package/dist/parquet-uint32-columns.d.ts +30 -0
- package/dist/parquet-uint32-columns.d.ts.map +1 -0
- package/dist/parquet-uint32-columns.js +38 -0
- package/dist/parquet-uint32-columns.js.map +1 -0
- package/dist/reference-collector.d.ts +0 -31
- package/dist/reference-collector.d.ts.map +1 -1
- package/dist/reference-collector.js +27 -82
- package/dist/reference-collector.js.map +1 -1
- package/dist/related-entities.d.ts.map +1 -1
- package/dist/related-entities.js +19 -4
- package/dist/related-entities.js.map +1 -1
- package/dist/schema-converter-attr-remap.d.ts +53 -0
- package/dist/schema-converter-attr-remap.d.ts.map +1 -0
- package/dist/schema-converter-attr-remap.js +106 -0
- package/dist/schema-converter-attr-remap.js.map +1 -0
- package/dist/schema-converter.d.ts.map +1 -1
- package/dist/schema-converter.js +24 -1
- package/dist/schema-converter.js.map +1 -1
- package/dist/schema-untranslatable.d.ts +35 -0
- package/dist/schema-untranslatable.d.ts.map +1 -0
- package/dist/schema-untranslatable.js +60 -0
- package/dist/schema-untranslatable.js.map +1 -0
- package/dist/step-argument-parser.d.ts.map +1 -1
- package/dist/step-argument-parser.js +11 -1
- package/dist/step-argument-parser.js.map +1 -1
- package/dist/step-attribute-mutations.js +14 -11
- package/dist/step-attribute-mutations.js.map +1 -1
- package/dist/step-collection.d.ts.map +1 -1
- package/dist/step-collection.js +20 -6
- package/dist/step-collection.js.map +1 -1
- package/dist/step-export-types.d.ts +15 -0
- package/dist/step-export-types.d.ts.map +1 -1
- package/dist/step-export-types.js +10 -0
- package/dist/step-export-types.js.map +1 -1
- package/dist/step-exporter.d.ts.map +1 -1
- package/dist/step-exporter.js +9 -1
- package/dist/step-exporter.js.map +1 -1
- package/dist/step-map-unit.d.ts +2 -1
- package/dist/step-map-unit.d.ts.map +1 -1
- package/dist/step-map-unit.js.map +1 -1
- package/dist/step-serialization.d.ts +10 -30
- package/dist/step-serialization.d.ts.map +1 -1
- package/dist/step-serialization.js +10 -60
- package/dist/step-serialization.js.map +1 -1
- package/dist/step-source-iteration.d.ts.map +1 -1
- package/dist/step-source-iteration.js +21 -0
- package/dist/step-source-iteration.js.map +1 -1
- package/dist/style-closure.d.ts +91 -0
- package/dist/style-closure.d.ts.map +1 -0
- package/dist/style-closure.js +164 -0
- package/dist/style-closure.js.map +1 -0
- package/dist/subset-entity-reader.d.ts +10 -9
- package/dist/subset-entity-reader.d.ts.map +1 -1
- package/dist/subset-entity-reader.js +21 -11
- package/dist/subset-entity-reader.js.map +1 -1
- package/dist/subset-roots.d.ts +17 -1
- package/dist/subset-roots.d.ts.map +1 -1
- package/dist/subset-roots.js +67 -2
- package/dist/subset-roots.js.map +1 -1
- package/dist/type-owned-psets.d.ts +19 -0
- package/dist/type-owned-psets.d.ts.map +1 -1
- package/dist/type-owned-psets.js +31 -0
- package/dist/type-owned-psets.js.map +1 -1
- package/dist/unit-normalize.d.ts.map +1 -1
- package/dist/unit-normalize.js +10 -2
- package/dist/unit-normalize.js.map +1 -1
- package/package.json +8 -7
package/dist/merged-exporter.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
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 { generateHeader,
|
|
4
|
+
import { generateHeader, IfcParser, asSourceBytes, } from '@ifc-lite/parser';
|
|
5
5
|
import { decodeIfcString } from '@ifc-lite/encoding';
|
|
6
|
-
import { collectReferencedEntityIds, getVisibleEntityIds,
|
|
6
|
+
import { collectReferencedEntityIds, getVisibleEntityIds, filterHiddenRefsFromRelationshipLine, } from './reference-collector.js';
|
|
7
|
+
import { collectStyleEntities, STYLE_RESCUE_TYPES } from './style-closure.js';
|
|
8
|
+
import { collectGeoreferencingEntities } from './georef-closure.js';
|
|
7
9
|
import { convertStepLine, needsConversion } from './schema-converter.js';
|
|
8
10
|
import { assembleStepBytes, assembleStepBlob } from './step-file-assembly.js';
|
|
9
11
|
import { getCompleteEntityIndex, getMaxExpressId } from './entity-iteration.js';
|
|
10
12
|
import { StepExporter } from './step-exporter.js';
|
|
11
13
|
import { rescaleEntityLengths, computeNormalizeFactor } from './unit-normalize.js';
|
|
14
|
+
import { planInfrastructureUnify, resolvePrimaryContextState } from './merged-context.js';
|
|
15
|
+
import { remapEntityText } from './merged-remap.js';
|
|
16
|
+
import { extractGlobalIdFast, mintUniqueGuid, readLeadingGuid, replaceGlobalId, } from './merged-guid.js';
|
|
17
|
+
import { planEmptyContainerDrops, EMPTY_MODEL_VIEW, } from './merged-empty-containers.js';
|
|
18
|
+
import { skipRedundantRelAggregates, applyRelAggregateStrip, collectRelAggregatePairs } from './merged-rel-aggregates.js';
|
|
12
19
|
/**
|
|
13
20
|
* UTF-8 decode of `[start, end)` of a model's source, accepting either the raw
|
|
14
21
|
* bytes or the {@link IfcSourceBytes} accessor (#2183). Replaces the direct
|
|
@@ -25,38 +32,6 @@ const SHARED_INFRASTRUCTURE_TYPES = new Set([
|
|
|
25
32
|
'IFCGEOMETRICREPRESENTATIONCONTEXT',
|
|
26
33
|
'IFCGEOMETRICREPRESENTATIONSUBCONTEXT',
|
|
27
34
|
]);
|
|
28
|
-
/**
|
|
29
|
-
* An IfcGloballyUniqueId is exactly 22 characters of the buildingSMART base64
|
|
30
|
-
* alphabet. We use this to recognise a rooted entity (IfcRoot subtype) by its
|
|
31
|
-
* first attribute. Geometry/list entities never carry a string there, but some
|
|
32
|
-
* non-rooted RESOURCE entities lead with a Name/Identifier string that can
|
|
33
|
-
* legitimately be 22 charset chars (e.g. a coded property key). Those are
|
|
34
|
-
* excluded with a schema-derived rootedness check ({@link isRootedType}) so
|
|
35
|
-
* their Name is never mistaken for a GlobalId — otherwise the GlobalId
|
|
36
|
-
* reconciliation could drop or rename them.
|
|
37
|
-
*/
|
|
38
|
-
const GLOBAL_ID_RE = /^[0-9A-Za-z_$]{22}$/;
|
|
39
|
-
/**
|
|
40
|
-
* Whether `type` is an IfcRoot subtype — a schema-derived replacement for a
|
|
41
|
-
* hand-maintained denylist of "non-rooted types whose first attribute happens
|
|
42
|
-
* to be a string". A denylist has to be told about every such type by hand and
|
|
43
|
-
* silently under-covers as the schema grows — `IfcMaterialProfileWithOffsets`
|
|
44
|
-
* and several other resource types were missing and got their leading Name
|
|
45
|
-
* treated as a GlobalId, corrupting ordinary model data on a collision.
|
|
46
|
-
*
|
|
47
|
-
* `getInheritanceChainAcrossSchemas` walks the bundled IFC2X3/IFC4/IFC4X3
|
|
48
|
-
* schema union to the entity's root ancestor; a rooted entity's chain always
|
|
49
|
-
* ends in `IfcRoot`. This mirrors the Rust side's `IfcType::is_subtype_of`
|
|
50
|
-
* schema check, so the two implementations of "is this rooted" agree instead
|
|
51
|
-
* of drifting apart as separate hand-maintained lists.
|
|
52
|
-
*
|
|
53
|
-
* A type unknown to every bundled schema (typo, vendor extension) yields an
|
|
54
|
-
* empty chain and is treated as non-rooted — the same safe-miss direction the
|
|
55
|
-
* old denylist documented: it just skips one GlobalId reconciliation.
|
|
56
|
-
*/
|
|
57
|
-
export function isRootedType(type) {
|
|
58
|
-
return getInheritanceChainAcrossSchemas(type).includes('IfcRoot');
|
|
59
|
-
}
|
|
60
35
|
/** True for IfcRelationship subtypes (objectified relationships). */
|
|
61
36
|
function isRelationshipType(typeUpper) {
|
|
62
37
|
return typeUpper.startsWith('IFCREL');
|
|
@@ -163,6 +138,7 @@ export class MergedExporter {
|
|
|
163
138
|
}
|
|
164
139
|
const models = withUsableSource(this.models);
|
|
165
140
|
const setup = this.buildMergeSetup(options, models);
|
|
141
|
+
const containerDrops = this.planContainerDrops(options, models, setup);
|
|
166
142
|
const allEntityLines = [];
|
|
167
143
|
// Tracks every GlobalId already emitted → its final express id + unit scale,
|
|
168
144
|
// so later models can unify against (shared unit) or stay unique from
|
|
@@ -189,13 +165,14 @@ export class MergedExporter {
|
|
|
189
165
|
this.collectNormalizeCaveats(model, normalizeWarnings);
|
|
190
166
|
}
|
|
191
167
|
const plan = this.planModel(model, completeIndex, isFirstModel, mode.compatible, mode.lengthFactor, setup, guidToFinalId);
|
|
168
|
+
this.applyContainerDrops(plan, containerDrops?.byModel.get(model.id));
|
|
192
169
|
const sourceSchema = model.dataStore.schemaVersion || 'IFC4';
|
|
193
170
|
for (const [expressId, entityRef] of completeIndex) {
|
|
194
171
|
if (visibility !== null && !visibility.included.has(expressId))
|
|
195
172
|
continue;
|
|
196
173
|
if (plan.skipEntityIds.has(expressId))
|
|
197
174
|
continue;
|
|
198
|
-
const line = this.renderEntity(expressId, entityRef, source, offset, plan, sourceSchema, schema, guidToFinalId, mode, visibility?.hiddenProductIds ?? null, completeIndex);
|
|
175
|
+
const line = this.renderEntity(expressId, entityRef, source, offset, plan, sourceSchema, schema, guidToFinalId, mode, visibility?.hiddenProductIds ?? null, completeIndex, visibility?.included ?? null);
|
|
199
176
|
if (line !== null)
|
|
200
177
|
allEntityLines.push(line);
|
|
201
178
|
}
|
|
@@ -207,7 +184,7 @@ export class MergedExporter {
|
|
|
207
184
|
const content = assembleStepBytes(header, allEntityLines);
|
|
208
185
|
return {
|
|
209
186
|
content,
|
|
210
|
-
stats: this.buildStats(allEntityLines.length, content.byteLength, federatedModelCount, normalizedModelCount, normalizeWarnings),
|
|
187
|
+
stats: this.buildStats(allEntityLines.length, content.byteLength, federatedModelCount, normalizedModelCount, normalizeWarnings, containerDrops?.count ?? 0),
|
|
211
188
|
};
|
|
212
189
|
}
|
|
213
190
|
/**
|
|
@@ -223,7 +200,7 @@ export class MergedExporter {
|
|
|
223
200
|
}
|
|
224
201
|
return {
|
|
225
202
|
content,
|
|
226
|
-
stats: this.buildStats(doc.allEntityLines.length, content.byteLength, doc.federatedModelCount, doc.normalizedModelCount, doc.normalizeWarnings),
|
|
203
|
+
stats: this.buildStats(doc.allEntityLines.length, content.byteLength, doc.federatedModelCount, doc.normalizedModelCount, doc.normalizeWarnings, doc.droppedContainerCount),
|
|
227
204
|
};
|
|
228
205
|
}
|
|
229
206
|
/**
|
|
@@ -244,7 +221,7 @@ export class MergedExporter {
|
|
|
244
221
|
}
|
|
245
222
|
return {
|
|
246
223
|
content,
|
|
247
|
-
stats: this.buildStats(doc.allEntityLines.length, content.size, doc.federatedModelCount, doc.normalizedModelCount, doc.normalizeWarnings),
|
|
224
|
+
stats: this.buildStats(doc.allEntityLines.length, content.size, doc.federatedModelCount, doc.normalizedModelCount, doc.normalizeWarnings, doc.droppedContainerCount),
|
|
248
225
|
};
|
|
249
226
|
}
|
|
250
227
|
/**
|
|
@@ -267,6 +244,7 @@ export class MergedExporter {
|
|
|
267
244
|
// without edits pass through unchanged (no export/parse cost).
|
|
268
245
|
const models = withUsableSource(await this.bakeMutatedModels());
|
|
269
246
|
const setup = this.buildMergeSetup(options, models);
|
|
247
|
+
const containerDrops = this.planContainerDrops(options, models, setup);
|
|
270
248
|
const allEntityLines = [];
|
|
271
249
|
const guidToFinalId = new Map();
|
|
272
250
|
// First pass: count total entities for progress
|
|
@@ -306,6 +284,7 @@ export class MergedExporter {
|
|
|
306
284
|
this.collectNormalizeCaveats(model, normalizeWarnings);
|
|
307
285
|
}
|
|
308
286
|
const plan = this.planModel(model, completeIndex, isFirstModel, mode.compatible, mode.lengthFactor, setup, guidToFinalId);
|
|
287
|
+
this.applyContainerDrops(plan, containerDrops?.byModel.get(model.id));
|
|
309
288
|
const sourceSchema = model.dataStore.schemaVersion || 'IFC4';
|
|
310
289
|
let entityCount = 0;
|
|
311
290
|
for (const [expressId, entityRef] of completeIndex) {
|
|
@@ -313,7 +292,7 @@ export class MergedExporter {
|
|
|
313
292
|
continue;
|
|
314
293
|
if (plan.skipEntityIds.has(expressId))
|
|
315
294
|
continue;
|
|
316
|
-
const line = this.renderEntity(expressId, entityRef, source, offset, plan, sourceSchema, schema, guidToFinalId, mode, visibility?.hiddenProductIds ?? null, completeIndex);
|
|
295
|
+
const line = this.renderEntity(expressId, entityRef, source, offset, plan, sourceSchema, schema, guidToFinalId, mode, visibility?.hiddenProductIds ?? null, completeIndex, visibility?.included ?? null);
|
|
317
296
|
if (line !== null)
|
|
318
297
|
allEntityLines.push(line);
|
|
319
298
|
entityCount++;
|
|
@@ -339,7 +318,7 @@ export class MergedExporter {
|
|
|
339
318
|
onProgress({ phase: 'assembling', percent: 0.9, entitiesProcessed: totalEntities, entitiesTotal: totalEntities });
|
|
340
319
|
}
|
|
341
320
|
await new Promise(r => setTimeout(r, 0));
|
|
342
|
-
return { header, allEntityLines, federatedModelCount, normalizedModelCount, normalizeWarnings, totalEntities };
|
|
321
|
+
return { header, allEntityLines, federatedModelCount, normalizedModelCount, normalizeWarnings, droppedContainerCount: containerDrops?.count ?? 0, totalEntities };
|
|
343
322
|
}
|
|
344
323
|
/**
|
|
345
324
|
* Bake each model's pending edits into its source bytes before merging.
|
|
@@ -387,7 +366,7 @@ export class MergedExporter {
|
|
|
387
366
|
/**
|
|
388
367
|
* Assemble the result stats, including any federation conformance warnings.
|
|
389
368
|
*/
|
|
390
|
-
buildStats(totalEntityCount, fileSize, federatedModelCount, normalizedModelCount, normalizeWarnings) {
|
|
369
|
+
buildStats(totalEntityCount, fileSize, federatedModelCount, normalizedModelCount, normalizeWarnings, droppedContainerCount) {
|
|
391
370
|
const warnings = [];
|
|
392
371
|
if (federatedModelCount > 0) {
|
|
393
372
|
warnings.push(`${federatedModelCount} model(s) had a length unit differing from the first model and were ` +
|
|
@@ -398,7 +377,50 @@ export class MergedExporter {
|
|
|
398
377
|
`single-unit project, or 'assume-shared' when units are already normalised.`);
|
|
399
378
|
}
|
|
400
379
|
warnings.push(...normalizeWarnings);
|
|
401
|
-
return { modelCount: this.models.length, totalEntityCount, fileSize, federatedModelCount, normalizedModelCount, warnings };
|
|
380
|
+
return { modelCount: this.models.length, totalEntityCount, fileSize, federatedModelCount, normalizedModelCount, droppedContainerCount, warnings };
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Plan the empty spatial containers of the whole merge (#3643), or `null` when
|
|
384
|
+
* the caller did not ask for the drop. Reproduces the same spatial unification
|
|
385
|
+
* {@link planModel} will, so emptiness is judged on the containers the merge
|
|
386
|
+
* actually keeps rather than on each file in isolation.
|
|
387
|
+
*/
|
|
388
|
+
planContainerDrops(options, models, setup) {
|
|
389
|
+
if (!options.dropEmptyContainers)
|
|
390
|
+
return null;
|
|
391
|
+
const views = models.map((model, index) => {
|
|
392
|
+
const source = model.dataStore.source;
|
|
393
|
+
if (!source || source.length === 0)
|
|
394
|
+
return EMPTY_MODEL_VIEW;
|
|
395
|
+
const entities = getCompleteEntityIndex(model.dataStore);
|
|
396
|
+
const mode = this.resolveModelMode(model, index === 0, setup);
|
|
397
|
+
const sharedRemap = new Map();
|
|
398
|
+
if (index > 0 && mode.compatible) {
|
|
399
|
+
this.unifySpatialEntities(model.dataStore, setup.spatialLookup, setup.firstModelOffset, mode.lengthFactor, sharedRemap, new Set(), setup);
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
entities,
|
|
403
|
+
source: asSourceBytes(source),
|
|
404
|
+
included: this.computeIncludedEntityIds(model, options, entities, source)?.included ?? null,
|
|
405
|
+
sharedRemap,
|
|
406
|
+
offset: setup.modelOffsets.get(model.id),
|
|
407
|
+
compatible: mode.compatible,
|
|
408
|
+
};
|
|
409
|
+
});
|
|
410
|
+
const plan = planEmptyContainerDrops(views);
|
|
411
|
+
return {
|
|
412
|
+
byModel: new Map(models.map((model, index) => [model.id, plan.droppedByModel[index]])),
|
|
413
|
+
count: plan.droppedCount,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/** Fold a model's dropped containers into its plan: the container lines are
|
|
417
|
+
* skipped outright, and {@link renderEntity} narrows every line naming one. */
|
|
418
|
+
applyContainerDrops(plan, dropped) {
|
|
419
|
+
if (dropped === undefined || dropped.size === 0)
|
|
420
|
+
return;
|
|
421
|
+
plan.droppedContainerIds = dropped;
|
|
422
|
+
for (const id of dropped)
|
|
423
|
+
plan.skipEntityIds.add(id);
|
|
402
424
|
}
|
|
403
425
|
/**
|
|
404
426
|
* Record advisories for a model being normalized. The rescaler derives its
|
|
@@ -451,12 +473,16 @@ export class MergedExporter {
|
|
|
451
473
|
}
|
|
452
474
|
const firstModel = models[0];
|
|
453
475
|
const primaryScale = this.resolveUnitScale(firstModel);
|
|
476
|
+
const firstModelOffset = modelOffsets.get(firstModel.id);
|
|
477
|
+
const firstModelInfraMap = this.findInfrastructureEntities(firstModel.dataStore);
|
|
454
478
|
return {
|
|
455
479
|
modelOffsets,
|
|
456
|
-
firstModelOffset
|
|
457
|
-
firstModelInfraMap
|
|
480
|
+
firstModelOffset,
|
|
481
|
+
firstModelInfraMap,
|
|
482
|
+
firstModelContext: resolvePrimaryContextState(firstModel.dataStore, firstModelInfraMap.get('IFCGEOMETRICREPRESENTATIONSUBCONTEXT') ?? [], primaryScale),
|
|
458
483
|
firstProjectIds: this.findEntitiesByType(firstModel.dataStore, 'IFCPROJECT'),
|
|
459
484
|
spatialLookup: this.buildSpatialLookup(firstModel.dataStore),
|
|
485
|
+
primaryAggregatePairs: collectRelAggregatePairs(firstModel.dataStore, this.findEntitiesByType.bind(this), this.extractStepAttribute.bind(this), firstModelOffset),
|
|
460
486
|
primaryScale,
|
|
461
487
|
primaryAreaScale: this.resolveDerivedUnitScale(firstModel.dataStore, 'AREAUNIT', primaryScale, 2),
|
|
462
488
|
primaryVolumeScale: this.resolveDerivedUnitScale(firstModel.dataStore, 'VOLUMEUNIT', primaryScale, 3),
|
|
@@ -511,8 +537,8 @@ export class MergedExporter {
|
|
|
511
537
|
* Resolve a model's declared AREAUNIT / VOLUMEUNIT scale (SI m² / m³ per unit)
|
|
512
538
|
* by walking IfcProject → IfcUnitAssignment. Falls back to the length-derived
|
|
513
539
|
* unit (`lengthScale ** power`) when the model declares no explicit area/volume
|
|
514
|
-
* unit — the IFC default. A prefixed SI area/volume unit (rare)
|
|
515
|
-
* prefix
|
|
540
|
+
* unit — the IFC default. A prefixed SI area/volume unit (rare) raises the
|
|
541
|
+
* prefix to `power` (area = prefix², volume = prefix³), matching `rust/core`.
|
|
516
542
|
*/
|
|
517
543
|
resolveDerivedUnitScale(dataStore, wantType, lengthScale, power) {
|
|
518
544
|
const fallback = Math.pow(lengthScale, power);
|
|
@@ -539,8 +565,8 @@ export class MergedExporter {
|
|
|
539
565
|
const prefixRaw = this.extractStepAttribute(uid, dataStore, 2);
|
|
540
566
|
if (!prefixRaw || prefixRaw === '$' || prefixRaw === '*')
|
|
541
567
|
return 1.0; // square/cubic metre
|
|
542
|
-
const mult = SI_PREFIX_MULTIPLIERS[this.normalizeEnum(prefixRaw)];
|
|
543
|
-
return mult !== undefined ? mult : 1.0;
|
|
568
|
+
const mult = SI_PREFIX_MULTIPLIERS[this.normalizeEnum(prefixRaw)]; // see doc: raised to `power`
|
|
569
|
+
return mult !== undefined ? Math.pow(mult, power) : 1.0;
|
|
544
570
|
}
|
|
545
571
|
if (utype === 'IFCCONVERSIONBASEDUNIT') {
|
|
546
572
|
// IfcConversionBasedUnit: [1] UnitType, [2] Name, [3] ConversionFactor.
|
|
@@ -614,11 +640,13 @@ export class MergedExporter {
|
|
|
614
640
|
const isolatedIds = options.isolatedEntityIdsByModel?.get(model.id) ?? null;
|
|
615
641
|
const { roots, hiddenProductIds } = getVisibleEntityIds(model.dataStore, hiddenIds, isolatedIds);
|
|
616
642
|
const included = collectReferencedEntityIds(roots, source, completeIndex, hiddenProductIds);
|
|
617
|
-
// Second pass:
|
|
643
|
+
// Second pass: style entities referencing included geometry (see style-closure.ts).
|
|
618
644
|
collectStyleEntities(included, source, {
|
|
619
645
|
byId: completeIndex,
|
|
620
646
|
byType: model.dataStore.entityIndex.byType,
|
|
621
|
-
});
|
|
647
|
+
}, hiddenProductIds);
|
|
648
|
+
// Third pass: rescue IFCMAPCONVERSION/IFCPROJECTEDCRS — see georef-closure.ts.
|
|
649
|
+
collectGeoreferencingEntities(included, source, { byId: completeIndex, byType: model.dataStore.entityIndex.byType }, hiddenProductIds);
|
|
622
650
|
return { included, hiddenProductIds };
|
|
623
651
|
}
|
|
624
652
|
/**
|
|
@@ -641,10 +669,11 @@ export class MergedExporter {
|
|
|
641
669
|
const sharedRemap = new Map();
|
|
642
670
|
const skipEntityIds = new Set();
|
|
643
671
|
const guidRewrite = new Map();
|
|
672
|
+
const relAggregateStrip = new Map();
|
|
644
673
|
// One cheap pass to read each rooted entity's GlobalId (first attribute).
|
|
645
674
|
const localGuids = new Map();
|
|
646
675
|
for (const [id, ref] of completeIndex) {
|
|
647
|
-
const guid =
|
|
676
|
+
const guid = extractGlobalIdFast(ref, source);
|
|
648
677
|
if (guid !== null)
|
|
649
678
|
localGuids.set(id, guid);
|
|
650
679
|
}
|
|
@@ -657,21 +686,15 @@ export class MergedExporter {
|
|
|
657
686
|
skipEntityIds.add(pid);
|
|
658
687
|
}
|
|
659
688
|
}
|
|
660
|
-
// Remap and skip duplicate infrastructure (units, contexts).
|
|
661
|
-
|
|
662
|
-
for (const [type, firstIds] of setup.firstModelInfraMap) {
|
|
663
|
-
const thisIds = modelInfra.get(type);
|
|
664
|
-
if (thisIds && firstIds.length > 0 && thisIds.length > 0) {
|
|
665
|
-
sharedRemap.set(thisIds[0], firstIds[0] + setup.firstModelOffset);
|
|
666
|
-
skipEntityIds.add(thisIds[0]);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
689
|
+
// Remap and skip duplicate infrastructure (units, contexts) — WCS-gated; scale choice documented on planInfrastructureUnify (merged-context.ts).
|
|
690
|
+
planInfrastructureUnify(model.dataStore, this.findInfrastructureEntities(model.dataStore), setup.firstModelInfraMap, setup.firstModelContext, setup.firstModelOffset, setup.assumeShared ? setup.primaryScale : this.resolveUnitScale(model), sharedRemap, skipEntityIds);
|
|
669
691
|
// Unify spatial hierarchy: match Site, Building, Storey to first model.
|
|
670
692
|
// Under normalize, this model's raw elevations are in its own unit, so the
|
|
671
693
|
// elevation match is done in the primary unit (rawElevation * lengthFactor).
|
|
672
694
|
this.unifySpatialEntities(model.dataStore, setup.spatialLookup, setup.firstModelOffset, lengthFactor, sharedRemap, skipEntityIds, setup);
|
|
673
|
-
// Skip IfcRelAggregates that become fully redundant after unification
|
|
674
|
-
|
|
695
|
+
// Skip IfcRelAggregates that become fully redundant after unification,
|
|
696
|
+
// and strip individually-duplicated members from ones only partially so.
|
|
697
|
+
skipRedundantRelAggregates(model.dataStore, sharedRemap, skipEntityIds, relAggregateStrip, setup.primaryAggregatePairs, this.findEntitiesByType.bind(this), this.extractStepAttribute.bind(this));
|
|
675
698
|
}
|
|
676
699
|
if (!isFirstModel) {
|
|
677
700
|
// GlobalId reconciliation against everything emitted by earlier models.
|
|
@@ -699,11 +722,11 @@ export class MergedExporter {
|
|
|
699
722
|
skipEntityIds.add(id);
|
|
700
723
|
}
|
|
701
724
|
else {
|
|
702
|
-
guidRewrite.set(id,
|
|
725
|
+
guidRewrite.set(id, mintUniqueGuid(guid, model.id, guidToFinalId, pendingMinted));
|
|
703
726
|
}
|
|
704
727
|
}
|
|
705
728
|
}
|
|
706
|
-
return { sharedRemap, skipEntityIds, guidRewrite, localGuids };
|
|
729
|
+
return { sharedRemap, skipEntityIds, guidRewrite, localGuids, relAggregateStrip };
|
|
707
730
|
}
|
|
708
731
|
/**
|
|
709
732
|
* Render one source entity into its final STEP line: apply id offset + shared
|
|
@@ -713,43 +736,69 @@ export class MergedExporter {
|
|
|
713
736
|
* `hiddenProductIds` withholds a relationship whose every named subject was
|
|
714
737
|
* hidden (below).
|
|
715
738
|
*/
|
|
716
|
-
renderEntity(localId, entityRef, source, offset, plan, sourceSchema, targetSchema, guidToFinalId, mode, hiddenProductIds, completeIndex) {
|
|
739
|
+
renderEntity(localId, entityRef, source, offset, plan, sourceSchema, targetSchema, guidToFinalId, mode, hiddenProductIds, completeIndex, includedIds) {
|
|
717
740
|
let entityText = decodeRange(source, entityRef.byteOffset, entityRef.byteOffset + entityRef.byteLength);
|
|
718
741
|
// A `visibleOnly` export must narrow — or entirely withhold — a
|
|
719
|
-
// relationship's
|
|
720
|
-
// (`isOmittedFromOutput`,
|
|
721
|
-
// `filterHiddenRefsFromRelationshipLine` call sites
|
|
722
|
-
//
|
|
723
|
-
//
|
|
724
|
-
// an unreadable source ref and a geometry exclusion, which this one — a
|
|
725
|
-
// hidden product or an id absent from the complete index — does not.
|
|
742
|
+
// relationship's (and, below, a rescued style/layer entity's) own OUTPUT
|
|
743
|
+
// line the same way `StepExporter` does (`isOmittedFromOutput`, its two
|
|
744
|
+
// `filterHiddenRefsFromRelationshipLine` call sites). NOTE the predicates
|
|
745
|
+
// are not identical: `StepExporter`'s also answers for an unreadable
|
|
746
|
+
// source ref and a geometry exclusion, which these do not.
|
|
726
747
|
// `collectReferencedEntityIds` already refuses to WALK INTO a relationship
|
|
727
748
|
// whose sole subject is hidden (#2548), but a root's own bytes are still
|
|
728
749
|
// copied to the output verbatim unless narrowed here too — without this,
|
|
729
|
-
// a hidden id
|
|
730
|
-
//
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
750
|
+
// a hidden id survives as a dangling `#N` with no `#N=` line (#2398).
|
|
751
|
+
// Runs in LOCAL id space, before the remap below.
|
|
752
|
+
if (hiddenProductIds !== null) {
|
|
753
|
+
const entityTypeUpper = entityRef.type.toUpperCase();
|
|
754
|
+
// Same narrowing for a rescued style/layer entity (`STYLE_RESCUE_TYPES`,
|
|
755
|
+
// `style-closure.ts`): its line can name a kept AND an excluded id (a
|
|
756
|
+
// shared CAD layer) — `hiddenProductIds` misses that id (exclusively-
|
|
757
|
+
// owned geometry, not a hidden PRODUCT), so this arm uses `includedIds`.
|
|
758
|
+
const isExcluded = isRelationshipType(entityTypeUpper)
|
|
759
|
+
? (id) => hiddenProductIds.has(id) || !completeIndex.has(id)
|
|
760
|
+
: STYLE_RESCUE_TYPES.has(entityTypeUpper) && includedIds !== null
|
|
761
|
+
? (id) => !includedIds.has(id) || !completeIndex.has(id)
|
|
762
|
+
: null;
|
|
763
|
+
if (isExcluded) {
|
|
764
|
+
const filtered = filterHiddenRefsFromRelationshipLine(entityText, isExcluded);
|
|
765
|
+
if (filtered === null)
|
|
766
|
+
return null;
|
|
767
|
+
entityText = filtered;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
// A dropped empty spatial container (#3643) is never written, so any line
|
|
771
|
+
// naming one is narrowed the same way — or withheld with it. Runs in LOCAL
|
|
772
|
+
// id space, before the remap below, like the visibility narrowing above.
|
|
773
|
+
if (plan.droppedContainerIds !== undefined) {
|
|
774
|
+
const kept = filterHiddenRefsFromRelationshipLine(entityText, id => plan.droppedContainerIds.has(id));
|
|
775
|
+
if (kept === null)
|
|
738
776
|
return null;
|
|
739
|
-
entityText =
|
|
777
|
+
entityText = kept;
|
|
740
778
|
}
|
|
779
|
+
// Drop RelatedObjects members a partially redundant IFCRELAGGREGATES
|
|
780
|
+
// already shares with the first model's OWN relationship to the same
|
|
781
|
+
// (now-unified) RelatingObject — see skipRedundantRelAggregates /
|
|
782
|
+
// applyRelAggregateStrip (merged-rel-aggregates.ts). Runs in LOCAL id
|
|
783
|
+
// space, before the remap below. `null` (the filter would withhold the
|
|
784
|
+
// whole line) propagates like the two passes above: every edge the line
|
|
785
|
+
// declared already exists in the primary model.
|
|
786
|
+
const stripped = applyRelAggregateStrip(entityText, localId, plan.relAggregateStrip);
|
|
787
|
+
if (stripped === null)
|
|
788
|
+
return null;
|
|
789
|
+
entityText = stripped;
|
|
741
790
|
// Remap ids. Fast path: the first model (offset 0, no remaps) is byte-identical.
|
|
742
791
|
let finalText;
|
|
743
792
|
if (offset === 0 && plan.sharedRemap.size === 0) {
|
|
744
793
|
finalText = entityText;
|
|
745
794
|
}
|
|
746
795
|
else {
|
|
747
|
-
finalText =
|
|
796
|
+
finalText = remapEntityText(entityText, offset, plan.sharedRemap);
|
|
748
797
|
}
|
|
749
798
|
// Re-stamp the GlobalId for a federated entity whose id collides.
|
|
750
799
|
const mintedGuid = plan.guidRewrite.get(localId);
|
|
751
800
|
if (mintedGuid !== undefined) {
|
|
752
|
-
finalText =
|
|
801
|
+
finalText = replaceGlobalId(finalText, mintedGuid);
|
|
753
802
|
}
|
|
754
803
|
// Normalize units: rescale every length/area/volume-valued datum into the
|
|
755
804
|
// primary unit. Done on the SOURCE-schema text (entityRef.type), before any
|
|
@@ -771,7 +820,7 @@ export class MergedExporter {
|
|
|
771
820
|
// Emitted entities are not sharedRemap keys, so their final id is
|
|
772
821
|
// localId + offset.
|
|
773
822
|
if (plan.localGuids.has(localId)) {
|
|
774
|
-
const emittedGuid =
|
|
823
|
+
const emittedGuid = readLeadingGuid(finalText)
|
|
775
824
|
?? mintedGuid ?? plan.localGuids.get(localId);
|
|
776
825
|
if (emittedGuid !== undefined) {
|
|
777
826
|
guidToFinalId.set(emittedGuid, { finalId: localId + offset, scale: mode.effectiveScale });
|
|
@@ -779,142 +828,6 @@ export class MergedExporter {
|
|
|
779
828
|
}
|
|
780
829
|
return finalText;
|
|
781
830
|
}
|
|
782
|
-
/**
|
|
783
|
-
* Read the GlobalId (first quoted attribute) from an already-rendered STEP
|
|
784
|
-
* line. Used to register the id that was actually emitted, after any id
|
|
785
|
-
* remap, GlobalId re-stamp, or schema conversion. Returns null if the first
|
|
786
|
-
* quoted token is not a 22-char GlobalId.
|
|
787
|
-
*/
|
|
788
|
-
readLeadingGuid(entityText) {
|
|
789
|
-
const open = entityText.indexOf('(');
|
|
790
|
-
if (open === -1)
|
|
791
|
-
return null;
|
|
792
|
-
const q1 = entityText.indexOf("'", open + 1);
|
|
793
|
-
if (q1 === -1)
|
|
794
|
-
return null;
|
|
795
|
-
const q2 = entityText.indexOf("'", q1 + 1);
|
|
796
|
-
if (q2 === -1)
|
|
797
|
-
return null;
|
|
798
|
-
const raw = entityText.slice(q1 + 1, q2);
|
|
799
|
-
return GLOBAL_ID_RE.test(raw) ? raw : null;
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* Mint a fresh, deterministic, collision-free GlobalId for an entity whose id
|
|
803
|
-
* collides. Seeded from the original GlobalId and the model's stable id so the
|
|
804
|
-
* output is reproducible and does not churn when an unrelated earlier model
|
|
805
|
-
* changes size; checked against both already-emitted ids and the ids minted
|
|
806
|
-
* so far for this model.
|
|
807
|
-
*/
|
|
808
|
-
mintUniqueGuid(original, modelId, guidToFinalId, pendingMinted) {
|
|
809
|
-
let candidate = deterministicGlobalId(`${original}#${modelId}`);
|
|
810
|
-
let n = 0;
|
|
811
|
-
while (guidToFinalId.has(candidate) || pendingMinted.has(candidate)) {
|
|
812
|
-
candidate = deterministicGlobalId(`${original}#${modelId}#${n++}`);
|
|
813
|
-
}
|
|
814
|
-
pendingMinted.add(candidate);
|
|
815
|
-
return candidate;
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* Read an entity's GlobalId (first attribute) by decoding only its head.
|
|
819
|
-
* Returns the 22-char id for a rooted entity, or `null` for any entity whose
|
|
820
|
-
* first attribute is not a GlobalId (geometry, lists, property atoms, …).
|
|
821
|
-
*/
|
|
822
|
-
extractGlobalIdFast(ref, source) {
|
|
823
|
-
// Non-rooted resource entities (property/quantity/material/style/actor …)
|
|
824
|
-
// lead with a Name string that can itself be 22 charset chars; never treat
|
|
825
|
-
// those as a GlobalId or reconciliation would drop/rename them.
|
|
826
|
-
if (!isRootedType(ref.type ?? ''))
|
|
827
|
-
return null;
|
|
828
|
-
// 128 bytes comfortably spans `#<id>=<LONGEST_TYPE_NAME>('<22-char id>'`,
|
|
829
|
-
// so the GlobalId is always fully inside the window.
|
|
830
|
-
const end = Math.min(ref.byteOffset + 128, ref.byteOffset + ref.byteLength);
|
|
831
|
-
const head = decodeRange(source, ref.byteOffset, end);
|
|
832
|
-
const open = head.indexOf('(');
|
|
833
|
-
if (open === -1)
|
|
834
|
-
return null;
|
|
835
|
-
let i = open + 1;
|
|
836
|
-
while (i < head.length && (head[i] === ' ' || head[i] === '\t' || head[i] === '\n' || head[i] === '\r'))
|
|
837
|
-
i++;
|
|
838
|
-
if (head[i] !== "'")
|
|
839
|
-
return null;
|
|
840
|
-
// A GlobalId never contains a quote (charset excludes it), so the next
|
|
841
|
-
// quote closes it.
|
|
842
|
-
const close = head.indexOf("'", i + 1);
|
|
843
|
-
if (close === -1)
|
|
844
|
-
return null;
|
|
845
|
-
const raw = head.slice(i + 1, close);
|
|
846
|
-
return GLOBAL_ID_RE.test(raw) ? raw : null;
|
|
847
|
-
}
|
|
848
|
-
/**
|
|
849
|
-
* Replace an entity's GlobalId (first quoted attribute) with `newGuid`.
|
|
850
|
-
* `newGuid` is a 22-char IFC id (no quote in its charset), so this is safe.
|
|
851
|
-
*/
|
|
852
|
-
replaceGlobalId(entityText, newGuid) {
|
|
853
|
-
const open = entityText.indexOf('(');
|
|
854
|
-
if (open === -1)
|
|
855
|
-
return entityText;
|
|
856
|
-
const q1 = entityText.indexOf("'", open + 1);
|
|
857
|
-
if (q1 === -1)
|
|
858
|
-
return entityText;
|
|
859
|
-
const q2 = entityText.indexOf("'", q1 + 1);
|
|
860
|
-
if (q2 === -1)
|
|
861
|
-
return entityText;
|
|
862
|
-
return entityText.slice(0, q1 + 1) + newGuid + entityText.slice(q2);
|
|
863
|
-
}
|
|
864
|
-
/**
|
|
865
|
-
* Remap all #ID references in a STEP entity line.
|
|
866
|
-
* Applies offset to all IDs, then overrides with specific remappings.
|
|
867
|
-
*
|
|
868
|
-
* Only `#<digits>` tokens in code positions are rewritten; tokens inside
|
|
869
|
-
* single-quoted STEP strings (e.g. a 'Room #205' Name or a 'http://x#42'
|
|
870
|
-
* URL) are left untouched so string attribute values are not corrupted.
|
|
871
|
-
*/
|
|
872
|
-
remapEntityText(entityText, offset, sharedRemap) {
|
|
873
|
-
const remapId = (originalId) => {
|
|
874
|
-
// Check if this ID has a specific remap (project, shared infrastructure)
|
|
875
|
-
const remapped = sharedRemap.get(originalId);
|
|
876
|
-
if (remapped !== undefined) {
|
|
877
|
-
return `#${remapped}`;
|
|
878
|
-
}
|
|
879
|
-
// Apply offset
|
|
880
|
-
return `#${originalId + offset}`;
|
|
881
|
-
};
|
|
882
|
-
let out = '';
|
|
883
|
-
let inString = false;
|
|
884
|
-
for (let i = 0; i < entityText.length; i++) {
|
|
885
|
-
const char = entityText[i];
|
|
886
|
-
if (inString) {
|
|
887
|
-
out += char;
|
|
888
|
-
if (char === "'") {
|
|
889
|
-
// STEP escapes a literal quote by doubling it ('').
|
|
890
|
-
if (entityText[i + 1] === "'") {
|
|
891
|
-
out += entityText[i + 1];
|
|
892
|
-
i++;
|
|
893
|
-
}
|
|
894
|
-
else {
|
|
895
|
-
inString = false;
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
continue;
|
|
899
|
-
}
|
|
900
|
-
if (char === "'") {
|
|
901
|
-
inString = true;
|
|
902
|
-
out += char;
|
|
903
|
-
continue;
|
|
904
|
-
}
|
|
905
|
-
if (char === '#' && entityText[i + 1] >= '0' && entityText[i + 1] <= '9') {
|
|
906
|
-
let j = i + 1;
|
|
907
|
-
while (j < entityText.length && entityText[j] >= '0' && entityText[j] <= '9')
|
|
908
|
-
j++;
|
|
909
|
-
const originalId = parseInt(entityText.slice(i + 1, j), 10);
|
|
910
|
-
out += remapId(originalId);
|
|
911
|
-
i = j - 1;
|
|
912
|
-
continue;
|
|
913
|
-
}
|
|
914
|
-
out += char;
|
|
915
|
-
}
|
|
916
|
-
return out;
|
|
917
|
-
}
|
|
918
831
|
/**
|
|
919
832
|
* Find entity IDs of shared infrastructure types in a data store.
|
|
920
833
|
* Returns a map of uppercase type name → array of expressIds.
|
|
@@ -1083,43 +996,6 @@ export class MergedExporter {
|
|
|
1083
996
|
return byName();
|
|
1084
997
|
return byName() ?? bySingle();
|
|
1085
998
|
}
|
|
1086
|
-
/**
|
|
1087
|
-
* Skip IfcRelAggregates that become fully redundant after spatial unification.
|
|
1088
|
-
*
|
|
1089
|
-
* When Model2's `IfcRelAggregates(Project, (Site))` gets remapped to
|
|
1090
|
-
* `IfcRelAggregates(FirstProject, (FirstSite))`, it duplicates Model1's
|
|
1091
|
-
* existing relationship, causing viewers to show Site multiple times.
|
|
1092
|
-
*
|
|
1093
|
-
* An IfcRelAggregates is redundant if both its RelatingObject (attr 4)
|
|
1094
|
-
* and ALL its RelatedObjects (attr 5) were remapped via sharedRemap.
|
|
1095
|
-
*/
|
|
1096
|
-
skipRedundantRelAggregates(dataStore, sharedRemap, skipEntityIds) {
|
|
1097
|
-
for (const relId of this.findEntitiesByType(dataStore, 'IFCRELAGGREGATES')) {
|
|
1098
|
-
// RelatingObject is attr 4 — single #ref
|
|
1099
|
-
const relatingAttr = this.extractStepAttribute(relId, dataStore, 4);
|
|
1100
|
-
if (!relatingAttr)
|
|
1101
|
-
continue;
|
|
1102
|
-
const relatingRef = relatingAttr.match(/^#(\d+)$/);
|
|
1103
|
-
if (!relatingRef || !sharedRemap.has(parseInt(relatingRef[1], 10)))
|
|
1104
|
-
continue;
|
|
1105
|
-
// RelatedObjects is attr 5 — list of #refs like (#2,#3)
|
|
1106
|
-
const relatedAttr = this.extractStepAttribute(relId, dataStore, 5);
|
|
1107
|
-
if (!relatedAttr)
|
|
1108
|
-
continue;
|
|
1109
|
-
const refs = [];
|
|
1110
|
-
const refRegex = /#(\d+)/g;
|
|
1111
|
-
let m;
|
|
1112
|
-
while ((m = refRegex.exec(relatedAttr)) !== null) {
|
|
1113
|
-
refs.push(parseInt(m[1], 10));
|
|
1114
|
-
}
|
|
1115
|
-
if (refs.length === 0)
|
|
1116
|
-
continue;
|
|
1117
|
-
// If ALL related objects were also remapped, this rel is fully redundant
|
|
1118
|
-
if (refs.every(ref => sharedRemap.has(ref))) {
|
|
1119
|
-
skipEntityIds.add(relId);
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
999
|
/**
|
|
1124
1000
|
* Extract the Name attribute (index 2) from a STEP entity.
|
|
1125
1001
|
*/
|