@ifc-lite/parser 9.2.0 → 9.4.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 +31 -0
- package/dist/abort-error.d.ts +7 -0
- package/dist/abort-error.d.ts.map +1 -0
- package/dist/abort-error.js +17 -0
- package/dist/abort-error.js.map +1 -0
- package/dist/classification-overlay.d.ts +13 -0
- package/dist/classification-overlay.d.ts.map +1 -0
- package/dist/classification-overlay.js +65 -0
- package/dist/classification-overlay.js.map +1 -0
- package/dist/classification-resolver.d.ts +13 -9
- package/dist/classification-resolver.d.ts.map +1 -1
- package/dist/classification-resolver.js +109 -74
- package/dist/classification-resolver.js.map +1 -1
- package/dist/columnar-parser-attributes.d.ts +1 -1
- package/dist/columnar-parser-attributes.d.ts.map +1 -1
- package/dist/columnar-parser-attributes.js +16 -6
- package/dist/columnar-parser-attributes.js.map +1 -1
- package/dist/columnar-parser-root-attributes.d.ts +15 -1
- package/dist/columnar-parser-root-attributes.d.ts.map +1 -1
- package/dist/columnar-parser-root-attributes.js +26 -11
- package/dist/columnar-parser-root-attributes.js.map +1 -1
- package/dist/columnar-parser.d.ts +9 -8
- package/dist/columnar-parser.d.ts.map +1 -1
- package/dist/columnar-parser.js +11 -23
- package/dist/columnar-parser.js.map +1 -1
- package/dist/current-occurrence-quantities.d.ts +13 -0
- package/dist/current-occurrence-quantities.d.ts.map +1 -0
- package/dist/current-occurrence-quantities.js +69 -0
- package/dist/current-occurrence-quantities.js.map +1 -0
- package/dist/current-project-units.d.ts +11 -0
- package/dist/current-project-units.d.ts.map +1 -0
- package/dist/current-project-units.js +41 -0
- package/dist/current-project-units.js.map +1 -0
- package/dist/current-type-quantities.d.ts +13 -0
- package/dist/current-type-quantities.d.ts.map +1 -0
- package/dist/current-type-quantities.js +274 -0
- package/dist/current-type-quantities.js.map +1 -0
- package/dist/data-store-transport.d.ts +1 -0
- package/dist/data-store-transport.d.ts.map +1 -1
- package/dist/data-store-transport.js +2 -0
- package/dist/data-store-transport.js.map +1 -1
- package/dist/document-resolver.d.ts +23 -0
- package/dist/document-resolver.d.ts.map +1 -0
- package/dist/document-resolver.js +122 -0
- package/dist/document-resolver.js.map +1 -0
- package/dist/effective-entity-record.d.ts +3 -2
- package/dist/effective-entity-record.d.ts.map +1 -1
- package/dist/effective-entity-record.js +5 -5
- package/dist/effective-entity-record.js.map +1 -1
- package/dist/effective-metadata-record.d.ts +23 -0
- package/dist/effective-metadata-record.d.ts.map +1 -0
- package/dist/effective-metadata-record.js +50 -0
- package/dist/effective-metadata-record.js.map +1 -0
- package/dist/entity-scanner.d.ts +6 -8
- package/dist/entity-scanner.d.ts.map +1 -1
- package/dist/entity-scanner.js +26 -13
- package/dist/entity-scanner.js.map +1 -1
- package/dist/ifc-schema.d.ts +2 -1
- package/dist/ifc-schema.d.ts.map +1 -1
- package/dist/ifc-schema.js +4 -3
- package/dist/ifc-schema.js.map +1 -1
- package/dist/index.d.ts +8 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -10
- package/dist/index.js.map +1 -1
- package/dist/length-unit-source.d.ts +25 -0
- package/dist/length-unit-source.d.ts.map +1 -0
- package/dist/length-unit-source.js +14 -0
- package/dist/length-unit-source.js.map +1 -0
- package/dist/material-definitions.d.ts +10 -0
- package/dist/material-definitions.d.ts.map +1 -0
- package/dist/material-definitions.js +224 -0
- package/dist/material-definitions.js.map +1 -0
- package/dist/material-overlay.d.ts +38 -0
- package/dist/material-overlay.d.ts.map +1 -0
- package/dist/material-overlay.js +146 -0
- package/dist/material-overlay.js.map +1 -0
- package/dist/material-properties-extractor.d.ts +2 -11
- package/dist/material-properties-extractor.d.ts.map +1 -1
- package/dist/material-properties-extractor.js +21 -38
- package/dist/material-properties-extractor.js.map +1 -1
- package/dist/material-resolver.d.ts +6 -4
- package/dist/material-resolver.d.ts.map +1 -1
- package/dist/material-resolver.js +176 -386
- package/dist/material-resolver.js.map +1 -1
- package/dist/measure-unit-scale.d.ts +2 -0
- package/dist/measure-unit-scale.d.ts.map +1 -1
- package/dist/measure-unit-scale.js +7 -1
- package/dist/measure-unit-scale.js.map +1 -1
- package/dist/metadata-edit-value.d.ts +4 -0
- package/dist/metadata-edit-value.d.ts.map +1 -0
- package/dist/metadata-edit-value.js +57 -0
- package/dist/metadata-edit-value.js.map +1 -0
- package/dist/native-metadata-public.d.ts +9 -0
- package/dist/native-metadata-public.d.ts.map +1 -0
- package/dist/native-metadata-public.js +12 -0
- package/dist/native-metadata-public.js.map +1 -0
- package/dist/occurrence-quantity-read-error.d.ts +7 -0
- package/dist/occurrence-quantity-read-error.d.ts.map +1 -0
- package/dist/occurrence-quantity-read-error.js +13 -0
- package/dist/occurrence-quantity-read-error.js.map +1 -0
- package/dist/on-demand-extractors.d.ts +3 -38
- package/dist/on-demand-extractors.d.ts.map +1 -1
- package/dist/on-demand-extractors.js +4 -184
- package/dist/on-demand-extractors.js.map +1 -1
- package/dist/parser-worker-trace.d.ts +18 -0
- package/dist/parser-worker-trace.d.ts.map +1 -0
- package/dist/parser-worker-trace.js +32 -0
- package/dist/parser-worker-trace.js.map +1 -0
- package/dist/parser.worker.d.ts.map +1 -1
- package/dist/parser.worker.js +34 -13
- package/dist/parser.worker.js.map +1 -1
- package/dist/project-units.d.ts +17 -4
- package/dist/project-units.d.ts.map +1 -1
- package/dist/project-units.js +104 -37
- package/dist/project-units.js.map +1 -1
- package/dist/property-complex-value.d.ts.map +1 -1
- package/dist/property-complex-value.js +1 -0
- package/dist/property-complex-value.js.map +1 -1
- package/dist/property-unit.d.ts +2 -0
- package/dist/property-unit.d.ts.map +1 -1
- package/dist/property-unit.js +4 -1
- package/dist/property-unit.js.map +1 -1
- package/dist/property-value-parser.d.ts +14 -1
- package/dist/property-value-parser.d.ts.map +1 -1
- package/dist/property-value-parser.js +42 -22
- package/dist/property-value-parser.js.map +1 -1
- package/dist/quantity-collect.d.ts +14 -2
- package/dist/quantity-collect.d.ts.map +1 -1
- package/dist/quantity-collect.js +19 -8
- package/dist/quantity-collect.js.map +1 -1
- package/dist/relationship-schema-slots.d.ts.map +1 -1
- package/dist/relationship-schema-slots.js +2 -1
- package/dist/relationship-schema-slots.js.map +1 -1
- package/dist/scan-worker-inline.d.ts.map +1 -1
- package/dist/scan-worker-inline.js +2 -1
- package/dist/scan-worker-inline.js.map +1 -1
- package/dist/schedule-extractor.d.ts +2 -1
- package/dist/schedule-extractor.d.ts.map +1 -1
- package/dist/schedule-extractor.js +11 -2
- package/dist/schedule-extractor.js.map +1 -1
- package/dist/schedule-serializer-builders.d.ts +7 -0
- package/dist/schedule-serializer-builders.d.ts.map +1 -1
- package/dist/schedule-serializer-builders.js +17 -17
- package/dist/schedule-serializer-builders.js.map +1 -1
- package/dist/schedule-serializer.d.ts +8 -3
- package/dist/schedule-serializer.d.ts.map +1 -1
- package/dist/schedule-serializer.js +36 -1
- package/dist/schedule-serializer.js.map +1 -1
- package/dist/schedule-task-products.d.ts +38 -0
- package/dist/schedule-task-products.d.ts.map +1 -0
- package/dist/schedule-task-products.js +69 -0
- package/dist/schedule-task-products.js.map +1 -0
- package/dist/schedule-types.d.ts +11 -1
- package/dist/schedule-types.d.ts.map +1 -1
- package/dist/schedule-types.js.map +1 -1
- package/dist/schema-entity-name.d.ts +14 -0
- package/dist/schema-entity-name.d.ts.map +1 -0
- package/dist/schema-entity-name.js +34 -0
- package/dist/schema-entity-name.js.map +1 -0
- package/dist/schema-exports.d.ts +7 -0
- package/dist/schema-exports.d.ts.map +1 -0
- package/dist/schema-exports.js +10 -0
- package/dist/schema-exports.js.map +1 -0
- package/dist/source-project-length-unit.d.ts +53 -0
- package/dist/source-project-length-unit.d.ts.map +1 -0
- package/dist/source-project-length-unit.js +201 -0
- package/dist/source-project-length-unit.js.map +1 -0
- package/dist/structural-extractor.js +2 -2
- package/dist/structural-extractor.js.map +1 -1
- package/dist/structural-load-extractor.d.ts +9 -3
- package/dist/structural-load-extractor.d.ts.map +1 -1
- package/dist/structural-load-extractor.js +26 -20
- package/dist/structural-load-extractor.js.map +1 -1
- package/dist/structural-step-values.d.ts +0 -9
- package/dist/structural-step-values.d.ts.map +1 -1
- package/dist/structural-step-values.js +13 -1
- package/dist/structural-step-values.js.map +1 -1
- package/dist/type-own-quantity-source.d.ts +37 -0
- package/dist/type-own-quantity-source.d.ts.map +1 -0
- package/dist/type-own-quantity-source.js +204 -0
- package/dist/type-own-quantity-source.js.map +1 -0
- package/dist/unit-extractor.d.ts +13 -0
- package/dist/unit-extractor.d.ts.map +1 -1
- package/dist/unit-extractor.js +32 -29
- package/dist/unit-extractor.js.map +1 -1
- package/dist/worker-parser.d.ts +7 -0
- package/dist/worker-parser.d.ts.map +1 -1
- package/dist/worker-parser.js +14 -16
- package/dist/worker-parser.js.map +1 -1
- package/package.json +6 -5
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* Includes cycle detection for recursive material references.
|
|
8
8
|
*/
|
|
9
9
|
import { EntityExtractor } from './entity-extractor.js';
|
|
10
|
+
import { getReference, getReferences } from './attribute-helpers.js';
|
|
11
|
+
import { materialRecordReader, materialAssignmentState } from './material-overlay.js';
|
|
12
|
+
import { resolveMaterial } from './material-definitions.js';
|
|
10
13
|
import { RelationshipType } from '@ifc-lite/data';
|
|
11
14
|
import { isIfcTypeLikeEntity } from './columnar-parser-indexes.js';
|
|
12
|
-
import {
|
|
13
|
-
import { resolveAllMaterialDefIds, resolveMaterialOwnerAndDefIds, resolveOwnMaterialDefIds } from './material-associations.js';
|
|
15
|
+
import { resolveAllMaterialDefIds, resolveOwnMaterialDefIds } from './material-associations.js';
|
|
14
16
|
export { resolveAllMaterialDefIds } from './material-associations.js';
|
|
15
17
|
/**
|
|
16
18
|
* Extract EVERY material association for an entity ON-DEMAND, resolved to
|
|
@@ -19,25 +21,29 @@ export { resolveAllMaterialDefIds } from './material-associations.js';
|
|
|
19
21
|
* fallback IfcMaterial yield several. Order matches
|
|
20
22
|
* {@link resolveAllMaterialDefIds}. Consumers that need a single value use
|
|
21
23
|
* {@link extractMaterialsOnDemand} (=== element 0 here).
|
|
24
|
+
* An optional native view applies current assignments, aliases and material edits.
|
|
22
25
|
*/
|
|
23
|
-
export function extractAllMaterialsOnDemand(store, entityId) {
|
|
24
|
-
const { ownerId, defIds } =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
export function extractAllMaterialsOnDemand(store, entityId, view) {
|
|
27
|
+
const { ownerId, defIds, complete } = materialAssignmentState(store, entityId, view);
|
|
28
|
+
const read = materialRecordReader(store, view);
|
|
29
|
+
return defIds.map((id) => {
|
|
30
|
+
const record = read(id);
|
|
31
|
+
if (!store.source?.length) {
|
|
32
|
+
const edits = view?.getEffectiveChanges?.().some(edit => {
|
|
33
|
+
if (!['attribute', 'type', 'entity-deleted'].includes(edit.kind))
|
|
34
|
+
return false;
|
|
35
|
+
// @raw-entity-enumeration-ok Immutable source kind identifies a current effective edit; material membership and values come from the effective reader.
|
|
36
|
+
const sourceType = store.entityIndex.byId.get(edit.entityId)?.type ?? store.deferredEntityIndex?.get(edit.entityId)?.type;
|
|
37
|
+
return edit.entityId === id || Boolean(sourceType?.toUpperCase().startsWith('IFCMATERIAL'));
|
|
38
|
+
});
|
|
39
|
+
const forwarded = complete && !edits ? store.resolvedMaterials?.get(ownerId)?.get(id) : undefined;
|
|
40
|
+
if (forwarded)
|
|
41
|
+
return forwarded;
|
|
42
|
+
if (!record || record.type.toUpperCase() !== 'IFCMATERIAL')
|
|
43
|
+
return { type: 'Material', unresolved: true };
|
|
44
|
+
}
|
|
45
|
+
return resolveMaterial(store, read, id, new Set(), view?.resolveBaseEntityId?.(entityId) ?? entityId) ?? { type: 'Material', unresolved: true };
|
|
46
|
+
});
|
|
41
47
|
}
|
|
42
48
|
/**
|
|
43
49
|
* Extract materials for a single entity ON-DEMAND.
|
|
@@ -48,226 +54,9 @@ export function extractAllMaterialsOnDemand(store, entityId) {
|
|
|
48
54
|
* Returns the entity's PRIMARY material (lowest-rel-express-id association);
|
|
49
55
|
* use {@link extractAllMaterialsOnDemand} when every association matters.
|
|
50
56
|
*/
|
|
51
|
-
export function extractMaterialsOnDemand(store, entityId) {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
return null;
|
|
55
|
-
if (!store.source?.length) {
|
|
56
|
-
const info = extractAllMaterialsOnDemand(store, entityId)[0];
|
|
57
|
-
return info?.unresolved ? null : info ?? null;
|
|
58
|
-
}
|
|
59
|
-
const extractor = new EntityExtractor(store.source);
|
|
60
|
-
return resolveMaterial(store, extractor, materialId, new Set(), entityId);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Resolve a material entity by ID, handling all IFC material types. `visited`
|
|
64
|
-
* guards cyclic *Usage references; `originEntityId` (the calling element/type)
|
|
65
|
-
* lets a layer's thickness resolve its own project's unit scale below.
|
|
66
|
-
*/
|
|
67
|
-
function resolveMaterial(store, extractor, materialId, visited = new Set(), originEntityId) {
|
|
68
|
-
if (visited.has(materialId))
|
|
69
|
-
return null;
|
|
70
|
-
visited.add(materialId);
|
|
71
|
-
// @raw-entity-enumeration-ok decode the selected material definition's one source STEP record
|
|
72
|
-
const ref = store.entityIndex.byId.get(materialId);
|
|
73
|
-
if (!ref)
|
|
74
|
-
return null;
|
|
75
|
-
const entity = extractor.extractEntity(ref);
|
|
76
|
-
if (!entity)
|
|
77
|
-
return null;
|
|
78
|
-
const typeUpper = entity.type.toUpperCase();
|
|
79
|
-
const attrs = entity.attributes || [];
|
|
80
|
-
switch (typeUpper) {
|
|
81
|
-
case 'IFCMATERIAL': {
|
|
82
|
-
// IfcMaterial: [Name, Description, Category]
|
|
83
|
-
return {
|
|
84
|
-
type: 'Material',
|
|
85
|
-
name: typeof attrs[0] === 'string' ? attrs[0] : undefined,
|
|
86
|
-
description: typeof attrs[1] === 'string' ? attrs[1] : undefined,
|
|
87
|
-
category: typeof attrs[2] === 'string' ? attrs[2] : undefined,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
case 'IFCMATERIALLAYERSET': {
|
|
91
|
-
// IfcMaterialLayerSet: [MaterialLayers, LayerSetName, Description]
|
|
92
|
-
const layerIds = Array.isArray(attrs[0]) ? attrs[0].filter((id) => typeof id === 'number') : [];
|
|
93
|
-
const layers = [];
|
|
94
|
-
for (const layerId of layerIds) {
|
|
95
|
-
// @raw-entity-enumeration-ok decode this material set's referenced layer record
|
|
96
|
-
const layerRef = store.entityIndex.byId.get(layerId);
|
|
97
|
-
if (!layerRef)
|
|
98
|
-
continue;
|
|
99
|
-
const layerEntity = extractor.extractEntity(layerRef);
|
|
100
|
-
if (!layerEntity)
|
|
101
|
-
continue;
|
|
102
|
-
const la = layerEntity.attributes || [];
|
|
103
|
-
// IfcMaterialLayer: [Material, LayerThickness, IsVentilated, Name, Description, Category, Priority]
|
|
104
|
-
const matId = typeof la[0] === 'number' ? la[0] : undefined;
|
|
105
|
-
let materialName;
|
|
106
|
-
let materialCategory;
|
|
107
|
-
if (matId) {
|
|
108
|
-
// @raw-entity-enumeration-ok decode the layer's referenced material record
|
|
109
|
-
const matRef = store.entityIndex.byId.get(matId);
|
|
110
|
-
if (matRef) {
|
|
111
|
-
const matEntity = extractor.extractEntity(matRef);
|
|
112
|
-
if (matEntity) {
|
|
113
|
-
materialName = typeof matEntity.attributes?.[0] === 'string' ? matEntity.attributes[0] : undefined;
|
|
114
|
-
materialCategory = typeof matEntity.attributes?.[2] === 'string' ? matEntity.attributes[2] : undefined;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
// Convert raw IFC value to metres (a 60 mm slab must not read
|
|
119
|
-
// "60.0 m"). `store.lengthUnitScale` answers for the file's
|
|
120
|
-
// FIRST IfcProject only, wrong for a MergedExporter federated
|
|
121
|
-
// layer in a LATER project — resolve per `originEntityId`.
|
|
122
|
-
const rawThickness = typeof la[1] === 'number' ? la[1] : undefined;
|
|
123
|
-
const scale = originEntityId !== undefined ? resolveEntityLengthUnitScale(store.source, store.entityIndex, store.relationships, originEntityId) : (store.lengthUnitScale ?? 1);
|
|
124
|
-
const thickness = rawThickness !== undefined ? rawThickness * scale : undefined;
|
|
125
|
-
layers.push({
|
|
126
|
-
materialName,
|
|
127
|
-
thickness,
|
|
128
|
-
isVentilated: la[2] === true || la[2] === '.T.',
|
|
129
|
-
name: typeof la[3] === 'string' ? la[3] : undefined,
|
|
130
|
-
category: typeof la[5] === 'string' ? la[5] : undefined,
|
|
131
|
-
materialCategory,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
type: 'MaterialLayerSet',
|
|
136
|
-
name: typeof attrs[1] === 'string' ? attrs[1] : undefined,
|
|
137
|
-
description: typeof attrs[2] === 'string' ? attrs[2] : undefined,
|
|
138
|
-
layers,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
case 'IFCMATERIALPROFILESET': {
|
|
142
|
-
// IfcMaterialProfileSet: [Name, Description, MaterialProfiles, CompositeProfile]
|
|
143
|
-
const profileIds = Array.isArray(attrs[2]) ? attrs[2].filter((id) => typeof id === 'number') : [];
|
|
144
|
-
const profiles = [];
|
|
145
|
-
for (const profId of profileIds) {
|
|
146
|
-
// @raw-entity-enumeration-ok decode this profile set's referenced profile record
|
|
147
|
-
const profRef = store.entityIndex.byId.get(profId);
|
|
148
|
-
if (!profRef)
|
|
149
|
-
continue;
|
|
150
|
-
const profEntity = extractor.extractEntity(profRef);
|
|
151
|
-
if (!profEntity)
|
|
152
|
-
continue;
|
|
153
|
-
const pa = profEntity.attributes || [];
|
|
154
|
-
// IfcMaterialProfile: [Name, Description, Material, Profile, Priority, Category]
|
|
155
|
-
const matId = typeof pa[2] === 'number' ? pa[2] : undefined;
|
|
156
|
-
let materialName;
|
|
157
|
-
let materialCategory;
|
|
158
|
-
if (matId) {
|
|
159
|
-
// @raw-entity-enumeration-ok decode the profile's referenced material record
|
|
160
|
-
const matRef = store.entityIndex.byId.get(matId);
|
|
161
|
-
if (matRef) {
|
|
162
|
-
const matEntity = extractor.extractEntity(matRef);
|
|
163
|
-
if (matEntity) {
|
|
164
|
-
materialName = typeof matEntity.attributes?.[0] === 'string' ? matEntity.attributes[0] : undefined;
|
|
165
|
-
materialCategory = typeof matEntity.attributes?.[2] === 'string' ? matEntity.attributes[2] : undefined;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
profiles.push({
|
|
170
|
-
materialName,
|
|
171
|
-
name: typeof pa[0] === 'string' ? pa[0] : undefined,
|
|
172
|
-
category: typeof pa[5] === 'string' ? pa[5] : undefined,
|
|
173
|
-
materialCategory,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
type: 'MaterialProfileSet',
|
|
178
|
-
name: typeof attrs[0] === 'string' ? attrs[0] : undefined,
|
|
179
|
-
description: typeof attrs[1] === 'string' ? attrs[1] : undefined,
|
|
180
|
-
profiles,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
case 'IFCMATERIALCONSTITUENTSET': {
|
|
184
|
-
// IfcMaterialConstituentSet: [Name, Description, MaterialConstituents]
|
|
185
|
-
const constituentIds = Array.isArray(attrs[2]) ? attrs[2].filter((id) => typeof id === 'number') : [];
|
|
186
|
-
const constituents = [];
|
|
187
|
-
for (const constId of constituentIds) {
|
|
188
|
-
// @raw-entity-enumeration-ok decode this constituent set's referenced constituent record
|
|
189
|
-
const constRef = store.entityIndex.byId.get(constId);
|
|
190
|
-
if (!constRef)
|
|
191
|
-
continue;
|
|
192
|
-
const constEntity = extractor.extractEntity(constRef);
|
|
193
|
-
if (!constEntity)
|
|
194
|
-
continue;
|
|
195
|
-
const ca = constEntity.attributes || [];
|
|
196
|
-
// IfcMaterialConstituent: [Name, Description, Material, Fraction, Category]
|
|
197
|
-
const matId = typeof ca[2] === 'number' ? ca[2] : undefined;
|
|
198
|
-
let materialName;
|
|
199
|
-
let materialCategory;
|
|
200
|
-
if (matId) {
|
|
201
|
-
// @raw-entity-enumeration-ok decode the constituent's referenced material record
|
|
202
|
-
const matRef = store.entityIndex.byId.get(matId);
|
|
203
|
-
if (matRef) {
|
|
204
|
-
const matEntity = extractor.extractEntity(matRef);
|
|
205
|
-
if (matEntity) {
|
|
206
|
-
materialName = typeof matEntity.attributes?.[0] === 'string' ? matEntity.attributes[0] : undefined;
|
|
207
|
-
// IfcMaterial: [Name, Description, Category] — IDS material
|
|
208
|
-
// checks consider both the constituent's own category AND
|
|
209
|
-
// the underlying IfcMaterial.Category as candidates for
|
|
210
|
-
// a value match.
|
|
211
|
-
materialCategory = typeof matEntity.attributes?.[2] === 'string' ? matEntity.attributes[2] : undefined;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
constituents.push({
|
|
216
|
-
materialName,
|
|
217
|
-
name: typeof ca[0] === 'string' ? ca[0] : undefined,
|
|
218
|
-
fraction: typeof ca[3] === 'number' ? ca[3] : undefined,
|
|
219
|
-
category: typeof ca[4] === 'string' ? ca[4] : undefined,
|
|
220
|
-
materialCategory,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
return {
|
|
224
|
-
type: 'MaterialConstituentSet',
|
|
225
|
-
name: typeof attrs[0] === 'string' ? attrs[0] : undefined,
|
|
226
|
-
description: typeof attrs[1] === 'string' ? attrs[1] : undefined,
|
|
227
|
-
constituents,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
case 'IFCMATERIALLIST': {
|
|
231
|
-
// IfcMaterialList: [Materials]
|
|
232
|
-
const matIds = Array.isArray(attrs[0]) ? attrs[0].filter((id) => typeof id === 'number') : [];
|
|
233
|
-
const materials = [];
|
|
234
|
-
for (const matId of matIds) {
|
|
235
|
-
// @raw-entity-enumeration-ok decode this material list's referenced material record
|
|
236
|
-
const matRef = store.entityIndex.byId.get(matId);
|
|
237
|
-
if (!matRef)
|
|
238
|
-
continue;
|
|
239
|
-
const matEntity = extractor.extractEntity(matRef);
|
|
240
|
-
if (matEntity) {
|
|
241
|
-
const name = typeof matEntity.attributes?.[0] === 'string'
|
|
242
|
-
? matEntity.attributes[0] : `Material #${matId}`;
|
|
243
|
-
const category = typeof matEntity.attributes?.[2] === 'string' ? matEntity.attributes[2] : undefined;
|
|
244
|
-
materials.push({ name, ...(category ? { category } : {}) });
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return {
|
|
248
|
-
type: 'MaterialList',
|
|
249
|
-
materials,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
case 'IFCMATERIALLAYERSETUSAGE': {
|
|
253
|
-
// IfcMaterialLayerSetUsage: [ForLayerSet, LayerSetDirection, DirectionSense, OffsetFromReferenceLine, ...]
|
|
254
|
-
const layerSetId = typeof attrs[0] === 'number' ? attrs[0] : undefined;
|
|
255
|
-
if (layerSetId) {
|
|
256
|
-
return resolveMaterial(store, extractor, layerSetId, visited, originEntityId);
|
|
257
|
-
}
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
case 'IFCMATERIALPROFILESETUSAGE': {
|
|
261
|
-
// IfcMaterialProfileSetUsage: [ForProfileSet, ...]
|
|
262
|
-
const profileSetId = typeof attrs[0] === 'number' ? attrs[0] : undefined;
|
|
263
|
-
if (profileSetId) {
|
|
264
|
-
return resolveMaterial(store, extractor, profileSetId, visited, originEntityId);
|
|
265
|
-
}
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
default:
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
57
|
+
export function extractMaterialsOnDemand(store, entityId, view) {
|
|
58
|
+
const first = extractAllMaterialsOnDemand(store, entityId, view)[0];
|
|
59
|
+
return first?.unresolved ? null : first ?? null;
|
|
271
60
|
}
|
|
272
61
|
/** Resolve an entity ref from the primary index, falling back to deferred atoms. */
|
|
273
62
|
function getRef(store, id) {
|
|
@@ -275,11 +64,8 @@ function getRef(store, id) {
|
|
|
275
64
|
return store.entityIndex.byId.get(id) ?? store.deferredEntityIndex?.get(id);
|
|
276
65
|
}
|
|
277
66
|
/** Read an IfcMaterial's Name (attr 0) and Category (attr 2). */
|
|
278
|
-
function readMaterialNameCategory(
|
|
279
|
-
const
|
|
280
|
-
if (!ref)
|
|
281
|
-
return {};
|
|
282
|
-
const entity = extractor.extractEntity(ref);
|
|
67
|
+
function readMaterialNameCategory(read, materialId) {
|
|
68
|
+
const entity = read(materialId);
|
|
283
69
|
const attrs = entity?.attributes ?? [];
|
|
284
70
|
return {
|
|
285
71
|
name: typeof attrs[0] === 'string' ? attrs[0] : undefined,
|
|
@@ -303,7 +89,9 @@ const leavesCache = new WeakMap();
|
|
|
303
89
|
* leaf with weight 1. Results are memoised per (store, defId) — a type-shared
|
|
304
90
|
* layer set is resolved once for the whole model.
|
|
305
91
|
*/
|
|
306
|
-
export function collectMaterialLeaves(store, defId) {
|
|
92
|
+
export function collectMaterialLeaves(store, defId, view) {
|
|
93
|
+
if (view)
|
|
94
|
+
return resolveLeaves(materialRecordReader(store, view), defId, new Set());
|
|
307
95
|
let cache = leavesCache.get(store);
|
|
308
96
|
if (!cache) {
|
|
309
97
|
cache = new Map();
|
|
@@ -312,10 +100,9 @@ export function collectMaterialLeaves(store, defId) {
|
|
|
312
100
|
const cached = cache.get(defId);
|
|
313
101
|
if (cached)
|
|
314
102
|
return cached;
|
|
315
|
-
const extractor = store.source?.length ? new EntityExtractor(store.source) : null;
|
|
316
103
|
let result;
|
|
317
|
-
if (
|
|
318
|
-
result = resolveLeaves(store,
|
|
104
|
+
if (store.source?.length) {
|
|
105
|
+
result = resolveLeaves(materialRecordReader(store), defId, new Set());
|
|
319
106
|
}
|
|
320
107
|
else {
|
|
321
108
|
// Source-less store (server-loaded models keep `source` as an EMPTY
|
|
@@ -351,147 +138,145 @@ function mergeLeaves(into, leaf) {
|
|
|
351
138
|
}
|
|
352
139
|
/** Recursively resolve a material definition into weighted base-material leaves
|
|
353
140
|
* (cycle-guarded via `visited`). See {@link collectMaterialLeaves}. */
|
|
354
|
-
function resolveLeaves(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const { name, category } = readMaterialNameCategory(store, extractor, matId);
|
|
371
|
-
mergeLeaves(merged, { id: matId, name, category, weight });
|
|
372
|
-
};
|
|
373
|
-
switch (typeUpper) {
|
|
374
|
-
case 'IFCMATERIAL':
|
|
375
|
-
return [{
|
|
376
|
-
id: defId,
|
|
377
|
-
name: typeof attrs[0] === 'string' ? attrs[0] : undefined,
|
|
378
|
-
category: typeof attrs[2] === 'string' ? attrs[2] : undefined,
|
|
379
|
-
weight: 1,
|
|
380
|
-
}];
|
|
381
|
-
case 'IFCMATERIALLAYERSET': {
|
|
382
|
-
// IfcMaterialLayerSet: [MaterialLayers, LayerSetName, Description]
|
|
383
|
-
const layerIds = Array.isArray(attrs[0]) ? attrs[0].filter((id) => typeof id === 'number') : [];
|
|
384
|
-
const layers = [];
|
|
385
|
-
for (const layerId of layerIds) {
|
|
386
|
-
const layerRef = getRef(store, layerId);
|
|
387
|
-
if (!layerRef)
|
|
388
|
-
continue;
|
|
389
|
-
const la = extractor.extractEntity(layerRef)?.attributes ?? [];
|
|
390
|
-
// IfcMaterialLayer: [Material, LayerThickness, IsVentilated, Name, Description, Category, Priority]
|
|
391
|
-
layers.push({
|
|
392
|
-
matId: typeof la[0] === 'number' ? la[0] : undefined,
|
|
393
|
-
// Non-finite thickness would turn every weight into NaN
|
|
394
|
-
// (Infinity/Infinity) - treat it as absent like <= 0.
|
|
395
|
-
thickness: typeof la[1] === 'number' && Number.isFinite(la[1]) && la[1] > 0 ? la[1] : 0,
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
const totalThickness = layers.reduce((s, l) => s + l.thickness, 0);
|
|
399
|
-
for (const layer of layers) {
|
|
400
|
-
const weight = totalThickness > 0
|
|
401
|
-
? layer.thickness / totalThickness
|
|
402
|
-
: (layers.length > 0 ? 1 / layers.length : 0);
|
|
403
|
-
addMaterialLeaf(layer.matId, weight);
|
|
404
|
-
}
|
|
405
|
-
return [...merged.values()];
|
|
141
|
+
function resolveLeaves(read, defId, visited) {
|
|
142
|
+
while (true) {
|
|
143
|
+
if (visited.has(defId))
|
|
144
|
+
return [];
|
|
145
|
+
visited.add(defId);
|
|
146
|
+
const entity = read(defId);
|
|
147
|
+
if (!entity)
|
|
148
|
+
return [];
|
|
149
|
+
const typeUpper = entity.type.toUpperCase();
|
|
150
|
+
const attrs = entity.attributes || [];
|
|
151
|
+
if (typeUpper === 'IFCMATERIALLAYERSETUSAGE' || typeUpper === 'IFCMATERIALPROFILESETUSAGE') {
|
|
152
|
+
const next = getReference(attrs[0]);
|
|
153
|
+
if (next === undefined)
|
|
154
|
+
return [];
|
|
155
|
+
defId = next;
|
|
156
|
+
continue;
|
|
406
157
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
158
|
+
const merged = new Map();
|
|
159
|
+
const addMaterialLeaf = (matId, weight) => {
|
|
160
|
+
if (matId === undefined)
|
|
161
|
+
return;
|
|
162
|
+
const { name, category } = readMaterialNameCategory(read, matId);
|
|
163
|
+
mergeLeaves(merged, { id: matId, name, category, weight });
|
|
164
|
+
};
|
|
165
|
+
switch (typeUpper) {
|
|
166
|
+
case 'IFCMATERIAL':
|
|
167
|
+
return [{
|
|
168
|
+
id: defId,
|
|
169
|
+
name: typeof attrs[0] === 'string' ? attrs[0] : undefined,
|
|
170
|
+
category: typeof attrs[2] === 'string' ? attrs[2] : undefined,
|
|
171
|
+
weight: 1,
|
|
172
|
+
}];
|
|
173
|
+
case 'IFCMATERIALLAYERSET': {
|
|
174
|
+
// IfcMaterialLayerSet: [MaterialLayers, LayerSetName, Description]
|
|
175
|
+
const layerIds = getReferences(attrs[0]) ?? [];
|
|
176
|
+
const layers = [];
|
|
177
|
+
for (const layerId of layerIds) {
|
|
178
|
+
const layerRef = read(layerId);
|
|
179
|
+
if (!layerRef)
|
|
180
|
+
continue;
|
|
181
|
+
const la = layerRef.attributes ?? [];
|
|
182
|
+
// IfcMaterialLayer: [Material, LayerThickness, IsVentilated, Name, Description, Category, Priority]
|
|
183
|
+
layers.push({
|
|
184
|
+
matId: getReference(la[0]),
|
|
185
|
+
// Non-finite thickness would turn every weight into NaN
|
|
186
|
+
// (Infinity/Infinity) - treat it as absent like <= 0.
|
|
187
|
+
thickness: typeof la[1] === 'number' && Number.isFinite(la[1]) && la[1] > 0 ? la[1] : 0,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const totalThickness = layers.reduce((s, l) => s + l.thickness, 0);
|
|
191
|
+
for (const layer of layers) {
|
|
192
|
+
const weight = totalThickness > 0
|
|
193
|
+
? layer.thickness / totalThickness
|
|
194
|
+
: (layers.length > 0 ? 1 / layers.length : 0);
|
|
195
|
+
addMaterialLeaf(layer.matId, weight);
|
|
196
|
+
}
|
|
197
|
+
return [...merged.values()];
|
|
418
198
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
// "contributes nothing"), distinct from an ABSENT fraction.
|
|
433
|
-
constituents.push({
|
|
434
|
-
matId: typeof ca[2] === 'number' ? ca[2] : undefined,
|
|
435
|
-
// Non-finite (NaN/Infinity) and negative fractions are
|
|
436
|
-
// malformed for an IfcNormalisedRatioMeasure — treat them
|
|
437
|
-
// as unset so they can't poison the weight arithmetic
|
|
438
|
-
// below. An authored 0.0 is legal and PRESERVED: it is an
|
|
439
|
-
// explicit "contributes nothing", distinct from absent.
|
|
440
|
-
fraction: typeof ca[3] === 'number' && Number.isFinite(ca[3]) && ca[3] >= 0 ? ca[3] : undefined,
|
|
441
|
-
});
|
|
199
|
+
case 'IFCMATERIALPROFILESET': {
|
|
200
|
+
// IfcMaterialProfileSet: [Name, Description, MaterialProfiles, CompositeProfile]
|
|
201
|
+
const profileIds = getReferences(attrs[2]) ?? [];
|
|
202
|
+
const n = profileIds.length;
|
|
203
|
+
for (const profId of profileIds) {
|
|
204
|
+
const profRef = read(profId);
|
|
205
|
+
if (!profRef)
|
|
206
|
+
continue;
|
|
207
|
+
const pa = profRef.attributes ?? [];
|
|
208
|
+
// IfcMaterialProfile: [Name, Description, Material, Profile, Priority, Category]
|
|
209
|
+
addMaterialLeaf(getReference(pa[2]), n > 0 ? 1 / n : 0);
|
|
210
|
+
}
|
|
211
|
+
return [...merged.values()];
|
|
442
212
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
|
|
213
|
+
case 'IFCMATERIALCONSTITUENTSET': {
|
|
214
|
+
// IfcMaterialConstituentSet: [Name, Description, MaterialConstituents]
|
|
215
|
+
const constIds = getReferences(attrs[2]) ?? [];
|
|
216
|
+
const constituents = [];
|
|
217
|
+
for (const constId of constIds) {
|
|
218
|
+
const constRef = read(constId);
|
|
219
|
+
if (!constRef)
|
|
220
|
+
continue;
|
|
221
|
+
const ca = constRef.attributes ?? [];
|
|
222
|
+
// IfcMaterialConstituent: [Name, Description, Material, Fraction, Category]
|
|
223
|
+
// An authored Fraction of 0 is preserved as 0 (an explicit
|
|
224
|
+
// "contributes nothing"), distinct from an ABSENT fraction.
|
|
225
|
+
constituents.push({
|
|
226
|
+
matId: getReference(ca[2]),
|
|
227
|
+
// Non-finite (NaN/Infinity) and negative fractions are
|
|
228
|
+
// malformed for an IfcNormalisedRatioMeasure — treat them
|
|
229
|
+
// as unset so they can't poison the weight arithmetic
|
|
230
|
+
// below. An authored 0.0 is legal and PRESERVED: it is an
|
|
231
|
+
// explicit "contributes nothing", distinct from absent.
|
|
232
|
+
fraction: typeof ca[3] === 'number' && Number.isFinite(ca[3]) && ca[3] >= 0 ? ca[3] : undefined,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
// Constituent Fraction is optional per-constituent. Constituents
|
|
236
|
+
// WITHOUT an explicit fraction share whatever remains of the whole
|
|
237
|
+
// (1 - sum of explicit) evenly, so they must not collapse to
|
|
238
|
+
// weight 0 and vanish from the totals. When the explicit fractions
|
|
239
|
+
// already fill (or overflow) the whole, each implicit sibling gets
|
|
240
|
+
// an even 1/n share instead so it still registers. The set is then
|
|
241
|
+
// renormalised to sum to exactly 1 (e.g. {1.0, unset} would
|
|
242
|
+
// otherwise weigh 1.5x the element), so the totals panel can never
|
|
243
|
+
// over-report an element's quantities. Explicit zeros survive all
|
|
244
|
+
// branches: an all-explicit-zero set keeps every weight at 0
|
|
245
|
+
// rather than inventing equal shares.
|
|
246
|
+
const explicitTotal = constituents.reduce((s, c) => s + (c.fraction ?? 0), 0);
|
|
247
|
+
const implicitCount = constituents.reduce((n, c) => n + (c.fraction === undefined ? 1 : 0), 0);
|
|
248
|
+
const remaining = 1 - explicitTotal;
|
|
249
|
+
const perImplicit = implicitCount > 0
|
|
250
|
+
? (remaining > 0 ? remaining / implicitCount : 1 / constituents.length)
|
|
251
|
+
: 0;
|
|
252
|
+
const provisional = constituents.map((c) => c.fraction ?? perImplicit);
|
|
253
|
+
const totalWeight = provisional.reduce((s, w) => s + w, 0);
|
|
254
|
+
for (let i = 0; i < constituents.length; i++) {
|
|
255
|
+
// totalWeight can only be 0 when EVERY fraction is an authored
|
|
256
|
+
// 0.0 (absent fractions receive a positive share) — keep those
|
|
257
|
+
// explicit zeros instead of inventing equal weights.
|
|
258
|
+
const weight = totalWeight > 0 ? provisional[i] / totalWeight : 0;
|
|
259
|
+
addMaterialLeaf(constituents[i].matId, weight);
|
|
260
|
+
}
|
|
261
|
+
return [...merged.values()];
|
|
468
262
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
return profileSetId ? resolveLeaves(store, extractor, profileSetId, visited) : [];
|
|
263
|
+
case 'IFCMATERIALLIST': {
|
|
264
|
+
// IfcMaterialList: [Materials]
|
|
265
|
+
const matIds = getReferences(attrs[0]) ?? [];
|
|
266
|
+
const n = matIds.length;
|
|
267
|
+
for (const matId of matIds)
|
|
268
|
+
addMaterialLeaf(matId, n > 0 ? 1 / n : 0);
|
|
269
|
+
return [...merged.values()];
|
|
270
|
+
}
|
|
271
|
+
default:
|
|
272
|
+
// Unknown definition — treat as a single opaque material so it still
|
|
273
|
+
// appears in the tab and carries the element's full volume.
|
|
274
|
+
return [{
|
|
275
|
+
id: defId,
|
|
276
|
+
name: typeof attrs[0] === 'string' ? attrs[0] : undefined,
|
|
277
|
+
weight: 1,
|
|
278
|
+
}];
|
|
486
279
|
}
|
|
487
|
-
default:
|
|
488
|
-
// Unknown definition — treat as a single opaque material so it still
|
|
489
|
-
// appears in the tab and carries the element's full volume.
|
|
490
|
-
return [{
|
|
491
|
-
id: defId,
|
|
492
|
-
name: typeof attrs[0] === 'string' ? attrs[0] : undefined,
|
|
493
|
-
weight: 1,
|
|
494
|
-
}];
|
|
495
280
|
}
|
|
496
281
|
}
|
|
497
282
|
const usageIndexCache = new WeakMap();
|
|
@@ -648,7 +433,12 @@ export function buildMaterialUsageIndex(store) {
|
|
|
648
433
|
const displayCache = new WeakMap();
|
|
649
434
|
/** Resolve a material entity's display name + IFC class for headers/labels.
|
|
650
435
|
* Memoised per (store, materialId) — callers resolve many materials in a loop. */
|
|
651
|
-
export function getMaterialDisplay(store, materialId) {
|
|
436
|
+
export function getMaterialDisplay(store, materialId, view) {
|
|
437
|
+
if (view) {
|
|
438
|
+
const record = materialRecordReader(store, view)(materialId);
|
|
439
|
+
return { name: typeof record?.attributes[0] === 'string' ? record.attributes[0] : `Material #${materialId}`,
|
|
440
|
+
type: record?.type ?? 'IfcMaterial' };
|
|
441
|
+
}
|
|
652
442
|
let cache = displayCache.get(store);
|
|
653
443
|
if (!cache) {
|
|
654
444
|
cache = new Map();
|