@ifc-lite/parser 4.3.1 → 5.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/dist/attribute-helpers.d.ts +38 -4
- package/dist/attribute-helpers.d.ts.map +1 -1
- package/dist/attribute-helpers.js +71 -4
- package/dist/attribute-helpers.js.map +1 -1
- package/dist/columnar-parser-attributes.d.ts.map +1 -1
- package/dist/columnar-parser-attributes.js +35 -5
- package/dist/columnar-parser-attributes.js.map +1 -1
- package/dist/columnar-parser-indexes.d.ts.map +1 -1
- package/dist/columnar-parser-indexes.js +17 -2
- package/dist/columnar-parser-indexes.js.map +1 -1
- package/dist/columnar-parser-relationships.d.ts.map +1 -1
- package/dist/columnar-parser-relationships.js +6 -1
- package/dist/columnar-parser-relationships.js.map +1 -1
- package/dist/columnar-parser.d.ts +3 -1
- package/dist/columnar-parser.d.ts.map +1 -1
- package/dist/columnar-parser.js +27 -76
- package/dist/columnar-parser.js.map +1 -1
- package/dist/compact-entity-index-transport.d.ts +23 -0
- package/dist/compact-entity-index-transport.d.ts.map +1 -0
- package/dist/compact-entity-index-transport.js +28 -0
- package/dist/compact-entity-index-transport.js.map +1 -0
- package/dist/compact-entity-index.d.ts +0 -10
- package/dist/compact-entity-index.d.ts.map +1 -1
- package/dist/compact-entity-index.js +31 -24
- package/dist/compact-entity-index.js.map +1 -1
- package/dist/data-store-transport.d.ts +2 -12
- package/dist/data-store-transport.d.ts.map +1 -1
- package/dist/data-store-transport.js +7 -20
- package/dist/data-store-transport.js.map +1 -1
- package/dist/entity-extractor.d.ts.map +1 -1
- package/dist/entity-extractor.js +101 -6
- package/dist/entity-extractor.js.map +1 -1
- package/dist/entity-refs-from-index.d.ts.map +1 -1
- package/dist/entity-refs-from-index.js +12 -2
- package/dist/entity-refs-from-index.js.map +1 -1
- package/dist/entity-scanner.d.ts +63 -0
- package/dist/entity-scanner.d.ts.map +1 -1
- package/dist/entity-scanner.js +79 -2
- package/dist/entity-scanner.js.map +1 -1
- package/dist/express-id.d.ts +47 -0
- package/dist/express-id.d.ts.map +1 -0
- package/dist/express-id.js +57 -0
- package/dist/express-id.js.map +1 -0
- package/dist/generated/entities.d.ts +1 -1
- package/dist/generated/entities.d.ts.map +1 -1
- package/dist/generated/schema-registry.js +146 -146
- package/dist/generated/schema-registry.js.map +1 -1
- package/dist/georef-epset.d.ts +25 -0
- package/dist/georef-epset.d.ts.map +1 -0
- package/dist/georef-epset.js +214 -0
- package/dist/georef-epset.js.map +1 -0
- package/dist/georef-extractor.d.ts +9 -12
- package/dist/georef-extractor.d.ts.map +1 -1
- package/dist/georef-extractor.js +66 -304
- package/dist/georef-extractor.js.map +1 -1
- package/dist/georef-map-conversion.d.ts +29 -0
- package/dist/georef-map-conversion.d.ts.map +1 -0
- package/dist/georef-map-conversion.js +75 -0
- package/dist/georef-map-conversion.js.map +1 -0
- package/dist/georef-transform.d.ts +25 -0
- package/dist/georef-transform.d.ts.map +1 -0
- package/dist/georef-transform.js +97 -0
- package/dist/georef-transform.js.map +1 -0
- package/dist/ifc-schema.d.ts +4 -2
- package/dist/ifc-schema.d.ts.map +1 -1
- package/dist/ifc-schema.js +15 -5
- package/dist/ifc-schema.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/map-unit-label.d.ts +43 -0
- package/dist/map-unit-label.d.ts.map +1 -0
- package/dist/map-unit-label.js +140 -0
- package/dist/map-unit-label.js.map +1 -0
- package/dist/material-extractor.d.ts +2 -17
- package/dist/material-extractor.d.ts.map +1 -1
- package/dist/material-extractor.js +12 -37
- package/dist/material-extractor.js.map +1 -1
- package/dist/material-layer-reader.d.ts +45 -0
- package/dist/material-layer-reader.d.ts.map +1 -0
- package/dist/material-layer-reader.js +63 -0
- package/dist/material-layer-reader.js.map +1 -0
- package/dist/material-resolver.d.ts.map +1 -1
- package/dist/material-resolver.js +14 -14
- package/dist/material-resolver.js.map +1 -1
- package/dist/measure-unit-scale.d.ts +41 -0
- package/dist/measure-unit-scale.d.ts.map +1 -0
- package/dist/measure-unit-scale.js +53 -0
- package/dist/measure-unit-scale.js.map +1 -0
- package/dist/on-demand-cache.d.ts +3 -0
- package/dist/on-demand-cache.d.ts.map +1 -0
- package/dist/on-demand-cache.js +25 -0
- package/dist/on-demand-cache.js.map +1 -0
- package/dist/on-demand-extractors.d.ts +5 -21
- package/dist/on-demand-extractors.d.ts.map +1 -1
- package/dist/on-demand-extractors.js +64 -437
- package/dist/on-demand-extractors.js.map +1 -1
- package/dist/on-demand-georeferencing.d.ts +19 -0
- package/dist/on-demand-georeferencing.d.ts.map +1 -0
- package/dist/on-demand-georeferencing.js +139 -0
- package/dist/on-demand-georeferencing.js.map +1 -0
- package/dist/owning-project.d.ts +54 -0
- package/dist/owning-project.d.ts.map +1 -0
- package/dist/owning-project.js +76 -0
- package/dist/owning-project.js.map +1 -0
- package/dist/parser.worker.d.ts +14 -0
- package/dist/parser.worker.d.ts.map +1 -1
- package/dist/parser.worker.js +2 -0
- package/dist/parser.worker.js.map +1 -1
- package/dist/project-units-symbols.d.ts +33 -0
- package/dist/project-units-symbols.d.ts.map +1 -0
- package/dist/project-units-symbols.js +265 -0
- package/dist/project-units-symbols.js.map +1 -0
- package/dist/project-units.d.ts +11 -19
- package/dist/project-units.d.ts.map +1 -1
- package/dist/project-units.js +9 -267
- package/dist/project-units.js.map +1 -1
- package/dist/property-set-merge.d.ts +65 -8
- package/dist/property-set-merge.d.ts.map +1 -1
- package/dist/property-set-merge.js +130 -29
- package/dist/property-set-merge.js.map +1 -1
- package/dist/property-value-parser.d.ts +55 -0
- package/dist/property-value-parser.d.ts.map +1 -0
- package/dist/property-value-parser.js +277 -0
- package/dist/property-value-parser.js.map +1 -0
- package/dist/quantity-collect.d.ts +135 -0
- package/dist/quantity-collect.d.ts.map +1 -0
- package/dist/quantity-collect.js +253 -0
- package/dist/quantity-collect.js.map +1 -0
- package/dist/query-backend-maps.d.ts +5 -8
- package/dist/query-backend-maps.d.ts.map +1 -1
- package/dist/query-backend-maps.js +159 -15
- package/dist/query-backend-maps.js.map +1 -1
- package/dist/relationship-extractor.d.ts.map +1 -1
- package/dist/relationship-extractor.js +18 -0
- package/dist/relationship-extractor.js.map +1 -1
- package/dist/scan-entities-balanced.d.ts +38 -0
- package/dist/scan-entities-balanced.d.ts.map +1 -0
- package/dist/scan-entities-balanced.js +258 -0
- package/dist/scan-entities-balanced.js.map +1 -0
- package/dist/scan-worker-inline.d.ts +17 -11
- package/dist/scan-worker-inline.d.ts.map +1 -1
- package/dist/scan-worker-inline.js +9 -235
- package/dist/scan-worker-inline.js.map +1 -1
- package/dist/scan-worker-source.d.ts +9 -0
- package/dist/scan-worker-source.d.ts.map +1 -0
- package/dist/scan-worker-source.js +383 -0
- package/dist/scan-worker-source.js.map +1 -0
- package/dist/source-header.d.ts +23 -1
- package/dist/source-header.d.ts.map +1 -1
- package/dist/source-header.js +133 -43
- package/dist/source-header.js.map +1 -1
- package/dist/spatial-hierarchy-builder.js +3 -3
- package/dist/spatial-hierarchy-builder.js.map +1 -1
- package/dist/step-lexing.d.ts +94 -0
- package/dist/step-lexing.d.ts.map +1 -1
- package/dist/step-lexing.js +292 -25
- package/dist/step-lexing.js.map +1 -1
- package/dist/step-trivia.d.ts +60 -0
- package/dist/step-trivia.d.ts.map +1 -0
- package/dist/step-trivia.js +63 -0
- package/dist/step-trivia.js.map +1 -0
- package/dist/tokenizer.d.ts +20 -21
- package/dist/tokenizer.d.ts.map +1 -1
- package/dist/tokenizer.js +164 -203
- package/dist/tokenizer.js.map +1 -1
- package/dist/unit-extractor.d.ts +56 -1
- package/dist/unit-extractor.d.ts.map +1 -1
- package/dist/unit-extractor.js +80 -6
- package/dist/unit-extractor.js.map +1 -1
- package/dist/worker-parser.d.ts +11 -1
- package/dist/worker-parser.d.ts.map +1 -1
- package/dist/worker-parser.js +16 -2
- package/dist/worker-parser.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,10 +1,17 @@
|
|
|
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
|
+
* On-demand extraction functions for classifications, materials, documents,
|
|
6
|
+
* georeferencing, relationships, and type properties.
|
|
7
|
+
*
|
|
8
|
+
* These functions parse data lazily from the IFC source buffer when accessed,
|
|
9
|
+
* rather than pre-building all data upfront during initial parse.
|
|
10
|
+
*/
|
|
4
11
|
import { EntityExtractor } from './entity-extractor.js';
|
|
5
|
-
import { RelationshipType,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
12
|
+
import { RelationshipType, } from '@ifc-lite/data';
|
|
13
|
+
import { readQuantitySet } from './quantity-collect.js';
|
|
14
|
+
import { appendSetsFromSecondSource, setIdentityKey } from './property-set-merge.js';
|
|
8
15
|
// Re-export classification and material resolvers
|
|
9
16
|
export { extractClassificationsOnDemand, extractClassificationSystemsOnDemand } from './classification-resolver.js';
|
|
10
17
|
export { extractMaterialsOnDemand, extractAllMaterialsOnDemand } from './material-resolver.js';
|
|
@@ -13,219 +20,11 @@ import { resolveAllMaterialDefIds as resolveAllMaterialDefIdsImpl, collectMateri
|
|
|
13
20
|
// ============================================================================
|
|
14
21
|
// Property Value Parsing Helpers
|
|
15
22
|
// ============================================================================
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* - IfcPropertyBoundedValue: upper/lower bounds → "value [min – max]"
|
|
22
|
-
* - IfcPropertyListValue: list of values → joined string
|
|
23
|
-
* - IfcPropertyTableValue: defining/defined value pairs → "Table(N rows)"
|
|
24
|
-
* - IfcPropertyReferenceValue: entity reference → "Reference #ID"
|
|
25
|
-
*/
|
|
26
|
-
export function parsePropertyValue(propEntity) {
|
|
27
|
-
const attrs = propEntity.attributes || [];
|
|
28
|
-
const typeUpper = propEntity.type.toUpperCase();
|
|
29
|
-
switch (typeUpper) {
|
|
30
|
-
case 'IFCPROPERTYENUMERATEDVALUE': {
|
|
31
|
-
// [Name, Description, EnumerationValues (list), EnumerationReference]
|
|
32
|
-
const enumValues = attrs[2];
|
|
33
|
-
if (Array.isArray(enumValues)) {
|
|
34
|
-
const values = enumValues.map(v => {
|
|
35
|
-
if (Array.isArray(v) && v.length === 2)
|
|
36
|
-
return String(v[1]); // Typed value
|
|
37
|
-
return String(v);
|
|
38
|
-
}).filter(v => v !== 'null' && v !== 'undefined');
|
|
39
|
-
// Surface the raw value list separately so IDS facet
|
|
40
|
-
// checks can iterate "any matching value passes". The
|
|
41
|
-
// joined display string remains the primary `value`
|
|
42
|
-
// for visualisation/property-table consumers.
|
|
43
|
-
return { type: 0, value: values.join(', ') || null, values };
|
|
44
|
-
}
|
|
45
|
-
return { type: 0, value: null };
|
|
46
|
-
}
|
|
47
|
-
case 'IFCPROPERTYBOUNDEDVALUE': {
|
|
48
|
-
// [Name, Description, UpperBoundValue, LowerBoundValue, Unit, SetPointValue]
|
|
49
|
-
const upper = extractNumericValue(attrs[2]);
|
|
50
|
-
const lower = extractNumericValue(attrs[3]);
|
|
51
|
-
const setPoint = extractNumericValue(attrs[5]);
|
|
52
|
-
const displayValue = setPoint ?? upper ?? lower;
|
|
53
|
-
let display = displayValue != null ? String(displayValue) : '';
|
|
54
|
-
if (lower != null && upper != null) {
|
|
55
|
-
display += ` [${lower} – ${upper}]`;
|
|
56
|
-
}
|
|
57
|
-
// Surface every defined bound as a candidate value — IDS
|
|
58
|
-
// bounded-property checks pass when ANY of the bounds /
|
|
59
|
-
// setpoint matches the constraint, per upstream ifctester.
|
|
60
|
-
const candidates = [];
|
|
61
|
-
if (lower != null)
|
|
62
|
-
candidates.push(String(lower));
|
|
63
|
-
if (upper != null && upper !== lower)
|
|
64
|
-
candidates.push(String(upper));
|
|
65
|
-
if (setPoint != null && setPoint !== lower && setPoint !== upper) {
|
|
66
|
-
candidates.push(String(setPoint));
|
|
67
|
-
}
|
|
68
|
-
// Carry the IFC-declared measure tag so the IDS-side data
|
|
69
|
-
// type comparison and unit conversion both work.
|
|
70
|
-
const inferDataType = (attr) => {
|
|
71
|
-
if (Array.isArray(attr) && attr.length === 2) {
|
|
72
|
-
return String(attr[0]).toUpperCase();
|
|
73
|
-
}
|
|
74
|
-
return undefined;
|
|
75
|
-
};
|
|
76
|
-
const dataType = inferDataType(attrs[5]) ||
|
|
77
|
-
inferDataType(attrs[2]) ||
|
|
78
|
-
inferDataType(attrs[3]);
|
|
79
|
-
return {
|
|
80
|
-
type: displayValue != null ? 1 : 0,
|
|
81
|
-
value: display || null,
|
|
82
|
-
...(candidates.length > 0 ? { values: candidates } : {}),
|
|
83
|
-
...(dataType ? { dataType } : {}),
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
case 'IFCPROPERTYLISTVALUE': {
|
|
87
|
-
// [Name, Description, ListValues (list), Unit]
|
|
88
|
-
const listValues = attrs[2];
|
|
89
|
-
if (Array.isArray(listValues)) {
|
|
90
|
-
const values = listValues.map(v => {
|
|
91
|
-
if (Array.isArray(v) && v.length === 2)
|
|
92
|
-
return String(v[1]);
|
|
93
|
-
return String(v);
|
|
94
|
-
}).filter(v => v !== 'null' && v !== 'undefined');
|
|
95
|
-
return { type: 0, value: values.join(', ') || null, values };
|
|
96
|
-
}
|
|
97
|
-
return { type: 0, value: null };
|
|
98
|
-
}
|
|
99
|
-
case 'IFCPROPERTYTABLEVALUE': {
|
|
100
|
-
// [Name, Description, DefiningValues, DefinedValues, ...]
|
|
101
|
-
const definingValues = attrs[2];
|
|
102
|
-
const definedValues = attrs[3];
|
|
103
|
-
const rowCount = Array.isArray(definingValues) ? definingValues.length : 0;
|
|
104
|
-
if (rowCount > 0 && Array.isArray(definedValues) && Array.isArray(definingValues)) {
|
|
105
|
-
// Surface both defining and defined values as candidate
|
|
106
|
-
// matches — IDS table-value checks pass when ANY entry
|
|
107
|
-
// matches the constraint (per upstream ifctester).
|
|
108
|
-
const stringify = (v) => {
|
|
109
|
-
if (Array.isArray(v) && v.length === 2)
|
|
110
|
-
return String(v[1]);
|
|
111
|
-
return String(v);
|
|
112
|
-
};
|
|
113
|
-
const values = [
|
|
114
|
-
...definingValues.map(stringify),
|
|
115
|
-
...definedValues.map(stringify),
|
|
116
|
-
].filter(v => v !== 'null' && v !== 'undefined');
|
|
117
|
-
// Tables mix types per column (label / length / …),
|
|
118
|
-
// so we can't surface a single representative
|
|
119
|
-
// dataType. Leaving it unset lets the IDS check fall
|
|
120
|
-
// through to a pure value match against any of the
|
|
121
|
-
// candidates — which is what upstream ifctester does
|
|
122
|
-
// for table values.
|
|
123
|
-
return {
|
|
124
|
-
type: 0,
|
|
125
|
-
value: `Table (${rowCount} rows)`,
|
|
126
|
-
values,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
return { type: 0, value: null };
|
|
130
|
-
}
|
|
131
|
-
case 'IFCPROPERTYREFERENCEVALUE': {
|
|
132
|
-
// [Name, Description, PropertyReference]
|
|
133
|
-
const refValue = attrs[2];
|
|
134
|
-
if (typeof refValue === 'number') {
|
|
135
|
-
return { type: 0, value: `#${refValue}` };
|
|
136
|
-
}
|
|
137
|
-
return { type: 0, value: null };
|
|
138
|
-
}
|
|
139
|
-
default: {
|
|
140
|
-
// IfcPropertySingleValue and fallback: [Name, Description, NominalValue, Unit]
|
|
141
|
-
const nominalValue = attrs[2];
|
|
142
|
-
let type = PropertyValueType.String;
|
|
143
|
-
let value = nominalValue;
|
|
144
|
-
let dataType;
|
|
145
|
-
// Handle typed values like IFCBOOLEAN(.T.), IFCREAL(1.5)
|
|
146
|
-
if (Array.isArray(nominalValue) && nominalValue.length === 2) {
|
|
147
|
-
const innerValue = nominalValue[1];
|
|
148
|
-
const typeName = String(nominalValue[0]).toUpperCase();
|
|
149
|
-
dataType = typeName;
|
|
150
|
-
if (typeName.includes('BOOLEAN')) {
|
|
151
|
-
type = PropertyValueType.Boolean;
|
|
152
|
-
value = innerValue === '.T.' || innerValue === true;
|
|
153
|
-
}
|
|
154
|
-
else if (typeName.includes('LOGICAL')) {
|
|
155
|
-
type = PropertyValueType.Logical;
|
|
156
|
-
// Preserve .U. (unknown) as null; .T./.F. as boolean
|
|
157
|
-
if (innerValue === '.U.' || innerValue === '.X.') {
|
|
158
|
-
value = null;
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
value = innerValue === '.T.' || innerValue === true;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
else if (typeof innerValue === 'number') {
|
|
165
|
-
// Preserve the IFC-declared numeric measure (IFCREAL,
|
|
166
|
-
// IFCINTEGER, IFCLENGTHMEASURE, IFCAREAMEASURE, …) —
|
|
167
|
-
// the source explicitly tagged the value, so don't
|
|
168
|
-
// re-infer from JS number-ness (which would
|
|
169
|
-
// misclassify e.g. `IFCREAL(0.0)` as integer).
|
|
170
|
-
if (typeName === 'IFCINTEGER' || typeName === 'IFCCOUNTMEASURE') {
|
|
171
|
-
type = PropertyValueType.Integer;
|
|
172
|
-
}
|
|
173
|
-
else if (typeName === 'IFCREAL' ||
|
|
174
|
-
typeName.endsWith('MEASURE') ||
|
|
175
|
-
typeName.endsWith('RATIO')) {
|
|
176
|
-
type = PropertyValueType.Real;
|
|
177
|
-
}
|
|
178
|
-
else if (Number.isInteger(innerValue)) {
|
|
179
|
-
type = PropertyValueType.Integer;
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
type = PropertyValueType.Real;
|
|
183
|
-
}
|
|
184
|
-
value = innerValue;
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
type = PropertyValueType.String;
|
|
188
|
-
value = String(innerValue);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
else if (typeof nominalValue === 'number') {
|
|
192
|
-
type = Number.isInteger(nominalValue) ? PropertyValueType.Integer : PropertyValueType.Real;
|
|
193
|
-
}
|
|
194
|
-
else if (typeof nominalValue === 'boolean') {
|
|
195
|
-
type = PropertyValueType.Boolean;
|
|
196
|
-
}
|
|
197
|
-
else if (nominalValue !== null && nominalValue !== undefined) {
|
|
198
|
-
// Normalize untagged STEP enumeration tokens. Conformant IFC wraps
|
|
199
|
-
// booleans as IFCBOOLEAN(.T.) (handled above), but some authoring
|
|
200
|
-
// tools emit the bare tokens directly in the NominalValue slot.
|
|
201
|
-
if (nominalValue === '.T.') {
|
|
202
|
-
type = PropertyValueType.Boolean;
|
|
203
|
-
value = true;
|
|
204
|
-
}
|
|
205
|
-
else if (nominalValue === '.F.') {
|
|
206
|
-
type = PropertyValueType.Boolean;
|
|
207
|
-
value = false;
|
|
208
|
-
}
|
|
209
|
-
else if (nominalValue === '.U.' || nominalValue === '.X.') {
|
|
210
|
-
type = PropertyValueType.Logical;
|
|
211
|
-
value = null;
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
value = String(nominalValue);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return { type, value, ...(dataType ? { dataType } : {}) };
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
/** Extract a numeric value from a possibly typed STEP value. */
|
|
222
|
-
export function extractNumericValue(attr) {
|
|
223
|
-
if (typeof attr === 'number')
|
|
224
|
-
return attr;
|
|
225
|
-
if (Array.isArray(attr) && attr.length === 2 && typeof attr[1] === 'number')
|
|
226
|
-
return attr[1];
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
23
|
+
//
|
|
24
|
+
// Moved to ./property-value-parser.ts to stay under the module-size budget;
|
|
25
|
+
// re-exported here so every existing import of this module keeps resolving.
|
|
26
|
+
export { parsePropertyValue, extractNumericValue, resolveComplexPropertyValue, parsePropertyValueWithComplex, } from './property-value-parser.js';
|
|
27
|
+
import { parsePropertyValueWithComplex } from './property-value-parser.js';
|
|
229
28
|
// ============================================================================
|
|
230
29
|
// Property Set Extraction Helpers
|
|
231
30
|
// ============================================================================
|
|
@@ -247,7 +46,7 @@ export function extractPsetsFromIds(store, extractor, psetIds) {
|
|
|
247
46
|
continue;
|
|
248
47
|
const psetAttrs = psetEntity.attributes || [];
|
|
249
48
|
const psetGlobalId = typeof psetAttrs[0] === 'string' ? psetAttrs[0] : undefined;
|
|
250
|
-
const psetName = typeof psetAttrs[2] === 'string' ? psetAttrs[2] : `PropertySet
|
|
49
|
+
const psetName = typeof psetAttrs[2] === 'string' ? psetAttrs[2] : ''; // not `PropertySet #<id>` (#3530)
|
|
251
50
|
const hasProperties = psetAttrs[4];
|
|
252
51
|
const properties = [];
|
|
253
52
|
if (Array.isArray(hasProperties)) {
|
|
@@ -264,7 +63,7 @@ export function extractPsetsFromIds(store, extractor, psetIds) {
|
|
|
264
63
|
const propName = typeof propAttrs[0] === 'string' ? propAttrs[0] : '';
|
|
265
64
|
if (!propName)
|
|
266
65
|
continue;
|
|
267
|
-
const parsed =
|
|
66
|
+
const parsed = parsePropertyValueWithComplex(store, extractor, propEntity);
|
|
268
67
|
const entry = {
|
|
269
68
|
name: propName,
|
|
270
69
|
type: parsed.type,
|
|
@@ -279,8 +78,9 @@ export function extractPsetsFromIds(store, extractor, psetIds) {
|
|
|
279
78
|
}
|
|
280
79
|
// Only surface sets that actually carry properties. An empty, named set
|
|
281
80
|
// contributes nothing and — because extractTypePropertiesOnDemand dedups
|
|
282
|
-
// by name — an empty set from one source could
|
|
283
|
-
// populated same-
|
|
81
|
+
// by (name, globalId) identity — an empty set from one source could
|
|
82
|
+
// otherwise suppress a populated same-identity set from another
|
|
83
|
+
// (symmetric with extractQsetsFromIds).
|
|
284
84
|
if (properties.length > 0) {
|
|
285
85
|
result.push({ name: psetName, globalId: psetGlobalId, properties });
|
|
286
86
|
}
|
|
@@ -317,31 +117,26 @@ export function extractTypePropertiesOnDemand(store, entityId) {
|
|
|
317
117
|
? typeEntity.attributes[2]
|
|
318
118
|
: typeRef.type;
|
|
319
119
|
const allPsets = [];
|
|
320
|
-
const
|
|
120
|
+
const seenPsetKeys = new Set();
|
|
121
|
+
const ownPsetIds = new Set();
|
|
321
122
|
// Source 1: HasPropertySets attribute on the type entity (index 5 for IfcTypeObject subtypes)
|
|
322
123
|
// Works for both IFC2X3 and IFC4
|
|
323
124
|
if (typeEntity) {
|
|
324
125
|
const hasPropertySets = typeEntity.attributes?.[5];
|
|
325
126
|
if (Array.isArray(hasPropertySets)) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
127
|
+
for (const id of hasPropertySets)
|
|
128
|
+
if (typeof id === 'number')
|
|
129
|
+
ownPsetIds.add(id);
|
|
130
|
+
for (const pset of extractPsetsFromIds(store, extractor, [...ownPsetIds])) {
|
|
131
|
+
seenPsetKeys.add(setIdentityKey(pset));
|
|
330
132
|
allPsets.push(pset);
|
|
331
133
|
}
|
|
332
134
|
}
|
|
333
135
|
}
|
|
334
136
|
// Source 2: onDemandPropertyMap for the type entity (IFC4: via IFCRELDEFINESBYPROPERTIES)
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const psets = extractPsetsFromIds(store, extractor, typePsetIds);
|
|
339
|
-
for (const pset of psets) {
|
|
340
|
-
if (!seenPsetNames.has(pset.name)) {
|
|
341
|
-
allPsets.push(pset);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
137
|
+
const typePsetIds = store.onDemandPropertyMap?.get(typeId);
|
|
138
|
+
if (typePsetIds && typePsetIds.length > 0) {
|
|
139
|
+
appendSetsFromSecondSource(allPsets, ownPsetIds, seenPsetKeys, typePsetIds, (ids) => extractPsetsFromIds(store, extractor, ids));
|
|
345
140
|
}
|
|
346
141
|
if (allPsets.length === 0)
|
|
347
142
|
return null;
|
|
@@ -365,28 +160,23 @@ export function extractTypeEntityOwnProperties(store, typeEntityId) {
|
|
|
365
160
|
if (!typeEntity)
|
|
366
161
|
return [];
|
|
367
162
|
const allPsets = [];
|
|
368
|
-
const
|
|
163
|
+
const seenPsetKeys = new Set();
|
|
164
|
+
const ownPsetIds = new Set();
|
|
369
165
|
// Source 1: HasPropertySets attribute (index 5 for IfcTypeObject subtypes)
|
|
370
166
|
const hasPropertySets = typeEntity.attributes?.[5];
|
|
371
167
|
if (Array.isArray(hasPropertySets)) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
168
|
+
for (const id of hasPropertySets)
|
|
169
|
+
if (typeof id === 'number')
|
|
170
|
+
ownPsetIds.add(id);
|
|
171
|
+
for (const pset of extractPsetsFromIds(store, extractor, [...ownPsetIds])) {
|
|
172
|
+
seenPsetKeys.add(setIdentityKey(pset));
|
|
376
173
|
allPsets.push(pset);
|
|
377
174
|
}
|
|
378
175
|
}
|
|
379
176
|
// Source 2: onDemandPropertyMap (IFC4: via IFCRELDEFINESBYPROPERTIES)
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const psets = extractPsetsFromIds(store, extractor, typePsetIds);
|
|
384
|
-
for (const pset of psets) {
|
|
385
|
-
if (!seenPsetNames.has(pset.name)) {
|
|
386
|
-
allPsets.push(pset);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
177
|
+
const typePsetIds = store.onDemandPropertyMap?.get(typeEntityId);
|
|
178
|
+
if (typePsetIds && typePsetIds.length > 0) {
|
|
179
|
+
appendSetsFromSecondSource(allPsets, ownPsetIds, seenPsetKeys, typePsetIds, (ids) => extractPsetsFromIds(store, extractor, ids));
|
|
390
180
|
}
|
|
391
181
|
return allPsets;
|
|
392
182
|
}
|
|
@@ -408,40 +198,14 @@ export function extractQsetsFromIds(store, extractor, qsetIds) {
|
|
|
408
198
|
// Only extract IFCELEMENTQUANTITY entities (skip property sets etc.)
|
|
409
199
|
if (qsetRef.type.toUpperCase() !== 'IFCELEMENTQUANTITY')
|
|
410
200
|
continue;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
for (const qtyRef of hasQuantities) {
|
|
420
|
-
if (typeof qtyRef !== 'number')
|
|
421
|
-
continue;
|
|
422
|
-
const qtyEntityRef = store.entityIndex.byId.get(qtyRef);
|
|
423
|
-
if (!qtyEntityRef)
|
|
424
|
-
continue;
|
|
425
|
-
const qtyEntity = extractor.extractEntity(qtyEntityRef);
|
|
426
|
-
if (!qtyEntity)
|
|
427
|
-
continue;
|
|
428
|
-
const qtyAttrs = qtyEntity.attributes || [];
|
|
429
|
-
const qtyName = typeof qtyAttrs[0] === 'string' ? qtyAttrs[0] : '';
|
|
430
|
-
if (!qtyName)
|
|
431
|
-
continue;
|
|
432
|
-
const qtyType = QUANTITY_TYPE_MAP[qtyEntity.type.toUpperCase()] ?? QuantityType.Count;
|
|
433
|
-
// Value is at index 3 for the simple IfcQuantity* subtypes.
|
|
434
|
-
const value = typeof qtyAttrs[3] === 'number' ? qtyAttrs[3] : 0;
|
|
435
|
-
quantities.push({ name: qtyName, type: qtyType, value });
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
// Only surface sets that actually carry quantities. An empty set would
|
|
439
|
-
// add nothing to a schedule, and (because `extractTypeQuantitiesOnDemand`
|
|
440
|
-
// dedups by name) an empty set from one source could otherwise suppress a
|
|
441
|
-
// populated same-named set from another.
|
|
442
|
-
if (quantities.length > 0) {
|
|
443
|
-
result.push({ name: qsetName, quantities });
|
|
444
|
-
}
|
|
201
|
+
// A set that walks to zero quantities is dropped — see
|
|
202
|
+
// {@link readQuantitySet}. Here that also stops an empty set from one
|
|
203
|
+
// source suppressing a populated same-identity set from another, since
|
|
204
|
+
// `extractTypeQuantitiesOnDemand` still dedups NAMED sets by
|
|
205
|
+
// `(name, globalId)` identity (see {@link appendSetsFromSecondSource}).
|
|
206
|
+
const qset = readQuantitySet(store, extractor, qsetRef);
|
|
207
|
+
if (qset)
|
|
208
|
+
result.push(qset);
|
|
445
209
|
}
|
|
446
210
|
return result;
|
|
447
211
|
}
|
|
@@ -474,28 +238,26 @@ export function extractTypeQuantitiesOnDemand(store, entityId) {
|
|
|
474
238
|
? typeEntity.attributes[2]
|
|
475
239
|
: typeRef.type;
|
|
476
240
|
const allQsets = [];
|
|
477
|
-
const
|
|
241
|
+
const seenQsetKeys = new Set();
|
|
242
|
+
const ownSetIds = new Set();
|
|
478
243
|
// Source 1: HasPropertySets attribute on the type (index 5) — quantity sets
|
|
479
244
|
// live alongside property sets in this IfcPropertySetDefinition list.
|
|
480
245
|
if (typeEntity) {
|
|
481
246
|
const hasPropertySets = typeEntity.attributes?.[5];
|
|
482
247
|
if (Array.isArray(hasPropertySets)) {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
248
|
+
for (const id of hasPropertySets)
|
|
249
|
+
if (typeof id === 'number')
|
|
250
|
+
ownSetIds.add(id);
|
|
251
|
+
for (const qset of extractQsetsFromIds(store, extractor, [...ownSetIds])) {
|
|
252
|
+
seenQsetKeys.add(setIdentityKey(qset));
|
|
486
253
|
allQsets.push(qset);
|
|
487
254
|
}
|
|
488
255
|
}
|
|
489
256
|
}
|
|
490
257
|
// Source 2: onDemandQuantityMap for the type entity (IFC4 IfcRelDefinesByProperties).
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
for (const qset of extractQsetsFromIds(store, extractor, typeQsetIds)) {
|
|
495
|
-
if (!seenQsetNames.has(qset.name))
|
|
496
|
-
allQsets.push(qset);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
258
|
+
const typeQsetIds = store.onDemandQuantityMap?.get(typeId);
|
|
259
|
+
if (typeQsetIds && typeQsetIds.length > 0) {
|
|
260
|
+
appendSetsFromSecondSource(allQsets, ownSetIds, seenQsetKeys, typeQsetIds, (ids) => extractQsetsFromIds(store, extractor, ids));
|
|
499
261
|
}
|
|
500
262
|
if (allQsets.length === 0)
|
|
501
263
|
return null;
|
|
@@ -705,146 +467,11 @@ export function extractGroupMembersOnDemand(store, groupId) {
|
|
|
705
467
|
// ============================================================================
|
|
706
468
|
// On-Demand Georeferencing Extraction
|
|
707
469
|
// ============================================================================
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
* that carry CRS in ePSet_MapConversion / ePSet_ProjectedCRS) the underlying
|
|
714
|
-
* scan decodes EVERY IfcPropertySet from the source buffer to match by name —
|
|
715
|
-
* tens of thousands of decodes on property-heavy models. The viewer calls this
|
|
716
|
-
* on the load/render path (ViewportContainer's Cesium-availability check), which
|
|
717
|
-
* re-runs on every streamed geometry batch, so without caching the cost is
|
|
718
|
-
* O(batches x propertySets) and can turn a multi-second load into minutes.
|
|
719
|
-
* Caching collapses it to a single scan per store. Safe because the result is a
|
|
720
|
-
* pure function of the immutable source + entityIndex; georef *edits* are layered
|
|
721
|
-
* on top later in getEffectiveGeoreference(), not here.
|
|
722
|
-
*/
|
|
723
|
-
/**
|
|
724
|
-
* Memoize an O(entities) on-demand extraction per store. On-demand extractors
|
|
725
|
-
* derive purely from the immutable source + entityIndex, but the viewer calls
|
|
726
|
-
* them on render/stream hot paths where they can re-run once per geometry batch
|
|
727
|
-
* (regression #1404). Caching by store collapses that to one scan per model.
|
|
728
|
-
* Use this for any new `extract*OnDemand` so the whole family stays O(1)-per-call
|
|
729
|
-
* regardless of how often the render layer invokes it.
|
|
730
|
-
*/
|
|
731
|
-
const onDemandCaches = new WeakMap();
|
|
732
|
-
function oncePerStore(store, key, compute) {
|
|
733
|
-
let byKey = onDemandCaches.get(store);
|
|
734
|
-
if (!byKey) {
|
|
735
|
-
byKey = new Map();
|
|
736
|
-
onDemandCaches.set(store, byKey);
|
|
737
|
-
}
|
|
738
|
-
if (byKey.has(key))
|
|
739
|
-
return byKey.get(key);
|
|
740
|
-
const value = compute();
|
|
741
|
-
byKey.set(key, value);
|
|
742
|
-
return value;
|
|
743
|
-
}
|
|
744
|
-
export function extractGeoreferencingOnDemand(store) {
|
|
745
|
-
// Don't cache a not-yet-loaded store — it may gain source/entityIndex later.
|
|
746
|
-
if (!store.source?.length || !store.entityIndex)
|
|
747
|
-
return null;
|
|
748
|
-
return oncePerStore(store, 'georef', () => computeGeoreferencingOnDemand(store));
|
|
749
|
-
}
|
|
750
|
-
function computeGeoreferencingOnDemand(store) {
|
|
751
|
-
if (!store.source?.length || !store.entityIndex)
|
|
752
|
-
return null;
|
|
753
|
-
const extractor = new EntityExtractor(store.source);
|
|
754
|
-
const { byId, byType } = store.entityIndex;
|
|
755
|
-
// Build a lightweight entity map for just the georef-related types
|
|
756
|
-
const entityMap = new Map();
|
|
757
|
-
const typeMap = new Map();
|
|
758
|
-
for (const typeName of ['IFCMAPCONVERSION', 'IFCPROJECTEDCRS', 'IFCSITE']) {
|
|
759
|
-
const ids = byType.get(typeName);
|
|
760
|
-
if (!ids?.length)
|
|
761
|
-
continue;
|
|
762
|
-
// Use mixed-case for the georef extractor's type lookup
|
|
763
|
-
const displayName = typeName === 'IFCMAPCONVERSION'
|
|
764
|
-
? 'IfcMapConversion'
|
|
765
|
-
: typeName === 'IFCPROJECTEDCRS'
|
|
766
|
-
? 'IfcProjectedCRS'
|
|
767
|
-
: 'IfcSite';
|
|
768
|
-
typeMap.set(displayName, ids);
|
|
769
|
-
for (const id of ids) {
|
|
770
|
-
const ref = byId.get(id);
|
|
771
|
-
if (!ref)
|
|
772
|
-
continue;
|
|
773
|
-
const entity = extractor.extractEntity(ref);
|
|
774
|
-
if (entity) {
|
|
775
|
-
entityMap.set(id, entity);
|
|
776
|
-
// For IfcProjectedCRS, also resolve the MapUnit reference (attribute [6])
|
|
777
|
-
// so the georef extractor can determine the actual unit scale
|
|
778
|
-
if (typeName === 'IFCPROJECTEDCRS' && entity.attributes) {
|
|
779
|
-
const mapUnitAttr = entity.attributes[6];
|
|
780
|
-
const mapUnitRefId = typeof mapUnitAttr === 'number' ? mapUnitAttr : null;
|
|
781
|
-
if (mapUnitRefId && !entityMap.has(mapUnitRefId)) {
|
|
782
|
-
const unitRef = byId.get(mapUnitRefId);
|
|
783
|
-
if (unitRef) {
|
|
784
|
-
const unitEntity = extractor.extractEntity(unitRef);
|
|
785
|
-
if (unitEntity)
|
|
786
|
-
entityMap.set(mapUnitRefId, unitEntity);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
// IFC2x3 fallback: models without IfcMapConversion store georeferencing in
|
|
794
|
-
// ePSet_MapConversion / ePSet_ProjectedCRS property sets. Those aren't
|
|
795
|
-
// loaded above, so the ePSet path in extractGeorefFromEntities had nothing
|
|
796
|
-
// to read and the model fell back to the legacy IfcSite EPSG:4326 (wrong
|
|
797
|
-
// CRS). Only scan property sets when no IfcMapConversion exists, and only
|
|
798
|
-
// pull in the georef ePSets + their values — not every pset in the model.
|
|
799
|
-
if (!typeMap.has('IfcMapConversion')) {
|
|
800
|
-
const psetIds = byType.get('IFCPROPERTYSET');
|
|
801
|
-
if (psetIds?.length) {
|
|
802
|
-
const georefPsetIds = [];
|
|
803
|
-
const childIds = new Set();
|
|
804
|
-
for (const id of psetIds) {
|
|
805
|
-
const ref = byId.get(id);
|
|
806
|
-
if (!ref)
|
|
807
|
-
continue;
|
|
808
|
-
const entity = extractor.extractEntity(ref);
|
|
809
|
-
if (!entity?.attributes)
|
|
810
|
-
continue;
|
|
811
|
-
// IfcPropertySet: Name (2), HasProperties (4)
|
|
812
|
-
const name = typeof entity.attributes[2] === 'string'
|
|
813
|
-
? entity.attributes[2].toLowerCase()
|
|
814
|
-
: '';
|
|
815
|
-
if (name !== 'epset_mapconversion' && name !== 'epset_projectedcrs')
|
|
816
|
-
continue;
|
|
817
|
-
entityMap.set(id, entity);
|
|
818
|
-
georefPsetIds.push(id);
|
|
819
|
-
const props = entity.attributes[4];
|
|
820
|
-
if (Array.isArray(props)) {
|
|
821
|
-
for (const propRef of props) {
|
|
822
|
-
const propId = typeof propRef === 'number' ? propRef : null;
|
|
823
|
-
if (propId === null || childIds.has(propId))
|
|
824
|
-
continue;
|
|
825
|
-
// Property atoms may be deferred on huge files (not in
|
|
826
|
-
// the primary byId index) — fall back like refFromStore.
|
|
827
|
-
const childRef = byId.get(propId) ?? store.deferredEntityIndex?.get(propId);
|
|
828
|
-
if (!childRef)
|
|
829
|
-
continue;
|
|
830
|
-
const child = extractor.extractEntity(childRef);
|
|
831
|
-
if (child) {
|
|
832
|
-
entityMap.set(propId, child);
|
|
833
|
-
childIds.add(propId);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
if (georefPsetIds.length) {
|
|
839
|
-
typeMap.set('IfcPropertySet', georefPsetIds);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
if (entityMap.size === 0)
|
|
844
|
-
return null;
|
|
845
|
-
// Cast to IfcEntity (they share the same shape)
|
|
846
|
-
return extractGeorefFromEntities(entityMap, typeMap);
|
|
847
|
-
}
|
|
470
|
+
// The per-store memo now lives in ./on-demand-cache.ts (import it there for
|
|
471
|
+
// any new extract*OnDemand) and the georeferencing extractor in
|
|
472
|
+
// ./on-demand-georeferencing.ts, re-exported here so every existing import
|
|
473
|
+
// of this module keeps resolving.
|
|
474
|
+
export { extractGeoreferencingOnDemand } from './on-demand-georeferencing.js';
|
|
848
475
|
const materialPropertyIndexCache = new WeakMap();
|
|
849
476
|
/** Resolve an entity ref from the primary index, falling back to deferred atoms. */
|
|
850
477
|
function refFromStore(store, id) {
|
|
@@ -926,7 +553,7 @@ function getMaterialPropertyIndex(store) {
|
|
|
926
553
|
const propName = typeof propAttrs[0] === 'string' ? propAttrs[0] : '';
|
|
927
554
|
if (!propName)
|
|
928
555
|
continue;
|
|
929
|
-
const pv =
|
|
556
|
+
const pv = parsePropertyValueWithComplex(store, extractor, propEntity);
|
|
930
557
|
const entry = {
|
|
931
558
|
name: propName,
|
|
932
559
|
type: pv.type,
|