brepjs-bim 0.24.3 → 0.24.5

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
@@ -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
  }
@@ -11691,22 +11716,22 @@ function writeStairAssembly(w, spec, stairKey, assemblyGuid, ownerHistoryId, geo
11691
11716
  const flightExpressIds = [];
11692
11717
  let geometrySimplified = false;
11693
11718
  for (const [i, flight] of spec.flights.entries()) try {
11694
- var _usingCtx$3 = _usingCtx();
11719
+ var _usingCtx$4 = _usingCtx();
11695
11720
  const built = stairFlightToSolid(flight);
11696
11721
  if (!built.ok) return {
11697
11722
  ok: false,
11698
11723
  error: built.error
11699
11724
  };
11700
- const flightSolid = _usingCtx$3.u(built.value.solid);
11725
+ const flightSolid = _usingCtx$4.u(built.value.solid);
11701
11726
  const flightPlacementId = writeLocalPlacement(w, flight.origin, flight.axisZ, flight.axisX, stairPlacementId);
11702
11727
  const tess = writeTessellation(w, flightSolid, geomSubContextId, flightPlacementId);
11703
11728
  if (tess.usedFallback) geometrySimplified = true;
11704
11729
  const flightExpressId = writeStairFlightEntity(w, deriveIfcGuidSync(`elem:STAIR_FLIGHT:${stairKey}:${i}`), `${stairName} Flight ${i + 1}`, flight, ownerHistoryId, flightPlacementId, tess.productDefinitionShapeId);
11705
11730
  flightExpressIds.push(flightExpressId);
11706
11731
  } catch (_) {
11707
- _usingCtx$3.e = _;
11732
+ _usingCtx$4.e = _;
11708
11733
  } finally {
11709
- _usingCtx$3.d();
11734
+ _usingCtx$4.d();
11710
11735
  }
11711
11736
  writeRelAggregates(w, deriveIfcGuidSync(`rel:AGGREGATES:STAIR:${stairKey}`), ownerHistoryId, assemblyExpressId, flightExpressIds);
11712
11737
  writeAssemblyType(w, WebIFC.IFCSTAIRTYPE, "Stair Type", deriveIfcGuidSync(`type:STAIR:${stairKey}`), deriveIfcGuidSync(`rel-type:STAIR:${stairKey}`), predefinedType, ownerHistoryId, [assemblyExpressId]);
@@ -13069,7 +13094,7 @@ function pushWeightQuantity(w, qtyIds, volumeM3, densityKgM3) {
13069
13094
  if (densityKgM3 === void 0) return;
13070
13095
  qtyIds.push(writeWeightQuantity(w, "GrossWeight", volumeM3, densityKgM3));
13071
13096
  }
13072
- function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, openings, densityKgM3) {
13097
+ function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, openings, netVolumeM3, densityKgM3) {
13073
13098
  const lengthM = toIfcLengthM(spec.length);
13074
13099
  const widthM = toIfcLengthM(spec.thickness);
13075
13100
  const heightM = toIfcLengthM(spec.height);
@@ -13085,7 +13110,6 @@ function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, opening
13085
13110
  if (toIfcLengthM(op.offsetFromFloor) < FLOOR_TOUCH_EPSILON_M) sumFloorTouchingFootprintM2 += opWidthM * widthM;
13086
13111
  }
13087
13112
  const netSideAreaM2 = grossSideAreaM2 - sumOpeningAreaM2;
13088
- const netVolumeM3 = grossVolumeM3 - sumOpeningAreaM2 * widthM;
13089
13113
  const netFootprintM2 = grossFootprintM2 - sumFloorTouchingFootprintM2;
13090
13114
  const qtyIds = [
13091
13115
  writeQtyLength(w, "Length", lengthM),
@@ -13101,7 +13125,7 @@ function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, opening
13101
13125
  pushWeightQuantity(w, qtyIds, netVolumeM3, densityKgM3);
13102
13126
  writeRelDefinesByProperties(w, ownerHistoryId, wallExpressId, writeElementQuantity(w, ownerHistoryId, "Qto_WallBaseQuantities", qtyIds));
13103
13127
  }
