brepjs-bim 0.23.0 → 0.23.2
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 +8 -0
- package/dist/brepjs-bim.cjs +525 -128
- package/dist/brepjs-bim.js +517 -120
- package/dist/import/fromIfc.d.ts +7 -1
- package/dist/import/geometryRead.d.ts +13 -1
- package/dist/import/importedModel.d.ts +17 -8
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/brepjs-bim.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, cut, err, extrude, fuse, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, translate, validSolid } from "brepjs";
|
|
1
|
+
import { addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, cut, err, extrude, fuse, getBounds, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, translate, validSolid } from "brepjs";
|
|
2
2
|
import * as WebIFC from "web-ifc";
|
|
3
3
|
import { Handle, IfcAPI } from "web-ifc";
|
|
4
4
|
//#region src/identity/ifcGuid.ts
|
|
@@ -183,7 +183,7 @@ function fromBrepError(inner, code, message) {
|
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
185
|
//#endregion
|
|
186
|
-
//#region \0@oxc-project+runtime@0.
|
|
186
|
+
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/usingCtx.js
|
|
187
187
|
function _usingCtx() {
|
|
188
188
|
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
189
189
|
var n = Error();
|
|
@@ -8756,7 +8756,7 @@ function writeWallGeometry(w, spec, geomSubContextId, parentPlacementId) {
|
|
|
8756
8756
|
value: "AREA"
|
|
8757
8757
|
},
|
|
8758
8758
|
ProfileName: null,
|
|
8759
|
-
Position: w.ref(writeAxis2Placement2D$1(w)),
|
|
8759
|
+
Position: w.ref(writeAxis2Placement2D$1(w, [thicknessM / 2, heightM / 2])),
|
|
8760
8760
|
XDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, thicknessM),
|
|
8761
8761
|
YDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, heightM)
|
|
8762
8762
|
});
|
|
@@ -8977,12 +8977,12 @@ function writeRoofGeometry(w, spec, solid, geomSubContextId, parentPlacementId)
|
|
|
8977
8977
|
usedFallback: false
|
|
8978
8978
|
};
|
|
8979
8979
|
}
|
|
8980
|
-
function writeAxis2Placement2D$1(w) {
|
|
8980
|
+
function writeAxis2Placement2D$1(w, location = [0, 0]) {
|
|
8981
8981
|
const originId = w.nextId();
|
|
8982
8982
|
w.writeLine({
|
|
8983
8983
|
expressID: originId,
|
|
8984
8984
|
type: WebIFC.IFCCARTESIANPOINT,
|
|
8985
|
-
Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, 0), w.mkType(WebIFC.IFCLENGTHMEASURE,
|
|
8985
|
+
Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, location[0]), w.mkType(WebIFC.IFCLENGTHMEASURE, location[1])]
|
|
8986
8986
|
});
|
|
8987
8987
|
const id = w.nextId();
|
|
8988
8988
|
w.writeLine({
|
|
@@ -11487,11 +11487,10 @@ function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, g
|
|
|
11487
11487
|
const offsetAlongWallM = toIfcLengthM(openingSpec.offsetAlongWall);
|
|
11488
11488
|
const offsetFromFloorM = toIfcLengthM(openingSpec.offsetFromFloor);
|
|
11489
11489
|
const thicknessM = toIfcLengthM(wallSpec.thickness);
|
|
11490
|
-
const wallHeightM = toIfcLengthM(wallSpec.height);
|
|
11491
11490
|
const placement3DId = writeAxis2Placement3D(w, [
|
|
11492
11491
|
offsetAlongWallM + widthM / 2,
|
|
11493
|
-
thicknessM
|
|
11494
|
-
|
|
11492
|
+
thicknessM,
|
|
11493
|
+
offsetFromFloorM + heightM / 2
|
|
11495
11494
|
], [
|
|
11496
11495
|
0,
|
|
11497
11496
|
-1,
|
|
@@ -12367,6 +12366,24 @@ var SUPPORTED_SCHEMAS = [
|
|
|
12367
12366
|
* MUST call {@link SpfReader.close} (it always issues `CloseModel`, preventing
|
|
12368
12367
|
* WASM handle leaks). The handle is `using`-friendly via `Symbol.dispose`.
|
|
12369
12368
|
*/
|
|
12369
|
+
var IFC_Z_UP_TRANSFORMATION = [
|
|
12370
|
+
1,
|
|
12371
|
+
0,
|
|
12372
|
+
0,
|
|
12373
|
+
0,
|
|
12374
|
+
0,
|
|
12375
|
+
0,
|
|
12376
|
+
1,
|
|
12377
|
+
0,
|
|
12378
|
+
0,
|
|
12379
|
+
-1,
|
|
12380
|
+
0,
|
|
12381
|
+
0,
|
|
12382
|
+
0,
|
|
12383
|
+
0,
|
|
12384
|
+
0,
|
|
12385
|
+
1
|
|
12386
|
+
];
|
|
12370
12387
|
function deleteVector(vec) {
|
|
12371
12388
|
vec.delete?.();
|
|
12372
12389
|
}
|
|
@@ -12399,6 +12416,7 @@ var SpfReader = class SpfReader {
|
|
|
12399
12416
|
if (modelId >= 0) api.CloseModel(modelId);
|
|
12400
12417
|
return err(importError("OPEN_MODEL_FAILED", "web-ifc OpenModel returned an invalid model id"));
|
|
12401
12418
|
}
|
|
12419
|
+
api.SetGeometryTransformation(modelId, IFC_Z_UP_TRANSFORMATION);
|
|
12402
12420
|
const schemaRaw = api.GetModelSchema(modelId);
|
|
12403
12421
|
if (!SUPPORTED_SCHEMAS.includes(schemaRaw)) {
|
|
12404
12422
|
api.CloseModel(modelId);
|
|
@@ -14047,6 +14065,7 @@ function buildElementContainmentMap(reader) {
|
|
|
14047
14065
|
//#endregion
|
|
14048
14066
|
//#region src/import/geometryRead.ts
|
|
14049
14067
|
var NONE = { kind: "NONE" };
|
|
14068
|
+
var testHooks$1 = null;
|
|
14050
14069
|
/**
|
|
14051
14070
|
* Reconstructs the `Body` (SweptSolid) representation of a product into a brepjs
|
|
14052
14071
|
* solid, falling back to tessellated mesh import when no parametric path exists.
|
|
@@ -14057,27 +14076,66 @@ var NONE = { kind: "NONE" };
|
|
|
14057
14076
|
* single bad element never aborts a model import.
|
|
14058
14077
|
*/
|
|
14059
14078
|
function readBodyGeometry(reader, productExpressId, scale, diagnostics) {
|
|
14079
|
+
const body = readBodyItems(reader, productExpressId, scale, diagnostics);
|
|
14080
|
+
const selected = body.items.find((item) => item.kind !== "NONE") ?? NONE;
|
|
14081
|
+
for (const item of body.items) if (item !== selected && item.kind === "SOLID") item.solid[Symbol.dispose]();
|
|
14082
|
+
return selected;
|
|
14083
|
+
}
|
|
14084
|
+
/** Reconstructs each supported IFC Body item independently in representation order. */
|
|
14085
|
+
function readBodyItems(reader, productExpressId, scale, diagnostics) {
|
|
14060
14086
|
const product = reader.getLine(productExpressId);
|
|
14061
|
-
if (product === null) return
|
|
14087
|
+
if (product === null) return {
|
|
14088
|
+
hasBody: false,
|
|
14089
|
+
itemCount: 0,
|
|
14090
|
+
items: []
|
|
14091
|
+
};
|
|
14062
14092
|
const representationRef = asRef(product["Representation"]);
|
|
14063
|
-
if (representationRef === void 0) return
|
|
14093
|
+
if (representationRef === void 0) return {
|
|
14094
|
+
hasBody: false,
|
|
14095
|
+
itemCount: 0,
|
|
14096
|
+
items: []
|
|
14097
|
+
};
|
|
14064
14098
|
const productShape = reader.getLine(representationRef.value);
|
|
14065
14099
|
const representations = productShape === null ? void 0 : asRefArray(productShape["Representations"]);
|
|
14066
|
-
if (representations === void 0) return
|
|
14067
|
-
|
|
14068
|
-
|
|
14100
|
+
if (representations === void 0) return {
|
|
14101
|
+
hasBody: false,
|
|
14102
|
+
itemCount: 0,
|
|
14103
|
+
items: []
|
|
14104
|
+
};
|
|
14105
|
+
const bodyItemIds = findBodyItems(reader, representations);
|
|
14106
|
+
if (bodyItemIds === null) return {
|
|
14107
|
+
hasBody: false,
|
|
14108
|
+
itemCount: 0,
|
|
14109
|
+
items: []
|
|
14110
|
+
};
|
|
14069
14111
|
const worldTransform = readWorldTransform(reader, product, scale);
|
|
14070
|
-
const
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
} catch (
|
|
14076
|
-
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", `Body
|
|
14077
|
-
return NONE;
|
|
14112
|
+
const itemTypes = new Map(bodyItemIds.map((id) => [id, reader.getLineType(id)]));
|
|
14113
|
+
const streamedItemIds = new Set(bodyItemIds.filter((id) => isStreamedTessellatedType(itemTypes.get(id))));
|
|
14114
|
+
let streamedMeshes = /* @__PURE__ */ new Map();
|
|
14115
|
+
if (streamedItemIds.size > 0) try {
|
|
14116
|
+
streamedMeshes = collectItemMeshes(reader, productExpressId, streamedItemIds);
|
|
14117
|
+
} catch (cause) {
|
|
14118
|
+
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", `Body tessellation stream failed: ${errMsg$1(cause)}`, productExpressId));
|
|
14078
14119
|
}
|
|
14079
|
-
|
|
14080
|
-
|
|
14120
|
+
const items = bodyItemIds.map((bodyItemId) => {
|
|
14121
|
+
const itemType = itemTypes.get(bodyItemId);
|
|
14122
|
+
try {
|
|
14123
|
+
if (itemType === WebIFC.IFCEXTRUDEDAREASOLID) return reconstructExtrusion(reader, bodyItemId, scale, worldTransform, diagnostics);
|
|
14124
|
+
if (itemType === WebIFC.IFCREVOLVEDAREASOLID) return reconstructRevolution(reader, bodyItemId, scale, worldTransform, diagnostics);
|
|
14125
|
+
if (itemType === WebIFC.IFCTRIANGULATEDFACESET) return reconstructTriangulatedItem(reader, bodyItemId, scale, worldTransform, diagnostics);
|
|
14126
|
+
if (isStreamedTessellatedType(itemType)) return reconstructStreamedTessellatedItem(streamedMeshes.get(bodyItemId), bodyItemId, diagnostics);
|
|
14127
|
+
} catch (cause) {
|
|
14128
|
+
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", `Body item ${bodyItemId} reconstruction threw: ${errMsg$1(cause)}`, bodyItemId, { productExpressId }));
|
|
14129
|
+
return NONE;
|
|
14130
|
+
}
|
|
14131
|
+
diagnostics.push(issue("warning", "UNSUPPORTED_REPRESENTATION_ITEM", `Unsupported Body item ${bodyItemId} type ${itemType}; geometry skipped`, bodyItemId, { productExpressId }));
|
|
14132
|
+
return NONE;
|
|
14133
|
+
});
|
|
14134
|
+
return {
|
|
14135
|
+
hasBody: true,
|
|
14136
|
+
itemCount: bodyItemIds.length,
|
|
14137
|
+
items
|
|
14138
|
+
};
|
|
14081
14139
|
}
|
|
14082
14140
|
function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagnostics) {
|
|
14083
14141
|
const ext = reader.getLine(extrusionId);
|
|
@@ -14098,12 +14156,14 @@ function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagno
|
|
|
14098
14156
|
diagnostics.push(issue("warning", faceResult.error.code, faceResult.error.message, extrusionId));
|
|
14099
14157
|
return NONE;
|
|
14100
14158
|
}
|
|
14159
|
+
const profileFrame = readProfilePosition(reader, sweptArea.value, scale);
|
|
14101
14160
|
const depthMm = depth * scale * 1e3;
|
|
14102
|
-
const
|
|
14161
|
+
const sweptDir = readDirection(reader, ext["ExtrudedDirection"]) ?? [
|
|
14103
14162
|
0,
|
|
14104
14163
|
0,
|
|
14105
14164
|
1
|
|
14106
14165
|
];
|
|
14166
|
+
const extrudeDir = profileFrame === null ? sweptDir : rotateByTranspose(profileFrame.linear, sweptDir);
|
|
14107
14167
|
const extrudeVec = [
|
|
14108
14168
|
extrudeDir[0] * depthMm,
|
|
14109
14169
|
extrudeDir[1] * depthMm,
|
|
@@ -14125,7 +14185,11 @@ function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagno
|
|
|
14125
14185
|
return NONE;
|
|
14126
14186
|
}
|
|
14127
14187
|
const localFrame = readAxis2Placement3D(reader, ext["Position"], scale);
|
|
14128
|
-
const placed = placeSolid(solidResult.value,
|
|
14188
|
+
const placed = placeSolid(solidResult.value, [
|
|
14189
|
+
worldTransform,
|
|
14190
|
+
localFrame,
|
|
14191
|
+
profileFrame
|
|
14192
|
+
]);
|
|
14129
14193
|
if (!placed.ok) {
|
|
14130
14194
|
diagnostics.push(issue("warning", placed.error.code, placed.error.message, extrusionId));
|
|
14131
14195
|
return NONE;
|
|
@@ -14153,16 +14217,19 @@ function reconstructRevolution(reader, revolutionId, scale, worldTransform, diag
|
|
|
14153
14217
|
return NONE;
|
|
14154
14218
|
}
|
|
14155
14219
|
const axis1 = reader.getLine(axisRef.value);
|
|
14156
|
-
const
|
|
14220
|
+
const sweptCenter = (axis1 === null ? void 0 : readPoint(reader, axis1["Location"], scale)) ?? [
|
|
14157
14221
|
0,
|
|
14158
14222
|
0,
|
|
14159
14223
|
0
|
|
14160
14224
|
];
|
|
14161
|
-
const
|
|
14225
|
+
const sweptDirection = (axis1 === null ? void 0 : readDirection(reader, axis1["Axis"])) ?? [
|
|
14162
14226
|
0,
|
|
14163
14227
|
0,
|
|
14164
14228
|
1
|
|
14165
14229
|
];
|
|
14230
|
+
const profileFrame = readProfilePosition(reader, sweptArea.value, scale);
|
|
14231
|
+
const center = profileFrame === null ? sweptCenter : inverseRigidPoint(profileFrame, sweptCenter);
|
|
14232
|
+
const direction = profileFrame === null ? sweptDirection : rotateByTranspose(profileFrame.linear, sweptDirection);
|
|
14166
14233
|
const revolved = (() => {
|
|
14167
14234
|
try {
|
|
14168
14235
|
var _usingCtx3 = _usingCtx();
|
|
@@ -14189,40 +14256,72 @@ function reconstructRevolution(reader, revolutionId, scale, worldTransform, diag
|
|
|
14189
14256
|
return NONE;
|
|
14190
14257
|
}
|
|
14191
14258
|
const localFrame = readAxis2Placement3D(reader, rev["Position"], scale);
|
|
14192
|
-
const placed = placeSolid(revolved.value,
|
|
14259
|
+
const placed = placeSolid(revolved.value, [
|
|
14260
|
+
worldTransform,
|
|
14261
|
+
localFrame,
|
|
14262
|
+
profileFrame
|
|
14263
|
+
]);
|
|
14193
14264
|
if (!placed.ok) {
|
|
14194
14265
|
diagnostics.push(issue("warning", placed.error.code, placed.error.message, revolutionId));
|
|
14195
14266
|
return NONE;
|
|
14196
14267
|
}
|
|
14197
14268
|
return finalizeSolid(placed.value, revolutionId, diagnostics);
|
|
14198
14269
|
}
|
|
14199
|
-
function
|
|
14200
|
-
const
|
|
14270
|
+
function reconstructTriangulatedItem(reader, itemExpressId, scale, worldTransform, diagnostics) {
|
|
14271
|
+
const faceSet = reader.getLine(itemExpressId);
|
|
14272
|
+
const coordinatesRef = faceSet === null ? void 0 : asRef(faceSet["Coordinates"]);
|
|
14273
|
+
const rawPoints = (coordinatesRef === void 0 ? null : reader.getLine(coordinatesRef.value))?.["CoordList"];
|
|
14274
|
+
const rawTriangles = faceSet?.["CoordIndex"];
|
|
14275
|
+
return reconstructTessellatedMesh(readTriangulatedMesh(rawPoints, rawTriangles, scale, worldTransform), 1, itemExpressId, diagnostics);
|
|
14276
|
+
}
|
|
14277
|
+
function reconstructStreamedTessellatedItem(mesh, itemExpressId, diagnostics) {
|
|
14278
|
+
return reconstructTessellatedMesh(mesh ?? null, 1e3, itemExpressId, diagnostics);
|
|
14279
|
+
}
|
|
14280
|
+
function reconstructTessellatedMesh(mesh, scaleToMm, itemExpressId, diagnostics) {
|
|
14201
14281
|
if (mesh === null || mesh.indices.length === 0) {
|
|
14202
|
-
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", "Tessellated geometry yielded no triangles",
|
|
14282
|
+
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", "Tessellated geometry yielded no triangles", itemExpressId));
|
|
14203
14283
|
return NONE;
|
|
14204
14284
|
}
|
|
14205
|
-
const stl = packBinaryStl(mesh.vertices, mesh.indices,
|
|
14206
|
-
let solid =
|
|
14285
|
+
const stl = packBinaryStl(mesh.vertices, mesh.indices, scaleToMm);
|
|
14286
|
+
let solid = sewMeshToSolid(mesh, scaleToMm, itemExpressId, diagnostics);
|
|
14207
14287
|
try {
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14288
|
+
if (solid === null) {
|
|
14289
|
+
const cast = castFreshResult(getKernel().importSTL(new Uint8Array(stl).buffer));
|
|
14290
|
+
if (isSolid(cast)) {
|
|
14291
|
+
const valid = validSolid(cast);
|
|
14292
|
+
if (valid.ok) solid = valid.value;
|
|
14293
|
+
else cast[Symbol.dispose]();
|
|
14294
|
+
} else {
|
|
14295
|
+
const solids = getSolids(cast);
|
|
14296
|
+
if (solids.length === 1 && solids[0] !== void 0) {
|
|
14297
|
+
const copied = clone(solids[0]);
|
|
14298
|
+
if (copied.ok) {
|
|
14299
|
+
const valid = validSolid(copied.value);
|
|
14300
|
+
if (valid.ok) solid = valid.value;
|
|
14301
|
+
else copied.value[Symbol.dispose]();
|
|
14302
|
+
}
|
|
14303
|
+
}
|
|
14304
|
+
cast[Symbol.dispose]();
|
|
14305
|
+
}
|
|
14306
|
+
}
|
|
14214
14307
|
} catch (e) {
|
|
14215
|
-
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `STL round-trip failed: ${errMsg$1(e)}`,
|
|
14308
|
+
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `STL round-trip failed: ${errMsg$1(e)}`, itemExpressId));
|
|
14216
14309
|
}
|
|
14217
14310
|
if (solid !== null) {
|
|
14218
|
-
|
|
14311
|
+
try {
|
|
14312
|
+
testHooks$1?.afterItemSolid?.(itemExpressId, solid);
|
|
14313
|
+
} catch (cause) {
|
|
14314
|
+
solid[Symbol.dispose]();
|
|
14315
|
+
throw cause;
|
|
14316
|
+
}
|
|
14317
|
+
diagnostics.push(issue("info", "TESSELLATED_MANIFOLD", "Tessellated mesh recovered as a closed solid", itemExpressId));
|
|
14219
14318
|
return {
|
|
14220
14319
|
kind: "SOLID",
|
|
14221
14320
|
solid,
|
|
14222
14321
|
lossy: true
|
|
14223
14322
|
};
|
|
14224
14323
|
}
|
|
14225
|
-
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", "Tessellated mesh is not closed/manifold; returning raw triangle data (lossy)",
|
|
14324
|
+
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", "Tessellated mesh is not closed/manifold; returning raw triangle data (lossy)", itemExpressId));
|
|
14226
14325
|
return {
|
|
14227
14326
|
kind: "MESH",
|
|
14228
14327
|
vertices: mesh.vertices,
|
|
@@ -14230,43 +14329,122 @@ function reconstructTessellated(reader, productExpressId, diagnostics) {
|
|
|
14230
14329
|
diagnostic: "TESSELLATED_LOSSY"
|
|
14231
14330
|
};
|
|
14232
14331
|
}
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
|
|
14332
|
+
/** Loose enough to weld float32 vertices emitted per face, expressed in millimetres. */
|
|
14333
|
+
var MESH_SEW_TOLERANCE_MM = .001;
|
|
14334
|
+
/**
|
|
14335
|
+
* Brands a fresh kernel result and releases its pre-downcast arena slot when
|
|
14336
|
+
* the cast moved to a new one. In-place kernels share the slot.
|
|
14337
|
+
*/
|
|
14338
|
+
function castFreshResult(raw) {
|
|
14339
|
+
const cast = castShape(raw);
|
|
14340
|
+
const rawId = raw.id;
|
|
14341
|
+
const castId = cast.wrapped.id;
|
|
14342
|
+
if (!(rawId !== void 0 && castId !== void 0 ? rawId === castId : cast.wrapped === raw)) getKernel().dispose(raw);
|
|
14343
|
+
return cast;
|
|
14344
|
+
}
|
|
14345
|
+
function sewMeshToSolid(mesh, scaleToMm, itemExpressId, diagnostics) {
|
|
14346
|
+
const kernel = getKernel();
|
|
14347
|
+
const { vertices, indices } = mesh;
|
|
14348
|
+
const point = (index) => [
|
|
14349
|
+
(vertices[index * 3] ?? 0) * scaleToMm,
|
|
14350
|
+
(vertices[index * 3 + 1] ?? 0) * scaleToMm,
|
|
14351
|
+
(vertices[index * 3 + 2] ?? 0) * scaleToMm
|
|
14352
|
+
];
|
|
14353
|
+
const triangles = [];
|
|
14354
|
+
for (let index = 0; index + 2 < indices.length; index += 3) {
|
|
14355
|
+
const face = kernel.buildTriFace(point(indices[index] ?? 0), point(indices[index + 1] ?? 0), point(indices[index + 2] ?? 0));
|
|
14356
|
+
if (face !== null) triangles.push(face);
|
|
14357
|
+
}
|
|
14358
|
+
if (triangles.length === 0) return null;
|
|
14359
|
+
try {
|
|
14360
|
+
const cast = castFreshResult(kernel.sewAndSolidify(triangles, MESH_SEW_TOLERANCE_MM));
|
|
14361
|
+
const valid = isSolid(cast) ? validSolid(cast) : null;
|
|
14362
|
+
if (valid !== null && valid.ok) return valid.value;
|
|
14363
|
+
cast[Symbol.dispose]();
|
|
14364
|
+
return null;
|
|
14365
|
+
} catch (cause) {
|
|
14366
|
+
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `Mesh sewing failed: ${errMsg$1(cause)}`, itemExpressId));
|
|
14367
|
+
return null;
|
|
14368
|
+
} finally {
|
|
14369
|
+
for (const face of triangles) kernel.dispose(face);
|
|
14370
|
+
}
|
|
14371
|
+
}
|
|
14372
|
+
/** Streams a product once, then separates web-ifc geometry by Body item express ID. */
|
|
14373
|
+
function collectItemMeshes(reader, productExpressId, itemExpressIds) {
|
|
14374
|
+
const byItem = /* @__PURE__ */ new Map();
|
|
14375
|
+
const soleItemExpressId = itemExpressIds.size === 1 ? [...itemExpressIds][0] : void 0;
|
|
14237
14376
|
reader.streamMeshes([productExpressId], (flatMesh) => {
|
|
14238
14377
|
try {
|
|
14239
14378
|
const geometries = flatMesh.geometries;
|
|
14240
|
-
for (let
|
|
14241
|
-
const placed = geometries.get(
|
|
14242
|
-
const
|
|
14379
|
+
for (let geometryIndex = 0; geometryIndex < geometries.size(); geometryIndex++) {
|
|
14380
|
+
const placed = geometries.get(geometryIndex);
|
|
14381
|
+
const itemExpressId = itemExpressIds.has(placed.geometryExpressID) ? placed.geometryExpressID : soleItemExpressId;
|
|
14382
|
+
if (itemExpressId === void 0) continue;
|
|
14383
|
+
const accumulator = byItem.get(itemExpressId) ?? {
|
|
14384
|
+
vertices: [],
|
|
14385
|
+
indices: []
|
|
14386
|
+
};
|
|
14387
|
+
byItem.set(itemExpressId, accumulator);
|
|
14388
|
+
const geometry = reader.getGeometry(placed.geometryExpressID);
|
|
14243
14389
|
try {
|
|
14244
|
-
const
|
|
14245
|
-
const
|
|
14246
|
-
const
|
|
14247
|
-
const
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
const
|
|
14251
|
-
const
|
|
14252
|
-
|
|
14390
|
+
const vertices = reader.getVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize());
|
|
14391
|
+
const indices = reader.getIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize());
|
|
14392
|
+
const transform = placed.flatTransformation;
|
|
14393
|
+
const base = accumulator.vertices.length / 3;
|
|
14394
|
+
const vertexCount = vertices.length / 6;
|
|
14395
|
+
for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
|
|
14396
|
+
const x = vertices[vertexIndex * 6] ?? 0;
|
|
14397
|
+
const y = vertices[vertexIndex * 6 + 1] ?? 0;
|
|
14398
|
+
const z = vertices[vertexIndex * 6 + 2] ?? 0;
|
|
14399
|
+
accumulator.vertices.push((transform[0] ?? 1) * x + (transform[4] ?? 0) * y + (transform[8] ?? 0) * z + (transform[12] ?? 0), (transform[1] ?? 0) * x + (transform[5] ?? 1) * y + (transform[9] ?? 0) * z + (transform[13] ?? 0), (transform[2] ?? 0) * x + (transform[6] ?? 0) * y + (transform[10] ?? 1) * z + (transform[14] ?? 0));
|
|
14253
14400
|
}
|
|
14254
|
-
for (let
|
|
14255
|
-
base += vertexCount;
|
|
14401
|
+
for (let index = 0; index < indices.length; index++) accumulator.indices.push(base + (indices[index] ?? 0));
|
|
14256
14402
|
} finally {
|
|
14257
|
-
|
|
14403
|
+
geometry.delete();
|
|
14258
14404
|
}
|
|
14259
14405
|
}
|
|
14260
14406
|
} finally {
|
|
14261
|
-
flatMesh.
|
|
14407
|
+
releaseEmbind(flatMesh.geometries);
|
|
14408
|
+
releaseEmbind(flatMesh);
|
|
14262
14409
|
}
|
|
14263
14410
|
});
|
|
14264
|
-
|
|
14411
|
+
return new Map([...byItem].map(([itemExpressId, mesh]) => [itemExpressId, {
|
|
14412
|
+
vertices: new Float32Array(mesh.vertices),
|
|
14413
|
+
indices: new Uint32Array(mesh.indices)
|
|
14414
|
+
}]));
|
|
14415
|
+
}
|
|
14416
|
+
function readTriangulatedMesh(rawPoints, rawTriangles, scale, worldTransform) {
|
|
14417
|
+
if (!Array.isArray(rawPoints) || !Array.isArray(rawTriangles)) return null;
|
|
14418
|
+
const vertices = [];
|
|
14419
|
+
const indices = [];
|
|
14420
|
+
const lengthFactor = scale * 1e3;
|
|
14421
|
+
for (const rawPoint of rawPoints) {
|
|
14422
|
+
if (!Array.isArray(rawPoint) || rawPoint.length < 3) return null;
|
|
14423
|
+
const placed = transformPoint([
|
|
14424
|
+
(readMeasure(rawPoint[0]) ?? 0) * lengthFactor,
|
|
14425
|
+
(readMeasure(rawPoint[1]) ?? 0) * lengthFactor,
|
|
14426
|
+
(readMeasure(rawPoint[2]) ?? 0) * lengthFactor
|
|
14427
|
+
], worldTransform);
|
|
14428
|
+
vertices.push(placed[0], placed[1], placed[2]);
|
|
14429
|
+
}
|
|
14430
|
+
for (const rawTriangle of rawTriangles) {
|
|
14431
|
+
if (!Array.isArray(rawTriangle) || rawTriangle.length < 3) return null;
|
|
14432
|
+
indices.push((readMeasure(rawTriangle[0]) ?? 1) - 1, (readMeasure(rawTriangle[1]) ?? 1) - 1, (readMeasure(rawTriangle[2]) ?? 1) - 1);
|
|
14433
|
+
}
|
|
14265
14434
|
return {
|
|
14266
14435
|
vertices: new Float32Array(vertices),
|
|
14267
14436
|
indices: new Uint32Array(indices)
|
|
14268
14437
|
};
|
|
14269
14438
|
}
|
|
14439
|
+
function transformPoint(point, transform) {
|
|
14440
|
+
if (transform === null) return point;
|
|
14441
|
+
const m = transform.linear;
|
|
14442
|
+
return [
|
|
14443
|
+
(m[0] ?? 1) * point[0] + (m[1] ?? 0) * point[1] + (m[2] ?? 0) * point[2] + transform.translation[0],
|
|
14444
|
+
(m[3] ?? 0) * point[0] + (m[4] ?? 1) * point[1] + (m[5] ?? 0) * point[2] + transform.translation[1],
|
|
14445
|
+
(m[6] ?? 0) * point[0] + (m[7] ?? 0) * point[1] + (m[8] ?? 1) * point[2] + transform.translation[2]
|
|
14446
|
+
];
|
|
14447
|
+
}
|
|
14270
14448
|
function packBinaryStl(vertices, indices, scaleToMm) {
|
|
14271
14449
|
const triCount = Math.floor(indices.length / 3);
|
|
14272
14450
|
const buffer = /* @__PURE__ */ new ArrayBuffer(84 + triCount * 50);
|
|
@@ -14429,19 +14607,99 @@ function profileToFace(profile) {
|
|
|
14429
14607
|
if (!face.ok) return err(importError("GEOMETRY_RECONSTRUCTION_FAILED", `Profile face build failed: ${face.error.message}`));
|
|
14430
14608
|
return ok(face.value);
|
|
14431
14609
|
}
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14610
|
+
/**
|
|
14611
|
+
* IfcParameterizedProfileDef.Position places the profile inside the swept
|
|
14612
|
+
* solid's XY plane; the writers rely on it to corner-anchor centred rectangle
|
|
14613
|
+
* profiles. Absent on arbitrary (polyline) profiles.
|
|
14614
|
+
*/
|
|
14615
|
+
function readProfilePosition(reader, profileExpressId, scale) {
|
|
14616
|
+
const def = reader.getLine(profileExpressId);
|
|
14617
|
+
const positionRef = def === null ? void 0 : asRef(def["Position"]);
|
|
14618
|
+
if (positionRef === void 0) return null;
|
|
14619
|
+
const position = reader.getLine(positionRef.value);
|
|
14620
|
+
if (position === null) return null;
|
|
14621
|
+
const [x, y] = readPoint2D(reader, position["Location"], scale) ?? [0, 0];
|
|
14622
|
+
const [dx, dy] = readDirection2D(reader, position["RefDirection"]) ?? [1, 0];
|
|
14623
|
+
const len = Math.hypot(dx, dy);
|
|
14624
|
+
const c = len < 1e-12 ? 1 : dx / len;
|
|
14625
|
+
const s = len < 1e-12 ? 0 : dy / len;
|
|
14626
|
+
return {
|
|
14627
|
+
linear: [
|
|
14628
|
+
c,
|
|
14629
|
+
-s,
|
|
14630
|
+
0,
|
|
14631
|
+
s,
|
|
14632
|
+
c,
|
|
14633
|
+
0,
|
|
14634
|
+
0,
|
|
14635
|
+
0,
|
|
14636
|
+
1
|
|
14637
|
+
],
|
|
14638
|
+
translation: [
|
|
14639
|
+
x,
|
|
14640
|
+
y,
|
|
14641
|
+
0
|
|
14642
|
+
]
|
|
14643
|
+
};
|
|
14644
|
+
}
|
|
14645
|
+
/**
|
|
14646
|
+
* Applies the placement chain (outermost first) as one composed rigid motion.
|
|
14647
|
+
* A second applyMatrix on an already-transformed solid can fail BRepCheck on
|
|
14648
|
+
* occt-wasm, so the frames are never applied one after another.
|
|
14649
|
+
*/
|
|
14650
|
+
function placeSolid(solid, frames) {
|
|
14651
|
+
let composed = null;
|
|
14652
|
+
for (const frame of frames) {
|
|
14653
|
+
if (frame === null) continue;
|
|
14654
|
+
composed = composed === null ? frame : composeRigid(composed, frame);
|
|
14655
|
+
}
|
|
14656
|
+
if (composed === null || isIdentity(composed)) return ok(solid);
|
|
14657
|
+
const applied = applyMatrix(solid, composed);
|
|
14658
|
+
solid[Symbol.dispose]();
|
|
14659
|
+
if (!applied.ok) return err(importError("PLACEMENT_READ_FAILED", `Placement transform failed: ${applied.error.message}`));
|
|
14660
|
+
return ok(applied.value);
|
|
14661
|
+
}
|
|
14662
|
+
/** `outer ∘ inner`: the motion that applies `inner` first, then `outer`. */
|
|
14663
|
+
function composeRigid(outer, inner) {
|
|
14664
|
+
const [a00, a01, a02, a10, a11, a12, a20, a21, a22] = outer.linear;
|
|
14665
|
+
const [b00, b01, b02, b10, b11, b12, b20, b21, b22] = inner.linear;
|
|
14666
|
+
const [tx, ty, tz] = inner.translation;
|
|
14667
|
+
const [ux, uy, uz] = outer.translation;
|
|
14668
|
+
return {
|
|
14669
|
+
linear: [
|
|
14670
|
+
a00 * b00 + a01 * b10 + a02 * b20,
|
|
14671
|
+
a00 * b01 + a01 * b11 + a02 * b21,
|
|
14672
|
+
a00 * b02 + a01 * b12 + a02 * b22,
|
|
14673
|
+
a10 * b00 + a11 * b10 + a12 * b20,
|
|
14674
|
+
a10 * b01 + a11 * b11 + a12 * b21,
|
|
14675
|
+
a10 * b02 + a11 * b12 + a12 * b22,
|
|
14676
|
+
a20 * b00 + a21 * b10 + a22 * b20,
|
|
14677
|
+
a20 * b01 + a21 * b11 + a22 * b21,
|
|
14678
|
+
a20 * b02 + a21 * b12 + a22 * b22
|
|
14679
|
+
],
|
|
14680
|
+
translation: [
|
|
14681
|
+
a00 * tx + a01 * ty + a02 * tz + ux,
|
|
14682
|
+
a10 * tx + a11 * ty + a12 * tz + uy,
|
|
14683
|
+
a20 * tx + a21 * ty + a22 * tz + uz
|
|
14684
|
+
]
|
|
14685
|
+
};
|
|
14686
|
+
}
|
|
14687
|
+
/** Rᵀ·v for a rigid frame's rotation part. */
|
|
14688
|
+
function rotateByTranspose(linear, v) {
|
|
14689
|
+
return [
|
|
14690
|
+
linear[0] * v[0] + linear[3] * v[1] + linear[6] * v[2],
|
|
14691
|
+
linear[1] * v[0] + linear[4] * v[1] + linear[7] * v[2],
|
|
14692
|
+
linear[2] * v[0] + linear[5] * v[1] + linear[8] * v[2]
|
|
14693
|
+
];
|
|
14694
|
+
}
|
|
14695
|
+
/** Maps a point from the frame's parent space into the frame: Rᵀ·(p − t). */
|
|
14696
|
+
function inverseRigidPoint(frame, p) {
|
|
14697
|
+
const t = frame.translation;
|
|
14698
|
+
return rotateByTranspose(frame.linear, [
|
|
14699
|
+
p[0] - t[0],
|
|
14700
|
+
p[1] - t[1],
|
|
14701
|
+
p[2] - t[2]
|
|
14702
|
+
]);
|
|
14445
14703
|
}
|
|
14446
14704
|
function finalizeSolid(solid, entity, diagnostics) {
|
|
14447
14705
|
if (!isSolid(solid)) {
|
|
@@ -14558,15 +14816,16 @@ function isIdentity(t) {
|
|
|
14558
14816
|
for (let i = 0; i < 9; i++) if (Math.abs((t.linear[i] ?? 0) - (expected[i] ?? 0)) > 1e-9) return false;
|
|
14559
14817
|
return Math.abs(t.translation[0]) < 1e-9 && Math.abs(t.translation[1]) < 1e-9 && Math.abs(t.translation[2]) < 1e-9;
|
|
14560
14818
|
}
|
|
14561
|
-
function
|
|
14819
|
+
function findBodyItems(reader, representations) {
|
|
14562
14820
|
for (const repRef of representations) {
|
|
14563
14821
|
const rep = reader.getLine(repRef.value);
|
|
14564
14822
|
if (rep === null) continue;
|
|
14565
14823
|
if (readLabel(rep["RepresentationIdentifier"]) !== "Body") continue;
|
|
14566
14824
|
const items = asRefArray(rep["Items"]);
|
|
14567
|
-
if (items === void 0
|
|
14568
|
-
return items
|
|
14825
|
+
if (items === void 0) return [];
|
|
14826
|
+
return items.map((item) => item.value);
|
|
14569
14827
|
}
|
|
14828
|
+
return null;
|
|
14570
14829
|
}
|
|
14571
14830
|
function readPolylinePoints(reader, ref, scale) {
|
|
14572
14831
|
const curveRef = asRef(ref);
|
|
@@ -14590,6 +14849,24 @@ function readPolylinePoints(reader, ref, scale) {
|
|
|
14590
14849
|
}
|
|
14591
14850
|
return out.length >= 3 ? out : void 0;
|
|
14592
14851
|
}
|
|
14852
|
+
function readPoint2D(reader, ref, scale) {
|
|
14853
|
+
const pointRef = asRef(ref);
|
|
14854
|
+
if (pointRef === void 0) return void 0;
|
|
14855
|
+
const pt = reader.getLine(pointRef.value);
|
|
14856
|
+
if (pt === null) return void 0;
|
|
14857
|
+
const coords = asMeasureArray(pt["Coordinates"]);
|
|
14858
|
+
if (coords.length < 2) return void 0;
|
|
14859
|
+
return [(coords[0] ?? 0) * scale * 1e3, (coords[1] ?? 0) * scale * 1e3];
|
|
14860
|
+
}
|
|
14861
|
+
function readDirection2D(reader, ref) {
|
|
14862
|
+
const dirRef = asRef(ref);
|
|
14863
|
+
if (dirRef === void 0) return void 0;
|
|
14864
|
+
const dir = reader.getLine(dirRef.value);
|
|
14865
|
+
if (dir === null) return void 0;
|
|
14866
|
+
const ratios = asMeasureArray(dir["DirectionRatios"]);
|
|
14867
|
+
if (ratios.length < 2) return void 0;
|
|
14868
|
+
return [ratios[0] ?? 0, ratios[1] ?? 0];
|
|
14869
|
+
}
|
|
14593
14870
|
function readPoint(reader, ref, scale) {
|
|
14594
14871
|
const pointRef = asRef(ref);
|
|
14595
14872
|
if (pointRef === void 0) return void 0;
|
|
@@ -14616,6 +14893,9 @@ function readDirection(reader, ref) {
|
|
|
14616
14893
|
ratios[2] ?? 0
|
|
14617
14894
|
];
|
|
14618
14895
|
}
|
|
14896
|
+
function releaseEmbind(value) {
|
|
14897
|
+
value.delete?.();
|
|
14898
|
+
}
|
|
14619
14899
|
function asRef(value) {
|
|
14620
14900
|
if (value !== null && typeof value === "object" && "value" in value) {
|
|
14621
14901
|
const v = value.value;
|
|
@@ -14650,8 +14930,8 @@ function readLabel(value) {
|
|
|
14650
14930
|
if (typeof v === "string") return v;
|
|
14651
14931
|
}
|
|
14652
14932
|
}
|
|
14653
|
-
function
|
|
14654
|
-
return type === WebIFC.
|
|
14933
|
+
function isStreamedTessellatedType(type) {
|
|
14934
|
+
return type === WebIFC.IFCPOLYGONALFACESET || type === WebIFC.IFCFACEBASEDSURFACEMODEL;
|
|
14655
14935
|
}
|
|
14656
14936
|
function normalize(v) {
|
|
14657
14937
|
const len = Math.hypot(v[0], v[1], v[2]);
|
|
@@ -14990,6 +15270,7 @@ function readOwnerHistory(reader, ownerHistoryExpressId) {
|
|
|
14990
15270
|
}
|
|
14991
15271
|
//#endregion
|
|
14992
15272
|
//#region src/import/fromIfc.ts
|
|
15273
|
+
var testHooks = null;
|
|
14993
15274
|
/**
|
|
14994
15275
|
* Each enumerated physical-element IFC type and the {@link ImportedElementCategory}
|
|
14995
15276
|
* it maps to. Curtain-wall sub-components (IfcPlate/IfcMember) and stair/ramp
|
|
@@ -15038,6 +15319,7 @@ async function fromIfc(bytes, options = {}) {
|
|
|
15038
15319
|
const readerResult = await SpfReader.create(bytes, settings);
|
|
15039
15320
|
if (!readerResult.ok) return err(readerResult.error);
|
|
15040
15321
|
const reader = readerResult.value;
|
|
15322
|
+
const elements = [];
|
|
15041
15323
|
try {
|
|
15042
15324
|
reader.buildGuidMap();
|
|
15043
15325
|
const diagnostics = [];
|
|
@@ -15046,13 +15328,13 @@ async function fromIfc(bytes, options = {}) {
|
|
|
15046
15328
|
const spatialRoot = buildSpatialTree(reader, scale);
|
|
15047
15329
|
const containment = buildElementContainmentMap(reader);
|
|
15048
15330
|
const typeEnums = buildTypePredefinedMap(reader);
|
|
15049
|
-
const elements = [];
|
|
15050
15331
|
const byExpressId = /* @__PURE__ */ new Map();
|
|
15051
15332
|
for (const [type, category] of ELEMENT_TYPES) for (const expressId of reader.getLinesOfType(type)) {
|
|
15052
15333
|
const element = readElement(reader, expressId, category, scale, containment, typeEnums, options.skipGeometry ?? false, diagnostics);
|
|
15053
15334
|
if (element === null) continue;
|
|
15054
15335
|
elements.push(element);
|
|
15055
15336
|
byExpressId.set(element.expressId, element);
|
|
15337
|
+
testHooks?.afterElement?.(element, elements.length);
|
|
15056
15338
|
}
|
|
15057
15339
|
const report = appendIssues(emptyReport(), diagnostics);
|
|
15058
15340
|
const applicationName = readApplicationName(reader);
|
|
@@ -15066,6 +15348,7 @@ async function fromIfc(bytes, options = {}) {
|
|
|
15066
15348
|
};
|
|
15067
15349
|
return ok(model);
|
|
15068
15350
|
} catch (e) {
|
|
15351
|
+
disposeElements(elements);
|
|
15069
15352
|
return err(importError("IMPORT_FAILED", "Unexpected failure during IFC import", e));
|
|
15070
15353
|
} finally {
|
|
15071
15354
|
reader.close();
|
|
@@ -15077,6 +15360,7 @@ async function fromIfc(bytes, options = {}) {
|
|
|
15077
15360
|
* sub-read failure is isolated by its own reader and never aborts the element.
|
|
15078
15361
|
*/
|
|
15079
15362
|
function readElement(reader, expressId, category, scale, containment, typeEnums, skipGeometry, diagnostics) {
|
|
15363
|
+
let geometry = null;
|
|
15080
15364
|
try {
|
|
15081
15365
|
const line = reader.getLine(expressId);
|
|
15082
15366
|
if (line === null) {
|
|
@@ -15088,10 +15372,15 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
|
|
|
15088
15372
|
const predefinedType = readPredefinedType(line) ?? typeEnums.get(expressId);
|
|
15089
15373
|
const voidedBy = readVoids(reader, expressId).map((v) => v.openingExpressId);
|
|
15090
15374
|
const fills = findFills(reader, expressId);
|
|
15091
|
-
|
|
15375
|
+
geometry = skipGeometry ? {
|
|
15092
15376
|
fidelity: "NONE",
|
|
15093
|
-
|
|
15377
|
+
completeness: "NONE",
|
|
15378
|
+
solids: [],
|
|
15379
|
+
solid: null,
|
|
15380
|
+
bounds: null,
|
|
15381
|
+
volumeMm3: null
|
|
15094
15382
|
} : reconstructGeometry(reader, expressId, scale, voidedBy, diagnostics);
|
|
15383
|
+
testHooks?.afterGeometry?.(expressId, geometry);
|
|
15095
15384
|
const psets = readPsets(reader, expressId).map(toImportedPset);
|
|
15096
15385
|
const material = readMaterial(reader, expressId, scale);
|
|
15097
15386
|
const classification = readClassification(reader, expressId);
|
|
@@ -15114,6 +15403,7 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
|
|
|
15114
15403
|
...fills !== void 0 ? { fills } : {}
|
|
15115
15404
|
};
|
|
15116
15405
|
} catch (e) {
|
|
15406
|
+
if (geometry !== null) disposeGeometry(geometry);
|
|
15117
15407
|
diagnostics.push(issue("error", "ELEMENT_READ_FAILED", `Element ${expressId} reconstruction threw: ${errMsg(e)}`, expressId));
|
|
15118
15408
|
return null;
|
|
15119
15409
|
}
|
|
@@ -15135,42 +15425,134 @@ function findFills(reader, elementExpressId) {
|
|
|
15135
15425
|
* model. Falls back to the uncut solid (with a diagnostic) if a cut fails.
|
|
15136
15426
|
*/
|
|
15137
15427
|
function reconstructGeometry(reader, expressId, scale, voidedBy, diagnostics) {
|
|
15138
|
-
const base = toImportedGeometry(
|
|
15139
|
-
if (base.fidelity !== "PARAMETRIC" || base.
|
|
15140
|
-
|
|
15428
|
+
const base = toImportedGeometry(reader, expressId, scale, diagnostics);
|
|
15429
|
+
if (base.fidelity !== "PARAMETRIC" || base.completeness !== "COMPLETE" || voidedBy.length === 0) return base;
|
|
15430
|
+
const firstHost = base.solids[0];
|
|
15431
|
+
if (firstHost === void 0) return base;
|
|
15432
|
+
const hosts = [firstHost, ...base.solids.slice(1)];
|
|
15141
15433
|
for (const openingId of voidedBy) {
|
|
15142
15434
|
const opening = readBodyGeometry(reader, openingId, scale, diagnostics);
|
|
15143
15435
|
if (opening.kind !== "SOLID") continue;
|
|
15144
|
-
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
|
|
15150
|
-
|
|
15151
|
-
|
|
15152
|
-
|
|
15436
|
+
try {
|
|
15437
|
+
for (let hostIndex = 0; hostIndex < hosts.length; hostIndex++) {
|
|
15438
|
+
const host = hosts[hostIndex];
|
|
15439
|
+
if (host === void 0) continue;
|
|
15440
|
+
const cutResult = cut(host, opening.solid);
|
|
15441
|
+
if (!cutResult.ok) {
|
|
15442
|
+
diagnostics.push(issue("warning", "VOID_SUBTRACTION_FAILED", `Opening ${openingId} could not be subtracted from element ${expressId}: ${cutResult.error.message}`, expressId));
|
|
15443
|
+
continue;
|
|
15444
|
+
}
|
|
15445
|
+
host[Symbol.dispose]();
|
|
15446
|
+
hosts[hostIndex] = cutResult.value;
|
|
15447
|
+
}
|
|
15448
|
+
} finally {
|
|
15449
|
+
opening.solid[Symbol.dispose]();
|
|
15450
|
+
}
|
|
15451
|
+
}
|
|
15452
|
+
return completeImportedGeometry("PARAMETRIC", hosts, expressId, diagnostics);
|
|
15453
|
+
}
|
|
15454
|
+
function toImportedGeometry(reader, expressId, scale, diagnostics) {
|
|
15455
|
+
const body = readBodyItems(reader, expressId, scale, diagnostics);
|
|
15456
|
+
const solids = [];
|
|
15457
|
+
let hasTessellatedSolid = false;
|
|
15458
|
+
let lossyMesh = null;
|
|
15459
|
+
for (const item of body.items) if (item.kind === "SOLID") {
|
|
15460
|
+
solids.push(item.solid);
|
|
15461
|
+
hasTessellatedSolid ||= item.lossy;
|
|
15462
|
+
} else if (item.kind === "MESH") {
|
|
15463
|
+
const meshAccumulator = lossyMesh ?? {
|
|
15464
|
+
vertices: [],
|
|
15465
|
+
indices: []
|
|
15466
|
+
};
|
|
15467
|
+
lossyMesh = meshAccumulator;
|
|
15468
|
+
const vertexOffset = meshAccumulator.vertices.length / 3;
|
|
15469
|
+
for (const vertex of item.vertices) meshAccumulator.vertices.push(vertex);
|
|
15470
|
+
for (const index of item.indices) meshAccumulator.indices.push(vertexOffset + index);
|
|
15471
|
+
}
|
|
15472
|
+
const completeness = body.hasBody && body.itemCount > 0 && solids.length === body.itemCount ? "COMPLETE" : solids.length > 0 ? "PARTIAL" : "NONE";
|
|
15473
|
+
if (completeness === "PARTIAL") diagnostics.push(issue("warning", "PARTIAL_BODY_RECONSTRUCTION", `Reconstructed ${solids.length} of ${body.itemCount} Body items`, expressId, {
|
|
15474
|
+
reconstructedItems: solids.length,
|
|
15475
|
+
bodyItems: body.itemCount
|
|
15476
|
+
}));
|
|
15477
|
+
else if (completeness === "NONE" && body.hasBody) diagnostics.push(issue("warning", "BODY_RECONSTRUCTION_NONE", `The Product has ${body.itemCount} Body item(s), but none reconstructed as solids`, expressId, { bodyItems: body.itemCount }));
|
|
15478
|
+
const fidelity = lossyMesh !== null ? "TESSELLATED_LOSSY" : hasTessellatedSolid ? "TESSELLATED_MANIFOLD" : solids.length > 0 ? "PARAMETRIC" : "NONE";
|
|
15479
|
+
const aggregate = completeness === "COMPLETE" ? measureCompleteBody(solids, expressId, diagnostics) : {
|
|
15480
|
+
bounds: null,
|
|
15481
|
+
volumeMm3: null
|
|
15482
|
+
};
|
|
15153
15483
|
return {
|
|
15154
|
-
fidelity
|
|
15155
|
-
|
|
15484
|
+
fidelity,
|
|
15485
|
+
completeness,
|
|
15486
|
+
solids,
|
|
15487
|
+
solid: completeness === "COMPLETE" && solids.length === 1 ? solids[0] ?? null : null,
|
|
15488
|
+
...aggregate,
|
|
15489
|
+
...lossyMesh !== null ? {
|
|
15490
|
+
meshVertices: new Float32Array(lossyMesh.vertices),
|
|
15491
|
+
meshIndices: new Uint32Array(lossyMesh.indices)
|
|
15492
|
+
} : {}
|
|
15156
15493
|
};
|
|
15157
15494
|
}
|
|
15158
|
-
function
|
|
15159
|
-
if (result.kind === "SOLID") return {
|
|
15160
|
-
fidelity: result.lossy ? "TESSELLATED_MANIFOLD" : "PARAMETRIC",
|
|
15161
|
-
solid: result.solid
|
|
15162
|
-
};
|
|
15163
|
-
if (result.kind === "MESH") return {
|
|
15164
|
-
fidelity: "TESSELLATED_LOSSY",
|
|
15165
|
-
solid: null,
|
|
15166
|
-
meshVertices: result.vertices,
|
|
15167
|
-
meshIndices: result.indices
|
|
15168
|
-
};
|
|
15495
|
+
function completeImportedGeometry(fidelity, solids, expressId, diagnostics) {
|
|
15169
15496
|
return {
|
|
15170
|
-
fidelity
|
|
15171
|
-
|
|
15497
|
+
fidelity,
|
|
15498
|
+
completeness: "COMPLETE",
|
|
15499
|
+
solids,
|
|
15500
|
+
solid: solids.length === 1 ? solids[0] : null,
|
|
15501
|
+
...measureCompleteBody(solids, expressId, diagnostics)
|
|
15172
15502
|
};
|
|
15173
15503
|
}
|
|
15504
|
+
function measureCompleteBody(solids, expressId, diagnostics) {
|
|
15505
|
+
try {
|
|
15506
|
+
const first = solids[0];
|
|
15507
|
+
if (first === void 0) return {
|
|
15508
|
+
bounds: null,
|
|
15509
|
+
volumeMm3: null
|
|
15510
|
+
};
|
|
15511
|
+
const initial = getBounds(first);
|
|
15512
|
+
let xMin = initial.xMin;
|
|
15513
|
+
let xMax = initial.xMax;
|
|
15514
|
+
let yMin = initial.yMin;
|
|
15515
|
+
let yMax = initial.yMax;
|
|
15516
|
+
let zMin = initial.zMin;
|
|
15517
|
+
let zMax = initial.zMax;
|
|
15518
|
+
let volumeMm3 = 0;
|
|
15519
|
+
for (const solid of solids) {
|
|
15520
|
+
const measured = measureVolume(solid);
|
|
15521
|
+
if (!measured.ok) throw new Error(measured.error.message);
|
|
15522
|
+
volumeMm3 += measured.value;
|
|
15523
|
+
const itemBounds = getBounds(solid);
|
|
15524
|
+
xMin = Math.min(xMin, itemBounds.xMin);
|
|
15525
|
+
xMax = Math.max(xMax, itemBounds.xMax);
|
|
15526
|
+
yMin = Math.min(yMin, itemBounds.yMin);
|
|
15527
|
+
yMax = Math.max(yMax, itemBounds.yMax);
|
|
15528
|
+
zMin = Math.min(zMin, itemBounds.zMin);
|
|
15529
|
+
zMax = Math.max(zMax, itemBounds.zMax);
|
|
15530
|
+
}
|
|
15531
|
+
return {
|
|
15532
|
+
bounds: {
|
|
15533
|
+
xMin,
|
|
15534
|
+
xMax,
|
|
15535
|
+
yMin,
|
|
15536
|
+
yMax,
|
|
15537
|
+
zMin,
|
|
15538
|
+
zMax
|
|
15539
|
+
},
|
|
15540
|
+
volumeMm3
|
|
15541
|
+
};
|
|
15542
|
+
} catch (cause) {
|
|
15543
|
+
diagnostics.push(issue("warning", "BODY_AGGREGATE_MEASUREMENT_FAILED", `Complete Body aggregate measurement failed: ${errMsg(cause)}`, expressId));
|
|
15544
|
+
return {
|
|
15545
|
+
bounds: null,
|
|
15546
|
+
volumeMm3: null
|
|
15547
|
+
};
|
|
15548
|
+
}
|
|
15549
|
+
}
|
|
15550
|
+
function disposeGeometry(geometry) {
|
|
15551
|
+
for (const solid of geometry.solids) solid[Symbol.dispose]();
|
|
15552
|
+
}
|
|
15553
|
+
function disposeElements(elements) {
|
|
15554
|
+
for (const element of elements) disposeGeometry(element.geometry);
|
|
15555
|
+
}
|
|
15174
15556
|
function toImportedPset(pset) {
|
|
15175
15557
|
return {
|
|
15176
15558
|
name: pset.name,
|
|
@@ -15248,7 +15630,7 @@ function errMsg(e) {
|
|
|
15248
15630
|
* with the model (it is not reclaimed automatically when the model is GC'd).
|
|
15249
15631
|
*/
|
|
15250
15632
|
function disposeImportedModel(model) {
|
|
15251
|
-
for (const el of model.elements) el.geometry.solid
|
|
15633
|
+
for (const el of model.elements) for (const solid of el.geometry.solids) solid[Symbol.dispose]();
|
|
15252
15634
|
}
|
|
15253
15635
|
//#endregion
|
|
15254
15636
|
//#region src/specs/materialSpec.ts
|
|
@@ -28832,13 +29214,25 @@ function specLocalShift(el) {
|
|
|
28832
29214
|
0
|
|
28833
29215
|
];
|
|
28834
29216
|
}
|
|
29217
|
+
/** Skip the resolver occurrence wrappers already owned by `cumulativeFrame`,
|
|
29218
|
+
* then recover only the Body's outer literal Datum translations. An unexpected
|
|
29219
|
+
* wrapper shape returns zero rather than risking an occurrence transform twice. */
|
|
29220
|
+
function bodyLocalTranslation(el, occurrenceTransformDepth) {
|
|
29221
|
+
let body = el.geometry;
|
|
29222
|
+
for (let i = 0; i < occurrenceTransformDepth; i++) {
|
|
29223
|
+
if (body.kind !== "Translate" && body.kind !== "Rotate") return ZERO_TRANSLATION;
|
|
29224
|
+
body = body.target;
|
|
29225
|
+
}
|
|
29226
|
+
return peelTranslates(body).total;
|
|
29227
|
+
}
|
|
28835
29228
|
/** World frame of an element's own body: the walk's cumulative frame (all
|
|
28836
29229
|
* authored transforms, ancestors + own) composed with the body's authored axes
|
|
28837
|
-
* (`axisX`/`axisZ` props) and its local origin (`origin` prop +
|
|
28838
|
-
* shift). Under no rotation this reduces to `composedOrigin`
|
|
28839
|
-
|
|
29230
|
+
* (`axisX`/`axisZ` props) and its local origin (`origin` prop + literal Body
|
|
29231
|
+
* Datum + centring shift). Under no rotation this reduces to `composedOrigin`
|
|
29232
|
+
* + shift. */
|
|
29233
|
+
function elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth) {
|
|
28840
29234
|
return frameMul(frameMul(cumulativeFrame, frameFromPlacement({
|
|
28841
|
-
origin: authoredSpecOrigin(el),
|
|
29235
|
+
origin: addTranslation(authoredSpecOrigin(el), bodyLocalTranslation(el, occurrenceTransformDepth)),
|
|
28842
29236
|
axisX: authoredAxisX(el),
|
|
28843
29237
|
axisZ: authoredAxisZ(el)
|
|
28844
29238
|
})), translationFrame(specLocalShift(el)));
|
|
@@ -28857,10 +29251,10 @@ function civilNodeFrame(el, cumulativeFrame) {
|
|
|
28857
29251
|
* `axisZ` are recomputed from the element's world frame relative to its spatial
|
|
28858
29252
|
* container. Stair/ramp flights, which carry their own per-flight frame, are
|
|
28859
29253
|
* each re-placed the same way. */
|
|
28860
|
-
function rotatedRoutedInput(flatInput, el, cumulativeFrame, spatialFrame) {
|
|
29254
|
+
function rotatedRoutedInput(flatInput, el, cumulativeFrame, occurrenceTransformDepth, spatialFrame) {
|
|
28861
29255
|
const toSpatial = frameInverse(spatialFrame);
|
|
28862
29256
|
if (Array.isArray(flatInput["flights"])) {
|
|
28863
|
-
const elementFrame = elementBodyFrame(el, cumulativeFrame);
|
|
29257
|
+
const elementFrame = elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth);
|
|
28864
29258
|
const flights = flatInput["flights"];
|
|
28865
29259
|
return {
|
|
28866
29260
|
...flatInput,
|
|
@@ -28901,7 +29295,7 @@ function rotatedRoutedInput(flatInput, el, cumulativeFrame, spatialFrame) {
|
|
|
28901
29295
|
})
|
|
28902
29296
|
};
|
|
28903
29297
|
}
|
|
28904
|
-
const placed = decomposeFrame(frameMul(toSpatial, elementBodyFrame(el, cumulativeFrame)));
|
|
29298
|
+
const placed = decomposeFrame(frameMul(toSpatial, elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth)));
|
|
28905
29299
|
return {
|
|
28906
29300
|
...flatInput,
|
|
28907
29301
|
origin: placed.origin,
|
|
@@ -29066,6 +29460,7 @@ function familiesToBim(root, options) {
|
|
|
29066
29460
|
const walk = (el, state) => {
|
|
29067
29461
|
const rotatedHere = state.rotated || hasRotateOp(el);
|
|
29068
29462
|
const cumulativeTranslationHere = addTranslation(state.cumulativeTranslation, authoredTranslation(el));
|
|
29463
|
+
const occurrenceTransformDepthHere = state.occurrenceTransformDepth + el.localTransforms.length;
|
|
29069
29464
|
const cumulativeFrameHere = frameMul(state.cumulativeFrame, frameFromOps(el.localTransforms));
|
|
29070
29465
|
let proxiedHere = false;
|
|
29071
29466
|
let nextRotated = rotatedHere;
|
|
@@ -29129,7 +29524,7 @@ function familiesToBim(root, options) {
|
|
|
29129
29524
|
}
|
|
29130
29525
|
const routedInput = el.semantics?.kind === "product" ? civilProductSpecInput(el) : ("input" in route ? route.input : specInput)(el);
|
|
29131
29526
|
const rotatedBase = Array.isArray(routedInput["flights"]) ? specInput(el) : routedInput;
|
|
29132
|
-
const placedInput = rotatedHere ? rotatedRoutedInput(rotatedBase, el, cumulativeFrameHere, state.spatialFrame) : usesAuthoredCivilHierarchy ? relativeSpecInput(routedInput, state.projectedSpatialTranslation) : routedInput;
|
|
29527
|
+
const placedInput = rotatedHere ? rotatedRoutedInput(rotatedBase, el, cumulativeFrameHere, occurrenceTransformDepthHere, state.spatialFrame) : usesAuthoredCivilHierarchy ? relativeSpecInput(routedInput, state.projectedSpatialTranslation) : routedInput;
|
|
29133
29528
|
const parsed = route.parse(placedInput);
|
|
29134
29529
|
if (!parsed.ok) return parsed;
|
|
29135
29530
|
const added = route.add(model, parsed.value, el.keyPath);
|
|
@@ -29166,6 +29561,7 @@ function familiesToBim(root, options) {
|
|
|
29166
29561
|
rotated: nextRotated,
|
|
29167
29562
|
cumulativeTranslation: cumulativeTranslationHere,
|
|
29168
29563
|
projectedSpatialTranslation: nextProjectedSpatialTranslation,
|
|
29564
|
+
occurrenceTransformDepth: occurrenceTransformDepthHere,
|
|
29169
29565
|
cumulativeFrame: nextCumulativeFrame,
|
|
29170
29566
|
spatialFrame: nextSpatialFrame
|
|
29171
29567
|
});
|
|
@@ -29179,6 +29575,7 @@ function familiesToBim(root, options) {
|
|
|
29179
29575
|
rotated: false,
|
|
29180
29576
|
cumulativeTranslation: ZERO_TRANSLATION,
|
|
29181
29577
|
projectedSpatialTranslation: ZERO_TRANSLATION,
|
|
29578
|
+
occurrenceTransformDepth: 0,
|
|
29182
29579
|
cumulativeFrame: IDENTITY_FRAME,
|
|
29183
29580
|
spatialFrame: IDENTITY_FRAME
|
|
29184
29581
|
});
|