@ifc-lite/export 4.7.2 → 4.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ /**
2
+ * One-parent-per-object bookkeeping for the decomposition relationships of a
3
+ * merge (#5471, #5726).
4
+ *
5
+ * Split out of {@link ../merged-exporter.ts} (kept under its module-size
6
+ * budget) rather than duplicated: `findEntitiesByType`/`extractStepAttribute`
7
+ * stay the single source of truth in `MergedExporter` and are passed in here,
8
+ * so this file has no data-model logic of its own to drift from it.
9
+ */
10
+ import type { IfcDataStore } from '@ifc-lite/parser';
11
+ import type { IfcSchemaVersion } from './schema-converter.js';
12
+ /**
13
+ * The final ids already given a WRITTEN parent in the merge output, per
14
+ * single-valued decomposition inverse of the output schema. One instance per
15
+ * merge, grown model by model in merge order.
16
+ */
17
+ export declare class DecompositionClaims {
18
+ /** Relationship type (uppercase) → the inverse it fills; see {@link IFC4_INVERSES}. */
19
+ readonly inverseOf: ReadonlyMap<string, string>;
20
+ private readonly parented;
21
+ /** `only` narrows the claimed relationship types (the #5725 drop planner's view). */
22
+ constructor(outputSchema: IfcSchemaVersion, only?: (relType: string) => boolean);
23
+ /** The final ids that already fill `inverse` in the output. */
24
+ parentedVia(inverse: string): Set<number>;
25
+ }
26
+ /** What {@link claimDecompositionParents} needs to know about one model's plan. */
27
+ export interface DecompositionClaimInput {
28
+ dataStore: IfcDataStore;
29
+ /** Local id → final id for every unified entity (spatial, infrastructure, GlobalId). */
30
+ sharedRemap: ReadonlyMap<number, number>;
31
+ /** This model's id offset: an unremapped local id's final id is `id + idOffset`. */
32
+ idOffset: number;
33
+ /** Local ids not written; a skipped rel claims nothing, and a rel skipped here is added. */
34
+ skipEntityIds: Set<number>;
35
+ /** Local rel id → member ids to drop from its written RelatedObjects list. */
36
+ relParentStrip: Map<number, Set<number>>;
37
+ /**
38
+ * Whether a local id survives into the output as a reference: false for a
39
+ * hidden product under `visibleOnly` or a dropped empty container, which
40
+ * `renderEntity` narrows out of (or, as a RelatingObject, withholds) the rel.
41
+ */
42
+ isEmitted: (localId: number) => boolean;
43
+ /** Whether the rel line itself is in the written set at all (visibility closure). */
44
+ isIncluded: (localId: number) => boolean;
45
+ /** Strip already-parented members (a later, unified model); false just records claims. */
46
+ dedupe: boolean;
47
+ }
48
+ /**
49
+ * Keep one decomposition parent per object and inverse across a merge (#5471,
50
+ * #5726).
51
+ *
52
+ * `IfcObjectDefinition.Decomposes` (and, from IFC4, `.Nests`) is `SET [0:1]`,
53
+ * and `IfcSpatialStructureElement.WR41` requires exactly one for a building or
54
+ * storey. Once a later model's Building unifies with the primary's, the
55
+ * primary's own rel already gives it a parent, so a later rel naming the same
56
+ * (remapped) Building gives it a second one, whether that rel's RelatingObject
57
+ * remaps to the same parent (a duplicate edge, e.g. `(Project, (Site))` twice)
58
+ * or a different one (model B aggregates its Building under its Project, model
59
+ * A under a Site). Which relationships share an inverse is the output schema's
60
+ * call, held by {@link DecompositionClaims}.
61
+ *
62
+ * `claims` holds the final ids that already have a parent WRITTEN to the
63
+ * output. Called once per model in merge order, after all of that model's
64
+ * unification and container drops: every rel the model will write adds its
65
+ * written members. A rel that will not be written (skipped, outside the
66
+ * visibility closure, or with a hidden or dropped RelatingObject) claims
67
+ * nothing, and neither does a member narrowed out of it, so a later model's rel
68
+ * stays the parent of an object whose primary parent is not in the output.
69
+ *
70
+ * With `dedupe`, a written member already claimed for the rel's inverse is
71
+ * redundant: a rel whose written members are ALL redundant is skipped, one with
72
+ * SOME is kept for its new members and the redundant ids go to
73
+ * `relParentStrip` for {@link applyRelParentStrip}. A unified member with no
74
+ * parent yet is kept, since that rel is then its only parentage statement
75
+ * (#3550).
76
+ */
77
+ export declare function claimDecompositionParents(input: DecompositionClaimInput, claims: DecompositionClaims, findEntitiesByType: (dataStore: IfcDataStore, typeUpper: string) => number[], extractStepAttribute: (expressId: number, dataStore: IfcDataStore, attrIndex: number) => string | null): void;
78
+ /**
79
+ * Render-time counterpart of {@link claimDecompositionParents}: drop the
80
+ * RelatedObjects members of a partially redundant IFCRELAGGREGATES or
81
+ * IFCRELNESTS that already have a parent of that kind in the output.
82
+ * Reuses the same list/scalar-aware ref filter the
83
+ * `visibleOnly`/deletion dangling-ref path uses. Must run in LOCAL id
84
+ * space, before any id offset/remap — `localId` and the ids inside
85
+ * `relParentStrip` are both local to the model being rendered.
86
+ *
87
+ * Returns `entityText` unchanged when `localId` has no strip entry, and
88
+ * `null` when the filter would withhold the whole line — a strip set built
89
+ * by {@link claimDecompositionParents} is a strict subset of the
90
+ * RelatedObjects list, so for well-formed input the filter only narrows,
91
+ * but a degenerate file (a stripped member id that also appears as a
92
+ * single-valued ref, e.g. self-aggregation) can null the line. The caller
93
+ * must withhold it, like every other user of the filter: every edge the
94
+ * line declared is already declared by the primary model, and emitting the
95
+ * unfiltered bytes instead would reintroduce the duplicate membership this
96
+ * module exists to remove.
97
+ */
98
+ export declare function applyRelParentStrip(entityText: string, localId: number, relParentStrip: ReadonlyMap<number, ReadonlySet<number>>): string | null;
99
+ //# sourceMappingURL=merged-decomposition-parents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merged-decomposition-parents.d.ts","sourceRoot":"","sources":["../src/merged-decomposition-parents.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAuB9D;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,uFAAuF;IACvF,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAE3D,qFAAqF;gBACzE,YAAY,EAAE,gBAAgB,EAAE,IAAI,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAoB;IAK3F,+DAA+D;IAC/D,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;CAK1C;AAED,mFAAmF;AACnF,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,YAAY,CAAC;IACxB,wFAAwF;IACxF,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,8EAA8E;IAC9E,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACzC;;;;OAIG;IACH,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,qFAAqF;IACrF,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,0FAA0F;IAC1F,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,uBAAuB,EAC9B,MAAM,EAAE,mBAAmB,EAC3B,kBAAkB,EAAE,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,EAAE,EAC5E,oBAAoB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GACrG,IAAI,CAwBN;AAYD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,GACvD,MAAM,GAAG,IAAI,CAIf"}
@@ -0,0 +1,142 @@
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 { filterHiddenRefsFromRelationshipLine } from './reference-collector.js';
5
+ /**
6
+ * Each decomposition relationship → the `SET [0:1]` inverse on
7
+ * `IfcObjectDefinition` its RelatedObjects fill, in the OUTPUT schema. Both
8
+ * relationships carry RelatingObject at attr 4 and RelatedObjects at attr 5 in
9
+ * every schema.
10
+ *
11
+ * IFC2X3: `Decomposes : SET [0:1] OF IfcRelDecomposes`, and IfcRelAggregates
12
+ * and IfcRelNests are both IfcRelDecomposes, so one nesting and one
13
+ * aggregation parent together are already two (#5726). IFC4 and later split
14
+ * `Nests : SET [0:1] OF IfcRelNests` off `Decomposes : SET [0:1] OF
15
+ * IfcRelAggregates`: one parent of each kind, but still only one each.
16
+ */
17
+ const IFC2X3_INVERSES = new Map([
18
+ ['IFCRELAGGREGATES', 'Decomposes'],
19
+ ['IFCRELNESTS', 'Decomposes'],
20
+ ]);
21
+ const IFC4_INVERSES = new Map([
22
+ ['IFCRELAGGREGATES', 'Decomposes'],
23
+ ['IFCRELNESTS', 'Nests'],
24
+ ]);
25
+ /**
26
+ * The final ids already given a WRITTEN parent in the merge output, per
27
+ * single-valued decomposition inverse of the output schema. One instance per
28
+ * merge, grown model by model in merge order.
29
+ */
30
+ export class DecompositionClaims {
31
+ /** Relationship type (uppercase) → the inverse it fills; see {@link IFC4_INVERSES}. */
32
+ inverseOf;
33
+ parented = new Map();
34
+ /** `only` narrows the claimed relationship types (the #5725 drop planner's view). */
35
+ constructor(outputSchema, only = () => true) {
36
+ const inverses = outputSchema === 'IFC2X3' ? IFC2X3_INVERSES : IFC4_INVERSES;
37
+ this.inverseOf = new Map([...inverses].filter(([relType]) => only(relType)));
38
+ }
39
+ /** The final ids that already fill `inverse` in the output. */
40
+ parentedVia(inverse) {
41
+ let ids = this.parented.get(inverse);
42
+ if (ids === undefined)
43
+ this.parented.set(inverse, (ids = new Set()));
44
+ return ids;
45
+ }
46
+ }
47
+ /**
48
+ * Keep one decomposition parent per object and inverse across a merge (#5471,
49
+ * #5726).
50
+ *
51
+ * `IfcObjectDefinition.Decomposes` (and, from IFC4, `.Nests`) is `SET [0:1]`,
52
+ * and `IfcSpatialStructureElement.WR41` requires exactly one for a building or
53
+ * storey. Once a later model's Building unifies with the primary's, the
54
+ * primary's own rel already gives it a parent, so a later rel naming the same
55
+ * (remapped) Building gives it a second one, whether that rel's RelatingObject
56
+ * remaps to the same parent (a duplicate edge, e.g. `(Project, (Site))` twice)
57
+ * or a different one (model B aggregates its Building under its Project, model
58
+ * A under a Site). Which relationships share an inverse is the output schema's
59
+ * call, held by {@link DecompositionClaims}.
60
+ *
61
+ * `claims` holds the final ids that already have a parent WRITTEN to the
62
+ * output. Called once per model in merge order, after all of that model's
63
+ * unification and container drops: every rel the model will write adds its
64
+ * written members. A rel that will not be written (skipped, outside the
65
+ * visibility closure, or with a hidden or dropped RelatingObject) claims
66
+ * nothing, and neither does a member narrowed out of it, so a later model's rel
67
+ * stays the parent of an object whose primary parent is not in the output.
68
+ *
69
+ * With `dedupe`, a written member already claimed for the rel's inverse is
70
+ * redundant: a rel whose written members are ALL redundant is skipped, one with
71
+ * SOME is kept for its new members and the redundant ids go to
72
+ * `relParentStrip` for {@link applyRelParentStrip}. A unified member with no
73
+ * parent yet is kept, since that rel is then its only parentage statement
74
+ * (#3550).
75
+ */
76
+ export function claimDecompositionParents(input, claims, findEntitiesByType, extractStepAttribute) {
77
+ const { dataStore, sharedRemap, idOffset, skipEntityIds, relParentStrip, isEmitted, isIncluded, dedupe } = input;
78
+ const finalId = (ref) => sharedRemap.get(ref) ?? ref + idOffset;
79
+ for (const [relType, inverse] of claims.inverseOf) {
80
+ const parented = claims.parentedVia(inverse);
81
+ for (const relId of findEntitiesByType(dataStore, relType)) {
82
+ if (skipEntityIds.has(relId) || !isIncluded(relId))
83
+ continue;
84
+ // RelatingObject is attr 4 — a single #ref; a hidden or dropped one withholds the line.
85
+ const relating = extractStepAttribute(relId, dataStore, 4)?.match(/^#(\d+)$/);
86
+ if (relating && !isEmitted(parseInt(relating[1], 10)))
87
+ continue;
88
+ // RelatedObjects is attr 5 — list of #refs like (#2,#3); hidden members are narrowed out.
89
+ const refs = listRefs(extractStepAttribute(relId, dataStore, 5)).filter(isEmitted);
90
+ if (refs.length === 0)
91
+ continue;
92
+ const redundantRefs = dedupe ? refs.filter(ref => parented.has(finalId(ref))) : [];
93
+ if (redundantRefs.length === refs.length) {
94
+ // Every written member already has a parent in the output — fully redundant.
95
+ skipEntityIds.add(relId);
96
+ continue;
97
+ }
98
+ if (redundantRefs.length > 0)
99
+ relParentStrip.set(relId, new Set(redundantRefs));
100
+ for (const ref of refs)
101
+ parented.add(finalId(ref));
102
+ }
103
+ }
104
+ }
105
+ /** The `#id`s in one STEP list attribute, in order; `[]` for a missing one. */
106
+ function listRefs(attr) {
107
+ if (!attr)
108
+ return [];
109
+ const refs = [];
110
+ const refRegex = /#(\d+)/g;
111
+ let match;
112
+ while ((match = refRegex.exec(attr)) !== null)
113
+ refs.push(parseInt(match[1], 10));
114
+ return refs;
115
+ }
116
+ /**
117
+ * Render-time counterpart of {@link claimDecompositionParents}: drop the
118
+ * RelatedObjects members of a partially redundant IFCRELAGGREGATES or
119
+ * IFCRELNESTS that already have a parent of that kind in the output.
120
+ * Reuses the same list/scalar-aware ref filter the
121
+ * `visibleOnly`/deletion dangling-ref path uses. Must run in LOCAL id
122
+ * space, before any id offset/remap — `localId` and the ids inside
123
+ * `relParentStrip` are both local to the model being rendered.
124
+ *
125
+ * Returns `entityText` unchanged when `localId` has no strip entry, and
126
+ * `null` when the filter would withhold the whole line — a strip set built
127
+ * by {@link claimDecompositionParents} is a strict subset of the
128
+ * RelatedObjects list, so for well-formed input the filter only narrows,
129
+ * but a degenerate file (a stripped member id that also appears as a
130
+ * single-valued ref, e.g. self-aggregation) can null the line. The caller
131
+ * must withhold it, like every other user of the filter: every edge the
132
+ * line declared is already declared by the primary model, and emitting the
133
+ * unfiltered bytes instead would reintroduce the duplicate membership this
134
+ * module exists to remove.
135
+ */
136
+ export function applyRelParentStrip(entityText, localId, relParentStrip) {
137
+ const toStrip = relParentStrip.get(localId);
138
+ if (toStrip === undefined)
139
+ return entityText;
140
+ return filterHiddenRefsFromRelationshipLine(entityText, id => toStrip.has(id));
141
+ }
142
+ //# sourceMappingURL=merged-decomposition-parents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merged-decomposition-parents.js","sourceRoot":"","sources":["../src/merged-decomposition-parents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /** Top-level arguments of a `#id=TYPE(…);` line, or `null` when unparseable. */
2
+ export declare function topLevelAttrs(line: string): string[] | null;
3
+ /** `"#7"` → `7` via shared, trivia-tolerant {@link BARE_REF_RE} (#4227 — a narrower regex misclassified a comment-wrapped `RelatingObject`). */
4
+ export declare function singleRef(arg: string): number | null;
5
+ /** Parse a `(#a,#b,…)` list argument into ids. */
6
+ export declare function refList(arg: string): number[];
7
+ /**
8
+ * Every reference in a line's argument list, paired with whether it sits
9
+ * somewhere neither `filterHiddenRefsFromRelationshipLine` can strip (a direct
10
+ * list element) nor withhold the line for (a whole single-valued attribute) —
11
+ * i.e. nested inside a list of lists or a typed value. `null` when the line's
12
+ * argument list cannot be parsed at all: a line no rewrite can narrow, which the
13
+ * analysis must treat as a blocker rather than as "names nothing".
14
+ */
15
+ export declare function classifyRefs(line: string): Array<[number, boolean]> | null;
16
+ /**
17
+ * Every `#N` reference in a chunk of STEP text, skipping quoted strings (where a
18
+ * `#` is literal) and any leading `#id=` (the line's own id). Reads bytes rather
19
+ * than decoded text so the reverse-reference scan never decodes a whole model.
20
+ */
21
+ export declare function argRefs(text: Uint8Array | string): number[];
22
+ /** The GlobalId (first quoted attribute) of a rooted entity's line. */
23
+ export declare function leadingGuid(line: string): string | null;
24
+ //# sourceMappingURL=merged-empty-containers-refs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merged-empty-containers-refs.d.ts","sourceRoot":"","sources":["../src/merged-empty-containers-refs.ts"],"names":[],"mappings":"AAkBA,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAG3D;AAED,gJAAgJ;AAChJ,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGpD;AAED,kDAAkD;AAClD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAW7C;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,CAuB1E;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,EAAE,CAyB3D;AAED,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASvD"}
@@ -0,0 +1,126 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * STEP reference reading for the empty-container analysis in
6
+ * `merged-empty-containers.ts` (#3643): which `#N` a line names, and where.
7
+ * Split out of that module when #5725 grew it past the module-size limit.
8
+ *
9
+ * Every reader here tolerates STEP trivia (whitespace and/or a `/* ... *​/`
10
+ * comment, #3789) between tokens; a line whose arguments cannot be read comes
11
+ * back `null`, which the analysis treats as "can't safely rewrite" and so blocks
12
+ * the drop, never as "names nothing".
13
+ */
14
+ import { readStepSlots, splitTopLevelListItems } from './step-argument-parser.js';
15
+ import { BARE_REF_RE } from './reference-collector.js';
16
+ /** Top-level arguments of a `#id=TYPE(…);` line, or `null` when unparseable. */
17
+ export function topLevelAttrs(line) {
18
+ const record = readStepSlots(line);
19
+ return record === null ? null : record.slots.map(arg => arg.trim());
20
+ }
21
+ /** `"#7"` → `7` via shared, trivia-tolerant {@link BARE_REF_RE} (#4227 — a narrower regex misclassified a comment-wrapped `RelatingObject`). */
22
+ export function singleRef(arg) {
23
+ const match = BARE_REF_RE.exec(arg);
24
+ return match ? Number(match[1]) : null;
25
+ }
26
+ /** Parse a `(#a,#b,…)` list argument into ids. */
27
+ export function refList(arg) {
28
+ const trimmed = arg.trim();
29
+ if (!trimmed.startsWith('(') || !trimmed.endsWith(')'))
30
+ return [];
31
+ const inner = trimmed.slice(1, -1).trim();
32
+ if (inner === '')
33
+ return [];
34
+ const ids = [];
35
+ for (const item of splitTopLevelListItems(inner)) {
36
+ const id = singleRef(item);
37
+ if (id !== null)
38
+ ids.push(id);
39
+ }
40
+ return ids;
41
+ }
42
+ /**
43
+ * Every reference in a line's argument list, paired with whether it sits
44
+ * somewhere neither `filterHiddenRefsFromRelationshipLine` can strip (a direct
45
+ * list element) nor withhold the line for (a whole single-valued attribute) —
46
+ * i.e. nested inside a list of lists or a typed value. `null` when the line's
47
+ * argument list cannot be parsed at all: a line no rewrite can narrow, which the
48
+ * analysis must treat as a blocker rather than as "names nothing".
49
+ */
50
+ export function classifyRefs(line) {
51
+ const attrs = topLevelAttrs(line);
52
+ if (attrs === null)
53
+ return null;
54
+ const out = [];
55
+ for (const attr of attrs) {
56
+ const direct = singleRef(attr);
57
+ if (direct !== null) {
58
+ out.push([direct, false]);
59
+ continue;
60
+ }
61
+ if (attr.startsWith('(') && attr.endsWith(')')) {
62
+ const inner = attr.slice(1, -1).trim();
63
+ if (inner === '')
64
+ continue;
65
+ for (const item of splitTopLevelListItems(inner)) {
66
+ const id = singleRef(item);
67
+ if (id !== null)
68
+ out.push([id, false]);
69
+ else
70
+ for (const nested of argRefs(item))
71
+ out.push([nested, true]);
72
+ }
73
+ continue;
74
+ }
75
+ for (const nested of argRefs(attr))
76
+ out.push([nested, true]);
77
+ }
78
+ return out;
79
+ }
80
+ /**
81
+ * Every `#N` reference in a chunk of STEP text, skipping quoted strings (where a
82
+ * `#` is literal) and any leading `#id=` (the line's own id). Reads bytes rather
83
+ * than decoded text so the reverse-reference scan never decodes a whole model.
84
+ */
85
+ export function argRefs(text) {
86
+ const bytes = typeof text === 'string' ? new TextEncoder().encode(text) : text;
87
+ const eq = bytes.indexOf(0x3d /* = */);
88
+ const from = eq === -1 ? 0 : eq + 1;
89
+ const out = [];
90
+ let inString = false;
91
+ for (let i = from; i < bytes.length; i++) {
92
+ const byte = bytes[i];
93
+ if (byte === 0x27 /* ' */) {
94
+ inString = !inString;
95
+ continue;
96
+ }
97
+ if (inString || byte !== 0x23 /* # */)
98
+ continue;
99
+ let j = i + 1;
100
+ let value = 0;
101
+ while (j < bytes.length && bytes[j] >= 0x30 && bytes[j] <= 0x39) {
102
+ value = value * 10 + (bytes[j] - 0x30);
103
+ j++;
104
+ }
105
+ if (j > i + 1) {
106
+ out.push(value);
107
+ i = j - 1;
108
+ }
109
+ }
110
+ return out;
111
+ }
112
+ /** The GlobalId (first quoted attribute) of a rooted entity's line. */
113
+ export function leadingGuid(line) {
114
+ const open = line.indexOf('(');
115
+ if (open === -1)
116
+ return null;
117
+ const first = line.indexOf("'", open + 1);
118
+ if (first === -1)
119
+ return null;
120
+ const second = line.indexOf("'", first + 1);
121
+ if (second === -1)
122
+ return null;
123
+ const raw = line.slice(first + 1, second);
124
+ return /^[0-9A-Za-z_$]{22}$/.test(raw) ? raw : null;
125
+ }
126
+ //# sourceMappingURL=merged-empty-containers-refs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merged-empty-containers-refs.js","sourceRoot":"","sources":["../src/merged-empty-containers-refs.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,35 @@ export interface EmptyContainerModelView {
36
36
  offset: number;
37
37
  /** True when the model unifies into the first model's project / unit space. */
38
38
  compatible: boolean;
39
+ /**
40
+ * Run this model's one-parent claim pass (#5471) and return the aggregation
41
+ * edges it withholds. Called once per model in merge order, on a claim set
42
+ * of the plan's own. Omitted: every edge counts.
43
+ *
44
+ * The drops must be known before emission, yet the emit-time claim pass
45
+ * withholds aggregation edges, and a container that loses its only edge is
46
+ * empty (#5725). This pass runs with no drops applied, and that is already
47
+ * the fixed point: a claim that would differ once drops are known needs a
48
+ * dropped parent, but a parent whose kept edge reaches a kept child is kept
49
+ * itself. That holds only if this pass never withholds an edge the emit pass
50
+ * writes, or a still-full child loses its only parent. So it may see LESS
51
+ * than the emit pass, never more. It claims only through
52
+ * {@link isStructureRelation} types, and resolves ids through `sharedRemap`
53
+ * (spatial unification, which the emit pass repeats exactly), not through
54
+ * this module's GlobalId canonicalisation, which the emit pass does not
55
+ * always repeat (a GlobalId duplicated within one model, or an
56
+ * `assume-shared` emitter in another unit). What it misses is an edge the
57
+ * emit pass withholds and this one counts, which only keeps a container
58
+ * (#3643's behaviour before #5725).
59
+ */
60
+ claimParents?: () => WithheldParents;
61
+ }
62
+ /** Aggregation edges the one-parent pass keeps out of the output (#5725). */
63
+ export interface WithheldParents {
64
+ /** Rels skipped outright: every member already had a parent. */
65
+ skipEntityIds: ReadonlySet<number>;
66
+ /** Rel → members stripped from its RelatedObjects list. */
67
+ relParentStrip: ReadonlyMap<number, ReadonlySet<number>>;
39
68
  }
40
69
  /** Which containers the emit loop must not write. */
41
70
  export interface EmptyContainerPlan {
@@ -45,6 +74,8 @@ export interface EmptyContainerPlan {
45
74
  * across three inputs counts once). */
46
75
  droppedCount: number;
47
76
  }
77
+ /** True when `typeUpper` is a relationship the analysis counts as spatial structure. */
78
+ export declare function isStructureRelation(typeUpper: string): boolean;
48
79
  /**
49
80
  * A model with nothing to analyse — the placeholder for an input whose source
50
81
  * bytes are missing (a cache-restored, metadata-only store), which the emit loop
@@ -1 +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;AAIvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAgCjE,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"}
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;AAwBjE,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;IACpB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,YAAY,CAAC,EAAE,MAAM,eAAe,CAAC;CACtC;AAED,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,2DAA2D;IAC3D,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;CAC1D;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;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE9D;AAOD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,uBAO9B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,uBAAuB,EAAE,GAAG,kBAAkB,CA2C7F"}
@@ -2,16 +2,7 @@
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
4
  import { asSourceBytes } from '@ifc-lite/parser';
5
- import { readStepSlots, splitTopLevelListItems } from './step-argument-parser.js';
6
- import { BARE_REF_RE } from './reference-collector.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 line's arguments are unreadable here (`topLevelAttrs` returns
12
- * `null`), which the caller treats as "can't safely rewrite" and blocks the
13
- * empty-container drop for that entity.
14
- */
5
+ import { argRefs, classifyRefs, leadingGuid, refList, singleRef, topLevelAttrs } from './merged-empty-containers-refs.js';
15
6
  /**
16
7
  * Spatial container types dropped when they end up empty. `IfcProject` is
17
8
  * deliberately absent: it is the file's root, never a candidate.
@@ -32,6 +23,10 @@ const STRUCTURE_RELATIONS = {
32
23
  IFCRELCONTAINEDINSPATIALSTRUCTURE: [5, 4],
33
24
  IFCRELREFERENCEDINSPATIALSTRUCTURE: [5, 4],
34
25
  };
26
+ /** True when `typeUpper` is a relationship the analysis counts as spatial structure. */
27
+ export function isStructureRelation(typeUpper) {
28
+ return typeUpper in STRUCTURE_RELATIONS;
29
+ }
35
30
  /** True when `typeUpper` is a droppable spatial container type. */
36
31
  function isSpatialContainerType(typeUpper) {
37
32
  return CONTAINER_TYPES.has(typeUpper);
@@ -70,7 +65,7 @@ export function planEmptyContainerDrops(models) {
70
65
  containersByModel.push(canon);
71
66
  for (const node of canon.values())
72
67
  nodes.add(node);
73
- recordEdges(view, canon, hasContent, parents);
68
+ recordEdges(view, canon, hasContent, parents, view.claimParents?.());
74
69
  recordBlocks(view, canon, blocked);
75
70
  }
76
71
  // A container is non-empty when it holds something, is blocked, or has a
@@ -138,14 +133,17 @@ function canonicalContainers(view, guidNode) {
138
133
  /**
139
134
  * Record what this model contributes to each container: contained elements and
140
135
  * aggregated objects become content; aggregated spatial children become upward
141
- * edges, so a non-empty storey keeps its building and its site.
136
+ * edges, so a non-empty storey keeps its building and its site. An aggregation
137
+ * edge the one-parent pass withholds is never written, so it counts for nothing.
142
138
  */
143
- function recordEdges(view, canon, hasContent, parents) {
139
+ function recordEdges(view, canon, hasContent, parents, withheld) {
144
140
  if (canon.size === 0)
145
141
  return;
146
142
  for (const [localId, ref] of view.entities) {
147
143
  if (view.included !== null && !view.included.has(localId))
148
144
  continue;
145
+ if (withheld?.skipEntityIds.has(localId))
146
+ continue;
149
147
  const slots = STRUCTURE_RELATIONS[ref.type.toUpperCase()];
150
148
  if (slots === undefined)
151
149
  continue;
@@ -161,9 +159,12 @@ function recordEdges(view, canon, hasContent, parents) {
161
159
  const parent = canon.get(relating);
162
160
  if (parent === undefined)
163
161
  continue;
162
+ const stripped = withheld?.relParentStrip.get(localId);
164
163
  for (const child of refList(attrs[relatedIndex] ?? '')) {
165
164
  if (view.included !== null && !view.included.has(child))
166
165
  continue;
166
+ if (stripped?.has(child))
167
+ continue;
167
168
  const childNode = canon.get(child);
168
169
  if (childNode === undefined) {
169
170
  hasContent.add(parent);
@@ -239,114 +240,4 @@ function lineOf(view, localId) {
239
240
  return '';
240
241
  return view.source.decodeUtf8(ref.byteOffset, ref.byteOffset + ref.byteLength);
241
242
  }
242
- /** Top-level arguments of a `#id=TYPE(…);` line, or `null` when unparseable. */
243
- function topLevelAttrs(line) {
244
- const record = readStepSlots(line);
245
- return record === null ? null : record.slots.map(arg => arg.trim());
246
- }
247
- /** `"#7"` → `7` via shared, trivia-tolerant {@link BARE_REF_RE} (#4227 — a narrower regex misclassified a comment-wrapped `RelatingObject`). */
248
- function singleRef(arg) {
249
- const match = BARE_REF_RE.exec(arg);
250
- return match ? Number(match[1]) : null;
251
- }
252
- /** Parse a `(#a,#b,…)` list argument into ids. */
253
- function refList(arg) {
254
- const trimmed = arg.trim();
255
- if (!trimmed.startsWith('(') || !trimmed.endsWith(')'))
256
- return [];
257
- const inner = trimmed.slice(1, -1).trim();
258
- if (inner === '')
259
- return [];
260
- const ids = [];
261
- for (const item of splitTopLevelListItems(inner)) {
262
- const id = singleRef(item);
263
- if (id !== null)
264
- ids.push(id);
265
- }
266
- return ids;
267
- }
268
- /**
269
- * Every reference in a line's argument list, paired with whether it sits
270
- * somewhere neither `filterHiddenRefsFromRelationshipLine` can strip (a direct
271
- * list element) nor withhold the line for (a whole single-valued attribute) —
272
- * i.e. nested inside a list of lists or a typed value. `null` when the line's
273
- * argument list cannot be parsed at all: a line no rewrite can narrow, which the
274
- * analysis must treat as a blocker rather than as "names nothing".
275
- */
276
- function classifyRefs(line) {
277
- const attrs = topLevelAttrs(line);
278
- if (attrs === null)
279
- return null;
280
- const out = [];
281
- for (const attr of attrs) {
282
- const direct = singleRef(attr);
283
- if (direct !== null) {
284
- out.push([direct, false]);
285
- continue;
286
- }
287
- if (attr.startsWith('(') && attr.endsWith(')')) {
288
- const inner = attr.slice(1, -1).trim();
289
- if (inner === '')
290
- continue;
291
- for (const item of splitTopLevelListItems(inner)) {
292
- const id = singleRef(item);
293
- if (id !== null)
294
- out.push([id, false]);
295
- else
296
- for (const nested of argRefs(item))
297
- out.push([nested, true]);
298
- }
299
- continue;
300
- }
301
- for (const nested of argRefs(attr))
302
- out.push([nested, true]);
303
- }
304
- return out;
305
- }
306
- /**
307
- * Every `#N` reference in a chunk of STEP text, skipping quoted strings (where a
308
- * `#` is literal) and any leading `#id=` (the line's own id). Reads bytes rather
309
- * than decoded text so the reverse-reference scan never decodes a whole model.
310
- */
311
- function argRefs(text) {
312
- const bytes = typeof text === 'string' ? new TextEncoder().encode(text) : text;
313
- const eq = bytes.indexOf(0x3d /* = */);
314
- const from = eq === -1 ? 0 : eq + 1;
315
- const out = [];
316
- let inString = false;
317
- for (let i = from; i < bytes.length; i++) {
318
- const byte = bytes[i];
319
- if (byte === 0x27 /* ' */) {
320
- inString = !inString;
321
- continue;
322
- }
323
- if (inString || byte !== 0x23 /* # */)
324
- continue;
325
- let j = i + 1;
326
- let value = 0;
327
- while (j < bytes.length && bytes[j] >= 0x30 && bytes[j] <= 0x39) {
328
- value = value * 10 + (bytes[j] - 0x30);
329
- j++;
330
- }
331
- if (j > i + 1) {
332
- out.push(value);
333
- i = j - 1;
334
- }
335
- }
336
- return out;
337
- }
338
- /** The GlobalId (first quoted attribute) of a rooted entity's line. */
339
- function leadingGuid(line) {
340
- const open = line.indexOf('(');
341
- if (open === -1)
342
- return null;
343
- const first = line.indexOf("'", open + 1);
344
- if (first === -1)
345
- return null;
346
- const second = line.indexOf("'", first + 1);
347
- if (second === -1)
348
- return null;
349
- const raw = line.slice(first + 1, second);
350
- return /^[0-9A-Za-z_$]{22}$/.test(raw) ? raw : null;
351
- }
352
243
  //# sourceMappingURL=merged-empty-containers.js.map