@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.
- package/dist/merged-decomposition-parents.d.ts +99 -0
- package/dist/merged-decomposition-parents.d.ts.map +1 -0
- package/dist/merged-decomposition-parents.js +142 -0
- package/dist/merged-decomposition-parents.js.map +1 -0
- package/dist/merged-empty-containers-refs.d.ts +24 -0
- package/dist/merged-empty-containers-refs.d.ts.map +1 -0
- package/dist/merged-empty-containers-refs.js +126 -0
- package/dist/merged-empty-containers-refs.js.map +1 -0
- package/dist/merged-empty-containers.d.ts +31 -0
- package/dist/merged-empty-containers.d.ts.map +1 -1
- package/dist/merged-empty-containers.js +14 -123
- package/dist/merged-empty-containers.js.map +1 -1
- package/dist/merged-exporter.d.ts +4 -1
- package/dist/merged-exporter.d.ts.map +1 -1
- package/dist/merged-exporter.js +30 -24
- package/dist/merged-exporter.js.map +1 -1
- package/dist/parquet-exporter.d.ts.map +1 -1
- package/dist/parquet-exporter.js +23 -23
- package/dist/parquet-exporter.js.map +1 -1
- package/dist/parquet-spatial-rows.d.ts +14 -0
- package/dist/parquet-spatial-rows.d.ts.map +1 -0
- package/dist/parquet-spatial-rows.js +57 -0
- package/dist/parquet-spatial-rows.js.map +1 -0
- package/package.json +9 -8
- package/dist/merged-rel-aggregates.d.ts +0 -63
- package/dist/merged-rel-aggregates.d.ts.map +0 -1
- package/dist/merged-rel-aggregates.js +0 -122
- package/dist/merged-rel-aggregates.js.map +0 -1
|
@@ -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;
|
|
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 {
|
|
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
|