13104
- function writeExactWallBaseQuantities(w, ownerHistoryId, wallExpressId, values) {
13128
+ function writeWallBodyQuantities(w, ownerHistoryId, wallExpressId, values) {
13105
13129
  writeRelDefinesByProperties(w, ownerHistoryId, wallExpressId, writeElementQuantity(w, ownerHistoryId, "Qto_WallBaseQuantities", [
13106
13130
  writeQtyLength(w, "Length", values.lengthM),
13107
13131
  writeQtyLength(w, "Width", values.widthM),
@@ -15165,10 +15189,25 @@ function preflightExactBody(input) {
15165
15189
  return ok([first.value, ...remainingPrepared]);
15166
15190
  }
15167
15191
  //#endregion
15168
- //#region src/serialize/exactWallQuantities.ts
15169
- function deriveExactWallQuantities(input) {
15192
+ //#region src/serialize/wallQuantities.ts
15193
+ function deriveWallQuantities(input) {
15170
15194
  try {
15171
- const measured = measureProductBodyMaterial(input.solids);
15195
+ const items = [];
15196
+ for (const retained of input.solids) {
15197
+ if (getKernel().shapeType(retained.wrapped) !== "compound") {
15198
+ items.push(retained);
15199
+ continue;
15200
+ }
15201
+ const cardinality = singleWrapperChild(retained);
15202
+ if (!cardinality.ok) return cardinality;
15203
+ const children = getSolids(retained);
15204
+ const [child] = children;
15205
+ if (children.length !== 1 || child === void 0 || !isValidSolid(child)) return wallVolumeError(/* @__PURE__ */ new Error("Expected one valid solid in a retained recipe wrapper"));
15206
+ items.push(child);
15207
+ }
15208
+ const [first, ...rest] = items;
15209
+ if (first === void 0) return wallVolumeError(/* @__PURE__ */ new Error("Expected retained Wall material"));
15210
+ const measured = measureProductBodyMaterial([first, ...rest]);
15172
15211
  if (!measured.ok) return wallVolumeError(measured.error);
15173
15212
  return ok({
15174
15213
  lengthM: toIfcLengthM(input.spec.length),
@@ -15180,8 +15219,24 @@ function deriveExactWallQuantities(input) {
15180
15219
  return wallVolumeError(cause);
15181
15220
  }
15182
15221
  }
15222
+ /** Count before cached extraction: a failed later cast cannot strand earlier children. */
15223
+ function singleWrapperChild(retained) {
15224
+ const kernel = getKernel();
15225
+ let count;
15226
+ if (kernel.subShapeCount !== void 0) count = kernel.subShapeCount(retained.wrapped, "solid");
15227
+ else {
15228
+ const rawChildren = kernel.iterShapes(retained.wrapped, "solid");
15229
+ const cleanup = cleanupOwnedResources(rawChildren.map((raw, itemIndex) => ({
15230
+ resource: { [Symbol.dispose]: () => kernel.dispose(raw) },
15231
+ itemIndex
15232
+ })), { operation: "wallQuantityWrapperCount" });
15233
+ if (cleanup.kind === "FAILED") return wallVolumeError({ cleanup });
15234
+ count = rawChildren.length;
15235
+ }
15236
+ return count === 1 ? ok(void 0) : wallVolumeError(/* @__PURE__ */ new Error("Expected one valid solid in a retained recipe wrapper"));
15237
+ }
15183
15238
  function wallVolumeError(cause) {
15184
- return err(ifcError("IFC_EXACT_WALL_QUANTITY_DERIVATION_FAILED", "Failed to derive a positive finite NetVolume for the retained Wall Body", cause));
15239
+ return err(ifcError("IFC_WALL_QUANTITY_DERIVATION_FAILED", "Failed to derive a positive finite NetVolume for the retained Wall Body", cause));
15185
15240
  }
15186
15241
  //#endregion
15187
15242
  //#region src/serialize/toIfc.ts
@@ -15212,8 +15267,13 @@ function preflightProductBodies(elements) {
15212
15267
  return ok(preflighted);
15213
15268
  }
15214
15269
  async function toIfc(model, meta) {
15270
+ const serialized = await serializeIfc(model, meta);
15271
+ return serialized.ok ? ok(serialized.value.bytes) : serialized;
15272
+ }
15273
+ async function serializeIfc(model, meta) {
15215
15274
  try {
15216
- var _usingCtx$2 = _usingCtx();
15275
+ var _usingCtx$3 = _usingCtx();
15276
+ const issues = [];
15217
15277
  const project = model.getProject();
15218
15278
  if (!project) return err(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
15219
15279
  if ((model.getBridges().length > 0 || model.getBridgeParts().length > 0 || model.getEarthworksFills().length > 0) && (meta.ifcSchema ?? "IFC4") !== "IFC4X3") return err(ifcError("IFC4X3_REQUIRED", "Bridge, Bridge Part, and Earthworks Fill entities require IFC4X3 serialization"));
@@ -15223,7 +15283,7 @@ async function toIfc(model, meta) {
15223
15283
  organization: meta.organizationName
15224
15284
  });
15225
15285
  if (!writerResult.ok) return writerResult;
15226
- const w = _usingCtx$2.u(writerResult.value);
15286
+ const w = _usingCtx$3.u(writerResult.value);
15227
15287
  w.setModelScope(project.guid);
15228
15288
  const elements = model.getAllElements();
15229
15289
  const relationships = model.getAllRelationships();
@@ -15331,10 +15391,10 @@ async function toIfc(model, meta) {
15331
15391
  const wall = preflighted.element;
15332
15392
  const containingId = findContainerOf(wall.localId, relationships);
15333
15393
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
15334
- const exactQuantities = preflighted.kind === "EXACT" ? deriveExactWallQuantities({
15394
+ const bodyQuantities = deriveWallQuantities({
15335
15395
  spec: wall.spec,
15336
- solids: preflighted.solids
15337
- }) : null;
15396
+ solids: bodySolids(wall.geometry)
15397
+ });
15338
15398
  const { localPlacementId, productDefinitionShapeId } = preflighted.kind === "EXACT" ? writeExactBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId) : writeWallGeometry(w, wall.spec, geomSubContextId, storeyPlacementId);
15339
15399
  const wallExpressId = writeWallEntity(w, wall.guid, `Wall ${i + 1}`, ownerHistoryId, localPlacementId, productDefinitionShapeId);
15340
15400
  idMap.set(wall.localId, wallExpressId);
@@ -15342,9 +15402,12 @@ async function toIfc(model, meta) {
15342
15402
  writeWallCommonPset(w, ownerHistoryId, wallExpressId, wall.spec);
15343
15403
  writeManufacturerPset(w, ownerHistoryId, wallExpressId, wall.spec);
15344
15404
  if (wall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, wallExpressId, wall.spec.customProperties);
15345
- if (preflighted.kind === "EXACT") {
15346
- if (exactQuantities !== null && exactQuantities.ok) writeExactWallBaseQuantities(w, ownerHistoryId, wallExpressId, exactQuantities.value);
15347
- } else writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, wall.spec, openingsByWall.get(wall.localId) ?? [], densityByElement.get(wall.localId));
15405
+ if (!bodyQuantities.ok) issues.push(issue("warning", "WALL_QUANTITY_OMITTED", "Wall quantities omitted because occupied material volume could not be measured", wall.localId, {
15406
+ guid: wall.guid,
15407
+ cause: bodyQuantities.error
15408
+ }));
15409
+ else if (wall.geometry.kind === "PARAMETRIC") writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, wall.spec, openingsByWall.get(wall.localId) ?? [], bodyQuantities.value.netVolumeM3, densityByElement.get(wall.localId));
15410
+ else writeWallBodyQuantities(w, ownerHistoryId, wallExpressId, bodyQuantities.value);
15348
15411
  }
15349
15412
  for (const [i, slab] of slabs.entries()) {
15350
15413
  const containingId = findContainerOf(slab.localId, relationships);
@@ -15688,11 +15751,15 @@ async function toIfc(model, meta) {
15688
15751
  for (const { ref, ids } of classByKey.values()) byClassification.set(ref, ids);
15689
15752
  if (byClassification.size > 0) writeClassificationRefs(w, ownerHistoryId, byClassification);
15690
15753
  writeTypeLayer(w, ownerHistoryId, model, idMap);
15691
- return w.save();
15754
+ const saved = w.save();
15755
+ return saved.ok ? ok({
15756
+ bytes: saved.value,
15757
+ issues
15758
+ }) : saved;
15692
15759
  } catch (_) {
15693
- _usingCtx$2.e = _;
15760
+ _usingCtx$3.e = _;
15694
15761
  } finally {
15695
- _usingCtx$2.d();
15762
+ _usingCtx$3.d();
15696
15763
  }
15697
15764
  }
15698
15765
  /**
@@ -15808,15 +15875,16 @@ function toOccurrences(elements, predefined) {
15808
15875
  async function toIfcValidated(model, meta) {
15809
15876
  const integrity = checkReferentialIntegrity(model);
15810
15877
  if (hasErrors(integrity)) return err(ifcError("INTEGRITY_FAILURE", "Model failed referential integrity check", integrity.issues.filter((i) => i.severity === "error")));
15811
- const bytesResult = await toIfc(model, meta);
15812
- if (!bytesResult.ok) return bytesResult;
15813
- const bytes = bytesResult.value;
15878
+ const serialized = await serializeIfc(model, meta);
15879
+ if (!serialized.ok) return serialized;
15880
+ const { bytes } = serialized.value;
15814
15881
  const geometry = collectGeometryIssues(model);
15815
15882
  const schema = await checkSchema(bytes);
15816
15883
  const roundTrip = await checkRoundTrip(bytes);
15817
15884
  const gherkin = await checkGherkinRules(bytes);
15818
15885
  const report = { issues: [
15819
15886
  ...integrity.issues,
15887
+ ...serialized.value.issues,
15820
15888
  ...geometry.issues,
15821
15889
  ...schema.issues,
15822
15890
  ...roundTrip.issues,
@@ -16409,13 +16477,13 @@ function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagno
16409
16477
  ];
16410
16478
  const solidResult = (() => {
16411
16479
  try {
16412
- var _usingCtx$1 = _usingCtx();
16413
- const face = _usingCtx$1.u(faceResult.value);
16480
+ var _usingCtx$2 = _usingCtx();
16481
+ const face = _usingCtx$2.u(faceResult.value);
16414
16482
  return extrude(face, extrudeVec);
16415
16483
  } catch (_) {
16416
- _usingCtx$1.e = _;
16484
+ _usingCtx$2.e = _;
16417
16485
  } finally {
16418
- _usingCtx$1.d();
16486
+ _usingCtx$2.d();
16419
16487
  }
16420
16488
  })();
16421
16489
  if (!solidResult.ok) {
@@ -17137,6 +17205,161 @@ function errMsg$1(e) {
17137
17205
  return e instanceof Error ? e.message : String(e);
17138
17206
  }
17139
17207
  //#endregion
17208
+ //#region src/geometryCleanupDiagnostics.ts
17209
+ var diagnosticSchema = object({
17210
+ operation: string(),
17211
+ itemIndex: number().int().nonnegative(),
17212
+ resourceKind: _enum(["SHAPE", "TRANSFORM"]),
17213
+ localId: number().optional(),
17214
+ cause: unknown()
17215
+ });
17216
+ var failedCleanup = object({
17217
+ kind: literal("FAILED"),
17218
+ diagnostics: array(unknown()).nonempty()
17219
+ });
17220
+ var errorMetadata = object({ cleanup: unknown().optional() });
17221
+ var cleanupAttempt = Symbol("geometryCleanupAttempt");
17222
+ var bodyOperation = _enum(["productBodyBounds", "measureProductBodyMaterial"]);
17223
+ function attemptIdentity(source) {
17224
+ const identity = cleanupAttempt in source ? source[cleanupAttempt] : source;
17225
+ return typeof identity === "object" && identity !== null ? identity : source;
17226
+ }
17227
+ function errorChildren(value) {
17228
+ const children = [];
17229
+ if (value instanceof AggregateError) {
17230
+ const errors = value.errors;
17231
+ children.push(...errors);
17232
+ }
17233
+ if ("cause" in value) children.push(value.cause);
17234
+ if ("error" in value && "suppressed" in value) children.push(value.error, value.suppressed);
17235
+ return children;
17236
+ }
17237
+ /** Keep the actual failed-attempt markers; their causes need not be unique. */
17238
+ function nativeCleanupAttempts(error) {
17239
+ const attempts = [];
17240
+ const seen = /* @__PURE__ */ new Set();
17241
+ const visit = (value) => {
17242
+ if (typeof value !== "object" || value === null || seen.has(value)) return;
17243
+ seen.add(value);
17244
+ if (value instanceof GeometryCleanupError) attempts.push(value);
17245
+ else errorChildren(value).forEach(visit);
17246
+ };
17247
+ visit(error);
17248
+ return attempts;
17249
+ }
17250
+ /** Read prior owner attempts without retrying releases or merging distinct failures. */
17251
+ function reportedGeometryCleanup(error, operation, context = {}) {
17252
+ const diagnostics = [];
17253
+ const seenErrors = /* @__PURE__ */ new Set();
17254
+ const seenAttempts = /* @__PURE__ */ new Set();
17255
+ const add = (diagnostic, identity) => {
17256
+ if (seenAttempts.has(identity)) return;
17257
+ seenAttempts.add(identity);
17258
+ diagnostics.push(Object.freeze({
17259
+ operation: diagnostic.operation,
17260
+ itemIndex: diagnostic.itemIndex,
17261
+ resourceKind: diagnostic.resourceKind,
17262
+ cause: diagnostic.cause,
17263
+ ...diagnostic.localId === void 0 ? {} : { localId: diagnostic.localId },
17264
+ [cleanupAttempt]: identity
17265
+ }));
17266
+ };
17267
+ const visit = (value) => {
17268
+ if (typeof value !== "object" || value === null || seenErrors.has(value)) return;
17269
+ seenErrors.add(value);
17270
+ const metadata = errorMetadata.safeParse("metadata" in value ? value.metadata : void 0);
17271
+ const reports = [metadata.success ? metadata.data.cleanup : void 0, "cleanup" in value ? value.cleanup : void 0];
17272
+ const nativeAttempts = bodyOperation.safeParse("operation" in value ? value.operation : void 0).success ? nativeCleanupAttempts(value) : [];
17273
+ const matched = /* @__PURE__ */ new Set();
17274
+ const seenEntries = /* @__PURE__ */ new Set();
17275
+ for (const report of reports) {
17276
+ const parsed = failedCleanup.safeParse(report);
17277
+ if (!parsed.success) continue;
17278
+ for (const source of parsed.data.diagnostics) {
17279
+ if (typeof source !== "object" || source === null || seenEntries.has(source)) continue;
17280
+ const diagnostic = diagnosticSchema.safeParse(source);
17281
+ if (!diagnostic.success) continue;
17282
+ seenEntries.add(source);
17283
+ let identity = attemptIdentity(source);
17284
+ if (identity === source) {
17285
+ const native = nativeAttempts.find((attempt) => !matched.has(attempt) && attempt.resourceKind === diagnostic.data.resourceKind && attempt.cause === diagnostic.data.cause);
17286
+ if (native !== void 0) identity = native;
17287
+ }
17288
+ if (identity instanceof GeometryCleanupError) matched.add(identity);
17289
+ add(diagnostic.data, identity);
17290
+ }
17291
+ }
17292
+ if (value instanceof GeometryCleanupError) add({
17293
+ operation,
17294
+ itemIndex: context.itemIndex ?? 0,
17295
+ resourceKind: value.resourceKind,
17296
+ cause: value.cause
17297
+ }, value);
17298
+ else errorChildren(value).forEach(visit);
17299
+ };
17300
+ visit(error);
17301
+ return diagnostics;
17302
+ }
17303
+ //#endregion
17304
+ //#region src/import/cutImportedSolids.ts
17305
+ /** Borrow World-placed inputs; own every disconnected result independently of the cut's cache. */
17306
+ function cutImportedSolids(host, opening) {
17307
+ const context = { operation: "cutImportedSolids" };
17308
+ const temporaries = [];
17309
+ const outputs = [];
17310
+ const build = () => {
17311
+ const cutResult = cut(host, opening);
17312
+ if (!cutResult.ok) return err(fromBrepError(cutResult.error, "VOID_CUT_FAILED", "Opening cut failed"));
17313
+ temporaries.push({
17314
+ resource: cutResult.value,
17315
+ itemIndex: 0
17316
+ });
17317
+ const children = getKernel().iterShapes(cutResult.value.wrapped, "solid").map(createSolid);
17318
+ temporaries.push(...children.map((resource, itemIndex) => ({
17319
+ resource,
17320
+ itemIndex
17321
+ })));
17322
+ for (const solid of children) {
17323
+ const copied = clone(solid);
17324
+ if (!copied.ok) return err(fromBrepError(copied.error, "VOID_COPY_FAILED", "Could not retain opening result"));
17325
+ const candidate = copied.value;
17326
+ temporaries.push({
17327
+ resource: candidate,
17328
+ itemIndex: temporaries.length
17329
+ });
17330
+ const validated = validSolid(candidate);
17331
+ if (!validated.ok) return err(importError("VOID_INVALID_SOLID", "Opening result is not valid", validated.error));
17332
+ outputs.push(validated.value);
17333
+ }
17334
+ return ok(outputs);
17335
+ };
17336
+ let result;
17337
+ try {
17338
+ result = build();
17339
+ } catch (cause) {
17340
+ result = err(importError("VOID_CUT_FAILED", "Opening cut failed", cause));
17341
+ }
17342
+ const survivors = new Set(result.ok ? outputs : []);
17343
+ const cleanup = cleanupOwnedResources(temporaries.filter(({ resource }) => !survivors.has(resource)).reverse(), context);
17344
+ const diagnostics = [...result.ok ? [] : reportedGeometryCleanup(result.error, context.operation), ...cleanup.kind === "FAILED" ? cleanup.diagnostics : []];
17345
+ if (result.ok && diagnostics.length === 0) return result;
17346
+ if (result.ok) {
17347
+ const outputCleanup = cleanupOwnedResources(outputs.map((resource, itemIndex) => ({
17348
+ resource,
17349
+ itemIndex
17350
+ })), context);
17351
+ if (outputCleanup.kind === "FAILED") diagnostics.push(...outputCleanup.diagnostics);
17352
+ }
17353
+ const failure = result.ok ? importError("VOID_CLEANUP_FAILED", "Opening result cleanup failed") : result.error;
17354
+ return err({
17355
+ ...failure,
17356
+ metadata: {
17357
+ ...failure.metadata,
17358
+ cleanup: cleanupReport(diagnostics)
17359
+ }
17360
+ });
17361
+ }
17362
+ //#endregion
17140
17363
  //#region src/import/dataRead.ts
17141
17364
  /** Narrows an unknown line attribute to the `{ type, value }` wrapper shape. */
17142
17365
  function asTyped(v) {
@@ -17492,7 +17715,7 @@ var ELEMENT_TYPES = [
17492
17715
  * fatal failures — bad bytes, unsupported schema, WASM open failure — return
17493
17716
  * `err`. Inspect {@link ImportedModel.diagnostics} for per-element quality.
17494
17717
  *
17495
- * The web-ifc model handle is always closed in a `finally` block.
17718
+ * The web-ifc model handle is closed before handing reconstructed owners to the caller.
17496
17719
  */
17497
17720
  async function fromIfc(bytes, options = {}) {
17498
17721
  const settings = { coordinateToOrigin: options.coordinateToOrigin ?? false };
@@ -17500,6 +17723,7 @@ async function fromIfc(bytes, options = {}) {
17500
17723
  if (!readerResult.ok) return err(readerResult.error);
17501
17724
  const reader = readerResult.value;
17502
17725
  const elements = [];
17726
+ let result;
17503
17727
  try {
17504
17728
  reader.buildGuidMap();
17505
17729
  const diagnostics = [];
@@ -17526,13 +17750,23 @@ async function fromIfc(bytes, options = {}) {
17526
17750
  diagnostics: report,
17527
17751
  ...applicationName !== void 0 ? { applicationName } : {}
17528
17752
  };
17529
- return ok(model);
17753
+ result = ok(model);
17530
17754
  } catch (e) {
17531
- disposeElements(elements);
17532
- return err(importError("IMPORT_FAILED", "Unexpected failure during IFC import", e));
17533
- } finally {
17755
+ result = err(importError("IMPORT_FAILED", "Unexpected failure during IFC import", e));
17756
+ }
17757
+ try {
17534
17758
  reader.close();
17759
+ } catch (cause) {
17760
+ result = err(result.ok ? importError("IMPORT_CLOSE_FAILED", "Failed to close IFC reader", cause) : {
17761
+ ...result.error,
17762
+ metadata: {
17763
+ ...result.error.metadata,
17764
+ readerCloseCause: cause
17765
+ }
17766
+ });
17535
17767
  }
17768
+ if (!result.ok) return err(withCleanup(result.error, disposeElements(elements), "fromIfc"));
17769
+ return result;
17536
17770
  }
17537
17771
  /**
17538
17772
  * Reads one product into an {@link ImportedElement}. Returns null only when the
@@ -17583,8 +17817,8 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
17583
17817
  ...fills !== void 0 ? { fills } : {}
17584
17818
  };
17585
17819
  } catch (e) {
17586
- if (geometry !== null) disposeGeometry(geometry);
17587
- diagnostics.push(issue("error", "ELEMENT_READ_FAILED", `Element ${expressId} reconstruction threw: ${errMsg(e)}`, expressId));
17820
+ const failure = geometry === null ? e : withCleanup(importError("ELEMENT_READ_FAILED", "Element read failed", e), disposeGeometry(geometry), "readElement");
17821
+ diagnostics.push(issue("error", "ELEMENT_READ_FAILED", `Element ${expressId} reconstruction threw: ${errMsg(e)}`, expressId, { cause: failure }));
17588
17822
  return null;
17589
17823
  }
17590
17824
  }
@@ -17600,36 +17834,79 @@ function findFills(reader, elementExpressId) {
17600
17834
  }
17601
17835
  /**
17602
17836
  * Reconstructs a host element's body geometry and, per IFC `IfcRelVoidsElement`
17603
- * semantics, subtracts the reconstructed solid of every opening that voids it —
17837
+ * semantics, subtracts each opening's Body representation from a parametric host —
17604
17838
  * so a wall with a door hole comes back as the cut solid, matching the source
17605
17839
  * model. Falls back to the uncut solid (with a diagnostic) if a cut fails.
17606
17840
  */
17607
17841
  function reconstructGeometry(reader, expressId, scale, voidedBy, diagnostics) {
17608
17842
  const base = toImportedGeometry(reader, expressId, scale, diagnostics);
17609
17843
  if (base.fidelity !== "PARAMETRIC" || base.completeness !== "COMPLETE" || voidedBy.length === 0) return base;
17610
- const firstHost = base.solids[0];
17611
- if (firstHost === void 0) return base;
17612
- const hosts = [firstHost, ...base.solids.slice(1)];
17613
- for (const openingId of voidedBy) {
17614
- const opening = readBodyGeometry(reader, openingId, scale, diagnostics);
17615
- if (opening.kind !== "SOLID") continue;
17616
- try {
17617
- for (let hostIndex = 0; hostIndex < hosts.length; hostIndex++) {
17844
+ if (base.solids[0] === void 0) return base;
17845
+ const hosts = [...base.solids];
17846
+ try {
17847
+ for (const openingId of voidedBy) try {
17848
+ var _usingCtx$1 = _usingCtx();
17849
+ const opening = readBodyGeometry(reader, openingId, scale, diagnostics);
17850
+ if (opening.kind !== "SOLID") continue;
17851
+ const openingOwner = _usingCtx$1.u(ownOpening(opening.solid, openingId));
17852
+ for (let hostIndex = 0; hostIndex < hosts.length;) {
17618
17853
  const host = hosts[hostIndex];
17619
- if (host === void 0) continue;
17620
- const cutResult = cut(host, opening.solid);
17854
+ if (host === void 0) break;
17855
+ const cutResult = cutImportedSolids(host, openingOwner.solid);
17621
17856
  if (!cutResult.ok) {
17622
- diagnostics.push(issue("warning", "VOID_SUBTRACTION_FAILED", `Opening ${openingId} could not be subtracted from element ${expressId}: ${cutResult.error.message}`, expressId));
17857
+ diagnostics.push(issue("warning", "VOID_SUBTRACTION_FAILED", `Opening ${openingId} could not be subtracted from element ${expressId}: ${cutResult.error.message}`, expressId, { cause: cutResult.error }));
17858
+ hostIndex++;
17623
17859
  continue;
17624
17860
  }
17625
- host[Symbol.dispose]();
17626
- hosts[hostIndex] = cutResult.value;
17861
+ hosts.splice(hostIndex, 1, ...cutResult.value);
17862
+ const cleanup = cleanupOwnedResources([{
17863
+ resource: host,
17864
+ itemIndex: hostIndex
17865
+ }], { operation: "importOpening" });
17866
+ if (cleanup.kind === "FAILED") diagnostics.push(issue("warning", "VOID_HOST_CLEANUP_FAILED", "Opening result retained, but previous host release failed", expressId, { cleanup }));
17867
+ hostIndex += cutResult.value.length;
17627
17868
  }
17869
+ } catch (_) {
17870
+ _usingCtx$1.e = _;
17628
17871
  } finally {
17629
- opening.solid[Symbol.dispose]();
17872
+ _usingCtx$1.d();
17630
17873
  }
17874
+ return completeImportedGeometry("PARAMETRIC", hosts, expressId, diagnostics);
17875
+ } catch (cause) {
17876
+ const failure = importError("GEOMETRY_RECONSTRUCTION_FAILED", "Opening reconstruction failed", cause);
17877
+ const cleanup = cleanupOwnedResources(hosts.map((resource, itemIndex) => ({
17878
+ resource,
17879
+ itemIndex
17880
+ })), { operation: "reconstructGeometry" });
17881
+ throw Object.assign(new Error(failure.message, { cause }), {
17882
+ code: failure.code,
17883
+ metadata: withCleanup(failure, cleanup, "reconstructGeometry").metadata
17884
+ });
17631
17885
  }
17632
- return completeImportedGeometry("PARAMETRIC", hosts, expressId, diagnostics);
17886
+ }
17887
+ function ownOpening(solid, openingId) {
17888
+ return {
17889
+ solid,
17890
+ [Symbol.dispose]() {
17891
+ const cleanup = cleanupOwnedResources([{
17892
+ resource: solid,
17893
+ itemIndex: 0
17894
+ }], { operation: "importOpeningTool" });
17895
+ if (cleanup.kind === "FAILED") throw new Error(`Opening ${openingId} cleanup failed`, { cause: {
17896
+ ...importError("OPENING_CLEANUP_FAILED", "Temporary opening release failed"),
17897
+ metadata: { cleanup }
17898
+ } });
17899
+ }
17900
+ };
17901
+ }
17902
+ function withCleanup(error, cleanup, operation) {
17903
+ return {
17904
+ ...error,
17905
+ metadata: {
17906
+ ...error.metadata,
17907
+ cleanup: cleanupReport([...reportedGeometryCleanup(error, operation), ...cleanup.kind === "FAILED" ? cleanup.diagnostics : []])
17908
+ }
17909
+ };
17633
17910
  }
17634
17911
  function toImportedGeometry(reader, expressId, scale, diagnostics) {
17635
17912
  const body = readBodyItems(reader, expressId, scale, diagnostics);
@@ -17677,61 +17954,48 @@ function completeImportedGeometry(fidelity, solids, expressId, diagnostics) {
17677
17954
  fidelity,
17678
17955
  completeness: "COMPLETE",
17679
17956
  solids,
17680
- solid: solids.length === 1 ? solids[0] : null,
17957
+ solid: solids.length === 1 ? solids[0] ?? null : null,
17681
17958
  ...measureCompleteBody(solids, expressId, diagnostics)
17682
17959
  };
17683
17960
  }
17684
17961
  function measureCompleteBody(solids, expressId, diagnostics) {
17962
+ const unavailable = (cause, message = errMsg(cause)) => {
17963
+ diagnostics.push(issue("warning", "BODY_AGGREGATE_MEASUREMENT_FAILED", `Complete Body aggregate measurement failed: ${message}`, expressId, { cause }));
17964
+ return {
17965
+ bounds: null,
17966
+ volumeMm3: null
17967
+ };
17968
+ };
17685
17969
  try {
17686
- const first = solids[0];
17970
+ const [first, ...rest] = solids;
17687
17971
  if (first === void 0) return {
17688
17972
  bounds: null,
17689
17973
  volumeMm3: null
17690
17974
  };
17691
- const initial = getBounds(first);
17692
- let xMin = initial.xMin;
17693
- let xMax = initial.xMax;
17694
- let yMin = initial.yMin;
17695
- let yMax = initial.yMax;
17696
- let zMin = initial.zMin;
17697
- let zMax = initial.zMax;
17698
- let volumeMm3 = 0;
17699
- for (const solid of solids) {
17700
- const measured = measureVolume(solid);
17701
- if (!measured.ok) throw new Error(measured.error.message);
17702
- volumeMm3 += measured.value;
17703
- const itemBounds = getBounds(solid);
17704
- xMin = Math.min(xMin, itemBounds.xMin);
17705
- xMax = Math.max(xMax, itemBounds.xMax);
17706
- yMin = Math.min(yMin, itemBounds.yMin);
17707
- yMax = Math.max(yMax, itemBounds.yMax);
17708
- zMin = Math.min(zMin, itemBounds.zMin);
17709
- zMax = Math.max(zMax, itemBounds.zMax);
17710
- }
17975
+ const items = [first, ...rest];
17976
+ const bounds = productBodyBounds(items);
17977
+ if (!bounds.ok) return unavailable(bounds.error, bounds.error.message);
17978
+ const volume = measureProductBodyMaterial(items);
17979
+ if (!volume.ok) return unavailable(volume.error, volume.error.message);
17711
17980
  return {
17712
- bounds: {
17713
- xMin,
17714
- xMax,
17715
- yMin,
17716
- yMax,
17717
- zMin,
17718
- zMax
17719
- },
17720
- volumeMm3
17981
+ bounds: bounds.value.bounds,
17982
+ volumeMm3: volume.value
17721
17983
  };
17722
17984
  } catch (cause) {
17723
- diagnostics.push(issue("warning", "BODY_AGGREGATE_MEASUREMENT_FAILED", `Complete Body aggregate measurement failed: ${errMsg(cause)}`, expressId));
17724
- return {
17725
- bounds: null,
17726
- volumeMm3: null
17727
- };
17985
+ return unavailable(cause);
17728
17986
  }
17729
17987
  }
17730
17988
  function disposeGeometry(geometry) {
17731
- for (const solid of geometry.solids) solid[Symbol.dispose]();
17989
+ return cleanupOwnedResources(geometry.solids.map((resource, itemIndex) => ({
17990
+ resource,
17991
+ itemIndex
17992
+ })), { operation: "disposeImportedGeometry" });
17732
17993
  }
17733
17994
  function disposeElements(elements) {
17734
- for (const element of elements) disposeGeometry(element.geometry);
17995
+ return cleanupOwnedResources(elements.flatMap(({ geometry }) => geometry.solids).map((resource, itemIndex) => ({
17996
+ resource,
17997
+ itemIndex
17998
+ })), { operation: "disposeImportedElements" });
17735
17999
  }
17736
18000
  function toImportedPset(pset) {
17737
18001
  return {