brepjs-bim 0.24.4 → 0.25.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.
@@ -1,4 +1,4 @@
1
- import { GeometryCleanupError, addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, cut, err, extrude, fuse, fuseAll, getBounds, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, locate, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, translate, validSolid } from "brepjs";
1
+ import { GeometryCleanupError, addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, createSolid, cut, err, extrude, fuse, fuseAll, getBounds, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, locate, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, translate, validSolid } from "brepjs";
2
2
  import * as WebIFC from "web-ifc";
3
3
  import { Handle, IFCROOT, IfcAPI } from "web-ifc";
4
4
  //#region src/identity/ifcGuid.ts
@@ -243,7 +243,7 @@ function _usingCtx() {
243
243
  //#region src/elementFns/wallFns.ts
244
244
  function wallToSolid(spec) {
245
245
  try {
246
- var _usingCtx$20 = _usingCtx();
246
+ var _usingCtx$21 = _usingCtx();
247
247
  if (spec.length <= 0) return err(specError("WALL_ZERO_LENGTH", "Wall length must be positive"));
248
248
  if (spec.height <= 0) return err(specError("WALL_ZERO_HEIGHT", "Wall height must be positive"));
249
249
  if (spec.thickness <= 0) return err(specError("WALL_ZERO_THICKNESS", "Wall thickness must be positive"));
@@ -271,7 +271,7 @@ function wallToSolid(spec) {
271
271
  ]
272
272
  ]);
273
273
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "WALL_PROFILE_FAILED", "Failed to create wall profile"));
274
- const profile = _usingCtx$20.u(profileResult.value);
274
+ const profile = _usingCtx$21.u(profileResult.value);
275
275
  const solidResult = extrude(profile, [
276
276
  length,
277
277
  0,
@@ -285,16 +285,16 @@ function wallToSolid(spec) {
285
285
  }
286
286
  return ok(solid);
287
287
  } catch (_) {
288
- _usingCtx$20.e = _;
288
+ _usingCtx$21.e = _;
289
289
  } finally {
290
- _usingCtx$20.d();
290
+ _usingCtx$21.d();
291
291
  }
292
292
  }
293
293
  //#endregion
294
294
  //#region src/elementFns/slabFns.ts
295
295
  function slabToSolid(spec) {
296
296
  try {
297
- var _usingCtx$19 = _usingCtx();
297
+ var _usingCtx$20 = _usingCtx();
298
298
  if (spec.length <= 0) return err(specError("SLAB_ZERO_LENGTH", "Slab length must be positive"));
299
299
  if (spec.width <= 0) return err(specError("SLAB_ZERO_WIDTH", "Slab width must be positive"));
300
300
  if (spec.thickness <= 0) return err(specError("SLAB_ZERO_THICKNESS", "Slab thickness must be positive"));
@@ -322,7 +322,7 @@ function slabToSolid(spec) {
322
322
  ]
323
323
  ]);
324
324
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SLAB_PROFILE_FAILED", "Failed to create slab profile"));
325
- const profile = _usingCtx$19.u(profileResult.value);
325
+ const profile = _usingCtx$20.u(profileResult.value);
326
326
  const solidResult = extrude(profile, [
327
327
  0,
328
328
  0,
@@ -336,9 +336,9 @@ function slabToSolid(spec) {
336
336
  }
337
337
  return ok(solid);
338
338
  } catch (_) {
339
- _usingCtx$19.e = _;
339
+ _usingCtx$20.e = _;
340
340
  } finally {
341
- _usingCtx$19.d();
341
+ _usingCtx$20.d();
342
342
  }
343
343
  }
344
344
  //#endregion
