@ifc-lite/export 3.0.1 → 4.0.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 +37 -1
- package/dist/anonymize-export.d.ts +60 -0
- package/dist/anonymize-export.d.ts.map +1 -0
- package/dist/anonymize-export.js +217 -0
- package/dist/anonymize-export.js.map +1 -0
- package/dist/anonymize-placement.d.ts +71 -0
- package/dist/anonymize-placement.d.ts.map +1 -0
- package/dist/anonymize-placement.js +266 -0
- package/dist/anonymize-placement.js.map +1 -0
- package/dist/anonymize-scrub.d.ts +64 -0
- package/dist/anonymize-scrub.d.ts.map +1 -0
- package/dist/anonymize-scrub.js +315 -0
- package/dist/anonymize-scrub.js.map +1 -0
- package/dist/anonymize-types.d.ts +235 -0
- package/dist/anonymize-types.d.ts.map +1 -0
- package/dist/anonymize-types.js +5 -0
- package/dist/anonymize-types.js.map +1 -0
- 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/entity-type-sets.d.ts +47 -0
- package/dist/entity-type-sets.d.ts.map +1 -0
- package/dist/entity-type-sets.js +91 -0
- package/dist/entity-type-sets.js.map +1 -0
- 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 +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.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 +68 -90
- 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 +47 -51
- package/dist/reference-collector.d.ts.map +1 -1
- package/dist/reference-collector.js +115 -171
- package/dist/reference-collector.js.map +1 -1
- package/dist/related-entities.d.ts +10 -0
- package/dist/related-entities.d.ts.map +1 -0
- package/dist/related-entities.js +274 -0
- package/dist/related-entities.js.map +1 -0
- 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 +51 -8
- package/dist/step-collection.js.map +1 -1
- package/dist/step-export-types.d.ts +42 -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-header.d.ts +1 -1
- package/dist/step-header.d.ts.map +1 -1
- package/dist/step-header.js +2 -2
- package/dist/step-header.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 +104 -0
- package/dist/subset-entity-reader.d.ts.map +1 -0
- package/dist/subset-entity-reader.js +112 -0
- package/dist/subset-entity-reader.js.map +1 -0
- package/dist/subset-roots.d.ts +74 -0
- package/dist/subset-roots.d.ts.map +1 -0
- package/dist/subset-roots.js +168 -0
- package/dist/subset-roots.js.map +1 -0
- 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 -8
|
@@ -0,0 +1,211 @@
|
|
|
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
|
+
import { asSourceBytes, STEP_TRIVIA } from '@ifc-lite/parser';
|
|
5
|
+
import { splitTopLevelStepArguments } from './step-argument-parser.js';
|
|
6
|
+
import { groupSubContextsByKey, planSubContextUnify } from './merged-subcontext.js';
|
|
7
|
+
/**
|
|
8
|
+
* `#N=TYPE(...)` record, with STEP trivia (whitespace and/or a
|
|
9
|
+
* `/* ... */` comment, #3789) tolerated between the type name and `(` —
|
|
10
|
+
* same adjacency fix as `entity-extractor.ts`'s `extractEntity`. Without it
|
|
11
|
+
* a wrapped record's attribute is silently unreadable here (`getStepAttr`
|
|
12
|
+
* returns `null`), which reads identically to a genuinely-absent attribute.
|
|
13
|
+
*/
|
|
14
|
+
const RECORD_RE = new RegExp(`^#\\d+\\s*=\\s*\\w+${STEP_TRIVIA}\\(([\\s\\S]*)\\)\\s*;?\\s*$`);
|
|
15
|
+
/** Tolerance for WCS origins (metres) and normalized directions. */
|
|
16
|
+
const WCS_TOLERANCE_M = 1e-6;
|
|
17
|
+
/** 0-based attribute index of `IfcGeometricRepresentationSubContext.ParentContext`. */
|
|
18
|
+
const SUBCONTEXT_PARENT_CONTEXT_ATTR = 6;
|
|
19
|
+
function decodeEntity(dataStore, expressId) {
|
|
20
|
+
const source = dataStore.source;
|
|
21
|
+
if (!source)
|
|
22
|
+
return null;
|
|
23
|
+
const ref = dataStore.entityIndex.byId.get(expressId);
|
|
24
|
+
if (!ref)
|
|
25
|
+
return null;
|
|
26
|
+
return asSourceBytes(source).decodeUtf8(ref.byteOffset, ref.byteOffset + ref.byteLength);
|
|
27
|
+
}
|
|
28
|
+
/** Extract one 0-based STEP attribute of `expressId`'s entity, or null if unreadable. */
|
|
29
|
+
function getStepAttr(dataStore, expressId, index) {
|
|
30
|
+
const text = decodeEntity(dataStore, expressId);
|
|
31
|
+
const match = text?.match(RECORD_RE);
|
|
32
|
+
if (!match)
|
|
33
|
+
return null;
|
|
34
|
+
const args = splitTopLevelStepArguments(match[1]);
|
|
35
|
+
const raw = args?.[index];
|
|
36
|
+
return raw === undefined ? null : raw.trim();
|
|
37
|
+
}
|
|
38
|
+
function refId(raw) {
|
|
39
|
+
const m = raw?.match(/^#(\d+)$/);
|
|
40
|
+
return m ? parseInt(m[1], 10) : null;
|
|
41
|
+
}
|
|
42
|
+
function parseCoord(raw) {
|
|
43
|
+
if (raw === undefined)
|
|
44
|
+
return null;
|
|
45
|
+
const n = Number(raw.trim());
|
|
46
|
+
return Number.isFinite(n) ? n : null;
|
|
47
|
+
}
|
|
48
|
+
function parseVector(dataStore, directionId, fallback) {
|
|
49
|
+
if (directionId === null)
|
|
50
|
+
return fallback;
|
|
51
|
+
const raw = getStepAttr(dataStore, directionId, 0);
|
|
52
|
+
const inner = raw?.replace(/^\(|\)$/g, '');
|
|
53
|
+
const parts = inner === undefined ? null : splitTopLevelStepArguments(inner);
|
|
54
|
+
if (!parts || parts.length < 2)
|
|
55
|
+
return null;
|
|
56
|
+
const x = parseCoord(parts[0]);
|
|
57
|
+
const y = parseCoord(parts[1]);
|
|
58
|
+
const z = parts.length > 2 ? parseCoord(parts[2]) : 0;
|
|
59
|
+
if (x === null || y === null || z === null)
|
|
60
|
+
return null;
|
|
61
|
+
const magnitude = Math.hypot(x, y, z);
|
|
62
|
+
if (magnitude === 0)
|
|
63
|
+
return null;
|
|
64
|
+
return [x / magnitude, y / magnitude, z / magnitude];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the WorldCoordinateSystem frame of `contextId` (an
|
|
68
|
+
* `IfcGeometricRepresentationContext`), in metres.
|
|
69
|
+
*
|
|
70
|
+
* Attr 4 is `WorldCoordinateSystem`, an `IfcAxis2Placement` (2D/3D) whose
|
|
71
|
+
* attr 0 is `Location` (an `IfcCartesianPoint`), attrs 1/2 are `Axis` and
|
|
72
|
+
* `RefDirection` — but tolerates a context
|
|
73
|
+
* pointing straight at a point (no placement wrapper), the shape this
|
|
74
|
+
* package's own test fixtures use. Returns null when any hop is
|
|
75
|
+
* unresolvable (missing entity, `$`, non-numeric): callers must treat null
|
|
76
|
+
* permissively (compatible), never as "known to differ", so an unusual but
|
|
77
|
+
* harmless context shape never blocks a merge that used to work.
|
|
78
|
+
*/
|
|
79
|
+
export function resolveContextWcsMetres(dataStore, contextId, lengthUnitScale) {
|
|
80
|
+
const wcsRef = refId(getStepAttr(dataStore, contextId, 4));
|
|
81
|
+
if (wcsRef === null)
|
|
82
|
+
return null;
|
|
83
|
+
const wcsType = (dataStore.entityIndex.byId.get(wcsRef)?.type ?? '').toUpperCase();
|
|
84
|
+
const pointId = wcsType.includes('CARTESIANPOINT') ? wcsRef : refId(getStepAttr(dataStore, wcsRef, 0));
|
|
85
|
+
if (pointId === null)
|
|
86
|
+
return null;
|
|
87
|
+
const coordsRaw = getStepAttr(dataStore, pointId, 0);
|
|
88
|
+
const inner = coordsRaw?.replace(/^\(|\)$/g, '');
|
|
89
|
+
const parts = inner === undefined ? null : splitTopLevelStepArguments(inner);
|
|
90
|
+
if (!parts || parts.length < 2)
|
|
91
|
+
return null;
|
|
92
|
+
const x = parseCoord(parts[0]);
|
|
93
|
+
const y = parseCoord(parts[1]);
|
|
94
|
+
const z = parts.length > 2 ? parseCoord(parts[2]) : 0;
|
|
95
|
+
if (x === null || y === null || z === null)
|
|
96
|
+
return null;
|
|
97
|
+
const scale = Number.isFinite(lengthUnitScale) && lengthUnitScale > 0 ? lengthUnitScale : 1;
|
|
98
|
+
// Axis2Placement defaults omitted Axis and RefDirection to +Z and +X.
|
|
99
|
+
// Treat omitted and explicit defaults identically, not as an unknown shape.
|
|
100
|
+
const isPoint = wcsType.includes('CARTESIANPOINT');
|
|
101
|
+
const is2dPlacement = wcsType.includes('AXIS2PLACEMENT2D');
|
|
102
|
+
// IFC2D places its sole RefDirection at attribute 1; treating that slot as
|
|
103
|
+
// a 3D Axis loses rotations in the XY frame and can wrongly unify contexts.
|
|
104
|
+
const axis = isPoint || is2dPlacement
|
|
105
|
+
? [0, 0, 1]
|
|
106
|
+
: parseVector(dataStore, refId(getStepAttr(dataStore, wcsRef, 1)), [0, 0, 1]);
|
|
107
|
+
const refDirection = isPoint
|
|
108
|
+
? [1, 0, 0]
|
|
109
|
+
: parseVector(dataStore, refId(getStepAttr(dataStore, wcsRef, is2dPlacement ? 1 : 2)), [1, 0, 0]);
|
|
110
|
+
if (axis === null || refDirection === null)
|
|
111
|
+
return null;
|
|
112
|
+
return { x: x * scale, y: y * scale, z: z * scale, axis, refDirection };
|
|
113
|
+
}
|
|
114
|
+
/** `resolveContextWcsMetres` for a model's first `IfcGeometricRepresentationContext`, or null if it has none. */
|
|
115
|
+
export function resolveModelContextWcs(dataStore, lengthUnitScale) {
|
|
116
|
+
const contextId = (dataStore.entityIndex.byType.get('IFCGEOMETRICREPRESENTATIONCONTEXT') ?? [])[0];
|
|
117
|
+
return contextId === undefined ? null : resolveContextWcsMetres(dataStore, contextId, lengthUnitScale);
|
|
118
|
+
}
|
|
119
|
+
/** Build the {@link PrimaryContextState} for the primary model. */
|
|
120
|
+
export function resolvePrimaryContextState(dataStore, subContextIds, primaryScale) {
|
|
121
|
+
return {
|
|
122
|
+
subContextsByKey: groupSubContextsByKey(dataStore, subContextIds),
|
|
123
|
+
contextWcs: resolveModelContextWcs(dataStore, primaryScale),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* True when two resolved WCS frames are the same within tolerance, OR
|
|
128
|
+
* either is null (unresolvable — permissive: never block a merge on a
|
|
129
|
+
* context shape this reader couldn't fully parse).
|
|
130
|
+
*/
|
|
131
|
+
function wcsSignaturesCompatible(a, b) {
|
|
132
|
+
if (a === null || b === null)
|
|
133
|
+
return true;
|
|
134
|
+
return Math.abs(a.x - b.x) <= WCS_TOLERANCE_M
|
|
135
|
+
&& Math.abs(a.y - b.y) <= WCS_TOLERANCE_M
|
|
136
|
+
&& Math.abs(a.z - b.z) <= WCS_TOLERANCE_M
|
|
137
|
+
&& a.axis.every((value, index) => Math.abs(value - b.axis[index]) <= WCS_TOLERANCE_M)
|
|
138
|
+
&& a.refDirection.every((value, index) => Math.abs(value - b.refDirection[index]) <= WCS_TOLERANCE_M);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Plan the whole shared-infrastructure dedup for one model — `MergedExporter`
|
|
142
|
+
* delegates its entire "remap and skip duplicate infrastructure" step here.
|
|
143
|
+
*
|
|
144
|
+
* `IfcUnitAssignment` unifies by position unconditionally. The representation
|
|
145
|
+
* context (`IFCGEOMETRICREPRESENTATIONCONTEXT`) unifies only when its resolved
|
|
146
|
+
* WCS frame agrees with the primary model's (origin in metres AND
|
|
147
|
+
* axis/refDirection orientation, so a differing length unit or a rotated frame
|
|
148
|
+
* doesn't produce a false match); a mismatch leaves it un-remapped — kept as
|
|
149
|
+
* this model's own root — so its untouched coordinates are never
|
|
150
|
+
* re-interpreted against the wrong frame.
|
|
151
|
+
*
|
|
152
|
+
* Its subcontexts (`IFCGEOMETRICREPRESENTATIONSUBCONTEXT`) go through
|
|
153
|
+
* {@link planSubContextUnify} instead of positional matching — kind-matched by
|
|
154
|
+
* `ContextIdentifier`/`TargetView` so a 'Body' subcontext never unifies onto an
|
|
155
|
+
* 'Axis' one (merge invariant lens pass 3, item 1) — gated on the SAME
|
|
156
|
+
* `contextsCompatible` computed once up front: a subcontext inherits its WCS
|
|
157
|
+
* from `ParentContext`, so when the parent root's frame disagrees, retaining
|
|
158
|
+
* the parent means every child must be retained too, not just remapped in
|
|
159
|
+
* isolation. Mutates `sharedRemap`/`skipEntityIds`.
|
|
160
|
+
*
|
|
161
|
+
* `lengthUnitScale` is the scale THIS model's raw WCS coordinates resolve
|
|
162
|
+
* under. Under assume-shared the caller asserts raw coordinates are already
|
|
163
|
+
* in the primary's unit, and they are copied verbatim — so the caller must
|
|
164
|
+
* pass the PRIMARY scale (matching how {@link PrimaryContextState.contextWcs}
|
|
165
|
+
* was computed), not the model's own declared unit, or two frames that agree
|
|
166
|
+
* raw-value-for-raw-value can compare unequal (and vice versa: differing raw
|
|
167
|
+
* origins can collide once scaled). Under auto/normalize the caller passes
|
|
168
|
+
* the model's own declared scale, comparing true metric frames.
|
|
169
|
+
*/
|
|
170
|
+
export function planInfrastructureUnify(dataStore, modelInfra, firstModelInfraMap, firstModelContext, firstModelOffset, lengthUnitScale, sharedRemap, skipEntityIds) {
|
|
171
|
+
// Computed once, up front — not per type in the loop below — so its result
|
|
172
|
+
// does not depend on `IFCGEOMETRICREPRESENTATIONCONTEXT` iterating before
|
|
173
|
+
// `IFCGEOMETRICREPRESENTATIONSUBCONTEXT` in `firstModelInfraMap`.
|
|
174
|
+
const contextsCompatible = wcsSignaturesCompatible(firstModelContext.contextWcs, resolveModelContextWcs(dataStore, lengthUnitScale));
|
|
175
|
+
// Only the model's FIRST top-level context is (positionally) unified below;
|
|
176
|
+
// any later sibling context — a model with both a 'Model' and a 'Plan'
|
|
177
|
+
// context, say — is always retained. A subcontext may therefore unify ONLY
|
|
178
|
+
// when its own `ParentContext` is the context actually being remapped: a
|
|
179
|
+
// child of a retained sibling must stay with its parent, or its shape
|
|
180
|
+
// representations get re-anchored onto the primary's frame while the parent
|
|
181
|
+
// keeps its own.
|
|
182
|
+
const modelContextIds = modelInfra.get('IFCGEOMETRICREPRESENTATIONCONTEXT') ?? [];
|
|
183
|
+
const firstContextIds = firstModelInfraMap.get('IFCGEOMETRICREPRESENTATIONCONTEXT') ?? [];
|
|
184
|
+
const unifiedContextId = contextsCompatible && modelContextIds.length > 0 && firstContextIds.length > 0
|
|
185
|
+
? modelContextIds[0]
|
|
186
|
+
: null;
|
|
187
|
+
for (const [type, firstIds] of firstModelInfraMap) {
|
|
188
|
+
const thisIds = modelInfra.get(type);
|
|
189
|
+
if (!thisIds || firstIds.length === 0 || thisIds.length === 0)
|
|
190
|
+
continue;
|
|
191
|
+
if (type === 'IFCGEOMETRICREPRESENTATIONSUBCONTEXT') {
|
|
192
|
+
if (unifiedContextId === null)
|
|
193
|
+
continue;
|
|
194
|
+
// An unreadable ParentContext stays permissive (treated as a child of
|
|
195
|
+
// the unified context), matching this file's null-handling doctrine.
|
|
196
|
+
const childIds = thisIds.filter((id) => {
|
|
197
|
+
const parentId = refId(getStepAttr(dataStore, id, SUBCONTEXT_PARENT_CONTEXT_ATTR));
|
|
198
|
+
return parentId === null || parentId === unifiedContextId;
|
|
199
|
+
});
|
|
200
|
+
if (childIds.length > 0) {
|
|
201
|
+
planSubContextUnify(dataStore, childIds, firstModelContext.subContextsByKey, firstModelOffset, sharedRemap, skipEntityIds);
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (type === 'IFCGEOMETRICREPRESENTATIONCONTEXT' && !contextsCompatible)
|
|
206
|
+
continue;
|
|
207
|
+
sharedRemap.set(thisIds[0], firstIds[0] + firstModelOffset);
|
|
208
|
+
skipEntityIds.add(thisIds[0]);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=merged-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merged-context.js","sourceRoot":"","sources":["../src/merged-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empty-spatial-container analysis for the merged exporter (issue #3643) — the
|
|
3
|
+
* step of IfcOpenShell/BlenderBIM's "Merge Projects" recipe that container
|
|
4
|
+
* *matching* (`mergeSites` / `mergeBuildings` / `mergeStoreys`) leaves behind.
|
|
5
|
+
*
|
|
6
|
+
* An `IfcSite` / `IfcBuilding` / `IfcBuildingStorey` / `IfcSpace` is empty when
|
|
7
|
+
* it contains no surviving element (`IfcRelContainedInSpatialStructure`),
|
|
8
|
+
* directly aggregates no surviving non-spatial object, and transitively
|
|
9
|
+
* aggregates no non-empty spatial child. `IfcProject` is never a candidate.
|
|
10
|
+
*
|
|
11
|
+
* Emptiness is a fact about the MERGED model, not about one input: a site that
|
|
12
|
+
* is empty in its own file is not empty once a later model's storeys are unified
|
|
13
|
+
* onto it. So this runs across every model — after visibility filtering and
|
|
14
|
+
* after spatial unification, the two things that make a container empty in the
|
|
15
|
+
* first place — and yields the express ids the emit loop then never writes.
|
|
16
|
+
* Nothing is emitted referencing them, so no dangling-reference clean-up pass
|
|
17
|
+
* has to follow; that is why this belongs inside the merge rather than after it.
|
|
18
|
+
*
|
|
19
|
+
* The Rust twin is `rust/export/src/merged/empty.rs`, which the native merged
|
|
20
|
+
* export (`export_merged_models`) drives from the same rules.
|
|
21
|
+
*/
|
|
22
|
+
import type { IfcSourceBytes } from '@ifc-lite/parser';
|
|
23
|
+
import type { CompleteEntityIndex } from './entity-iteration.js';
|
|
24
|
+
/** One model as the analysis sees it, in the merge's own terms. */
|
|
25
|
+
export interface EmptyContainerModelView {
|
|
26
|
+
/** Complete entity index (primary + deferred atoms). */
|
|
27
|
+
entities: CompleteEntityIndex;
|
|
28
|
+
/** The model's source bytes. */
|
|
29
|
+
source: IfcSourceBytes;
|
|
30
|
+
/** Visible express ids, or `null` when the whole model is exported. */
|
|
31
|
+
included: ReadonlySet<number> | null;
|
|
32
|
+
/** Local express id → final express id, for containers unified onto an
|
|
33
|
+
* earlier model's (the spatial-unification remap). */
|
|
34
|
+
sharedRemap: ReadonlyMap<number, number>;
|
|
35
|
+
/** Express-id offset applied to this model in the merged file. */
|
|
36
|
+
offset: number;
|
|
37
|
+
/** True when the model unifies into the first model's project / unit space. */
|
|
38
|
+
compatible: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Which containers the emit loop must not write. */
|
|
41
|
+
export interface EmptyContainerPlan {
|
|
42
|
+
/** Per input model (same order): local express ids never emitted. */
|
|
43
|
+
droppedByModel: Set<number>[];
|
|
44
|
+
/** Containers dropped, counted in the MERGED model (a container unified
|
|
45
|
+
* across three inputs counts once). */
|
|
46
|
+
droppedCount: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A model with nothing to analyse — the placeholder for an input whose source
|
|
50
|
+
* bytes are missing (a cache-restored, metadata-only store), which the emit loop
|
|
51
|
+
* skips too. Keeps the caller's per-model arrays aligned with its inputs.
|
|
52
|
+
*/
|
|
53
|
+
export declare const EMPTY_MODEL_VIEW: EmptyContainerModelView;
|
|
54
|
+
/**
|
|
55
|
+
* Plan which spatial containers the merge leaves holding nothing.
|
|
56
|
+
*
|
|
57
|
+
* The returned local ids are what the caller withholds from the output; every
|
|
58
|
+
* surviving line that named one is narrowed (or withheld with it) by
|
|
59
|
+
* `filterHiddenRefsFromRelationshipLine`, which is what keeps the result free of
|
|
60
|
+
* dangling references.
|
|
61
|
+
*/
|
|
62
|
+
export declare function planEmptyContainerDrops(models: EmptyContainerModelView[]): EmptyContainerPlan;
|
|
63
|
+
//# sourceMappingURL=merged-empty-containers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merged-empty-containers.d.ts","sourceRoot":"","sources":["../src/merged-empty-containers.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAkCjE,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,wDAAwD;IACxD,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,gCAAgC;IAChC,MAAM,EAAE,cAAc,CAAC;IACvB,uEAAuE;IACvE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACrC;2DACuD;IACvD,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;IAC9B;4CACwC;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB;AAOD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,uBAO9B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,uBAAuB,EAAE,GAAG,kBAAkB,CA2C7F"}
|
|
@@ -0,0 +1,354 @@
|
|
|
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
|
+
import { asSourceBytes, STEP_TRIVIA } from '@ifc-lite/parser';
|
|
5
|
+
import { splitTopLevelArgs } from './step-argument-parser.js';
|
|
6
|
+
/**
|
|
7
|
+
* `#N=TYPE(...)` record, with STEP trivia (whitespace and/or a
|
|
8
|
+
* `/* ... */` comment, #3789) tolerated between the type name and `(` —
|
|
9
|
+
* same adjacency fix as `entity-extractor.ts`'s `extractEntity`. Without it
|
|
10
|
+
* a wrapped line's arguments are unreadable here (`topLevelAttrs` returns
|
|
11
|
+
* `null`), which the caller treats as "can't safely rewrite" and blocks the
|
|
12
|
+
* empty-container drop for that entity.
|
|
13
|
+
*/
|
|
14
|
+
const RECORD_RE = new RegExp(`^#\\d+\\s*=\\s*\\w+${STEP_TRIVIA}\\(([\\s\\S]*)\\)\\s*;?\\s*$`);
|
|
15
|
+
/**
|
|
16
|
+
* Spatial container types dropped when they end up empty. `IfcProject` is
|
|
17
|
+
* deliberately absent: it is the file's root, never a candidate.
|
|
18
|
+
*/
|
|
19
|
+
const CONTAINER_TYPES = new Set([
|
|
20
|
+
'IFCSITE',
|
|
21
|
+
'IFCBUILDING',
|
|
22
|
+
'IFCBUILDINGSTOREY',
|
|
23
|
+
'IFCSPACE',
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* Relationships that express spatial structure, as
|
|
27
|
+
* `[relatingAttributeIndex, relatedAttributeIndex]` — `IfcRelAggregates` names
|
|
28
|
+
* the whole first, the containment relationships name the parts first.
|
|
29
|
+
*/
|
|
30
|
+
const STRUCTURE_RELATIONS = {
|
|
31
|
+
IFCRELAGGREGATES: [4, 5],
|
|
32
|
+
IFCRELCONTAINEDINSPATIALSTRUCTURE: [5, 4],
|
|
33
|
+
IFCRELREFERENCEDINSPATIALSTRUCTURE: [5, 4],
|
|
34
|
+
};
|
|
35
|
+
/** True when `typeUpper` is a droppable spatial container type. */
|
|
36
|
+
function isSpatialContainerType(typeUpper) {
|
|
37
|
+
return CONTAINER_TYPES.has(typeUpper);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A model with nothing to analyse — the placeholder for an input whose source
|
|
41
|
+
* bytes are missing (a cache-restored, metadata-only store), which the emit loop
|
|
42
|
+
* skips too. Keeps the caller's per-model arrays aligned with its inputs.
|
|
43
|
+
*/
|
|
44
|
+
export const EMPTY_MODEL_VIEW = {
|
|
45
|
+
entities: new Map(),
|
|
46
|
+
source: asSourceBytes(new Uint8Array()),
|
|
47
|
+
included: null,
|
|
48
|
+
sharedRemap: new Map(),
|
|
49
|
+
offset: 0,
|
|
50
|
+
compatible: false,
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Plan which spatial containers the merge leaves holding nothing.
|
|
54
|
+
*
|
|
55
|
+
* The returned local ids are what the caller withholds from the output; every
|
|
56
|
+
* surviving line that named one is narrowed (or withheld with it) by
|
|
57
|
+
* `filterHiddenRefsFromRelationshipLine`, which is what keeps the result free of
|
|
58
|
+
* dangling references.
|
|
59
|
+
*/
|
|
60
|
+
export function planEmptyContainerDrops(models) {
|
|
61
|
+
const nodes = new Set();
|
|
62
|
+
const hasContent = new Set();
|
|
63
|
+
const blocked = new Set();
|
|
64
|
+
/** Spatial child node → its container nodes (upward edges for the sweep). */
|
|
65
|
+
const parents = new Map();
|
|
66
|
+
const guidNode = new Map();
|
|
67
|
+
const containersByModel = [];
|
|
68
|
+
for (const view of models) {
|
|
69
|
+
const canon = canonicalContainers(view, guidNode);
|
|
70
|
+
containersByModel.push(canon);
|
|
71
|
+
for (const node of canon.values())
|
|
72
|
+
nodes.add(node);
|
|
73
|
+
recordEdges(view, canon, hasContent, parents);
|
|
74
|
+
recordBlocks(view, canon, blocked);
|
|
75
|
+
}
|
|
76
|
+
// A container is non-empty when it holds something, is blocked, or has a
|
|
77
|
+
// non-empty spatial child — the last propagated upward from the first two.
|
|
78
|
+
// The visited check also makes a self-referential aggregation terminate.
|
|
79
|
+
const nonEmpty = new Set([...hasContent, ...blocked]);
|
|
80
|
+
const stack = [...nonEmpty];
|
|
81
|
+
while (stack.length > 0) {
|
|
82
|
+
const node = stack.pop();
|
|
83
|
+
for (const parent of parents.get(node) ?? []) {
|
|
84
|
+
if (!nonEmpty.has(parent)) {
|
|
85
|
+
nonEmpty.add(parent);
|
|
86
|
+
stack.push(parent);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const dropped = new Set([...nodes].filter(node => !nonEmpty.has(node)));
|
|
91
|
+
return {
|
|
92
|
+
droppedByModel: containersByModel.map(canon => {
|
|
93
|
+
const ids = new Set();
|
|
94
|
+
for (const [localId, node] of canon) {
|
|
95
|
+
if (dropped.has(node))
|
|
96
|
+
ids.add(localId);
|
|
97
|
+
}
|
|
98
|
+
return ids;
|
|
99
|
+
}),
|
|
100
|
+
droppedCount: dropped.size,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Map each of this model's visible containers to its node in the merged model:
|
|
105
|
+
* the container it was unified onto, the earlier instance that already carries
|
|
106
|
+
* its GlobalId, or its own final express id. Mirrors the emit loop's
|
|
107
|
+
* unification order (iteration order, first occurrence wins).
|
|
108
|
+
*/
|
|
109
|
+
function canonicalContainers(view, guidNode) {
|
|
110
|
+
const canon = new Map();
|
|
111
|
+
for (const [localId, ref] of view.entities) {
|
|
112
|
+
if (view.included !== null && !view.included.has(localId))
|
|
113
|
+
continue;
|
|
114
|
+
if (!isSpatialContainerType(ref.type.toUpperCase()))
|
|
115
|
+
continue;
|
|
116
|
+
const unified = view.sharedRemap.get(localId);
|
|
117
|
+
if (unified !== undefined) {
|
|
118
|
+
canon.set(localId, unified);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const finalId = localId + view.offset;
|
|
122
|
+
if (!view.compatible) {
|
|
123
|
+
canon.set(localId, finalId);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const guid = leadingGuid(lineOf(view, localId));
|
|
127
|
+
if (guid === null) {
|
|
128
|
+
canon.set(localId, finalId);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const known = guidNode.get(guid);
|
|
132
|
+
if (known === undefined)
|
|
133
|
+
guidNode.set(guid, finalId);
|
|
134
|
+
canon.set(localId, known ?? finalId);
|
|
135
|
+
}
|
|
136
|
+
return canon;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Record what this model contributes to each container: contained elements and
|
|
140
|
+
* aggregated objects become content; aggregated spatial children become upward
|
|
141
|
+
* edges, so a non-empty storey keeps its building and its site.
|
|
142
|
+
*/
|
|
143
|
+
function recordEdges(view, canon, hasContent, parents) {
|
|
144
|
+
if (canon.size === 0)
|
|
145
|
+
return;
|
|
146
|
+
for (const [localId, ref] of view.entities) {
|
|
147
|
+
if (view.included !== null && !view.included.has(localId))
|
|
148
|
+
continue;
|
|
149
|
+
const slots = STRUCTURE_RELATIONS[ref.type.toUpperCase()];
|
|
150
|
+
if (slots === undefined)
|
|
151
|
+
continue;
|
|
152
|
+
const attrs = topLevelAttrs(lineOf(view, localId));
|
|
153
|
+
if (attrs === null)
|
|
154
|
+
continue;
|
|
155
|
+
const [relatingIndex, relatedIndex] = slots;
|
|
156
|
+
const relating = singleRef(attrs[relatingIndex] ?? '');
|
|
157
|
+
// Not a container (an IfcProject aggregating its sites, say) — its children
|
|
158
|
+
// are roots of the spatial tree, with nothing above them to keep.
|
|
159
|
+
if (relating === null)
|
|
160
|
+
continue;
|
|
161
|
+
const parent = canon.get(relating);
|
|
162
|
+
if (parent === undefined)
|
|
163
|
+
continue;
|
|
164
|
+
for (const child of refList(attrs[relatedIndex] ?? '')) {
|
|
165
|
+
if (view.included !== null && !view.included.has(child))
|
|
166
|
+
continue;
|
|
167
|
+
const childNode = canon.get(child);
|
|
168
|
+
if (childNode === undefined) {
|
|
169
|
+
hasContent.add(parent);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const edges = parents.get(childNode);
|
|
173
|
+
if (edges === undefined)
|
|
174
|
+
parents.set(childNode, [parent]);
|
|
175
|
+
else
|
|
176
|
+
edges.push(parent);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Block every container this model names in a way the emit loop could not
|
|
182
|
+
* rewrite. Dropping such a container would leave a dangling `#ref`, so it stays
|
|
183
|
+
* (counting as non-empty, which keeps its ancestors too) instead.
|
|
184
|
+
*
|
|
185
|
+
* Rewritable is: a reference from an objectified relationship (`IfcRel*`) that
|
|
186
|
+
* nothing else references, since `filterHiddenRefsFromRelationshipLine` can
|
|
187
|
+
* strip it from a list or withhold the whole line. Everything else — a
|
|
188
|
+
* non-relationship referrer, a reference nested inside a typed value, or a
|
|
189
|
+
* relationship that is itself referenced (withholding it would dangle in turn) —
|
|
190
|
+
* blocks.
|
|
191
|
+
*/
|
|
192
|
+
function recordBlocks(view, canon, blocked) {
|
|
193
|
+
if (canon.size === 0)
|
|
194
|
+
return;
|
|
195
|
+
const referrers = [];
|
|
196
|
+
const referencedRelationships = new Set();
|
|
197
|
+
for (const [localId, ref] of view.entities) {
|
|
198
|
+
if (view.included !== null && !view.included.has(localId))
|
|
199
|
+
continue;
|
|
200
|
+
let namesContainer = false;
|
|
201
|
+
for (const target of argRefs(view.source.slice(ref.byteOffset, ref.byteOffset + ref.byteLength))) {
|
|
202
|
+
if (canon.has(target))
|
|
203
|
+
namesContainer = true;
|
|
204
|
+
const targetType = view.entities.get(target)?.type.toUpperCase();
|
|
205
|
+
if (targetType?.startsWith('IFCREL'))
|
|
206
|
+
referencedRelationships.add(target);
|
|
207
|
+
}
|
|
208
|
+
if (namesContainer)
|
|
209
|
+
referrers.push(localId);
|
|
210
|
+
}
|
|
211
|
+
for (const localId of referrers) {
|
|
212
|
+
const typeUpper = view.entities.get(localId).type.toUpperCase();
|
|
213
|
+
const rewritable = typeUpper.startsWith('IFCREL') && !referencedRelationships.has(localId);
|
|
214
|
+
const line = lineOf(view, localId);
|
|
215
|
+
const slots = classifyRefs(line);
|
|
216
|
+
if (slots === null) {
|
|
217
|
+
// The argument list could not be parsed, so no rewrite can narrow this
|
|
218
|
+
// line: every container it names has to stay.
|
|
219
|
+
for (const target of argRefs(line)) {
|
|
220
|
+
const node = canon.get(target);
|
|
221
|
+
if (node !== undefined)
|
|
222
|
+
blocked.add(node);
|
|
223
|
+
}
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
for (const [target, nested] of slots) {
|
|
227
|
+
const node = canon.get(target);
|
|
228
|
+
if (node === undefined)
|
|
229
|
+
continue;
|
|
230
|
+
if (nested || !rewritable)
|
|
231
|
+
blocked.add(node);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** The entity's STEP line text. */
|
|
236
|
+
function lineOf(view, localId) {
|
|
237
|
+
const ref = view.entities.get(localId);
|
|
238
|
+
if (ref === undefined)
|
|
239
|
+
return '';
|
|
240
|
+
return view.source.decodeUtf8(ref.byteOffset, ref.byteOffset + ref.byteLength);
|
|
241
|
+
}
|
|
242
|
+
/** Top-level arguments of a `#id=TYPE(…);` line, or `null` when unparseable. */
|
|
243
|
+
function topLevelAttrs(line) {
|
|
244
|
+
const match = line.match(RECORD_RE);
|
|
245
|
+
if (!match)
|
|
246
|
+
return null;
|
|
247
|
+
return splitTopLevelArgs(match[1]).map(arg => arg.trim());
|
|
248
|
+
}
|
|
249
|
+
/** Parse an argument that is exactly one reference (`"#7"` → `7`). */
|
|
250
|
+
function singleRef(arg) {
|
|
251
|
+
const match = arg.trim().match(/^#(\d+)$/);
|
|
252
|
+
return match ? Number(match[1]) : null;
|
|
253
|
+
}
|
|
254
|
+
/** Parse a `(#a,#b,…)` list argument into ids. */
|
|
255
|
+
function refList(arg) {
|
|
256
|
+
const trimmed = arg.trim();
|
|
257
|
+
if (!trimmed.startsWith('(') || !trimmed.endsWith(')'))
|
|
258
|
+
return [];
|
|
259
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
260
|
+
if (inner === '')
|
|
261
|
+
return [];
|
|
262
|
+
const ids = [];
|
|
263
|
+
for (const item of splitTopLevelArgs(inner)) {
|
|
264
|
+
const id = singleRef(item);
|
|
265
|
+
if (id !== null)
|
|
266
|
+
ids.push(id);
|
|
267
|
+
}
|
|
268
|
+
return ids;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Every reference in a line's argument list, paired with whether it sits
|
|
272
|
+
* somewhere neither `filterHiddenRefsFromRelationshipLine` can strip (a direct
|
|
273
|
+
* list element) nor withhold the line for (a whole single-valued attribute) —
|
|
274
|
+
* i.e. nested inside a list of lists or a typed value. `null` when the line's
|
|
275
|
+
* argument list cannot be parsed at all: a line no rewrite can narrow, which the
|
|
276
|
+
* analysis must treat as a blocker rather than as "names nothing".
|
|
277
|
+
*/
|
|
278
|
+
function classifyRefs(line) {
|
|
279
|
+
const attrs = topLevelAttrs(line);
|
|
280
|
+
if (attrs === null)
|
|
281
|
+
return null;
|
|
282
|
+
const out = [];
|
|
283
|
+
for (const attr of attrs) {
|
|
284
|
+
const direct = singleRef(attr);
|
|
285
|
+
if (direct !== null) {
|
|
286
|
+
out.push([direct, false]);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (attr.startsWith('(') && attr.endsWith(')')) {
|
|
290
|
+
const inner = attr.slice(1, -1).trim();
|
|
291
|
+
if (inner === '')
|
|
292
|
+
continue;
|
|
293
|
+
for (const item of splitTopLevelArgs(inner)) {
|
|
294
|
+
const id = singleRef(item);
|
|
295
|
+
if (id !== null)
|
|
296
|
+
out.push([id, false]);
|
|
297
|
+
else
|
|
298
|
+
for (const nested of argRefs(item))
|
|
299
|
+
out.push([nested, true]);
|
|
300
|
+
}
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
for (const nested of argRefs(attr))
|
|
304
|
+
out.push([nested, true]);
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Every `#N` reference in a chunk of STEP text, skipping quoted strings (where a
|
|
310
|
+
* `#` is literal) and any leading `#id=` (the line's own id). Reads bytes rather
|
|
311
|
+
* than decoded text so the reverse-reference scan never decodes a whole model.
|
|
312
|
+
*/
|
|
313
|
+
function argRefs(text) {
|
|
314
|
+
const bytes = typeof text === 'string' ? new TextEncoder().encode(text) : text;
|
|
315
|
+
const eq = bytes.indexOf(0x3d /* = */);
|
|
316
|
+
const from = eq === -1 ? 0 : eq + 1;
|
|
317
|
+
const out = [];
|
|
318
|
+
let inString = false;
|
|
319
|
+
for (let i = from; i < bytes.length; i++) {
|
|
320
|
+
const byte = bytes[i];
|
|
321
|
+
if (byte === 0x27 /* ' */) {
|
|
322
|
+
inString = !inString;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (inString || byte !== 0x23 /* # */)
|
|
326
|
+
continue;
|
|
327
|
+
let j = i + 1;
|
|
328
|
+
let value = 0;
|
|
329
|
+
while (j < bytes.length && bytes[j] >= 0x30 && bytes[j] <= 0x39) {
|
|
330
|
+
value = value * 10 + (bytes[j] - 0x30);
|
|
331
|
+
j++;
|
|
332
|
+
}
|
|
333
|
+
if (j > i + 1) {
|
|
334
|
+
out.push(value);
|
|
335
|
+
i = j - 1;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
/** The GlobalId (first quoted attribute) of a rooted entity's line. */
|
|
341
|
+
function leadingGuid(line) {
|
|
342
|
+
const open = line.indexOf('(');
|
|
343
|
+
if (open === -1)
|
|
344
|
+
return null;
|
|
345
|
+
const first = line.indexOf("'", open + 1);
|
|
346
|
+
if (first === -1)
|
|
347
|
+
return null;
|
|
348
|
+
const second = line.indexOf("'", first + 1);
|
|
349
|
+
if (second === -1)
|
|
350
|
+
return null;
|
|
351
|
+
const raw = line.slice(first + 1, second);
|
|
352
|
+
return /^[0-9A-Za-z_$]{22}$/.test(raw) ? raw : null;
|
|
353
|
+
}
|
|
354
|
+
//# sourceMappingURL=merged-empty-containers.js.map
|