brepjs-bim 0.23.2 → 0.24.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.
@@ -134,12 +134,13 @@ function specError(code, message, cause) {
134
134
  cause
135
135
  };
136
136
  }
137
- function ifcError(code, message, cause) {
137
+ function ifcError(code, message, cause, metadata) {
138
138
  return {
139
139
  kind: "BIM_IFC",
140
140
  code,
141
141
  message,
142
- cause
142
+ cause,
143
+ ...metadata !== void 0 ? { metadata } : {}
143
144
  };
144
145
  }
145
146
  function geometryError(code, message, cause) {
@@ -265,7 +266,7 @@ function _usingCtx() {
265
266
  //#region src/elementFns/wallFns.ts
266
267
  function wallToSolid(spec) {
267
268
  try {
268
- var _usingCtx$19 = _usingCtx();
269
+ var _usingCtx$20 = _usingCtx();
269
270
  if (spec.length <= 0) return (0, brepjs.err)(specError("WALL_ZERO_LENGTH", "Wall length must be positive"));
270
271
  if (spec.height <= 0) return (0, brepjs.err)(specError("WALL_ZERO_HEIGHT", "Wall height must be positive"));
271
272
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("WALL_ZERO_THICKNESS", "Wall thickness must be positive"));
@@ -293,7 +294,7 @@ function wallToSolid(spec) {
293
294
  ]
294
295
  ]);
295
296
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "WALL_PROFILE_FAILED", "Failed to create wall profile"));
296
- const profile = _usingCtx$19.u(profileResult.value);
297
+ const profile = _usingCtx$20.u(profileResult.value);
297
298
  const solidResult = (0, brepjs.extrude)(profile, [
298
299
  length,
299
300
  0,
@@ -307,16 +308,16 @@ function wallToSolid(spec) {
307
308
  }
308
309
  return (0, brepjs.ok)(solid);
309
310
  } catch (_) {
310
- _usingCtx$19.e = _;
311
+ _usingCtx$20.e = _;
311
312
  } finally {
312
- _usingCtx$19.d();
313
+ _usingCtx$20.d();
313
314
  }
314
315
  }
315
316
  //#endregion
316
317
  //#region src/elementFns/slabFns.ts
317
318
  function slabToSolid(spec) {
318
319
  try {
319
- var _usingCtx$18 = _usingCtx();
320
+ var _usingCtx$19 = _usingCtx();
320
321
  if (spec.length <= 0) return (0, brepjs.err)(specError("SLAB_ZERO_LENGTH", "Slab length must be positive"));
321
322
  if (spec.width <= 0) return (0, brepjs.err)(specError("SLAB_ZERO_WIDTH", "Slab width must be positive"));
322
323
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("SLAB_ZERO_THICKNESS", "Slab thickness must be positive"));
@@ -344,7 +345,7 @@ function slabToSolid(spec) {
344
345
  ]
345
346
  ]);
346
347
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "SLAB_PROFILE_FAILED", "Failed to create slab profile"));
347
- const profile = _usingCtx$18.u(profileResult.value);
348
+ const profile = _usingCtx$19.u(profileResult.value);
348
349
  const solidResult = (0, brepjs.extrude)(profile, [
349
350
  0,
350
351
  0,
@@ -358,9 +359,9 @@ function slabToSolid(spec) {
358
359
  }
359
360
  return (0, brepjs.ok)(solid);
360
361
  } catch (_) {
361
- _usingCtx$18.e = _;
362
+ _usingCtx$19.e = _;
362
363
  } finally {
363
- _usingCtx$18.d();
364
+ _usingCtx$19.d();
364
365
  }
365
366
  }
366
367
  //#endregion
@@ -5048,14 +5049,14 @@ function to3D(points) {
5048
5049
  }
5049
5050
  function extendedProfileToFace(profile) {
5050
5051
  try {
5051
- var _usingCtx$17 = _usingCtx();
5052
+ var _usingCtx$18 = _usingCtx();
5052
5053
  const invalid = validateProfile(profile);
5053
5054
  if (invalid !== null) return (0, brepjs.err)(invalid);
5054
5055
  const outerResult = (0, brepjs.polygon)(to3D(outerLoop(profile)));
5055
5056
  if (!outerResult.ok) return (0, brepjs.err)(fromBrepError(outerResult.error, "PROFILE_FACE_FAILED", "Failed to build profile outer face"));
5056
5057
  const holes = holeLoops(profile);
5057
5058
  if (holes.length === 0) return (0, brepjs.ok)(outerResult.value);
5058
- const outerFace = _usingCtx$17.u(outerResult.value);
5059
+ const outerFace = _usingCtx$18.u(outerResult.value);
5059
5060
  const holeFaces = [];
5060
5061
  const holeWires = [];
5061
5062
  const disposeHoleFaces = () => {
@@ -5080,9 +5081,9 @@ function extendedProfileToFace(profile) {
5080
5081
  disposeHoleFaces();
5081
5082
  return (0, brepjs.ok)(faceWithHoles);
5082
5083
  } catch (_) {
5083
- _usingCtx$17.e = _;
5084
+ _usingCtx$18.e = _;
5084
5085
  } finally {
5085
- _usingCtx$17.d();
5086
+ _usingCtx$18.d();
5086
5087
  }
5087
5088
  }
5088
5089
  //#endregion
@@ -5404,7 +5405,7 @@ function profileToPolygon(profile, circleSegments = 32) {
5404
5405
  //#region src/elementFns/beamFns.ts
5405
5406
  function beamToSolid(spec) {
5406
5407
  try {
5407
- var _usingCtx$16 = _usingCtx();
5408
+ var _usingCtx$17 = _usingCtx();
5408
5409
  if (spec.length <= 0) return (0, brepjs.err)(specError("BEAM_ZERO_LENGTH", "Beam length must be positive"));
5409
5410
  if (isExtendedProfile(spec.profile)) try {
5410
5411
  var _usingCtx3 = _usingCtx();
@@ -5442,7 +5443,7 @@ function beamToSolid(spec) {
5442
5443
  ]);
5443
5444
  const profileResult = (0, brepjs.polygon)(profilePts);
5444
5445
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "BEAM_PROFILE_FAILED", "Failed to create beam profile"));
5445
- const profile = _usingCtx$16.u(profileResult.value);
5446
+ const profile = _usingCtx$17.u(profileResult.value);
5446
5447
  const solidResult = (0, brepjs.extrude)(profile, [
5447
5448
  spec.length,
5448
5449
  0,
@@ -5456,16 +5457,16 @@ function beamToSolid(spec) {
5456
5457
  }
5457
5458
  return (0, brepjs.ok)(solid);
5458
5459
  } catch (_) {
5459
- _usingCtx$16.e = _;
5460
+ _usingCtx$17.e = _;
5460
5461
  } finally {
5461
- _usingCtx$16.d();
5462
+ _usingCtx$17.d();
5462
5463
  }
5463
5464
  }
5464
5465
  //#endregion
5465
5466
  //#region src/elementFns/columnFns.ts
5466
5467
  function columnToSolid(spec) {
5467
5468
  try {
5468
- var _usingCtx$15 = _usingCtx();
5469
+ var _usingCtx$16 = _usingCtx();
5469
5470
  if (spec.height <= 0) return (0, brepjs.err)(specError("COLUMN_ZERO_HEIGHT", "Column height must be positive"));
5470
5471
  if (isExtendedProfile(spec.profile)) try {
5471
5472
  var _usingCtx3 = _usingCtx();
@@ -5494,7 +5495,7 @@ function columnToSolid(spec) {
5494
5495
  const profilePts = profilePtsResult.value;
5495
5496
  const profileResult = (0, brepjs.polygon)(profilePts);
5496
5497
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "COLUMN_PROFILE_FAILED", "Failed to create column profile"));
5497
- const profile = _usingCtx$15.u(profileResult.value);
5498
+ const profile = _usingCtx$16.u(profileResult.value);
5498
5499
  const solidResult = (0, brepjs.extrude)(profile, [
5499
5500
  0,
5500
5501
  0,
@@ -5508,9 +5509,9 @@ function columnToSolid(spec) {
5508
5509
  }
5509
5510
  return (0, brepjs.ok)(solid);
5510
5511
  } catch (_) {
5511
- _usingCtx$15.e = _;
5512
+ _usingCtx$16.e = _;
5512
5513
  } finally {
5513
- _usingCtx$15.d();
5514
+ _usingCtx$16.d();
5514
5515
  }
5515
5516
  }
5516
5517
  //#endregion
@@ -5518,7 +5519,7 @@ function columnToSolid(spec) {
5518
5519
  var EPSILON_MM = 1;
5519
5520
  function openingToSolid(spec, wallThickness) {
5520
5521
  try {
5521
- var _usingCtx$14 = _usingCtx();
5522
+ var _usingCtx$15 = _usingCtx();
5522
5523
  if (spec.width <= 0) return (0, brepjs.err)(specError("OPENING_ZERO_WIDTH", "Opening width must be positive"));
5523
5524
  if (spec.height <= 0) return (0, brepjs.err)(specError("OPENING_ZERO_HEIGHT", "Opening height must be positive"));
5524
5525
  if (wallThickness <= 0) return (0, brepjs.err)(specError("OPENING_ZERO_WALL_THICKNESS", "Wall thickness must be positive"));
@@ -5550,7 +5551,7 @@ function openingToSolid(spec, wallThickness) {
5550
5551
  ]
5551
5552
  ]);
5552
5553
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "OPENING_PROFILE_FAILED", "Failed to create opening profile"));
5553
- const profile = _usingCtx$14.u(profileResult.value);
5554
+ const profile = _usingCtx$15.u(profileResult.value);
5554
5555
  const solidResult = (0, brepjs.extrude)(profile, [
5555
5556
  spec.width,
5556
5557
  0,
@@ -5564,16 +5565,16 @@ function openingToSolid(spec, wallThickness) {
5564
5565
  }
5565
5566
  return (0, brepjs.ok)(solid);
5566
5567
  } catch (_) {
5567
- _usingCtx$14.e = _;
5568
+ _usingCtx$15.e = _;
5568
5569
  } finally {
5569
- _usingCtx$14.d();
5570
+ _usingCtx$15.d();
5570
5571
  }
5571
5572
  }
5572
5573
  //#endregion
5573
5574
  //#region src/elementFns/slabOpeningFns.ts
5574
5575
  function slabOpeningToSolid(spec, slabThickness) {
5575
5576
  try {
5576
- var _usingCtx$13 = _usingCtx();
5577
+ var _usingCtx$14 = _usingCtx();
5577
5578
  if (spec.sizeX <= 0) return (0, brepjs.err)(specError("SLAB_OPENING_ZERO_SIZE_X", "Slab opening sizeX must be positive"));
5578
5579
  if (spec.sizeY <= 0) return (0, brepjs.err)(specError("SLAB_OPENING_ZERO_SIZE_Y", "Slab opening sizeY must be positive"));
5579
5580
  if (slabThickness <= 0) return (0, brepjs.err)(specError("SLAB_OPENING_ZERO_SLAB_THICKNESS", "Slab thickness must be positive"));
@@ -5605,7 +5606,7 @@ function slabOpeningToSolid(spec, slabThickness) {
5605
5606
  ]
5606
5607
  ]);
5607
5608
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "SLAB_OPENING_PROFILE_FAILED", "Failed to create slab opening profile"));
5608
- const profile = _usingCtx$13.u(profileResult.value);
5609
+ const profile = _usingCtx$14.u(profileResult.value);
5609
5610
  const solidResult = (0, brepjs.extrude)(profile, [
5610
5611
  0,
5611
5612
  0,
@@ -5619,16 +5620,16 @@ function slabOpeningToSolid(spec, slabThickness) {
5619
5620
  }
5620
5621
  return (0, brepjs.ok)(solid);
5621
5622
  } catch (_) {
5622
- _usingCtx$13.e = _;
5623
+ _usingCtx$14.e = _;
5623
5624
  } finally {
5624
- _usingCtx$13.d();
5625
+ _usingCtx$14.d();
5625
5626
  }
5626
5627
  }
5627
5628
  //#endregion
5628
5629
  //#region src/elementFns/spaceFns.ts
5629
5630
  function spaceToSolid(spec) {
5630
5631
  try {
5631
- var _usingCtx$12 = _usingCtx();
5632
+ var _usingCtx$13 = _usingCtx();
5632
5633
  if (spec.length <= 0) return (0, brepjs.err)(specError("SPACE_ZERO_LENGTH", "Space length must be positive"));
5633
5634
  if (spec.width <= 0) return (0, brepjs.err)(specError("SPACE_ZERO_WIDTH", "Space width must be positive"));
5634
5635
  if (spec.height <= 0) return (0, brepjs.err)(specError("SPACE_ZERO_HEIGHT", "Space height must be positive"));
@@ -5656,7 +5657,7 @@ function spaceToSolid(spec) {
5656
5657
  ]
5657
5658
  ]);
5658
5659
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "SPACE_PROFILE_FAILED", "Failed to create space footprint"));
5659
- const profile = _usingCtx$12.u(profileResult.value);
5660
+ const profile = _usingCtx$13.u(profileResult.value);
5660
5661
  const solidResult = (0, brepjs.extrude)(profile, [
5661
5662
  0,
5662
5663
  0,
@@ -5670,9 +5671,9 @@ function spaceToSolid(spec) {
5670
5671
  }
5671
5672
  return (0, brepjs.ok)(solid);
5672
5673
  } catch (_) {
5673
- _usingCtx$12.e = _;
5674
+ _usingCtx$13.e = _;
5674
5675
  } finally {
5675
- _usingCtx$12.d();
5676
+ _usingCtx$13.d();
5676
5677
  }
5677
5678
  }
5678
5679
  //#endregion
@@ -5687,7 +5688,7 @@ function gate(solid, code) {
5687
5688
  }
