@ifc-lite/parser 6.5.0 → 7.0.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/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 +396 -0
- package/dist/cost-evaluator.js.map +1 -0
- package/dist/cost-extractor.d.ts +3 -8
- package/dist/cost-extractor.d.ts.map +1 -1
- package/dist/cost-extractor.js +330 -335
- package/dist/cost-extractor.js.map +1 -1
- 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 +26 -0
- package/dist/cost-reader.d.ts.map +1 -0
- package/dist/cost-reader.js +117 -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 +210 -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 +4 -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/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/package.json +4 -3
- 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,364 @@
|
|
|
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
|
-
* what binds both "schedule controls cost items" and "cost item controls
|
|
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;
|
|
90
|
-
}
|
|
91
|
-
function asEnum(v) {
|
|
92
|
-
if (typeof v !== 'string' || v.length === 0)
|
|
93
|
-
return undefined;
|
|
94
|
-
const match = v.match(/^\.([A-Z_]+)\.$/);
|
|
95
|
-
return match ? match[1] : undefined;
|
|
96
|
-
}
|
|
97
|
-
function asRef(v) {
|
|
98
|
-
if (typeof v === 'number' && Number.isInteger(v) && v > 0)
|
|
99
|
-
return v;
|
|
100
|
-
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 { CostUnitResolver } from './cost-units.js';
|
|
11
|
+
function scalarString(value) {
|
|
12
|
+
if (Array.isArray(value) && value.length === 2)
|
|
13
|
+
return scalarString(value[1]);
|
|
14
|
+
if (typeof value === 'string' && value.length > 0)
|
|
15
|
+
return value;
|
|
16
|
+
return typeof value === 'number' && Number.isFinite(value) ? value.toString() : undefined;
|
|
101
17
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (
|
|
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;
|
|
18
|
+
function dateTime2x3(reader, value) {
|
|
19
|
+
const pad = (part) => String(part).padStart(2, '0');
|
|
20
|
+
let current = asRef(value);
|
|
21
|
+
let clock;
|
|
22
|
+
const visited = new Set();
|
|
23
|
+
while (current !== undefined) {
|
|
24
|
+
if (visited.has(current))
|
|
25
|
+
return undefined;
|
|
26
|
+
visited.add(current);
|
|
27
|
+
const entity = reader.get(current);
|
|
28
|
+
if (!entity)
|
|
29
|
+
return undefined;
|
|
30
|
+
const a = entity.attributes ?? [];
|
|
31
|
+
const type = entity.type.toUpperCase();
|
|
32
|
+
if (type === 'IFCCALENDARDATE' &&
|
|
33
|
+
typeof a[0] === 'number' && typeof a[1] === 'number' && typeof a[2] === 'number') {
|
|
34
|
+
const date = `${a[2]}-${pad(a[1])}-${pad(a[0])}`;
|
|
35
|
+
return clock ? `${date}T${clock}` : date;
|
|
132
36
|
}
|
|
37
|
+
if (type !== 'IFCDATEANDTIME')
|
|
38
|
+
return undefined;
|
|
39
|
+
if (!clock) {
|
|
40
|
+
const timeId = asRef(a[1]);
|
|
41
|
+
const time = timeId === undefined ? undefined : reader.get(timeId);
|
|
42
|
+
const t = time?.type.toUpperCase() === 'IFCLOCALTIME' ? time.attributes ?? [] : [];
|
|
43
|
+
if (typeof t[0] === 'number') {
|
|
44
|
+
const minute = typeof t[1] === 'number' ? t[1] : 0;
|
|
45
|
+
const second = typeof t[2] === 'number' ? Math.floor(t[2]) : 0;
|
|
46
|
+
clock = `${pad(t[0])}:${pad(minute)}:${pad(second)}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
current = asRef(a[0]);
|
|
133
50
|
}
|
|
134
51
|
return undefined;
|
|
135
52
|
}
|
|
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)
|
|
53
|
+
function appliedValue(value, reader, expressId) {
|
|
54
|
+
if (!reader.attributePresent(expressId, 2))
|
|
161
55
|
return undefined;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
56
|
+
if (value === null || value === undefined)
|
|
57
|
+
return { Kind: 'Unsupported', Raw: value };
|
|
58
|
+
if (Array.isArray(value) && value.length === 2 && typeof value[0] === 'string') {
|
|
59
|
+
const parsed = reader.decimalLexeme(expressId, 2);
|
|
60
|
+
return parsed === undefined
|
|
61
|
+
? { Kind: 'Unsupported', Raw: value, InvalidNumber: true }
|
|
62
|
+
: { Kind: 'Typed', Type: value[0].toUpperCase(), Value: parsed };
|
|
63
|
+
}
|
|
64
|
+
const reference = reader.referenceLexeme(expressId, 2);
|
|
65
|
+
if (reference !== undefined && reader.get(reference)) {
|
|
66
|
+
return { Kind: 'Reference', expressId: reference };
|
|
67
|
+
}
|
|
68
|
+
return { Kind: 'Unsupported', Raw: value };
|
|
173
69
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
|
|
70
|
+
function quantityType(dimension) {
|
|
71
|
+
switch (dimension) {
|
|
72
|
+
case 'length': return QuantityType.Length;
|
|
73
|
+
case 'area': return QuantityType.Area;
|
|
74
|
+
case 'volume': return QuantityType.Volume;
|
|
75
|
+
case 'mass': return QuantityType.Weight;
|
|
76
|
+
case 'time': return QuantityType.Time;
|
|
77
|
+
case 'number': return QuantityType.Number;
|
|
78
|
+
default: return QuantityType.Count;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function finiteCompatibilityNumber(value) {
|
|
82
|
+
if (value === undefined)
|
|
183
83
|
return undefined;
|
|
184
|
-
|
|
84
|
+
const converted = Number(value);
|
|
85
|
+
if (!Number.isFinite(converted))
|
|
185
86
|
return undefined;
|
|
186
|
-
|
|
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
|
-
};
|
|
87
|
+
return converted !== 0 || isZeroCostNumericLexeme(value) ? converted : undefined;
|
|
207
88
|
}
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
if (ids.length === 0)
|
|
89
|
+
function pushInvalidList(reader, diagnostics, expressId, index, attribute, value) {
|
|
90
|
+
if (!reader.attributePresent(expressId, index))
|
|
211
91
|
return undefined;
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
* Walks every IfcCostItem / IfcCostSchedule / IfcRelNests /
|
|
222
|
-
* IfcRelAssignsToControl entity and assembles a connected CostExtraction.
|
|
223
|
-
*/
|
|
224
|
-
export function extractCostOnDemand(store) {
|
|
225
|
-
if (!store.source?.length) {
|
|
226
|
-
return { costSchedules: [], costItems: [], hasCost: false };
|
|
92
|
+
const refs = reader.referenceListLexeme(expressId, index);
|
|
93
|
+
if (!refs || refs.length === 0 || !Array.isArray(value) || refs.length !== value.length) {
|
|
94
|
+
diagnostics.push({
|
|
95
|
+
Code: 'INVALID_LIST',
|
|
96
|
+
Message: `${attribute} on #${expressId} must contain at least one reference`,
|
|
97
|
+
Severity: 'warning',
|
|
98
|
+
expressId,
|
|
99
|
+
});
|
|
100
|
+
return [];
|
|
227
101
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
102
|
+
return refs;
|
|
103
|
+
}
|
|
104
|
+
function extractSchedules(reader, schemaIs2x3) {
|
|
105
|
+
const result = [];
|
|
106
|
+
for (const expressId of reader.ids('IFCCOSTSCHEDULE')) {
|
|
107
|
+
const a = reader.get(expressId)?.attributes ?? [];
|
|
108
|
+
result.push(schemaIs2x3 ? {
|
|
109
|
+
expressId,
|
|
110
|
+
GlobalId: asString(a[0]) ?? '',
|
|
111
|
+
Name: asString(a[2]), Description: asString(a[3]), ObjectType: asString(a[4]),
|
|
112
|
+
SubmittedOn: dateTime2x3(reader, a[7]), Status: asString(a[8]),
|
|
113
|
+
UpdateDate: dateTime2x3(reader, a[10]), ID: asString(a[11]), PredefinedType: asEnum(a[12]),
|
|
114
|
+
globalId: asString(a[0]) ?? '', name: asString(a[2]) ?? '',
|
|
115
|
+
predefinedType: asEnum(a[12]), status: asString(a[8]),
|
|
116
|
+
submittedOn: dateTime2x3(reader, a[7]), updateDate: dateTime2x3(reader, a[10]),
|
|
117
|
+
costItemGlobalIds: [],
|
|
118
|
+
} : {
|
|
119
|
+
expressId,
|
|
120
|
+
GlobalId: asString(a[0]) ?? '',
|
|
121
|
+
Name: asString(a[2]), Description: asString(a[3]), ObjectType: asString(a[4]),
|
|
122
|
+
Identification: asString(a[5]), PredefinedType: asEnum(a[6]), Status: asString(a[7]),
|
|
123
|
+
SubmittedOn: scalarString(a[8]), UpdateDate: scalarString(a[9]),
|
|
124
|
+
globalId: asString(a[0]) ?? '', name: asString(a[2]) ?? '',
|
|
125
|
+
predefinedType: asEnum(a[6]), status: asString(a[7]),
|
|
126
|
+
submittedOn: scalarString(a[8]), updateDate: scalarString(a[9]),
|
|
127
|
+
costItemGlobalIds: [],
|
|
128
|
+
});
|
|
235
129
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
function extractItems(reader, schemaIs2x3, diagnostics) {
|
|
133
|
+
return reader.ids('IFCCOSTITEM').map((expressId) => {
|
|
134
|
+
const a = reader.get(expressId)?.attributes ?? [];
|
|
135
|
+
const base = {
|
|
136
|
+
expressId, GlobalId: asString(a[0]) ?? '', Name: asString(a[2]),
|
|
137
|
+
Description: asString(a[3]), ObjectType: asString(a[4]),
|
|
138
|
+
globalId: asString(a[0]) ?? '', name: asString(a[2]) ?? '',
|
|
139
|
+
childGlobalIds: [], productExpressIds: [], productGlobalIds: [],
|
|
140
|
+
controllingScheduleGlobalIds: [],
|
|
141
|
+
};
|
|
142
|
+
if (schemaIs2x3)
|
|
143
|
+
return base;
|
|
144
|
+
base.Identification = asString(a[5]);
|
|
145
|
+
base.PredefinedType = asEnum(a[6]);
|
|
146
|
+
base.predefinedType = base.PredefinedType;
|
|
147
|
+
base.CostValues = pushInvalidList(reader, diagnostics, expressId, 7, 'CostValues', a[7]);
|
|
148
|
+
base.CostQuantities = pushInvalidList(reader, diagnostics, expressId, 8, 'CostQuantities', a[8]);
|
|
149
|
+
return base;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function extractValues(reader, schemaIs2x3, diagnostics) {
|
|
153
|
+
const ids = new Set([...reader.ids('IFCCOSTVALUE'), ...reader.ids('IFCAPPLIEDVALUE')]);
|
|
154
|
+
const values = [];
|
|
155
|
+
for (const expressId of ids) {
|
|
156
|
+
const entity = reader.get(expressId);
|
|
247
157
|
if (!entity)
|
|
248
158
|
continue;
|
|
249
|
-
const a = entity.attributes
|
|
250
|
-
const
|
|
251
|
-
const
|
|
159
|
+
const a = entity.attributes ?? [];
|
|
160
|
+
const AppliedValue = appliedValue(a[2], reader, expressId);
|
|
161
|
+
const ApplicableDate = schemaIs2x3 ? dateTime2x3(reader, a[4]) : scalarString(a[4]);
|
|
162
|
+
const FixedUntilDate = schemaIs2x3 ? dateTime2x3(reader, a[5]) : scalarString(a[5]);
|
|
163
|
+
const Condition = asString(a[7]);
|
|
164
|
+
const InvalidCondition = reader.attributePresent(expressId, 7) && Condition === undefined;
|
|
165
|
+
const UnitBasis = reader.referenceLexeme(expressId, 3);
|
|
166
|
+
const InvalidUnitBasis = reader.attributePresent(expressId, 3) && UnitBasis === undefined;
|
|
167
|
+
const base = {
|
|
252
168
|
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: [],
|
|
169
|
+
Type: entity.type.toUpperCase() === 'IFCCOSTVALUE' ? 'IfcCostValue' : 'IfcAppliedValue',
|
|
170
|
+
Name: asString(a[0]), Description: asString(a[1]), AppliedValue,
|
|
171
|
+
UnitBasis, InvalidUnitBasis: InvalidUnitBasis || undefined, ApplicableDate, FixedUntilDate,
|
|
172
|
+
InvalidCondition: InvalidCondition || undefined,
|
|
173
|
+
name: asString(a[0]), description: asString(a[1]),
|
|
174
|
+
appliedValue: AppliedValue?.Kind === 'Typed'
|
|
175
|
+
? finiteCompatibilityNumber(AppliedValue.Value)
|
|
176
|
+
: undefined,
|
|
177
|
+
applicableDate: ApplicableDate, fixedUntilDate: FixedUntilDate,
|
|
269
178
|
};
|
|
270
|
-
|
|
179
|
+
if (schemaIs2x3) {
|
|
180
|
+
base.CostType = asString(a[6]);
|
|
181
|
+
base.Condition = Condition;
|
|
182
|
+
base.condition = base.Condition;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
base.Category = asString(a[6]);
|
|
186
|
+
base.Condition = Condition;
|
|
187
|
+
base.ArithmeticOperator = asEnum(a[8]);
|
|
188
|
+
base.Components = pushInvalidList(reader, diagnostics, expressId, 9, 'Components', a[9]);
|
|
189
|
+
base.category = base.Category;
|
|
190
|
+
base.condition = base.Condition;
|
|
191
|
+
base.arithmeticOperator = base.ArithmeticOperator;
|
|
192
|
+
}
|
|
193
|
+
values.push(base);
|
|
271
194
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
195
|
+
return values;
|
|
196
|
+
}
|
|
197
|
+
/** Extract a schema-aware, read-only IFC cost graph from a parsed store. */
|
|
198
|
+
export function extractCostOnDemand(store) {
|
|
199
|
+
const schema = store.schemaVersion;
|
|
200
|
+
const diagnostics = [];
|
|
201
|
+
const empty = () => ({
|
|
202
|
+
SchemaVersion: schema, CostSchedules: [], CostItems: [], CostValues: [],
|
|
203
|
+
CostQuantities: [], Units: [], MeasuresWithUnit: [], ProjectUnits: {}, Relationships: [], Diagnostics: diagnostics,
|
|
204
|
+
HasCostData: false, costSchedules: [], costItems: [], hasCost: false,
|
|
205
|
+
});
|
|
206
|
+
if (!store.source?.length)
|
|
207
|
+
return empty();
|
|
208
|
+
const reader = new CostEntityReader(store);
|
|
209
|
+
const hasCost = reader.ids('IFCCOSTSCHEDULE').length + reader.ids('IFCCOSTITEM').length +
|
|
210
|
+
reader.ids('IFCCOSTVALUE').length + reader.ids('IFCAPPLIEDVALUE').length > 0;
|
|
211
|
+
if (!hasCost)
|
|
212
|
+
return empty();
|
|
213
|
+
if (schema === 'IFC5') {
|
|
214
|
+
diagnostics.push({ Code: 'UNSUPPORTED_SCHEMA', Message: 'IFC5 cost extraction is not supported', Severity: 'error' });
|
|
215
|
+
return { ...empty(), HasCostData: true };
|
|
216
|
+
}
|
|
217
|
+
const schemaIs2x3 = schema === 'IFC2X3';
|
|
218
|
+
if (schemaIs2x3)
|
|
219
|
+
diagnostics.push({
|
|
220
|
+
Code: 'IFC2X3_PARTIAL_READ',
|
|
221
|
+
Message: 'IFC2X3 cost metadata and relationships are preserved, but values are not evaluated',
|
|
222
|
+
Severity: 'warning',
|
|
223
|
+
});
|
|
224
|
+
const CostSchedules = extractSchedules(reader, schemaIs2x3);
|
|
225
|
+
const CostItems = extractItems(reader, schemaIs2x3, diagnostics);
|
|
226
|
+
const CostValues = extractValues(reader, schemaIs2x3, diagnostics);
|
|
227
|
+
const itemIds = new Set(CostItems.map(item => item.expressId));
|
|
228
|
+
const scheduleIds = new Set(CostSchedules.map(schedule => schedule.expressId));
|
|
229
|
+
const valueIds = new Set(CostValues.flatMap(value => value.expressId === undefined ? [] : [value.expressId]));
|
|
230
|
+
const Relationships = extractCostRelationships(reader, itemIds, scheduleIds, valueIds);
|
|
231
|
+
const quantityMap = new Map();
|
|
232
|
+
extractCostQuantities(reader, CostItems.flatMap(item => item.CostQuantities ?? []), quantityMap, diagnostics);
|
|
233
|
+
const unitResolver = new CostUnitResolver(reader, diagnostics);
|
|
234
|
+
for (const quantity of quantityMap.values()) {
|
|
235
|
+
if (quantity.Unit !== undefined)
|
|
236
|
+
unitResolver.resolve(quantity.Unit);
|
|
237
|
+
}
|
|
238
|
+
for (const value of CostValues) {
|
|
239
|
+
if (value.UnitBasis !== undefined) {
|
|
240
|
+
const basis = unitResolver.resolveMeasureWithUnit(value.UnitBasis);
|
|
241
|
+
const compatibility = unitResolver.compatibilityMeasureWithUnit(value.UnitBasis, basis);
|
|
242
|
+
const valueComponent = finiteCompatibilityNumber(compatibility?.Value);
|
|
243
|
+
const unitSiScale = finiteCompatibilityNumber(compatibility?.Unit?.Scale);
|
|
244
|
+
value.unitBasis = compatibility ? {
|
|
245
|
+
valueComponent,
|
|
246
|
+
unitSymbol: compatibility.Unit?.Scale === undefined ? undefined : compatibility.Unit.Symbol,
|
|
247
|
+
unitSiScale,
|
|
248
|
+
} : undefined;
|
|
249
|
+
}
|
|
250
|
+
if (value.AppliedValue?.Kind === 'Reference' && reader.typeOf(value.AppliedValue.expressId) === 'IFCMEASUREWITHUNIT') {
|
|
251
|
+
unitResolver.resolveMeasureWithUnit(value.AppliedValue.expressId);
|
|
296
252
|
}
|
|
297
253
|
}
|
|
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);
|
|
254
|
+
diagnoseCostGraphs(Relationships, CostValues, itemIds, diagnostics);
|
|
255
|
+
const schedules = new Map(CostSchedules.map(schedule => [schedule.expressId, schedule]));
|
|
256
|
+
const items = new Map(CostItems.map(item => [item.expressId, item]));
|
|
257
|
+
const values = new Map(CostValues.map(value => [value.expressId, value]));
|
|
258
|
+
const legacyComponents = compatibilityTreeBuilder(values, diagnostics);
|
|
259
|
+
const quantities = new Map([...quantityMap.values()].map(quantity => [quantity.expressId, quantity]));
|
|
260
|
+
for (const value of CostValues) {
|
|
261
|
+
for (const componentId of value.Components ?? []) {
|
|
262
|
+
if (!values.has(componentId))
|
|
263
|
+
diagnostics.push({
|
|
264
|
+
Code: 'MISSING_REFERENCE',
|
|
265
|
+
Message: `IfcAppliedValue component #${componentId} cannot be resolved`,
|
|
266
|
+
Severity: 'error', expressId: value.expressId, RelatedExpressId: componentId,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
327
269
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
270
|
+
const compatibilityBuilt = new Set();
|
|
271
|
+
for (const item of CostItems) {
|
|
272
|
+
for (const valueId of item.CostValues ?? []) {
|
|
273
|
+
const value = values.get(valueId);
|
|
274
|
+
if (!value)
|
|
275
|
+
diagnostics.push({
|
|
276
|
+
Code: 'MISSING_REFERENCE',
|
|
277
|
+
Message: `IfcCostValue #${valueId} cannot be resolved`,
|
|
278
|
+
Severity: 'error', expressId: item.expressId, RelatedExpressId: valueId,
|
|
279
|
+
});
|
|
280
|
+
else if (value.Type !== 'IfcCostValue')
|
|
281
|
+
diagnostics.push({
|
|
282
|
+
Code: 'INVALID_LIST',
|
|
283
|
+
Message: `CostValues on IfcCostItem #${item.expressId} must reference IfcCostValue`,
|
|
284
|
+
Severity: 'error', expressId: item.expressId, RelatedExpressId: valueId,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
item.costValues = item.CostValues
|
|
288
|
+
?.map(id => {
|
|
289
|
+
const value = values.get(id);
|
|
290
|
+
if (value && !compatibilityBuilt.has(id)) {
|
|
291
|
+
value.components = legacyComponents(id);
|
|
292
|
+
compatibilityBuilt.add(id);
|
|
293
|
+
}
|
|
294
|
+
return value;
|
|
295
|
+
})
|
|
296
|
+
.filter((entry) => entry !== undefined);
|
|
297
|
+
item.costQuantities = item.CostQuantities
|
|
298
|
+
?.map(id => quantities.get(id))
|
|
299
|
+
.filter((entry) => entry !== undefined && costQuantityExactValue(entry) !== undefined)
|
|
300
|
+
.flatMap(quantity => {
|
|
301
|
+
const value = finiteCompatibilityNumber(costQuantityExactValue(quantity));
|
|
302
|
+
if (value === undefined)
|
|
303
|
+
return [];
|
|
304
|
+
const unit = quantity.Unit === undefined ? undefined : unitResolver.Units.get(quantity.Unit);
|
|
305
|
+
const explicitUnitSiScale = finiteCompatibilityNumber(unit?.Scale);
|
|
306
|
+
return [{
|
|
307
|
+
name: quantity.Name ?? '',
|
|
308
|
+
type: quantityType(quantity.Dimension),
|
|
309
|
+
value,
|
|
310
|
+
...(explicitUnitSiScale !== undefined ? { explicitUnitSiScale } : {}),
|
|
311
|
+
}];
|
|
312
|
+
});
|
|
313
|
+
if (item.costQuantities?.length === 0)
|
|
314
|
+
item.costQuantities = undefined;
|
|
315
|
+
}
|
|
316
|
+
for (const relation of Relationships) {
|
|
317
|
+
if (relation.Type === 'IfcRelNests' && relation.RelatingObject !== undefined) {
|
|
318
|
+
const parent = items.get(relation.RelatingObject);
|
|
319
|
+
if (!parent)
|
|
320
|
+
continue;
|
|
321
|
+
for (const childId of relation.RelatedObjects ?? []) {
|
|
322
|
+
const child = items.get(childId);
|
|
323
|
+
if (!child)
|
|
348
324
|
continue;
|
|
349
|
-
|
|
350
|
-
|
|
325
|
+
parent.childGlobalIds.push(child.GlobalId ?? '');
|
|
326
|
+
child.parentGlobalId ??= parent.GlobalId ?? '';
|
|
351
327
|
}
|
|
352
|
-
continue;
|
|
353
328
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
329
|
+
else if ((relation.Type === 'IfcRelAssignsToControl' || relation.Type === 'IfcRelSchedulesCostItems') &&
|
|
330
|
+
relation.RelatingControl !== undefined) {
|
|
331
|
+
const schedule = schedules.get(relation.RelatingControl);
|
|
332
|
+
const item = items.get(relation.RelatingControl);
|
|
333
|
+
for (const relatedId of relation.RelatedObjects ?? []) {
|
|
334
|
+
const relatedItem = items.get(relatedId);
|
|
335
|
+
if (schedule && relatedItem) {
|
|
336
|
+
schedule.costItemGlobalIds.push(relatedItem.GlobalId ?? '');
|
|
337
|
+
relatedItem.controllingScheduleGlobalIds.push(schedule.GlobalId ?? '');
|
|
338
|
+
}
|
|
339
|
+
else if (item) {
|
|
340
|
+
item.productExpressIds.push(relatedId);
|
|
341
|
+
item.productGlobalIds.push(store.entities?.getGlobalId?.(relatedId) ?? '');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else if (relation.Type === 'IfcRelAssignsToProduct' && relation.RelatingProduct !== undefined) {
|
|
346
|
+
for (const relatedId of relation.RelatedObjects ?? []) {
|
|
347
|
+
const item = items.get(relatedId);
|
|
348
|
+
if (!item)
|
|
349
|
+
continue;
|
|
350
|
+
item.productExpressIds.push(relation.RelatingProduct);
|
|
351
|
+
item.productGlobalIds.push(store.entities?.getGlobalId?.(relation.RelatingProduct) ?? '');
|
|
360
352
|
}
|
|
361
353
|
}
|
|
362
354
|
}
|
|
363
355
|
return {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
356
|
+
SchemaVersion: schema, CostSchedules, CostItems, CostValues,
|
|
357
|
+
CostQuantities: [...quantityMap.values()], Units: [...unitResolver.Units.values()],
|
|
358
|
+
MeasuresWithUnit: [...unitResolver.MeasuresWithUnit.values()],
|
|
359
|
+
ProjectUnits: Object.fromEntries([...unitResolver.ProjectUnits.entries()].map(([dimension, unit]) => [dimension, unit.expressId])),
|
|
360
|
+
Relationships, Diagnostics: diagnostics, HasCostData: true, Currency: unitResolver.Currency,
|
|
361
|
+
costSchedules: CostSchedules, costItems: CostItems, hasCost: true,
|
|
367
362
|
};
|
|
368
363
|
}
|
|
369
364
|
//# sourceMappingURL=cost-extractor.js.map
|