@ifc-lite/cli 0.27.0 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commands/bcf.js +1 -1
- package/dist/commands/bcf.js.map +1 -1
- package/dist/commands/delivery-checks.d.ts +56 -0
- package/dist/commands/delivery-checks.d.ts.map +1 -0
- package/dist/commands/delivery-checks.js +222 -0
- package/dist/commands/delivery-checks.js.map +1 -0
- package/dist/commands/delivery-recipe.d.ts +23 -0
- package/dist/commands/delivery-recipe.d.ts.map +1 -0
- package/dist/commands/delivery-recipe.js +104 -0
- package/dist/commands/delivery-recipe.js.map +1 -0
- package/dist/commands/delivery-report.d.ts +51 -0
- package/dist/commands/delivery-report.d.ts.map +1 -0
- package/dist/commands/delivery-report.js +114 -0
- package/dist/commands/delivery-report.js.map +1 -0
- package/dist/commands/delivery.d.ts +2 -0
- package/dist/commands/delivery.d.ts.map +1 -0
- package/dist/commands/delivery.js +127 -0
- package/dist/commands/delivery.js.map +1 -0
- package/dist/commands/export-rust-formats.d.ts +11 -0
- package/dist/commands/export-rust-formats.d.ts.map +1 -0
- package/dist/commands/export-rust-formats.js +161 -0
- package/dist/commands/export-rust-formats.js.map +1 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +31 -145
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/extract-entities.d.ts +10 -17
- package/dist/commands/extract-entities.d.ts.map +1 -1
- package/dist/commands/extract-entities.js +34 -23
- package/dist/commands/extract-entities.js.map +1 -1
- package/dist/commands/gym.js +2 -2
- package/dist/commands/mutate.d.ts +16 -4
- package/dist/commands/mutate.d.ts.map +1 -1
- package/dist/commands/mutate.js +38 -46
- package/dist/commands/mutate.js.map +1 -1
- package/dist/commands/step-args.d.ts +114 -0
- package/dist/commands/step-args.d.ts.map +1 -0
- package/dist/commands/step-args.js +299 -0
- package/dist/commands/step-args.js.map +1 -0
- package/dist/commands/subset-relations.d.ts +124 -0
- package/dist/commands/subset-relations.d.ts.map +1 -0
- package/dist/commands/subset-relations.js +273 -0
- package/dist/commands/subset-relations.js.map +1 -0
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
|
@@ -0,0 +1,299 @@
|
|
|
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
|
+
* Reading a STEP record's top-level argument list out of its text, for a
|
|
6
|
+
* caller that then edits one slot BY INDEX.
|
|
7
|
+
*
|
|
8
|
+
* The failure this exists to prevent is silent. A scanner that loses track of
|
|
9
|
+
* quote state or paren depth still produces parts; they are just not the
|
|
10
|
+
* record's arguments any more, because the commas it swallowed took every
|
|
11
|
+
* following slot with them. The write lands on whatever the mis-scan
|
|
12
|
+
* accumulated and the caller reports a success that did not happen
|
|
13
|
+
* (LTplus-AG/ifc-lite#2470, #4125 for the `mutate` instance). So this returns
|
|
14
|
+
* null rather than parts it does not believe in.
|
|
15
|
+
*
|
|
16
|
+
* The mis-scan nothing structural notices is an undoubled apostrophe, what an
|
|
17
|
+
* authoring tool emits when it forgets to double one. The scan closes the
|
|
18
|
+
* string at it and reopens on the next quote, so the text between the two is
|
|
19
|
+
* read inside-out and every comma in it is swallowed. TWO of them leave quote
|
|
20
|
+
* parity EVEN and paren depth at ZERO, so the scan ends clean on text that
|
|
21
|
+
* split wrong: `'guid',$,'John's wall',$,$,$,$,'A's',.NOTDEFINED.` is nine
|
|
22
|
+
* attributes read as four, and writing slot 2 deletes attributes 3 to 7.
|
|
23
|
+
*
|
|
24
|
+
* What always shows is a token ending where a token cannot end: the phantom
|
|
25
|
+
* terminator leaves the rest of the content where a separator belongs
|
|
26
|
+
* (`'John's wall'` closes after `John`, leaving a bare `s`). So this validates
|
|
27
|
+
* the list as a grammar rather than counting characters. An argument is ONE
|
|
28
|
+
* token, after a token only whitespace and then `,` or `)` may follow, and that
|
|
29
|
+
* (In the STEP examples below, `*\/` is a JavaScript escape so the example does not
|
|
30
|
+
* terminate this comment block. The backslash is not in the STEP text, and STEP
|
|
31
|
+
* gives `\` its own meaning, so strip it before copying an example into a test.)
|
|
32
|
+
*
|
|
33
|
+
* holds AT EVERY DEPTH. Applied to the top level alone it misses a phantom that
|
|
34
|
+
* swallows a `)`, a `(` and the comma between them, which leaves the depth
|
|
35
|
+
* balanced and each surviving part passing a token check on its own:
|
|
36
|
+
* `IFCLABEL('a's'),$,IFCLABEL('b's'),$` is four attributes read as two. Any
|
|
37
|
+
* two top-level slots that each hold a string inside parens can do it, and real
|
|
38
|
+
* entities have that shape (`IfcPerson`'s MiddleNames / PrefixTitles,
|
|
39
|
+
* `IfcPropertyTableValue`'s DefiningValues / DefinedValues).
|
|
40
|
+
*
|
|
41
|
+
* What the rule cannot see is a corruption whose bytes are themselves a valid
|
|
42
|
+
* argument list: a stray apostrophe at the END of a string's content emits a
|
|
43
|
+
* doubled quote by accident, so `''','''` is two arguments to its author and
|
|
44
|
+
* one to anyone reading the text. A generated sweep accepted 814 of 80,035 corruptions
|
|
45
|
+
* with a split other than the author's, down from 1,066 before the rule reached into
|
|
46
|
+
* lists, and every survivor it looked at had that shape; separating them needs the
|
|
47
|
+
* schema, not the text. That harness is NOT committed, so those figures record one run
|
|
48
|
+
* and cannot be reproduced from this tree.
|
|
49
|
+
*
|
|
50
|
+
* A STEP block comment is refused rather than understood, because `mutate` must
|
|
51
|
+
* not rewrite a record it cannot read: `/` breaks a bare run, so a part
|
|
52
|
+
* carrying a comment outside a string is never one token, whatever the comment
|
|
53
|
+
* CONTAINS. Whitespace in the comment is not what gives it away:
|
|
54
|
+
* `$,/*renamed*\/,$` has none and was read as three slots for two attributes,
|
|
55
|
+
* the same phantom-slot shift as the rest of #4125 (see {@link isTokenBreak}
|
|
56
|
+
* for why breaking on the OPENER is enough).
|
|
57
|
+
*
|
|
58
|
+
* Several readers in this repo DO skip comments; diverging from them is
|
|
59
|
+
* deliberate. The nearest is `validate.ts` in this very package, which skips
|
|
60
|
+
* `/* ... *\/` while counting top-level attribute indices; it returns indices
|
|
61
|
+
* rather than parts, so it never has to put the bytes back. `source-header.ts`'s
|
|
62
|
+
* `splitTopLevel` is closer still, since it returns PARTS, but it drops the
|
|
63
|
+
* comment bytes, so it cannot satisfy the round-trip contract below either.
|
|
64
|
+
* And `packages/parser`'s
|
|
65
|
+
* `entity-extractor.ts` reads comments at every depth through
|
|
66
|
+
* `StepTextScan.skipLexicalAt`, and says so as policy: one comment-skip rule for
|
|
67
|
+
* decoded STEP text rather than a fourth hand-rolled copy. The difference is what
|
|
68
|
+
* the two produce. That one extracts VALUES and collapses a comment to a space;
|
|
69
|
+
* this one must satisfy `parts.join(',') === input` byte for byte, because its
|
|
70
|
+
* caller rewrites the user's file. Skipping a comment under that contract would
|
|
71
|
+
* silently delete it from their file, and keeping it inside the part means the
|
|
72
|
+
* part is not one token. Doing both needs a richer return type than `string[]`,
|
|
73
|
+
* which is a different module. (`STEP_TRIVIA` itself is legal anywhere whitespace
|
|
74
|
+
* is. Its call sites are what place it, and they all put it immediately before a
|
|
75
|
+
* `\(`.)
|
|
76
|
+
*
|
|
77
|
+
* What this module does NOT cover, so its refusal is not read as more than it is:
|
|
78
|
+
* `mutate` finds records with a line regex that allows only whitespace before the
|
|
79
|
+
* `(`, so a comment after the class keyword is skipped BEFORE reaching this code,
|
|
80
|
+
* and the run reports success having changed nothing; and `lastIndexOf(')')` can
|
|
81
|
+
* slice into a legal trailing comment. Both are #4163, one on each SIDE of the
|
|
82
|
+
* argument list (before the `(`, after the `)`), and both are the same
|
|
83
|
+
* line-versus-record shape as the wrapped-record case #4163 also covers.
|
|
84
|
+
*
|
|
85
|
+
* `packages/export/src/step-argument-parser.ts` is the nearest copy, and this is
|
|
86
|
+
* deliberately its near-twin rather than an import: `@ifc-lite/export`'s
|
|
87
|
+
* `exports` map exposes only `.`, so reaching it would mean adding a published
|
|
88
|
+
* export (and an `api-surface` entry) to a v4.0.0 package to fix a CLI bug.
|
|
89
|
+
* #4125 tracks consolidating them (`derive-variants.mts` and
|
|
90
|
+
* `placement-datum.test.ts` are two the issue does not name), and that is a real merge rather
|
|
91
|
+
* than a no-op swap: its `splitTopLevelStepArguments` has the three structural
|
|
92
|
+
* checks and NO per-part check at all, which is exactly the top-level-only
|
|
93
|
+
* version the two paragraphs above show is insufficient. Measured, it splits
|
|
94
|
+
* `$,/* renamed *\/ $` into two parts and `'guid',$,'Name',/* c *\/,$` into five.
|
|
95
|
+
* The three structural checks are the contract the two DO share.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Split a STEP argument list on top-level commas, or return null when the text
|
|
99
|
+
* cannot be scanned as one. `input` is the text BETWEEN a record's outermost
|
|
100
|
+
* parentheses; the module header says why null rather than parts.
|
|
101
|
+
*
|
|
102
|
+
* Nothing is trimmed and nothing is normalised, so for input this accepts
|
|
103
|
+
* `parts.join(',')` reproduces `input` byte for byte and a caller that replaces
|
|
104
|
+
* one part leaves every other byte of the record alone. Rejected: a quote still
|
|
105
|
+
* open at the end, a paren depth that does not return to zero, a depth that
|
|
106
|
+
* ever goes NEGATIVE (a stray closing paren balanced by a later opening one),
|
|
107
|
+
* and a part that is not one token at every depth (see {@link isLoneStepToken}).
|
|
108
|
+
*
|
|
109
|
+
* An EMPTY top-level slot (`a,,b`, or a trailing comma) is NOT rejected: it is
|
|
110
|
+
* invalid STEP that costs no alignment, since an empty argument is one part
|
|
111
|
+
* exactly as the entity parser counts it, so every index still names the
|
|
112
|
+
* attribute it is meant to. An empty INPUT is not an empty slot: `#1=IFCFOO();`
|
|
113
|
+
* has no arguments, so it splits to `[]` and any slot request then fails the
|
|
114
|
+
* caller's bounds check.
|
|
115
|
+
*/
|
|
116
|
+
export function splitTopLevelStepArgs(input) {
|
|
117
|
+
if (input === '')
|
|
118
|
+
return [];
|
|
119
|
+
const parts = [];
|
|
120
|
+
let current = '';
|
|
121
|
+
let depth = 0;
|
|
122
|
+
let inString = false;
|
|
123
|
+
for (let i = 0; i < input.length; i++) {
|
|
124
|
+
const char = input[i];
|
|
125
|
+
if (char === "'") {
|
|
126
|
+
current += char;
|
|
127
|
+
// A doubled quote INSIDE a string is an escaped apostrophe. Outside one
|
|
128
|
+
// the first quote opens a string and the second is read on its own next
|
|
129
|
+
// pass, so `''` there is the empty string rather than an escape.
|
|
130
|
+
if (inString && input[i + 1] === "'") {
|
|
131
|
+
current += input[i + 1];
|
|
132
|
+
i++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
inString = !inString;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (!inString) {
|
|
139
|
+
if (char === '(') {
|
|
140
|
+
depth++;
|
|
141
|
+
}
|
|
142
|
+
else if (char === ')') {
|
|
143
|
+
depth--;
|
|
144
|
+
if (depth < 0)
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
else if (char === ',' && depth === 0) {
|
|
148
|
+
parts.push(current);
|
|
149
|
+
current = '';
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
current += char;
|
|
154
|
+
}
|
|
155
|
+
if (inString || depth !== 0)
|
|
156
|
+
return null;
|
|
157
|
+
parts.push(current);
|
|
158
|
+
// These three are the contract this shares with
|
|
159
|
+
// `packages/export/src/step-argument-parser.ts`, which has exactly them and no
|
|
160
|
+
// per-part check. That parity, not speed, is why they are kept: they add no
|
|
161
|
+
// coverage at all, and deleting any one of them, or all three, fails no test
|
|
162
|
+
// (measured). Only the `depth < 0` check above is an early exit; these two run
|
|
163
|
+
// after the whole scan and save only a walk over the parts. Kept as
|
|
164
|
+
// the shared spelling until #4125 merges the copies.
|
|
165
|
+
return parts.every(isLoneStepToken) ? parts : null;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Is `part` exactly ONE STEP argument, all the way down? Surrounding whitespace
|
|
169
|
+
* is ignored, because a record may carry it and this must not refuse a record
|
|
170
|
+
* it could rewrite. An empty part passes, as an empty slot is accepted above.
|
|
171
|
+
*/
|
|
172
|
+
function isLoneStepToken(part) {
|
|
173
|
+
const text = part.trim();
|
|
174
|
+
return text === '' || skipToken(text, 0) === text.length;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Index just past the one token starting at `from`, or -1 when the text there
|
|
178
|
+
* is not one token. The forms, per ISO 10303-21: a string (`'...'`, `''`
|
|
179
|
+
* escaping an apostrophe), a list (`(...)`), or a bare run (`$`, `*`, `#123`, a
|
|
180
|
+
* number, an enumeration `.T.`, a binary `"0F"`) optionally applied to a list
|
|
181
|
+
* (`IFCINTEGER(3)`).
|
|
182
|
+
*
|
|
183
|
+
* Deliberately loose about what a bare run CONTAINS and strict only about where
|
|
184
|
+
* it may end: this is not a STEP validator and must not become one. A list's
|
|
185
|
+
* elements are held to the same rule, so a fragment left by a phantom string
|
|
186
|
+
* terminator is caught wherever it lands. Nesting is carried in `depth` rather
|
|
187
|
+
* than by recursing, so a pathologically nested record (this text comes from a
|
|
188
|
+
* file) refuses instead of overflowing the stack.
|
|
189
|
+
*/
|
|
190
|
+
function skipToken(text, from) {
|
|
191
|
+
let i = from;
|
|
192
|
+
let depth = 0;
|
|
193
|
+
let expectToken = true;
|
|
194
|
+
for (;;) {
|
|
195
|
+
i += countWhitespace(text, i);
|
|
196
|
+
const char = text[i];
|
|
197
|
+
if (expectToken) {
|
|
198
|
+
// A list element may be empty (`(1,,2)`), as a top-level slot may be.
|
|
199
|
+
if (depth > 0 && (char === ',' || char === ')')) {
|
|
200
|
+
expectToken = false;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (char === "'") {
|
|
204
|
+
i = skipString(text, i);
|
|
205
|
+
if (i < 0)
|
|
206
|
+
return -1;
|
|
207
|
+
}
|
|
208
|
+
else if (char === '(') {
|
|
209
|
+
depth++;
|
|
210
|
+
i++;
|
|
211
|
+
continue; // the list's first element is still a token to read
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const start = i;
|
|
215
|
+
while (i < text.length && !isTokenBreak(text[i]))
|
|
216
|
+
i++;
|
|
217
|
+
// Nothing consumed: the end of the text, or a separator where a token
|
|
218
|
+
// belongs.
|
|
219
|
+
if (i === start)
|
|
220
|
+
return -1;
|
|
221
|
+
const afterKeyword = i + countWhitespace(text, i);
|
|
222
|
+
if (text[afterKeyword] === '(') {
|
|
223
|
+
depth++;
|
|
224
|
+
i = afterKeyword + 1;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
expectToken = false;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
// A token just closed, so only `,` or `)` may follow. At depth zero the
|
|
232
|
+
// token was the whole argument.
|
|
233
|
+
if (depth === 0)
|
|
234
|
+
return i;
|
|
235
|
+
if (char === ')') {
|
|
236
|
+
depth--;
|
|
237
|
+
i++;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (char !== ',')
|
|
241
|
+
return -1;
|
|
242
|
+
i++;
|
|
243
|
+
expectToken = true;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/** Index just past the string starting at `from`, or -1 if it never closes. */
|
|
247
|
+
function skipString(text, from) {
|
|
248
|
+
for (let i = from + 1; i < text.length; i++) {
|
|
249
|
+
if (text[i] !== "'")
|
|
250
|
+
continue;
|
|
251
|
+
if (text[i + 1] === "'")
|
|
252
|
+
i++;
|
|
253
|
+
else
|
|
254
|
+
return i + 1;
|
|
255
|
+
}
|
|
256
|
+
return -1;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Can this character only begin or separate another token?
|
|
260
|
+
*
|
|
261
|
+
* `/` is in the set for the STEP block comment, which opens with `/*`. Breaking
|
|
262
|
+
* on the OPENER is enough, and is why there is no comment state machine here: a
|
|
263
|
+
* comment can only sit where a token or a separator belongs, and either way the
|
|
264
|
+
* `/` ends the bare run short of the part's end, so `isLoneStepToken` refuses
|
|
265
|
+
* the part however the comment is written. `*` is deliberately NOT in the set,
|
|
266
|
+
* because it is the derived-attribute marker and a token in its own right, so
|
|
267
|
+
* breaking on it would refuse `$,$,*`.
|
|
268
|
+
*/
|
|
269
|
+
function isTokenBreak(char) {
|
|
270
|
+
return (char === "'" ||
|
|
271
|
+
char === '(' ||
|
|
272
|
+
char === ')' ||
|
|
273
|
+
char === ',' ||
|
|
274
|
+
char === '/' ||
|
|
275
|
+
char === ' ' ||
|
|
276
|
+
char === '\t');
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* How many whitespace characters run from `from`.
|
|
280
|
+
*
|
|
281
|
+
* Space and tab only, where every NAMED STEP whitespace set in this repo
|
|
282
|
+
* (`is_step_space`, `isSpaceByte`, `isAsciiSpace`, `STEP_TRIVIA`) is the six
|
|
283
|
+
* characters ` \t\n\r\x0b\x0c`. That is safe here only because the caller
|
|
284
|
+
* splits on newlines before this ever runs. `\n` and `\r` are in neither this
|
|
285
|
+
* set nor {@link isTokenBreak}, so a bare run spans them silently and two tokens
|
|
286
|
+
* separated by a newline come back as ONE part (measured: `'$\n$'` splits to
|
|
287
|
+
* `["$\n$"]`, where `'$ $'` and `'$\t$'` are refused). That is the same
|
|
288
|
+
* token-ending-where-it-cannot defect this module exists to catch, unreachable only
|
|
289
|
+
* because the caller pre-splits on newlines. #4163's multi-line work would
|
|
290
|
+
* start feeding real multi-line argument lists through here; widen both sets then
|
|
291
|
+
* rather than relying on that accident.
|
|
292
|
+
*/
|
|
293
|
+
function countWhitespace(text, from) {
|
|
294
|
+
let n = 0;
|
|
295
|
+
while (from + n < text.length && (text[from + n] === ' ' || text[from + n] === '\t'))
|
|
296
|
+
n++;
|
|
297
|
+
return n;
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=step-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-args.js","sourceRoot":"","sources":["../../src/commands/step-args.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spatial-structure relations for an extracted subset: which ones join it, and
|
|
3
|
+
* what text each one emits.
|
|
4
|
+
*
|
|
5
|
+
* `IfcRelContainedInSpatialStructure` is one-to-MANY, and a real exporter
|
|
6
|
+
* writes exactly ONE of them per storey, naming every product in that storey.
|
|
7
|
+
* An all-or-nothing rule (keep the relation only when every id it mentions is
|
|
8
|
+
* kept) therefore drops containment on ANY strict subset of a real model: the
|
|
9
|
+
* extracted products land outside the spatial tree and a viewer shows the
|
|
10
|
+
* storey with nothing under it. So the `RelatedElements` SET is rewritten down
|
|
11
|
+
* to the kept members instead of the relation being dropped.
|
|
12
|
+
*
|
|
13
|
+
* The no-dangling-reference invariant is unchanged: every `#id` in an emitted
|
|
14
|
+
* record is an id the subset keeps, and since #4128 the caller's forward
|
|
15
|
+
* closure keeps only ids the file DEFINES, so kept implies defined. That covers
|
|
16
|
+
* the ids this module CHOOSES; it does not cover the references inside a kept
|
|
17
|
+
* record, which are emitted verbatim, so a source file that already dangles
|
|
18
|
+
* still dangles. That is the rest of the rules:
|
|
19
|
+
* - the RELATING object (`RelatingStructure` / `RelatingObject`, the spatial
|
|
20
|
+
* parent) is a hard requirement. A containment with no parent is
|
|
21
|
+
* meaningless, and it would dangle. A KNOWN residual gap follows from that,
|
|
22
|
+
* and it is not this module's to close: `buildSubset` force-keeps only
|
|
23
|
+
* `IfcProject` / `IfcSite` / `IfcBuilding` / `IfcBuildingStorey`, so a
|
|
24
|
+
* product contained in an `IfcSpace` or `IfcSpatialZone` still has an
|
|
25
|
+
* unkept parent and still loses containment. Widening that type list is the
|
|
26
|
+
* wrong close, because it force-keeps every space AND its forward closure in
|
|
27
|
+
* every extraction; the right one is a BACKWARD closure from each kept
|
|
28
|
+
* product up its containment and aggregation edges, which keeps exactly the
|
|
29
|
+
* seeds' ancestors and needs no type list at all. Either way it is a change
|
|
30
|
+
* to how `buildSubset` seeds, not to the rule here. Filed as #4124.
|
|
31
|
+
* - an empty intersection drops the relation.
|
|
32
|
+
* - every other non-set reference must be kept too. That is attribute 1,
|
|
33
|
+
* `OwnerHistory`; usually it already is, because each kept product's own
|
|
34
|
+
* body names the same shared `IfcOwnerHistory` and the products' forward
|
|
35
|
+
* closure keeps it. An exporter that writes a PER-RELATIONSHIP
|
|
36
|
+
* `IfcOwnerHistory` referenced by nothing else hits this rule every time,
|
|
37
|
+
* and dropping there was the orphaned-storey symptom again (#4126). This
|
|
38
|
+
* function still takes no `parsed`, so it cannot close over such a
|
|
39
|
+
* reference; it REPORTS it in {@link SpatialRelationPlan.blockedOn} and the
|
|
40
|
+
* caller, which does have `parsed`, keeps it and replans. Purity is intact:
|
|
41
|
+
* `blockedOn` is a finding, not a mutation.
|
|
42
|
+
*
|
|
43
|
+
* A KNOWN gap: {@link keepWhole}, the fallback for a record this module could
|
|
44
|
+
* not read as its six attributes, still drops on the same private
|
|
45
|
+
* `OwnerHistory` and reports nothing. That is deliberate. Its references are
|
|
46
|
+
* read off the raw body with no established string boundaries, so a `#6` it
|
|
47
|
+
* names may be text rather than a reference, and forward-closing over it would
|
|
48
|
+
* re-create the hash-in-a-Name bug the scanner exists to avoid.
|
|
49
|
+
*
|
|
50
|
+
* A relation that loses NO member re-emits its source line verbatim, so an
|
|
51
|
+
* extraction that happened to keep every member does not churn.
|
|
52
|
+
*
|
|
53
|
+
* ## What this module re-implements, and why
|
|
54
|
+
*
|
|
55
|
+
* Three things here already exist in `@ifc-lite/export`, and all three are
|
|
56
|
+
* copied for ONE reason: that package's `exports` map exposes only `.`, and its
|
|
57
|
+
* `index.ts` re-exports none of them, so reaching any of them would mean adding
|
|
58
|
+
* a published export (and an `api-surface` entry) to a v4.0.0 package in order
|
|
59
|
+
* to fix a CLI bug.
|
|
60
|
+
*
|
|
61
|
+
* - `filterHiddenRefsFromRelationshipLine` (`reference-collector.ts`) is the
|
|
62
|
+
* same job, done better: it filters EVERY parenthesised attribute of ANY
|
|
63
|
+
* `IFCREL*` line against an `isExcluded` predicate, so it needs no slot
|
|
64
|
+
* table at all, and it carries an edge case this module has no equivalent
|
|
65
|
+
* of (`IfcRelConnectsStructuralMember`'s optional trailing placement).
|
|
66
|
+
* Adopting it would delete most of this file. The export barrier above is
|
|
67
|
+
* the whole reason it is not adopted here, and nothing else: fed only
|
|
68
|
+
* today's three types it would change no behaviour, because the CALLER
|
|
69
|
+
* picks which lines it sees. Widening the type set is the separate decision,
|
|
70
|
+
* and that is the one that changes which relations survive an extraction.
|
|
71
|
+
* Consolidation follow-up: #4125.
|
|
72
|
+
* - `splitTopLevelStepArguments` (`step-argument-parser.ts`): see
|
|
73
|
+
* {@link splitTopLevelArgs}.
|
|
74
|
+
* - `STRUCTURE_RELATIONS` (`merged-empty-containers.ts`): three lines, see
|
|
75
|
+
* below.
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* One parsed STEP record, as `extract-entities.ts`'s `parseStep` produces it.
|
|
79
|
+
* Declared HERE and imported there, rather than the other way round, so the
|
|
80
|
+
* dependency runs one way: `extract-entities.ts` imports this module, never
|
|
81
|
+
* the reverse.
|
|
82
|
+
*/
|
|
83
|
+
export interface StepRecord {
|
|
84
|
+
id: number;
|
|
85
|
+
type: string;
|
|
86
|
+
/** Argument text between the outermost parentheses. */
|
|
87
|
+
body: string;
|
|
88
|
+
/** The verbatim `#id= TYPE(...);` text. */
|
|
89
|
+
full: string;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The assembled subset: the ids to emit, plus the record text to emit for the
|
|
93
|
+
* relations whose related-objects SET was filtered. An id absent from
|
|
94
|
+
* `rewritten` emits its source line unchanged.
|
|
95
|
+
*/
|
|
96
|
+
export interface Subset {
|
|
97
|
+
keep: Set<number>;
|
|
98
|
+
rewritten: ReadonlyMap<number, string>;
|
|
99
|
+
}
|
|
100
|
+
/** What {@link planSpatialRelations} found: relations to add, and their text. */
|
|
101
|
+
export interface SpatialRelationPlan {
|
|
102
|
+
/** Relation ids that join the subset. */
|
|
103
|
+
add: number[];
|
|
104
|
+
/** Relation id → rewritten record text, for the ones that lost a member. */
|
|
105
|
+
rewritten: Map<number, string>;
|
|
106
|
+
/**
|
|
107
|
+
* Unkept non-SET references, in practice a relation-private `OwnerHistory`,
|
|
108
|
+
* of the relations that this plan dropped for THAT reason alone: their
|
|
109
|
+
* relating parent is kept and their member intersection is non-empty, so
|
|
110
|
+
* keeping these ids is all that stands between them and surviving. A caller
|
|
111
|
+
* holding the parsed model can close over them and replan (#4126). Every
|
|
112
|
+
* other drop is final and reports nothing here.
|
|
113
|
+
*/
|
|
114
|
+
blockedOn: number[];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Decide every spatial-structure relation against a kept-id set.
|
|
118
|
+
*
|
|
119
|
+
* Pure in both arguments: adding relation ids to `keep` afterwards cannot
|
|
120
|
+
* change any verdict, because a relation of these types references products,
|
|
121
|
+
* spatial containers and an OwnerHistory, never another relation.
|
|
122
|
+
*/
|
|
123
|
+
export declare function planSpatialRelations(instances: Iterable<StepRecord>, keep: ReadonlySet<number>): SpatialRelationPlan;
|
|
124
|
+
//# sourceMappingURL=subset-relations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subset-relations.d.ts","sourceRoot":"","sources":["../../src/commands/subset-relations.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,yCAAyC;IACzC,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,4EAA4E;IAC5E,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AA4CD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC/B,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GACxB,mBAAmB,CAarB"}
|