5688
5689
  function flatRoof(spec) {
5689
5690
  try {
5690
- var _usingCtx$11 = _usingCtx();
5691
+ var _usingCtx$12 = _usingCtx();
5691
5692
  const { length, width, thickness } = spec;
5692
5693
  const face = (0, brepjs.polygon)([
5693
5694
  [
@@ -5712,7 +5713,7 @@ function flatRoof(spec) {
5712
5713
  ]
5713
5714
  ]);
5714
5715
  if (!face.ok) return (0, brepjs.err)(fromBrepError(face.error, "ROOF_PROFILE_FAILED", "Failed to create roof profile"));
5715
- const profile = _usingCtx$11.u(face.value);
5716
+ const profile = _usingCtx$12.u(face.value);
5716
5717
  const solid = (0, brepjs.extrude)(profile, [
5717
5718
  0,
5718
5719
  0,
@@ -5721,9 +5722,9 @@ function flatRoof(spec) {
5721
5722
  if (!solid.ok) return (0, brepjs.err)(fromBrepError(solid.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude roof profile"));
5722
5723
  return gate(solid.value, "ROOF_INVALID_SOLID");
5723
5724
  } catch (_) {
5724
- _usingCtx$11.e = _;
5725
+ _usingCtx$12.e = _;
5725
5726
  } finally {
5726
- _usingCtx$11.d();
5727
+ _usingCtx$12.d();
5727
5728
  }
5728
5729
  }
5729
5730
  function shedRoof(spec, pitch) {
@@ -5919,7 +5920,7 @@ function roofToSolid(spec) {
5919
5920
  //#region src/elementFns/curtainWallFns.ts
5920
5921
  function boxSolid(sizeX, sizeY, sizeZ) {
5921
5922
  try {
5922
- var _usingCtx$10 = _usingCtx();
5923
+ var _usingCtx$11 = _usingCtx();
5923
5924
  const profileResult = (0, brepjs.polygon)([
5924
5925
  [
5925
5926
  0,
@@ -5943,7 +5944,7 @@ function boxSolid(sizeX, sizeY, sizeZ) {
5943
5944
  ]
5944
5945
  ]);
5945
5946
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "CURTAIN_WALL_PROFILE_FAILED", "Failed to create component profile"));
5946
- const profile = _usingCtx$10.u(profileResult.value);
5947
+ const profile = _usingCtx$11.u(profileResult.value);
5947
5948
  const solidResult = (0, brepjs.extrude)(profile, [
5948
5949
  0,
5949
5950
  0,
@@ -5957,9 +5958,9 @@ function boxSolid(sizeX, sizeY, sizeZ) {
5957
5958
  }
5958
5959
  return (0, brepjs.ok)(solid);
5959
5960
  } catch (_) {
5960
- _usingCtx$10.e = _;
5961
+ _usingCtx$11.e = _;
5961
5962
  } finally {
5962
- _usingCtx$10.d();
5963
+ _usingCtx$11.d();
5963
5964
  }
5964
5965
  }
5965
5966
  function disposeComponents(components) {
@@ -6058,7 +6059,7 @@ function curtainWallToGrid(spec) {
6058
6059
  //#region src/elementFns/foundationFns.ts
6059
6060
  function footingToSolid(spec) {
6060
6061
  try {
6061
- var _usingCtx$9 = _usingCtx();
6062
+ var _usingCtx$10 = _usingCtx();
6062
6063
  if (spec.length <= 0) return (0, brepjs.err)(specError("FOOTING_ZERO_LENGTH", "Footing length must be positive"));
6063
6064
  if (spec.width <= 0) return (0, brepjs.err)(specError("FOOTING_ZERO_WIDTH", "Footing width must be positive"));
6064
6065
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("FOOTING_ZERO_THICKNESS", "Footing thickness must be positive"));
@@ -6086,7 +6087,7 @@ function footingToSolid(spec) {
6086
6087
  ]
6087
6088
  ]);
6088
6089
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "FOOTING_PROFILE_FAILED", "Failed to create footing profile"));
6089
- const profile = _usingCtx$9.u(profileResult.value);
6090
+ const profile = _usingCtx$10.u(profileResult.value);
6090
6091
  const solidResult = (0, brepjs.extrude)(profile, [
6091
6092
  0,
6092
6093
  0,
@@ -6100,9 +6101,9 @@ function footingToSolid(spec) {
6100
6101
  }
6101
6102
  return (0, brepjs.ok)(solid);
6102
6103
  } catch (_) {
6103
- _usingCtx$9.e = _;
6104
+ _usingCtx$10.e = _;
6104
6105
  } finally {
6105
- _usingCtx$9.d();
6106
+ _usingCtx$10.d();
6106
6107
  }
6107
6108
  }
6108
6109
  function pileToSolid(spec) {
@@ -6159,7 +6160,7 @@ function pileToSolid(spec) {
6159
6160
  //#region src/elementFns/railingFns.ts
6160
6161
  function panelRailing(spec) {
6161
6162
  try {
6162
- var _usingCtx$8 = _usingCtx();
6163
+ var _usingCtx$9 = _usingCtx();
6163
6164
  const { length, height, thickness } = spec;
6164
6165
  const profileResult = (0, brepjs.polygon)([
6165
6166
  [
@@ -6184,7 +6185,7 @@ function panelRailing(spec) {
6184
6185
  ]
6185
6186
  ]);
6186
6187
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "RAILING_PROFILE_FAILED", "Failed to create railing profile"));
6187
- const profile = _usingCtx$8.u(profileResult.value);
6188
+ const profile = _usingCtx$9.u(profileResult.value);
6188
6189
  const solidResult = (0, brepjs.extrude)(profile, [
6189
6190
  length,
6190
6191
  0,
@@ -6198,9 +6199,9 @@ function panelRailing(spec) {
6198
6199
  }
6199
6200
  return (0, brepjs.ok)(solid);
6200
6201
  } catch (_) {
6201
- _usingCtx$8.e = _;
6202
+ _usingCtx$9.e = _;
6202
6203
  } finally {
6203
- _usingCtx$8.d();
6204
+ _usingCtx$9.d();
6204
6205
  }
6205
6206
  }
6206
6207
  function postedRailing(spec) {
@@ -6263,7 +6264,7 @@ function railingToSolid(spec) {
6263
6264
  //#region src/elementFns/coveringFns.ts
6264
6265
  function coveringToSolid(spec) {
6265
6266
  try {
6266
- var _usingCtx$7 = _usingCtx();
6267
+ var _usingCtx$8 = _usingCtx();
6267
6268
  if (spec.length <= 0) return (0, brepjs.err)(specError("COVERING_ZERO_LENGTH", "Covering length must be positive"));
6268
6269
  if (spec.width <= 0) return (0, brepjs.err)(specError("COVERING_ZERO_WIDTH", "Covering width must be positive"));
6269
6270
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("COVERING_ZERO_THICKNESS", "Covering thickness must be positive"));
@@ -6291,7 +6292,7 @@ function coveringToSolid(spec) {
6291
6292
  ]
6292
6293
  ]);
6293
6294
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "COVERING_PROFILE_FAILED", "Failed to create covering profile"));
6294
- const profile = _usingCtx$7.u(profileResult.value);
6295
+ const profile = _usingCtx$8.u(profileResult.value);
6295
6296
  const solidResult = (0, brepjs.extrude)(profile, [
6296
6297
  0,
6297
6298
  0,
@@ -6305,13 +6306,29 @@ function coveringToSolid(spec) {
6305
6306
  }
6306
6307
  return (0, brepjs.ok)(solid);
6307
6308
  } catch (_) {
6308
- _usingCtx$7.e = _;
6309
+ _usingCtx$8.e = _;
6309
6310
  } finally {
6310
- _usingCtx$7.d();
6311
+ _usingCtx$8.d();
6312
+ }
6313
+ }
6314
+ //#endregion
6315
+ //#region src/types/productBody.ts
6316
+ /** Returns borrowed Product-local solids. The model retains ownership. */
6317
+ function bodySolids(body) {
6318
+ switch (body.kind) {
6319
+ case "PARAMETRIC": return [body.solid];
6320
+ case "EXACT": return body.solids;
6311
6321
  }
6312
6322
  }
6323
+ /** Model-owner cleanup. Borrowers must use {@link bodySolids} without disposing its items. */
6324
+ function disposeProductBody(body) {
6325
+ for (const solid of bodySolids(body)) solid[Symbol.dispose]();
6326
+ }
6313
6327
  //#endregion
6314
6328
  //#region src/model/bimModel.ts
6329
+ function exactWallBodyImmutable() {
6330
+ return (0, brepjs.err)(specError("EXACT_WALL_BODY_IMMUTABLE", "Cannot add an opening after a wall has taken an exact Product Body"));
6331
+ }
6315
6332
  var BimModel = class {
6316
6333
  #elements = /* @__PURE__ */ new Map();
6317
6334
  #relationships = /* @__PURE__ */ new Map();
@@ -6330,7 +6347,8 @@ var BimModel = class {
6330
6347
  return (0, brepjs.ok)(id);
6331
6348
  }
6332
6349
  [Symbol.dispose]() {
6333
- for (const el of this.#elements.values()) if (el.category === "WALL" || el.category === "SLAB" || el.category === "BEAM" || el.category === "COLUMN" || el.category === "PROXY" || el.category === "EARTHWORKS_FILL" || el.category === "SPACE" || el.category === "ROOF" || el.category === "FOOTING" || el.category === "PILE" || el.category === "RAILING" || el.category === "COVERING") el.geometry[Symbol.dispose]();
6350
+ for (const el of this.#elements.values()) if (el.category === "SLAB" || el.category === "BEAM" || el.category === "COLUMN" || el.category === "PROXY" || el.category === "EARTHWORKS_FILL" || el.category === "SPACE" || el.category === "ROOF" || el.category === "FOOTING" || el.category === "PILE" || el.category === "COVERING") el.geometry[Symbol.dispose]();
6351
+ else if (el.category === "WALL" || el.category === "RAILING") disposeProductBody(el.geometry);
6334
6352
  else if (el.category === "CURTAIN_WALL") {
6335
6353
  for (const panel of el.geometry.panels) panel.solid[Symbol.dispose]();
6336
6354
  for (const mullion of el.geometry.mullions) mullion.solid[Symbol.dispose]();
@@ -6391,7 +6409,10 @@ var BimModel = class {
6391
6409
  if (!keyCheck.ok) return keyCheck;
6392
6410
  const geomResult = wallToSolid(spec);
6393
6411
  if (!geomResult.ok) return (0, brepjs.err)(geomResult.error);
6394
- const id = this.#makeElement("WALL", spec, geomResult.value, options?.stableKey);
6412
+ const id = this.#makeElement("WALL", spec, {
6413
+ kind: "PARAMETRIC",
6414
+ solid: geomResult.value
6415
+ }, options?.stableKey);
6395
6416
  this.#associateMaterial(id, spec);
6396
6417
  this.#associateClassification(id, spec);
6397
6418
  return (0, brepjs.ok)(id);
@@ -6506,12 +6527,47 @@ var BimModel = class {
6506
6527
  if (!keyCheck.ok) return keyCheck;
6507
6528
  const geomResult = railingToSolid(spec);
6508
6529
  if (!geomResult.ok) return (0, brepjs.err)(geomResult.error);
6509
- const id = this.#makeElement("RAILING", spec, geomResult.value, options?.stableKey);
6530
+ const id = this.#makeElement("RAILING", spec, {
6531
+ kind: "PARAMETRIC",
6532
+ solid: geomResult.value
6533
+ }, options?.stableKey);
6510
6534
  this.#associateMaterial(id, spec);
6511
6535
  this.#associateClassification(id, spec);
6512
6536
  return (0, brepjs.ok)(id);
6513
6537
  }
6514
6538
  /**
6539
+ * Atomically replaces a parametric wall or railing Body with authoritative,
6540
+ * caller-owned exact solids. Success transfers every supplied handle to this
6541
+ * model. Failure leaves both the model and all supplied handles unchanged.
6542
+ */
6543
+ takeExactProductBody(localId, body) {
6544
+ const target = this.#elements.get(localId);
6545
+ if (target === void 0) return (0, brepjs.err)(specError("EXACT_BODY_TARGET_NOT_FOUND", `No element found for localId ${localId}`));
6546
+ if (target.category !== "WALL" && target.category !== "RAILING") return (0, brepjs.err)(specError("EXACT_BODY_UNSUPPORTED_CATEGORY", `Exact Product Bodies are supported only for walls and railings, not ${target.category}`));
6547
+ if (target.geometry.kind === "EXACT") return (0, brepjs.err)(specError("EXACT_BODY_ALREADY_EXACT", `Element ${localId} already has an exact Product Body`));
6548
+ const solids = body.solids;
6549
+ if (solids.length === 0) return (0, brepjs.err)(specError("EXACT_BODY_EMPTY", "An exact Product Body must contain at least one solid"));
6550
+ if (solids.includes(target.geometry.solid)) return (0, brepjs.err)(specError("EXACT_BODY_SOLID_OWNERSHIP_CONFLICT", `Exact Product Body for ${localId} reuses the target parametric solid`));
6551
+ const identities = /* @__PURE__ */ new Set();
6552
+ for (const [itemIndex, solid] of solids.entries()) {
6553
+ if (identities.has(solid)) return (0, brepjs.err)(specError("EXACT_BODY_DUPLICATE_SOLID", `Exact Product Body item ${itemIndex} duplicates an earlier handle`));
6554
+ identities.add(solid);
6555
+ if (solid.disposed) return (0, brepjs.err)(specError("EXACT_BODY_SOLID_DISPOSED", `Exact Product Body item ${itemIndex} is already disposed`));
6556
+ try {
6557
+ if (!(0, brepjs.isValidSolid)(solid)) return (0, brepjs.err)(specError("EXACT_BODY_SOLID_INVALID", `Exact Product Body item ${itemIndex} is not a valid solid`));
6558
+ } catch (cause) {
6559
+ return (0, brepjs.err)(specError("EXACT_BODY_SOLID_INVALID", `Exact Product Body item ${itemIndex} could not be validated`, cause));
6560
+ }
6561
+ }
6562
+ const replaced = {
6563
+ ...target,
6564
+ geometry: body
6565
+ };
6566
+ this.#elements.set(localId, replaced);
6567
+ disposeProductBody(target.geometry);
6568
+ return (0, brepjs.ok)(void 0);
6569
+ }
6570
+ /**
6515
6571
  * Adds an IfcCovering. When `hostLocalId` is supplied, an
6516
6572
  * IfcRelCoversBldgElements linking the covering to its host (e.g. a slab it
6517
6573
  * finishes) is recorded for export.
@@ -6708,10 +6764,11 @@ var BimModel = class {
6708
6764
  return (0, brepjs.ok)(id);
6709
6765
  }
6710
6766
  addDoor(spec, options) {
6711
- const keyCheck = this.#checkOpeningKeys(options);
6712
- if (!keyCheck.ok) return keyCheck;
6713
6767
  const wall = this.#elements.get(spec.wallLocalId);
6714
6768
  if (wall === void 0 || wall.category !== "WALL") return (0, brepjs.err)(specError("DOOR_WALL_NOT_FOUND", `No wall found for localId ${spec.wallLocalId}`));
6769
+ if (wall.geometry.kind === "EXACT") return exactWallBodyImmutable();
6770
+ const keyCheck = this.#checkOpeningKeys(options);
6771
+ if (!keyCheck.ok) return keyCheck;
6715
6772
  if (spec.offsetAlongWall + spec.width > wall.spec.length) return (0, brepjs.err)(specError("DOOR_EXCEEDS_WALL_BOUNDS", "Door (offsetAlongWall + width) exceeds wall length"));
6716
6773
  if (spec.offsetFromFloor + spec.height > wall.spec.height) return (0, brepjs.err)(specError("DOOR_EXCEEDS_WALL_BOUNDS", "Door (offsetFromFloor + height) exceeds wall height"));
6717
6774
  const openingSpec = {
@@ -6744,10 +6801,11 @@ var BimModel = class {
6744
6801
  return (0, brepjs.ok)(doorId);
6745
6802
  }
6746
6803
  addWindow(spec, options) {
6747
- const keyCheck = this.#checkOpeningKeys(options);
6748
- if (!keyCheck.ok) return keyCheck;
6749
6804
  const wall = this.#elements.get(spec.wallLocalId);
6750
6805
  if (wall === void 0 || wall.category !== "WALL") return (0, brepjs.err)(specError("WINDOW_WALL_NOT_FOUND", `No wall found for localId ${spec.wallLocalId}`));
6806
+ if (wall.geometry.kind === "EXACT") return exactWallBodyImmutable();
6807
+ const keyCheck = this.#checkOpeningKeys(options);
6808
+ if (!keyCheck.ok) return keyCheck;
6751
6809
  if (spec.offsetAlongWall + spec.width > wall.spec.length) return (0, brepjs.err)(specError("WINDOW_EXCEEDS_WALL_BOUNDS", "Window (offsetAlongWall + width) exceeds wall length"));
6752
6810
  if (spec.offsetFromFloor + spec.height > wall.spec.height) return (0, brepjs.err)(specError("WINDOW_EXCEEDS_WALL_BOUNDS", "Window (offsetFromFloor + height) exceeds wall height"));
6753
6811
  const openingSpec = {
@@ -6821,27 +6879,31 @@ var BimModel = class {
6821
6879
  }
6822
6880
  #cutWallGeometry(wall, openingSpec) {
6823
6881
  try {
6824
- var _usingCtx$6 = _usingCtx();
6882
+ var _usingCtx$7 = _usingCtx();
6883
+ if (wall.geometry.kind === "EXACT") return exactWallBodyImmutable();
6825
6884
  const toolResult = openingToSolid(openingSpec, wall.spec.thickness);
6826
6885
  if (!toolResult.ok) return (0, brepjs.err)(toolResult.error);
6827
- const tool = _usingCtx$6.u(toolResult.value);
6828
- const cutResult = (0, brepjs.cut)(wall.geometry, tool);
6886
+ const tool = _usingCtx$7.u(toolResult.value);
6887
+ const cutResult = (0, brepjs.cut)(wall.geometry.solid, tool);
6829
6888
  if (!cutResult.ok) return (0, brepjs.err)(fromBrepError(cutResult.error, "WALL_CUT_FAILED", "Boolean cut of wall with opening failed"));
6830
6889
  return (0, brepjs.ok)(cutResult.value);
6831
6890
  } catch (_) {
6832
- _usingCtx$6.e = _;
6891
+ _usingCtx$7.e = _;
6833
6892
  } finally {
6834
- _usingCtx$6.d();
6893
+ _usingCtx$7.d();
6835
6894
  }
6836
6895
  }
6837
6896
  #replaceWallGeometry(wall, newGeometry) {
6838
6897
  const oldGeometry = wall.geometry;
6839
6898
  const replaced = {
6840
6899
  ...wall,
6841
- geometry: newGeometry
6900
+ geometry: {
6901
+ kind: "PARAMETRIC",
6902
+ solid: newGeometry
6903
+ }
6842
6904
  };
6843
6905
  this.#elements.set(wall.localId, replaced);
6844
- oldGeometry[Symbol.dispose]();
6906
+ disposeProductBody(oldGeometry);
6845
6907
  }
6846
6908
  #cutSlabGeometry(slab, openingSpec) {
6847
6909
  try {
@@ -7100,6 +7162,129 @@ var BimModel = class {
7100
7162
  }
7101
7163
  };
7102
7164
  //#endregion
7165
+ //#region src/elementFns/stairFns.ts
7166
+ function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
7167
+ const pts = [];
7168
+ pts.push([
7169
+ 0,
7170
+ 0,
7171
+ 0
7172
+ ]);
7173
+ let x = 0;
7174
+ let z = 0;
7175
+ for (let i = 0; i < numberOfRisers; i++) {
7176
+ z += riserHeight;
7177
+ pts.push([
7178
+ x,
7179
+ 0,
7180
+ z
7181
+ ]);
7182
+ x += treadLength;
7183
+ pts.push([
7184
+ x,
7185
+ 0,
7186
+ z
7187
+ ]);
7188
+ }
7189
+ pts.push([
7190
+ x,
7191
+ 0,
7192
+ 0
7193
+ ]);
7194
+ return pts;
7195
+ }
7196
+ function stairFlightToSolid(spec) {
7197
+ try {
7198
+ var _usingCtx$6 = _usingCtx();
7199
+ if (spec.width <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
7200
+ if (spec.riserHeight <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
7201
+ if (spec.treadLength <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
7202
+ if (!Number.isInteger(spec.numberOfRisers) || spec.numberOfRisers < 1) return (0, brepjs.err)(specError("STAIR_FLIGHT_BAD_RISERS", "Stair flight numberOfRisers must be a positive integer"));
7203
+ const silhouette = buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength);
7204
+ const profileResult = (0, brepjs.polygon)(silhouette);
7205
+ if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
7206
+ const profile = _usingCtx$6.u(profileResult.value);
7207
+ const solidResult = (0, brepjs.extrude)(profile, [
7208
+ 0,
7209
+ spec.width,
7210
+ 0
7211
+ ]);
7212
+ if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "STAIR_FLIGHT_EXTRUDE_FAILED", "Failed to extrude stair flight silhouette"));
7213
+ const solid = solidResult.value;
7214
+ if (!(0, brepjs.isValidSolid)(solid)) {
7215
+ solid[Symbol.dispose]();
7216
+ return (0, brepjs.err)(geometryError("STAIR_FLIGHT_INVALID_SOLID", "Stair flight solid failed validity check"));
7217
+ }
7218
+ return (0, brepjs.ok)({
7219
+ solid,
7220
+ geometrySimplified: false
7221
+ });
7222
+ } catch (_) {
7223
+ _usingCtx$6.e = _;
7224
+ } finally {
7225
+ _usingCtx$6.d();
7226
+ }
7227
+ }
7228
+ //#endregion
7229
+ //#region src/elementFns/rampFns.ts
7230
+ function buildSilhouette(length, rise, thickness) {
7231
+ return [
7232
+ [
7233
+ 0,
7234
+ 0,
7235
+ -thickness
7236
+ ],
7237
+ [
7238
+ length,
7239
+ 0,
7240
+ rise - thickness
7241
+ ],
7242
+ [
7243
+ length,
7244
+ 0,
7245
+ rise
7246
+ ],
7247
+ [
7248
+ 0,
7249
+ 0,
7250
+ 0
7251
+ ]
7252
+ ];
7253
+ }
7254
+ function rampFlightToSolid(spec) {
7255
+ try {
7256
+ var _usingCtx$5 = _usingCtx();
7257
+ if (spec.width <= 0) return (0, brepjs.err)(specError("RAMP_FLIGHT_ZERO_WIDTH", "Ramp flight width must be positive"));
7258
+ if (spec.length <= 0) return (0, brepjs.err)(specError("RAMP_FLIGHT_ZERO_LENGTH", "Ramp flight length must be positive"));
7259
+ if (spec.slope <= 0) return (0, brepjs.err)(specError("RAMP_FLIGHT_ZERO_SLOPE", "Ramp flight slope must be positive"));
7260
+ if (spec.thickness <= 0) return (0, brepjs.err)(specError("RAMP_FLIGHT_ZERO_THICKNESS", "Ramp flight thickness must be positive"));
7261
+ const rise = spec.length * spec.slope;
7262
+ const silhouette = buildSilhouette(spec.length, rise, spec.thickness);
7263
+ const profileResult = (0, brepjs.polygon)(silhouette);
7264
+ if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "RAMP_FLIGHT_PROFILE_FAILED", "Failed to create ramp flight silhouette profile"));
7265
+ const profile = _usingCtx$5.u(profileResult.value);
7266
+ const solidResult = (0, brepjs.extrude)(profile, [
7267
+ 0,
7268
+ spec.width,
7269
+ 0
7270
+ ]);
7271
+ if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "RAMP_FLIGHT_EXTRUDE_FAILED", "Failed to extrude ramp flight silhouette"));
7272
+ const solid = solidResult.value;
7273
+ if (!(0, brepjs.isValidSolid)(solid)) {
7274
+ solid[Symbol.dispose]();
7275
+ return (0, brepjs.err)(geometryError("RAMP_FLIGHT_INVALID_SOLID", "Ramp flight solid failed validity check"));
7276
+ }
7277
+ return (0, brepjs.ok)({
7278
+ solid,
7279
+ geometrySimplified: true
7280
+ });
7281
+ } catch (_) {
7282
+ _usingCtx$5.e = _;
7283
+ } finally {
7284
+ _usingCtx$5.d();
7285
+ }
7286
+ }
7287
+ //#endregion
7103
7288
  //#region src/import/placement.ts
7104
7289
  /**
7105
7290
  * Metres-per-file-unit length scale read from the IfcUnitAssignment's
@@ -7307,148 +7492,100 @@ function enumValue(v) {
7307
7492
  return typeof value === "string" ? value : null;
7308
7493
  }
7309
7494
  //#endregion
7310
- //#region src/elementFns/stairFns.ts
7311
- function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
7312
- const pts = [];
7313
- pts.push([
7314
- 0,
7315
- 0,
7316
- 0
7317
- ]);
7318
- let x = 0;
7319
- let z = 0;
7320
- for (let i = 0; i < numberOfRisers; i++) {
7321
- z += riserHeight;
7322
- pts.push([
7323
- x,
7324
- 0,
7325
- z
7326
- ]);
7327
- x += treadLength;
7328
- pts.push([
7329
- x,
7330
- 0,
7331
- z
7332
- ]);
7333
- }
7334
- pts.push([
7335
- x,
7336
- 0,
7337
- 0
7338
- ]);
7339
- return pts;
7340
- }
7341
- function stairFlightToSolid(spec) {
7342
- try {
7343
- var _usingCtx$5 = _usingCtx();
7344
- if (spec.width <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
7345
- if (spec.riserHeight <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
7346
- if (spec.treadLength <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
7347
- if (!Number.isInteger(spec.numberOfRisers) || spec.numberOfRisers < 1) return (0, brepjs.err)(specError("STAIR_FLIGHT_BAD_RISERS", "Stair flight numberOfRisers must be a positive integer"));
7348
- const silhouette = buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength);
7349
- const profileResult = (0, brepjs.polygon)(silhouette);
7350
- if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
7351
- const profile = _usingCtx$5.u(profileResult.value);
7352
- const solidResult = (0, brepjs.extrude)(profile, [
7353
- 0,
7354
- spec.width,
7355
- 0
7356
- ]);
7357
- if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "STAIR_FLIGHT_EXTRUDE_FAILED", "Failed to extrude stair flight silhouette"));
7358
- const solid = solidResult.value;
7359
- if (!(0, brepjs.isValidSolid)(solid)) {
7360
- solid[Symbol.dispose]();
7361
- return (0, brepjs.err)(geometryError("STAIR_FLIGHT_INVALID_SOLID", "Stair flight solid failed validity check"));
7362
- }
7363
- return (0, brepjs.ok)({
7364
- solid,
7365
- geometrySimplified: false
7366
- });
7367
- } catch (_) {
7368
- _usingCtx$5.e = _;
7369
- } finally {
7370
- _usingCtx$5.d();
7371
- }
7372
- }
7373
- //#endregion
7374
- //#region src/elementFns/rampFns.ts
7375
- function buildSilhouette(length, rise, thickness) {
7376
- return [
7377
- [
7378
- 0,
7379
- 0,
7380
- -thickness
7381
- ],
7382
- [
7383
- length,
7384
- 0,
7385
- rise - thickness
7386
- ],
7387
- [
7388
- length,
7389
- 0,
7390
- rise
7391
- ],
7392
- [
7393
- 0,
7394
- 0,
7395
- 0
7396
- ]
7495
+ //#region src/rigidPlacement.ts
7496
+ var RAD_TO_DEG = 180 / Math.PI;
7497
+ var ROTATION_EPSILON = 1e-12;
7498
+ /** Applies a rigid placement as a location so the source BRep is not rebuilt. */
7499
+ function locateShapeInFrame(shape, frame) {
7500
+ return (0, brepjs.locate)(shape, placementOps(frame));
7501
+ }
7502
+ function placementOps(frame) {
7503
+ const matrix = placementToMatrix(frame);
7504
+ const rotation = rotationOp(matrix.linear);
7505
+ const translation = {
7506
+ type: "translate",
7507
+ v: matrix.translation
7508
+ };
7509
+ return rotation === null ? [translation] : [rotation, translation];
7510
+ }
7511
+ function rotationOp(matrix) {
7512
+ const [m00, m01, m02, m10, m11, m12, m20, m21, m22] = matrix;
7513
+ const trace = m00 + m11 + m22;
7514
+ let w;
7515
+ let x;
7516
+ let y;
7517
+ let z;
7518
+ if (trace > 0) {
7519
+ const scale = 2 * Math.sqrt(trace + 1);
7520
+ w = scale / 4;
7521
+ x = (m21 - m12) / scale;
7522
+ y = (m02 - m20) / scale;
7523
+ z = (m10 - m01) / scale;
7524
+ } else if (m00 > m11 && m00 > m22) {
7525
+ const scale = 2 * Math.sqrt(1 + m00 - m11 - m22);
7526
+ w = (m21 - m12) / scale;
7527
+ x = scale / 4;
7528
+ y = (m01 + m10) / scale;
7529
+ z = (m02 + m20) / scale;
7530
+ } else if (m11 > m22) {
7531
+ const scale = 2 * Math.sqrt(1 + m11 - m00 - m22);
7532
+ w = (m02 - m20) / scale;
7533
+ x = (m01 + m10) / scale;
7534
+ y = scale / 4;
7535
+ z = (m12 + m21) / scale;
7536
+ } else {
7537
+ const scale = 2 * Math.sqrt(1 + m22 - m00 - m11);
7538
+ w = (m10 - m01) / scale;
7539
+ x = (m02 + m20) / scale;
7540
+ y = (m12 + m21) / scale;
7541
+ z = scale / 4;
7542
+ }
7543
+ const length = Math.hypot(w, x, y, z);
7544
+ const sign = w < 0 ? -1 : 1;
7545
+ const normalizedW = sign * w / length;
7546
+ const normalizedX = sign * x / length;
7547
+ const normalizedY = sign * y / length;
7548
+ const normalizedZ = sign * z / length;
7549
+ const sinHalfAngle = Math.hypot(normalizedX, normalizedY, normalizedZ);
7550
+ if (sinHalfAngle < ROTATION_EPSILON) return null;
7551
+ const axis = [
7552
+ normalizedX / sinHalfAngle,
7553
+ normalizedY / sinHalfAngle,
7554
+ normalizedZ / sinHalfAngle
7397
7555
  ];
7398
- }
7399
- function rampFlightToSolid(spec) {
7400
- try {
7401
- var _usingCtx$4 = _usingCtx();
7402
- if (spec.width <= 0) return (0, brepjs.err)(specError("RAMP_FLIGHT_ZERO_WIDTH", "Ramp flight width must be positive"));
7403
- if (spec.length <= 0) return (0, brepjs.err)(specError("RAMP_FLIGHT_ZERO_LENGTH", "Ramp flight length must be positive"));
7404
- if (spec.slope <= 0) return (0, brepjs.err)(specError("RAMP_FLIGHT_ZERO_SLOPE", "Ramp flight slope must be positive"));
7405
- if (spec.thickness <= 0) return (0, brepjs.err)(specError("RAMP_FLIGHT_ZERO_THICKNESS", "Ramp flight thickness must be positive"));
7406
- const rise = spec.length * spec.slope;
7407
- const silhouette = buildSilhouette(spec.length, rise, spec.thickness);
7408
- const profileResult = (0, brepjs.polygon)(silhouette);
7409
- if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "RAMP_FLIGHT_PROFILE_FAILED", "Failed to create ramp flight silhouette profile"));
7410
- const profile = _usingCtx$4.u(profileResult.value);
7411
- const solidResult = (0, brepjs.extrude)(profile, [
7412
- 0,
7413
- spec.width,
7414
- 0
7415
- ]);
7416
- if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "RAMP_FLIGHT_EXTRUDE_FAILED", "Failed to extrude ramp flight silhouette"));
7417
- const solid = solidResult.value;
7418
- if (!(0, brepjs.isValidSolid)(solid)) {
7419
- solid[Symbol.dispose]();
7420
- return (0, brepjs.err)(geometryError("RAMP_FLIGHT_INVALID_SOLID", "Ramp flight solid failed validity check"));
7421
- }
7422
- return (0, brepjs.ok)({
7423
- solid,
7424
- geometrySimplified: true
7425
- });
7426
- } catch (_) {
7427
- _usingCtx$4.e = _;
7428
- } finally {
7429
- _usingCtx$4.d();
7430
- }
7556
+ return {
7557
+ type: "rotate",
7558
+ angle: 2 * Math.atan2(sinHalfAngle, normalizedW) * RAD_TO_DEG,
7559
+ axis
7560
+ };
7431
7561
  }
7432
7562
  //#endregion
7433
7563
  //#region src/elementFns/placedGeometry.ts
7564
+ var testHooks$3 = null;
7434
7565
  function place(solid, frame) {
7435
- const result = (0, brepjs.applyMatrix)(solid, placementToMatrix(frame));
7436
- if (!result.ok) return (0, brepjs.err)(fromBrepError(result.error, "PLACED_GEOMETRY_FAILED", "Failed to place element geometry"));
7437
- return (0, brepjs.ok)(result.value);
7566
+ let placed = null;
7567
+ try {
7568
+ placed = locateShapeInFrame(solid, frame);
7569
+ testHooks$3?.afterPlaced?.(placed);
7570
+ return (0, brepjs.ok)(placed);
7571
+ } catch (cause) {
7572
+ placed?.[Symbol.dispose]();
7573
+ return (0, brepjs.err)(geometryError("PLACED_GEOMETRY_FAILED", "Element placement threw while transforming geometry", cause));
7574
+ }
7438
7575
  }
7439
7576
  function disposeAll(solids) {
7440
7577
  for (const s of solids) s[Symbol.dispose]();
7441
7578
  }
7442
7579
  function placeWithinParent(solid, localFrame, parentFrame) {
7443
7580
  try {
7444
- var _usingCtx$3 = _usingCtx();
7581
+ var _usingCtx$4 = _usingCtx();
7445
7582
  const local = place(solid, localFrame);
7446
7583
  if (!local.ok || parentFrame === void 0) return local;
7447
- return place(_usingCtx$3.u(local.value), parentFrame);
7584
+ return place(_usingCtx$4.u(local.value), parentFrame);
7448
7585
  } catch (_) {
7449
- _usingCtx$3.e = _;
7586
+ _usingCtx$4.e = _;
7450
7587
  } finally {
7451
- _usingCtx$3.d();
7588
+ _usingCtx$4.d();
7452
7589
  }
7453
7590
  }
7454
7591
  /**
@@ -7473,6 +7610,18 @@ function placedSolids(el, options = {}) {
7473
7610
  const parentFrame = options.parentFrame;
7474
7611
  switch (el.category) {
7475
7612
  case "WALL":
7613
+ case "RAILING": {
7614
+ const out = [];
7615
+ for (const solid of bodySolids(el.geometry)) {
7616
+ const placed = placeWithinParent(solid, el.spec, parentFrame);
7617
+ if (!placed.ok) {
7618
+ disposeAll(out);
7619
+ return placed;
7620
+ }
7621
+ out.push(placed.value);
7622
+ }
7623
+ return (0, brepjs.ok)(out);
7624
+ }
7476
7625
  case "SLAB":
7477
7626
  case "BEAM":
7478
7627
  case "COLUMN":
@@ -7480,8 +7629,7 @@ function placedSolids(el, options = {}) {
7480
7629
  case "ROOF":
7481
7630
  case "FOOTING":
7482
7631
  case "PILE":
7483
- case "COVERING":
7484
- case "RAILING": {
7632
+ case "COVERING": {
7485
7633
  const placed = placeWithinParent(el.geometry, el.spec, parentFrame);
7486
7634
  if (!placed.ok) return placed;
7487
7635
  return (0, brepjs.ok)([placed.value]);
@@ -7680,6 +7828,7 @@ async function initIfcApi(api) {
7680
7828
  }
7681
7829
  //#endregion
7682
7830
  //#region src/ifc-writer/ifcWriter.ts
7831
+ var testHooks$2 = null;
7683
7832
  /** Default MVD ViewDefinition declared in the STEP FILE_DESCRIPTION header. */
7684
7833
  var DEFAULT_MVD_VIEW_DEFINITION = "ReferenceView_v1.2";
7685
7834
  var VIEW_DEFINITION_RE = /ViewDefinition \[[^\]]*\]/;
@@ -7720,6 +7869,10 @@ var IfcWriter = class IfcWriter {
7720
7869
  return (0, brepjs.err)(ifcError("IFC_INIT_FAILED", "Failed to initialize web-ifc", e));
7721
7870
  }
7722
7871
  }
7872
+ /** Package-internal lifecycle seam for deterministic writer cleanup tests. */
7873
+ static fromApiForTesting(api, modelId = 0) {
7874
+ return new IfcWriter(api, modelId, DEFAULT_MVD_VIEW_DEFINITION, {});
7875
+ }
7723
7876
  nextId() {
7724
7877
  return this.#nextExpressId++;
7725
7878
  }
@@ -7737,6 +7890,7 @@ var IfcWriter = class IfcWriter {
7737
7890
  }
7738
7891
  writeLine(entity) {
7739
7892
  this.#api.WriteLine(this.#modelId, entity);
7893
+ testHooks$2?.afterWriteLine?.();
7740
7894
  return entity.expressID;
7741
7895
  }
7742
7896
  ref(id) {
@@ -7745,6 +7899,15 @@ var IfcWriter = class IfcWriter {
7745
7899
  mkType(type, value) {
7746
7900
  return this.#api.CreateIfcType(this.#modelId, type, value);
7747
7901
  }
7902
+ close() {
7903
+ if (this.#closed) return;
7904
+ this.#closed = true;
7905
+ this.#api.CloseModel(this.#modelId);
7906
+ testHooks$2?.afterClose?.();
7907
+ }
7908
+ [Symbol.dispose]() {
7909
+ this.close();
7910
+ }
7748
7911
  save() {
7749
7912
  if (this.#closed) return (0, brepjs.err)(ifcError("IFC_ALREADY_SAVED", "Model has already been saved and closed"));
7750
7913
  try {
@@ -7753,8 +7916,7 @@ var IfcWriter = class IfcWriter {
7753
7916
  } catch (e) {
7754
7917
  return (0, brepjs.err)(ifcError("IFC_SAVE_FAILED", "Failed to serialize IFC model", e));
7755
7918
  } finally {
7756
- this.#api.CloseModel(this.#modelId);
7757
- this.#closed = true;
7919
+ this.close();
7758
7920
  }
7759
7921
  }
7760
7922
  /**
@@ -8301,6 +8463,45 @@ function writeColumnEntity(w, guid, name, _predefinedType, ownerHistoryId, local
8301
8463
  //#region src/ifc-writer/tessellationWriter.ts
8302
8464
  var IFC_MESH_TOLERANCE_MM = 5;
8303
8465
  var IFC_MESH_ANGULAR_TOLERANCE_RAD = .3;
8466
+ function prepareTessellation(solid, toleranceMm = IFC_MESH_TOLERANCE_MM) {
8467
+ let meshData;
8468
+ try {
8469
+ meshData = (0, brepjs.mesh)(solid, {
8470
+ tolerance: toleranceMm,
8471
+ angularTolerance: IFC_MESH_ANGULAR_TOLERANCE_RAD
8472
+ });
8473
+ } catch (cause) {
8474
+ return {
8475
+ ok: false,
8476
+ reason: cause instanceof Error ? cause.message : String(cause),
8477
+ cause
8478
+ };
8479
+ }
8480
+ const { vertices, triangles } = meshData;
8481
+ if (vertices.length === 0 || triangles.length === 0) return {
8482
+ ok: false,
8483
+ reason: "mesh() returned an empty triangle set"
8484
+ };
8485
+ const coordList = [];
8486
+ for (let i = 0; i + 2 < vertices.length; i += 3) coordList.push([
8487
+ toIfcLengthM(vertices[i] ?? 0),
8488
+ toIfcLengthM(vertices[i + 1] ?? 0),
8489
+ toIfcLengthM(vertices[i + 2] ?? 0)
8490
+ ]);
8491
+ const coordIndex = [];
8492
+ for (let i = 0; i + 2 < triangles.length; i += 3) coordIndex.push([
8493
+ (triangles[i] ?? 0) + 1,
8494
+ (triangles[i + 1] ?? 0) + 1,
8495
+ (triangles[i + 2] ?? 0) + 1
8496
+ ]);
8497
+ return {
8498
+ ok: true,
8499
+ value: {
8500
+ coordList,
8501
+ coordIndex
8502
+ }
8503
+ };
8504
+ }
8304
8505
  /**
8305
8506
  * Writes an IfcTriangulatedFaceSet (preferred IFC4 tessellation) from a brepjs
8306
8507
  * ValidSolid, wrapped in an IfcShapeRepresentation (Body/Tessellation) and an
@@ -8319,52 +8520,20 @@ var IFC_MESH_ANGULAR_TOLERANCE_RAD = .3;
8319
8520
  * shape representation, so it is not referenced here.
8320
8521
  */
8321
8522
  function writeTessellation(w, solid, geomSubContextId, _localPlacementId, toleranceMm = IFC_MESH_TOLERANCE_MM) {
8322
- let meshData;
8323
- try {
8324
- meshData = (0, brepjs.mesh)(solid, {
8325
- tolerance: toleranceMm,
8326
- angularTolerance: IFC_MESH_ANGULAR_TOLERANCE_RAD
8327
- });
8328
- } catch (e) {
8329
- const reason = e instanceof Error ? e.message : String(e);
8523
+ const prepared = prepareTessellation(solid, toleranceMm);
8524
+ if (!prepared.ok) {
8525
+ const reason = prepared.reason;
8330
8526
  console.warn(`writeTessellation: mesh() failed, using IfcFacetedBrep fallback: ${reason}`);
8331
8527
  return writeFacetedBrepFallback(w, geomSubContextId, reason);
8332
8528
  }
8333
- const { vertices, triangles } = meshData;
8334
- if (vertices.length === 0 || triangles.length === 0) {
8335
- const reason = "mesh() returned an empty triangle set";
8336
- console.warn(`writeTessellation: ${reason}, using IfcFacetedBrep fallback`);
8337
- return writeFacetedBrepFallback(w, geomSubContextId, reason);
8338
- }
8339
- const coordList = [];
8340
- for (let i = 0; i + 2 < vertices.length; i += 3) coordList.push([
8341
- toIfcLengthM(vertices[i] ?? 0),
8342
- toIfcLengthM(vertices[i + 1] ?? 0),
8343
- toIfcLengthM(vertices[i + 2] ?? 0)
8344
- ]);
8345
- const pointListId = w.nextId();
8346
- w.writeLine({
8347
- expressID: pointListId,
8348
- type: web_ifc.IFCCARTESIANPOINTLIST3D,
8349
- CoordList: coordList.map((pt) => pt.map((v) => w.mkType(web_ifc.IFCLENGTHMEASURE, v))),
8350
- TagList: null
8351
- });
8352
- const coordIndex = [];
8353
- for (let i = 0; i + 2 < triangles.length; i += 3) coordIndex.push([
8354
- (triangles[i] ?? 0) + 1,
8355
- (triangles[i + 1] ?? 0) + 1,
8356
- (triangles[i + 2] ?? 0) + 1
8357
- ]);
8358
- const faceSetId = w.nextId();
8359
- w.writeLine({
8360
- expressID: faceSetId,
8361
- type: web_ifc.IFCTRIANGULATEDFACESET,
8362
- Coordinates: w.ref(pointListId),
8363
- Normals: null,
8364
- Closed: w.mkType(web_ifc.IFCBOOLEAN, true),
8365
- CoordIndex: coordIndex.map((tri) => tri.map((idx) => w.mkType(web_ifc.IFCPOSITIVEINTEGER, idx))),
8366
- PnIndex: null
8367
- });
8529
+ const { productDefinitionShapeId } = writePreparedTessellationBody(w, [prepared.value], geomSubContextId);
8530
+ return {
8531
+ productDefinitionShapeId,
8532
+ usedFallback: false
8533
+ };
8534
+ }
8535
+ function writePreparedTessellationBody(w, items, geomSubContextId) {
8536
+ const bodyItemIds = items.map((item) => writePreparedTessellationItem(w, item));
8368
8537
  const shapeRepId = w.nextId();
8369
8538
  w.writeLine({
8370
8539
  expressID: shapeRepId,
@@ -8372,7 +8541,7 @@ function writeTessellation(w, solid, geomSubContextId, _localPlacementId, tolera
8372
8541
  ContextOfItems: w.ref(geomSubContextId),
8373
8542
  RepresentationIdentifier: w.mkType(web_ifc.IFCLABEL, "Body"),
8374
8543
  RepresentationType: w.mkType(web_ifc.IFCLABEL, "Tessellation"),
8375
- Items: [w.ref(faceSetId)]
8544
+ Items: bodyItemIds.map((id) => w.ref(id))
8376
8545
  });
8377
8546
  const productDefinitionShapeId = w.nextId();
8378
8547
  w.writeLine({
@@ -8384,9 +8553,55 @@ function writeTessellation(w, solid, geomSubContextId, _localPlacementId, tolera
8384
8553
  });
8385
8554
  return {
8386
8555
  productDefinitionShapeId,
8387
- usedFallback: false
8556
+ bodyItemIds
8557
+ };
8558
+ }
8559
+ function writeExactBodyGeometry(w, placement, items, geomSubContextId, parentPlacementId) {
8560
+ const placement3DId = writeAxis2Placement3D(w, [
8561
+ toIfcLengthM(placement.origin[0]),
8562
+ toIfcLengthM(placement.origin[1]),
8563
+ toIfcLengthM(placement.origin[2])
8564
+ ], [
8565
+ placement.axisZ[0],
8566
+ placement.axisZ[1],
8567
+ placement.axisZ[2]
8568
+ ], [
8569
+ placement.axisX[0],
8570
+ placement.axisX[1],
8571
+ placement.axisX[2]
8572
+ ]);
8573
+ const localPlacementId = w.nextId();
8574
+ w.writeLine({
8575
+ expressID: localPlacementId,
8576
+ type: web_ifc.IFCLOCALPLACEMENT,
8577
+ PlacementRelTo: parentPlacementId === null ? null : w.ref(parentPlacementId),
8578
+ RelativePlacement: w.ref(placement3DId)
8579
+ });
8580
+ return {
8581
+ localPlacementId,
8582
+ ...writePreparedTessellationBody(w, items, geomSubContextId)
8388
8583
  };
8389
8584
  }
8585
+ function writePreparedTessellationItem(w, prepared) {
8586
+ const pointListId = w.nextId();
8587
+ w.writeLine({
8588
+ expressID: pointListId,
8589
+ type: web_ifc.IFCCARTESIANPOINTLIST3D,
8590
+ CoordList: prepared.coordList.map((point) => point.map((value) => w.mkType(web_ifc.IFCLENGTHMEASURE, value))),
8591
+ TagList: null
8592
+ });
8593
+ const faceSetId = w.nextId();
8594
+ w.writeLine({
8595
+ expressID: faceSetId,
8596
+ type: web_ifc.IFCTRIANGULATEDFACESET,
8597
+ Coordinates: w.ref(pointListId),
8598
+ Normals: null,
8599
+ Closed: w.mkType(web_ifc.IFCBOOLEAN, true),
8600
+ CoordIndex: prepared.coordIndex.map((triangle) => triangle.map((index) => w.mkType(web_ifc.IFCPOSITIVEINTEGER, index))),
8601
+ PnIndex: null
8602
+ });
8603
+ return faceSetId;
8604
+ }
8390
8605
  /**
8391
8606
  * Builds a wall 'Axis' IfcShapeRepresentation: an IfcPolyline from (0,0) to
8392
8607
  * (lengthM, 0) in the wall's local XY plane, wrapped in an IfcShapeRepresentation
@@ -9883,22 +10098,22 @@ function writeStairAssembly(w, spec, stairKey, assemblyGuid, ownerHistoryId, geo
9883
10098
  const flightExpressIds = [];
9884
10099
  let geometrySimplified = false;
9885
10100
  for (const [i, flight] of spec.flights.entries()) try {
9886
- var _usingCtx$2 = _usingCtx();
10101
+ var _usingCtx$3 = _usingCtx();
9887
10102
  const built = stairFlightToSolid(flight);
9888
10103
  if (!built.ok) return {
9889
10104
  ok: false,
9890
10105
  error: built.error
9891
10106
  };
9892
- const flightSolid = _usingCtx$2.u(built.value.solid);
10107
+ const flightSolid = _usingCtx$3.u(built.value.solid);
9893
10108
  const flightPlacementId = writeLocalPlacement(w, flight.origin, flight.axisZ, flight.axisX, stairPlacementId);
9894
10109
  const tess = writeTessellation(w, flightSolid, geomSubContextId, flightPlacementId);
9895
10110
  if (tess.usedFallback) geometrySimplified = true;
9896
10111
  const flightExpressId = writeStairFlightEntity(w, deriveIfcGuidSync(`elem:STAIR_FLIGHT:${stairKey}:${i}`), `${stairName} Flight ${i + 1}`, flight, ownerHistoryId, flightPlacementId, tess.productDefinitionShapeId);
9897
10112
  flightExpressIds.push(flightExpressId);
9898
10113
  } catch (_) {
9899
- _usingCtx$2.e = _;
10114
+ _usingCtx$3.e = _;
9900
10115
  } finally {
9901
- _usingCtx$2.d();
10116
+ _usingCtx$3.d();
9902
10117
  }
9903
10118
  writeRelAggregates(w, deriveIfcGuidSync(`rel:AGGREGATES:STAIR:${stairKey}`), ownerHistoryId, assemblyExpressId, flightExpressIds);
9904
10119
  writeAssemblyType(w, web_ifc.IFCSTAIRTYPE, "Stair Type", deriveIfcGuidSync(`type:STAIR:${stairKey}`), deriveIfcGuidSync(`rel-type:STAIR:${stairKey}`), predefinedType, ownerHistoryId, [assemblyExpressId]);
@@ -11293,6 +11508,14 @@ function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, opening
11293
11508
  pushWeightQuantity(w, qtyIds, netVolumeM3, densityKgM3);
11294
11509
  writeRelDefinesByProperties(w, ownerHistoryId, wallExpressId, writeElementQuantity(w, ownerHistoryId, "Qto_WallBaseQuantities", qtyIds));
11295
11510
  }
11511
+ function writeExactWallBaseQuantities(w, ownerHistoryId, wallExpressId, values) {
11512
+ writeRelDefinesByProperties(w, ownerHistoryId, wallExpressId, writeElementQuantity(w, ownerHistoryId, "Qto_WallBaseQuantities", [
11513
+ writeQtyLength(w, "Length", values.lengthM),
11514
+ writeQtyLength(w, "Width", values.widthM),
11515
+ writeQtyLength(w, "Height", values.heightM),
11516
+ writeQtyVolume(w, "NetVolume", values.netVolumeM3)
11517
+ ]));
11518
+ }
11296
11519
  function writeSlabCommonPset(w, ownerHistoryId, slabExpressId, spec) {
11297
11520
  const values = {};
11298
11521
  if (spec.isExternal !== void 0) values["IsExternal"] = spec.isExternal;
@@ -13305,457 +13528,580 @@ function checkGeoreferencing(reader) {
13305
13528
  return [issue("warning", "NO_GEOREFERENCING", "Model contains a facility but no IfcProjectedCRS/IfcGeographicCRS (GRF003) — set ProjectSpec.crs")];
13306
13529
  }
13307
13530
  //#endregion
13308
- //#region src/serialize/toIfc.ts
13309
- async function toIfc(model, meta) {
13310
- const project = model.getProject();
13311
- if (!project) return (0, brepjs.err)(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
13312
- if ((model.getBridges().length > 0 || model.getBridgeParts().length > 0 || model.getEarthworksFills().length > 0) && (meta.ifcSchema ?? "IFC4") !== "IFC4X3") return (0, brepjs.err)(ifcError("IFC4X3_REQUIRED", "Bridge, Bridge Part, and Earthworks Fill entities require IFC4X3 serialization"));
13313
- const authorName = [meta.author?.givenName, meta.author?.familyName].filter((p) => Boolean(p)).join(" ");
13314
- const writerResult = await IfcWriter.create(meta.mvdViewDefinition, meta.ifcSchema, {
13315
- author: authorName,
13316
- organization: meta.organizationName
13317
- });
13318
- if (!writerResult.ok) return writerResult;
13319
- const w = writerResult.value;
13320
- w.setModelScope(project.guid);
13321
- const elements = model.getAllElements();
13322
- const relationships = model.getAllRelationships();
13323
- const walls = model.getWalls();
13324
- const slabs = model.getSlabs();
13325
- const beams = model.getBeams();
13326
- const bridges = model.getBridges();
13327
- const bridgeParts = model.getBridgeParts();
13328
- const earthworksFills = model.getEarthworksFills();
13329
- const columns = model.getColumns();
13330
- const doors = model.getDoors();
13331
- const windows = model.getWindows();
13332
- const proxies = model.getProxies();
13333
- const spaces = model.getSpaces();
13334
- const roofs = model.getRoofs();
13335
- const curtainWalls = model.getCurtainWalls();
13336
- const footings = model.getFootings();
13337
- const piles = model.getPiles();
13338
- const stairs = model.getStairs();
13339
- const ramps = model.getRamps();
13340
- const railings = model.getRailings();
13341
- const coverings = model.getCoverings();
13342
- const assemblies = model.getElementAssemblies();
13343
- const zones = model.getZones();
13344
- const systems = model.getSystems();
13345
- const { ownerHistoryId, geomContextId, geomSubContextId, unitAssignmentId, lengthUnitId } = writeHeader(w, meta);
13346
- if (project.spec.crs !== void 0) writeMapConversion(w, project.spec.crs, geomContextId, lengthUnitId);
13347
- const densityByElement = buildDensityMap(relationships);
13348
- const idMap = /* @__PURE__ */ new Map();
13349
- const placementMap = /* @__PURE__ */ new Map();
13350
- const projectExpressId = writeProject$1(w, project.guid, project.spec.name, ownerHistoryId, unitAssignmentId, geomContextId);
13351
- idMap.set(project.localId, projectExpressId);
13352
- for (const el of elements) {
13353
- if (el.category !== "SITE") continue;
13354
- const parentId = findParentOf(el.localId, relationships);
13355
- const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13356
- const { entityId, placementId } = writeSite(w, el.guid, el.spec, ownerHistoryId, parentPlacementId);
13357
- idMap.set(el.localId, entityId);
13358
- placementMap.set(el.localId, placementId);
13359
- }
13360
- for (const bridge of bridges) {
13361
- const parentId = findParentOf(bridge.localId, relationships);
13362
- const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13363
- const { entityId, placementId } = writeBridge(w, bridge.guid, bridge.spec, ownerHistoryId, parentPlacementId);
13364
- idMap.set(bridge.localId, entityId);
13365
- placementMap.set(bridge.localId, placementId);
13366
- }
13367
- const pendingParts = [...bridgeParts];
13368
- while (pendingParts.length > 0) {
13369
- const readyIndex = pendingParts.findIndex((candidate) => {
13370
- const candidateParentId = findParentOf(candidate.localId, relationships);
13371
- return candidateParentId === null || placementMap.has(candidateParentId);
13372
- });
13373
- const [part] = pendingParts.splice(readyIndex === -1 ? 0 : readyIndex, 1);
13374
- if (part === void 0) break;
13375
- const parentId = findParentOf(part.localId, relationships);
13376
- const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13377
- const { entityId, placementId } = writeBridgePart(w, part.guid, part.spec, ownerHistoryId, parentPlacementId);
13378
- idMap.set(part.localId, entityId);
13379
- placementMap.set(part.localId, placementId);
13380
- }
13381
- for (const el of elements) {
13382
- if (el.category !== "BUILDING") continue;
13383
- const parentSiteId = findParentOf(el.localId, relationships);
13384
- const parentPlacementId = parentSiteId !== null ? placementMap.get(parentSiteId) ?? null : null;
13385
- const { entityId, placementId } = writeBuilding(w, el.guid, el.spec.name, ownerHistoryId, parentPlacementId);
13386
- idMap.set(el.localId, entityId);
13387
- placementMap.set(el.localId, placementId);
13388
- }
13389
- for (const el of elements) {
13390
- if (el.category !== "STOREY") continue;
13391
- const parentBuildingId = findParentOf(el.localId, relationships);
13392
- const parentPlacementId = parentBuildingId !== null ? placementMap.get(parentBuildingId) ?? null : null;
13393
- const { entityId, placementId } = writeStorey(w, el.guid, el.spec.name, el.spec.elevation, ownerHistoryId, parentPlacementId);
13394
- idMap.set(el.localId, entityId);
13395
- placementMap.set(el.localId, placementId);
13396
- }
13397
- const openingsByWall = /* @__PURE__ */ new Map();
13398
- for (const rel of relationships) {
13399
- if (rel.kind !== "VOIDS_WALL") continue;
13400
- const opening = elements.find((el) => el.localId === rel.openingLocalId);
13401
- if (opening === void 0 || opening.category !== "OPENING") continue;
13402
- if (!isWallOpening(opening.spec)) continue;
13403
- const list = openingsByWall.get(rel.wallLocalId) ?? [];
13404
- list.push(opening.spec);
13405
- openingsByWall.set(rel.wallLocalId, list);
13406
- }
13407
- const openingsBySlab = /* @__PURE__ */ new Map();
13408
- for (const rel of relationships) {
13409
- if (rel.kind !== "VOIDS_SLAB") continue;
13410
- const opening = elements.find((el) => el.localId === rel.openingLocalId);
13411
- if (opening === void 0 || opening.category !== "OPENING") continue;
13412
- if (!isSlabOpening(opening.spec)) continue;
13413
- const list = openingsBySlab.get(rel.slabLocalId) ?? [];
13414
- list.push(opening.spec);
13415
- openingsBySlab.set(rel.slabLocalId, list);
13416
- }
13417
- for (const [i, wall] of walls.entries()) {
13418
- const containingId = findContainerOf(wall.localId, relationships);
13419
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13420
- const { localPlacementId, productDefinitionShapeId } = writeWallGeometry(w, wall.spec, geomSubContextId, storeyPlacementId);
13421
- const wallExpressId = writeWallEntity(w, wall.guid, `Wall ${i + 1}`, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13422
- idMap.set(wall.localId, wallExpressId);
13423
- placementMap.set(wall.localId, localPlacementId);
13424
- writeWallCommonPset(w, ownerHistoryId, wallExpressId, wall.spec);
13425
- writeManufacturerPset(w, ownerHistoryId, wallExpressId, wall.spec);
13426
- if (wall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, wallExpressId, wall.spec.customProperties);
13427
- writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, wall.spec, openingsByWall.get(wall.localId) ?? [], densityByElement.get(wall.localId));
13428
- }
13429
- for (const [i, slab] of slabs.entries()) {
13430
- const containingId = findContainerOf(slab.localId, relationships);
13431
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13432
- const { localPlacementId, productDefinitionShapeId } = writeSlabGeometry(w, slab.spec, geomSubContextId, storeyPlacementId);
13433
- const slabExpressId = writeSlabEntity(w, slab.guid, `Slab ${i + 1}`, slab.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13434
- idMap.set(slab.localId, slabExpressId);
13435
- placementMap.set(slab.localId, localPlacementId);
13436
- writeSlabCommonPset(w, ownerHistoryId, slabExpressId, slab.spec);
13437
- writeManufacturerPset(w, ownerHistoryId, slabExpressId, slab.spec);
13438
- if (slab.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, slabExpressId, slab.spec.customProperties);
13439
- writeSlabBaseQuantities(w, ownerHistoryId, slabExpressId, slab.spec, openingsBySlab.get(slab.localId) ?? [], densityByElement.get(slab.localId));
13440
- }
13441
- for (const [i, beam] of beams.entries()) {
13442
- const containingId = findContainerOf(beam.localId, relationships);
13443
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13444
- const { localPlacementId, productDefinitionShapeId } = writeBeamGeometry(w, beam.spec, geomSubContextId, storeyPlacementId);
13445
- const beamExpressId = writeBeamEntity(w, beam.guid, `Beam ${i + 1}`, beam.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13446
- idMap.set(beam.localId, beamExpressId);
13447
- placementMap.set(beam.localId, localPlacementId);
13448
- writeBeamCommonPset(w, ownerHistoryId, beamExpressId, beam.spec);
13449
- writeManufacturerPset(w, ownerHistoryId, beamExpressId, beam.spec);
13450
- if (beam.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, beamExpressId, beam.spec.customProperties);
13451
- writeBeamBaseQuantities(w, ownerHistoryId, beamExpressId, beam.spec);
13452
- }
13453
- for (const [i, column] of columns.entries()) {
13454
- const containingId = findContainerOf(column.localId, relationships);
13455
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13456
- const { localPlacementId, productDefinitionShapeId } = writeColumnGeometry(w, column.spec, geomSubContextId, storeyPlacementId);
13457
- const columnExpressId = writeColumnEntity(w, column.guid, `Column ${i + 1}`, column.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13458
- idMap.set(column.localId, columnExpressId);
13459
- placementMap.set(column.localId, localPlacementId);
13460
- writeColumnCommonPset(w, ownerHistoryId, columnExpressId, column.spec);
13461
- writeManufacturerPset(w, ownerHistoryId, columnExpressId, column.spec);
13462
- if (column.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, columnExpressId, column.spec.customProperties);
13463
- writeColumnBaseQuantities(w, ownerHistoryId, columnExpressId, column.spec);
13464
- }
13465
- for (const [i, proxy] of proxies.entries()) {
13466
- const containingId = findContainerOf(proxy.localId, relationships);
13467
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13468
- const { localPlacementId, productDefinitionShapeId } = writeProxyGeometry(w, proxy.spec, geomSubContextId, storeyPlacementId);
13469
- const proxyExpressId = writeProxyEntity(w, proxy.guid, proxy.spec.name || `Proxy ${i + 1}`, proxy.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13470
- idMap.set(proxy.localId, proxyExpressId);
13471
- placementMap.set(proxy.localId, localPlacementId);
13472
- if (proxy.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, proxyExpressId, proxy.spec.customProperties);
13473
- }
13474
- for (const fill of earthworksFills) {
13475
- const containingId = findContainerOf(fill.localId, relationships);
13476
- const parentPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13477
- const { localPlacementId, productDefinitionShapeId } = writeTessellatedProductGeometry(w, fill.geometry, geomSubContextId, parentPlacementId);
13478
- const expressId = writeEarthworksFillEntity(w, fill.guid, fill.spec, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13479
- idMap.set(fill.localId, expressId);
13480
- placementMap.set(fill.localId, localPlacementId);
13481
- if (fill.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, expressId, fill.spec.customProperties);
13482
- }
13483
- for (const [i, space] of spaces.entries()) {
13484
- const containingId = findContainerOf(space.localId, relationships);
13485
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13486
- const { localPlacementId, productDefinitionShapeId } = writeSpaceGeometry(w, space.spec, geomSubContextId, storeyPlacementId);
13487
- const spaceExpressId = writeSpaceEntity(w, space.guid, space.spec.name || `Space ${i + 1}`, space.spec.longName ?? null, space.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13488
- idMap.set(space.localId, spaceExpressId);
13489
- placementMap.set(space.localId, localPlacementId);
13490
- writeSpaceCommonPset(w, ownerHistoryId, spaceExpressId, space.spec);
13491
- if (space.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, spaceExpressId, space.spec.customProperties);
13492
- writeSpaceBaseQuantities(w, ownerHistoryId, spaceExpressId, space.spec);
13493
- }
13494
- for (const [i, roof] of roofs.entries()) {
13495
- const containingId = findContainerOf(roof.localId, relationships);
13496
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13497
- const { localPlacementId, productDefinitionShapeId, usedFallback } = writeRoofGeometry(w, roof.spec, roof.geometry, geomSubContextId, storeyPlacementId);
13498
- if (usedFallback) console.warn(`Roof ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
13499
- const roofExpressId = writeRoofEntity(w, roof.guid, `Roof ${i + 1}`, roof.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13500
- idMap.set(roof.localId, roofExpressId);
13501
- placementMap.set(roof.localId, localPlacementId);
13502
- writeRoofCommonPset(w, ownerHistoryId, roofExpressId, roof.spec);
13503
- writeManufacturerPset(w, ownerHistoryId, roofExpressId, roof.spec);
13504
- if (roof.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, roofExpressId, roof.spec.customProperties);
13505
- writeRoofBaseQuantities(w, ownerHistoryId, roofExpressId, roof.spec);
13506
- }
13507
- for (const [i, curtainWall] of curtainWalls.entries()) {
13508
- const containingId = findContainerOf(curtainWall.localId, relationships);
13509
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13510
- const { curtainWallId } = writeCurtainWall(w, curtainWall.spec, curtainWall.geometry, `elem:CURTAIN_WALL:${curtainWall.localId}`, `Curtain Wall ${i + 1}`, ownerHistoryId, geomSubContextId, storeyPlacementId);
13511
- idMap.set(curtainWall.localId, curtainWallId);
13512
- writeCurtainWallCommonPset(w, ownerHistoryId, curtainWallId, curtainWall.spec);
13513
- if (curtainWall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, curtainWallId, curtainWall.spec.customProperties);
13514
- }
13515
- for (const [i, footing] of footings.entries()) {
13516
- const containingId = findContainerOf(footing.localId, relationships);
13517
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13518
- const { localPlacementId, productDefinitionShapeId } = writeFootingGeometry(w, footing.spec, geomSubContextId, storeyPlacementId);
13519
- const footingExpressId = writeFootingEntity(w, footing.guid, `Footing ${i + 1}`, footing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13520
- idMap.set(footing.localId, footingExpressId);
13521
- placementMap.set(footing.localId, localPlacementId);
13522
- writeFootingCommonPset(w, ownerHistoryId, footingExpressId, footing.spec);
13523
- if (footing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, footingExpressId, footing.spec.customProperties);
13524
- writeFootingBaseQuantities(w, ownerHistoryId, footingExpressId, footing.spec);
13525
- }
13526
- for (const [i, pile] of piles.entries()) {
13527
- const containingId = findContainerOf(pile.localId, relationships);
13528
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13529
- const { localPlacementId, productDefinitionShapeId } = writePileGeometry(w, pile.spec, geomSubContextId, storeyPlacementId);
13530
- const pileExpressId = writePileEntity(w, pile.guid, `Pile ${i + 1}`, pile.spec.predefinedType ?? "NOTDEFINED", pile.spec.constructionType ?? null, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13531
- idMap.set(pile.localId, pileExpressId);
13532
- placementMap.set(pile.localId, localPlacementId);
13533
- writePileCommonPset(w, ownerHistoryId, pileExpressId, pile.spec);
13534
- if (pile.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, pileExpressId, pile.spec.customProperties);
13535
- writePileBaseQuantities(w, ownerHistoryId, pileExpressId, pile.spec);
13536
- }
13537
- for (const [i, assembly] of assemblies.entries()) {
13538
- const containingId = findContainerOf(assembly.localId, relationships);
13539
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13540
- const assemblyExpressId = writeElementAssemblyEntity(w, assembly.guid, assembly.spec.name ?? `Assembly ${i + 1}`, assembly.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, storeyPlacementId, null, assembly.spec.assemblyPlace ?? "NOTDEFINED");
13541
- idMap.set(assembly.localId, assemblyExpressId);
13542
- }
13543
- for (const zone of zones) {
13544
- const zoneExpressId = writeZoneEntity(w, zone.guid, zone.spec.name, zone.spec.longName ?? null, zone.spec.objectType ?? null, ownerHistoryId);
13545
- idMap.set(zone.localId, zoneExpressId);
13546
- }
13547
- for (const system of systems) {
13548
- const systemExpressId = writeSystemEntity(w, system.guid, system.spec.name, system.spec.longName ?? null, system.spec.objectType ?? null, ownerHistoryId);
13549
- idMap.set(system.localId, systemExpressId);
13550
- }
13551
- for (const stair of stairs) {
13552
- const containingId = findContainerOf(stair.localId, relationships);
13553
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13554
- const result = writeStairAssembly(w, stair.spec, `${stair.localId}`, stair.guid, ownerHistoryId, geomSubContextId, storeyPlacementId);
13555
- if (!result.ok) return (0, brepjs.err)(result.error);
13556
- idMap.set(stair.localId, result.value.assemblyExpressId);
13557
- writeStairCommonPset(w, ownerHistoryId, result.value.assemblyExpressId, stair.spec);
13558
- }
13559
- for (const ramp of ramps) {
13560
- const containingId = findContainerOf(ramp.localId, relationships);
13561
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13562
- const result = writeRampAssembly(w, ramp.spec, `${ramp.localId}`, ramp.guid, ownerHistoryId, geomSubContextId, storeyPlacementId);
13563
- if (!result.ok) return (0, brepjs.err)(result.error);
13564
- idMap.set(ramp.localId, result.value.assemblyExpressId);
13565
- writeRampCommonPset(w, ownerHistoryId, result.value.assemblyExpressId, ramp.spec);
13566
- }
13567
- for (const [i, railing] of railings.entries()) {
13568
- const containingId = findContainerOf(railing.localId, relationships);
13569
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13570
- const { localPlacementId, productDefinitionShapeId, bodyItemId, usedFallback } = writeRailingGeometry(w, railing.spec, railing.geometry, geomSubContextId, storeyPlacementId);
13571
- if (usedFallback) console.warn(`Railing ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
13572
- const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13573
- idMap.set(railing.localId, railingExpressId);
13574
- placementMap.set(railing.localId, localPlacementId);
13575
- writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
13576
- writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
13577
- if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
13578
- if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
13579
- }
13580
- for (const [i, covering] of coverings.entries()) {
13581
- const containingId = findContainerOf(covering.localId, relationships);
13582
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13583
- const { localPlacementId, productDefinitionShapeId, bodyItemId } = writeCoveringGeometry(w, covering.spec, geomSubContextId, storeyPlacementId);
13584
- const coveringExpressId = writeCoveringEntity(w, covering.guid, `Covering ${i + 1}`, covering.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13585
- idMap.set(covering.localId, coveringExpressId);
13586
- placementMap.set(covering.localId, localPlacementId);
13587
- writeCoveringCommonPset(w, ownerHistoryId, coveringExpressId, covering.spec);
13588
- writeManufacturerPset(w, ownerHistoryId, coveringExpressId, covering.spec);
13589
- if (covering.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, coveringExpressId, covering.spec.customProperties);
13590
- applySurfaceStyle(w, model, covering.localId, bodyItemId);
13591
- }
13592
- const openingPlacementMap = /* @__PURE__ */ new Map();
13593
- const openingEntityMap = /* @__PURE__ */ new Map();
13594
- for (const rel of relationships) {
13595
- if (rel.kind !== "VOIDS_WALL") continue;
13596
- const wallElement = elements.find((el) => el.category === "WALL" && el.localId === rel.wallLocalId);
13597
- if (wallElement === void 0) continue;
13598
- const wallExpressId = idMap.get(rel.wallLocalId);
13599
- const wallPlacementId = placementMap.get(rel.wallLocalId);
13600
- if (wallExpressId === void 0 || wallPlacementId === void 0) continue;
13601
- const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
13602
- if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
13603
- if (!isWallOpening(openingElement.spec)) continue;
13604
- const { openingEntityId, openingPlacementId } = writeOpeningGeometry(w, openingElement.guid, openingElement.spec, wallElement.spec, wallPlacementId, geomSubContextId, ownerHistoryId);
13605
- idMap.set(rel.openingLocalId, openingEntityId);
13606
- openingPlacementMap.set(rel.openingLocalId, openingPlacementId);
13607
- openingEntityMap.set(rel.openingLocalId, openingEntityId);
13608
- writeRelVoidsElement(w, rel.guid, ownerHistoryId, wallExpressId, openingEntityId);
13609
- }
13610
- for (const rel of relationships) {
13611
- if (rel.kind !== "VOIDS_SLAB") continue;
13612
- const slabElement = elements.find((el) => el.category === "SLAB" && el.localId === rel.slabLocalId);
13613
- if (slabElement === void 0) continue;
13614
- const slabExpressId = idMap.get(rel.slabLocalId);
13615
- const slabPlacementId = placementMap.get(rel.slabLocalId);
13616
- if (slabExpressId === void 0 || slabPlacementId === void 0) continue;
13617
- const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
13618
- if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
13619
- if (!isSlabOpening(openingElement.spec)) continue;
13620
- const { openingEntityId } = writeSlabOpeningGeometry(w, openingElement.guid, openingElement.spec, slabElement.spec, slabPlacementId, geomSubContextId, ownerHistoryId);
13621
- idMap.set(rel.openingLocalId, openingEntityId);
13622
- writeRelVoidsElement(w, rel.guid, ownerHistoryId, slabExpressId, openingEntityId);
13623
- }
13624
- for (const [i, door] of doors.entries()) {
13625
- const fillsRel = relationships.find((rel) => rel.kind === "FILLS_OPENING" && rel.fillerLocalId === door.localId);
13626
- if (fillsRel === void 0 || fillsRel.kind !== "FILLS_OPENING") continue;
13627
- const openingPlacementId = openingPlacementMap.get(fillsRel.openingLocalId);
13628
- const openingEntityId = openingEntityMap.get(fillsRel.openingLocalId);
13629
- if (openingPlacementId === void 0 || openingEntityId === void 0) continue;
13630
- const doorExpressId = writeDoorEntity(w, door.guid, `Door ${i + 1}`, ownerHistoryId, openingPlacementId, geomSubContextId, toIfcLengthM(door.spec.width), toIfcLengthM(door.spec.height));
13631
- idMap.set(door.localId, doorExpressId);
13632
- writeRelFillsElement(w, fillsRel.guid, ownerHistoryId, openingEntityId, doorExpressId);
13633
- writeDoorCommonPset(w, ownerHistoryId, doorExpressId, door.spec);
13634
- }
13635
- for (const [i, win] of windows.entries()) {
13636
- const fillsRel = relationships.find((rel) => rel.kind === "FILLS_OPENING" && rel.fillerLocalId === win.localId);
13637
- if (fillsRel === void 0 || fillsRel.kind !== "FILLS_OPENING") continue;
13638
- const openingPlacementId = openingPlacementMap.get(fillsRel.openingLocalId);
13639
- const openingEntityId = openingEntityMap.get(fillsRel.openingLocalId);
13640
- if (openingPlacementId === void 0 || openingEntityId === void 0) continue;
13641
- const windowExpressId = writeWindowEntity(w, win.guid, `Window ${i + 1}`, ownerHistoryId, openingPlacementId, geomSubContextId, toIfcLengthM(win.spec.width), toIfcLengthM(win.spec.height));
13642
- idMap.set(win.localId, windowExpressId);
13643
- writeRelFillsElement(w, fillsRel.guid, ownerHistoryId, openingEntityId, windowExpressId);
13644
- writeWindowCommonPset(w, ownerHistoryId, windowExpressId, win.spec);
13645
- }
13646
- for (const rel of relationships) {
13647
- if (rel.kind !== "AGGREGATES") continue;
13648
- const parentExpressId = idMap.get(rel.relatingObject);
13649
- const childExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13650
- if (parentExpressId === void 0 || childExpressIds.length === 0) continue;
13651
- writeRelAggregates(w, rel.guid, ownerHistoryId, parentExpressId, childExpressIds);
13652
- }
13653
- for (const rel of relationships) {
13654
- if (rel.kind !== "CONTAINED_IN") continue;
13655
- const structureExpressId = idMap.get(rel.relatingStructure);
13656
- const elementExpressIds = rel.relatedElements.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13657
- if (structureExpressId === void 0 || elementExpressIds.length === 0) continue;
13658
- writeRelContainedInSpatialStructure(w, rel.guid, ownerHistoryId, structureExpressId, elementExpressIds);
13659
- }
13660
- for (const rel of relationships) {
13661
- if (rel.kind !== "SPACE_BOUNDARY") continue;
13662
- const spaceExpressId = idMap.get(rel.spaceLocalId);
13663
- const elementExpressId = idMap.get(rel.elementLocalId);
13664
- if (spaceExpressId === void 0 || elementExpressId === void 0) continue;
13665
- writeRelSpaceBoundary(w, rel.guid, ownerHistoryId, spaceExpressId, elementExpressId, rel.connectionType);
13666
- }
13667
- for (const rel of relationships) {
13668
- if (rel.kind !== "NESTS") continue;
13669
- const parentExpressId = idMap.get(rel.relatingObject);
13670
- const childExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13671
- if (parentExpressId === void 0 || childExpressIds.length === 0) continue;
13672
- writeRelNests(w, rel.guid, ownerHistoryId, parentExpressId, childExpressIds);
13673
- }
13674
- for (const rel of relationships) {
13675
- if (rel.kind !== "COVERS_ELEMENT") continue;
13676
- const hostExpressId = idMap.get(rel.hostLocalId);
13677
- const coveringExpressId = idMap.get(rel.coveringLocalId);
13678
- if (hostExpressId === void 0 || coveringExpressId === void 0) continue;
13679
- writeRelCoversBldgElements(w, rel.guid, ownerHistoryId, hostExpressId, [coveringExpressId]);
13531
+ //#region src/serialize/exactBodyPreflight.ts
13532
+ var testItemPreparer = null;
13533
+ /** Prepares every exact Body item without writing IFC lines. Source solids remain borrowed. */
13534
+ function preflightExactBody(input) {
13535
+ const prepareItem = input.prepareItem ?? testItemPreparer ?? prepareTessellation;
13536
+ const prepareAt = (solid, itemIndex) => {
13537
+ const item = prepareItem(solid);
13538
+ if (!item.ok) return (0, brepjs.err)(ifcError("EXACT_BODY_TESSELLATION_FAILED", `Exact Product Body item ${itemIndex} for ${input.localId} could not be tessellated: ${item.reason}`, item.cause, {
13539
+ localId: input.localId,
13540
+ itemIndex
13541
+ }));
13542
+ return (0, brepjs.ok)(item.value);
13543
+ };
13544
+ const [firstSolid, ...remainingSolids] = input.solids;
13545
+ const first = prepareAt(firstSolid, 0);
13546
+ if (!first.ok) return first;
13547
+ const remainingPrepared = [];
13548
+ for (const [remainingIndex, solid] of remainingSolids.entries()) {
13549
+ const item = prepareAt(solid, remainingIndex + 1);
13550
+ if (!item.ok) return item;
13551
+ remainingPrepared.push(item.value);
13680
13552
  }
13681
- for (const rel of relationships) {
13682
- if (rel.kind !== "ASSIGNS_TO_GROUP") continue;
13683
- const groupExpressId = idMap.get(rel.groupLocalId);
13684
- const memberExpressIds = rel.memberLocalIds.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13685
- if (groupExpressId === void 0 || memberExpressIds.length === 0) continue;
13686
- writeRelAssignsToGroup(w, rel.guid, ownerHistoryId, groupExpressId, memberExpressIds);
13553
+ return (0, brepjs.ok)([first.value, ...remainingPrepared]);
13554
+ }
13555
+ //#endregion
13556
+ //#region src/serialize/exactWallQuantities.ts
13557
+ function deriveExactWallQuantities(input) {
13558
+ const measure = input.dependencies?.measure ?? brepjs.measureVolume;
13559
+ let volumeMm3;
13560
+ if (input.solids.length === 1) try {
13561
+ const measured = measure(input.solids[0]);
13562
+ if (!measured.ok) return exactVolumeError(measured.error);
13563
+ volumeMm3 = measured.value;
13564
+ } catch (cause) {
13565
+ return exactVolumeError(cause);
13687
13566
  }
13688
- for (const rel of relationships) {
13689
- if (rel.kind !== "CONNECTS_ELEMENTS") continue;
13690
- const relatingExpressId = idMap.get(rel.relatingElementLocalId);
13691
- const relatedExpressId = idMap.get(rel.relatedElementLocalId);
13692
- if (relatingExpressId === void 0 || relatedExpressId === void 0) continue;
13693
- writeRelConnectsElements(w, rel.guid, ownerHistoryId, relatingExpressId, relatedExpressId, rel.description ?? null);
13567
+ else {
13568
+ const fuse = input.dependencies?.fuse ?? brepjs.fuseAll;
13569
+ let union = null;
13570
+ try {
13571
+ const fused = fuse([...input.solids]);
13572
+ if (!fused.ok) return exactVolumeError(fused.error);
13573
+ union = fused.value;
13574
+ const measured = measure(union);
13575
+ if (!measured.ok) return exactVolumeError(measured.error);
13576
+ volumeMm3 = measured.value;
13577
+ } catch (cause) {
13578
+ return exactVolumeError(cause);
13579
+ } finally {
13580
+ union?.[Symbol.dispose]();
13581
+ }
13694
13582
  }
13695
- for (const rel of relationships) {
13696
- if (rel.kind !== "CONNECTS_PATH_ELEMENTS") continue;
13697
- const relatingExpressId = idMap.get(rel.relatingElementLocalId);
13698
- const relatedExpressId = idMap.get(rel.relatedElementLocalId);
13699
- if (relatingExpressId === void 0 || relatedExpressId === void 0) continue;
13700
- writeRelConnectsPathElements(w, rel.guid, ownerHistoryId, relatingExpressId, relatedExpressId, rel.relatingConnectionType, rel.relatedConnectionType, rel.description ?? null);
13701
- }
13702
- const bySimpleMaterial = /* @__PURE__ */ new Map();
13703
- const layeredAssociations = [];
13704
- const categoryById = new Map(elements.map((e) => [e.localId, e.category]));
13705
- for (const rel of relationships) {
13706
- if (rel.kind !== "ASSOCIATES_MATERIAL") continue;
13707
- const objectExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13708
- if (objectExpressIds.length === 0) continue;
13709
- if (rel.materialLayers !== void 0 && rel.materialLayers.length > 0) {
13710
- const firstObj = rel.relatedObjects[0];
13711
- const hostCat = firstObj !== void 0 ? categoryById.get(firstObj) : void 0;
13712
- const direction = hostCat === "SLAB" || hostCat === "ROOF" || hostCat === "COVERING" ? "AXIS3" : "AXIS2";
13713
- layeredAssociations.push({
13714
- guid: rel.guid,
13715
- layerSetName: rel.layerSetName ?? rel.materialName,
13716
- layers: rel.materialLayers,
13717
- ids: objectExpressIds,
13718
- direction
13583
+ if (!Number.isFinite(volumeMm3) || volumeMm3 <= 0) return exactVolumeError(/* @__PURE__ */ new Error(`Measured exact wall volume was ${volumeMm3}`));
13584
+ return (0, brepjs.ok)({
13585
+ lengthM: toIfcLengthM(input.spec.length),
13586
+ widthM: toIfcLengthM(input.spec.thickness),
13587
+ heightM: toIfcLengthM(input.spec.height),
13588
+ netVolumeM3: volumeMm3 / 1e9
13589
+ });
13590
+ }
13591
+ function exactVolumeError(cause) {
13592
+ return (0, brepjs.err)(ifcError("IFC_EXACT_WALL_QUANTITY_DERIVATION_FAILED", "Failed to derive a positive finite NetVolume for an exact wall Product Body", cause));
13593
+ }
13594
+ //#endregion
13595
+ //#region src/serialize/toIfc.ts
13596
+ function preflightProductBodies(elements) {
13597
+ const preflighted = [];
13598
+ for (const element of elements) {
13599
+ const body = element.geometry;
13600
+ if (body.kind === "PARAMETRIC") {
13601
+ preflighted.push({
13602
+ kind: "PARAMETRIC",
13603
+ element,
13604
+ solid: body.solid
13719
13605
  });
13720
13606
  continue;
13721
13607
  }
13722
- const existing = bySimpleMaterial.get(rel.materialName);
13723
- if (existing !== void 0) bySimpleMaterial.set(rel.materialName, {
13724
- guid: existing.guid,
13725
- ids: [...existing.ids, ...objectExpressIds]
13608
+ const prepared = preflightExactBody({
13609
+ localId: element.localId,
13610
+ solids: body.solids
13726
13611
  });
13727
- else bySimpleMaterial.set(rel.materialName, {
13728
- guid: rel.guid,
13729
- ids: objectExpressIds
13612
+ if (!prepared.ok) return (0, brepjs.err)(prepared.error);
13613
+ preflighted.push({
13614
+ kind: "EXACT",
13615
+ element,
13616
+ solids: body.solids,
13617
+ items: prepared.value
13730
13618
  });
13731
13619
  }
13732
- for (const [materialName, { guid, ids }] of bySimpleMaterial) {
13733
- if (ids.length === 0) continue;
13734
- writeMaterialSimple(w, guid, ownerHistoryId, materialName, ids);
13735
- }
13736
- for (const assoc of layeredAssociations) writeMaterialLayerSet(w, assoc.guid, ownerHistoryId, {
13737
- kind: "LAYER_SET",
13738
- layerSetName: assoc.layerSetName,
13739
- layers: assoc.layers
13740
- }, assoc.ids, assoc.direction);
13741
- const classByKey = /* @__PURE__ */ new Map();
13742
- for (const rel of relationships) {
13743
- if (rel.kind !== "ASSOCIATES_CLASSIFICATION") continue;
13744
- const objectExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13745
- if (objectExpressIds.length === 0) continue;
13746
- const key = `${rel.ref.system}:${rel.ref.code}`;
13747
- const entry = classByKey.get(key);
13748
- if (entry === void 0) classByKey.set(key, {
13749
- ref: rel.ref,
13750
- ids: [...objectExpressIds]
13620
+ return (0, brepjs.ok)(preflighted);
13621
+ }
13622
+ async function toIfc(model, meta) {
13623
+ try {
13624
+ var _usingCtx$2 = _usingCtx();
13625
+ const project = model.getProject();
13626
+ if (!project) return (0, brepjs.err)(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
13627
+ if ((model.getBridges().length > 0 || model.getBridgeParts().length > 0 || model.getEarthworksFills().length > 0) && (meta.ifcSchema ?? "IFC4") !== "IFC4X3") return (0, brepjs.err)(ifcError("IFC4X3_REQUIRED", "Bridge, Bridge Part, and Earthworks Fill entities require IFC4X3 serialization"));
13628
+ const authorName = [meta.author?.givenName, meta.author?.familyName].filter((p) => Boolean(p)).join(" ");
13629
+ const writerResult = await IfcWriter.create(meta.mvdViewDefinition, meta.ifcSchema, {
13630
+ author: authorName,
13631
+ organization: meta.organizationName
13751
13632
  });
13752
- else entry.ids.push(...objectExpressIds);
13633
+ if (!writerResult.ok) return writerResult;
13634
+ const w = _usingCtx$2.u(writerResult.value);
13635
+ w.setModelScope(project.guid);
13636
+ const elements = model.getAllElements();
13637
+ const relationships = model.getAllRelationships();
13638
+ const walls = model.getWalls();
13639
+ const slabs = model.getSlabs();
13640
+ const beams = model.getBeams();
13641
+ const bridges = model.getBridges();
13642
+ const bridgeParts = model.getBridgeParts();
13643
+ const earthworksFills = model.getEarthworksFills();
13644
+ const columns = model.getColumns();
13645
+ const doors = model.getDoors();
13646
+ const windows = model.getWindows();
13647
+ const proxies = model.getProxies();
13648
+ const spaces = model.getSpaces();
13649
+ const roofs = model.getRoofs();
13650
+ const curtainWalls = model.getCurtainWalls();
13651
+ const footings = model.getFootings();
13652
+ const piles = model.getPiles();
13653
+ const stairs = model.getStairs();
13654
+ const ramps = model.getRamps();
13655
+ const railings = model.getRailings();
13656
+ const coverings = model.getCoverings();
13657
+ const assemblies = model.getElementAssemblies();
13658
+ const zones = model.getZones();
13659
+ const systems = model.getSystems();
13660
+ const preflightedWallsResult = preflightProductBodies(walls);
13661
+ if (!preflightedWallsResult.ok) return (0, brepjs.err)(preflightedWallsResult.error);
13662
+ const preflightedRailingsResult = preflightProductBodies(railings);
13663
+ if (!preflightedRailingsResult.ok) return (0, brepjs.err)(preflightedRailingsResult.error);
13664
+ const preflightedWalls = preflightedWallsResult.value;
13665
+ const preflightedRailings = preflightedRailingsResult.value;
13666
+ const { ownerHistoryId, geomContextId, geomSubContextId, unitAssignmentId, lengthUnitId } = writeHeader(w, meta);
13667
+ if (project.spec.crs !== void 0) writeMapConversion(w, project.spec.crs, geomContextId, lengthUnitId);
13668
+ const densityByElement = buildDensityMap(relationships);
13669
+ const idMap = /* @__PURE__ */ new Map();
13670
+ const placementMap = /* @__PURE__ */ new Map();
13671
+ const projectExpressId = writeProject$1(w, project.guid, project.spec.name, ownerHistoryId, unitAssignmentId, geomContextId);
13672
+ idMap.set(project.localId, projectExpressId);
13673
+ for (const el of elements) {
13674
+ if (el.category !== "SITE") continue;
13675
+ const parentId = findParentOf(el.localId, relationships);
13676
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13677
+ const { entityId, placementId } = writeSite(w, el.guid, el.spec, ownerHistoryId, parentPlacementId);
13678
+ idMap.set(el.localId, entityId);
13679
+ placementMap.set(el.localId, placementId);
13680
+ }
13681
+ for (const bridge of bridges) {
13682
+ const parentId = findParentOf(bridge.localId, relationships);
13683
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13684
+ const { entityId, placementId } = writeBridge(w, bridge.guid, bridge.spec, ownerHistoryId, parentPlacementId);
13685
+ idMap.set(bridge.localId, entityId);
13686
+ placementMap.set(bridge.localId, placementId);
13687
+ }
13688
+ const pendingParts = [...bridgeParts];
13689
+ while (pendingParts.length > 0) {
13690
+ const readyIndex = pendingParts.findIndex((candidate) => {
13691
+ const candidateParentId = findParentOf(candidate.localId, relationships);
13692
+ return candidateParentId === null || placementMap.has(candidateParentId);
13693
+ });
13694
+ const [part] = pendingParts.splice(readyIndex === -1 ? 0 : readyIndex, 1);
13695
+ if (part === void 0) break;
13696
+ const parentId = findParentOf(part.localId, relationships);
13697
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13698
+ const { entityId, placementId } = writeBridgePart(w, part.guid, part.spec, ownerHistoryId, parentPlacementId);
13699
+ idMap.set(part.localId, entityId);
13700
+ placementMap.set(part.localId, placementId);
13701
+ }
13702
+ for (const el of elements) {
13703
+ if (el.category !== "BUILDING") continue;
13704
+ const parentSiteId = findParentOf(el.localId, relationships);
13705
+ const parentPlacementId = parentSiteId !== null ? placementMap.get(parentSiteId) ?? null : null;
13706
+ const { entityId, placementId } = writeBuilding(w, el.guid, el.spec.name, ownerHistoryId, parentPlacementId);
13707
+ idMap.set(el.localId, entityId);
13708
+ placementMap.set(el.localId, placementId);
13709
+ }
13710
+ for (const el of elements) {
13711
+ if (el.category !== "STOREY") continue;
13712
+ const parentBuildingId = findParentOf(el.localId, relationships);
13713
+ const parentPlacementId = parentBuildingId !== null ? placementMap.get(parentBuildingId) ?? null : null;
13714
+ const { entityId, placementId } = writeStorey(w, el.guid, el.spec.name, el.spec.elevation, ownerHistoryId, parentPlacementId);
13715
+ idMap.set(el.localId, entityId);
13716
+ placementMap.set(el.localId, placementId);
13717
+ }
13718
+ const openingsByWall = /* @__PURE__ */ new Map();
13719
+ for (const rel of relationships) {
13720
+ if (rel.kind !== "VOIDS_WALL") continue;
13721
+ const opening = elements.find((el) => el.localId === rel.openingLocalId);
13722
+ if (opening === void 0 || opening.category !== "OPENING") continue;
13723
+ if (!isWallOpening(opening.spec)) continue;
13724
+ const list = openingsByWall.get(rel.wallLocalId) ?? [];
13725
+ list.push(opening.spec);
13726
+ openingsByWall.set(rel.wallLocalId, list);
13727
+ }
13728
+ const openingsBySlab = /* @__PURE__ */ new Map();
13729
+ for (const rel of relationships) {
13730
+ if (rel.kind !== "VOIDS_SLAB") continue;
13731
+ const opening = elements.find((el) => el.localId === rel.openingLocalId);
13732
+ if (opening === void 0 || opening.category !== "OPENING") continue;
13733
+ if (!isSlabOpening(opening.spec)) continue;
13734
+ const list = openingsBySlab.get(rel.slabLocalId) ?? [];
13735
+ list.push(opening.spec);
13736
+ openingsBySlab.set(rel.slabLocalId, list);
13737
+ }
13738
+ for (const [i, preflighted] of preflightedWalls.entries()) {
13739
+ const wall = preflighted.element;
13740
+ const containingId = findContainerOf(wall.localId, relationships);
13741
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13742
+ const exactQuantities = preflighted.kind === "EXACT" ? deriveExactWallQuantities({
13743
+ spec: wall.spec,
13744
+ solids: preflighted.solids
13745
+ }) : null;
13746
+ const { localPlacementId, productDefinitionShapeId } = preflighted.kind === "EXACT" ? writeExactBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId) : writeWallGeometry(w, wall.spec, geomSubContextId, storeyPlacementId);
13747
+ const wallExpressId = writeWallEntity(w, wall.guid, `Wall ${i + 1}`, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13748
+ idMap.set(wall.localId, wallExpressId);
13749
+ placementMap.set(wall.localId, localPlacementId);
13750
+ writeWallCommonPset(w, ownerHistoryId, wallExpressId, wall.spec);
13751
+ writeManufacturerPset(w, ownerHistoryId, wallExpressId, wall.spec);
13752
+ if (wall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, wallExpressId, wall.spec.customProperties);
13753
+ if (preflighted.kind === "EXACT") {
13754
+ if (exactQuantities !== null && exactQuantities.ok) writeExactWallBaseQuantities(w, ownerHistoryId, wallExpressId, exactQuantities.value);
13755
+ } else writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, wall.spec, openingsByWall.get(wall.localId) ?? [], densityByElement.get(wall.localId));
13756
+ }
13757
+ for (const [i, slab] of slabs.entries()) {
13758
+ const containingId = findContainerOf(slab.localId, relationships);
13759
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13760
+ const { localPlacementId, productDefinitionShapeId } = writeSlabGeometry(w, slab.spec, geomSubContextId, storeyPlacementId);
13761
+ const slabExpressId = writeSlabEntity(w, slab.guid, `Slab ${i + 1}`, slab.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13762
+ idMap.set(slab.localId, slabExpressId);
13763
+ placementMap.set(slab.localId, localPlacementId);
13764
+ writeSlabCommonPset(w, ownerHistoryId, slabExpressId, slab.spec);
13765
+ writeManufacturerPset(w, ownerHistoryId, slabExpressId, slab.spec);
13766
+ if (slab.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, slabExpressId, slab.spec.customProperties);
13767
+ writeSlabBaseQuantities(w, ownerHistoryId, slabExpressId, slab.spec, openingsBySlab.get(slab.localId) ?? [], densityByElement.get(slab.localId));
13768
+ }
13769
+ for (const [i, beam] of beams.entries()) {
13770
+ const containingId = findContainerOf(beam.localId, relationships);
13771
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13772
+ const { localPlacementId, productDefinitionShapeId } = writeBeamGeometry(w, beam.spec, geomSubContextId, storeyPlacementId);
13773
+ const beamExpressId = writeBeamEntity(w, beam.guid, `Beam ${i + 1}`, beam.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13774
+ idMap.set(beam.localId, beamExpressId);
13775
+ placementMap.set(beam.localId, localPlacementId);
13776
+ writeBeamCommonPset(w, ownerHistoryId, beamExpressId, beam.spec);
13777
+ writeManufacturerPset(w, ownerHistoryId, beamExpressId, beam.spec);
13778
+ if (beam.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, beamExpressId, beam.spec.customProperties);
13779
+ writeBeamBaseQuantities(w, ownerHistoryId, beamExpressId, beam.spec);
13780
+ }
13781
+ for (const [i, column] of columns.entries()) {
13782
+ const containingId = findContainerOf(column.localId, relationships);
13783
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13784
+ const { localPlacementId, productDefinitionShapeId } = writeColumnGeometry(w, column.spec, geomSubContextId, storeyPlacementId);
13785
+ const columnExpressId = writeColumnEntity(w, column.guid, `Column ${i + 1}`, column.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13786
+ idMap.set(column.localId, columnExpressId);
13787
+ placementMap.set(column.localId, localPlacementId);
13788
+ writeColumnCommonPset(w, ownerHistoryId, columnExpressId, column.spec);
13789
+ writeManufacturerPset(w, ownerHistoryId, columnExpressId, column.spec);
13790
+ if (column.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, columnExpressId, column.spec.customProperties);
13791
+ writeColumnBaseQuantities(w, ownerHistoryId, columnExpressId, column.spec);
13792
+ }
13793
+ for (const [i, proxy] of proxies.entries()) {
13794
+ const containingId = findContainerOf(proxy.localId, relationships);
13795
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13796
+ const { localPlacementId, productDefinitionShapeId } = writeProxyGeometry(w, proxy.spec, geomSubContextId, storeyPlacementId);
13797
+ const proxyExpressId = writeProxyEntity(w, proxy.guid, proxy.spec.name || `Proxy ${i + 1}`, proxy.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13798
+ idMap.set(proxy.localId, proxyExpressId);
13799
+ placementMap.set(proxy.localId, localPlacementId);
13800
+ if (proxy.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, proxyExpressId, proxy.spec.customProperties);
13801
+ }
13802
+ for (const fill of earthworksFills) {
13803
+ const containingId = findContainerOf(fill.localId, relationships);
13804
+ const parentPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13805
+ const { localPlacementId, productDefinitionShapeId } = writeTessellatedProductGeometry(w, fill.geometry, geomSubContextId, parentPlacementId);
13806
+ const expressId = writeEarthworksFillEntity(w, fill.guid, fill.spec, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13807
+ idMap.set(fill.localId, expressId);
13808
+ placementMap.set(fill.localId, localPlacementId);
13809
+ if (fill.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, expressId, fill.spec.customProperties);
13810
+ }
13811
+ for (const [i, space] of spaces.entries()) {
13812
+ const containingId = findContainerOf(space.localId, relationships);
13813
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13814
+ const { localPlacementId, productDefinitionShapeId } = writeSpaceGeometry(w, space.spec, geomSubContextId, storeyPlacementId);
13815
+ const spaceExpressId = writeSpaceEntity(w, space.guid, space.spec.name || `Space ${i + 1}`, space.spec.longName ?? null, space.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13816
+ idMap.set(space.localId, spaceExpressId);
13817
+ placementMap.set(space.localId, localPlacementId);
13818
+ writeSpaceCommonPset(w, ownerHistoryId, spaceExpressId, space.spec);
13819
+ if (space.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, spaceExpressId, space.spec.customProperties);
13820
+ writeSpaceBaseQuantities(w, ownerHistoryId, spaceExpressId, space.spec);
13821
+ }
13822
+ for (const [i, roof] of roofs.entries()) {
13823
+ const containingId = findContainerOf(roof.localId, relationships);
13824
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13825
+ const { localPlacementId, productDefinitionShapeId, usedFallback } = writeRoofGeometry(w, roof.spec, roof.geometry, geomSubContextId, storeyPlacementId);
13826
+ if (usedFallback) console.warn(`Roof ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
13827
+ const roofExpressId = writeRoofEntity(w, roof.guid, `Roof ${i + 1}`, roof.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13828
+ idMap.set(roof.localId, roofExpressId);
13829
+ placementMap.set(roof.localId, localPlacementId);
13830
+ writeRoofCommonPset(w, ownerHistoryId, roofExpressId, roof.spec);
13831
+ writeManufacturerPset(w, ownerHistoryId, roofExpressId, roof.spec);
13832
+ if (roof.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, roofExpressId, roof.spec.customProperties);
13833
+ writeRoofBaseQuantities(w, ownerHistoryId, roofExpressId, roof.spec);
13834
+ }
13835
+ for (const [i, curtainWall] of curtainWalls.entries()) {
13836
+ const containingId = findContainerOf(curtainWall.localId, relationships);
13837
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13838
+ const { curtainWallId } = writeCurtainWall(w, curtainWall.spec, curtainWall.geometry, `elem:CURTAIN_WALL:${curtainWall.localId}`, `Curtain Wall ${i + 1}`, ownerHistoryId, geomSubContextId, storeyPlacementId);
13839
+ idMap.set(curtainWall.localId, curtainWallId);
13840
+ writeCurtainWallCommonPset(w, ownerHistoryId, curtainWallId, curtainWall.spec);
13841
+ if (curtainWall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, curtainWallId, curtainWall.spec.customProperties);
13842
+ }
13843
+ for (const [i, footing] of footings.entries()) {
13844
+ const containingId = findContainerOf(footing.localId, relationships);
13845
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13846
+ const { localPlacementId, productDefinitionShapeId } = writeFootingGeometry(w, footing.spec, geomSubContextId, storeyPlacementId);
13847
+ const footingExpressId = writeFootingEntity(w, footing.guid, `Footing ${i + 1}`, footing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13848
+ idMap.set(footing.localId, footingExpressId);
13849
+ placementMap.set(footing.localId, localPlacementId);
13850
+ writeFootingCommonPset(w, ownerHistoryId, footingExpressId, footing.spec);
13851
+ if (footing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, footingExpressId, footing.spec.customProperties);
13852
+ writeFootingBaseQuantities(w, ownerHistoryId, footingExpressId, footing.spec);
13853
+ }
13854
+ for (const [i, pile] of piles.entries()) {
13855
+ const containingId = findContainerOf(pile.localId, relationships);
13856
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13857
+ const { localPlacementId, productDefinitionShapeId } = writePileGeometry(w, pile.spec, geomSubContextId, storeyPlacementId);
13858
+ const pileExpressId = writePileEntity(w, pile.guid, `Pile ${i + 1}`, pile.spec.predefinedType ?? "NOTDEFINED", pile.spec.constructionType ?? null, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13859
+ idMap.set(pile.localId, pileExpressId);
13860
+ placementMap.set(pile.localId, localPlacementId);
13861
+ writePileCommonPset(w, ownerHistoryId, pileExpressId, pile.spec);
13862
+ if (pile.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, pileExpressId, pile.spec.customProperties);
13863
+ writePileBaseQuantities(w, ownerHistoryId, pileExpressId, pile.spec);
13864
+ }
13865
+ for (const [i, assembly] of assemblies.entries()) {
13866
+ const containingId = findContainerOf(assembly.localId, relationships);
13867
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13868
+ const assemblyExpressId = writeElementAssemblyEntity(w, assembly.guid, assembly.spec.name ?? `Assembly ${i + 1}`, assembly.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, storeyPlacementId, null, assembly.spec.assemblyPlace ?? "NOTDEFINED");
13869
+ idMap.set(assembly.localId, assemblyExpressId);
13870
+ }
13871
+ for (const zone of zones) {
13872
+ const zoneExpressId = writeZoneEntity(w, zone.guid, zone.spec.name, zone.spec.longName ?? null, zone.spec.objectType ?? null, ownerHistoryId);
13873
+ idMap.set(zone.localId, zoneExpressId);
13874
+ }
13875
+ for (const system of systems) {
13876
+ const systemExpressId = writeSystemEntity(w, system.guid, system.spec.name, system.spec.longName ?? null, system.spec.objectType ?? null, ownerHistoryId);
13877
+ idMap.set(system.localId, systemExpressId);
13878
+ }
13879
+ for (const stair of stairs) {
13880
+ const containingId = findContainerOf(stair.localId, relationships);
13881
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13882
+ const result = writeStairAssembly(w, stair.spec, `${stair.localId}`, stair.guid, ownerHistoryId, geomSubContextId, storeyPlacementId);
13883
+ if (!result.ok) return (0, brepjs.err)(result.error);
13884
+ idMap.set(stair.localId, result.value.assemblyExpressId);
13885
+ writeStairCommonPset(w, ownerHistoryId, result.value.assemblyExpressId, stair.spec);
13886
+ }
13887
+ for (const ramp of ramps) {
13888
+ const containingId = findContainerOf(ramp.localId, relationships);
13889
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13890
+ const result = writeRampAssembly(w, ramp.spec, `${ramp.localId}`, ramp.guid, ownerHistoryId, geomSubContextId, storeyPlacementId);
13891
+ if (!result.ok) return (0, brepjs.err)(result.error);
13892
+ idMap.set(ramp.localId, result.value.assemblyExpressId);
13893
+ writeRampCommonPset(w, ownerHistoryId, result.value.assemblyExpressId, ramp.spec);
13894
+ }
13895
+ for (const [i, preflighted] of preflightedRailings.entries()) {
13896
+ const railing = preflighted.element;
13897
+ const containingId = findContainerOf(railing.localId, relationships);
13898
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13899
+ let representation;
13900
+ let exactBodyItemIds = [];
13901
+ if (preflighted.kind === "EXACT") {
13902
+ const exact = writeExactBodyGeometry(w, railing.spec, preflighted.items, geomSubContextId, storeyPlacementId);
13903
+ exactBodyItemIds = exact.bodyItemIds;
13904
+ representation = {
13905
+ ...exact,
13906
+ bodyItemId: null,
13907
+ usedFallback: false
13908
+ };
13909
+ } else representation = writeRailingGeometry(w, railing.spec, preflighted.solid, geomSubContextId, storeyPlacementId);
13910
+ const { localPlacementId, productDefinitionShapeId, bodyItemId, usedFallback } = representation;
13911
+ if (usedFallback) console.warn(`Railing ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
13912
+ const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13913
+ idMap.set(railing.localId, railingExpressId);
13914
+ placementMap.set(railing.localId, localPlacementId);
13915
+ writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
13916
+ writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
13917
+ if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
13918
+ if (preflighted.kind === "EXACT") for (const itemId of exactBodyItemIds) applySurfaceStyle(w, model, railing.localId, itemId);
13919
+ else if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
13920
+ }
13921
+ for (const [i, covering] of coverings.entries()) {
13922
+ const containingId = findContainerOf(covering.localId, relationships);
13923
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13924
+ const { localPlacementId, productDefinitionShapeId, bodyItemId } = writeCoveringGeometry(w, covering.spec, geomSubContextId, storeyPlacementId);
13925
+ const coveringExpressId = writeCoveringEntity(w, covering.guid, `Covering ${i + 1}`, covering.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13926
+ idMap.set(covering.localId, coveringExpressId);
13927
+ placementMap.set(covering.localId, localPlacementId);
13928
+ writeCoveringCommonPset(w, ownerHistoryId, coveringExpressId, covering.spec);
13929
+ writeManufacturerPset(w, ownerHistoryId, coveringExpressId, covering.spec);
13930
+ if (covering.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, coveringExpressId, covering.spec.customProperties);
13931
+ applySurfaceStyle(w, model, covering.localId, bodyItemId);
13932
+ }
13933
+ const openingPlacementMap = /* @__PURE__ */ new Map();
13934
+ const openingEntityMap = /* @__PURE__ */ new Map();
13935
+ for (const rel of relationships) {
13936
+ if (rel.kind !== "VOIDS_WALL") continue;
13937
+ const wallElement = elements.find((el) => el.category === "WALL" && el.localId === rel.wallLocalId);
13938
+ if (wallElement === void 0) continue;
13939
+ const wallExpressId = idMap.get(rel.wallLocalId);
13940
+ const wallPlacementId = placementMap.get(rel.wallLocalId);
13941
+ if (wallExpressId === void 0 || wallPlacementId === void 0) continue;
13942
+ const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
13943
+ if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
13944
+ if (!isWallOpening(openingElement.spec)) continue;
13945
+ const { openingEntityId, openingPlacementId } = writeOpeningGeometry(w, openingElement.guid, openingElement.spec, wallElement.spec, wallPlacementId, geomSubContextId, ownerHistoryId);
13946
+ idMap.set(rel.openingLocalId, openingEntityId);
13947
+ openingPlacementMap.set(rel.openingLocalId, openingPlacementId);
13948
+ openingEntityMap.set(rel.openingLocalId, openingEntityId);
13949
+ writeRelVoidsElement(w, rel.guid, ownerHistoryId, wallExpressId, openingEntityId);
13950
+ }
13951
+ for (const rel of relationships) {
13952
+ if (rel.kind !== "VOIDS_SLAB") continue;
13953
+ const slabElement = elements.find((el) => el.category === "SLAB" && el.localId === rel.slabLocalId);
13954
+ if (slabElement === void 0) continue;
13955
+ const slabExpressId = idMap.get(rel.slabLocalId);
13956
+ const slabPlacementId = placementMap.get(rel.slabLocalId);
13957
+ if (slabExpressId === void 0 || slabPlacementId === void 0) continue;
13958
+ const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
13959
+ if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
13960
+ if (!isSlabOpening(openingElement.spec)) continue;
13961
+ const { openingEntityId } = writeSlabOpeningGeometry(w, openingElement.guid, openingElement.spec, slabElement.spec, slabPlacementId, geomSubContextId, ownerHistoryId);
13962
+ idMap.set(rel.openingLocalId, openingEntityId);
13963
+ writeRelVoidsElement(w, rel.guid, ownerHistoryId, slabExpressId, openingEntityId);
13964
+ }
13965
+ for (const [i, door] of doors.entries()) {
13966
+ const fillsRel = relationships.find((rel) => rel.kind === "FILLS_OPENING" && rel.fillerLocalId === door.localId);
13967
+ if (fillsRel === void 0 || fillsRel.kind !== "FILLS_OPENING") continue;
13968
+ const openingPlacementId = openingPlacementMap.get(fillsRel.openingLocalId);
13969
+ const openingEntityId = openingEntityMap.get(fillsRel.openingLocalId);
13970
+ if (openingPlacementId === void 0 || openingEntityId === void 0) continue;
13971
+ const doorExpressId = writeDoorEntity(w, door.guid, `Door ${i + 1}`, ownerHistoryId, openingPlacementId, geomSubContextId, toIfcLengthM(door.spec.width), toIfcLengthM(door.spec.height));
13972
+ idMap.set(door.localId, doorExpressId);
13973
+ writeRelFillsElement(w, fillsRel.guid, ownerHistoryId, openingEntityId, doorExpressId);
13974
+ writeDoorCommonPset(w, ownerHistoryId, doorExpressId, door.spec);
13975
+ }
13976
+ for (const [i, win] of windows.entries()) {
13977
+ const fillsRel = relationships.find((rel) => rel.kind === "FILLS_OPENING" && rel.fillerLocalId === win.localId);
13978
+ if (fillsRel === void 0 || fillsRel.kind !== "FILLS_OPENING") continue;
13979
+ const openingPlacementId = openingPlacementMap.get(fillsRel.openingLocalId);
13980
+ const openingEntityId = openingEntityMap.get(fillsRel.openingLocalId);
13981
+ if (openingPlacementId === void 0 || openingEntityId === void 0) continue;
13982
+ const windowExpressId = writeWindowEntity(w, win.guid, `Window ${i + 1}`, ownerHistoryId, openingPlacementId, geomSubContextId, toIfcLengthM(win.spec.width), toIfcLengthM(win.spec.height));
13983
+ idMap.set(win.localId, windowExpressId);
13984
+ writeRelFillsElement(w, fillsRel.guid, ownerHistoryId, openingEntityId, windowExpressId);
13985
+ writeWindowCommonPset(w, ownerHistoryId, windowExpressId, win.spec);
13986
+ }
13987
+ for (const rel of relationships) {
13988
+ if (rel.kind !== "AGGREGATES") continue;
13989
+ const parentExpressId = idMap.get(rel.relatingObject);
13990
+ const childExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13991
+ if (parentExpressId === void 0 || childExpressIds.length === 0) continue;
13992
+ writeRelAggregates(w, rel.guid, ownerHistoryId, parentExpressId, childExpressIds);
13993
+ }
13994
+ for (const rel of relationships) {
13995
+ if (rel.kind !== "CONTAINED_IN") continue;
13996
+ const structureExpressId = idMap.get(rel.relatingStructure);
13997
+ const elementExpressIds = rel.relatedElements.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13998
+ if (structureExpressId === void 0 || elementExpressIds.length === 0) continue;
13999
+ writeRelContainedInSpatialStructure(w, rel.guid, ownerHistoryId, structureExpressId, elementExpressIds);
14000
+ }
14001
+ for (const rel of relationships) {
14002
+ if (rel.kind !== "SPACE_BOUNDARY") continue;
14003
+ const spaceExpressId = idMap.get(rel.spaceLocalId);
14004
+ const elementExpressId = idMap.get(rel.elementLocalId);
14005
+ if (spaceExpressId === void 0 || elementExpressId === void 0) continue;
14006
+ writeRelSpaceBoundary(w, rel.guid, ownerHistoryId, spaceExpressId, elementExpressId, rel.connectionType);
14007
+ }
14008
+ for (const rel of relationships) {
14009
+ if (rel.kind !== "NESTS") continue;
14010
+ const parentExpressId = idMap.get(rel.relatingObject);
14011
+ const childExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
14012
+ if (parentExpressId === void 0 || childExpressIds.length === 0) continue;
14013
+ writeRelNests(w, rel.guid, ownerHistoryId, parentExpressId, childExpressIds);
14014
+ }
14015
+ for (const rel of relationships) {
14016
+ if (rel.kind !== "COVERS_ELEMENT") continue;
14017
+ const hostExpressId = idMap.get(rel.hostLocalId);
14018
+ const coveringExpressId = idMap.get(rel.coveringLocalId);
14019
+ if (hostExpressId === void 0 || coveringExpressId === void 0) continue;
14020
+ writeRelCoversBldgElements(w, rel.guid, ownerHistoryId, hostExpressId, [coveringExpressId]);
14021
+ }
14022
+ for (const rel of relationships) {
14023
+ if (rel.kind !== "ASSIGNS_TO_GROUP") continue;
14024
+ const groupExpressId = idMap.get(rel.groupLocalId);
14025
+ const memberExpressIds = rel.memberLocalIds.map((id) => idMap.get(id)).filter((id) => id !== void 0);
14026
+ if (groupExpressId === void 0 || memberExpressIds.length === 0) continue;
14027
+ writeRelAssignsToGroup(w, rel.guid, ownerHistoryId, groupExpressId, memberExpressIds);
14028
+ }
14029
+ for (const rel of relationships) {
14030
+ if (rel.kind !== "CONNECTS_ELEMENTS") continue;
14031
+ const relatingExpressId = idMap.get(rel.relatingElementLocalId);
14032
+ const relatedExpressId = idMap.get(rel.relatedElementLocalId);
14033
+ if (relatingExpressId === void 0 || relatedExpressId === void 0) continue;
14034
+ writeRelConnectsElements(w, rel.guid, ownerHistoryId, relatingExpressId, relatedExpressId, rel.description ?? null);
14035
+ }
14036
+ for (const rel of relationships) {
14037
+ if (rel.kind !== "CONNECTS_PATH_ELEMENTS") continue;
14038
+ const relatingExpressId = idMap.get(rel.relatingElementLocalId);
14039
+ const relatedExpressId = idMap.get(rel.relatedElementLocalId);
14040
+ if (relatingExpressId === void 0 || relatedExpressId === void 0) continue;
14041
+ writeRelConnectsPathElements(w, rel.guid, ownerHistoryId, relatingExpressId, relatedExpressId, rel.relatingConnectionType, rel.relatedConnectionType, rel.description ?? null);
14042
+ }
14043
+ const bySimpleMaterial = /* @__PURE__ */ new Map();
14044
+ const layeredAssociations = [];
14045
+ const categoryById = new Map(elements.map((e) => [e.localId, e.category]));
14046
+ for (const rel of relationships) {
14047
+ if (rel.kind !== "ASSOCIATES_MATERIAL") continue;
14048
+ const objectExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
14049
+ if (objectExpressIds.length === 0) continue;
14050
+ if (rel.materialLayers !== void 0 && rel.materialLayers.length > 0) {
14051
+ const firstObj = rel.relatedObjects[0];
14052
+ const hostCat = firstObj !== void 0 ? categoryById.get(firstObj) : void 0;
14053
+ const direction = hostCat === "SLAB" || hostCat === "ROOF" || hostCat === "COVERING" ? "AXIS3" : "AXIS2";
14054
+ layeredAssociations.push({
14055
+ guid: rel.guid,
14056
+ layerSetName: rel.layerSetName ?? rel.materialName,
14057
+ layers: rel.materialLayers,
14058
+ ids: objectExpressIds,
14059
+ direction
14060
+ });
14061
+ continue;
14062
+ }
14063
+ const existing = bySimpleMaterial.get(rel.materialName);
14064
+ if (existing !== void 0) bySimpleMaterial.set(rel.materialName, {
14065
+ guid: existing.guid,
14066
+ ids: [...existing.ids, ...objectExpressIds]
14067
+ });
14068
+ else bySimpleMaterial.set(rel.materialName, {
14069
+ guid: rel.guid,
14070
+ ids: objectExpressIds
14071
+ });
14072
+ }
14073
+ for (const [materialName, { guid, ids }] of bySimpleMaterial) {
14074
+ if (ids.length === 0) continue;
14075
+ writeMaterialSimple(w, guid, ownerHistoryId, materialName, ids);
14076
+ }
14077
+ for (const assoc of layeredAssociations) writeMaterialLayerSet(w, assoc.guid, ownerHistoryId, {
14078
+ kind: "LAYER_SET",
14079
+ layerSetName: assoc.layerSetName,
14080
+ layers: assoc.layers
14081
+ }, assoc.ids, assoc.direction);
14082
+ const classByKey = /* @__PURE__ */ new Map();
14083
+ for (const rel of relationships) {
14084
+ if (rel.kind !== "ASSOCIATES_CLASSIFICATION") continue;
14085
+ const objectExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
14086
+ if (objectExpressIds.length === 0) continue;
14087
+ const key = `${rel.ref.system}:${rel.ref.code}`;
14088
+ const entry = classByKey.get(key);
14089
+ if (entry === void 0) classByKey.set(key, {
14090
+ ref: rel.ref,
14091
+ ids: [...objectExpressIds]
14092
+ });
14093
+ else entry.ids.push(...objectExpressIds);
14094
+ }
14095
+ const byClassification = /* @__PURE__ */ new Map();
14096
+ for (const { ref, ids } of classByKey.values()) byClassification.set(ref, ids);
14097
+ if (byClassification.size > 0) writeClassificationRefs(w, ownerHistoryId, byClassification);
14098
+ writeTypeLayer(w, ownerHistoryId, model, idMap);
14099
+ return w.save();
14100
+ } catch (_) {
14101
+ _usingCtx$2.e = _;
14102
+ } finally {
14103
+ _usingCtx$2.d();
13753
14104
  }
13754
- const byClassification = /* @__PURE__ */ new Map();
13755
- for (const { ref, ids } of classByKey.values()) byClassification.set(ref, ids);
13756
- if (byClassification.size > 0) writeClassificationRefs(w, ownerHistoryId, byClassification);
13757
- writeTypeLayer(w, ownerHistoryId, model, idMap);
13758
- return w.save();
13759
14105
  }
13760
14106
  /**
13761
14107
  * Auto-derives one IfcType per (category, predefinedType) group of occurrences
@@ -13897,7 +14243,6 @@ async function toIfcValidated(model, meta) {
13897
14243
  function collectGeometryIssues(model) {
13898
14244
  const issues = [];
13899
14245
  const groups = [
13900
- ["Wall", model.getWalls()],
13901
14246
  ["Slab", model.getSlabs()],
13902
14247
  ["Beam", model.getBeams()],
13903
14248
  ["Column", model.getColumns()],
@@ -13907,13 +14252,19 @@ function collectGeometryIssues(model) {
13907
14252
  ["Roof", model.getRoofs()],
13908
14253
  ["Footing", model.getFootings()],
13909
14254
  ["Pile", model.getPiles()],
13910
- ["Railing", model.getRailings()],
13911
14255
  ["Covering", model.getCoverings()]
13912
14256
  ];
13913
14257
  for (const [label, elements] of groups) elements.forEach((el, index) => {
13914
14258
  const report = checkGeometryValidity(el.geometry, `${label} ${index + 1}`);
13915
14259
  issues.push(...report.issues);
13916
14260
  });
14261
+ const productGroups = [["Wall", model.getWalls()], ["Railing", model.getRailings()]];
14262
+ for (const [label, elements] of productGroups) elements.forEach((el, index) => {
14263
+ bodySolids(el.geometry).forEach((solid, itemIndex) => {
14264
+ const report = checkGeometryValidity(solid, `${label} ${index + 1} Body item ${itemIndex + 1}`);
14265
+ issues.push(...report.issues);
14266
+ });
14267
+ });
13917
14268
  model.getRamps().forEach((ramp, index) => {
13918
14269
  if (ramp.spec.flights.length === 0) return;
13919
14270
  issues.push(issue("info", "SIMPLIFIED_GEOMETRY", `Ramp ${index + 1} uses simplified inclined-slab flight geometry`));
@@ -29621,6 +29972,7 @@ exports.PSET_PROPERTY_TYPE_TABLE = PSET_PROPERTY_TYPE_TABLE;
29621
29972
  exports.PSET_TEMPLATES = PSET_TEMPLATES;
29622
29973
  exports.SpfReader = SpfReader;
29623
29974
  exports.bcfError = bcfError;
29975
+ exports.bodySolids = bodySolids;
29624
29976
  exports.checkGeometryValidity = checkGeometryValidity;
29625
29977
  exports.checkGherkinRules = checkGherkinRules;
29626
29978
  exports.checkIds = checkIdsData;