@ifc-lite/parser 6.5.0 → 7.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/README.md +21 -0
- package/dist/columnar-parser-root-attributes.d.ts +2 -5
- package/dist/columnar-parser-root-attributes.d.ts.map +1 -1
- package/dist/columnar-parser-root-attributes.js +22 -2
- package/dist/columnar-parser-root-attributes.js.map +1 -1
- package/dist/columnar-parser.d.ts +4 -0
- package/dist/columnar-parser.d.ts.map +1 -1
- package/dist/columnar-parser.js +2 -47
- package/dist/columnar-parser.js.map +1 -1
- package/dist/cost-category-buckets.d.ts +5 -0
- package/dist/cost-category-buckets.d.ts.map +1 -0
- package/dist/cost-category-buckets.js +37 -0
- package/dist/cost-category-buckets.js.map +1 -0
- package/dist/cost-compatibility-tree.d.ts +4 -0
- package/dist/cost-compatibility-tree.d.ts.map +1 -0
- package/dist/cost-compatibility-tree.js +46 -0
- package/dist/cost-compatibility-tree.js.map +1 -0
- package/dist/cost-evaluation-arithmetic.d.ts +16 -0
- package/dist/cost-evaluation-arithmetic.d.ts.map +1 -0
- package/dist/cost-evaluation-arithmetic.js +181 -0
- package/dist/cost-evaluation-arithmetic.js.map +1 -0
- package/dist/cost-evaluation-context.d.ts +20 -0
- package/dist/cost-evaluation-context.d.ts.map +1 -0
- package/dist/cost-evaluation-context.js +54 -0
- package/dist/cost-evaluation-context.js.map +1 -0
- package/dist/cost-evaluation-result.d.ts +5 -0
- package/dist/cost-evaluation-result.d.ts.map +1 -0
- package/dist/cost-evaluation-result.js +20 -0
- package/dist/cost-evaluation-result.js.map +1 -0
- package/dist/cost-evaluator.d.ts +6 -0
- package/dist/cost-evaluator.d.ts.map +1 -0
- package/dist/cost-evaluator.js +398 -0
- package/dist/cost-evaluator.js.map +1 -0
- package/dist/cost-extractor.d.ts +4 -8
- package/dist/cost-extractor.d.ts.map +1 -1
- package/dist/cost-extractor.js +346 -334
- package/dist/cost-extractor.js.map +1 -1
- package/dist/cost-overlay.d.ts +72 -0
- package/dist/cost-overlay.d.ts.map +1 -0
- package/dist/cost-overlay.js +5 -0
- package/dist/cost-overlay.js.map +1 -0
- package/dist/cost-quantities.d.ts +5 -0
- package/dist/cost-quantities.d.ts.map +1 -0
- package/dist/cost-quantities.js +116 -0
- package/dist/cost-quantities.js.map +1 -0
- package/dist/cost-quantity-evaluator.d.ts +23 -0
- package/dist/cost-quantity-evaluator.d.ts.map +1 -0
- package/dist/cost-quantity-evaluator.js +153 -0
- package/dist/cost-quantity-evaluator.js.map +1 -0
- package/dist/cost-reader.d.ts +59 -0
- package/dist/cost-reader.d.ts.map +1 -0
- package/dist/cost-reader.js +210 -0
- package/dist/cost-reader.js.map +1 -0
- package/dist/cost-relationships.d.ts +7 -0
- package/dist/cost-relationships.d.ts.map +1 -0
- package/dist/cost-relationships.js +237 -0
- package/dist/cost-relationships.js.map +1 -0
- package/dist/cost-step-lexemes.d.ts +16 -0
- package/dist/cost-step-lexemes.d.ts.map +1 -0
- package/dist/cost-step-lexemes.js +131 -0
- package/dist/cost-step-lexemes.js.map +1 -0
- package/dist/cost-types.d.ts +218 -76
- package/dist/cost-types.d.ts.map +1 -1
- package/dist/cost-units.d.ts +38 -0
- package/dist/cost-units.d.ts.map +1 -0
- package/dist/cost-units.js +348 -0
- package/dist/cost-units.js.map +1 -0
- package/dist/cost-value-evaluation-session.d.ts +19 -0
- package/dist/cost-value-evaluation-session.d.ts.map +1 -0
- package/dist/cost-value-evaluation-session.js +25 -0
- package/dist/cost-value-evaluation-session.js.map +1 -0
- package/dist/cost.d.ts +5 -0
- package/dist/cost.d.ts.map +1 -0
- package/dist/cost.js +6 -0
- package/dist/cost.js.map +1 -0
- package/dist/entity-extractor.d.ts.map +1 -1
- package/dist/entity-extractor.js +5 -2
- package/dist/entity-extractor.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/on-demand-extractors.d.ts +3 -4
- package/dist/on-demand-extractors.d.ts.map +1 -1
- package/dist/on-demand-extractors.js +9 -6
- package/dist/on-demand-extractors.js.map +1 -1
- package/dist/project-units.d.ts.map +1 -1
- package/dist/project-units.js +8 -2
- package/dist/project-units.js.map +1 -1
- package/dist/property-unit.d.ts +24 -0
- package/dist/property-unit.d.ts.map +1 -0
- package/dist/property-unit.js +29 -0
- package/dist/property-unit.js.map +1 -0
- package/dist/step-entity-parameters.d.ts +9 -0
- package/dist/step-entity-parameters.d.ts.map +1 -0
- package/dist/step-entity-parameters.js +30 -0
- package/dist/step-entity-parameters.js.map +1 -0
- package/dist/worker-parser.d.ts +29 -1
- package/dist/worker-parser.d.ts.map +1 -1
- package/dist/worker-parser.js +80 -7
- package/dist/worker-parser.js.map +1 -1
- package/package.json +6 -5
- package/dist/cost-schedule-2x3.d.ts +0 -31
- package/dist/cost-schedule-2x3.d.ts.map +0 -1
- package/dist/cost-schedule-2x3.js +0 -75
- package/dist/cost-schedule-2x3.js.map +0 -1
package/dist/cost-extractor.js
CHANGED
|
@@ -1,369 +1,381 @@
|
|
|
1
1
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* the objects it prices" — there is no separate relationship analogous to
|
|
19
|
-
* `IfcRelAssignsToProcess` for cost. Which of the two a given
|
|
20
|
-
* `IfcRelAssignsToControl` instance expresses is told apart here by
|
|
21
|
-
* what its `RelatingControl` expressId resolves to (a cost schedule or
|
|
22
|
-
* a cost item), not by anything the schema itself distinguishes.
|
|
23
|
-
* - Cost item breakdown (parent/child `IfcCostItem` nesting) uses
|
|
24
|
-
* `IfcRelNests` (`RelatingObject`/`RelatedObjects : IfcObjectDefinition`,
|
|
25
|
-
* which `IfcCostItem` satisfies via `IfcControl -> IfcObject ->
|
|
26
|
-
* IfcObjectDefinition`) — the same relationship `schedule-extractor.ts`
|
|
27
|
-
* uses for task hierarchy. The EXPRESS schema does not constrain this by
|
|
28
|
-
* a WHERE rule (nothing stops `IfcRelNests` from nesting unrelated
|
|
29
|
-
* objects), so this is the domain convention this extractor follows, not
|
|
30
|
-
* a schema-enforced fact — reported explicitly rather than asserted as
|
|
31
|
-
* certain.
|
|
32
|
-
*
|
|
33
|
-
* IFC2X3's `IfcCostItem` has NO attributes at all (`SUBTYPE OF (IfcControl)`
|
|
34
|
-
* only — no PredefinedType/CostValues/CostQuantities), so those fields are
|
|
35
|
-
* always `undefined` for a 2X3 file; this is schema-version-gated below, not
|
|
36
|
-
* inferred from an empty read. IFC2X3's `IfcCostSchedule` DOES carry
|
|
37
|
-
* Status/SubmittedOn/UpdateDate/PredefinedType, at different slots and with
|
|
38
|
-
* entity-valued dates — read through `cost-schedule-2x3.ts`.
|
|
39
|
-
*/
|
|
40
|
-
import { EntityExtractor } from './entity-extractor.js';
|
|
41
|
-
import { collectQuantitiesFromRefs } from './quantity-collect.js';
|
|
42
|
-
import { resolveUnitByRef } from './project-units.js';
|
|
43
|
-
import { COST_SCHEDULE_ATTR_2X3, resolveDateTimeSelect2x3 } from './cost-schedule-2x3.js';
|
|
44
|
-
/** Flattened IFC4/IFC4X3 STEP attribute indices for IfcCostItem. */
|
|
45
|
-
const COST_ITEM_ATTR = {
|
|
46
|
-
GlobalId: 0,
|
|
47
|
-
Name: 2,
|
|
48
|
-
Description: 3,
|
|
49
|
-
ObjectType: 4,
|
|
50
|
-
Identification: 5,
|
|
51
|
-
PredefinedType: 6,
|
|
52
|
-
CostValues: 7,
|
|
53
|
-
CostQuantities: 8,
|
|
54
|
-
};
|
|
55
|
-
const COST_SCHEDULE_ATTR = {
|
|
56
|
-
GlobalId: 0,
|
|
57
|
-
Name: 2,
|
|
58
|
-
Description: 3,
|
|
59
|
-
ObjectType: 4,
|
|
60
|
-
Identification: 5,
|
|
61
|
-
PredefinedType: 6,
|
|
62
|
-
Status: 7,
|
|
63
|
-
SubmittedOn: 8,
|
|
64
|
-
UpdateDate: 9,
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* IfcAppliedValue attribute slots (IfcCostValue adds no attributes of its
|
|
68
|
-
* own). Slot 3, `UnitBasis : OPTIONAL IfcMeasureWithUnit`, is what tells a
|
|
69
|
-
* RATE ("$85 per hour") apart from a flat total ("$5,000") — read via
|
|
70
|
-
* `extractUnitBasis` below, not skipped.
|
|
71
|
-
*/
|
|
72
|
-
const COST_VALUE_ATTR = {
|
|
73
|
-
Name: 0,
|
|
74
|
-
Description: 1,
|
|
75
|
-
AppliedValue: 2,
|
|
76
|
-
UnitBasis: 3,
|
|
77
|
-
ApplicableDate: 4,
|
|
78
|
-
FixedUntilDate: 5,
|
|
79
|
-
Category: 6,
|
|
80
|
-
Condition: 7,
|
|
81
|
-
ArithmeticOperator: 8,
|
|
82
|
-
Components: 9,
|
|
83
|
-
};
|
|
84
|
-
const REL_NESTS_ATTR = { RelatingObject: 4, RelatedObjects: 5 };
|
|
85
|
-
const REL_ASSIGNS_TO_CONTROL_ATTR = { RelatedObjects: 4, RelatingControl: 6 };
|
|
86
|
-
function asString(v) {
|
|
87
|
-
if (typeof v === 'string' && v.length > 0)
|
|
88
|
-
return v;
|
|
89
|
-
return undefined;
|
|
4
|
+
import { QuantityType } from '@ifc-lite/data';
|
|
5
|
+
import { compatibilityTreeBuilder } from './cost-compatibility-tree.js';
|
|
6
|
+
import { costQuantityExactValue, extractCostQuantities } from './cost-quantities.js';
|
|
7
|
+
import { asEnum, asRef, asString, CostEntityReader } from './cost-reader.js';
|
|
8
|
+
import { diagnoseCostGraphs, extractCostRelationships } from './cost-relationships.js';
|
|
9
|
+
import { isZeroCostNumericLexeme } from './cost-step-lexemes.js';
|
|
10
|
+
import { getInheritanceChain } from './ifc-schema.js';
|
|
11
|
+
import { CostUnitResolver } from './cost-units.js';
|
|
12
|
+
function scalarString(value) {
|
|
13
|
+
if (Array.isArray(value) && value.length === 2)
|
|
14
|
+
return scalarString(value[1]);
|
|
15
|
+
if (typeof value === 'string' && value.length > 0)
|
|
16
|
+
return value;
|
|
17
|
+
return typeof value === 'number' && Number.isFinite(value) ? value.toString() : undefined;
|
|
90
18
|
}
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
function asAppliedValueNumber(v) {
|
|
122
|
-
if (typeof v === 'number' && Number.isFinite(v))
|
|
123
|
-
return v;
|
|
124
|
-
if (Array.isArray(v) && v.length === 2) {
|
|
125
|
-
const inner = v[1];
|
|
126
|
-
if (typeof inner === 'number' && Number.isFinite(inner))
|
|
127
|
-
return inner;
|
|
128
|
-
if (typeof inner === 'string') {
|
|
129
|
-
const n = parseFloat(inner);
|
|
130
|
-
if (Number.isFinite(n))
|
|
131
|
-
return n;
|
|
19
|
+
function dateTime2x3(reader, value) {
|
|
20
|
+
const pad = (part) => String(part).padStart(2, '0');
|
|
21
|
+
let current = asRef(value);
|
|
22
|
+
let clock;
|
|
23
|
+
const visited = new Set();
|
|
24
|
+
while (current !== undefined) {
|
|
25
|
+
if (visited.has(current))
|
|
26
|
+
return undefined;
|
|
27
|
+
visited.add(current);
|
|
28
|
+
const entity = reader.get(current);
|
|
29
|
+
if (!entity)
|
|
30
|
+
return undefined;
|
|
31
|
+
const a = entity.attributes ?? [];
|
|
32
|
+
const type = entity.type.toUpperCase();
|
|
33
|
+
if (type === 'IFCCALENDARDATE' &&
|
|
34
|
+
typeof a[0] === 'number' && typeof a[1] === 'number' && typeof a[2] === 'number') {
|
|
35
|
+
const date = `${a[2]}-${pad(a[1])}-${pad(a[0])}`;
|
|
36
|
+
return clock ? `${date}T${clock}` : date;
|
|
37
|
+
}
|
|
38
|
+
if (type !== 'IFCDATEANDTIME')
|
|
39
|
+
return undefined;
|
|
40
|
+
if (!clock) {
|
|
41
|
+
const timeId = asRef(a[1]);
|
|
42
|
+
const time = timeId === undefined ? undefined : reader.get(timeId);
|
|
43
|
+
const t = time?.type.toUpperCase() === 'IFCLOCALTIME' ? time.attributes ?? [] : [];
|
|
44
|
+
if (typeof t[0] === 'number') {
|
|
45
|
+
const minute = typeof t[1] === 'number' ? t[1] : 0;
|
|
46
|
+
const second = typeof t[2] === 'number' ? Math.floor(t[2]) : 0;
|
|
47
|
+
clock = `${pad(t[0])}:${pad(minute)}:${pad(second)}`;
|
|
48
|
+
}
|
|
132
49
|
}
|
|
50
|
+
current = asRef(a[0]);
|
|
133
51
|
}
|
|
134
52
|
return undefined;
|
|
135
53
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
* into the minimal shape that answers "is this a rate or a total?" —
|
|
139
|
-
* `undefined` when `UnitBasis` is absent or its reference is broken;
|
|
140
|
-
* otherwise an object whose mere presence marks the owning `CostValueInfo` as
|
|
141
|
-
* a rate, with `valueComponent`/`unitSymbol` individually `undefined` when
|
|
142
|
-
* that half of `IfcMeasureWithUnit` could not itself be resolved (see
|
|
143
|
-
* {@link CostValueUnitBasis}'s doc comment in `cost-types.ts`).
|
|
144
|
-
*
|
|
145
|
-
* `IfcMeasureWithUnit` (`ValueComponent : IfcValue`, `UnitComponent :
|
|
146
|
-
* IfcUnit`) — both SELECT types, so each is resolved independently:
|
|
147
|
-
* - `ValueComponent` reuses `asAppliedValueNumber`, the same resolution
|
|
148
|
-
* `AppliedValue` above uses for its own `IfcAppliedValueSelect`.
|
|
149
|
-
* - `UnitComponent` reuses `resolveUnitByRef` (shared with
|
|
150
|
-
* `IfcPhysicalSimpleQuantity.Unit` in `quantity-collect.ts`), NOT
|
|
151
|
-
* `quantitySiScale`'s project-unit-fallback machinery: `IfcMeasureWithUnit`
|
|
152
|
-
* is not an `IfcPhysicalQuantity`, `UnitComponent` is not `OPTIONAL`
|
|
153
|
-
* within it, and there is no containing project unit to fall back to — the
|
|
154
|
-
* reference resolves or it does not.
|
|
155
|
-
*/
|
|
156
|
-
function extractUnitBasis(extractor, store, unitBasisRef) {
|
|
157
|
-
if (unitBasisRef === undefined)
|
|
158
|
-
return undefined;
|
|
159
|
-
const ref = store.entityIndex.byId.get(unitBasisRef);
|
|
160
|
-
if (!ref)
|
|
161
|
-
return undefined;
|
|
162
|
-
const entity = extractor.extractEntity(ref);
|
|
163
|
-
if (!entity || entity.type.toUpperCase() !== 'IFCMEASUREWITHUNIT')
|
|
54
|
+
function appliedValue(value, reader, expressId) {
|
|
55
|
+
if (!reader.attributePresent(expressId, 2))
|
|
164
56
|
return undefined;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
57
|
+
if (value === null || value === undefined)
|
|
58
|
+
return { Kind: 'Unsupported', Raw: value };
|
|
59
|
+
if (Array.isArray(value) && value.length === 2 && typeof value[0] === 'string') {
|
|
60
|
+
const parsed = reader.decimalLexeme(expressId, 2);
|
|
61
|
+
return parsed === undefined
|
|
62
|
+
? { Kind: 'Unsupported', Raw: value, InvalidNumber: true }
|
|
63
|
+
: { Kind: 'Typed', Type: value[0].toUpperCase(), Value: parsed };
|
|
64
|
+
}
|
|
65
|
+
const reference = reader.referenceLexeme(expressId, 2);
|
|
66
|
+
if (reference !== undefined && reader.get(reference)) {
|
|
67
|
+
return { Kind: 'Reference', expressId: reference };
|
|
68
|
+
}
|
|
69
|
+
return { Kind: 'Unsupported', Raw: value };
|
|
173
70
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (entity.type.toUpperCase() !== 'IFCCOSTVALUE')
|
|
185
|
-
return undefined;
|
|
186
|
-
const a = entity.attributes || [];
|
|
187
|
-
visiting.add(valueId);
|
|
188
|
-
const componentIds = asRefList(a[COST_VALUE_ATTR.Components]);
|
|
189
|
-
const components = componentIds.length
|
|
190
|
-
? componentIds
|
|
191
|
-
.map((id) => extractCostValue(extractor, store, id, depth + 1, visiting))
|
|
192
|
-
.filter((c) => c !== undefined)
|
|
193
|
-
: undefined;
|
|
194
|
-
visiting.delete(valueId);
|
|
195
|
-
return {
|
|
196
|
-
name: asString(a[COST_VALUE_ATTR.Name]),
|
|
197
|
-
description: asString(a[COST_VALUE_ATTR.Description]),
|
|
198
|
-
appliedValue: asAppliedValueNumber(a[COST_VALUE_ATTR.AppliedValue]),
|
|
199
|
-
unitBasis: extractUnitBasis(extractor, store, asRef(a[COST_VALUE_ATTR.UnitBasis])),
|
|
200
|
-
applicableDate: asString(a[COST_VALUE_ATTR.ApplicableDate]),
|
|
201
|
-
fixedUntilDate: asString(a[COST_VALUE_ATTR.FixedUntilDate]),
|
|
202
|
-
category: asString(a[COST_VALUE_ATTR.Category]),
|
|
203
|
-
condition: asString(a[COST_VALUE_ATTR.Condition]),
|
|
204
|
-
arithmeticOperator: asEnum(a[COST_VALUE_ATTR.ArithmeticOperator]),
|
|
205
|
-
...(components && components.length ? { components } : {}),
|
|
206
|
-
};
|
|
71
|
+
function quantityType(dimension) {
|
|
72
|
+
switch (dimension) {
|
|
73
|
+
case 'length': return QuantityType.Length;
|
|
74
|
+
case 'area': return QuantityType.Area;
|
|
75
|
+
case 'volume': return QuantityType.Volume;
|
|
76
|
+
case 'mass': return QuantityType.Weight;
|
|
77
|
+
case 'time': return QuantityType.Time;
|
|
78
|
+
case 'number': return QuantityType.Number;
|
|
79
|
+
default: return QuantityType.Count;
|
|
80
|
+
}
|
|
207
81
|
}
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
|
|
82
|
+
function finiteCompatibilityNumber(value) {
|
|
83
|
+
if (value === undefined)
|
|
84
|
+
return undefined;
|
|
85
|
+
const converted = Number(value);
|
|
86
|
+
if (!Number.isFinite(converted))
|
|
211
87
|
return undefined;
|
|
212
|
-
|
|
213
|
-
const values = ids
|
|
214
|
-
.map((id) => extractCostValue(extractor, store, id, 0, visiting))
|
|
215
|
-
.filter((v) => v !== undefined);
|
|
216
|
-
return values.length ? values : undefined;
|
|
88
|
+
return converted !== 0 || isZeroCostNumericLexeme(value) ? converted : undefined;
|
|
217
89
|
}
|
|
218
90
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
91
|
+
* `IfcRelAssignsToControl` legitimately binds tasks, resources and actors
|
|
92
|
+
* to a control (assigning a task to a cost item is exactly this
|
|
93
|
+
* relationship, with the cost item as `RelatingControl`) — not just
|
|
94
|
+
* products. `productExpressIds`/`productGlobalIds` are documented as an
|
|
95
|
+
* `IfcProduct` view, so this discriminates the related object's type via
|
|
96
|
+
* the schema-derived inheritance chain (same approach used elsewhere in
|
|
97
|
+
* the parser for product/group/root classification) before it is
|
|
98
|
+
* admitted (#4877).
|
|
223
99
|
*/
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
100
|
+
function isProductLike(reader, expressId) {
|
|
101
|
+
const type = reader.typeOf(expressId);
|
|
102
|
+
if (!type)
|
|
103
|
+
return false;
|
|
104
|
+
return getInheritanceChain(type).some((ancestor) => ancestor.toUpperCase() === 'IFCPRODUCT');
|
|
105
|
+
}
|
|
106
|
+
function pushInvalidList(reader, diagnostics, expressId, index, attribute, value) {
|
|
107
|
+
if (!reader.attributePresent(expressId, index))
|
|
108
|
+
return undefined;
|
|
109
|
+
const refs = reader.referenceListLexeme(expressId, index);
|
|
110
|
+
if (!refs || refs.length === 0 || !Array.isArray(value) || refs.length !== value.length) {
|
|
111
|
+
diagnostics.push({
|
|
112
|
+
Code: 'INVALID_LIST',
|
|
113
|
+
Message: `${attribute} on #${expressId} must contain at least one reference`,
|
|
114
|
+
Severity: 'warning',
|
|
115
|
+
expressId,
|
|
116
|
+
});
|
|
117
|
+
return [];
|
|
227
118
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
119
|
+
return refs;
|
|
120
|
+
}
|
|
121
|
+
function extractSchedules(reader, schemaIs2x3) {
|
|
122
|
+
const result = [];
|
|
123
|
+
for (const expressId of reader.ids('IFCCOSTSCHEDULE')) {
|
|
124
|
+
const a = reader.get(expressId)?.attributes ?? [];
|
|
125
|
+
result.push(schemaIs2x3 ? {
|
|
126
|
+
expressId,
|
|
127
|
+
GlobalId: asString(a[0]) ?? '',
|
|
128
|
+
Name: asString(a[2]), Description: asString(a[3]), ObjectType: asString(a[4]),
|
|
129
|
+
SubmittedOn: dateTime2x3(reader, a[7]), Status: asString(a[8]),
|
|
130
|
+
UpdateDate: dateTime2x3(reader, a[10]), ID: asString(a[11]), PredefinedType: asEnum(a[12]),
|
|
131
|
+
globalId: asString(a[0]) ?? '', name: asString(a[2]) ?? '',
|
|
132
|
+
predefinedType: asEnum(a[12]), status: asString(a[8]),
|
|
133
|
+
submittedOn: dateTime2x3(reader, a[7]), updateDate: dateTime2x3(reader, a[10]),
|
|
134
|
+
costItemGlobalIds: [],
|
|
135
|
+
} : {
|
|
136
|
+
expressId,
|
|
137
|
+
GlobalId: asString(a[0]) ?? '',
|
|
138
|
+
Name: asString(a[2]), Description: asString(a[3]), ObjectType: asString(a[4]),
|
|
139
|
+
Identification: asString(a[5]), PredefinedType: asEnum(a[6]), Status: asString(a[7]),
|
|
140
|
+
SubmittedOn: scalarString(a[8]), UpdateDate: scalarString(a[9]),
|
|
141
|
+
globalId: asString(a[0]) ?? '', name: asString(a[2]) ?? '',
|
|
142
|
+
predefinedType: asEnum(a[6]), status: asString(a[7]),
|
|
143
|
+
submittedOn: scalarString(a[8]), updateDate: scalarString(a[9]),
|
|
144
|
+
costItemGlobalIds: [],
|
|
145
|
+
});
|
|
235
146
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
function extractItems(reader, schemaIs2x3, diagnostics) {
|
|
150
|
+
return reader.ids('IFCCOSTITEM').map((expressId) => {
|
|
151
|
+
const a = reader.get(expressId)?.attributes ?? [];
|
|
152
|
+
const base = {
|
|
153
|
+
expressId, GlobalId: asString(a[0]) ?? '', Name: asString(a[2]),
|
|
154
|
+
Description: asString(a[3]), ObjectType: asString(a[4]),
|
|
155
|
+
globalId: asString(a[0]) ?? '', name: asString(a[2]) ?? '',
|
|
156
|
+
childGlobalIds: [], productExpressIds: [], productGlobalIds: [],
|
|
157
|
+
controllingScheduleGlobalIds: [],
|
|
158
|
+
};
|
|
159
|
+
if (schemaIs2x3)
|
|
160
|
+
return base;
|
|
161
|
+
base.Identification = asString(a[5]);
|
|
162
|
+
base.PredefinedType = asEnum(a[6]);
|
|
163
|
+
base.predefinedType = base.PredefinedType;
|
|
164
|
+
base.CostValues = pushInvalidList(reader, diagnostics, expressId, 7, 'CostValues', a[7]);
|
|
165
|
+
base.CostQuantities = pushInvalidList(reader, diagnostics, expressId, 8, 'CostQuantities', a[8]);
|
|
166
|
+
return base;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function extractValues(reader, schemaIs2x3, diagnostics) {
|
|
170
|
+
const ids = new Set([...reader.ids('IFCCOSTVALUE'), ...reader.ids('IFCAPPLIEDVALUE')]);
|
|
171
|
+
const values = [];
|
|
172
|
+
for (const expressId of ids) {
|
|
173
|
+
const entity = reader.get(expressId);
|
|
247
174
|
if (!entity)
|
|
248
175
|
continue;
|
|
249
|
-
const a = entity.attributes
|
|
250
|
-
const
|
|
251
|
-
const
|
|
176
|
+
const a = entity.attributes ?? [];
|
|
177
|
+
const AppliedValue = appliedValue(a[2], reader, expressId);
|
|
178
|
+
const ApplicableDate = schemaIs2x3 ? dateTime2x3(reader, a[4]) : scalarString(a[4]);
|
|
179
|
+
const FixedUntilDate = schemaIs2x3 ? dateTime2x3(reader, a[5]) : scalarString(a[5]);
|
|
180
|
+
const Condition = asString(a[7]);
|
|
181
|
+
const InvalidCondition = reader.attributePresent(expressId, 7) && Condition === undefined;
|
|
182
|
+
const UnitBasis = reader.referenceLexeme(expressId, 3);
|
|
183
|
+
const InvalidUnitBasis = reader.attributePresent(expressId, 3) && UnitBasis === undefined;
|
|
184
|
+
const base = {
|
|
252
185
|
expressId,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const qtys = collectQuantitiesFromRefs(store, extractor, a[COST_ITEM_ATTR.CostQuantities]);
|
|
263
|
-
return qtys.length ? qtys : undefined;
|
|
264
|
-
})(),
|
|
265
|
-
childGlobalIds: [],
|
|
266
|
-
productExpressIds: [],
|
|
267
|
-
productGlobalIds: [],
|
|
268
|
-
controllingScheduleGlobalIds: [],
|
|
186
|
+
Type: entity.type.toUpperCase() === 'IFCCOSTVALUE' ? 'IfcCostValue' : 'IfcAppliedValue',
|
|
187
|
+
Name: asString(a[0]), Description: asString(a[1]), AppliedValue,
|
|
188
|
+
UnitBasis, InvalidUnitBasis: InvalidUnitBasis || undefined, ApplicableDate, FixedUntilDate,
|
|
189
|
+
InvalidCondition: InvalidCondition || undefined,
|
|
190
|
+
name: asString(a[0]), description: asString(a[1]),
|
|
191
|
+
appliedValue: AppliedValue?.Kind === 'Typed'
|
|
192
|
+
? finiteCompatibilityNumber(AppliedValue.Value)
|
|
193
|
+
: undefined,
|
|
194
|
+
applicableDate: ApplicableDate, fixedUntilDate: FixedUntilDate,
|
|
269
195
|
};
|
|
270
|
-
|
|
196
|
+
if (schemaIs2x3) {
|
|
197
|
+
base.CostType = asString(a[6]);
|
|
198
|
+
base.Condition = Condition;
|
|
199
|
+
base.condition = base.Condition;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
base.Category = asString(a[6]);
|
|
203
|
+
base.Condition = Condition;
|
|
204
|
+
base.ArithmeticOperator = asEnum(a[8]);
|
|
205
|
+
base.Components = pushInvalidList(reader, diagnostics, expressId, 9, 'Components', a[9]);
|
|
206
|
+
base.category = base.Category;
|
|
207
|
+
base.condition = base.Condition;
|
|
208
|
+
base.arithmeticOperator = base.ArithmeticOperator;
|
|
209
|
+
}
|
|
210
|
+
values.push(base);
|
|
271
211
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
212
|
+
return values;
|
|
213
|
+
}
|
|
214
|
+
/** Extract a schema-aware, read-only IFC cost graph from a parsed store. */
|
|
215
|
+
export function extractCostOnDemand(store, options) {
|
|
216
|
+
const schema = store.schemaVersion;
|
|
217
|
+
const diagnostics = [];
|
|
218
|
+
const empty = () => ({
|
|
219
|
+
SchemaVersion: schema, CostSchedules: [], CostItems: [], CostValues: [],
|
|
220
|
+
CostQuantities: [], Units: [], MeasuresWithUnit: [], ProjectUnits: {}, Relationships: [], Diagnostics: diagnostics,
|
|
221
|
+
HasCostData: false, costSchedules: [], costItems: [], hasCost: false,
|
|
222
|
+
});
|
|
223
|
+
if (!store.source?.length)
|
|
224
|
+
return empty();
|
|
225
|
+
const reader = new CostEntityReader(store, options?.overlay, diagnostics);
|
|
226
|
+
const hasCost = reader.ids('IFCCOSTSCHEDULE').length + reader.ids('IFCCOSTITEM').length +
|
|
227
|
+
reader.ids('IFCCOSTVALUE').length + reader.ids('IFCAPPLIEDVALUE').length > 0;
|
|
228
|
+
if (!hasCost)
|
|
229
|
+
return empty();
|
|
230
|
+
if (schema === 'IFC5') {
|
|
231
|
+
diagnostics.push({ Code: 'UNSUPPORTED_SCHEMA', Message: 'IFC5 cost extraction is not supported', Severity: 'error' });
|
|
232
|
+
return { ...empty(), HasCostData: true };
|
|
233
|
+
}
|
|
234
|
+
const schemaIs2x3 = schema === 'IFC2X3';
|
|
235
|
+
if (schemaIs2x3)
|
|
236
|
+
diagnostics.push({
|
|
237
|
+
Code: 'IFC2X3_PARTIAL_READ',
|
|
238
|
+
Message: 'IFC2X3 cost metadata and relationships are preserved, but values are not evaluated',
|
|
239
|
+
Severity: 'warning',
|
|
240
|
+
});
|
|
241
|
+
const CostSchedules = extractSchedules(reader, schemaIs2x3);
|
|
242
|
+
const CostItems = extractItems(reader, schemaIs2x3, diagnostics);
|
|
243
|
+
const CostValues = extractValues(reader, schemaIs2x3, diagnostics);
|
|
244
|
+
const itemIds = new Set(CostItems.map(item => item.expressId));
|
|
245
|
+
const scheduleIds = new Set(CostSchedules.map(schedule => schedule.expressId));
|
|
246
|
+
const valueIds = new Set(CostValues.flatMap(value => value.expressId === undefined ? [] : [value.expressId]));
|
|
247
|
+
const Relationships = extractCostRelationships(reader, itemIds, scheduleIds, valueIds);
|
|
248
|
+
const quantityMap = new Map();
|
|
249
|
+
extractCostQuantities(reader, CostItems.flatMap(item => item.CostQuantities ?? []), quantityMap, diagnostics);
|
|
250
|
+
const unitResolver = new CostUnitResolver(reader, diagnostics);
|
|
251
|
+
for (const quantity of quantityMap.values()) {
|
|
252
|
+
if (quantity.Unit !== undefined)
|
|
253
|
+
unitResolver.resolve(quantity.Unit);
|
|
254
|
+
}
|
|
255
|
+
for (const value of CostValues) {
|
|
256
|
+
if (value.UnitBasis !== undefined) {
|
|
257
|
+
const basis = unitResolver.resolveMeasureWithUnit(value.UnitBasis);
|
|
258
|
+
const compatibility = unitResolver.compatibilityMeasureWithUnit(value.UnitBasis, basis);
|
|
259
|
+
const valueComponent = finiteCompatibilityNumber(compatibility?.Value);
|
|
260
|
+
const unitSiScale = finiteCompatibilityNumber(compatibility?.Unit?.Scale);
|
|
261
|
+
value.unitBasis = compatibility ? {
|
|
262
|
+
valueComponent,
|
|
263
|
+
unitSymbol: compatibility.Unit?.Scale === undefined ? undefined : compatibility.Unit.Symbol,
|
|
264
|
+
unitSiScale,
|
|
265
|
+
} : undefined;
|
|
266
|
+
}
|
|
267
|
+
if (value.AppliedValue?.Kind === 'Reference' && reader.typeOf(value.AppliedValue.expressId) === 'IFCMEASUREWITHUNIT') {
|
|
268
|
+
unitResolver.resolveMeasureWithUnit(value.AppliedValue.expressId);
|
|
296
269
|
}
|
|
297
270
|
}
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
name: asString(a[COST_SCHEDULE_ATTR.Name]) ?? '',
|
|
314
|
-
// IFC2X3 lays the schedule out differently (see cost-schedule-2x3.ts).
|
|
315
|
-
predefinedType: asEnum(a[schemaIs2x3 ? COST_SCHEDULE_ATTR_2X3.PredefinedType : COST_SCHEDULE_ATTR.PredefinedType]),
|
|
316
|
-
status: asString(a[schemaIs2x3 ? COST_SCHEDULE_ATTR_2X3.Status : COST_SCHEDULE_ATTR.Status]),
|
|
317
|
-
submittedOn: schemaIs2x3
|
|
318
|
-
? resolveDateTimeSelect2x3(extractor, store, a[COST_SCHEDULE_ATTR_2X3.SubmittedOn])
|
|
319
|
-
: asString(a[COST_SCHEDULE_ATTR.SubmittedOn]),
|
|
320
|
-
updateDate: schemaIs2x3
|
|
321
|
-
? resolveDateTimeSelect2x3(extractor, store, a[COST_SCHEDULE_ATTR_2X3.UpdateDate])
|
|
322
|
-
: asString(a[COST_SCHEDULE_ATTR.UpdateDate]),
|
|
323
|
-
costItemGlobalIds: [],
|
|
324
|
-
};
|
|
325
|
-
costSchedules.push(info);
|
|
326
|
-
scheduleByExpressId.set(expressId, info);
|
|
271
|
+
diagnoseCostGraphs(Relationships, CostValues, itemIds, diagnostics);
|
|
272
|
+
const schedules = new Map(CostSchedules.map(schedule => [schedule.expressId, schedule]));
|
|
273
|
+
const items = new Map(CostItems.map(item => [item.expressId, item]));
|
|
274
|
+
const values = new Map(CostValues.map(value => [value.expressId, value]));
|
|
275
|
+
const legacyComponents = compatibilityTreeBuilder(values, diagnostics);
|
|
276
|
+
const quantities = new Map([...quantityMap.values()].map(quantity => [quantity.expressId, quantity]));
|
|
277
|
+
for (const value of CostValues) {
|
|
278
|
+
for (const componentId of value.Components ?? []) {
|
|
279
|
+
if (!values.has(componentId))
|
|
280
|
+
diagnostics.push({
|
|
281
|
+
Code: 'MISSING_REFERENCE',
|
|
282
|
+
Message: `IfcAppliedValue component #${componentId} cannot be resolved`,
|
|
283
|
+
Severity: 'error', expressId: value.expressId, RelatedExpressId: componentId,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
327
286
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
287
|
+
const compatibilityBuilt = new Set();
|
|
288
|
+
for (const item of CostItems) {
|
|
289
|
+
for (const valueId of item.CostValues ?? []) {
|
|
290
|
+
const value = values.get(valueId);
|
|
291
|
+
if (!value)
|
|
292
|
+
diagnostics.push({
|
|
293
|
+
Code: 'MISSING_REFERENCE',
|
|
294
|
+
Message: `IfcCostValue #${valueId} cannot be resolved`,
|
|
295
|
+
Severity: 'error', expressId: item.expressId, RelatedExpressId: valueId,
|
|
296
|
+
});
|
|
297
|
+
else if (value.Type !== 'IfcCostValue')
|
|
298
|
+
diagnostics.push({
|
|
299
|
+
Code: 'INVALID_LIST',
|
|
300
|
+
Message: `CostValues on IfcCostItem #${item.expressId} must reference IfcCostValue`,
|
|
301
|
+
Severity: 'error', expressId: item.expressId, RelatedExpressId: valueId,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
item.costValues = item.CostValues
|
|
305
|
+
?.map(id => {
|
|
306
|
+
const value = values.get(id);
|
|
307
|
+
if (value && !compatibilityBuilt.has(id)) {
|
|
308
|
+
value.components = legacyComponents(id);
|
|
309
|
+
compatibilityBuilt.add(id);
|
|
310
|
+
}
|
|
311
|
+
return value;
|
|
312
|
+
})
|
|
313
|
+
.filter((entry) => entry !== undefined);
|
|
314
|
+
item.costQuantities = item.CostQuantities
|
|
315
|
+
?.map(id => quantities.get(id))
|
|
316
|
+
.filter((entry) => entry !== undefined && costQuantityExactValue(entry) !== undefined)
|
|
317
|
+
.flatMap(quantity => {
|
|
318
|
+
const value = finiteCompatibilityNumber(costQuantityExactValue(quantity));
|
|
319
|
+
if (value === undefined)
|
|
320
|
+
return [];
|
|
321
|
+
const unit = quantity.Unit === undefined ? undefined : unitResolver.Units.get(quantity.Unit);
|
|
322
|
+
const explicitUnitSiScale = finiteCompatibilityNumber(unit?.Scale);
|
|
323
|
+
return [{
|
|
324
|
+
name: quantity.Name ?? '',
|
|
325
|
+
type: quantityType(quantity.Dimension),
|
|
326
|
+
value,
|
|
327
|
+
...(explicitUnitSiScale !== undefined ? { explicitUnitSiScale } : {}),
|
|
328
|
+
}];
|
|
329
|
+
});
|
|
330
|
+
if (item.costQuantities?.length === 0)
|
|
331
|
+
item.costQuantities = undefined;
|
|
332
|
+
}
|
|
333
|
+
for (const relation of Relationships) {
|
|
334
|
+
if (relation.Type === 'IfcRelNests' && relation.RelatingObject !== undefined) {
|
|
335
|
+
const parent = items.get(relation.RelatingObject);
|
|
336
|
+
if (!parent)
|
|
337
|
+
continue;
|
|
338
|
+
for (const childId of relation.RelatedObjects ?? []) {
|
|
339
|
+
const child = items.get(childId);
|
|
340
|
+
if (!child)
|
|
348
341
|
continue;
|
|
349
|
-
|
|
350
|
-
|
|
342
|
+
parent.childGlobalIds.push(child.GlobalId ?? '');
|
|
343
|
+
child.parentGlobalId ??= parent.GlobalId ?? '';
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else if ((relation.Type === 'IfcRelAssignsToControl' || relation.Type === 'IfcRelSchedulesCostItems') &&
|
|
347
|
+
relation.RelatingControl !== undefined) {
|
|
348
|
+
const schedule = schedules.get(relation.RelatingControl);
|
|
349
|
+
const item = items.get(relation.RelatingControl);
|
|
350
|
+
for (const relatedId of relation.RelatedObjects ?? []) {
|
|
351
|
+
const relatedItem = items.get(relatedId);
|
|
352
|
+
if (schedule && relatedItem) {
|
|
353
|
+
schedule.costItemGlobalIds.push(relatedItem.GlobalId ?? '');
|
|
354
|
+
relatedItem.controllingScheduleGlobalIds.push(schedule.GlobalId ?? '');
|
|
355
|
+
}
|
|
356
|
+
else if (item && isProductLike(reader, relatedId)) {
|
|
357
|
+
item.productExpressIds.push(relatedId);
|
|
358
|
+
item.productGlobalIds.push(store.entities?.getGlobalId?.(relatedId) ?? '');
|
|
359
|
+
}
|
|
351
360
|
}
|
|
352
|
-
continue;
|
|
353
361
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
362
|
+
else if (relation.Type === 'IfcRelAssignsToProduct' && relation.RelatingProduct !== undefined) {
|
|
363
|
+
for (const relatedId of relation.RelatedObjects ?? []) {
|
|
364
|
+
const item = items.get(relatedId);
|
|
365
|
+
if (!item)
|
|
366
|
+
continue;
|
|
367
|
+
item.productExpressIds.push(relation.RelatingProduct);
|
|
368
|
+
item.productGlobalIds.push(store.entities?.getGlobalId?.(relation.RelatingProduct) ?? '');
|
|
360
369
|
}
|
|
361
370
|
}
|
|
362
371
|
}
|
|
363
372
|
return {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
373
|
+
SchemaVersion: schema, CostSchedules, CostItems, CostValues,
|
|
374
|
+
CostQuantities: [...quantityMap.values()], Units: [...unitResolver.Units.values()],
|
|
375
|
+
MeasuresWithUnit: [...unitResolver.MeasuresWithUnit.values()],
|
|
376
|
+
ProjectUnits: Object.fromEntries([...unitResolver.ProjectUnits.entries()].map(([dimension, unit]) => [dimension, unit.expressId])),
|
|
377
|
+
Relationships, Diagnostics: diagnostics, HasCostData: true, Currency: unitResolver.Currency,
|
|
378
|
+
costSchedules: CostSchedules, costItems: CostItems, hasCost: true,
|
|
367
379
|
};
|
|
368
380
|
}
|
|
369
381
|
//# sourceMappingURL=cost-extractor.js.map
|