@@ -1696,7 +1696,7 @@ var Doc = class {
1696
1696
  var version = {
1697
1697
  major: 4,
1698
1698
  minor: 6,
1699
- patch: 4
1699
+ patch: 5
1700
1700
  };
1701
1701
  //#endregion
1702
1702
  //#region ../../node_modules/zod/v4/core/schemas.js
@@ -6335,14 +6335,14 @@ function to3D(points) {
6335
6335
  }
6336
6336
  function extendedProfileToFace(profile) {
6337
6337
  try {
6338
- var _usingCtx$18 = _usingCtx();
6338
+ var _usingCtx$19 = _usingCtx();
6339
6339
  const invalid = validateProfile(profile);
6340
6340
  if (invalid !== null) return err(invalid);
6341
6341
  const outerResult = polygon(to3D(outerLoop(profile)));
6342
6342
  if (!outerResult.ok) return err(fromBrepError(outerResult.error, "PROFILE_FACE_FAILED", "Failed to build profile outer face"));
6343
6343
  const holes = holeLoops(profile);
6344
6344
  if (holes.length === 0) return ok(outerResult.value);
6345
- const outerFace = _usingCtx$18.u(outerResult.value);
6345
+ const outerFace = _usingCtx$19.u(outerResult.value);
6346
6346
  const holeFaces = [];
6347
6347
  const holeWires = [];
6348
6348
  const disposeHoleFaces = () => {
@@ -6367,9 +6367,9 @@ function extendedProfileToFace(profile) {
6367
6367
  disposeHoleFaces();
6368
6368
  return ok(faceWithHoles);
6369
6369
  } catch (_) {
6370
- _usingCtx$18.e = _;
6370
+ _usingCtx$19.e = _;
6371
6371
  } finally {
6372
- _usingCtx$18.d();
6372
+ _usingCtx$19.d();
6373
6373
  }
6374
6374
  }
6375
6375
  //#endregion
@@ -6691,7 +6691,7 @@ function profileToPolygon(profile, circleSegments = 32) {
6691
6691
  //#region src/elementFns/beamFns.ts
6692
6692
  function beamToSolid(spec) {
6693
6693
  try {
6694
- var _usingCtx$17 = _usingCtx();
6694
+ var _usingCtx$18 = _usingCtx();
6695
6695
  if (spec.length <= 0) return err(specError("BEAM_ZERO_LENGTH", "Beam length must be positive"));
6696
6696
  if (isExtendedProfile(spec.profile)) try {
6697
6697
  var _usingCtx3 = _usingCtx();
@@ -6729,7 +6729,7 @@ function beamToSolid(spec) {
6729
6729
  ]);
6730
6730
  const profileResult = polygon(profilePts);
6731
6731
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "BEAM_PROFILE_FAILED", "Failed to create beam profile"));
6732
- const profile = _usingCtx$17.u(profileResult.value);
6732
+ const profile = _usingCtx$18.u(profileResult.value);
6733
6733
  const solidResult = extrude(profile, [
6734
6734
  spec.length,
6735
6735
  0,
@@ -6743,16 +6743,16 @@ function beamToSolid(spec) {
6743
6743
  }
6744
6744
  return ok(solid);
6745
6745
  } catch (_) {
6746
- _usingCtx$17.e = _;
6746
+ _usingCtx$18.e = _;
6747
6747
  } finally {
6748
- _usingCtx$17.d();
6748
+ _usingCtx$18.d();
6749
6749
  }
6750
6750
  }
6751
6751
  //#endregion
6752
6752
  //#region src/elementFns/columnFns.ts
6753
6753
  function columnToSolid(spec) {
6754
6754
  try {
6755
- var _usingCtx$16 = _usingCtx();
6755
+ var _usingCtx$17 = _usingCtx();
6756
6756
  if (spec.height <= 0) return err(specError("COLUMN_ZERO_HEIGHT", "Column height must be positive"));
6757
6757
  if (isExtendedProfile(spec.profile)) try {
6758
6758
  var _usingCtx3 = _usingCtx();
@@ -6781,7 +6781,7 @@ function columnToSolid(spec) {
6781
6781
  const profilePts = profilePtsResult.value;
6782
6782
  const profileResult = polygon(profilePts);
6783
6783
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "COLUMN_PROFILE_FAILED", "Failed to create column profile"));
6784
- const profile = _usingCtx$16.u(profileResult.value);
6784
+ const profile = _usingCtx$17.u(profileResult.value);
6785
6785
  const solidResult = extrude(profile, [
6786
6786
  0,
6787
6787
  0,
@@ -6795,9 +6795,9 @@ function columnToSolid(spec) {
6795
6795
  }
6796
6796
  return ok(solid);
6797
6797
  } catch (_) {
6798
- _usingCtx$16.e = _;
6798
+ _usingCtx$17.e = _;
6799
6799
  } finally {
6800
- _usingCtx$16.d();
6800
+ _usingCtx$17.d();
6801
6801
  }
6802
6802
  }
6803
6803
  //#endregion
@@ -6805,7 +6805,7 @@ function columnToSolid(spec) {
6805
6805
  var EPSILON_MM = 1;
6806
6806
  function openingToSolid(spec, wallThickness) {
6807
6807
  try {
6808
- var _usingCtx$15 = _usingCtx();
6808
+ var _usingCtx$16 = _usingCtx();
6809
6809
  if (spec.width <= 0) return err(specError("OPENING_ZERO_WIDTH", "Opening width must be positive"));
6810
6810
  if (spec.height <= 0) return err(specError("OPENING_ZERO_HEIGHT", "Opening height must be positive"));
6811
6811
  if (wallThickness <= 0) return err(specError("OPENING_ZERO_WALL_THICKNESS", "Wall thickness must be positive"));
@@ -6837,7 +6837,7 @@ function openingToSolid(spec, wallThickness) {
6837
6837
  ]
6838
6838
  ]);
6839
6839
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "OPENING_PROFILE_FAILED", "Failed to create opening profile"));
6840
- const profile = _usingCtx$15.u(profileResult.value);
6840
+ const profile = _usingCtx$16.u(profileResult.value);
6841
6841
  const solidResult = extrude(profile, [
6842
6842
  spec.width,
6843
6843
  0,
@@ -6851,16 +6851,16 @@ function openingToSolid(spec, wallThickness) {
6851
6851
  }
6852
6852
  return ok(solid);
6853
6853
  } catch (_) {
6854
- _usingCtx$15.e = _;
6854
+ _usingCtx$16.e = _;
6855
6855
  } finally {
6856
- _usingCtx$15.d();
6856
+ _usingCtx$16.d();
6857
6857
  }
6858
6858
  }
6859
6859
  //#endregion
6860
6860
  //#region src/elementFns/slabOpeningFns.ts
6861
6861
  function slabOpeningToSolid(spec, slabThickness) {
6862
6862
  try {
6863
- var _usingCtx$14 = _usingCtx();
6863
+ var _usingCtx$15 = _usingCtx();
6864
6864
  if (spec.sizeX <= 0) return err(specError("SLAB_OPENING_ZERO_SIZE_X", "Slab opening sizeX must be positive"));
6865
6865
  if (spec.sizeY <= 0) return err(specError("SLAB_OPENING_ZERO_SIZE_Y", "Slab opening sizeY must be positive"));
6866
6866
  if (slabThickness <= 0) return err(specError("SLAB_OPENING_ZERO_SLAB_THICKNESS", "Slab thickness must be positive"));
@@ -6892,7 +6892,7 @@ function slabOpeningToSolid(spec, slabThickness) {
6892
6892
  ]
6893
6893
  ]);
6894
6894
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SLAB_OPENING_PROFILE_FAILED", "Failed to create slab opening profile"));
6895
- const profile = _usingCtx$14.u(profileResult.value);
6895
+ const profile = _usingCtx$15.u(profileResult.value);
6896
6896
  const solidResult = extrude(profile, [
6897
6897
  0,
6898
6898
  0,
@@ -6906,16 +6906,16 @@ function slabOpeningToSolid(spec, slabThickness) {
6906
6906
  }
6907
6907
  return ok(solid);
6908
6908
  } catch (_) {
6909
- _usingCtx$14.e = _;
6909
+ _usingCtx$15.e = _;
6910
6910
  } finally {
6911
- _usingCtx$14.d();
6911
+ _usingCtx$15.d();
6912
6912
  }
6913
6913
  }
6914
6914
  //#endregion
6915
6915
  //#region src/elementFns/spaceFns.ts
6916
6916
  function spaceToSolid(spec) {
6917
6917
  try {
6918
- var _usingCtx$13 = _usingCtx();
6918
+ var _usingCtx$14 = _usingCtx();
6919
6919
  if (spec.length <= 0) return err(specError("SPACE_ZERO_LENGTH", "Space length must be positive"));
6920
6920
  if (spec.width <= 0) return err(specError("SPACE_ZERO_WIDTH", "Space width must be positive"));
6921
6921
  if (spec.height <= 0) return err(specError("SPACE_ZERO_HEIGHT", "Space height must be positive"));
@@ -6943,7 +6943,7 @@ function spaceToSolid(spec) {
6943
6943
  ]
6944
6944
  ]);
6945
6945
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SPACE_PROFILE_FAILED", "Failed to create space footprint"));
6946
- const profile = _usingCtx$13.u(profileResult.value);
6946
+ const profile = _usingCtx$14.u(profileResult.value);
6947
6947
  const solidResult = extrude(profile, [
6948
6948
  0,
6949
6949
  0,
@@ -6957,9 +6957,9 @@ function spaceToSolid(spec) {
6957
6957
  }
6958
6958
  return ok(solid);
6959
6959
  } catch (_) {
6960
- _usingCtx$13.e = _;
6960
+ _usingCtx$14.e = _;
6961
6961
  } finally {
6962
- _usingCtx$13.d();
6962
+ _usingCtx$14.d();
6963
6963
  }
6964
6964
  }
6965
6965
  //#endregion
@@ -6974,7 +6974,7 @@ function gate(solid, code) {
6974
6974
  }
6975
6975
  function flatRoof(spec) {
6976
6976
  try {
6977
- var _usingCtx$12 = _usingCtx();
6977
+ var _usingCtx$13 = _usingCtx();
6978
6978
  const { length, width, thickness } = spec;
6979
6979
  const face = polygon([
6980
6980
  [
@@ -6999,7 +6999,7 @@ function flatRoof(spec) {
6999
6999
  ]
7000
7000
  ]);
7001
7001
  if (!face.ok) return err(fromBrepError(face.error, "ROOF_PROFILE_FAILED", "Failed to create roof profile"));
7002
- const profile = _usingCtx$12.u(face.value);
7002
+ const profile = _usingCtx$13.u(face.value);
7003
7003
  const solid = extrude(profile, [
7004
7004
  0,
7005
7005
  0,
@@ -7008,9 +7008,9 @@ function flatRoof(spec) {
7008
7008
  if (!solid.ok) return err(fromBrepError(solid.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude roof profile"));
7009
7009
  return gate(solid.value, "ROOF_INVALID_SOLID");
7010
7010
  } catch (_) {
7011
- _usingCtx$12.e = _;
7011
+ _usingCtx$13.e = _;
7012
7012
  } finally {
7013
- _usingCtx$12.d();
7013
+ _usingCtx$13.d();
7014
7014
  }
7015
7015
  }
7016
7016
  function shedRoof(spec, pitch) {
@@ -7206,7 +7206,7 @@ function roofToSolid(spec) {
7206
7206
  //#region src/elementFns/curtainWallFns.ts
7207
7207
  function boxSolid(sizeX, sizeY, sizeZ) {
7208
7208
  try {
7209
- var _usingCtx$11 = _usingCtx();
7209
+ var _usingCtx$12 = _usingCtx();
7210
7210
  const profileResult = polygon([
7211
7211
  [
7212
7212
  0,
@@ -7230,7 +7230,7 @@ function boxSolid(sizeX, sizeY, sizeZ) {
7230
7230
  ]
7231
7231
  ]);
7232
7232
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "CURTAIN_WALL_PROFILE_FAILED", "Failed to create component profile"));
7233
- const profile = _usingCtx$11.u(profileResult.value);
7233
+ const profile = _usingCtx$12.u(profileResult.value);
7234
7234
  const solidResult = extrude(profile, [
7235
7235
  0,
7236
7236
  0,
@@ -7244,9 +7244,9 @@ function boxSolid(sizeX, sizeY, sizeZ) {
7244
7244
  }
7245
7245
  return ok(solid);
7246
7246
  } catch (_) {
7247
- _usingCtx$11.e = _;
7247
+ _usingCtx$12.e = _;
7248
7248
  } finally {
7249
- _usingCtx$11.d();
7249
+ _usingCtx$12.d();
7250
7250
  }
7251
7251
  }
7252
7252
  function disposeComponents(components) {
@@ -7345,7 +7345,7 @@ function curtainWallToGrid(spec) {
7345
7345
  //#region src/elementFns/foundationFns.ts
7346
7346
  function footingToSolid(spec) {
7347
7347
  try {
7348
- var _usingCtx$10 = _usingCtx();
7348
+ var _usingCtx$11 = _usingCtx();
7349
7349
  if (spec.length <= 0) return err(specError("FOOTING_ZERO_LENGTH", "Footing length must be positive"));
7350
7350
  if (spec.width <= 0) return err(specError("FOOTING_ZERO_WIDTH", "Footing width must be positive"));
7351
7351
  if (spec.thickness <= 0) return err(specError("FOOTING_ZERO_THICKNESS", "Footing thickness must be positive"));
@@ -7373,7 +7373,7 @@ function footingToSolid(spec) {
7373
7373
  ]
7374
7374
  ]);
7375
7375
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "FOOTING_PROFILE_FAILED", "Failed to create footing profile"));
7376
- const profile = _usingCtx$10.u(profileResult.value);
7376
+ const profile = _usingCtx$11.u(profileResult.value);
7377
7377
  const solidResult = extrude(profile, [
7378
7378
  0,
7379
7379
  0,
@@ -7387,9 +7387,9 @@ function footingToSolid(spec) {
7387
7387
  }
7388
7388
  return ok(solid);
7389
7389
  } catch (_) {
7390
- _usingCtx$10.e = _;
7390
+ _usingCtx$11.e = _;
7391
7391
  } finally {
7392
- _usingCtx$10.d();
7392
+ _usingCtx$11.d();
7393
7393
  }
7394
7394
  }
7395
7395
  function pileToSolid(spec) {
@@ -7446,7 +7446,7 @@ function pileToSolid(spec) {
7446
7446
  //#region src/elementFns/railingFns.ts
7447
7447
  function panelRailing(spec) {
7448
7448
  try {
7449
- var _usingCtx$9 = _usingCtx();
7449
+ var _usingCtx$10 = _usingCtx();
7450
7450
  const { length, height, thickness } = spec;
7451
7451
  const profileResult = polygon([
7452
7452
  [
@@ -7471,7 +7471,7 @@ function panelRailing(spec) {
7471
7471
  ]
7472
7472
  ]);
7473
7473
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "RAILING_PROFILE_FAILED", "Failed to create railing profile"));
7474
- const profile = _usingCtx$9.u(profileResult.value);
7474
+ const profile = _usingCtx$10.u(profileResult.value);
7475
7475
  const solidResult = extrude(profile, [
7476
7476
  length,
7477
7477
  0,
@@ -7485,9 +7485,9 @@ function panelRailing(spec) {
7485
7485
  }
7486
7486
  return ok(solid);
7487
7487
  } catch (_) {
7488
- _usingCtx$9.e = _;
7488
+ _usingCtx$10.e = _;
7489
7489
  } finally {
7490
- _usingCtx$9.d();
7490
+ _usingCtx$10.d();
7491
7491
  }
7492
7492
  }
7493
7493
  function postedRailing(spec) {
@@ -7550,7 +7550,7 @@ function railingToSolid(spec) {
7550
7550
  //#region src/elementFns/coveringFns.ts
7551
7551
  function coveringToSolid(spec) {
7552
7552
  try {
7553
- var _usingCtx$8 = _usingCtx();
7553
+ var _usingCtx$9 = _usingCtx();
7554
7554
  if (spec.length <= 0) return err(specError("COVERING_ZERO_LENGTH", "Covering length must be positive"));
7555
7555
  if (spec.width <= 0) return err(specError("COVERING_ZERO_WIDTH", "Covering width must be positive"));
7556
7556
  if (spec.thickness <= 0) return err(specError("COVERING_ZERO_THICKNESS", "Covering thickness must be positive"));
@@ -7578,7 +7578,7 @@ function coveringToSolid(spec) {
7578
7578
  ]
7579
7579
  ]);
7580
7580
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "COVERING_PROFILE_FAILED", "Failed to create covering profile"));
7581
- const profile = _usingCtx$8.u(profileResult.value);
7581
+ const profile = _usingCtx$9.u(profileResult.value);
7582
7582
  const solidResult = extrude(profile, [
7583
7583
  0,
7584
7584
  0,
@@ -7592,9 +7592,9 @@ function coveringToSolid(spec) {
7592
7592
  }
7593
7593
  return ok(solid);
7594
7594
  } catch (_) {
7595
- _usingCtx$8.e = _;
7595
+ _usingCtx$9.e = _;
7596
7596
  } finally {
7597
- _usingCtx$8.d();
7597
+ _usingCtx$9.d();
7598
7598
  }
7599
7599
  }
7600
7600
  //#endregion
@@ -7782,6 +7782,31 @@ function measureProductBodyMaterial(solids) {
7782
7782
  return measured;
7783
7783
  });
7784
7784
  }
7785
+ /** Borrow all items and query their tight bounds in their existing coordinates. */
7786
+ function productBodyBounds(solids) {
7787
+ const items = validateItems(solids, "productBodyBounds");
7788
+ if (!items.ok) return items;
7789
+ return ownedOperation("productBodyBounds", (scope) => {
7790
+ let bounds;
7791
+ for (const [itemIndex, solid] of items.value.entries()) {
7792
+ scope.itemIndex = itemIndex;
7793
+ const ready = before("bounds", itemIndex);
7794
+ if (!ready.ok) return ready;
7795
+ const current = (productBodyTestHooks()?.bounds ?? getBounds)(solid);
7796
+ if (!Object.values(current).every(Number.isFinite) || current.xMin > current.xMax || current.yMin > current.yMax || current.zMin > current.zMax) return err(bodyError("productBodyBounds", "BODY_INVALID_BOUNDS", "Expected finite ordered bounds", current, itemIndex));
7797
+ bounds = bounds === void 0 ? { ...current } : {
7798
+ xMin: Math.min(bounds.xMin, current.xMin),
7799
+ xMax: Math.max(bounds.xMax, current.xMax),
7800
+ yMin: Math.min(bounds.yMin, current.yMin),
7801
+ yMax: Math.max(bounds.yMax, current.yMax),
7802
+ zMin: Math.min(bounds.zMin, current.zMin),
7803
+ zMax: Math.max(bounds.zMax, current.zMax)
7804
+ };
7805
+ }
7806
+ if (bounds === void 0) throw new Error("Validated Body produced no bounds");
7807
+ return ok(Object.freeze({ bounds: Object.freeze(bounds) }));
7808
+ });
7809
+ }
7785
7810
  //#endregion
7786
7811
  //#region src/model/bimModel.ts
7787
7812
  function exactWallBodyImmutable() {
@@ -8337,18 +8362,18 @@ var BimModel = class {
8337
8362
  }
8338
8363
  #cutWallGeometry(wall, openingSpec) {
8339
8364
  try {
8340
- var _usingCtx$7 = _usingCtx();
8365
+ var _usingCtx$8 = _usingCtx();
8341
8366
  if (wall.geometry.kind === "EXACT") return exactWallBodyImmutable();
8342
8367
  const toolResult = openingToSolid(openingSpec, wall.spec.thickness);
8343
8368
  if (!toolResult.ok) return err(toolResult.error);
8344
- const tool = _usingCtx$7.u(toolResult.value);
8369
+ const tool = _usingCtx$8.u(toolResult.value);
8345
8370
  const cutResult = cut(wall.geometry.solid, tool);
8346
8371
  if (!cutResult.ok) return err(fromBrepError(cutResult.error, "WALL_CUT_FAILED", "Boolean cut of wall with opening failed"));
8347
8372
  return ok(cutResult.value);
8348
8373
  } catch (_) {
8349
- _usingCtx$7.e = _;
8374
+ _usingCtx$8.e = _;
8350
8375
  } finally {
8351
- _usingCtx$7.d();
8376
+ _usingCtx$8.d();
8352
8377
  }
8353
8378
  }
8354
8379
  #replaceWallGeometry(wall, newGeometry) {
@@ -8963,7 +8988,7 @@ function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
8963
8988
  }
8964
8989
  function stairFlightToSolid(spec) {
8965
8990
  try {
8966
- var _usingCtx$6 = _usingCtx();
8991
+ var _usingCtx$7 = _usingCtx();
8967
8992
  if (spec.width <= 0) return err(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
8968
8993
  if (spec.riserHeight <= 0) return err(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
8969
8994
  if (spec.treadLength <= 0) return err(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
@@ -8971,7 +8996,7 @@ function stairFlightToSolid(spec) {
8971
8996
  const silhouette = buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength);
8972
8997
  const profileResult = polygon(silhouette);
8973
8998
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
8974
- const profile = _usingCtx$6.u(profileResult.value);
8999
+ const profile = _usingCtx$7.u(profileResult.value);
8975
9000
  const solidResult = extrude(profile, [
8976
9001
  0,
8977
9002
  spec.width,
@@ -8988,9 +9013,9 @@ function stairFlightToSolid(spec) {
8988
9013
  geometrySimplified: false
8989
9014
  });
8990
9015
  } catch (_) {
8991
- _usingCtx$6.e = _;
9016
+ _usingCtx$7.e = _;
8992
9017
  } finally {
8993
- _usingCtx$6.d();
9018
+ _usingCtx$7.d();
8994
9019
  }
8995
9020
  }
8996
9021
  //#endregion
@@ -9021,7 +9046,7 @@ function buildSilhouette(length, rise, thickness) {
9021
9046
  }
9022
9047
  function rampFlightToSolid(spec) {
9023
9048
  try {
9024
- var _usingCtx$5 = _usingCtx();
9049
+ var _usingCtx$6 = _usingCtx();
9025
9050
  if (spec.width <= 0) return err(specError("RAMP_FLIGHT_ZERO_WIDTH", "Ramp flight width must be positive"));
9026
9051
  if (spec.length <= 0) return err(specError("RAMP_FLIGHT_ZERO_LENGTH", "Ramp flight length must be positive"));
9027
9052
  if (spec.slope <= 0) return err(specError("RAMP_FLIGHT_ZERO_SLOPE", "Ramp flight slope must be positive"));
@@ -9030,7 +9055,7 @@ function rampFlightToSolid(spec) {
9030
9055
  const silhouette = buildSilhouette(spec.length, rise, spec.thickness);
9031
9056
  const profileResult = polygon(silhouette);
9032
9057
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "RAMP_FLIGHT_PROFILE_FAILED", "Failed to create ramp flight silhouette profile"));
9033
- const profile = _usingCtx$5.u(profileResult.value);
9058
+ const profile = _usingCtx$6.u(profileResult.value);
9034
9059
  const solidResult = extrude(profile, [
9035
9060
  0,
9036
9061
  spec.width,
@@ -9047,9 +9072,9 @@ function rampFlightToSolid(spec) {
9047
9072
  geometrySimplified: true
9048
9073
  });
9049
9074
  } catch (_) {
9050
- _usingCtx$5.e = _;
9075
+ _usingCtx$6.e = _;
9051
9076
  } finally {
9052
- _usingCtx$5.d();
9077
+ _usingCtx$6.d();
9053
9078
  }
9054
9079
  }
9055
9080
  //#endregion
@@ -9192,22 +9217,22 @@ function placedSolids(el, options = {}) {
9192
9217
  case "STAIR": {
9193
9218
  const out = [];
9194
9219
  for (const flight of el.spec.flights) try {
9195
- var _usingCtx$4 = _usingCtx();
9220
+ var _usingCtx$5 = _usingCtx();
9196
9221
  const built = stairFlightToSolid(flight);
9197
9222
  if (!built.ok) {
9198
9223
  disposeAll$1(out);
9199
9224
  return err(built.error);
9200
9225
  }
9201
- const placed = placeWithinParent(_usingCtx$4.u(built.value.solid), flight, parentFrame);
9226
+ const placed = placeWithinParent(_usingCtx$5.u(built.value.solid), flight, parentFrame);
9202
9227
  if (!placed.ok) {
9203
9228
  disposeAll$1(out);
9204
9229
  return placed;
9205
9230
  }
9206
9231
  out.push(placed.value);
9207
9232
  } catch (_) {
9208
- _usingCtx$4.e = _;
9233
+ _usingCtx$5.e = _;
9209
9234
  } finally {
9210
- _usingCtx$4.d();
9235
+ _usingCtx$5.d();
9211
9236
  }
9212
9237
  return ok(out);
9213
9238
  }
@@ -9717,11 +9742,21 @@ function writeHeader(w, meta) {
9717
9742
  WorldCoordinateSystem: w.ref(writeAxis2Placement3D(w)),
9718
9743
  TrueNorth: null
9719
9744
  });
9745
+ return {
9746
+ ownerHistoryId,
9747
+ geomContextId,
9748
+ geomSubContextId: writeGeometricSubContext(w, geomContextId, "Body"),
9749
+ unitAssignmentId,
9750
+ lengthUnitId
9751
+ };
9752
+ }
9753
+ /** Separates display Body geometry from non-subtractive Reference geometry. */
9754
+ function writeGeometricSubContext(w, geomContextId, identifier) {
9720
9755
  const geomSubContextId = w.nextId();
9721
9756
  w.writeLine({
9722
9757
  expressID: geomSubContextId,
9723
9758
  type: WebIFC.IFCGEOMETRICREPRESENTATIONSUBCONTEXT,
9724
- ContextIdentifier: w.mkType(WebIFC.IFCLABEL, "Body"),
9759
+ ContextIdentifier: w.mkType(WebIFC.IFCLABEL, identifier),
9725
9760
  ContextType: w.mkType(WebIFC.IFCLABEL, "Model"),
9726
9761
  CoordinateSpaceDimension: null,
9727
9762
  Precision: null,
@@ -9735,13 +9770,7 @@ function writeHeader(w, meta) {
9735
9770
  },
9736
9771
  UserDefinedTargetView: null
9737
9772
  });
9738
- return {
9739
- ownerHistoryId,
9740
- geomContextId,
9741
- geomSubContextId,
9742
- unitAssignmentId,
9743
- lengthUnitId
9744
- };
9773
+ return geomSubContextId;
9745
9774
  }
9746
9775
  /**
9747
9776
  * Writes IfcProjectedCRS + IfcMapConversion georeferencing the model context
@@ -10149,7 +10178,7 @@ function writePreparedTessellationBody(w, items, geomSubContextId) {
10149
10178
  bodyItemIds
10150
10179
  };
10151
10180
  }
10152
- function writeExactBodyGeometry(w, placement, items, geomSubContextId, parentPlacementId) {
10181
+ function writeProductBodyGeometry(w, placement, items, geomSubContextId, parentPlacementId) {
10153
10182
  const placement3DId = writeAxis2Placement3D(w, [
10154
10183
  toIfcLengthM(placement.origin[0]),
10155
10184
  toIfcLengthM(placement.origin[1]),
@@ -10195,47 +10224,6 @@ function writePreparedTessellationItem(w, prepared) {
10195
10224
  });
10196
10225
  return faceSetId;
10197
10226
  }
10198
- /**
10199
- * Builds a wall 'Axis' IfcShapeRepresentation: an IfcPolyline from (0,0) to
10200
- * (lengthM, 0) in the wall's local XY plane, wrapped in an IfcShapeRepresentation
10201
- * with RepresentationIdentifier='Axis', RepresentationType='Curve2D'. Returns
10202
- * the IfcShapeRepresentation express ID so callers can add it alongside the Body
10203
- * representation in an IfcProductDefinitionShape.
10204
- *
10205
- * wallLengthMm is the wall length in mm (brepjs native units); it is converted
10206
- * to metres for IFC.
10207
- */
10208
- function writeWallAxisRepresentation(w, wallLengthMm, geomSubContextId) {
10209
- const lengthM = toIfcLengthM(wallLengthMm);
10210
- const startId = w.nextId();
10211
- w.writeLine({
10212
- expressID: startId,
10213
- type: WebIFC.IFCCARTESIANPOINT,
10214
- Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, 0), w.mkType(WebIFC.IFCLENGTHMEASURE, 0)]
10215
- });
10216
- const endId = w.nextId();
10217
- w.writeLine({
10218
- expressID: endId,
10219
- type: WebIFC.IFCCARTESIANPOINT,
10220
- Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, lengthM), w.mkType(WebIFC.IFCLENGTHMEASURE, 0)]
10221
- });
10222
- const polylineId = w.nextId();
10223
- w.writeLine({
10224
- expressID: polylineId,
10225
- type: WebIFC.IFCPOLYLINE,
10226
- Points: [w.ref(startId), w.ref(endId)]
10227
- });
10228
- const shapeRepId = w.nextId();
10229
- w.writeLine({
10230
- expressID: shapeRepId,
10231
- type: WebIFC.IFCSHAPEREPRESENTATION,
10232
- ContextOfItems: w.ref(geomSubContextId),
10233
- RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, "Axis"),
10234
- RepresentationType: w.mkType(WebIFC.IFCLABEL, "Curve2D"),
10235
- Items: [w.ref(polylineId)]
10236
- });
10237
- return shapeRepId;
10238
- }
10239
10227
  function writeFacetedBrepFallback(w, geomSubContextId, reason) {
10240
10228
  const originId = w.nextId();
10241
10229
  w.writeLine({
@@ -10566,81 +10554,6 @@ function writePolyline(w, points) {
10566
10554
  }
10567
10555
  //#endregion
10568
10556
  //#region src/ifc-writer/geometryWriter.ts
10569
- function writeWallGeometry(w, spec, geomSubContextId, parentPlacementId) {
10570
- const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
10571
- const localPlacementId = w.nextId();
10572
- w.writeLine({
10573
- expressID: localPlacementId,
10574
- type: WebIFC.IFCLOCALPLACEMENT,
10575
- PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
10576
- RelativePlacement: w.ref(placement3DId)
10577
- });
10578
- const thicknessM = toIfcLengthM(spec.thickness);
10579
- const heightM = toIfcLengthM(spec.height);
10580
- const lengthM = toIfcLengthM(spec.length);
10581
- const profileId = w.nextId();
10582
- w.writeLine({
10583
- expressID: profileId,
10584
- type: WebIFC.IFCRECTANGLEPROFILEDEF,
10585
- ProfileType: {
10586
- type: 3,
10587
- value: "AREA"
10588
- },
10589
- ProfileName: null,
10590
- Position: w.ref(writeAxis2Placement2D$1(w, [thicknessM / 2, heightM / 2])),
10591
- XDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, thicknessM),
10592
- YDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, heightM)
10593
- });
10594
- const extrusionPosId = writeAxis2Placement3D(w, [
10595
- 0,
10596
- 0,
10597
- 0
10598
- ], [
10599
- 1,
10600
- 0,
10601
- 0
10602
- ], [
10603
- 0,
10604
- 1,
10605
- 0
10606
- ]);
10607
- const extrusionDirId = writeDirection(w, [
10608
- 0,
10609
- 0,
10610
- 1
10611
- ]);
10612
- const extrusionId = w.nextId();
10613
- w.writeLine({
10614
- expressID: extrusionId,
10615
- type: WebIFC.IFCEXTRUDEDAREASOLID,
10616
- SweptArea: w.ref(profileId),
10617
- Position: w.ref(extrusionPosId),
10618
- ExtrudedDirection: w.ref(extrusionDirId),
10619
- Depth: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, lengthM)
10620
- });
10621
- const shapeRepId = w.nextId();
10622
- w.writeLine({
10623
- expressID: shapeRepId,
10624
- type: WebIFC.IFCSHAPEREPRESENTATION,
10625
- ContextOfItems: w.ref(geomSubContextId),
10626
- RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, "Body"),
10627
- RepresentationType: w.mkType(WebIFC.IFCLABEL, "SweptSolid"),
10628
- Items: [w.ref(extrusionId)]
10629
- });
10630
- const axisRepId = writeWallAxisRepresentation(w, spec.length, geomSubContextId);
10631
- const productDefinitionShapeId = w.nextId();
10632
- w.writeLine({
10633
- expressID: productDefinitionShapeId,
10634
- type: WebIFC.IFCPRODUCTDEFINITIONSHAPE,
10635
- Name: null,
10636
- Description: null,
10637
- Representations: [w.ref(axisRepId), w.ref(shapeRepId)]
10638
- });
10639
- return {
10640
- localPlacementId,
10641
- productDefinitionShapeId
10642
- };
10643
- }
10644
10557
  function writeSlabGeometry(w, spec, geomSubContextId, parentPlacementId) {
10645
10558
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
10646
10559
  const localPlacementId = w.nextId();
@@ -11691,22 +11604,22 @@ function writeStairAssembly(w, spec, stairKey, assemblyGuid, ownerHistoryId, geo
11691
11604
  const flightExpressIds = [];
11692
11605
  let geometrySimplified = false;
11693
11606
  for (const [i, flight] of spec.flights.entries()) try {
11694
- var _usingCtx$3 = _usingCtx();
11607
+ var _usingCtx$4 = _usingCtx();
11695
11608
  const built = stairFlightToSolid(flight);
11696
11609
  if (!built.ok) return {
11697
11610
  ok: false,
11698
11611
  error: built.error
11699
11612
  };
11700
- const flightSolid = _usingCtx$3.u(built.value.solid);
11613
+ const flightSolid = _usingCtx$4.u(built.value.solid);
11701
11614
  const flightPlacementId = writeLocalPlacement(w, flight.origin, flight.axisZ, flight.axisX, stairPlacementId);
11702
11615
  const tess = writeTessellation(w, flightSolid, geomSubContextId, flightPlacementId);
11703
11616
  if (tess.usedFallback) geometrySimplified = true;
11704
11617
  const flightExpressId = writeStairFlightEntity(w, deriveIfcGuidSync(`elem:STAIR_FLIGHT:${stairKey}:${i}`), `${stairName} Flight ${i + 1}`, flight, ownerHistoryId, flightPlacementId, tess.productDefinitionShapeId);
11705
11618
  flightExpressIds.push(flightExpressId);
11706
11619
  } catch (_) {
11707
- _usingCtx$3.e = _;
11620
+ _usingCtx$4.e = _;
11708
11621
  } finally {
11709
- _usingCtx$3.d();
11622
+ _usingCtx$4.d();
11710
11623
  }
11711
11624
  writeRelAggregates(w, deriveIfcGuidSync(`rel:AGGREGATES:STAIR:${stairKey}`), ownerHistoryId, assemblyExpressId, flightExpressIds);
11712
11625
  writeAssemblyType(w, WebIFC.IFCSTAIRTYPE, "Stair Type", deriveIfcGuidSync(`type:STAIR:${stairKey}`), deriveIfcGuidSync(`rel-type:STAIR:${stairKey}`), predefinedType, ownerHistoryId, [assemblyExpressId]);
@@ -11783,104 +11696,6 @@ function writeRampAssembly(w, spec, rampKey, assemblyGuid, ownerHistoryId, geomS
11783
11696
  }
11784
11697
  //#endregion
11785
11698
  //#region src/ifc-writer/railingWriter.ts
11786
- function writeRailingGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
11787
- const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
11788
- const localPlacementId = w.nextId();
11789
- w.writeLine({
11790
- expressID: localPlacementId,
11791
- type: WebIFC.IFCLOCALPLACEMENT,
11792
- PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
11793
- RelativePlacement: w.ref(placement3DId)
11794
- });
11795
- if (spec.infill === "POSTED") {
11796
- const tess = writeTessellation(w, solid, geomSubContextId, localPlacementId);
11797
- return {
11798
- localPlacementId,
11799
- productDefinitionShapeId: tess.productDefinitionShapeId,
11800
- bodyItemId: null,
11801
- usedFallback: tess.usedFallback
11802
- };
11803
- }
11804
- const thicknessM = toIfcLengthM(spec.thickness);
11805
- const heightM = toIfcLengthM(spec.height);
11806
- const lengthM = toIfcLengthM(spec.length);
11807
- const profileOriginId = w.nextId();
11808
- w.writeLine({
11809
- expressID: profileOriginId,
11810
- type: WebIFC.IFCCARTESIANPOINT,
11811
- Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, thicknessM / 2), w.mkType(WebIFC.IFCLENGTHMEASURE, heightM / 2)]
11812
- });
11813
- const profilePosId = w.nextId();
11814
- w.writeLine({
11815
- expressID: profilePosId,
11816
- type: WebIFC.IFCAXIS2PLACEMENT2D,
11817
- Location: w.ref(profileOriginId),
11818
- RefDirection: null
11819
- });
11820
- const profileId = w.nextId();
11821
- w.writeLine({
11822
- expressID: profileId,
11823
- type: WebIFC.IFCRECTANGLEPROFILEDEF,
11824
- ProfileType: {
11825
- type: 3,
11826
- value: "AREA"
11827
- },
11828
- ProfileName: null,
11829
- Position: w.ref(profilePosId),
11830
- XDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, thicknessM),
11831
- YDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, heightM)
11832
- });
11833
- const extrusionPosId = writeAxis2Placement3D(w, [
11834
- 0,
11835
- 0,
11836
- 0
11837
- ], [
11838
- 1,
11839
- 0,
11840
- 0
11841
- ], [
11842
- 0,
11843
- 1,
11844
- 0
11845
- ]);
11846
- const extrusionDirId = writeDirection(w, [
11847
- 0,
11848
- 0,
11849
- 1
11850
- ]);
11851
- const extrusionId = w.nextId();
11852
- w.writeLine({
11853
- expressID: extrusionId,
11854
- type: WebIFC.IFCEXTRUDEDAREASOLID,
11855
- SweptArea: w.ref(profileId),
11856
- Position: w.ref(extrusionPosId),
11857
- ExtrudedDirection: w.ref(extrusionDirId),
11858
- Depth: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, lengthM)
11859
- });
11860
- const shapeRepId = w.nextId();
11861
- w.writeLine({
11862
- expressID: shapeRepId,
11863
- type: WebIFC.IFCSHAPEREPRESENTATION,
11864
- ContextOfItems: w.ref(geomSubContextId),
11865
- RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, "Body"),
11866
- RepresentationType: w.mkType(WebIFC.IFCLABEL, "SweptSolid"),
11867
- Items: [w.ref(extrusionId)]
11868
- });
11869
- const productDefinitionShapeId = w.nextId();
11870
- w.writeLine({
11871
- expressID: productDefinitionShapeId,
11872
- type: WebIFC.IFCPRODUCTDEFINITIONSHAPE,
11873
- Name: null,
11874
- Description: null,
11875
- Representations: [w.ref(shapeRepId)]
11876
- });
11877
- return {
11878
- localPlacementId,
11879
- productDefinitionShapeId,
11880
- bodyItemId: extrusionId,
11881
- usedFallback: false
11882
- };
11883
- }
11884
11699
  function writeRailingEntity(w, guid, name, _predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
11885
11700
  const id = w.nextId();
11886
11701
  w.writeLine({
@@ -13319,7 +13134,7 @@ function writeAxis2Placement2D(w) {
13319
13134
  });
13320
13135
  return id;
13321
13136
  }
13322
- function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, geomSubContextId, ownerHistoryId) {
13137
+ function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, geomSubContextId, ownerHistoryId, representationIdentifier) {
13323
13138
  const widthM = toIfcLengthM(openingSpec.width);
13324
13139
  const heightM = toIfcLengthM(openingSpec.height);
13325
13140
  const offsetAlongWallM = toIfcLengthM(openingSpec.offsetAlongWall);
@@ -13382,7 +13197,7 @@ function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, g
13382
13197
  expressID: shapeRepId,
13383
13198
  type: WebIFC.IFCSHAPEREPRESENTATION,
13384
13199
  ContextOfItems: w.ref(geomSubContextId),
13385
- RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, "Body"),
13200
+ RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, representationIdentifier),
13386
13201
  RepresentationType: w.mkType(WebIFC.IFCLABEL, "SweptSolid"),
13387
13202
  Items: [w.ref(extrusionId)]
13388
13203
  });
@@ -15139,18 +14954,22 @@ function checkGeoreferencing(reader) {
15139
14954
  return [issue("warning", "NO_GEOREFERENCING", "Model contains a facility but no IfcProjectedCRS/IfcGeographicCRS (GRF003) — set ProjectSpec.crs")];
15140
14955
  }
15141
14956
  //#endregion
15142
- //#region src/serialize/exactBodyPreflight.ts
14957
+ //#region src/serialize/productBodyPreflight.ts
15143
14958
  var testItemPreparer = null;
15144
- /** Prepares every exact Body item without writing IFC lines. Source solids remain borrowed. */
15145
- function preflightExactBody(input) {
14959
+ /** Prepares every retained Body item without writing IFC lines. Source solids remain borrowed. */
14960
+ function preflightProductBody(input) {
15146
14961
  const prepareItem = input.prepareItem ?? testItemPreparer ?? prepareTessellation;
14962
+ const failed = (itemIndex, reason, cause) => err(ifcError("BODY_TESSELLATION_FAILED", `Product Body item ${itemIndex} for ${input.localId} could not be tessellated: ${reason}`, cause, {
14963
+ localId: input.localId,
14964
+ itemIndex
14965
+ }));
15147
14966
  const prepareAt = (solid, itemIndex) => {
15148
- const item = prepareItem(solid);
15149
- if (!item.ok) return err(ifcError("EXACT_BODY_TESSELLATION_FAILED", `Exact Product Body item ${itemIndex} for ${input.localId} could not be tessellated: ${item.reason}`, item.cause, {
15150
- localId: input.localId,
15151
- itemIndex
15152
- }));
15153
- return ok(item.value);
14967
+ try {
14968
+ const item = prepareItem(solid);
14969
+ return item.ok ? ok(item.value) : failed(itemIndex, item.reason, item.cause);
14970
+ } catch (cause) {
14971
+ return failed(itemIndex, cause instanceof Error ? cause.message : String(cause), cause);
14972
+ }
15154
14973
  };
15155
14974
  const [firstSolid, ...remainingSolids] = input.solids;
15156
14975
  const first = prepareAt(firstSolid, 0);
@@ -15218,24 +15037,13 @@ function wallVolumeError(cause) {
15218
15037
  function preflightProductBodies(elements) {
15219
15038
  const preflighted = [];
15220
15039
  for (const element of elements) {
15221
- const body = element.geometry;
15222
- if (body.kind === "PARAMETRIC") {
15223
- preflighted.push({
15224
- kind: "PARAMETRIC",
15225
- element,
15226
- solid: body.solid
15227
- });
15228
- continue;
15229
- }
15230
- const prepared = preflightExactBody({
15040
+ const prepared = preflightProductBody({
15231
15041
  localId: element.localId,
15232
- solids: body.solids
15042
+ solids: bodySolids(element.geometry)
15233
15043
  });
15234
15044
  if (!prepared.ok) return err(prepared.error);
15235
15045
  preflighted.push({
15236
- kind: "EXACT",
15237
15046
  element,
15238
- solids: body.solids,
15239
15047
  items: prepared.value
15240
15048
  });
15241
15049
  }
@@ -15247,7 +15055,7 @@ async function toIfc(model, meta) {
15247
15055
  }
15248
15056
  async function serializeIfc(model, meta) {
15249
15057
  try {
15250
- var _usingCtx$2 = _usingCtx();
15058
+ var _usingCtx$3 = _usingCtx();
15251
15059
  const issues = [];
15252
15060
  const project = model.getProject();
15253
15061
  if (!project) return err(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
@@ -15258,7 +15066,7 @@ async function serializeIfc(model, meta) {
15258
15066
  organization: meta.organizationName
15259
15067
  });
15260
15068
  if (!writerResult.ok) return writerResult;
15261
- const w = _usingCtx$2.u(writerResult.value);
15069
+ const w = _usingCtx$3.u(writerResult.value);
15262
15070
  w.setModelScope(project.guid);
15263
15071
  const elements = model.getAllElements();
15264
15072
  const relationships = model.getAllRelationships();
@@ -15370,13 +15178,14 @@ async function serializeIfc(model, meta) {
15370
15178
  spec: wall.spec,
15371
15179
  solids: bodySolids(wall.geometry)
15372
15180
  });
15373
- const { localPlacementId, productDefinitionShapeId } = preflighted.kind === "EXACT" ? writeExactBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId) : writeWallGeometry(w, wall.spec, geomSubContextId, storeyPlacementId);
15181
+ const { localPlacementId, productDefinitionShapeId, bodyItemIds } = writeProductBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId);
15374
15182
  const wallExpressId = writeWallEntity(w, wall.guid, `Wall ${i + 1}`, ownerHistoryId, localPlacementId, productDefinitionShapeId);
15375
15183
  idMap.set(wall.localId, wallExpressId);
15376
15184
  placementMap.set(wall.localId, localPlacementId);
15377
15185
  writeWallCommonPset(w, ownerHistoryId, wallExpressId, wall.spec);
15378
15186
  writeManufacturerPset(w, ownerHistoryId, wallExpressId, wall.spec);
15379
15187
  if (wall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, wallExpressId, wall.spec.customProperties);
15188
+ for (const itemId of bodyItemIds) applySurfaceStyle(w, model, wall.localId, itemId);
15380
15189
  if (!bodyQuantities.ok) issues.push(issue("warning", "WALL_QUANTITY_OMITTED", "Wall quantities omitted because occupied material volume could not be measured", wall.localId, {
15381
15190
  guid: wall.guid,
15382
15191
  cause: bodyQuantities.error
@@ -15526,27 +15335,14 @@ async function serializeIfc(model, meta) {
15526
15335
  const railing = preflighted.element;
15527
15336
  const containingId = findContainerOf(railing.localId, relationships);
15528
15337
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
15529
- let representation;
15530
- let exactBodyItemIds = [];
15531
- if (preflighted.kind === "EXACT") {
15532
- const exact = writeExactBodyGeometry(w, railing.spec, preflighted.items, geomSubContextId, storeyPlacementId);
15533
- exactBodyItemIds = exact.bodyItemIds;
15534
- representation = {
15535
- ...exact,
15536
- bodyItemId: null,
15537
- usedFallback: false
15538
- };
15539
- } else representation = writeRailingGeometry(w, railing.spec, preflighted.solid, geomSubContextId, storeyPlacementId);
15540
- const { localPlacementId, productDefinitionShapeId, bodyItemId, usedFallback } = representation;
15541
- if (usedFallback) console.warn(`Railing ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
15338
+ const { localPlacementId, productDefinitionShapeId, bodyItemIds } = writeProductBodyGeometry(w, railing.spec, preflighted.items, geomSubContextId, storeyPlacementId);
15542
15339
  const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
15543
15340
  idMap.set(railing.localId, railingExpressId);
15544
15341
  placementMap.set(railing.localId, localPlacementId);
15545
15342
  writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
15546
15343
  writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
15547
15344
  if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
15548
- if (preflighted.kind === "EXACT") for (const itemId of exactBodyItemIds) applySurfaceStyle(w, model, railing.localId, itemId);
15549
- else if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
15345
+ for (const itemId of bodyItemIds) applySurfaceStyle(w, model, railing.localId, itemId);
15550
15346
  }
15551
15347
  for (const [i, covering] of coverings.entries()) {
15552
15348
  const containingId = findContainerOf(covering.localId, relationships);
@@ -15562,6 +15358,7 @@ async function serializeIfc(model, meta) {
15562
15358
  }
15563
15359
  const openingPlacementMap = /* @__PURE__ */ new Map();
15564
15360
  const openingEntityMap = /* @__PURE__ */ new Map();
15361
+ let referenceContextId;
15565
15362
  for (const rel of relationships) {
15566
15363
  if (rel.kind !== "VOIDS_WALL") continue;
15567
15364
  const wallElement = elements.find((el) => el.category === "WALL" && el.localId === rel.wallLocalId);
@@ -15572,7 +15369,8 @@ async function serializeIfc(model, meta) {
15572
15369
  const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
15573
15370
  if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
15574
15371
  if (!isWallOpening(openingElement.spec)) continue;
15575
- const { openingEntityId, openingPlacementId } = writeOpeningGeometry(w, openingElement.guid, openingElement.spec, wallElement.spec, wallPlacementId, geomSubContextId, ownerHistoryId);
15372
+ referenceContextId ??= writeGeometricSubContext(w, geomContextId, "Reference");
15373
+ const { openingEntityId, openingPlacementId } = writeOpeningGeometry(w, openingElement.guid, openingElement.spec, wallElement.spec, wallPlacementId, referenceContextId, ownerHistoryId, "Reference");
15576
15374
  idMap.set(rel.openingLocalId, openingEntityId);
15577
15375
  openingPlacementMap.set(rel.openingLocalId, openingPlacementId);
15578
15376
  openingEntityMap.set(rel.openingLocalId, openingEntityId);
@@ -15732,9 +15530,9 @@ async function serializeIfc(model, meta) {
15732
15530
  issues
15733
15531
  }) : saved;
15734
15532
  } catch (_) {
15735
- _usingCtx$2.e = _;
15533
+ _usingCtx$3.e = _;
15736
15534
  } finally {
15737
- _usingCtx$2.d();
15535
+ _usingCtx$3.d();
15738
15536
  }
15739
15537
  }
15740
15538
  /**
@@ -16452,13 +16250,13 @@ function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagno
16452
16250
  ];
16453
16251
  const solidResult = (() => {
16454
16252
  try {
16455
- var _usingCtx$1 = _usingCtx();
16456
- const face = _usingCtx$1.u(faceResult.value);
16253
+ var _usingCtx$2 = _usingCtx();
16254
+ const face = _usingCtx$2.u(faceResult.value);
16457
16255
  return extrude(face, extrudeVec);
16458
16256
  } catch (_) {
16459
- _usingCtx$1.e = _;
16257
+ _usingCtx$2.e = _;
16460
16258
  } finally {
16461
- _usingCtx$1.d();
16259
+ _usingCtx$2.d();
16462
16260
  }
16463
16261
  })();
16464
16262
  if (!solidResult.ok) {
@@ -17180,6 +16978,161 @@ function errMsg$1(e) {
17180
16978
  return e instanceof Error ? e.message : String(e);
17181
16979
  }
17182
16980
  //#endregion
16981
+ //#region src/geometryCleanupDiagnostics.ts
16982
+ var diagnosticSchema = object({
16983
+ operation: string(),
16984
+ itemIndex: number().int().nonnegative(),
16985
+ resourceKind: _enum(["SHAPE", "TRANSFORM"]),
16986
+ localId: number().optional(),
16987
+ cause: unknown()
16988
+ });
16989
+ var failedCleanup = object({
16990
+ kind: literal("FAILED"),
16991
+ diagnostics: array(unknown()).nonempty()
16992
+ });
16993
+ var errorMetadata = object({ cleanup: unknown().optional() });
16994
+ var cleanupAttempt = Symbol("geometryCleanupAttempt");
16995
+ var bodyOperation = _enum(["productBodyBounds", "measureProductBodyMaterial"]);
16996
+ function attemptIdentity(source) {
16997
+ const identity = cleanupAttempt in source ? source[cleanupAttempt] : source;
16998
+ return typeof identity === "object" && identity !== null ? identity : source;
16999
+ }
17000
+ function errorChildren(value) {
17001
+ const children = [];
17002
+ if (value instanceof AggregateError) {
17003
+ const errors = value.errors;
17004
+ children.push(...errors);
17005
+ }
17006
+ if ("cause" in value) children.push(value.cause);
17007
+ if ("error" in value && "suppressed" in value) children.push(value.error, value.suppressed);
17008
+ return children;
17009
+ }
17010
+ /** Keep the actual failed-attempt markers; their causes need not be unique. */
17011
+ function nativeCleanupAttempts(error) {
17012
+ const attempts = [];
17013
+ const seen = /* @__PURE__ */ new Set();
17014
+ const visit = (value) => {
17015
+ if (typeof value !== "object" || value === null || seen.has(value)) return;
17016
+ seen.add(value);
17017
+ if (value instanceof GeometryCleanupError) attempts.push(value);
17018
+ else errorChildren(value).forEach(visit);
17019
+ };
17020
+ visit(error);
17021
+ return attempts;
17022
+ }
17023
+ /** Read prior owner attempts without retrying releases or merging distinct failures. */
17024
+ function reportedGeometryCleanup(error, operation, context = {}) {
17025
+ const diagnostics = [];
17026
+ const seenErrors = /* @__PURE__ */ new Set();
17027
+ const seenAttempts = /* @__PURE__ */ new Set();
17028
+ const add = (diagnostic, identity) => {
17029
+ if (seenAttempts.has(identity)) return;
17030
+ seenAttempts.add(identity);
17031
+ diagnostics.push(Object.freeze({
17032
+ operation: diagnostic.operation,
17033
+ itemIndex: diagnostic.itemIndex,
17034
+ resourceKind: diagnostic.resourceKind,
17035
+ cause: diagnostic.cause,
17036
+ ...diagnostic.localId === void 0 ? {} : { localId: diagnostic.localId },
17037
+ [cleanupAttempt]: identity
17038
+ }));
17039
+ };
17040
+ const visit = (value) => {
17041
+ if (typeof value !== "object" || value === null || seenErrors.has(value)) return;
17042
+ seenErrors.add(value);
17043
+ const metadata = errorMetadata.safeParse("metadata" in value ? value.metadata : void 0);
17044
+ const reports = [metadata.success ? metadata.data.cleanup : void 0, "cleanup" in value ? value.cleanup : void 0];
17045
+ const nativeAttempts = bodyOperation.safeParse("operation" in value ? value.operation : void 0).success ? nativeCleanupAttempts(value) : [];
17046
+ const matched = /* @__PURE__ */ new Set();
17047
+ const seenEntries = /* @__PURE__ */ new Set();
17048
+ for (const report of reports) {
17049
+ const parsed = failedCleanup.safeParse(report);
17050
+ if (!parsed.success) continue;
17051
+ for (const source of parsed.data.diagnostics) {
17052
+ if (typeof source !== "object" || source === null || seenEntries.has(source)) continue;
17053
+ const diagnostic = diagnosticSchema.safeParse(source);
17054
+ if (!diagnostic.success) continue;
17055
+ seenEntries.add(source);
17056
+ let identity = attemptIdentity(source);
17057
+ if (identity === source) {
17058
+ const native = nativeAttempts.find((attempt) => !matched.has(attempt) && attempt.resourceKind === diagnostic.data.resourceKind && attempt.cause === diagnostic.data.cause);
17059
+ if (native !== void 0) identity = native;
17060
+ }
17061
+ if (identity instanceof GeometryCleanupError) matched.add(identity);
17062
+ add(diagnostic.data, identity);
17063
+ }
17064
+ }
17065
+ if (value instanceof GeometryCleanupError) add({
17066
+ operation,
17067
+ itemIndex: context.itemIndex ?? 0,
17068
+ resourceKind: value.resourceKind,
17069
+ cause: value.cause
17070
+ }, value);
17071
+ else errorChildren(value).forEach(visit);
17072
+ };
17073
+ visit(error);
17074
+ return diagnostics;
17075
+ }
17076
+ //#endregion
17077
+ //#region src/import/cutImportedSolids.ts
17078
+ /** Borrow World-placed inputs; own every disconnected result independently of the cut's cache. */
17079
+ function cutImportedSolids(host, opening) {
17080
+ const context = { operation: "cutImportedSolids" };
17081
+ const temporaries = [];
17082
+ const outputs = [];
17083
+ const build = () => {
17084
+ const cutResult = cut(host, opening);
17085
+ if (!cutResult.ok) return err(fromBrepError(cutResult.error, "VOID_CUT_FAILED", "Opening cut failed"));
17086
+ temporaries.push({
17087
+ resource: cutResult.value,
17088
+ itemIndex: 0
17089
+ });
17090
+ const children = getKernel().iterShapes(cutResult.value.wrapped, "solid").map(createSolid);
17091
+ temporaries.push(...children.map((resource, itemIndex) => ({
17092
+ resource,
17093
+ itemIndex
17094
+ })));
17095
+ for (const solid of children) {
17096
+ const copied = clone(solid);
17097
+ if (!copied.ok) return err(fromBrepError(copied.error, "VOID_COPY_FAILED", "Could not retain opening result"));
17098
+ const candidate = copied.value;
17099
+ temporaries.push({
17100
+ resource: candidate,
17101
+ itemIndex: temporaries.length
17102
+ });
17103
+ const validated = validSolid(candidate);
17104
+ if (!validated.ok) return err(importError("VOID_INVALID_SOLID", "Opening result is not valid", validated.error));
17105
+ outputs.push(validated.value);
17106
+ }
17107
+ return ok(outputs);
17108
+ };
17109
+ let result;
17110
+ try {
17111
+ result = build();
17112
+ } catch (cause) {
17113
+ result = err(importError("VOID_CUT_FAILED", "Opening cut failed", cause));
17114
+ }
17115
+ const survivors = new Set(result.ok ? outputs : []);
17116
+ const cleanup = cleanupOwnedResources(temporaries.filter(({ resource }) => !survivors.has(resource)).reverse(), context);
17117
+ const diagnostics = [...result.ok ? [] : reportedGeometryCleanup(result.error, context.operation), ...cleanup.kind === "FAILED" ? cleanup.diagnostics : []];
17118
+ if (result.ok && diagnostics.length === 0) return result;
17119
+ if (result.ok) {
17120
+ const outputCleanup = cleanupOwnedResources(outputs.map((resource, itemIndex) => ({
17121
+ resource,
17122
+ itemIndex
17123
+ })), context);
17124
+ if (outputCleanup.kind === "FAILED") diagnostics.push(...outputCleanup.diagnostics);
17125
+ }
17126
+ const failure = result.ok ? importError("VOID_CLEANUP_FAILED", "Opening result cleanup failed") : result.error;
17127
+ return err({
17128
+ ...failure,
17129
+ metadata: {
17130
+ ...failure.metadata,
17131
+ cleanup: cleanupReport(diagnostics)
17132
+ }
17133
+ });
17134
+ }
17135
+ //#endregion
17183
17136
  //#region src/import/dataRead.ts
17184
17137
  /** Narrows an unknown line attribute to the `{ type, value }` wrapper shape. */
17185
17138
  function asTyped(v) {
@@ -17535,7 +17488,7 @@ var ELEMENT_TYPES = [
17535
17488
  * fatal failures — bad bytes, unsupported schema, WASM open failure — return
17536
17489
  * `err`. Inspect {@link ImportedModel.diagnostics} for per-element quality.
17537
17490
  *
17538
- * The web-ifc model handle is always closed in a `finally` block.
17491
+ * The web-ifc model handle is closed before handing reconstructed owners to the caller.
17539
17492
  */
17540
17493
  async function fromIfc(bytes, options = {}) {
17541
17494
  const settings = { coordinateToOrigin: options.coordinateToOrigin ?? false };
@@ -17543,6 +17496,7 @@ async function fromIfc(bytes, options = {}) {
17543
17496
  if (!readerResult.ok) return err(readerResult.error);
17544
17497
  const reader = readerResult.value;
17545
17498
  const elements = [];
17499
+ let result;
17546
17500
  try {
17547
17501
  reader.buildGuidMap();
17548
17502
  const diagnostics = [];
@@ -17569,13 +17523,23 @@ async function fromIfc(bytes, options = {}) {
17569
17523
  diagnostics: report,
17570
17524
  ...applicationName !== void 0 ? { applicationName } : {}
17571
17525
  };
17572
- return ok(model);
17526
+ result = ok(model);
17573
17527
  } catch (e) {
17574
- disposeElements(elements);
17575
- return err(importError("IMPORT_FAILED", "Unexpected failure during IFC import", e));
17576
- } finally {
17528
+ result = err(importError("IMPORT_FAILED", "Unexpected failure during IFC import", e));
17529
+ }
17530
+ try {
17577
17531
  reader.close();
17532
+ } catch (cause) {
17533
+ result = err(result.ok ? importError("IMPORT_CLOSE_FAILED", "Failed to close IFC reader", cause) : {
17534
+ ...result.error,
17535
+ metadata: {
17536
+ ...result.error.metadata,
17537
+ readerCloseCause: cause
17538
+ }
17539
+ });
17578
17540
  }
17541
+ if (!result.ok) return err(withCleanup(result.error, disposeElements(elements), "fromIfc"));
17542
+ return result;
17579
17543
  }
17580
17544
  /**
17581
17545
  * Reads one product into an {@link ImportedElement}. Returns null only when the
@@ -17626,8 +17590,8 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
17626
17590
  ...fills !== void 0 ? { fills } : {}
17627
17591
  };
17628
17592
  } catch (e) {
17629
- if (geometry !== null) disposeGeometry(geometry);
17630
- diagnostics.push(issue("error", "ELEMENT_READ_FAILED", `Element ${expressId} reconstruction threw: ${errMsg(e)}`, expressId));
17593
+ const failure = geometry === null ? e : withCleanup(importError("ELEMENT_READ_FAILED", "Element read failed", e), disposeGeometry(geometry), "readElement");
17594
+ diagnostics.push(issue("error", "ELEMENT_READ_FAILED", `Element ${expressId} reconstruction threw: ${errMsg(e)}`, expressId, { cause: failure }));
17631
17595
  return null;
17632
17596
  }
17633
17597
  }
@@ -17643,36 +17607,79 @@ function findFills(reader, elementExpressId) {
17643
17607
  }
17644
17608
  /**
17645
17609
  * Reconstructs a host element's body geometry and, per IFC `IfcRelVoidsElement`
17646
- * semantics, subtracts the reconstructed solid of every opening that voids it —
17610
+ * semantics, subtracts each opening's Body representation from a parametric host —
17647
17611
  * so a wall with a door hole comes back as the cut solid, matching the source
17648
17612
  * model. Falls back to the uncut solid (with a diagnostic) if a cut fails.
17649
17613
  */
17650
17614
  function reconstructGeometry(reader, expressId, scale, voidedBy, diagnostics) {
17651
17615
  const base = toImportedGeometry(reader, expressId, scale, diagnostics);
17652
17616
  if (base.fidelity !== "PARAMETRIC" || base.completeness !== "COMPLETE" || voidedBy.length === 0) return base;
17653
- const firstHost = base.solids[0];
17654
- if (firstHost === void 0) return base;
17655
- const hosts = [firstHost, ...base.solids.slice(1)];
17656
- for (const openingId of voidedBy) {
17657
- const opening = readBodyGeometry(reader, openingId, scale, diagnostics);
17658
- if (opening.kind !== "SOLID") continue;
17659
- try {
17660
- for (let hostIndex = 0; hostIndex < hosts.length; hostIndex++) {
17617
+ if (base.solids[0] === void 0) return base;
17618
+ const hosts = [...base.solids];
17619
+ try {
17620
+ for (const openingId of voidedBy) try {
17621
+ var _usingCtx$1 = _usingCtx();
17622
+ const opening = readBodyGeometry(reader, openingId, scale, diagnostics);
17623
+ if (opening.kind !== "SOLID") continue;
17624
+ const openingOwner = _usingCtx$1.u(ownOpening(opening.solid, openingId));
17625
+ for (let hostIndex = 0; hostIndex < hosts.length;) {
17661
17626
  const host = hosts[hostIndex];
17662
- if (host === void 0) continue;
17663
- const cutResult = cut(host, opening.solid);
17627
+ if (host === void 0) break;
17628
+ const cutResult = cutImportedSolids(host, openingOwner.solid);
17664
17629
  if (!cutResult.ok) {
17665
- diagnostics.push(issue("warning", "VOID_SUBTRACTION_FAILED", `Opening ${openingId} could not be subtracted from element ${expressId}: ${cutResult.error.message}`, expressId));
17630
+ diagnostics.push(issue("warning", "VOID_SUBTRACTION_FAILED", `Opening ${openingId} could not be subtracted from element ${expressId}: ${cutResult.error.message}`, expressId, { cause: cutResult.error }));
17631
+ hostIndex++;
17666
17632
  continue;
17667
17633
  }
17668
- host[Symbol.dispose]();
17669
- hosts[hostIndex] = cutResult.value;
17634
+ hosts.splice(hostIndex, 1, ...cutResult.value);
17635
+ const cleanup = cleanupOwnedResources([{
17636
+ resource: host,
17637
+ itemIndex: hostIndex
17638
+ }], { operation: "importOpening" });
17639
+ if (cleanup.kind === "FAILED") diagnostics.push(issue("warning", "VOID_HOST_CLEANUP_FAILED", "Opening result retained, but previous host release failed", expressId, { cleanup }));
17640
+ hostIndex += cutResult.value.length;
17670
17641
  }
17642
+ } catch (_) {
17643
+ _usingCtx$1.e = _;
17671
17644
  } finally {
17672
- opening.solid[Symbol.dispose]();
17645
+ _usingCtx$1.d();
17673
17646
  }
17647
+ return completeImportedGeometry("PARAMETRIC", hosts, expressId, diagnostics);
17648
+ } catch (cause) {
17649
+ const failure = importError("GEOMETRY_RECONSTRUCTION_FAILED", "Opening reconstruction failed", cause);
17650
+ const cleanup = cleanupOwnedResources(hosts.map((resource, itemIndex) => ({
17651
+ resource,
17652
+ itemIndex
17653
+ })), { operation: "reconstructGeometry" });
17654
+ throw Object.assign(new Error(failure.message, { cause }), {
17655
+ code: failure.code,
17656
+ metadata: withCleanup(failure, cleanup, "reconstructGeometry").metadata
17657
+ });
17674
17658
  }
17675
- return completeImportedGeometry("PARAMETRIC", hosts, expressId, diagnostics);
17659
+ }
17660
+ function ownOpening(solid, openingId) {
17661
+ return {
17662
+ solid,
17663
+ [Symbol.dispose]() {
17664
+ const cleanup = cleanupOwnedResources([{
17665
+ resource: solid,
17666
+ itemIndex: 0
17667
+ }], { operation: "importOpeningTool" });
17668
+ if (cleanup.kind === "FAILED") throw new Error(`Opening ${openingId} cleanup failed`, { cause: {
17669
+ ...importError("OPENING_CLEANUP_FAILED", "Temporary opening release failed"),
17670
+ metadata: { cleanup }
17671
+ } });
17672
+ }
17673
+ };
17674
+ }
17675
+ function withCleanup(error, cleanup, operation) {
17676
+ return {
17677
+ ...error,
17678
+ metadata: {
17679
+ ...error.metadata,
17680
+ cleanup: cleanupReport([...reportedGeometryCleanup(error, operation), ...cleanup.kind === "FAILED" ? cleanup.diagnostics : []])
17681
+ }
17682
+ };
17676
17683
  }
17677
17684
  function toImportedGeometry(reader, expressId, scale, diagnostics) {
17678
17685
  const body = readBodyItems(reader, expressId, scale, diagnostics);
@@ -17720,61 +17727,48 @@ function completeImportedGeometry(fidelity, solids, expressId, diagnostics) {
17720
17727
  fidelity,
17721
17728
  completeness: "COMPLETE",
17722
17729
  solids,
17723
- solid: solids.length === 1 ? solids[0] : null,
17730
+ solid: solids.length === 1 ? solids[0] ?? null : null,
17724
17731
  ...measureCompleteBody(solids, expressId, diagnostics)
17725
17732
  };
17726
17733
  }
17727
17734
  function measureCompleteBody(solids, expressId, diagnostics) {
17735
+ const unavailable = (cause, message = errMsg(cause)) => {
17736
+ diagnostics.push(issue("warning", "BODY_AGGREGATE_MEASUREMENT_FAILED", `Complete Body aggregate measurement failed: ${message}`, expressId, { cause }));
17737
+ return {
17738
+ bounds: null,
17739
+ volumeMm3: null
17740
+ };
17741
+ };
17728
17742
  try {
17729
- const first = solids[0];
17743
+ const [first, ...rest] = solids;
17730
17744
  if (first === void 0) return {
17731
17745
  bounds: null,
17732
17746
  volumeMm3: null
17733
17747
  };
17734
- const initial = getBounds(first);
17735
- let xMin = initial.xMin;
17736
- let xMax = initial.xMax;
17737
- let yMin = initial.yMin;
17738
- let yMax = initial.yMax;
17739
- let zMin = initial.zMin;
17740
- let zMax = initial.zMax;
17741
- let volumeMm3 = 0;
17742
- for (const solid of solids) {
17743
- const measured = measureVolume(solid);
17744
- if (!measured.ok) throw new Error(measured.error.message);
17745
- volumeMm3 += measured.value;
17746
- const itemBounds = getBounds(solid);
17747
- xMin = Math.min(xMin, itemBounds.xMin);
17748
- xMax = Math.max(xMax, itemBounds.xMax);
17749
- yMin = Math.min(yMin, itemBounds.yMin);
17750
- yMax = Math.max(yMax, itemBounds.yMax);
17751
- zMin = Math.min(zMin, itemBounds.zMin);
17752
- zMax = Math.max(zMax, itemBounds.zMax);
17753
- }
17748
+ const items = [first, ...rest];
17749
+ const bounds = productBodyBounds(items);
17750
+ if (!bounds.ok) return unavailable(bounds.error, bounds.error.message);
17751
+ const volume = measureProductBodyMaterial(items);
17752
+ if (!volume.ok) return unavailable(volume.error, volume.error.message);
17754
17753
  return {
17755
- bounds: {
17756
- xMin,
17757
- xMax,
17758
- yMin,
17759
- yMax,
17760
- zMin,
17761
- zMax
17762
- },
17763
- volumeMm3
17754
+ bounds: bounds.value.bounds,
17755
+ volumeMm3: volume.value
17764
17756
  };
17765
17757
  } catch (cause) {
17766
- diagnostics.push(issue("warning", "BODY_AGGREGATE_MEASUREMENT_FAILED", `Complete Body aggregate measurement failed: ${errMsg(cause)}`, expressId));
17767
- return {
17768
- bounds: null,
17769
- volumeMm3: null
17770
- };
17758
+ return unavailable(cause);
17771
17759
  }
17772
17760
  }
17773
17761
  function disposeGeometry(geometry) {
17774
- for (const solid of geometry.solids) solid[Symbol.dispose]();
17762
+ return cleanupOwnedResources(geometry.solids.map((resource, itemIndex) => ({
17763
+ resource,
17764
+ itemIndex
17765
+ })), { operation: "disposeImportedGeometry" });
17775
17766
  }
17776
17767
  function disposeElements(elements) {
17777
- for (const element of elements) disposeGeometry(element.geometry);
17768
+ return cleanupOwnedResources(elements.flatMap(({ geometry }) => geometry.solids).map((resource, itemIndex) => ({
17769
+ resource,
17770
+ itemIndex
17771
+ })), { operation: "disposeImportedElements" });
17778
17772
  }
17779
17773
  function toImportedPset(pset) {
17780
17774
  return {