brepjs-bim 0.23.1 → 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 +332 -99
- package/dist/brepjs-bim.js +324 -91
- 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.cjs
CHANGED
|
@@ -535,7 +535,7 @@ var propertyKeyTypes = /* @__PURE__*/ new Set([
|
|
|
535
535
|
function escapeRegex(str) {
|
|
536
536
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
537
537
|
}
|
|
538
|
-
function clone$
|
|
538
|
+
function clone$2(inst, def, params) {
|
|
539
539
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
540
540
|
if (!def || params?.parent) cl._zod.parent = inst;
|
|
541
541
|
return cl;
|
|
@@ -571,7 +571,7 @@ function pick(schema, mask) {
|
|
|
571
571
|
const currDef = schema._zod.def;
|
|
572
572
|
const checks = currDef.checks;
|
|
573
573
|
if (checks && checks.length > 0) throw new Error(".pick() cannot be used on object schemas containing refinements");
|
|
574
|
-
return clone$
|
|
574
|
+
return clone$2(schema, mergeDefs(schema._zod.def, {
|
|
575
575
|
get shape() {
|
|
576
576
|
const newShape = {};
|
|
577
577
|
for (const key in mask) {
|
|
@@ -589,7 +589,7 @@ function omit(schema, mask) {
|
|
|
589
589
|
const currDef = schema._zod.def;
|
|
590
590
|
const checks = currDef.checks;
|
|
591
591
|
if (checks && checks.length > 0) throw new Error(".omit() cannot be used on object schemas containing refinements");
|
|
592
|
-
return clone$
|
|
592
|
+
return clone$2(schema, mergeDefs(schema._zod.def, {
|
|
593
593
|
get shape() {
|
|
594
594
|
const newShape = { ...schema._zod.def.shape };
|
|
595
595
|
for (const key in mask) {
|
|
@@ -610,7 +610,7 @@ function extend(schema, shape) {
|
|
|
610
610
|
const existingShape = schema._zod.def.shape;
|
|
611
611
|
for (const key in shape) if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
|
|
612
612
|
}
|
|
613
|
-
return clone$
|
|
613
|
+
return clone$2(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
614
614
|
const _shape = {
|
|
615
615
|
...schema._zod.def.shape,
|
|
616
616
|
...shape
|
|
@@ -621,7 +621,7 @@ function extend(schema, shape) {
|
|
|
621
621
|
}
|
|
622
622
|
function safeExtend(schema, shape) {
|
|
623
623
|
if (!isPlainObject(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
624
|
-
return clone$
|
|
624
|
+
return clone$2(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
625
625
|
const _shape = {
|
|
626
626
|
...schema._zod.def.shape,
|
|
627
627
|
...shape
|
|
@@ -632,7 +632,7 @@ function safeExtend(schema, shape) {
|
|
|
632
632
|
}
|
|
633
633
|
function merge(a, b) {
|
|
634
634
|
if (a._zod.def.checks?.length) throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");
|
|
635
|
-
return clone$
|
|
635
|
+
return clone$2(a, mergeDefs(a._zod.def, {
|
|
636
636
|
get shape() {
|
|
637
637
|
const _shape = {
|
|
638
638
|
...a._zod.def.shape,
|
|
@@ -650,7 +650,7 @@ function merge(a, b) {
|
|
|
650
650
|
function partial(Class, schema, mask) {
|
|
651
651
|
const checks = schema._zod.def.checks;
|
|
652
652
|
if (checks && checks.length > 0) throw new Error(".partial() cannot be used on object schemas containing refinements");
|
|
653
|
-
return clone$
|
|
653
|
+
return clone$2(schema, mergeDefs(schema._zod.def, {
|
|
654
654
|
get shape() {
|
|
655
655
|
const oldShape = schema._zod.def.shape;
|
|
656
656
|
const shape = { ...oldShape };
|
|
@@ -673,7 +673,7 @@ function partial(Class, schema, mask) {
|
|
|
673
673
|
}));
|
|
674
674
|
}
|
|
675
675
|
function required(Class, schema, mask) {
|
|
676
|
-
return clone$
|
|
676
|
+
return clone$2(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
677
677
|
const oldShape = schema._zod.def.shape;
|
|
678
678
|
const shape = { ...oldShape };
|
|
679
679
|
if (mask) for (const key in mask) {
|
|
@@ -4042,7 +4042,7 @@ var ZodType = /*@__PURE__*/ $constructor("ZodType", (inst, def) => {
|
|
|
4042
4042
|
return this.check(...chks);
|
|
4043
4043
|
},
|
|
4044
4044
|
clone(def, params) {
|
|
4045
|
-
return clone$
|
|
4045
|
+
return clone$2(this, def, params);
|
|
4046
4046
|
},
|
|
4047
4047
|
brand() {
|
|
4048
4048
|
return this;
|
|
@@ -14088,6 +14088,7 @@ function buildElementContainmentMap(reader) {
|
|
|
14088
14088
|
//#endregion
|
|
14089
14089
|
//#region src/import/geometryRead.ts
|
|
14090
14090
|
var NONE = { kind: "NONE" };
|
|
14091
|
+
var testHooks$1 = null;
|
|
14091
14092
|
/**
|
|
14092
14093
|
* Reconstructs the `Body` (SweptSolid) representation of a product into a brepjs
|
|
14093
14094
|
* solid, falling back to tessellated mesh import when no parametric path exists.
|
|
@@ -14098,27 +14099,66 @@ var NONE = { kind: "NONE" };
|
|
|
14098
14099
|
* single bad element never aborts a model import.
|
|
14099
14100
|
*/
|
|
14100
14101
|
function readBodyGeometry(reader, productExpressId, scale, diagnostics) {
|
|
14102
|
+
const body = readBodyItems(reader, productExpressId, scale, diagnostics);
|
|
14103
|
+
const selected = body.items.find((item) => item.kind !== "NONE") ?? NONE;
|
|
14104
|
+
for (const item of body.items) if (item !== selected && item.kind === "SOLID") item.solid[Symbol.dispose]();
|
|
14105
|
+
return selected;
|
|
14106
|
+
}
|
|
14107
|
+
/** Reconstructs each supported IFC Body item independently in representation order. */
|
|
14108
|
+
function readBodyItems(reader, productExpressId, scale, diagnostics) {
|
|
14101
14109
|
const product = reader.getLine(productExpressId);
|
|
14102
|
-
if (product === null) return
|
|
14110
|
+
if (product === null) return {
|
|
14111
|
+
hasBody: false,
|
|
14112
|
+
itemCount: 0,
|
|
14113
|
+
items: []
|
|
14114
|
+
};
|
|
14103
14115
|
const representationRef = asRef(product["Representation"]);
|
|
14104
|
-
if (representationRef === void 0) return
|
|
14116
|
+
if (representationRef === void 0) return {
|
|
14117
|
+
hasBody: false,
|
|
14118
|
+
itemCount: 0,
|
|
14119
|
+
items: []
|
|
14120
|
+
};
|
|
14105
14121
|
const productShape = reader.getLine(representationRef.value);
|
|
14106
14122
|
const representations = productShape === null ? void 0 : asRefArray(productShape["Representations"]);
|
|
14107
|
-
if (representations === void 0) return
|
|
14108
|
-
|
|
14109
|
-
|
|
14123
|
+
if (representations === void 0) return {
|
|
14124
|
+
hasBody: false,
|
|
14125
|
+
itemCount: 0,
|
|
14126
|
+
items: []
|
|
14127
|
+
};
|
|
14128
|
+
const bodyItemIds = findBodyItems(reader, representations);
|
|
14129
|
+
if (bodyItemIds === null) return {
|
|
14130
|
+
hasBody: false,
|
|
14131
|
+
itemCount: 0,
|
|
14132
|
+
items: []
|
|
14133
|
+
};
|
|
14110
14134
|
const worldTransform = readWorldTransform(reader, product, scale);
|
|
14111
|
-
const
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
14116
|
-
} catch (
|
|
14117
|
-
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", `Body
|
|
14118
|
-
return NONE;
|
|
14135
|
+
const itemTypes = new Map(bodyItemIds.map((id) => [id, reader.getLineType(id)]));
|
|
14136
|
+
const streamedItemIds = new Set(bodyItemIds.filter((id) => isStreamedTessellatedType(itemTypes.get(id))));
|
|
14137
|
+
let streamedMeshes = /* @__PURE__ */ new Map();
|
|
14138
|
+
if (streamedItemIds.size > 0) try {
|
|
14139
|
+
streamedMeshes = collectItemMeshes(reader, productExpressId, streamedItemIds);
|
|
14140
|
+
} catch (cause) {
|
|
14141
|
+
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", `Body tessellation stream failed: ${errMsg$1(cause)}`, productExpressId));
|
|
14119
14142
|
}
|
|
14120
|
-
|
|
14121
|
-
|
|
14143
|
+
const items = bodyItemIds.map((bodyItemId) => {
|
|
14144
|
+
const itemType = itemTypes.get(bodyItemId);
|
|
14145
|
+
try {
|
|
14146
|
+
if (itemType === web_ifc.IFCEXTRUDEDAREASOLID) return reconstructExtrusion(reader, bodyItemId, scale, worldTransform, diagnostics);
|
|
14147
|
+
if (itemType === web_ifc.IFCREVOLVEDAREASOLID) return reconstructRevolution(reader, bodyItemId, scale, worldTransform, diagnostics);
|
|
14148
|
+
if (itemType === web_ifc.IFCTRIANGULATEDFACESET) return reconstructTriangulatedItem(reader, bodyItemId, scale, worldTransform, diagnostics);
|
|
14149
|
+
if (isStreamedTessellatedType(itemType)) return reconstructStreamedTessellatedItem(streamedMeshes.get(bodyItemId), bodyItemId, diagnostics);
|
|
14150
|
+
} catch (cause) {
|
|
14151
|
+
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", `Body item ${bodyItemId} reconstruction threw: ${errMsg$1(cause)}`, bodyItemId, { productExpressId }));
|
|
14152
|
+
return NONE;
|
|
14153
|
+
}
|
|
14154
|
+
diagnostics.push(issue("warning", "UNSUPPORTED_REPRESENTATION_ITEM", `Unsupported Body item ${bodyItemId} type ${itemType}; geometry skipped`, bodyItemId, { productExpressId }));
|
|
14155
|
+
return NONE;
|
|
14156
|
+
});
|
|
14157
|
+
return {
|
|
14158
|
+
hasBody: true,
|
|
14159
|
+
itemCount: bodyItemIds.length,
|
|
14160
|
+
items
|
|
14161
|
+
};
|
|
14122
14162
|
}
|
|
14123
14163
|
function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagnostics) {
|
|
14124
14164
|
const ext = reader.getLine(extrusionId);
|
|
@@ -14250,22 +14290,61 @@ function reconstructRevolution(reader, revolutionId, scale, worldTransform, diag
|
|
|
14250
14290
|
}
|
|
14251
14291
|
return finalizeSolid(placed.value, revolutionId, diagnostics);
|
|
14252
14292
|
}
|
|
14253
|
-
function
|
|
14254
|
-
const
|
|
14293
|
+
function reconstructTriangulatedItem(reader, itemExpressId, scale, worldTransform, diagnostics) {
|
|
14294
|
+
const faceSet = reader.getLine(itemExpressId);
|
|
14295
|
+
const coordinatesRef = faceSet === null ? void 0 : asRef(faceSet["Coordinates"]);
|
|
14296
|
+
const rawPoints = (coordinatesRef === void 0 ? null : reader.getLine(coordinatesRef.value))?.["CoordList"];
|
|
14297
|
+
const rawTriangles = faceSet?.["CoordIndex"];
|
|
14298
|
+
return reconstructTessellatedMesh(readTriangulatedMesh(rawPoints, rawTriangles, scale, worldTransform), 1, itemExpressId, diagnostics);
|
|
14299
|
+
}
|
|
14300
|
+
function reconstructStreamedTessellatedItem(mesh, itemExpressId, diagnostics) {
|
|
14301
|
+
return reconstructTessellatedMesh(mesh ?? null, 1e3, itemExpressId, diagnostics);
|
|
14302
|
+
}
|
|
14303
|
+
function reconstructTessellatedMesh(mesh, scaleToMm, itemExpressId, diagnostics) {
|
|
14255
14304
|
if (mesh === null || mesh.indices.length === 0) {
|
|
14256
|
-
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", "Tessellated geometry yielded no triangles",
|
|
14305
|
+
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", "Tessellated geometry yielded no triangles", itemExpressId));
|
|
14257
14306
|
return NONE;
|
|
14258
14307
|
}
|
|
14259
|
-
const
|
|
14308
|
+
const stl = packBinaryStl(mesh.vertices, mesh.indices, scaleToMm);
|
|
14309
|
+
let solid = sewMeshToSolid(mesh, scaleToMm, itemExpressId, diagnostics);
|
|
14310
|
+
try {
|
|
14311
|
+
if (solid === null) {
|
|
14312
|
+
const cast = castFreshResult((0, brepjs.getKernel)().importSTL(new Uint8Array(stl).buffer));
|
|
14313
|
+
if ((0, brepjs.isSolid)(cast)) {
|
|
14314
|
+
const valid = (0, brepjs.validSolid)(cast);
|
|
14315
|
+
if (valid.ok) solid = valid.value;
|
|
14316
|
+
else cast[Symbol.dispose]();
|
|
14317
|
+
} else {
|
|
14318
|
+
const solids = (0, brepjs.getSolids)(cast);
|
|
14319
|
+
if (solids.length === 1 && solids[0] !== void 0) {
|
|
14320
|
+
const copied = (0, brepjs.clone)(solids[0]);
|
|
14321
|
+
if (copied.ok) {
|
|
14322
|
+
const valid = (0, brepjs.validSolid)(copied.value);
|
|
14323
|
+
if (valid.ok) solid = valid.value;
|
|
14324
|
+
else copied.value[Symbol.dispose]();
|
|
14325
|
+
}
|
|
14326
|
+
}
|
|
14327
|
+
cast[Symbol.dispose]();
|
|
14328
|
+
}
|
|
14329
|
+
}
|
|
14330
|
+
} catch (e) {
|
|
14331
|
+
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `STL round-trip failed: ${errMsg$1(e)}`, itemExpressId));
|
|
14332
|
+
}
|
|
14260
14333
|
if (solid !== null) {
|
|
14261
|
-
|
|
14334
|
+
try {
|
|
14335
|
+
testHooks$1?.afterItemSolid?.(itemExpressId, solid);
|
|
14336
|
+
} catch (cause) {
|
|
14337
|
+
solid[Symbol.dispose]();
|
|
14338
|
+
throw cause;
|
|
14339
|
+
}
|
|
14340
|
+
diagnostics.push(issue("info", "TESSELLATED_MANIFOLD", "Tessellated mesh recovered as a closed solid", itemExpressId));
|
|
14262
14341
|
return {
|
|
14263
14342
|
kind: "SOLID",
|
|
14264
14343
|
solid,
|
|
14265
14344
|
lossy: true
|
|
14266
14345
|
};
|
|
14267
14346
|
}
|
|
14268
|
-
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", "Tessellated mesh is not closed/manifold; returning raw triangle data (lossy)",
|
|
14347
|
+
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", "Tessellated mesh is not closed/manifold; returning raw triangle data (lossy)", itemExpressId));
|
|
14269
14348
|
return {
|
|
14270
14349
|
kind: "MESH",
|
|
14271
14350
|
vertices: mesh.vertices,
|
|
@@ -14273,12 +14352,11 @@ function reconstructTessellated(reader, productExpressId, diagnostics) {
|
|
|
14273
14352
|
diagnostic: "TESSELLATED_LOSSY"
|
|
14274
14353
|
};
|
|
14275
14354
|
}
|
|
14276
|
-
/** Loose enough to weld float32 vertices
|
|
14355
|
+
/** Loose enough to weld float32 vertices emitted per face, expressed in millimetres. */
|
|
14277
14356
|
var MESH_SEW_TOLERANCE_MM = .001;
|
|
14278
14357
|
/**
|
|
14279
14358
|
* Brands a fresh kernel result and releases its pre-downcast arena slot when
|
|
14280
|
-
* the cast moved to a new one
|
|
14281
|
-
* are left alone. Mirrors brepjs's internal castResultShape.
|
|
14359
|
+
* the cast moved to a new one. In-place kernels share the slot.
|
|
14282
14360
|
*/
|
|
14283
14361
|
function castFreshResult(raw) {
|
|
14284
14362
|
const cast = (0, brepjs.castShape)(raw);
|
|
@@ -14287,12 +14365,7 @@ function castFreshResult(raw) {
|
|
|
14287
14365
|
if (!(rawId !== void 0 && castId !== void 0 ? rawId === castId : cast.wrapped === raw)) (0, brepjs.getKernel)().dispose(raw);
|
|
14288
14366
|
return cast;
|
|
14289
14367
|
}
|
|
14290
|
-
|
|
14291
|
-
* Sews the streamed triangles into a closed solid through the kernel's mesh
|
|
14292
|
-
* builder, the same path brepjs's mesh importers use. Null when the mesh is
|
|
14293
|
-
* open or the kernel rejects it.
|
|
14294
|
-
*/
|
|
14295
|
-
function sewMeshToSolid(mesh, scaleToMm, productExpressId, diagnostics) {
|
|
14368
|
+
function sewMeshToSolid(mesh, scaleToMm, itemExpressId, diagnostics) {
|
|
14296
14369
|
const kernel = (0, brepjs.getKernel)();
|
|
14297
14370
|
const { vertices, indices } = mesh;
|
|
14298
14371
|
const point = (index) => [
|
|
@@ -14301,8 +14374,8 @@ function sewMeshToSolid(mesh, scaleToMm, productExpressId, diagnostics) {
|
|
|
14301
14374
|
(vertices[index * 3 + 2] ?? 0) * scaleToMm
|
|
14302
14375
|
];
|
|
14303
14376
|
const triangles = [];
|
|
14304
|
-
for (let
|
|
14305
|
-
const face = kernel.buildTriFace(point(indices[
|
|
14377
|
+
for (let index = 0; index + 2 < indices.length; index += 3) {
|
|
14378
|
+
const face = kernel.buildTriFace(point(indices[index] ?? 0), point(indices[index + 1] ?? 0), point(indices[index + 2] ?? 0));
|
|
14306
14379
|
if (face !== null) triangles.push(face);
|
|
14307
14380
|
}
|
|
14308
14381
|
if (triangles.length === 0) return null;
|
|
@@ -14312,38 +14385,45 @@ function sewMeshToSolid(mesh, scaleToMm, productExpressId, diagnostics) {
|
|
|
14312
14385
|
if (valid !== null && valid.ok) return valid.value;
|
|
14313
14386
|
cast[Symbol.dispose]();
|
|
14314
14387
|
return null;
|
|
14315
|
-
} catch (
|
|
14316
|
-
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `Mesh sewing failed: ${errMsg$1(
|
|
14388
|
+
} catch (cause) {
|
|
14389
|
+
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `Mesh sewing failed: ${errMsg$1(cause)}`, itemExpressId));
|
|
14317
14390
|
return null;
|
|
14318
14391
|
} finally {
|
|
14319
14392
|
for (const face of triangles) kernel.dispose(face);
|
|
14320
14393
|
}
|
|
14321
14394
|
}
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
const
|
|
14325
|
-
|
|
14395
|
+
/** Streams a product once, then separates web-ifc geometry by Body item express ID. */
|
|
14396
|
+
function collectItemMeshes(reader, productExpressId, itemExpressIds) {
|
|
14397
|
+
const byItem = /* @__PURE__ */ new Map();
|
|
14398
|
+
const soleItemExpressId = itemExpressIds.size === 1 ? [...itemExpressIds][0] : void 0;
|
|
14326
14399
|
reader.streamMeshes([productExpressId], (flatMesh) => {
|
|
14327
14400
|
try {
|
|
14328
14401
|
const geometries = flatMesh.geometries;
|
|
14329
|
-
for (let
|
|
14330
|
-
const placed = geometries.get(
|
|
14331
|
-
const
|
|
14402
|
+
for (let geometryIndex = 0; geometryIndex < geometries.size(); geometryIndex++) {
|
|
14403
|
+
const placed = geometries.get(geometryIndex);
|
|
14404
|
+
const itemExpressId = itemExpressIds.has(placed.geometryExpressID) ? placed.geometryExpressID : soleItemExpressId;
|
|
14405
|
+
if (itemExpressId === void 0) continue;
|
|
14406
|
+
const accumulator = byItem.get(itemExpressId) ?? {
|
|
14407
|
+
vertices: [],
|
|
14408
|
+
indices: []
|
|
14409
|
+
};
|
|
14410
|
+
byItem.set(itemExpressId, accumulator);
|
|
14411
|
+
const geometry = reader.getGeometry(placed.geometryExpressID);
|
|
14332
14412
|
try {
|
|
14333
|
-
const
|
|
14334
|
-
const
|
|
14335
|
-
const
|
|
14336
|
-
const
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
const
|
|
14340
|
-
const
|
|
14341
|
-
|
|
14413
|
+
const vertices = reader.getVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize());
|
|
14414
|
+
const indices = reader.getIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize());
|
|
14415
|
+
const transform = placed.flatTransformation;
|
|
14416
|
+
const base = accumulator.vertices.length / 3;
|
|
14417
|
+
const vertexCount = vertices.length / 6;
|
|
14418
|
+
for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
|
|
14419
|
+
const x = vertices[vertexIndex * 6] ?? 0;
|
|
14420
|
+
const y = vertices[vertexIndex * 6 + 1] ?? 0;
|
|
14421
|
+
const z = vertices[vertexIndex * 6 + 2] ?? 0;
|
|
14422
|
+
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));
|
|
14342
14423
|
}
|
|
14343
|
-
for (let
|
|
14344
|
-
base += vertexCount;
|
|
14424
|
+
for (let index = 0; index < indices.length; index++) accumulator.indices.push(base + (indices[index] ?? 0));
|
|
14345
14425
|
} finally {
|
|
14346
|
-
|
|
14426
|
+
geometry.delete();
|
|
14347
14427
|
}
|
|
14348
14428
|
}
|
|
14349
14429
|
} finally {
|
|
@@ -14351,12 +14431,62 @@ function collectMesh(reader, productExpressId) {
|
|
|
14351
14431
|
releaseEmbind(flatMesh);
|
|
14352
14432
|
}
|
|
14353
14433
|
});
|
|
14354
|
-
|
|
14434
|
+
return new Map([...byItem].map(([itemExpressId, mesh]) => [itemExpressId, {
|
|
14435
|
+
vertices: new Float32Array(mesh.vertices),
|
|
14436
|
+
indices: new Uint32Array(mesh.indices)
|
|
14437
|
+
}]));
|
|
14438
|
+
}
|
|
14439
|
+
function readTriangulatedMesh(rawPoints, rawTriangles, scale, worldTransform) {
|
|
14440
|
+
if (!Array.isArray(rawPoints) || !Array.isArray(rawTriangles)) return null;
|
|
14441
|
+
const vertices = [];
|
|
14442
|
+
const indices = [];
|
|
14443
|
+
const lengthFactor = scale * 1e3;
|
|
14444
|
+
for (const rawPoint of rawPoints) {
|
|
14445
|
+
if (!Array.isArray(rawPoint) || rawPoint.length < 3) return null;
|
|
14446
|
+
const placed = transformPoint([
|
|
14447
|
+
(readMeasure(rawPoint[0]) ?? 0) * lengthFactor,
|
|
14448
|
+
(readMeasure(rawPoint[1]) ?? 0) * lengthFactor,
|
|
14449
|
+
(readMeasure(rawPoint[2]) ?? 0) * lengthFactor
|
|
14450
|
+
], worldTransform);
|
|
14451
|
+
vertices.push(placed[0], placed[1], placed[2]);
|
|
14452
|
+
}
|
|
14453
|
+
for (const rawTriangle of rawTriangles) {
|
|
14454
|
+
if (!Array.isArray(rawTriangle) || rawTriangle.length < 3) return null;
|
|
14455
|
+
indices.push((readMeasure(rawTriangle[0]) ?? 1) - 1, (readMeasure(rawTriangle[1]) ?? 1) - 1, (readMeasure(rawTriangle[2]) ?? 1) - 1);
|
|
14456
|
+
}
|
|
14355
14457
|
return {
|
|
14356
14458
|
vertices: new Float32Array(vertices),
|
|
14357
14459
|
indices: new Uint32Array(indices)
|
|
14358
14460
|
};
|
|
14359
14461
|
}
|
|
14462
|
+
function transformPoint(point, transform) {
|
|
14463
|
+
if (transform === null) return point;
|
|
14464
|
+
const m = transform.linear;
|
|
14465
|
+
return [
|
|
14466
|
+
(m[0] ?? 1) * point[0] + (m[1] ?? 0) * point[1] + (m[2] ?? 0) * point[2] + transform.translation[0],
|
|
14467
|
+
(m[3] ?? 0) * point[0] + (m[4] ?? 1) * point[1] + (m[5] ?? 0) * point[2] + transform.translation[1],
|
|
14468
|
+
(m[6] ?? 0) * point[0] + (m[7] ?? 0) * point[1] + (m[8] ?? 1) * point[2] + transform.translation[2]
|
|
14469
|
+
];
|
|
14470
|
+
}
|
|
14471
|
+
function packBinaryStl(vertices, indices, scaleToMm) {
|
|
14472
|
+
const triCount = Math.floor(indices.length / 3);
|
|
14473
|
+
const buffer = /* @__PURE__ */ new ArrayBuffer(84 + triCount * 50);
|
|
14474
|
+
const view = new DataView(buffer);
|
|
14475
|
+
view.setUint32(80, triCount, true);
|
|
14476
|
+
let offset = 84;
|
|
14477
|
+
for (let t = 0; t < triCount; t++) {
|
|
14478
|
+
offset += 12;
|
|
14479
|
+
for (let c = 0; c < 3; c++) {
|
|
14480
|
+
const vi = (indices[t * 3 + c] ?? 0) * 3;
|
|
14481
|
+
view.setFloat32(offset, (vertices[vi] ?? 0) * scaleToMm, true);
|
|
14482
|
+
view.setFloat32(offset + 4, (vertices[vi + 1] ?? 0) * scaleToMm, true);
|
|
14483
|
+
view.setFloat32(offset + 8, (vertices[vi + 2] ?? 0) * scaleToMm, true);
|
|
14484
|
+
offset += 12;
|
|
14485
|
+
}
|
|
14486
|
+
offset += 2;
|
|
14487
|
+
}
|
|
14488
|
+
return new Uint8Array(buffer);
|
|
14489
|
+
}
|
|
14360
14490
|
var PARAMETRIC_PROFILE_BUILDERS = /* @__PURE__ */ new Map([
|
|
14361
14491
|
[web_ifc.IFCRECTANGLEPROFILEDEF, (f) => ({
|
|
14362
14492
|
kind: "RECTANGULAR",
|
|
@@ -14709,15 +14839,16 @@ function isIdentity(t) {
|
|
|
14709
14839
|
for (let i = 0; i < 9; i++) if (Math.abs((t.linear[i] ?? 0) - (expected[i] ?? 0)) > 1e-9) return false;
|
|
14710
14840
|
return Math.abs(t.translation[0]) < 1e-9 && Math.abs(t.translation[1]) < 1e-9 && Math.abs(t.translation[2]) < 1e-9;
|
|
14711
14841
|
}
|
|
14712
|
-
function
|
|
14842
|
+
function findBodyItems(reader, representations) {
|
|
14713
14843
|
for (const repRef of representations) {
|
|
14714
14844
|
const rep = reader.getLine(repRef.value);
|
|
14715
14845
|
if (rep === null) continue;
|
|
14716
14846
|
if (readLabel(rep["RepresentationIdentifier"]) !== "Body") continue;
|
|
14717
14847
|
const items = asRefArray(rep["Items"]);
|
|
14718
|
-
if (items === void 0
|
|
14719
|
-
return items
|
|
14848
|
+
if (items === void 0) return [];
|
|
14849
|
+
return items.map((item) => item.value);
|
|
14720
14850
|
}
|
|
14851
|
+
return null;
|
|
14721
14852
|
}
|
|
14722
14853
|
function readPolylinePoints(reader, ref, scale) {
|
|
14723
14854
|
const curveRef = asRef(ref);
|
|
@@ -14822,8 +14953,8 @@ function readLabel(value) {
|
|
|
14822
14953
|
if (typeof v === "string") return v;
|
|
14823
14954
|
}
|
|
14824
14955
|
}
|
|
14825
|
-
function
|
|
14826
|
-
return type === web_ifc.
|
|
14956
|
+
function isStreamedTessellatedType(type) {
|
|
14957
|
+
return type === web_ifc.IFCPOLYGONALFACESET || type === web_ifc.IFCFACEBASEDSURFACEMODEL;
|
|
14827
14958
|
}
|
|
14828
14959
|
function normalize(v) {
|
|
14829
14960
|
const len = Math.hypot(v[0], v[1], v[2]);
|
|
@@ -15162,6 +15293,7 @@ function readOwnerHistory(reader, ownerHistoryExpressId) {
|
|
|
15162
15293
|
}
|
|
15163
15294
|
//#endregion
|
|
15164
15295
|
//#region src/import/fromIfc.ts
|
|
15296
|
+
var testHooks = null;
|
|
15165
15297
|
/**
|
|
15166
15298
|
* Each enumerated physical-element IFC type and the {@link ImportedElementCategory}
|
|
15167
15299
|
* it maps to. Curtain-wall sub-components (IfcPlate/IfcMember) and stair/ramp
|
|
@@ -15210,6 +15342,7 @@ async function fromIfc(bytes, options = {}) {
|
|
|
15210
15342
|
const readerResult = await SpfReader.create(bytes, settings);
|
|
15211
15343
|
if (!readerResult.ok) return (0, brepjs.err)(readerResult.error);
|
|
15212
15344
|
const reader = readerResult.value;
|
|
15345
|
+
const elements = [];
|
|
15213
15346
|
try {
|
|
15214
15347
|
reader.buildGuidMap();
|
|
15215
15348
|
const diagnostics = [];
|
|
@@ -15218,13 +15351,13 @@ async function fromIfc(bytes, options = {}) {
|
|
|
15218
15351
|
const spatialRoot = buildSpatialTree(reader, scale);
|
|
15219
15352
|
const containment = buildElementContainmentMap(reader);
|
|
15220
15353
|
const typeEnums = buildTypePredefinedMap(reader);
|
|
15221
|
-
const elements = [];
|
|
15222
15354
|
const byExpressId = /* @__PURE__ */ new Map();
|
|
15223
15355
|
for (const [type, category] of ELEMENT_TYPES) for (const expressId of reader.getLinesOfType(type)) {
|
|
15224
15356
|
const element = readElement(reader, expressId, category, scale, containment, typeEnums, options.skipGeometry ?? false, diagnostics);
|
|
15225
15357
|
if (element === null) continue;
|
|
15226
15358
|
elements.push(element);
|
|
15227
15359
|
byExpressId.set(element.expressId, element);
|
|
15360
|
+
testHooks?.afterElement?.(element, elements.length);
|
|
15228
15361
|
}
|
|
15229
15362
|
const report = appendIssues(emptyReport(), diagnostics);
|
|
15230
15363
|
const applicationName = readApplicationName(reader);
|
|
@@ -15238,6 +15371,7 @@ async function fromIfc(bytes, options = {}) {
|
|
|
15238
15371
|
};
|
|
15239
15372
|
return (0, brepjs.ok)(model);
|
|
15240
15373
|
} catch (e) {
|
|
15374
|
+
disposeElements(elements);
|
|
15241
15375
|
return (0, brepjs.err)(importError("IMPORT_FAILED", "Unexpected failure during IFC import", e));
|
|
15242
15376
|
} finally {
|
|
15243
15377
|
reader.close();
|
|
@@ -15249,6 +15383,7 @@ async function fromIfc(bytes, options = {}) {
|
|
|
15249
15383
|
* sub-read failure is isolated by its own reader and never aborts the element.
|
|
15250
15384
|
*/
|
|
15251
15385
|
function readElement(reader, expressId, category, scale, containment, typeEnums, skipGeometry, diagnostics) {
|
|
15386
|
+
let geometry = null;
|
|
15252
15387
|
try {
|
|
15253
15388
|
const line = reader.getLine(expressId);
|
|
15254
15389
|
if (line === null) {
|
|
@@ -15260,10 +15395,15 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
|
|
|
15260
15395
|
const predefinedType = readPredefinedType(line) ?? typeEnums.get(expressId);
|
|
15261
15396
|
const voidedBy = readVoids(reader, expressId).map((v) => v.openingExpressId);
|
|
15262
15397
|
const fills = findFills(reader, expressId);
|
|
15263
|
-
|
|
15398
|
+
geometry = skipGeometry ? {
|
|
15264
15399
|
fidelity: "NONE",
|
|
15265
|
-
|
|
15400
|
+
completeness: "NONE",
|
|
15401
|
+
solids: [],
|
|
15402
|
+
solid: null,
|
|
15403
|
+
bounds: null,
|
|
15404
|
+
volumeMm3: null
|
|
15266
15405
|
} : reconstructGeometry(reader, expressId, scale, voidedBy, diagnostics);
|
|
15406
|
+
testHooks?.afterGeometry?.(expressId, geometry);
|
|
15267
15407
|
const psets = readPsets(reader, expressId).map(toImportedPset);
|
|
15268
15408
|
const material = readMaterial(reader, expressId, scale);
|
|
15269
15409
|
const classification = readClassification(reader, expressId);
|
|
@@ -15286,6 +15426,7 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
|
|
|
15286
15426
|
...fills !== void 0 ? { fills } : {}
|
|
15287
15427
|
};
|
|
15288
15428
|
} catch (e) {
|
|
15429
|
+
if (geometry !== null) disposeGeometry(geometry);
|
|
15289
15430
|
diagnostics.push(issue("error", "ELEMENT_READ_FAILED", `Element ${expressId} reconstruction threw: ${errMsg(e)}`, expressId));
|
|
15290
15431
|
return null;
|
|
15291
15432
|
}
|
|
@@ -15307,42 +15448,134 @@ function findFills(reader, elementExpressId) {
|
|
|
15307
15448
|
* model. Falls back to the uncut solid (with a diagnostic) if a cut fails.
|
|
15308
15449
|
*/
|
|
15309
15450
|
function reconstructGeometry(reader, expressId, scale, voidedBy, diagnostics) {
|
|
15310
|
-
const base = toImportedGeometry(
|
|
15311
|
-
if (base.fidelity !== "PARAMETRIC" || base.
|
|
15312
|
-
|
|
15451
|
+
const base = toImportedGeometry(reader, expressId, scale, diagnostics);
|
|
15452
|
+
if (base.fidelity !== "PARAMETRIC" || base.completeness !== "COMPLETE" || voidedBy.length === 0) return base;
|
|
15453
|
+
const firstHost = base.solids[0];
|
|
15454
|
+
if (firstHost === void 0) return base;
|
|
15455
|
+
const hosts = [firstHost, ...base.solids.slice(1)];
|
|
15313
15456
|
for (const openingId of voidedBy) {
|
|
15314
15457
|
const opening = readBodyGeometry(reader, openingId, scale, diagnostics);
|
|
15315
15458
|
if (opening.kind !== "SOLID") continue;
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15323
|
-
|
|
15324
|
-
|
|
15459
|
+
try {
|
|
15460
|
+
for (let hostIndex = 0; hostIndex < hosts.length; hostIndex++) {
|
|
15461
|
+
const host = hosts[hostIndex];
|
|
15462
|
+
if (host === void 0) continue;
|
|
15463
|
+
const cutResult = (0, brepjs.cut)(host, opening.solid);
|
|
15464
|
+
if (!cutResult.ok) {
|
|
15465
|
+
diagnostics.push(issue("warning", "VOID_SUBTRACTION_FAILED", `Opening ${openingId} could not be subtracted from element ${expressId}: ${cutResult.error.message}`, expressId));
|
|
15466
|
+
continue;
|
|
15467
|
+
}
|
|
15468
|
+
host[Symbol.dispose]();
|
|
15469
|
+
hosts[hostIndex] = cutResult.value;
|
|
15470
|
+
}
|
|
15471
|
+
} finally {
|
|
15472
|
+
opening.solid[Symbol.dispose]();
|
|
15473
|
+
}
|
|
15474
|
+
}
|
|
15475
|
+
return completeImportedGeometry("PARAMETRIC", hosts, expressId, diagnostics);
|
|
15476
|
+
}
|
|
15477
|
+
function toImportedGeometry(reader, expressId, scale, diagnostics) {
|
|
15478
|
+
const body = readBodyItems(reader, expressId, scale, diagnostics);
|
|
15479
|
+
const solids = [];
|
|
15480
|
+
let hasTessellatedSolid = false;
|
|
15481
|
+
let lossyMesh = null;
|
|
15482
|
+
for (const item of body.items) if (item.kind === "SOLID") {
|
|
15483
|
+
solids.push(item.solid);
|
|
15484
|
+
hasTessellatedSolid ||= item.lossy;
|
|
15485
|
+
} else if (item.kind === "MESH") {
|
|
15486
|
+
const meshAccumulator = lossyMesh ?? {
|
|
15487
|
+
vertices: [],
|
|
15488
|
+
indices: []
|
|
15489
|
+
};
|
|
15490
|
+
lossyMesh = meshAccumulator;
|
|
15491
|
+
const vertexOffset = meshAccumulator.vertices.length / 3;
|
|
15492
|
+
for (const vertex of item.vertices) meshAccumulator.vertices.push(vertex);
|
|
15493
|
+
for (const index of item.indices) meshAccumulator.indices.push(vertexOffset + index);
|
|
15494
|
+
}
|
|
15495
|
+
const completeness = body.hasBody && body.itemCount > 0 && solids.length === body.itemCount ? "COMPLETE" : solids.length > 0 ? "PARTIAL" : "NONE";
|
|
15496
|
+
if (completeness === "PARTIAL") diagnostics.push(issue("warning", "PARTIAL_BODY_RECONSTRUCTION", `Reconstructed ${solids.length} of ${body.itemCount} Body items`, expressId, {
|
|
15497
|
+
reconstructedItems: solids.length,
|
|
15498
|
+
bodyItems: body.itemCount
|
|
15499
|
+
}));
|
|
15500
|
+
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 }));
|
|
15501
|
+
const fidelity = lossyMesh !== null ? "TESSELLATED_LOSSY" : hasTessellatedSolid ? "TESSELLATED_MANIFOLD" : solids.length > 0 ? "PARAMETRIC" : "NONE";
|
|
15502
|
+
const aggregate = completeness === "COMPLETE" ? measureCompleteBody(solids, expressId, diagnostics) : {
|
|
15503
|
+
bounds: null,
|
|
15504
|
+
volumeMm3: null
|
|
15505
|
+
};
|
|
15325
15506
|
return {
|
|
15326
|
-
fidelity
|
|
15327
|
-
|
|
15507
|
+
fidelity,
|
|
15508
|
+
completeness,
|
|
15509
|
+
solids,
|
|
15510
|
+
solid: completeness === "COMPLETE" && solids.length === 1 ? solids[0] ?? null : null,
|
|
15511
|
+
...aggregate,
|
|
15512
|
+
...lossyMesh !== null ? {
|
|
15513
|
+
meshVertices: new Float32Array(lossyMesh.vertices),
|
|
15514
|
+
meshIndices: new Uint32Array(lossyMesh.indices)
|
|
15515
|
+
} : {}
|
|
15328
15516
|
};
|
|
15329
15517
|
}
|
|
15330
|
-
function
|
|
15331
|
-
if (result.kind === "SOLID") return {
|
|
15332
|
-
fidelity: result.lossy ? "TESSELLATED_MANIFOLD" : "PARAMETRIC",
|
|
15333
|
-
solid: result.solid
|
|
15334
|
-
};
|
|
15335
|
-
if (result.kind === "MESH") return {
|
|
15336
|
-
fidelity: "TESSELLATED_LOSSY",
|
|
15337
|
-
solid: null,
|
|
15338
|
-
meshVertices: result.vertices,
|
|
15339
|
-
meshIndices: result.indices
|
|
15340
|
-
};
|
|
15518
|
+
function completeImportedGeometry(fidelity, solids, expressId, diagnostics) {
|
|
15341
15519
|
return {
|
|
15342
|
-
fidelity
|
|
15343
|
-
|
|
15520
|
+
fidelity,
|
|
15521
|
+
completeness: "COMPLETE",
|
|
15522
|
+
solids,
|
|
15523
|
+
solid: solids.length === 1 ? solids[0] : null,
|
|
15524
|
+
...measureCompleteBody(solids, expressId, diagnostics)
|
|
15344
15525
|
};
|
|
15345
15526
|
}
|
|
15527
|
+
function measureCompleteBody(solids, expressId, diagnostics) {
|
|
15528
|
+
try {
|
|
15529
|
+
const first = solids[0];
|
|
15530
|
+
if (first === void 0) return {
|
|
15531
|
+
bounds: null,
|
|
15532
|
+
volumeMm3: null
|
|
15533
|
+
};
|
|
15534
|
+
const initial = (0, brepjs.getBounds)(first);
|
|
15535
|
+
let xMin = initial.xMin;
|
|
15536
|
+
let xMax = initial.xMax;
|
|
15537
|
+
let yMin = initial.yMin;
|
|
15538
|
+
let yMax = initial.yMax;
|
|
15539
|
+
let zMin = initial.zMin;
|
|
15540
|
+
let zMax = initial.zMax;
|
|
15541
|
+
let volumeMm3 = 0;
|
|
15542
|
+
for (const solid of solids) {
|
|
15543
|
+
const measured = (0, brepjs.measureVolume)(solid);
|
|
15544
|
+
if (!measured.ok) throw new Error(measured.error.message);
|
|
15545
|
+
volumeMm3 += measured.value;
|
|
15546
|
+
const itemBounds = (0, brepjs.getBounds)(solid);
|
|
15547
|
+
xMin = Math.min(xMin, itemBounds.xMin);
|
|
15548
|
+
xMax = Math.max(xMax, itemBounds.xMax);
|
|
15549
|
+
yMin = Math.min(yMin, itemBounds.yMin);
|
|
15550
|
+
yMax = Math.max(yMax, itemBounds.yMax);
|
|
15551
|
+
zMin = Math.min(zMin, itemBounds.zMin);
|
|
15552
|
+
zMax = Math.max(zMax, itemBounds.zMax);
|
|
15553
|
+
}
|
|
15554
|
+
return {
|
|
15555
|
+
bounds: {
|
|
15556
|
+
xMin,
|
|
15557
|
+
xMax,
|
|
15558
|
+
yMin,
|
|
15559
|
+
yMax,
|
|
15560
|
+
zMin,
|
|
15561
|
+
zMax
|
|
15562
|
+
},
|
|
15563
|
+
volumeMm3
|
|
15564
|
+
};
|
|
15565
|
+
} catch (cause) {
|
|
15566
|
+
diagnostics.push(issue("warning", "BODY_AGGREGATE_MEASUREMENT_FAILED", `Complete Body aggregate measurement failed: ${errMsg(cause)}`, expressId));
|
|
15567
|
+
return {
|
|
15568
|
+
bounds: null,
|
|
15569
|
+
volumeMm3: null
|
|
15570
|
+
};
|
|
15571
|
+
}
|
|
15572
|
+
}
|
|
15573
|
+
function disposeGeometry(geometry) {
|
|
15574
|
+
for (const solid of geometry.solids) solid[Symbol.dispose]();
|
|
15575
|
+
}
|
|
15576
|
+
function disposeElements(elements) {
|
|
15577
|
+
for (const element of elements) disposeGeometry(element.geometry);
|
|
15578
|
+
}
|
|
15346
15579
|
function toImportedPset(pset) {
|
|
15347
15580
|
return {
|
|
15348
15581
|
name: pset.name,
|
|
@@ -15420,7 +15653,7 @@ function errMsg(e) {
|
|
|
15420
15653
|
* with the model (it is not reclaimed automatically when the model is GC'd).
|
|
15421
15654
|
*/
|
|
15422
15655
|
function disposeImportedModel(model) {
|
|
15423
|
-
for (const el of model.elements) el.geometry.solid
|
|
15656
|
+
for (const el of model.elements) for (const solid of el.geometry.solids) solid[Symbol.dispose]();
|
|
15424
15657
|
}
|
|
15425
15658
|
//#endregion
|
|
15426
15659
|
//#region src/specs/materialSpec.ts
|