@ifc-lite/parser 5.2.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/columnar-entity-preparation.d.ts +3 -0
- package/dist/columnar-entity-preparation.d.ts.map +1 -1
- package/dist/columnar-entity-preparation.js +101 -11
- package/dist/columnar-entity-preparation.js.map +1 -1
- package/dist/columnar-parser-indexes.d.ts +17 -1
- package/dist/columnar-parser-indexes.d.ts.map +1 -1
- package/dist/columnar-parser-indexes.js +31 -21
- package/dist/columnar-parser-indexes.js.map +1 -1
- package/dist/columnar-parser.d.ts +10 -1
- package/dist/columnar-parser.d.ts.map +1 -1
- package/dist/columnar-parser.js +14 -2
- package/dist/columnar-parser.js.map +1 -1
- package/dist/cost-extractor.d.ts +10 -0
- package/dist/cost-extractor.d.ts.map +1 -0
- package/dist/cost-extractor.js +369 -0
- package/dist/cost-extractor.js.map +1 -0
- package/dist/cost-schedule-2x3.d.ts +31 -0
- package/dist/cost-schedule-2x3.d.ts.map +1 -0
- package/dist/cost-schedule-2x3.js +75 -0
- package/dist/cost-schedule-2x3.js.map +1 -0
- package/dist/cost-types.d.ts +105 -0
- package/dist/cost-types.d.ts.map +1 -0
- package/dist/cost-types.js +5 -0
- package/dist/cost-types.js.map +1 -0
- package/dist/data-store-transport.d.ts +1 -0
- package/dist/data-store-transport.d.ts.map +1 -1
- package/dist/data-store-transport.js +5 -29
- package/dist/data-store-transport.js.map +1 -1
- package/dist/drop-census.d.ts +136 -0
- package/dist/drop-census.d.ts.map +1 -0
- package/dist/drop-census.js +53 -0
- package/dist/drop-census.js.map +1 -0
- package/dist/entity-scanner.d.ts.map +1 -1
- package/dist/entity-scanner.js +10 -5
- package/dist/entity-scanner.js.map +1 -1
- package/dist/generated/ifc2x3/schema-registry.d.ts +50 -0
- package/dist/generated/ifc2x3/schema-registry.d.ts.map +1 -0
- package/dist/generated/ifc2x3/schema-registry.js +50052 -0
- package/dist/generated/ifc2x3/schema-registry.js.map +1 -0
- package/dist/generated/ifc4x3/schema-registry.d.ts +50 -0
- package/dist/generated/ifc4x3/schema-registry.d.ts.map +1 -0
- package/dist/generated/ifc4x3/schema-registry.js +71299 -0
- package/dist/generated/ifc4x3/schema-registry.js.map +1 -0
- package/dist/generated/schema-registry-by-version.d.ts +61 -0
- package/dist/generated/schema-registry-by-version.d.ts.map +1 -0
- package/dist/generated/schema-registry-by-version.js +86 -0
- package/dist/generated/schema-registry-by-version.js.map +1 -0
- package/dist/group-assignment-factor.d.ts +13 -0
- package/dist/group-assignment-factor.d.ts.map +1 -0
- package/dist/group-assignment-factor.js +30 -0
- package/dist/group-assignment-factor.js.map +1 -0
- package/dist/ifczip.d.ts +6 -0
- package/dist/ifczip.d.ts.map +1 -1
- package/dist/ifczip.js +20 -11
- package/dist/ifczip.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/on-demand-extractors.d.ts +1 -0
- package/dist/on-demand-extractors.d.ts.map +1 -1
- package/dist/on-demand-extractors.js +2 -1
- package/dist/on-demand-extractors.js.map +1 -1
- package/dist/property-value-parser.d.ts +10 -0
- package/dist/property-value-parser.d.ts.map +1 -1
- package/dist/property-value-parser.js +40 -1
- package/dist/property-value-parser.js.map +1 -1
- package/dist/scan-entities-balanced.d.ts.map +1 -1
- package/dist/scan-entities-balanced.js +8 -6
- package/dist/scan-entities-balanced.js.map +1 -1
- package/dist/scan-worker-lexing.d.ts +14 -0
- package/dist/scan-worker-lexing.d.ts.map +1 -0
- package/dist/scan-worker-lexing.js +101 -0
- package/dist/scan-worker-lexing.js.map +1 -0
- package/dist/scan-worker-source.d.ts +1 -1
- package/dist/scan-worker-source.d.ts.map +1 -1
- package/dist/scan-worker-source.js +61 -59
- package/dist/scan-worker-source.js.map +1 -1
- package/dist/schedule-extractor.d.ts +2 -80
- package/dist/schedule-extractor.d.ts.map +1 -1
- package/dist/schedule-extractor.js +100 -225
- package/dist/schedule-extractor.js.map +1 -1
- package/dist/schedule-serializer-builders.d.ts +28 -0
- package/dist/schedule-serializer-builders.d.ts.map +1 -0
- package/dist/schedule-serializer-builders.js +163 -0
- package/dist/schedule-serializer-builders.js.map +1 -0
- package/dist/schedule-serializer.d.ts +6 -0
- package/dist/schedule-serializer.d.ts.map +1 -1
- package/dist/schedule-serializer.js +26 -165
- package/dist/schedule-serializer.js.map +1 -1
- package/dist/schedule-types.d.ts +211 -0
- package/dist/schedule-types.d.ts.map +1 -0
- package/dist/schedule-types.js +221 -0
- package/dist/schedule-types.js.map +1 -0
- package/dist/spatial-hierarchy-ambiguity.d.ts +39 -0
- package/dist/spatial-hierarchy-ambiguity.d.ts.map +1 -0
- package/dist/spatial-hierarchy-ambiguity.js +25 -0
- package/dist/spatial-hierarchy-ambiguity.js.map +1 -0
- package/dist/spatial-hierarchy-attributes.d.ts +54 -0
- package/dist/spatial-hierarchy-attributes.d.ts.map +1 -0
- package/dist/spatial-hierarchy-attributes.js +145 -0
- package/dist/spatial-hierarchy-attributes.js.map +1 -0
- package/dist/spatial-hierarchy-builder.d.ts +0 -30
- package/dist/spatial-hierarchy-builder.d.ts.map +1 -1
- package/dist/spatial-hierarchy-builder.js +45 -167
- package/dist/spatial-hierarchy-builder.js.map +1 -1
- package/dist/spatial-hierarchy-canonical-parent.d.ts +52 -2
- package/dist/spatial-hierarchy-canonical-parent.d.ts.map +1 -1
- package/dist/spatial-hierarchy-canonical-parent.js +153 -4
- package/dist/spatial-hierarchy-canonical-parent.js.map +1 -1
- package/dist/step-lexing.d.ts +1 -1
- package/dist/step-lexing.d.ts.map +1 -1
- package/dist/step-lexing.js +1 -56
- package/dist/step-lexing.js.map +1 -1
- package/dist/step-record-boundary.d.ts +28 -0
- package/dist/step-record-boundary.d.ts.map +1 -0
- package/dist/step-record-boundary.js +108 -0
- package/dist/step-record-boundary.js.map +1 -0
- package/dist/step-record-boundary.vectors.d.ts +58 -0
- package/dist/step-record-boundary.vectors.d.ts.map +1 -0
- package/dist/step-record-boundary.vectors.js +80 -0
- package/dist/step-record-boundary.vectors.js.map +1 -0
- package/dist/structural-extractor.d.ts +9 -0
- package/dist/structural-extractor.d.ts.map +1 -0
- package/dist/structural-extractor.js +310 -0
- package/dist/structural-extractor.js.map +1 -0
- package/dist/structural-load-extractor.d.ts +147 -0
- package/dist/structural-load-extractor.d.ts.map +1 -0
- package/dist/structural-load-extractor.js +178 -0
- package/dist/structural-load-extractor.js.map +1 -0
- package/dist/structural-step-values.d.ts +51 -0
- package/dist/structural-step-values.d.ts.map +1 -0
- package/dist/structural-step-values.js +109 -0
- package/dist/structural-step-values.js.map +1 -0
- package/dist/structural-types.d.ts +145 -0
- package/dist/structural-types.d.ts.map +1 -0
- package/dist/structural-types.js +5 -0
- package/dist/structural-types.js.map +1 -0
- package/dist/tokenizer.d.ts +11 -5
- package/dist/tokenizer.d.ts.map +1 -1
- package/dist/tokenizer.js +75 -21
- package/dist/tokenizer.js.map +1 -1
- package/dist/wasm-init-retry.d.ts +6 -42
- package/dist/wasm-init-retry.d.ts.map +1 -1
- package/dist/wasm-init-retry.js +6 -99
- package/dist/wasm-init-retry.js.map +1 -1
- package/dist/wasm-panic-forward.d.ts +7 -63
- package/dist/wasm-panic-forward.d.ts.map +1 -1
- package/dist/wasm-panic-forward.js +7 -100
- package/dist/wasm-panic-forward.js.map +1 -1
- package/package.json +5 -4
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* function each mirrors.
|
|
12
12
|
*/
|
|
13
13
|
import { MAX_EXPRESS_ID } from './express-id.js';
|
|
14
|
+
import { WORKER_LEXING } from './scan-worker-lexing.js';
|
|
14
15
|
/**
|
|
15
16
|
* Self-contained entity scanner code (runs inside Web Worker).
|
|
16
17
|
* This is the same algorithm as StepTokenizer.scanEntitiesFast() but
|
|
@@ -36,51 +37,7 @@ self.onmessage = function(e) {
|
|
|
36
37
|
// (#3395). The worker runs from a Blob URL and cannot import at runtime, so
|
|
37
38
|
// the bound below is interpolated from express-id.ts when this template is
|
|
38
39
|
// evaluated -- one home for the number, not a copy that can drift.
|
|
39
|
-
|
|
40
|
-
function opensCommentAt(p) {
|
|
41
|
-
return p + 1 < len && buf[p] === 0x2F && buf[p + 1] === 0x2A;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Index just past the '*/' closing the comment at p, or -1 when it never
|
|
45
|
-
// closes. Counts the newlines it crosses so line numbers stay in step.
|
|
46
|
-
//
|
|
47
|
-
// Kept behaviourally identical to skipComment/skipTrivia in step-lexing.ts,
|
|
48
|
-
// which this cannot import: the worker source is a string, so this copy of
|
|
49
|
-
// the rule has to live here. Comments do not nest, per ISO 10303-21.
|
|
50
|
-
function skipCommentAt(p) {
|
|
51
|
-
var q = p + 2;
|
|
52
|
-
while (q + 1 < len) {
|
|
53
|
-
if (buf[q] === 0x2A && buf[q + 1] === 0x2F) return q + 2;
|
|
54
|
-
if (buf[q] === 0x0A) line++;
|
|
55
|
-
q++;
|
|
56
|
-
}
|
|
57
|
-
return -1;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Skip whitespace, comments, and any run of the two -- 10303-21 allows a
|
|
61
|
-
// comment wherever whitespace is allowed, INCLUDING inside a record.
|
|
62
|
-
// Returns -1 when a comment opens and never closes: everything from there on
|
|
63
|
-
// is inside it, so there is nothing left to find.
|
|
64
|
-
//
|
|
65
|
-
// The whitespace byte set (space, tab, CR, LF, form feed, vertical tab) is
|
|
66
|
-
// kept byte-for-byte in sync with isSpaceByte in step-lexing.ts and its
|
|
67
|
-
// three inline twins in tokenizer.ts's scanEntitiesFast -- this file is a
|
|
68
|
-
// string because a Blob worker cannot import at runtime, not a reason for
|
|
69
|
-
// the rule itself to drift.
|
|
70
|
-
function skipTriviaAt(p) {
|
|
71
|
-
for (;;) {
|
|
72
|
-
while (p < len) {
|
|
73
|
-
var t = buf[p];
|
|
74
|
-
if (t === 0x20 || t === 0x09 || t === 0x0D || t === 0x0C || t === 0x0B) { p++; }
|
|
75
|
-
else if (t === 0x0A) { line++; p++; }
|
|
76
|
-
else break;
|
|
77
|
-
}
|
|
78
|
-
if (!opensCommentAt(p)) return p;
|
|
79
|
-
var e = skipCommentAt(p);
|
|
80
|
-
if (e < 0) return -1;
|
|
81
|
-
p = e;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
40
|
+
${WORKER_LEXING}
|
|
84
41
|
|
|
85
42
|
var ids = new Uint32Array(estimatedCount);
|
|
86
43
|
var offsets = new Uint32Array(estimatedCount);
|
|
@@ -254,9 +211,22 @@ self.onmessage = function(e) {
|
|
|
254
211
|
if (buf[pos] !== 0x28) { declOpen = false; continue; }
|
|
255
212
|
declOpen = false; // Header complete: '(' found.
|
|
256
213
|
|
|
257
|
-
// Skip to semicolon (handling strings)
|
|
214
|
+
// Skip to semicolon (handling strings), bounded to THIS record's own
|
|
215
|
+
// body so a record missing its ';' cannot latch onto a later one and
|
|
216
|
+
// swallow what lies between (#4179): the ';' must be preceded, modulo
|
|
217
|
+
// trivia, by the ')' closing the parameter list, and no '=' may come
|
|
218
|
+
// before it outside a string or comment. On either failure the record is
|
|
219
|
+
// DROPPED and the scan resumes at that ')' rather than abandoning the
|
|
220
|
+
// file's tail. close_step_record in rust/core/src/parser/lexical.rs
|
|
221
|
+
// argues both rules and the recovery; this is a hand-duplicated copy
|
|
222
|
+
// because a Blob worker cannot import at runtime. Change them together.
|
|
223
|
+
var parenPos = pos;
|
|
258
224
|
var inString = false;
|
|
259
225
|
var foundTerminator = false;
|
|
226
|
+
// Memoised across the exact ';' check and the recovery below, which
|
|
227
|
+
// would otherwise balance the same record twice. -3 = not computed;
|
|
228
|
+
// -1 = unbalanced but readable; -2 = a literal/comment never closed.
|
|
229
|
+
var recordClose = -3;
|
|
260
230
|
while (pos < len) {
|
|
261
231
|
var c6 = buf[pos];
|
|
262
232
|
if (c6 === 0x27) { // quote
|
|
@@ -271,15 +241,26 @@ self.onmessage = function(e) {
|
|
|
271
241
|
// and parens inside it text -- the other direction of the rule the
|
|
272
242
|
// quote branch above gives for a '/*' inside a literal.
|
|
273
243
|
var ce = skipCommentAt(pos);
|
|
274
|
-
if (ce < 0)
|
|
275
|
-
// Unterminated: this record has no terminator, and neither has
|
|
276
|
-
// anything after it. Drop it and stop.
|
|
277
|
-
pos = len;
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
244
|
+
if (ce < 0) break; // Unterminated: recovery below finds no ')'.
|
|
280
245
|
pos = ce;
|
|
281
246
|
continue;
|
|
282
247
|
} else if (c6 === 0x3B && !inString) { // semicolon
|
|
248
|
+
// ')' modulo whitespace settles it for every record a real file
|
|
249
|
+
// holds. Anything else -- including the '/' of a trailing '*/',
|
|
250
|
+
// which a backwards walk cannot see through -- goes to the cold,
|
|
251
|
+
// exact balance-and-skip-trivia check.
|
|
252
|
+
var w = pos;
|
|
253
|
+
while (w > parenPos && isSpaceByteAt(w - 1)) w--;
|
|
254
|
+
if (buf[w - 1] !== 0x29) {
|
|
255
|
+
var savedBalLine = line;
|
|
256
|
+
recordClose = findEntityLengthAt(parenPos, startOffset);
|
|
257
|
+
line = savedBalLine;
|
|
258
|
+
if (recordClose <= 0) break;
|
|
259
|
+
recordClose += startOffset;
|
|
260
|
+
var after = skipTriviaAt(recordClose);
|
|
261
|
+
line = savedBalLine;
|
|
262
|
+
if (after < 0 || after !== pos) break;
|
|
263
|
+
}
|
|
283
264
|
var entityLength = pos - startOffset + 1;
|
|
284
265
|
|
|
285
266
|
// Grow if needed
|
|
@@ -295,19 +276,40 @@ self.onmessage = function(e) {
|
|
|
295
276
|
pos++;
|
|
296
277
|
foundTerminator = true;
|
|
297
278
|
break;
|
|
279
|
+
} else if (c6 === 0x3D && !inString) {
|
|
280
|
+
break; // '=': the NEXT declaration already started.
|
|
298
281
|
} else if (c6 === 0x0A) {
|
|
299
282
|
line++;
|
|
300
283
|
}
|
|
301
284
|
pos++;
|
|
302
285
|
}
|
|
303
286
|
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
// tokenizer.ts's scanEntitiesFast
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
if (!foundTerminator) {
|
|
287
|
+
// No ';' of this record's own. EVERY such exit lands here, so the
|
|
288
|
+
// recovery lives here once rather than at each break -- mirrors
|
|
289
|
+
// tokenizer.ts's scanEntitiesFast. Resume at the ')' balancing this
|
|
290
|
+
// record's own '(' when there is one, dropping just this record;
|
|
291
|
+
// otherwise run to len, ending the scan un-resynced rather than guessing
|
|
292
|
+
// a resume point from misaligned bytes.
|
|
293
|
+
if (!foundTerminator) {
|
|
294
|
+
stopped = true;
|
|
295
|
+
if (recordClose === -3) {
|
|
296
|
+
var savedRecLine = line;
|
|
297
|
+
recordClose = findEntityLengthAt(parenPos, startOffset);
|
|
298
|
+
line = savedRecLine;
|
|
299
|
+
if (recordClose > 0) recordClose += startOffset;
|
|
300
|
+
}
|
|
301
|
+
if (recordClose > 0) {
|
|
302
|
+
pos = recordClose;
|
|
303
|
+
line = startLine;
|
|
304
|
+
for (var q = startOffset; q < pos; q++) if (buf[q] === 0x0A) line++;
|
|
305
|
+
} else if (recordClose === -1) {
|
|
306
|
+
// Unbalanced but readable: re-hunt from past this record's '#'.
|
|
307
|
+
pos = startOffset + 1;
|
|
308
|
+
line = startLine;
|
|
309
|
+
} else {
|
|
310
|
+
pos = len; // Nothing to resume from (#3695).
|
|
311
|
+
}
|
|
312
|
+
}
|
|
311
313
|
} else if (ch === 0x0A) {
|
|
312
314
|
line++;
|
|
313
315
|
pos++;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan-worker-source.js","sourceRoot":"","sources":["../src/scan-worker-source.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"scan-worker-source.js","sourceRoot":"","sources":["../src/scan-worker-source.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD;;;;GAIG;AACH;4DAC4D;AAC5D,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;EAkBzB,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA0GS,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8OrC,CAAC"}
|
|
@@ -1,86 +1,8 @@
|
|
|
1
1
|
import type { IfcDataStore } from './columnar-parser.js';
|
|
2
2
|
import { parseIso8601Duration } from './iso8601-duration.js';
|
|
3
|
+
import type { SequenceTypeEnum, TaskDurationType, ScheduleTaskTimeInfo, ScheduleTaskInfo, ScheduleSequenceInfo, WorkScheduleInfo, ScheduleExtraction } from './schedule-types.js';
|
|
3
4
|
export { parseIso8601Duration };
|
|
4
|
-
export type SequenceTypeEnum
|
|
5
|
-
export type TaskDurationType = 'WORKTIME' | 'ELAPSEDTIME' | 'NOTDEFINED';
|
|
6
|
-
export interface ScheduleTaskTimeInfo {
|
|
7
|
-
scheduleStart?: string;
|
|
8
|
-
scheduleFinish?: string;
|
|
9
|
-
scheduleDuration?: string;
|
|
10
|
-
actualStart?: string;
|
|
11
|
-
actualFinish?: string;
|
|
12
|
-
actualDuration?: string;
|
|
13
|
-
earlyStart?: string;
|
|
14
|
-
earlyFinish?: string;
|
|
15
|
-
lateStart?: string;
|
|
16
|
-
lateFinish?: string;
|
|
17
|
-
freeFloat?: string;
|
|
18
|
-
totalFloat?: string;
|
|
19
|
-
remainingTime?: string;
|
|
20
|
-
durationType?: TaskDurationType;
|
|
21
|
-
statusTime?: string;
|
|
22
|
-
isCritical?: boolean;
|
|
23
|
-
completion?: number;
|
|
24
|
-
}
|
|
25
|
-
export interface ScheduleTaskInfo {
|
|
26
|
-
expressId: number;
|
|
27
|
-
globalId: string;
|
|
28
|
-
name: string;
|
|
29
|
-
description?: string;
|
|
30
|
-
objectType?: string;
|
|
31
|
-
identification?: string;
|
|
32
|
-
longDescription?: string;
|
|
33
|
-
status?: string;
|
|
34
|
-
workMethod?: string;
|
|
35
|
-
isMilestone: boolean;
|
|
36
|
-
priority?: number;
|
|
37
|
-
predefinedType?: string;
|
|
38
|
-
taskTime?: ScheduleTaskTimeInfo;
|
|
39
|
-
/** Parent task globalId (from IfcRelNests where this task is in RelatedObjects). */
|
|
40
|
-
parentGlobalId?: string;
|
|
41
|
-
/** Child task globalIds (from IfcRelNests where this task is RelatingObject). */
|
|
42
|
-
childGlobalIds: string[];
|
|
43
|
-
/** expressIds of products assigned to this task via IfcRelAssignsToProcess. */
|
|
44
|
-
productExpressIds: number[];
|
|
45
|
-
/** globalIds of the same products (aligned with productExpressIds by index). */
|
|
46
|
-
productGlobalIds: string[];
|
|
47
|
-
/** WorkSchedule globalIds that control this task via IfcRelAssignsToControl. */
|
|
48
|
-
controllingScheduleGlobalIds: string[];
|
|
49
|
-
}
|
|
50
|
-
export interface ScheduleSequenceInfo {
|
|
51
|
-
globalId: string;
|
|
52
|
-
relatingTaskGlobalId: string;
|
|
53
|
-
relatedTaskGlobalId: string;
|
|
54
|
-
sequenceType: SequenceTypeEnum;
|
|
55
|
-
userDefinedSequenceType?: string;
|
|
56
|
-
/** Lag value expressed in seconds if it resolves to an IfcDuration; otherwise undefined. */
|
|
57
|
-
timeLagSeconds?: number;
|
|
58
|
-
/** Original lag duration string (ISO 8601 like 'P1D'), if available. */
|
|
59
|
-
timeLagDuration?: string;
|
|
60
|
-
}
|
|
61
|
-
export interface WorkScheduleInfo {
|
|
62
|
-
expressId: number;
|
|
63
|
-
globalId: string;
|
|
64
|
-
kind: 'WorkSchedule' | 'WorkPlan';
|
|
65
|
-
name: string;
|
|
66
|
-
description?: string;
|
|
67
|
-
identification?: string;
|
|
68
|
-
creationDate?: string;
|
|
69
|
-
purpose?: string;
|
|
70
|
-
duration?: string;
|
|
71
|
-
startTime?: string;
|
|
72
|
-
finishTime?: string;
|
|
73
|
-
predefinedType?: string;
|
|
74
|
-
/** Root task globalIds directly assigned via IfcRelAssignsToControl. */
|
|
75
|
-
taskGlobalIds: string[];
|
|
76
|
-
}
|
|
77
|
-
export interface ScheduleExtraction {
|
|
78
|
-
workSchedules: WorkScheduleInfo[];
|
|
79
|
-
tasks: ScheduleTaskInfo[];
|
|
80
|
-
sequences: ScheduleSequenceInfo[];
|
|
81
|
-
/** True if we encountered any scheduling entity (useful for empty-state UI). */
|
|
82
|
-
hasSchedule: boolean;
|
|
83
|
-
}
|
|
5
|
+
export type { SequenceTypeEnum, TaskDurationType, ScheduleTaskTimeInfo, ScheduleTaskInfo, ScheduleSequenceInfo, WorkScheduleInfo, ScheduleExtraction, };
|
|
84
6
|
/**
|
|
85
7
|
* Extract all scheduling data from a parsed IFC store.
|
|
86
8
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schedule-extractor.d.ts","sourceRoot":"","sources":["../src/schedule-extractor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schedule-extractor.d.ts","sourceRoot":"","sources":["../src/schedule-extractor.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAoB7D,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAKhC,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,GACnB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,YAAY,GAAG,kBAAkB,CA4T/E"}
|
|
@@ -2,17 +2,25 @@
|
|
|
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
|
/**
|
|
5
|
-
* Schedule (4D) extractor —
|
|
5
|
+
* Schedule (4D) extractor — walks IfcTask, IfcTaskTime, IfcRelSequence,
|
|
6
6
|
* IfcRelAssignsToProcess, IfcRelAssignsToControl, IfcRelNests, IfcWorkSchedule,
|
|
7
|
-
* IfcWorkPlan, IfcLagTime
|
|
8
|
-
* ScheduleExtraction that the viewer can drive a Gantt/4D
|
|
7
|
+
* IfcWorkPlan, IfcLagTime entities in a parsed IfcDataStore and assembles a
|
|
8
|
+
* normalized ScheduleExtraction that the viewer can drive a Gantt/4D
|
|
9
|
+
* animation from.
|
|
9
10
|
*
|
|
10
11
|
* Handles IFC4 / IFC4X3. IFC2X3 has a different IfcTask layout (no TaskTime
|
|
11
12
|
* attribute, ScheduleStart/ScheduleFinish/TaskOwner instead) and is supported
|
|
12
13
|
* with best-effort degradation.
|
|
14
|
+
*
|
|
15
|
+
* STEP attribute-index layouts, the record types themselves, and the small
|
|
16
|
+
* pure/single-entity helpers (`asString` et al., `extractTaskTime`,
|
|
17
|
+
* `extractLagTimeSeconds`) live in `schedule-types.ts` — this file owns only
|
|
18
|
+
* the cross-entity orchestration (the multi-pass walk below that wires those
|
|
19
|
+
* records together via their IFC relationships).
|
|
13
20
|
*/
|
|
14
21
|
import { EntityExtractor } from './entity-extractor.js';
|
|
15
22
|
import { parseIso8601Duration } from './iso8601-duration.js';
|
|
23
|
+
import { TASK_ATTR, TASK_ATTR_2X3, REL_SEQUENCE_ATTR, REL_ASSIGNS_TO_PROCESS_ATTR, REL_ASSIGNS_TO_CONTROL_ATTR, REL_NESTS_ATTR, WORK_SCHEDULE_ATTR, WORK_PLAN_ATTR, asString, asNumber, asBoolean, asEnum, asRef, asRefList, sequenceTypeFromString, extractTaskTime, extractLagTimeSeconds, } from './schedule-types.js';
|
|
16
24
|
// Re-exported for backward compatibility — this is where consumers
|
|
17
25
|
// (including this package's own public surface, see index.ts) have always
|
|
18
26
|
// imported it from. The implementation itself now lives in
|
|
@@ -20,222 +28,6 @@ import { parseIso8601Duration } from './iso8601-duration.js';
|
|
|
20
28
|
// `secondsToIso8601Duration`, so the round-trip property between the two is
|
|
21
29
|
// visible in one place.
|
|
22
30
|
export { parseIso8601Duration };
|
|
23
|
-
/** IFC4 STEP attribute indices for IfcTask. */
|
|
24
|
-
const TASK_ATTR = {
|
|
25
|
-
GlobalId: 0,
|
|
26
|
-
Name: 2,
|
|
27
|
-
Description: 3,
|
|
28
|
-
ObjectType: 4,
|
|
29
|
-
Identification: 5,
|
|
30
|
-
LongDescription: 6,
|
|
31
|
-
Status: 7,
|
|
32
|
-
WorkMethod: 8,
|
|
33
|
-
IsMilestone: 9,
|
|
34
|
-
Priority: 10,
|
|
35
|
-
TaskTime: 11,
|
|
36
|
-
PredefinedType: 12,
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* IFC2X3 IfcTask layout — the attributes IfcTask itself adds over IfcObject
|
|
40
|
-
* (TaskId, Status, WorkMethod, IsMilestone, Priority). IFC2X3 schedule times
|
|
41
|
-
* live on `IfcScheduleTimeControl` and link to IfcTask via `IfcRelAssignsTasks`
|
|
42
|
-
* — we don't resolve those here yet; best-effort 2x3 models only surface task
|
|
43
|
-
* metadata without dates.
|
|
44
|
-
*/
|
|
45
|
-
const TASK_ATTR_2X3 = {
|
|
46
|
-
GlobalId: 0,
|
|
47
|
-
Name: 2,
|
|
48
|
-
Description: 3,
|
|
49
|
-
ObjectType: 4,
|
|
50
|
-
TaskId: 5,
|
|
51
|
-
Status: 6,
|
|
52
|
-
WorkMethod: 7,
|
|
53
|
-
IsMilestone: 8,
|
|
54
|
-
Priority: 9,
|
|
55
|
-
};
|
|
56
|
-
const TASK_TIME_ATTR = {
|
|
57
|
-
Name: 0,
|
|
58
|
-
DurationType: 3,
|
|
59
|
-
ScheduleDuration: 4,
|
|
60
|
-
ScheduleStart: 5,
|
|
61
|
-
ScheduleFinish: 6,
|
|
62
|
-
EarlyStart: 7,
|
|
63
|
-
EarlyFinish: 8,
|
|
64
|
-
LateStart: 9,
|
|
65
|
-
LateFinish: 10,
|
|
66
|
-
FreeFloat: 11,
|
|
67
|
-
TotalFloat: 12,
|
|
68
|
-
IsCritical: 13,
|
|
69
|
-
StatusTime: 14,
|
|
70
|
-
ActualDuration: 15,
|
|
71
|
-
ActualStart: 16,
|
|
72
|
-
ActualFinish: 17,
|
|
73
|
-
RemainingTime: 18,
|
|
74
|
-
Completion: 19,
|
|
75
|
-
};
|
|
76
|
-
const REL_SEQUENCE_ATTR = {
|
|
77
|
-
GlobalId: 0,
|
|
78
|
-
RelatingProcess: 4,
|
|
79
|
-
RelatedProcess: 5,
|
|
80
|
-
TimeLag: 6,
|
|
81
|
-
SequenceType: 7,
|
|
82
|
-
UserDefinedSequenceType: 8,
|
|
83
|
-
};
|
|
84
|
-
const REL_ASSIGNS_TO_PROCESS_ATTR = {
|
|
85
|
-
RelatedObjects: 4,
|
|
86
|
-
RelatingProcess: 6,
|
|
87
|
-
};
|
|
88
|
-
const REL_ASSIGNS_TO_CONTROL_ATTR = {
|
|
89
|
-
RelatedObjects: 4,
|
|
90
|
-
RelatingControl: 6,
|
|
91
|
-
};
|
|
92
|
-
const REL_NESTS_ATTR = {
|
|
93
|
-
RelatingObject: 4,
|
|
94
|
-
RelatedObjects: 5,
|
|
95
|
-
};
|
|
96
|
-
const WORK_SCHEDULE_ATTR = {
|
|
97
|
-
GlobalId: 0,
|
|
98
|
-
Name: 2,
|
|
99
|
-
Description: 3,
|
|
100
|
-
ObjectType: 4,
|
|
101
|
-
Identification: 5,
|
|
102
|
-
CreationDate: 6,
|
|
103
|
-
Purpose: 8,
|
|
104
|
-
Duration: 9,
|
|
105
|
-
TotalFloat: 10,
|
|
106
|
-
StartTime: 11,
|
|
107
|
-
FinishTime: 12,
|
|
108
|
-
PredefinedType: 13,
|
|
109
|
-
};
|
|
110
|
-
const WORK_PLAN_ATTR = WORK_SCHEDULE_ATTR;
|
|
111
|
-
const LAG_TIME_ATTR = {
|
|
112
|
-
LagValue: 3,
|
|
113
|
-
DurationType: 4,
|
|
114
|
-
};
|
|
115
|
-
function asString(v) {
|
|
116
|
-
if (typeof v === 'string' && v.length > 0)
|
|
117
|
-
return v;
|
|
118
|
-
return undefined;
|
|
119
|
-
}
|
|
120
|
-
function asNumber(v) {
|
|
121
|
-
if (typeof v === 'number' && Number.isFinite(v))
|
|
122
|
-
return v;
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
function asBoolean(v) {
|
|
126
|
-
if (typeof v === 'boolean')
|
|
127
|
-
return v;
|
|
128
|
-
// STEP booleans are typically stored as '.T.' / '.F.' after parsing
|
|
129
|
-
if (typeof v === 'string') {
|
|
130
|
-
if (v === '.T.' || v === 'T')
|
|
131
|
-
return true;
|
|
132
|
-
if (v === '.F.' || v === 'F')
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
return undefined;
|
|
136
|
-
}
|
|
137
|
-
function asEnum(v) {
|
|
138
|
-
if (typeof v !== 'string' || v.length === 0)
|
|
139
|
-
return undefined;
|
|
140
|
-
// STEP enum looks like .PLANNED.
|
|
141
|
-
const match = v.match(/^\.([A-Z_]+)\.$/);
|
|
142
|
-
return match ? match[1] : undefined;
|
|
143
|
-
}
|
|
144
|
-
function asRef(v) {
|
|
145
|
-
if (typeof v === 'number' && Number.isInteger(v) && v > 0)
|
|
146
|
-
return v;
|
|
147
|
-
return undefined;
|
|
148
|
-
}
|
|
149
|
-
function asRefList(v) {
|
|
150
|
-
if (!Array.isArray(v))
|
|
151
|
-
return [];
|
|
152
|
-
const out = [];
|
|
153
|
-
for (const x of v) {
|
|
154
|
-
const id = asRef(x);
|
|
155
|
-
if (id !== undefined)
|
|
156
|
-
out.push(id);
|
|
157
|
-
}
|
|
158
|
-
return out;
|
|
159
|
-
}
|
|
160
|
-
function sequenceTypeFromString(s) {
|
|
161
|
-
switch (s) {
|
|
162
|
-
case 'START_START':
|
|
163
|
-
case 'START_FINISH':
|
|
164
|
-
case 'FINISH_START':
|
|
165
|
-
case 'FINISH_FINISH':
|
|
166
|
-
case 'USERDEFINED':
|
|
167
|
-
case 'NOTDEFINED':
|
|
168
|
-
return s;
|
|
169
|
-
default:
|
|
170
|
-
return 'FINISH_START';
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
function durationTypeFromString(s) {
|
|
174
|
-
switch (s) {
|
|
175
|
-
case 'WORKTIME':
|
|
176
|
-
case 'ELAPSEDTIME':
|
|
177
|
-
case 'NOTDEFINED':
|
|
178
|
-
return s;
|
|
179
|
-
default:
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
function extractTaskTime(extractor, store, taskTimeId) {
|
|
184
|
-
const ref = store.entityIndex.byId.get(taskTimeId);
|
|
185
|
-
if (!ref)
|
|
186
|
-
return undefined;
|
|
187
|
-
const entity = extractor.extractEntity(ref);
|
|
188
|
-
if (!entity)
|
|
189
|
-
return undefined;
|
|
190
|
-
const t = entity.type.toUpperCase();
|
|
191
|
-
if (t !== 'IFCTASKTIME' && t !== 'IFCTASKTIMERECURRING')
|
|
192
|
-
return undefined;
|
|
193
|
-
const a = entity.attributes || [];
|
|
194
|
-
return {
|
|
195
|
-
durationType: durationTypeFromString(asEnum(a[TASK_TIME_ATTR.DurationType])),
|
|
196
|
-
scheduleDuration: asString(a[TASK_TIME_ATTR.ScheduleDuration]),
|
|
197
|
-
scheduleStart: asString(a[TASK_TIME_ATTR.ScheduleStart]),
|
|
198
|
-
scheduleFinish: asString(a[TASK_TIME_ATTR.ScheduleFinish]),
|
|
199
|
-
earlyStart: asString(a[TASK_TIME_ATTR.EarlyStart]),
|
|
200
|
-
earlyFinish: asString(a[TASK_TIME_ATTR.EarlyFinish]),
|
|
201
|
-
lateStart: asString(a[TASK_TIME_ATTR.LateStart]),
|
|
202
|
-
lateFinish: asString(a[TASK_TIME_ATTR.LateFinish]),
|
|
203
|
-
freeFloat: asString(a[TASK_TIME_ATTR.FreeFloat]),
|
|
204
|
-
totalFloat: asString(a[TASK_TIME_ATTR.TotalFloat]),
|
|
205
|
-
isCritical: asBoolean(a[TASK_TIME_ATTR.IsCritical]),
|
|
206
|
-
statusTime: asString(a[TASK_TIME_ATTR.StatusTime]),
|
|
207
|
-
actualDuration: asString(a[TASK_TIME_ATTR.ActualDuration]),
|
|
208
|
-
actualStart: asString(a[TASK_TIME_ATTR.ActualStart]),
|
|
209
|
-
actualFinish: asString(a[TASK_TIME_ATTR.ActualFinish]),
|
|
210
|
-
remainingTime: asString(a[TASK_TIME_ATTR.RemainingTime]),
|
|
211
|
-
completion: asNumber(a[TASK_TIME_ATTR.Completion]),
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
function extractLagTimeSeconds(extractor, store, lagId) {
|
|
215
|
-
const ref = store.entityIndex.byId.get(lagId);
|
|
216
|
-
if (!ref)
|
|
217
|
-
return {};
|
|
218
|
-
const entity = extractor.extractEntity(ref);
|
|
219
|
-
if (!entity)
|
|
220
|
-
return {};
|
|
221
|
-
if (entity.type.toUpperCase() !== 'IFCLAGTIME')
|
|
222
|
-
return {};
|
|
223
|
-
const a = entity.attributes || [];
|
|
224
|
-
const lagValue = a[LAG_TIME_ATTR.LagValue];
|
|
225
|
-
// lagValue is an IfcTimeOrRatioSelect — either a typed wrapper like
|
|
226
|
-
// ['IFCDURATION', 'P1D'] or a direct ratio number.
|
|
227
|
-
if (Array.isArray(lagValue) && lagValue.length === 2) {
|
|
228
|
-
const typeName = String(lagValue[0]).toUpperCase();
|
|
229
|
-
const inner = lagValue[1];
|
|
230
|
-
if (typeName === 'IFCDURATION' && typeof inner === 'string') {
|
|
231
|
-
return { seconds: parseIso8601Duration(inner), duration: inner };
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
else if (typeof lagValue === 'string') {
|
|
235
|
-
return { seconds: parseIso8601Duration(lagValue), duration: lagValue };
|
|
236
|
-
}
|
|
237
|
-
return {};
|
|
238
|
-
}
|
|
239
31
|
/**
|
|
240
32
|
* Extract all scheduling data from a parsed IFC store.
|
|
241
33
|
*
|
|
@@ -344,18 +136,27 @@ export function extractScheduleOnDemand(store) {
|
|
|
344
136
|
continue;
|
|
345
137
|
const parentTask = taskByExpressId.get(parent);
|
|
346
138
|
if (!parentTask)
|
|
347
|
-
continue; //
|
|
139
|
+
continue; // parent isn't a task — task/subtask nesting only; IfcWorkPlan -> IfcWorkSchedule nesting is handled in a separate pass below, once schedules are extracted
|
|
348
140
|
for (const childId of children) {
|
|
349
141
|
const childTask = taskByExpressId.get(childId);
|
|
350
142
|
if (!childTask)
|
|
351
143
|
continue;
|
|
352
|
-
|
|
144
|
+
// A source file may repeat the relation; guard as Pass 4b/5 do below.
|
|
145
|
+
if (!parentTask.childGlobalIds.includes(childTask.globalId)) {
|
|
146
|
+
parentTask.childGlobalIds.push(childTask.globalId);
|
|
147
|
+
}
|
|
353
148
|
if (!childTask.parentGlobalId) {
|
|
354
149
|
childTask.parentGlobalId = parentTask.globalId;
|
|
355
150
|
}
|
|
356
151
|
}
|
|
357
152
|
}
|
|
358
153
|
// Pass 3: resolve IfcRelAssignsToProcess — products assigned to tasks.
|
|
154
|
+
// NOT deduped, deliberately: IfcRelAssignsToProcess carries a
|
|
155
|
+
// QuantityInProcess attribute (not extracted here, but real in the
|
|
156
|
+
// schema), so the same product can legitimately repeat across relations
|
|
157
|
+
// to the same task as separate quantity assignments. Unlike the identity
|
|
158
|
+
// lists elsewhere in this file, this pair is a multiset — dedup would
|
|
159
|
+
// silently drop a legitimate repeated assignment.
|
|
359
160
|
for (const relId of relAssignsProcessIds) {
|
|
360
161
|
const ref = store.entityIndex.byId.get(relId);
|
|
361
162
|
if (!ref)
|
|
@@ -407,6 +208,7 @@ export function extractScheduleOnDemand(store) {
|
|
|
407
208
|
finishTime: asString(a[layout.FinishTime]),
|
|
408
209
|
predefinedType: asEnum(a[layout.PredefinedType]),
|
|
409
210
|
taskGlobalIds: [],
|
|
211
|
+
childScheduleGlobalIds: [],
|
|
410
212
|
};
|
|
411
213
|
if (globalId)
|
|
412
214
|
globalIdByExpressId.set(expressId, globalId);
|
|
@@ -426,7 +228,59 @@ export function extractScheduleOnDemand(store) {
|
|
|
426
228
|
scheduleByExpressId.set(id, info);
|
|
427
229
|
}
|
|
428
230
|
}
|
|
429
|
-
// Pass
|
|
231
|
+
// Pass 4b: walk IfcRelNests again — IfcWorkPlan nesting IfcWorkSchedule.
|
|
232
|
+
// Real-world files use IfcRelNests for this grouping (confirmed against the
|
|
233
|
+
// buildingSMART IFC4 spec's own reference example), distinct from Pass 2's
|
|
234
|
+
// task/subtask hierarchy above, which only resolves parents through
|
|
235
|
+
// taskByExpressId and silently skips a WorkPlan RelatingObject.
|
|
236
|
+
for (const relId of relNestsIds) {
|
|
237
|
+
const ref = store.entityIndex.byId.get(relId);
|
|
238
|
+
if (!ref)
|
|
239
|
+
continue;
|
|
240
|
+
const entity = extractor.extractEntity(ref);
|
|
241
|
+
if (!entity)
|
|
242
|
+
continue;
|
|
243
|
+
const a = entity.attributes || [];
|
|
244
|
+
const parent = asRef(a[REL_NESTS_ATTR.RelatingObject]);
|
|
245
|
+
const children = asRefList(a[REL_NESTS_ATTR.RelatedObjects]);
|
|
246
|
+
if (parent === undefined)
|
|
247
|
+
continue;
|
|
248
|
+
const parentPlan = scheduleByExpressId.get(parent);
|
|
249
|
+
if (!parentPlan || parentPlan.kind !== 'WorkPlan')
|
|
250
|
+
continue; // not a work-plan nest
|
|
251
|
+
for (const childId of children) {
|
|
252
|
+
const childSchedule = scheduleByExpressId.get(childId);
|
|
253
|
+
if (!childSchedule || childSchedule.kind !== 'WorkSchedule')
|
|
254
|
+
continue;
|
|
255
|
+
// This extractor is the sole producer of `parentPlan`: every
|
|
256
|
+
// WorkScheduleInfo it builds above sets `childScheduleGlobalIds: []`,
|
|
257
|
+
// so the field is never actually absent here. `??=` makes that
|
|
258
|
+
// invariant visible to the checker without a non-null assertion, and
|
|
259
|
+
// without treating "absent" any differently from "empty" — both still
|
|
260
|
+
// mean "no nested schedules yet" everywhere else that reads it.
|
|
261
|
+
// Two distinct IfcRelNests entities can nest the same WorkPlan/
|
|
262
|
+
// WorkSchedule pair (a source file may repeat the relation), so guard
|
|
263
|
+
// the push the same way Pass 5 below guards its own append.
|
|
264
|
+
const childGlobalIds = (parentPlan.childScheduleGlobalIds ??= []);
|
|
265
|
+
if (!childGlobalIds.includes(childSchedule.globalId)) {
|
|
266
|
+
childGlobalIds.push(childSchedule.globalId);
|
|
267
|
+
}
|
|
268
|
+
if (!childSchedule.parentPlanGlobalId) {
|
|
269
|
+
childSchedule.parentPlanGlobalId = parentPlan.globalId;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Pass 5: IfcRelAssignsToControl — map schedules to tasks, and (a second,
|
|
274
|
+
// distinct grouping path from Pass 4b's IfcRelNests) IfcWorkPlan grouping
|
|
275
|
+
// IfcWorkSchedule. The SDK's scripting bridge
|
|
276
|
+
// (`assignSchedulesToWorkPlan` in packages/create/src/ifc-creator.ts)
|
|
277
|
+
// emits exactly this relation — RelatingControl the plan, RelatedObjects
|
|
278
|
+
// the schedules — not IfcRelNests, so a plan grouped through that bridge
|
|
279
|
+
// must resolve here too or the SDK-authored grouping never reads back.
|
|
280
|
+
// If a source file expresses the same WorkPlan->WorkSchedule pair through
|
|
281
|
+
// *both* relations, Pass 4b (IfcRelNests) runs first and wins:
|
|
282
|
+
// `parentPlanGlobalId` is set-once (guarded below and in Pass 4b), and
|
|
283
|
+
// `childScheduleGlobalIds` is deduped so the pair is not double-counted.
|
|
430
284
|
for (const relId of relAssignsControlIds) {
|
|
431
285
|
const ref = store.entityIndex.byId.get(relId);
|
|
432
286
|
if (!ref)
|
|
@@ -444,10 +298,31 @@ export function extractScheduleOnDemand(store) {
|
|
|
444
298
|
continue;
|
|
445
299
|
for (const objId of objects) {
|
|
446
300
|
const task = taskByExpressId.get(objId);
|
|
447
|
-
if (
|
|
301
|
+
if (task) {
|
|
302
|
+
// A source file may repeat the relation; guard both paired arrays
|
|
303
|
+
// on one predicate to keep them in lockstep. IfcRelAssignsToControl
|
|
304
|
+
// carries no quantity attribute, so this pair is a pure identity
|
|
305
|
+
// edge, unlike Pass 3's productExpressIds/productGlobalIds above.
|
|
306
|
+
if (!schedule.taskGlobalIds.includes(task.globalId)) {
|
|
307
|
+
schedule.taskGlobalIds.push(task.globalId);
|
|
308
|
+
task.controllingScheduleGlobalIds.push(schedule.globalId);
|
|
309
|
+
}
|
|
448
310
|
continue;
|
|
449
|
-
|
|
450
|
-
task
|
|
311
|
+
}
|
|
312
|
+
// Not a task — check whether this is a WorkPlan grouping a
|
|
313
|
+
// WorkSchedule via IfcRelAssignsToControl instead of IfcRelNests.
|
|
314
|
+
if (schedule.kind !== 'WorkPlan')
|
|
315
|
+
continue;
|
|
316
|
+
const childSchedule = scheduleByExpressId.get(objId);
|
|
317
|
+
if (!childSchedule || childSchedule.kind !== 'WorkSchedule')
|
|
318
|
+
continue;
|
|
319
|
+
const siblings = (schedule.childScheduleGlobalIds ??= []);
|
|
320
|
+
if (!siblings.includes(childSchedule.globalId)) {
|
|
321
|
+
siblings.push(childSchedule.globalId);
|
|
322
|
+
}
|
|
323
|
+
if (!childSchedule.parentPlanGlobalId) {
|
|
324
|
+
childSchedule.parentPlanGlobalId = schedule.globalId;
|
|
325
|
+
}
|
|
451
326
|
}
|
|
452
327
|
}
|
|
453
328
|
// Pass 6: IfcRelSequence — dependency edges between tasks.
|