brepjs-bim 0.23.2 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, cut, err, extrude, fuse, getBounds, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, translate, validSolid } from "brepjs";
1
+ import { addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, cut, err, extrude, fuse, fuseAll, getBounds, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, locate, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, translate, validSolid } from "brepjs";
2
2
  import * as WebIFC from "web-ifc";
3
3
  import { Handle, IfcAPI } from "web-ifc";
4
4
  //#region src/identity/ifcGuid.ts
@@ -111,12 +111,13 @@ function specError(code, message, cause) {
111
111
  cause
112
112
  };
113
113
  }
114
- function ifcError(code, message, cause) {
114
+ function ifcError(code, message, cause, metadata) {
115
115
  return {
116
116
  kind: "BIM_IFC",
117
117
  code,
118
118
  message,
119
- cause
119
+ cause,
120
+ ...metadata !== void 0 ? { metadata } : {}
120
121
  };
121
122
  }
122
123
  function geometryError(code, message, cause) {
@@ -242,7 +243,7 @@ function _usingCtx() {
242
243
  //#region src/elementFns/wallFns.ts
243
244
  function wallToSolid(spec) {
244
245
  try {
245
- var _usingCtx$19 = _usingCtx();
246
+ var _usingCtx$20 = _usingCtx();
246
247
  if (spec.length <= 0) return err(specError("WALL_ZERO_LENGTH", "Wall length must be positive"));
247
248
  if (spec.height <= 0) return err(specError("WALL_ZERO_HEIGHT", "Wall height must be positive"));
248
249
  if (spec.thickness <= 0) return err(specError("WALL_ZERO_THICKNESS", "Wall thickness must be positive"));
@@ -270,7 +271,7 @@ function wallToSolid(spec) {
270
271
  ]
271
272
  ]);
272
273
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "WALL_PROFILE_FAILED", "Failed to create wall profile"));
273
- const profile = _usingCtx$19.u(profileResult.value);
274
+ const profile = _usingCtx$20.u(profileResult.value);
274
275
  const solidResult = extrude(profile, [
275
276
  length,
276
277
  0,
@@ -284,16 +285,16 @@ function wallToSolid(spec) {
284
285
  }
285
286
  return ok(solid);
286
287
  } catch (_) {
287
- _usingCtx$19.e = _;
288
+ _usingCtx$20.e = _;
288
289
  } finally {
289
- _usingCtx$19.d();
290
+ _usingCtx$20.d();
290
291
  }
291
292
  }
292
293
  //#endregion
293
294
  //#region src/elementFns/slabFns.ts
294
295
  function slabToSolid(spec) {
295
296
  try {
296
- var _usingCtx$18 = _usingCtx();
297
+ var _usingCtx$19 = _usingCtx();
297
298
  if (spec.length <= 0) return err(specError("SLAB_ZERO_LENGTH", "Slab length must be positive"));
298
299
  if (spec.width <= 0) return err(specError("SLAB_ZERO_WIDTH", "Slab width must be positive"));
299
300
  if (spec.thickness <= 0) return err(specError("SLAB_ZERO_THICKNESS", "Slab thickness must be positive"));
@@ -321,7 +322,7 @@ function slabToSolid(spec) {
321
322
  ]
322
323
  ]);
323
324
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SLAB_PROFILE_FAILED", "Failed to create slab profile"));
324
- const profile = _usingCtx$18.u(profileResult.value);
325
+ const profile = _usingCtx$19.u(profileResult.value);
325
326
  const solidResult = extrude(profile, [
326
327
  0,
327
328
  0,
@@ -335,9 +336,9 @@ function slabToSolid(spec) {
335
336
  }
336
337
  return ok(solid);
337
338
  } catch (_) {
338
- _usingCtx$18.e = _;
339
+ _usingCtx$19.e = _;
339
340
  } finally {
340
- _usingCtx$18.d();
341
+ _usingCtx$19.d();
341
342
  }
342
343
  }
343
344
  //#endregion
@@ -5025,14 +5026,14 @@ function to3D(points) {
5025
5026
  }
5026
5027
  function extendedProfileToFace(profile) {
5027
5028
  try {
5028
- var _usingCtx$17 = _usingCtx();
5029
+ var _usingCtx$18 = _usingCtx();
5029
5030
  const invalid = validateProfile(profile);
5030
5031
  if (invalid !== null) return err(invalid);
5031
5032
  const outerResult = polygon(to3D(outerLoop(profile)));
5032
5033
  if (!outerResult.ok) return err(fromBrepError(outerResult.error, "PROFILE_FACE_FAILED", "Failed to build profile outer face"));
5033
5034
  const holes = holeLoops(profile);
5034
5035
  if (holes.length === 0) return ok(outerResult.value);
5035
- const outerFace = _usingCtx$17.u(outerResult.value);
5036
+ const outerFace = _usingCtx$18.u(outerResult.value);
5036
5037
  const holeFaces = [];
5037
5038
  const holeWires = [];
5038
5039
  const disposeHoleFaces = () => {
@@ -5057,9 +5058,9 @@ function extendedProfileToFace(profile) {
5057
5058
  disposeHoleFaces();
5058
5059
  return ok(faceWithHoles);
5059
5060
  } catch (_) {
5060
- _usingCtx$17.e = _;
5061
+ _usingCtx$18.e = _;
5061
5062
  } finally {
5062
- _usingCtx$17.d();
5063
+ _usingCtx$18.d();
5063
5064
  }
5064
5065
  }
5065
5066
  //#endregion
@@ -5381,7 +5382,7 @@ function profileToPolygon(profile, circleSegments = 32) {
5381
5382
  //#region src/elementFns/beamFns.ts
5382
5383
  function beamToSolid(spec) {
5383
5384
  try {
5384
- var _usingCtx$16 = _usingCtx();
5385
+ var _usingCtx$17 = _usingCtx();
5385
5386
  if (spec.length <= 0) return err(specError("BEAM_ZERO_LENGTH", "Beam length must be positive"));
5386
5387
  if (isExtendedProfile(spec.profile)) try {
5387
5388
  var _usingCtx3 = _usingCtx();
@@ -5419,7 +5420,7 @@ function beamToSolid(spec) {
5419
5420
  ]);
5420
5421
  const profileResult = polygon(profilePts);
5421
5422
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "BEAM_PROFILE_FAILED", "Failed to create beam profile"));
5422
- const profile = _usingCtx$16.u(profileResult.value);
5423
+ const profile = _usingCtx$17.u(profileResult.value);
5423
5424
  const solidResult = extrude(profile, [
5424
5425
  spec.length,
5425
5426
  0,
@@ -5433,16 +5434,16 @@ function beamToSolid(spec) {
5433
5434
  }
5434
5435
  return ok(solid);
5435
5436
  } catch (_) {
5436
- _usingCtx$16.e = _;
5437
+ _usingCtx$17.e = _;
5437
5438
  } finally {
5438
- _usingCtx$16.d();
5439
+ _usingCtx$17.d();
5439
5440
  }
5440
5441
  }
5441
5442
  //#endregion
5442
5443
  //#region src/elementFns/columnFns.ts
5443
5444
  function columnToSolid(spec) {
5444
5445
  try {
5445
- var _usingCtx$15 = _usingCtx();
5446
+ var _usingCtx$16 = _usingCtx();
5446
5447
  if (spec.height <= 0) return err(specError("COLUMN_ZERO_HEIGHT", "Column height must be positive"));
5447
5448
  if (isExtendedProfile(spec.profile)) try {
5448
5449
  var _usingCtx3 = _usingCtx();
@@ -5471,7 +5472,7 @@ function columnToSolid(spec) {
5471
5472
  const profilePts = profilePtsResult.value;
5472
5473
  const profileResult = polygon(profilePts);
5473
5474
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "COLUMN_PROFILE_FAILED", "Failed to create column profile"));
5474
- const profile = _usingCtx$15.u(profileResult.value);
5475
+ const profile = _usingCtx$16.u(profileResult.value);
5475
5476
  const solidResult = extrude(profile, [
5476
5477
  0,
5477
5478
  0,
@@ -5485,9 +5486,9 @@ function columnToSolid(spec) {
5485
5486
  }
5486
5487
  return ok(solid);
5487
5488
  } catch (_) {
5488
- _usingCtx$15.e = _;
5489
+ _usingCtx$16.e = _;
5489
5490
  } finally {
5490
- _usingCtx$15.d();
5491
+ _usingCtx$16.d();
5491
5492
  }
5492
5493
  }
5493
5494
  //#endregion
@@ -5495,7 +5496,7 @@ function columnToSolid(spec) {
5495
5496
  var EPSILON_MM = 1;
5496
5497
  function openingToSolid(spec, wallThickness) {
5497
5498
  try {
5498
- var _usingCtx$14 = _usingCtx();
5499
+ var _usingCtx$15 = _usingCtx();
5499
5500
  if (spec.width <= 0) return err(specError("OPENING_ZERO_WIDTH", "Opening width must be positive"));
5500
5501
  if (spec.height <= 0) return err(specError("OPENING_ZERO_HEIGHT", "Opening height must be positive"));
5501
5502
  if (wallThickness <= 0) return err(specError("OPENING_ZERO_WALL_THICKNESS", "Wall thickness must be positive"));
@@ -5527,7 +5528,7 @@ function openingToSolid(spec, wallThickness) {
5527
5528
  ]
5528
5529
  ]);
5529
5530
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "OPENING_PROFILE_FAILED", "Failed to create opening profile"));
5530
- const profile = _usingCtx$14.u(profileResult.value);
5531
+ const profile = _usingCtx$15.u(profileResult.value);
5531
5532
  const solidResult = extrude(profile, [
5532
5533
  spec.width,
5533
5534
  0,
@@ -5541,16 +5542,16 @@ function openingToSolid(spec, wallThickness) {
5541
5542
  }
5542
5543
  return ok(solid);
5543
5544
  } catch (_) {
5544
- _usingCtx$14.e = _;
5545
+ _usingCtx$15.e = _;
5545
5546
  } finally {
5546
- _usingCtx$14.d();
5547
+ _usingCtx$15.d();
5547
5548
  }
5548
5549
  }
5549
5550
  //#endregion
5550
5551
  //#region src/elementFns/slabOpeningFns.ts
5551
5552
  function slabOpeningToSolid(spec, slabThickness) {
5552
5553
  try {
5553
- var _usingCtx$13 = _usingCtx();
5554
+ var _usingCtx$14 = _usingCtx();
5554
5555
  if (spec.sizeX <= 0) return err(specError("SLAB_OPENING_ZERO_SIZE_X", "Slab opening sizeX must be positive"));
5555
5556
  if (spec.sizeY <= 0) return err(specError("SLAB_OPENING_ZERO_SIZE_Y", "Slab opening sizeY must be positive"));
5556
5557
  if (slabThickness <= 0) return err(specError("SLAB_OPENING_ZERO_SLAB_THICKNESS", "Slab thickness must be positive"));
@@ -5582,7 +5583,7 @@ function slabOpeningToSolid(spec, slabThickness) {
5582
5583
  ]
5583
5584
  ]);
5584
5585
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SLAB_OPENING_PROFILE_FAILED", "Failed to create slab opening profile"));
5585
- const profile = _usingCtx$13.u(profileResult.value);
5586
+ const profile = _usingCtx$14.u(profileResult.value);
5586
5587
  const solidResult = extrude(profile, [
5587
5588
  0,
5588
5589
  0,
@@ -5596,16 +5597,16 @@ function slabOpeningToSolid(spec, slabThickness) {
5596
5597
  }
5597
5598
  return ok(solid);
5598
5599
  } catch (_) {
5599
- _usingCtx$13.e = _;
5600
+ _usingCtx$14.e = _;
5600
5601
  } finally {
5601
- _usingCtx$13.d();
5602
+ _usingCtx$14.d();
5602
5603
  }
5603
5604
  }
5604
5605
  //#endregion
5605
5606
  //#region src/elementFns/spaceFns.ts
5606
5607
  function spaceToSolid(spec) {
5607
5608
  try {
5608
- var _usingCtx$12 = _usingCtx();
5609
+ var _usingCtx$13 = _usingCtx();
5609
5610
  if (spec.length <= 0) return err(specError("SPACE_ZERO_LENGTH", "Space length must be positive"));
5610
5611
  if (spec.width <= 0) return err(specError("SPACE_ZERO_WIDTH", "Space width must be positive"));
5611
5612
  if (spec.height <= 0) return err(specError("SPACE_ZERO_HEIGHT", "Space height must be positive"));
@@ -5633,7 +5634,7 @@ function spaceToSolid(spec) {
5633
5634
  ]
5634
5635
  ]);
5635
5636
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SPACE_PROFILE_FAILED", "Failed to create space footprint"));
5636
- const profile = _usingCtx$12.u(profileResult.value);
5637
+ const profile = _usingCtx$13.u(profileResult.value);
5637
5638
  const solidResult = extrude(profile, [
5638
5639
  0,
5639
5640
  0,
@@ -5647,9 +5648,9 @@ function spaceToSolid(spec) {
5647
5648
  }
5648
5649
  return ok(solid);
5649
5650
  } catch (_) {
5650
- _usingCtx$12.e = _;
5651
+ _usingCtx$13.e = _;
5651
5652
  } finally {
5652
- _usingCtx$12.d();
5653
+ _usingCtx$13.d();
5653
5654
  }
5654
5655
  }
5655
5656
  //#endregion
@@ -5664,7 +5665,7 @@ function gate(solid, code) {
5664
5665
  }
5665
5666
  function flatRoof(spec) {
5666
5667
  try {
5667
- var _usingCtx$11 = _usingCtx();
5668
+ var _usingCtx$12 = _usingCtx();
5668
5669
  const { length, width, thickness } = spec;
5669
5670
  const face = polygon([
5670
5671
  [
@@ -5689,7 +5690,7 @@ function flatRoof(spec) {
5689
5690
  ]
5690
5691
  ]);
5691
5692
  if (!face.ok) return err(fromBrepError(face.error, "ROOF_PROFILE_FAILED", "Failed to create roof profile"));
5692
- const profile = _usingCtx$11.u(face.value);
5693
+ const profile = _usingCtx$12.u(face.value);
5693
5694
  const solid = extrude(profile, [
5694
5695
  0,
5695
5696
  0,
@@ -5698,9 +5699,9 @@ function flatRoof(spec) {
5698
5699
  if (!solid.ok) return err(fromBrepError(solid.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude roof profile"));
5699
5700
  return gate(solid.value, "ROOF_INVALID_SOLID");
5700
5701
  } catch (_) {
5701
- _usingCtx$11.e = _;
5702
+ _usingCtx$12.e = _;
5702
5703
  } finally {
5703
- _usingCtx$11.d();
5704
+ _usingCtx$12.d();
5704
5705
  }
5705
5706
  }
5706
5707
  function shedRoof(spec, pitch) {
@@ -5896,7 +5897,7 @@ function roofToSolid(spec) {
5896
5897
  //#region src/elementFns/curtainWallFns.ts
5897
5898
  function boxSolid(sizeX, sizeY, sizeZ) {
5898
5899
  try {
5899
- var _usingCtx$10 = _usingCtx();
5900
+ var _usingCtx$11 = _usingCtx();
5900
5901
  const profileResult = polygon([
5901
5902
  [
5902
5903
  0,
@@ -5920,7 +5921,7 @@ function boxSolid(sizeX, sizeY, sizeZ) {
5920
5921
  ]
5921
5922
  ]);
5922
5923
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "CURTAIN_WALL_PROFILE_FAILED", "Failed to create component profile"));
5923
- const profile = _usingCtx$10.u(profileResult.value);
5924
+ const profile = _usingCtx$11.u(profileResult.value);
5924
5925
  const solidResult = extrude(profile, [
5925
5926
  0,
5926
5927
  0,
@@ -5934,9 +5935,9 @@ function boxSolid(sizeX, sizeY, sizeZ) {
5934
5935
  }
5935
5936
  return ok(solid);
5936
5937
  } catch (_) {
5937
- _usingCtx$10.e = _;
5938
+ _usingCtx$11.e = _;
5938
5939
  } finally {
5939
- _usingCtx$10.d();
5940
+ _usingCtx$11.d();
5940
5941
  }
5941
5942
  }
5942
5943
  function disposeComponents(components) {
@@ -6035,7 +6036,7 @@ function curtainWallToGrid(spec) {
6035
6036
  //#region src/elementFns/foundationFns.ts
6036
6037
  function footingToSolid(spec) {
6037
6038
  try {
6038
- var _usingCtx$9 = _usingCtx();
6039
+ var _usingCtx$10 = _usingCtx();
6039
6040
  if (spec.length <= 0) return err(specError("FOOTING_ZERO_LENGTH", "Footing length must be positive"));
6040
6041
  if (spec.width <= 0) return err(specError("FOOTING_ZERO_WIDTH", "Footing width must be positive"));
6041
6042
  if (spec.thickness <= 0) return err(specError("FOOTING_ZERO_THICKNESS", "Footing thickness must be positive"));
@@ -6063,7 +6064,7 @@ function footingToSolid(spec) {
6063
6064
  ]
6064
6065
  ]);
6065
6066
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "FOOTING_PROFILE_FAILED", "Failed to create footing profile"));
6066
- const profile = _usingCtx$9.u(profileResult.value);
6067
+ const profile = _usingCtx$10.u(profileResult.value);
6067
6068
  const solidResult = extrude(profile, [
6068
6069
  0,
6069
6070
  0,
@@ -6077,9 +6078,9 @@ function footingToSolid(spec) {
6077
6078
  }
6078
6079
  return ok(solid);
6079
6080
  } catch (_) {
6080
- _usingCtx$9.e = _;
6081
+ _usingCtx$10.e = _;
6081
6082
  } finally {
6082
- _usingCtx$9.d();
6083
+ _usingCtx$10.d();
6083
6084
  }
6084
6085
  }
6085
6086
  function pileToSolid(spec) {
@@ -6136,7 +6137,7 @@ function pileToSolid(spec) {
6136
6137
  //#region src/elementFns/railingFns.ts
6137
6138
  function panelRailing(spec) {
6138
6139
  try {
6139
- var _usingCtx$8 = _usingCtx();
6140
+ var _usingCtx$9 = _usingCtx();
6140
6141
  const { length, height, thickness } = spec;
6141
6142
  const profileResult = polygon([
6142
6143
  [
@@ -6161,7 +6162,7 @@ function panelRailing(spec) {
6161
6162
  ]
6162
6163
  ]);
6163
6164
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "RAILING_PROFILE_FAILED", "Failed to create railing profile"));
6164
- const profile = _usingCtx$8.u(profileResult.value);
6165
+ const profile = _usingCtx$9.u(profileResult.value);
6165
6166
  const solidResult = extrude(profile, [
6166
6167
  length,
6167
6168
  0,
@@ -6175,9 +6176,9 @@ function panelRailing(spec) {
6175
6176
  }
6176
6177
  return ok(solid);
6177
6178
  } catch (_) {
6178
- _usingCtx$8.e = _;
6179
+ _usingCtx$9.e = _;
6179
6180
  } finally {
6180
- _usingCtx$8.d();
6181
+ _usingCtx$9.d();
6181
6182
  }
6182
6183
  }
6183
6184
  function postedRailing(spec) {
@@ -6240,7 +6241,7 @@ function railingToSolid(spec) {
6240
6241
  //#region src/elementFns/coveringFns.ts
6241
6242
  function coveringToSolid(spec) {
6242
6243
  try {
6243
- var _usingCtx$7 = _usingCtx();
6244
+ var _usingCtx$8 = _usingCtx();
6244
6245
  if (spec.length <= 0) return err(specError("COVERING_ZERO_LENGTH", "Covering length must be positive"));
6245
6246
  if (spec.width <= 0) return err(specError("COVERING_ZERO_WIDTH", "Covering width must be positive"));
6246
6247
  if (spec.thickness <= 0) return err(specError("COVERING_ZERO_THICKNESS", "Covering thickness must be positive"));
@@ -6268,7 +6269,7 @@ function coveringToSolid(spec) {
6268
6269
  ]
6269
6270
  ]);
6270
6271
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "COVERING_PROFILE_FAILED", "Failed to create covering profile"));
6271
- const profile = _usingCtx$7.u(profileResult.value);
6272
+ const profile = _usingCtx$8.u(profileResult.value);
6272
6273
  const solidResult = extrude(profile, [
6273
6274
  0,
6274
6275
  0,
@@ -6282,13 +6283,29 @@ function coveringToSolid(spec) {
6282
6283
  }
6283
6284
  return ok(solid);
6284
6285
  } catch (_) {
6285
- _usingCtx$7.e = _;
6286
+ _usingCtx$8.e = _;
6286
6287
  } finally {
6287
- _usingCtx$7.d();
6288
+ _usingCtx$8.d();
6289
+ }
6290
+ }
6291
+ //#endregion
6292
+ //#region src/types/productBody.ts
6293
+ /** Returns borrowed Product-local solids. The model retains ownership. */
6294
+ function bodySolids(body) {
6295
+ switch (body.kind) {
6296
+ case "PARAMETRIC": return [body.solid];
6297
+ case "EXACT": return body.solids;
6288
6298
  }
6289
6299
  }
6300
+ /** Model-owner cleanup. Borrowers must use {@link bodySolids} without disposing its items. */
6301
+ function disposeProductBody(body) {
6302
+ for (const solid of bodySolids(body)) solid[Symbol.dispose]();
6303
+ }
6290
6304
  //#endregion
6291
6305
  //#region src/model/bimModel.ts
6306
+ function exactWallBodyImmutable() {
6307
+ return err(specError("EXACT_WALL_BODY_IMMUTABLE", "Cannot add an opening after a wall has taken an exact Product Body"));
6308
+ }
6292
6309
  var BimModel = class {
6293
6310
  #elements = /* @__PURE__ */ new Map();
6294
6311
  #relationships = /* @__PURE__ */ new Map();
@@ -6307,7 +6324,8 @@ var BimModel = class {
6307
6324
  return ok(id);
6308
6325
  }
6309
6326
  [Symbol.dispose]() {
6310
- 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]();
6327
+ 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]();
6328
+ else if (el.category === "WALL" || el.category === "RAILING") disposeProductBody(el.geometry);
6311
6329
  else if (el.category === "CURTAIN_WALL") {
6312
6330
  for (const panel of el.geometry.panels) panel.solid[Symbol.dispose]();
6313
6331
  for (const mullion of el.geometry.mullions) mullion.solid[Symbol.dispose]();
@@ -6368,7 +6386,10 @@ var BimModel = class {
6368
6386
  if (!keyCheck.ok) return keyCheck;
6369
6387
  const geomResult = wallToSolid(spec);
6370
6388
  if (!geomResult.ok) return err(geomResult.error);
6371
- const id = this.#makeElement("WALL", spec, geomResult.value, options?.stableKey);
6389
+ const id = this.#makeElement("WALL", spec, {
6390
+ kind: "PARAMETRIC",
6391
+ solid: geomResult.value
6392
+ }, options?.stableKey);
6372
6393
  this.#associateMaterial(id, spec);
6373
6394
  this.#associateClassification(id, spec);
6374
6395
  return ok(id);
@@ -6483,12 +6504,47 @@ var BimModel = class {
6483
6504
  if (!keyCheck.ok) return keyCheck;
6484
6505
  const geomResult = railingToSolid(spec);
6485
6506
  if (!geomResult.ok) return err(geomResult.error);
6486
- const id = this.#makeElement("RAILING", spec, geomResult.value, options?.stableKey);
6507
+ const id = this.#makeElement("RAILING", spec, {
6508
+ kind: "PARAMETRIC",
6509
+ solid: geomResult.value
6510
+ }, options?.stableKey);
6487
6511
  this.#associateMaterial(id, spec);
6488
6512
  this.#associateClassification(id, spec);
6489
6513
  return ok(id);
6490
6514
  }
6491
6515
  /**
6516
+ * Atomically replaces a parametric wall or railing Body with authoritative,
6517
+ * caller-owned exact solids. Success transfers every supplied handle to this
6518
+ * model. Failure leaves both the model and all supplied handles unchanged.
6519
+ */
6520
+ takeExactProductBody(localId, body) {
6521
+ const target = this.#elements.get(localId);
6522
+ if (target === void 0) return err(specError("EXACT_BODY_TARGET_NOT_FOUND", `No element found for localId ${localId}`));
6523
+ if (target.category !== "WALL" && target.category !== "RAILING") return err(specError("EXACT_BODY_UNSUPPORTED_CATEGORY", `Exact Product Bodies are supported only for walls and railings, not ${target.category}`));
6524
+ if (target.geometry.kind === "EXACT") return err(specError("EXACT_BODY_ALREADY_EXACT", `Element ${localId} already has an exact Product Body`));
6525
+ const solids = body.solids;
6526
+ if (solids.length === 0) return err(specError("EXACT_BODY_EMPTY", "An exact Product Body must contain at least one solid"));
6527
+ if (solids.includes(target.geometry.solid)) return err(specError("EXACT_BODY_SOLID_OWNERSHIP_CONFLICT", `Exact Product Body for ${localId} reuses the target parametric solid`));
6528
+ const identities = /* @__PURE__ */ new Set();
6529
+ for (const [itemIndex, solid] of solids.entries()) {
6530
+ if (identities.has(solid)) return err(specError("EXACT_BODY_DUPLICATE_SOLID", `Exact Product Body item ${itemIndex} duplicates an earlier handle`));
6531
+ identities.add(solid);
6532
+ if (solid.disposed) return err(specError("EXACT_BODY_SOLID_DISPOSED", `Exact Product Body item ${itemIndex} is already disposed`));
6533
+ try {
6534
+ if (!isValidSolid(solid)) return err(specError("EXACT_BODY_SOLID_INVALID", `Exact Product Body item ${itemIndex} is not a valid solid`));
6535
+ } catch (cause) {
6536
+ return err(specError("EXACT_BODY_SOLID_INVALID", `Exact Product Body item ${itemIndex} could not be validated`, cause));
6537
+ }
6538
+ }
6539
+ const replaced = {
6540
+ ...target,
6541
+ geometry: body
6542
+ };
6543
+ this.#elements.set(localId, replaced);
6544
+ disposeProductBody(target.geometry);
6545
+ return ok(void 0);
6546
+ }
6547
+ /**
6492
6548
  * Adds an IfcCovering. When `hostLocalId` is supplied, an
6493
6549
  * IfcRelCoversBldgElements linking the covering to its host (e.g. a slab it
6494
6550
  * finishes) is recorded for export.
@@ -6685,10 +6741,11 @@ var BimModel = class {
6685
6741
  return ok(id);
6686
6742
  }
6687
6743
  addDoor(spec, options) {
6688
- const keyCheck = this.#checkOpeningKeys(options);
6689
- if (!keyCheck.ok) return keyCheck;
6690
6744
  const wall = this.#elements.get(spec.wallLocalId);
6691
6745
  if (wall === void 0 || wall.category !== "WALL") return err(specError("DOOR_WALL_NOT_FOUND", `No wall found for localId ${spec.wallLocalId}`));
6746
+ if (wall.geometry.kind === "EXACT") return exactWallBodyImmutable();
6747
+ const keyCheck = this.#checkOpeningKeys(options);
6748
+ if (!keyCheck.ok) return keyCheck;
6692
6749
  if (spec.offsetAlongWall + spec.width > wall.spec.length) return err(specError("DOOR_EXCEEDS_WALL_BOUNDS", "Door (offsetAlongWall + width) exceeds wall length"));
6693
6750
  if (spec.offsetFromFloor + spec.height > wall.spec.height) return err(specError("DOOR_EXCEEDS_WALL_BOUNDS", "Door (offsetFromFloor + height) exceeds wall height"));
6694
6751
  const openingSpec = {
@@ -6721,10 +6778,11 @@ var BimModel = class {
6721
6778
  return ok(doorId);
6722
6779
  }
6723
6780
  addWindow(spec, options) {
6724
- const keyCheck = this.#checkOpeningKeys(options);
6725
- if (!keyCheck.ok) return keyCheck;
6726
6781
  const wall = this.#elements.get(spec.wallLocalId);
6727
6782
  if (wall === void 0 || wall.category !== "WALL") return err(specError("WINDOW_WALL_NOT_FOUND", `No wall found for localId ${spec.wallLocalId}`));
6783
+ if (wall.geometry.kind === "EXACT") return exactWallBodyImmutable();
6784
+ const keyCheck = this.#checkOpeningKeys(options);
6785
+ if (!keyCheck.ok) return keyCheck;
6728
6786
  if (spec.offsetAlongWall + spec.width > wall.spec.length) return err(specError("WINDOW_EXCEEDS_WALL_BOUNDS", "Window (offsetAlongWall + width) exceeds wall length"));
6729
6787
  if (spec.offsetFromFloor + spec.height > wall.spec.height) return err(specError("WINDOW_EXCEEDS_WALL_BOUNDS", "Window (offsetFromFloor + height) exceeds wall height"));
6730
6788
  const openingSpec = {
@@ -6798,27 +6856,31 @@ var BimModel = class {
6798
6856
  }
6799
6857
  #cutWallGeometry(wall, openingSpec) {
6800
6858
  try {
6801
- var _usingCtx$6 = _usingCtx();
6859
+ var _usingCtx$7 = _usingCtx();
6860
+ if (wall.geometry.kind === "EXACT") return exactWallBodyImmutable();
6802
6861
  const toolResult = openingToSolid(openingSpec, wall.spec.thickness);
6803
6862
  if (!toolResult.ok) return err(toolResult.error);
6804
- const tool = _usingCtx$6.u(toolResult.value);
6805
- const cutResult = cut(wall.geometry, tool);
6863
+ const tool = _usingCtx$7.u(toolResult.value);
6864
+ const cutResult = cut(wall.geometry.solid, tool);
6806
6865
  if (!cutResult.ok) return err(fromBrepError(cutResult.error, "WALL_CUT_FAILED", "Boolean cut of wall with opening failed"));
6807
6866
  return ok(cutResult.value);
6808
6867
  } catch (_) {
6809
- _usingCtx$6.e = _;
6868
+ _usingCtx$7.e = _;
6810
6869
  } finally {
6811
- _usingCtx$6.d();
6870
+ _usingCtx$7.d();
6812
6871
  }
6813
6872
  }
6814
6873
  #replaceWallGeometry(wall, newGeometry) {
6815
6874
  const oldGeometry = wall.geometry;
6816
6875
  const replaced = {
6817
6876
  ...wall,
6818
- geometry: newGeometry
6877
+ geometry: {
6878
+ kind: "PARAMETRIC",
6879
+ solid: newGeometry
6880
+ }
6819
6881
  };
6820
6882
  this.#elements.set(wall.localId, replaced);
6821
- oldGeometry[Symbol.dispose]();
6883
+ disposeProductBody(oldGeometry);
6822
6884
  }
6823
6885
  #cutSlabGeometry(slab, openingSpec) {
6824
6886
  try {
@@ -7077,6 +7139,129 @@ var BimModel = class {
7077
7139
  }
7078
7140
  };
7079
7141
  //#endregion
7142
+ //#region src/elementFns/stairFns.ts
7143
+ function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
7144
+ const pts = [];
7145
+ pts.push([
7146
+ 0,
7147
+ 0,
7148
+ 0
7149
+ ]);
7150
+ let x = 0;
7151
+ let z = 0;
7152
+ for (let i = 0; i < numberOfRisers; i++) {
7153
+ z += riserHeight;
7154
+ pts.push([
7155
+ x,
7156
+ 0,
7157
+ z
7158
+ ]);
7159
+ x += treadLength;
7160
+ pts.push([
7161
+ x,
7162
+ 0,
7163
+ z
7164
+ ]);
7165
+ }
7166
+ pts.push([
7167
+ x,
7168
+ 0,
7169
+ 0
7170
+ ]);
7171
+ return pts;
7172
+ }
7173
+ function stairFlightToSolid(spec) {
7174
+ try {
7175
+ var _usingCtx$6 = _usingCtx();
7176
+ if (spec.width <= 0) return err(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
7177
+ if (spec.riserHeight <= 0) return err(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
7178
+ if (spec.treadLength <= 0) return err(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
7179
+ if (!Number.isInteger(spec.numberOfRisers) || spec.numberOfRisers < 1) return err(specError("STAIR_FLIGHT_BAD_RISERS", "Stair flight numberOfRisers must be a positive integer"));
7180
+ const silhouette = buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength);
7181
+ const profileResult = polygon(silhouette);
7182
+ if (!profileResult.ok) return err(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
7183
+ const profile = _usingCtx$6.u(profileResult.value);
7184
+ const solidResult = extrude(profile, [
7185
+ 0,
7186
+ spec.width,
7187
+ 0
7188
+ ]);
7189
+ if (!solidResult.ok) return err(fromBrepError(solidResult.error, "STAIR_FLIGHT_EXTRUDE_FAILED", "Failed to extrude stair flight silhouette"));
7190
+ const solid = solidResult.value;
7191
+ if (!isValidSolid(solid)) {
7192
+ solid[Symbol.dispose]();
7193
+ return err(geometryError("STAIR_FLIGHT_INVALID_SOLID", "Stair flight solid failed validity check"));
7194
+ }
7195
+ return ok({
7196
+ solid,
7197
+ geometrySimplified: false
7198
+ });
7199
+ } catch (_) {
7200
+ _usingCtx$6.e = _;
7201
+ } finally {
7202
+ _usingCtx$6.d();
7203
+ }
7204
+ }
7205
+ //#endregion
7206
+ //#region src/elementFns/rampFns.ts
7207
+ function buildSilhouette(length, rise, thickness) {
7208
+ return [
7209
+ [
7210
+ 0,
7211
+ 0,
7212
+ -thickness
7213
+ ],
7214
+ [
7215
+ length,
7216
+ 0,
7217
+ rise - thickness
7218
+ ],
7219
+ [
7220
+ length,
7221
+ 0,
7222
+ rise
7223
+ ],
7224
+ [
7225
+ 0,
7226
+ 0,
7227
+ 0
7228
+ ]
7229
+ ];
7230
+ }
7231
+ function rampFlightToSolid(spec) {
7232
+ try {
7233
+ var _usingCtx$5 = _usingCtx();
7234
+ if (spec.width <= 0) return err(specError("RAMP_FLIGHT_ZERO_WIDTH", "Ramp flight width must be positive"));
7235
+ if (spec.length <= 0) return err(specError("RAMP_FLIGHT_ZERO_LENGTH", "Ramp flight length must be positive"));
7236
+ if (spec.slope <= 0) return err(specError("RAMP_FLIGHT_ZERO_SLOPE", "Ramp flight slope must be positive"));
7237
+ if (spec.thickness <= 0) return err(specError("RAMP_FLIGHT_ZERO_THICKNESS", "Ramp flight thickness must be positive"));
7238
+ const rise = spec.length * spec.slope;
7239
+ const silhouette = buildSilhouette(spec.length, rise, spec.thickness);
7240
+ const profileResult = polygon(silhouette);
7241
+ if (!profileResult.ok) return err(fromBrepError(profileResult.error, "RAMP_FLIGHT_PROFILE_FAILED", "Failed to create ramp flight silhouette profile"));
7242
+ const profile = _usingCtx$5.u(profileResult.value);
7243
+ const solidResult = extrude(profile, [
7244
+ 0,
7245
+ spec.width,
7246
+ 0
7247
+ ]);
7248
+ if (!solidResult.ok) return err(fromBrepError(solidResult.error, "RAMP_FLIGHT_EXTRUDE_FAILED", "Failed to extrude ramp flight silhouette"));
7249
+ const solid = solidResult.value;
7250
+ if (!isValidSolid(solid)) {
7251
+ solid[Symbol.dispose]();
7252
+ return err(geometryError("RAMP_FLIGHT_INVALID_SOLID", "Ramp flight solid failed validity check"));
7253
+ }
7254
+ return ok({
7255
+ solid,
7256
+ geometrySimplified: true
7257
+ });
7258
+ } catch (_) {
7259
+ _usingCtx$5.e = _;
7260
+ } finally {
7261
+ _usingCtx$5.d();
7262
+ }
7263
+ }
7264
+ //#endregion
7080
7265
  //#region src/import/placement.ts
7081
7266
  /**
7082
7267
  * Metres-per-file-unit length scale read from the IfcUnitAssignment's
@@ -7284,148 +7469,100 @@ function enumValue(v) {
7284
7469
  return typeof value === "string" ? value : null;
7285
7470
  }
7286
7471
  //#endregion
7287
- //#region src/elementFns/stairFns.ts
7288
- function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
7289
- const pts = [];
7290
- pts.push([
7291
- 0,
7292
- 0,
7293
- 0
7294
- ]);
7295
- let x = 0;
7296
- let z = 0;
7297
- for (let i = 0; i < numberOfRisers; i++) {
7298
- z += riserHeight;
7299
- pts.push([
7300
- x,
7301
- 0,
7302
- z
7303
- ]);
7304
- x += treadLength;
7305
- pts.push([
7306
- x,
7307
- 0,
7308
- z
7309
- ]);
7310
- }
7311
- pts.push([
7312
- x,
7313
- 0,
7314
- 0
7315
- ]);
7316
- return pts;
7317
- }
7318
- function stairFlightToSolid(spec) {
7319
- try {
7320
- var _usingCtx$5 = _usingCtx();
7321
- if (spec.width <= 0) return err(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
7322
- if (spec.riserHeight <= 0) return err(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
7323
- if (spec.treadLength <= 0) return err(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
7324
- if (!Number.isInteger(spec.numberOfRisers) || spec.numberOfRisers < 1) return err(specError("STAIR_FLIGHT_BAD_RISERS", "Stair flight numberOfRisers must be a positive integer"));
7325
- const silhouette = buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength);
7326
- const profileResult = polygon(silhouette);
7327
- if (!profileResult.ok) return err(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
7328
- const profile = _usingCtx$5.u(profileResult.value);
7329
- const solidResult = extrude(profile, [
7330
- 0,
7331
- spec.width,
7332
- 0
7333
- ]);
7334
- if (!solidResult.ok) return err(fromBrepError(solidResult.error, "STAIR_FLIGHT_EXTRUDE_FAILED", "Failed to extrude stair flight silhouette"));
7335
- const solid = solidResult.value;
7336
- if (!isValidSolid(solid)) {
7337
- solid[Symbol.dispose]();
7338
- return err(geometryError("STAIR_FLIGHT_INVALID_SOLID", "Stair flight solid failed validity check"));
7339
- }
7340
- return ok({
7341
- solid,
7342
- geometrySimplified: false
7343
- });
7344
- } catch (_) {
7345
- _usingCtx$5.e = _;
7346
- } finally {
7347
- _usingCtx$5.d();
7348
- }
7349
- }
7350
- //#endregion
7351
- //#region src/elementFns/rampFns.ts
7352
- function buildSilhouette(length, rise, thickness) {
7353
- return [
7354
- [
7355
- 0,
7356
- 0,
7357
- -thickness
7358
- ],
7359
- [
7360
- length,
7361
- 0,
7362
- rise - thickness
7363
- ],
7364
- [
7365
- length,
7366
- 0,
7367
- rise
7368
- ],
7369
- [
7370
- 0,
7371
- 0,
7372
- 0
7373
- ]
7472
+ //#region src/rigidPlacement.ts
7473
+ var RAD_TO_DEG = 180 / Math.PI;
7474
+ var ROTATION_EPSILON = 1e-12;
7475
+ /** Applies a rigid placement as a location so the source BRep is not rebuilt. */
7476
+ function locateShapeInFrame(shape, frame) {
7477
+ return locate(shape, placementOps(frame));
7478
+ }
7479
+ function placementOps(frame) {
7480
+ const matrix = placementToMatrix(frame);
7481
+ const rotation = rotationOp(matrix.linear);
7482
+ const translation = {
7483
+ type: "translate",
7484
+ v: matrix.translation
7485
+ };
7486
+ return rotation === null ? [translation] : [rotation, translation];
7487
+ }
7488
+ function rotationOp(matrix) {
7489
+ const [m00, m01, m02, m10, m11, m12, m20, m21, m22] = matrix;
7490
+ const trace = m00 + m11 + m22;
7491
+ let w;
7492
+ let x;
7493
+ let y;
7494
+ let z;
7495
+ if (trace > 0) {
7496
+ const scale = 2 * Math.sqrt(trace + 1);
7497
+ w = scale / 4;
7498
+ x = (m21 - m12) / scale;
7499
+ y = (m02 - m20) / scale;
7500
+ z = (m10 - m01) / scale;
7501
+ } else if (m00 > m11 && m00 > m22) {
7502
+ const scale = 2 * Math.sqrt(1 + m00 - m11 - m22);
7503
+ w = (m21 - m12) / scale;
7504
+ x = scale / 4;
7505
+ y = (m01 + m10) / scale;
7506
+ z = (m02 + m20) / scale;
7507
+ } else if (m11 > m22) {
7508
+ const scale = 2 * Math.sqrt(1 + m11 - m00 - m22);
7509
+ w = (m02 - m20) / scale;
7510
+ x = (m01 + m10) / scale;
7511
+ y = scale / 4;
7512
+ z = (m12 + m21) / scale;
7513
+ } else {
7514
+ const scale = 2 * Math.sqrt(1 + m22 - m00 - m11);
7515
+ w = (m10 - m01) / scale;
7516
+ x = (m02 + m20) / scale;
7517
+ y = (m12 + m21) / scale;
7518
+ z = scale / 4;
7519
+ }
7520
+ const length = Math.hypot(w, x, y, z);
7521
+ const sign = w < 0 ? -1 : 1;
7522
+ const normalizedW = sign * w / length;
7523
+ const normalizedX = sign * x / length;
7524
+ const normalizedY = sign * y / length;
7525
+ const normalizedZ = sign * z / length;
7526
+ const sinHalfAngle = Math.hypot(normalizedX, normalizedY, normalizedZ);
7527
+ if (sinHalfAngle < ROTATION_EPSILON) return null;
7528
+ const axis = [
7529
+ normalizedX / sinHalfAngle,
7530
+ normalizedY / sinHalfAngle,
7531
+ normalizedZ / sinHalfAngle
7374
7532
  ];
7375
- }
7376
- function rampFlightToSolid(spec) {
7377
- try {
7378
- var _usingCtx$4 = _usingCtx();
7379
- if (spec.width <= 0) return err(specError("RAMP_FLIGHT_ZERO_WIDTH", "Ramp flight width must be positive"));
7380
- if (spec.length <= 0) return err(specError("RAMP_FLIGHT_ZERO_LENGTH", "Ramp flight length must be positive"));
7381
- if (spec.slope <= 0) return err(specError("RAMP_FLIGHT_ZERO_SLOPE", "Ramp flight slope must be positive"));
7382
- if (spec.thickness <= 0) return err(specError("RAMP_FLIGHT_ZERO_THICKNESS", "Ramp flight thickness must be positive"));
7383
- const rise = spec.length * spec.slope;
7384
- const silhouette = buildSilhouette(spec.length, rise, spec.thickness);
7385
- const profileResult = polygon(silhouette);
7386
- if (!profileResult.ok) return err(fromBrepError(profileResult.error, "RAMP_FLIGHT_PROFILE_FAILED", "Failed to create ramp flight silhouette profile"));
7387
- const profile = _usingCtx$4.u(profileResult.value);
7388
- const solidResult = extrude(profile, [
7389
- 0,
7390
- spec.width,
7391
- 0
7392
- ]);
7393
- if (!solidResult.ok) return err(fromBrepError(solidResult.error, "RAMP_FLIGHT_EXTRUDE_FAILED", "Failed to extrude ramp flight silhouette"));
7394
- const solid = solidResult.value;
7395
- if (!isValidSolid(solid)) {
7396
- solid[Symbol.dispose]();
7397
- return err(geometryError("RAMP_FLIGHT_INVALID_SOLID", "Ramp flight solid failed validity check"));
7398
- }
7399
- return ok({
7400
- solid,
7401
- geometrySimplified: true
7402
- });
7403
- } catch (_) {
7404
- _usingCtx$4.e = _;
7405
- } finally {
7406
- _usingCtx$4.d();
7407
- }
7533
+ return {
7534
+ type: "rotate",
7535
+ angle: 2 * Math.atan2(sinHalfAngle, normalizedW) * RAD_TO_DEG,
7536
+ axis
7537
+ };
7408
7538
  }
7409
7539
  //#endregion
7410
7540
  //#region src/elementFns/placedGeometry.ts
7541
+ var testHooks$5 = null;
7411
7542
  function place(solid, frame) {
7412
- const result = applyMatrix(solid, placementToMatrix(frame));
7413
- if (!result.ok) return err(fromBrepError(result.error, "PLACED_GEOMETRY_FAILED", "Failed to place element geometry"));
7414
- return ok(result.value);
7543
+ let placed = null;
7544
+ try {
7545
+ placed = locateShapeInFrame(solid, frame);
7546
+ testHooks$5?.afterPlaced?.(placed);
7547
+ return ok(placed);
7548
+ } catch (cause) {
7549
+ placed?.[Symbol.dispose]();
7550
+ return err(geometryError("PLACED_GEOMETRY_FAILED", "Element placement threw while transforming geometry", cause));
7551
+ }
7415
7552
  }
7416
- function disposeAll(solids) {
7553
+ function disposeAll$1(solids) {
7417
7554
  for (const s of solids) s[Symbol.dispose]();
7418
7555
  }
7419
7556
  function placeWithinParent(solid, localFrame, parentFrame) {
7420
7557
  try {
7421
- var _usingCtx$3 = _usingCtx();
7558
+ var _usingCtx$4 = _usingCtx();
7422
7559
  const local = place(solid, localFrame);
7423
7560
  if (!local.ok || parentFrame === void 0) return local;
7424
- return place(_usingCtx$3.u(local.value), parentFrame);
7561
+ return place(_usingCtx$4.u(local.value), parentFrame);
7425
7562
  } catch (_) {
7426
- _usingCtx$3.e = _;
7563
+ _usingCtx$4.e = _;
7427
7564
  } finally {
7428
- _usingCtx$3.d();
7565
+ _usingCtx$4.d();
7429
7566
  }
7430
7567
  }
7431
7568
  /**
@@ -7450,6 +7587,18 @@ function placedSolids(el, options = {}) {
7450
7587
  const parentFrame = options.parentFrame;
7451
7588
  switch (el.category) {
7452
7589
  case "WALL":
7590
+ case "RAILING": {
7591
+ const out = [];
7592
+ for (const solid of bodySolids(el.geometry)) {
7593
+ const placed = placeWithinParent(solid, el.spec, parentFrame);
7594
+ if (!placed.ok) {
7595
+ disposeAll$1(out);
7596
+ return placed;
7597
+ }
7598
+ out.push(placed.value);
7599
+ }
7600
+ return ok(out);
7601
+ }
7453
7602
  case "SLAB":
7454
7603
  case "BEAM":
7455
7604
  case "COLUMN":
@@ -7457,8 +7606,7 @@ function placedSolids(el, options = {}) {
7457
7606
  case "ROOF":
7458
7607
  case "FOOTING":
7459
7608
  case "PILE":
7460
- case "COVERING":
7461
- case "RAILING": {
7609
+ case "COVERING": {
7462
7610
  const placed = placeWithinParent(el.geometry, el.spec, parentFrame);
7463
7611
  if (!placed.ok) return placed;
7464
7612
  return ok([placed.value]);
@@ -7469,12 +7617,12 @@ function placedSolids(el, options = {}) {
7469
7617
  var _usingCtx3 = _usingCtx();
7470
7618
  const built = stairFlightToSolid(flight);
7471
7619
  if (!built.ok) {
7472
- disposeAll(out);
7620
+ disposeAll$1(out);
7473
7621
  return err(built.error);
7474
7622
  }
7475
7623
  const placed = placeWithinParent(_usingCtx3.u(built.value.solid), flight, parentFrame);
7476
7624
  if (!placed.ok) {
7477
- disposeAll(out);
7625
+ disposeAll$1(out);
7478
7626
  return placed;
7479
7627
  }
7480
7628
  out.push(placed.value);
@@ -7491,12 +7639,12 @@ function placedSolids(el, options = {}) {
7491
7639
  var _usingCtx4 = _usingCtx();
7492
7640
  const built = rampFlightToSolid(flight);
7493
7641
  if (!built.ok) {
7494
- disposeAll(out);
7642
+ disposeAll$1(out);
7495
7643
  return err(built.error);
7496
7644
  }
7497
7645
  const placed = placeWithinParent(_usingCtx4.u(built.value.solid), flight, parentFrame);
7498
7646
  if (!placed.ok) {
7499
- disposeAll(out);
7647
+ disposeAll$1(out);
7500
7648
  return placed;
7501
7649
  }
7502
7650
  out.push(placed.value);
@@ -7547,12 +7695,12 @@ function placedSolids(el, options = {}) {
7547
7695
  ]
7548
7696
  });
7549
7697
  if (!componentLocal.ok) {
7550
- disposeAll(out);
7698
+ disposeAll$1(out);
7551
7699
  return componentLocal;
7552
7700
  }
7553
7701
  const placed = placeWithinParent(_usingCtx5.u(componentLocal.value), el.spec, parentFrame);
7554
7702
  if (!placed.ok) {
7555
- disposeAll(out);
7703
+ disposeAll$1(out);
7556
7704
  return placed;
7557
7705
  }
7558
7706
  out.push(placed.value);
@@ -7657,6 +7805,7 @@ async function initIfcApi(api) {
7657
7805
  }
7658
7806
  //#endregion
7659
7807
  //#region src/ifc-writer/ifcWriter.ts
7808
+ var testHooks$4 = null;
7660
7809
  /** Default MVD ViewDefinition declared in the STEP FILE_DESCRIPTION header. */
7661
7810
  var DEFAULT_MVD_VIEW_DEFINITION = "ReferenceView_v1.2";
7662
7811
  var VIEW_DEFINITION_RE = /ViewDefinition \[[^\]]*\]/;
@@ -7697,6 +7846,10 @@ var IfcWriter = class IfcWriter {
7697
7846
  return err(ifcError("IFC_INIT_FAILED", "Failed to initialize web-ifc", e));
7698
7847
  }
7699
7848
  }
7849
+ /** Package-internal lifecycle seam for deterministic writer cleanup tests. */
7850
+ static fromApiForTesting(api, modelId = 0) {
7851
+ return new IfcWriter(api, modelId, DEFAULT_MVD_VIEW_DEFINITION, {});
7852
+ }
7700
7853
  nextId() {
7701
7854
  return this.#nextExpressId++;
7702
7855
  }
@@ -7714,6 +7867,7 @@ var IfcWriter = class IfcWriter {
7714
7867
  }
7715
7868
  writeLine(entity) {
7716
7869
  this.#api.WriteLine(this.#modelId, entity);
7870
+ testHooks$4?.afterWriteLine?.();
7717
7871
  return entity.expressID;
7718
7872
  }
7719
7873
  ref(id) {
@@ -7722,6 +7876,15 @@ var IfcWriter = class IfcWriter {
7722
7876
  mkType(type, value) {
7723
7877
  return this.#api.CreateIfcType(this.#modelId, type, value);
7724
7878
  }
7879
+ close() {
7880
+ if (this.#closed) return;
7881
+ this.#closed = true;
7882
+ this.#api.CloseModel(this.#modelId);
7883
+ testHooks$4?.afterClose?.();
7884
+ }
7885
+ [Symbol.dispose]() {
7886
+ this.close();
7887
+ }
7725
7888
  save() {
7726
7889
  if (this.#closed) return err(ifcError("IFC_ALREADY_SAVED", "Model has already been saved and closed"));
7727
7890
  try {
@@ -7730,8 +7893,7 @@ var IfcWriter = class IfcWriter {
7730
7893
  } catch (e) {
7731
7894
  return err(ifcError("IFC_SAVE_FAILED", "Failed to serialize IFC model", e));
7732
7895
  } finally {
7733
- this.#api.CloseModel(this.#modelId);
7734
- this.#closed = true;
7896
+ this.close();
7735
7897
  }
7736
7898
  }
7737
7899
  /**
@@ -8274,10 +8436,49 @@ function writeColumnEntity(w, guid, name, _predefinedType, ownerHistoryId, local
8274
8436
  });
8275
8437
  return id;
8276
8438
  }
8277
- //#endregion
8278
- //#region src/ifc-writer/tessellationWriter.ts
8279
- var IFC_MESH_TOLERANCE_MM = 5;
8280
- var IFC_MESH_ANGULAR_TOLERANCE_RAD = .3;
8439
+ //#endregion
8440
+ //#region src/ifc-writer/tessellationWriter.ts
8441
+ var IFC_MESH_TOLERANCE_MM = 5;
8442
+ var IFC_MESH_ANGULAR_TOLERANCE_RAD = .3;
8443
+ function prepareTessellation(solid, toleranceMm = IFC_MESH_TOLERANCE_MM) {
8444
+ let meshData;
8445
+ try {
8446
+ meshData = mesh(solid, {
8447
+ tolerance: toleranceMm,
8448
+ angularTolerance: IFC_MESH_ANGULAR_TOLERANCE_RAD
8449
+ });
8450
+ } catch (cause) {
8451
+ return {
8452
+ ok: false,
8453
+ reason: cause instanceof Error ? cause.message : String(cause),
8454
+ cause
8455
+ };
8456
+ }
8457
+ const { vertices, triangles } = meshData;
8458
+ if (vertices.length === 0 || triangles.length === 0) return {
8459
+ ok: false,
8460
+ reason: "mesh() returned an empty triangle set"
8461
+ };
8462
+ const coordList = [];
8463
+ for (let i = 0; i + 2 < vertices.length; i += 3) coordList.push([
8464
+ toIfcLengthM(vertices[i] ?? 0),
8465
+ toIfcLengthM(vertices[i + 1] ?? 0),
8466
+ toIfcLengthM(vertices[i + 2] ?? 0)
8467
+ ]);
8468
+ const coordIndex = [];
8469
+ for (let i = 0; i + 2 < triangles.length; i += 3) coordIndex.push([
8470
+ (triangles[i] ?? 0) + 1,
8471
+ (triangles[i + 1] ?? 0) + 1,
8472
+ (triangles[i + 2] ?? 0) + 1
8473
+ ]);
8474
+ return {
8475
+ ok: true,
8476
+ value: {
8477
+ coordList,
8478
+ coordIndex
8479
+ }
8480
+ };
8481
+ }
8281
8482
  /**
8282
8483
  * Writes an IfcTriangulatedFaceSet (preferred IFC4 tessellation) from a brepjs
8283
8484
  * ValidSolid, wrapped in an IfcShapeRepresentation (Body/Tessellation) and an
@@ -8296,52 +8497,20 @@ var IFC_MESH_ANGULAR_TOLERANCE_RAD = .3;
8296
8497
  * shape representation, so it is not referenced here.
8297
8498
  */
8298
8499
  function writeTessellation(w, solid, geomSubContextId, _localPlacementId, toleranceMm = IFC_MESH_TOLERANCE_MM) {
8299
- let meshData;
8300
- try {
8301
- meshData = mesh(solid, {
8302
- tolerance: toleranceMm,
8303
- angularTolerance: IFC_MESH_ANGULAR_TOLERANCE_RAD
8304
- });
8305
- } catch (e) {
8306
- const reason = e instanceof Error ? e.message : String(e);
8500
+ const prepared = prepareTessellation(solid, toleranceMm);
8501
+ if (!prepared.ok) {
8502
+ const reason = prepared.reason;
8307
8503
  console.warn(`writeTessellation: mesh() failed, using IfcFacetedBrep fallback: ${reason}`);
8308
8504
  return writeFacetedBrepFallback(w, geomSubContextId, reason);
8309
8505
  }
8310
- const { vertices, triangles } = meshData;
8311
- if (vertices.length === 0 || triangles.length === 0) {
8312
- const reason = "mesh() returned an empty triangle set";
8313
- console.warn(`writeTessellation: ${reason}, using IfcFacetedBrep fallback`);
8314
- return writeFacetedBrepFallback(w, geomSubContextId, reason);
8315
- }
8316
- const coordList = [];
8317
- for (let i = 0; i + 2 < vertices.length; i += 3) coordList.push([
8318
- toIfcLengthM(vertices[i] ?? 0),
8319
- toIfcLengthM(vertices[i + 1] ?? 0),
8320
- toIfcLengthM(vertices[i + 2] ?? 0)
8321
- ]);
8322
- const pointListId = w.nextId();
8323
- w.writeLine({
8324
- expressID: pointListId,
8325
- type: WebIFC.IFCCARTESIANPOINTLIST3D,
8326
- CoordList: coordList.map((pt) => pt.map((v) => w.mkType(WebIFC.IFCLENGTHMEASURE, v))),
8327
- TagList: null
8328
- });
8329
- const coordIndex = [];
8330
- for (let i = 0; i + 2 < triangles.length; i += 3) coordIndex.push([
8331
- (triangles[i] ?? 0) + 1,
8332
- (triangles[i + 1] ?? 0) + 1,
8333
- (triangles[i + 2] ?? 0) + 1
8334
- ]);
8335
- const faceSetId = w.nextId();
8336
- w.writeLine({
8337
- expressID: faceSetId,
8338
- type: WebIFC.IFCTRIANGULATEDFACESET,
8339
- Coordinates: w.ref(pointListId),
8340
- Normals: null,
8341
- Closed: w.mkType(WebIFC.IFCBOOLEAN, true),
8342
- CoordIndex: coordIndex.map((tri) => tri.map((idx) => w.mkType(WebIFC.IFCPOSITIVEINTEGER, idx))),
8343
- PnIndex: null
8344
- });
8506
+ const { productDefinitionShapeId } = writePreparedTessellationBody(w, [prepared.value], geomSubContextId);
8507
+ return {
8508
+ productDefinitionShapeId,
8509
+ usedFallback: false
8510
+ };
8511
+ }
8512
+ function writePreparedTessellationBody(w, items, geomSubContextId) {
8513
+ const bodyItemIds = items.map((item) => writePreparedTessellationItem(w, item));
8345
8514
  const shapeRepId = w.nextId();
8346
8515
  w.writeLine({
8347
8516
  expressID: shapeRepId,
@@ -8349,7 +8518,7 @@ function writeTessellation(w, solid, geomSubContextId, _localPlacementId, tolera
8349
8518
  ContextOfItems: w.ref(geomSubContextId),
8350
8519
  RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, "Body"),
8351
8520
  RepresentationType: w.mkType(WebIFC.IFCLABEL, "Tessellation"),
8352
- Items: [w.ref(faceSetId)]
8521
+ Items: bodyItemIds.map((id) => w.ref(id))
8353
8522
  });
8354
8523
  const productDefinitionShapeId = w.nextId();
8355
8524
  w.writeLine({
@@ -8361,9 +8530,55 @@ function writeTessellation(w, solid, geomSubContextId, _localPlacementId, tolera
8361
8530
  });
8362
8531
  return {
8363
8532
  productDefinitionShapeId,
8364
- usedFallback: false
8533
+ bodyItemIds
8534
+ };
8535
+ }
8536
+ function writeExactBodyGeometry(w, placement, items, geomSubContextId, parentPlacementId) {
8537
+ const placement3DId = writeAxis2Placement3D(w, [
8538
+ toIfcLengthM(placement.origin[0]),
8539
+ toIfcLengthM(placement.origin[1]),
8540
+ toIfcLengthM(placement.origin[2])
8541
+ ], [
8542
+ placement.axisZ[0],
8543
+ placement.axisZ[1],
8544
+ placement.axisZ[2]
8545
+ ], [
8546
+ placement.axisX[0],
8547
+ placement.axisX[1],
8548
+ placement.axisX[2]
8549
+ ]);
8550
+ const localPlacementId = w.nextId();
8551
+ w.writeLine({
8552
+ expressID: localPlacementId,
8553
+ type: WebIFC.IFCLOCALPLACEMENT,
8554
+ PlacementRelTo: parentPlacementId === null ? null : w.ref(parentPlacementId),
8555
+ RelativePlacement: w.ref(placement3DId)
8556
+ });
8557
+ return {
8558
+ localPlacementId,
8559
+ ...writePreparedTessellationBody(w, items, geomSubContextId)
8365
8560
  };
8366
8561
  }
8562
+ function writePreparedTessellationItem(w, prepared) {
8563
+ const pointListId = w.nextId();
8564
+ w.writeLine({
8565
+ expressID: pointListId,
8566
+ type: WebIFC.IFCCARTESIANPOINTLIST3D,
8567
+ CoordList: prepared.coordList.map((point) => point.map((value) => w.mkType(WebIFC.IFCLENGTHMEASURE, value))),
8568
+ TagList: null
8569
+ });
8570
+ const faceSetId = w.nextId();
8571
+ w.writeLine({
8572
+ expressID: faceSetId,
8573
+ type: WebIFC.IFCTRIANGULATEDFACESET,
8574
+ Coordinates: w.ref(pointListId),
8575
+ Normals: null,
8576
+ Closed: w.mkType(WebIFC.IFCBOOLEAN, true),
8577
+ CoordIndex: prepared.coordIndex.map((triangle) => triangle.map((index) => w.mkType(WebIFC.IFCPOSITIVEINTEGER, index))),
8578
+ PnIndex: null
8579
+ });
8580
+ return faceSetId;
8581
+ }
8367
8582
  /**
8368
8583
  * Builds a wall 'Axis' IfcShapeRepresentation: an IfcPolyline from (0,0) to
8369
8584
  * (lengthM, 0) in the wall's local XY plane, wrapped in an IfcShapeRepresentation
@@ -9860,22 +10075,22 @@ function writeStairAssembly(w, spec, stairKey, assemblyGuid, ownerHistoryId, geo
9860
10075
  const flightExpressIds = [];
9861
10076
  let geometrySimplified = false;
9862
10077
  for (const [i, flight] of spec.flights.entries()) try {
9863
- var _usingCtx$2 = _usingCtx();
10078
+ var _usingCtx$3 = _usingCtx();
9864
10079
  const built = stairFlightToSolid(flight);
9865
10080
  if (!built.ok) return {
9866
10081
  ok: false,
9867
10082
  error: built.error
9868
10083
  };
9869
- const flightSolid = _usingCtx$2.u(built.value.solid);
10084
+ const flightSolid = _usingCtx$3.u(built.value.solid);
9870
10085
  const flightPlacementId = writeLocalPlacement(w, flight.origin, flight.axisZ, flight.axisX, stairPlacementId);
9871
10086
  const tess = writeTessellation(w, flightSolid, geomSubContextId, flightPlacementId);
9872
10087
  if (tess.usedFallback) geometrySimplified = true;
9873
10088
  const flightExpressId = writeStairFlightEntity(w, deriveIfcGuidSync(`elem:STAIR_FLIGHT:${stairKey}:${i}`), `${stairName} Flight ${i + 1}`, flight, ownerHistoryId, flightPlacementId, tess.productDefinitionShapeId);
9874
10089
  flightExpressIds.push(flightExpressId);
9875
10090
  } catch (_) {
9876
- _usingCtx$2.e = _;
10091
+ _usingCtx$3.e = _;
9877
10092
  } finally {
9878
- _usingCtx$2.d();
10093
+ _usingCtx$3.d();
9879
10094
  }
9880
10095
  writeRelAggregates(w, deriveIfcGuidSync(`rel:AGGREGATES:STAIR:${stairKey}`), ownerHistoryId, assemblyExpressId, flightExpressIds);
9881
10096
  writeAssemblyType(w, WebIFC.IFCSTAIRTYPE, "Stair Type", deriveIfcGuidSync(`type:STAIR:${stairKey}`), deriveIfcGuidSync(`rel-type:STAIR:${stairKey}`), predefinedType, ownerHistoryId, [assemblyExpressId]);
@@ -11270,6 +11485,14 @@ function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, opening
11270
11485
  pushWeightQuantity(w, qtyIds, netVolumeM3, densityKgM3);
11271
11486
  writeRelDefinesByProperties(w, ownerHistoryId, wallExpressId, writeElementQuantity(w, ownerHistoryId, "Qto_WallBaseQuantities", qtyIds));
11272
11487
  }
11488
+ function writeExactWallBaseQuantities(w, ownerHistoryId, wallExpressId, values) {
11489
+ writeRelDefinesByProperties(w, ownerHistoryId, wallExpressId, writeElementQuantity(w, ownerHistoryId, "Qto_WallBaseQuantities", [
11490
+ writeQtyLength(w, "Length", values.lengthM),
11491
+ writeQtyLength(w, "Width", values.widthM),
11492
+ writeQtyLength(w, "Height", values.heightM),
11493
+ writeQtyVolume(w, "NetVolume", values.netVolumeM3)
11494
+ ]));
11495
+ }
11273
11496
  function writeSlabCommonPset(w, ownerHistoryId, slabExpressId, spec) {
11274
11497
  const values = {};
11275
11498
  if (spec.isExternal !== void 0) values["IsExternal"] = spec.isExternal;
@@ -13282,457 +13505,580 @@ function checkGeoreferencing(reader) {
13282
13505
  return [issue("warning", "NO_GEOREFERENCING", "Model contains a facility but no IfcProjectedCRS/IfcGeographicCRS (GRF003) — set ProjectSpec.crs")];
13283
13506
  }
13284
13507
  //#endregion
13285
- //#region src/serialize/toIfc.ts
13286
- async function toIfc(model, meta) {
13287
- const project = model.getProject();
13288
- if (!project) return err(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
13289
- if ((model.getBridges().length > 0 || model.getBridgeParts().length > 0 || model.getEarthworksFills().length > 0) && (meta.ifcSchema ?? "IFC4") !== "IFC4X3") return err(ifcError("IFC4X3_REQUIRED", "Bridge, Bridge Part, and Earthworks Fill entities require IFC4X3 serialization"));
13290
- const authorName = [meta.author?.givenName, meta.author?.familyName].filter((p) => Boolean(p)).join(" ");
13291
- const writerResult = await IfcWriter.create(meta.mvdViewDefinition, meta.ifcSchema, {
13292
- author: authorName,
13293
- organization: meta.organizationName
13294
- });
13295
- if (!writerResult.ok) return writerResult;
13296
- const w = writerResult.value;
13297
- w.setModelScope(project.guid);
13298
- const elements = model.getAllElements();
13299
- const relationships = model.getAllRelationships();
13300
- const walls = model.getWalls();
13301
- const slabs = model.getSlabs();
13302
- const beams = model.getBeams();
13303
- const bridges = model.getBridges();
13304
- const bridgeParts = model.getBridgeParts();
13305
- const earthworksFills = model.getEarthworksFills();
13306
- const columns = model.getColumns();
13307
- const doors = model.getDoors();
13308
- const windows = model.getWindows();
13309
- const proxies = model.getProxies();
13310
- const spaces = model.getSpaces();
13311
- const roofs = model.getRoofs();
13312
- const curtainWalls = model.getCurtainWalls();
13313
- const footings = model.getFootings();
13314
- const piles = model.getPiles();
13315
- const stairs = model.getStairs();
13316
- const ramps = model.getRamps();
13317
- const railings = model.getRailings();
13318
- const coverings = model.getCoverings();
13319
- const assemblies = model.getElementAssemblies();
13320
- const zones = model.getZones();
13321
- const systems = model.getSystems();
13322
- const { ownerHistoryId, geomContextId, geomSubContextId, unitAssignmentId, lengthUnitId } = writeHeader(w, meta);
13323
- if (project.spec.crs !== void 0) writeMapConversion(w, project.spec.crs, geomContextId, lengthUnitId);
13324
- const densityByElement = buildDensityMap(relationships);
13325
- const idMap = /* @__PURE__ */ new Map();
13326
- const placementMap = /* @__PURE__ */ new Map();
13327
- const projectExpressId = writeProject$1(w, project.guid, project.spec.name, ownerHistoryId, unitAssignmentId, geomContextId);
13328
- idMap.set(project.localId, projectExpressId);
13329
- for (const el of elements) {
13330
- if (el.category !== "SITE") continue;
13331
- const parentId = findParentOf(el.localId, relationships);
13332
- const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13333
- const { entityId, placementId } = writeSite(w, el.guid, el.spec, ownerHistoryId, parentPlacementId);
13334
- idMap.set(el.localId, entityId);
13335
- placementMap.set(el.localId, placementId);
13336
- }
13337
- for (const bridge of bridges) {
13338
- const parentId = findParentOf(bridge.localId, relationships);
13339
- const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13340
- const { entityId, placementId } = writeBridge(w, bridge.guid, bridge.spec, ownerHistoryId, parentPlacementId);
13341
- idMap.set(bridge.localId, entityId);
13342
- placementMap.set(bridge.localId, placementId);
13343
- }
13344
- const pendingParts = [...bridgeParts];
13345
- while (pendingParts.length > 0) {
13346
- const readyIndex = pendingParts.findIndex((candidate) => {
13347
- const candidateParentId = findParentOf(candidate.localId, relationships);
13348
- return candidateParentId === null || placementMap.has(candidateParentId);
13349
- });
13350
- const [part] = pendingParts.splice(readyIndex === -1 ? 0 : readyIndex, 1);
13351
- if (part === void 0) break;
13352
- const parentId = findParentOf(part.localId, relationships);
13353
- const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13354
- const { entityId, placementId } = writeBridgePart(w, part.guid, part.spec, ownerHistoryId, parentPlacementId);
13355
- idMap.set(part.localId, entityId);
13356
- placementMap.set(part.localId, placementId);
13357
- }
13358
- for (const el of elements) {
13359
- if (el.category !== "BUILDING") continue;
13360
- const parentSiteId = findParentOf(el.localId, relationships);
13361
- const parentPlacementId = parentSiteId !== null ? placementMap.get(parentSiteId) ?? null : null;
13362
- const { entityId, placementId } = writeBuilding(w, el.guid, el.spec.name, ownerHistoryId, parentPlacementId);
13363
- idMap.set(el.localId, entityId);
13364
- placementMap.set(el.localId, placementId);
13365
- }
13366
- for (const el of elements) {
13367
- if (el.category !== "STOREY") continue;
13368
- const parentBuildingId = findParentOf(el.localId, relationships);
13369
- const parentPlacementId = parentBuildingId !== null ? placementMap.get(parentBuildingId) ?? null : null;
13370
- const { entityId, placementId } = writeStorey(w, el.guid, el.spec.name, el.spec.elevation, ownerHistoryId, parentPlacementId);
13371
- idMap.set(el.localId, entityId);
13372
- placementMap.set(el.localId, placementId);
13373
- }
13374
- const openingsByWall = /* @__PURE__ */ new Map();
13375
- for (const rel of relationships) {
13376
- if (rel.kind !== "VOIDS_WALL") continue;
13377
- const opening = elements.find((el) => el.localId === rel.openingLocalId);
13378
- if (opening === void 0 || opening.category !== "OPENING") continue;
13379
- if (!isWallOpening(opening.spec)) continue;
13380
- const list = openingsByWall.get(rel.wallLocalId) ?? [];
13381
- list.push(opening.spec);
13382
- openingsByWall.set(rel.wallLocalId, list);
13383
- }
13384
- const openingsBySlab = /* @__PURE__ */ new Map();
13385
- for (const rel of relationships) {
13386
- if (rel.kind !== "VOIDS_SLAB") continue;
13387
- const opening = elements.find((el) => el.localId === rel.openingLocalId);
13388
- if (opening === void 0 || opening.category !== "OPENING") continue;
13389
- if (!isSlabOpening(opening.spec)) continue;
13390
- const list = openingsBySlab.get(rel.slabLocalId) ?? [];
13391
- list.push(opening.spec);
13392
- openingsBySlab.set(rel.slabLocalId, list);
13393
- }
13394
- for (const [i, wall] of walls.entries()) {
13395
- const containingId = findContainerOf(wall.localId, relationships);
13396
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13397
- const { localPlacementId, productDefinitionShapeId } = writeWallGeometry(w, wall.spec, geomSubContextId, storeyPlacementId);
13398
- const wallExpressId = writeWallEntity(w, wall.guid, `Wall ${i + 1}`, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13399
- idMap.set(wall.localId, wallExpressId);
13400
- placementMap.set(wall.localId, localPlacementId);
13401
- writeWallCommonPset(w, ownerHistoryId, wallExpressId, wall.spec);
13402
- writeManufacturerPset(w, ownerHistoryId, wallExpressId, wall.spec);
13403
- if (wall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, wallExpressId, wall.spec.customProperties);
13404
- writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, wall.spec, openingsByWall.get(wall.localId) ?? [], densityByElement.get(wall.localId));
13405
- }
13406
- for (const [i, slab] of slabs.entries()) {
13407
- const containingId = findContainerOf(slab.localId, relationships);
13408
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13409
- const { localPlacementId, productDefinitionShapeId } = writeSlabGeometry(w, slab.spec, geomSubContextId, storeyPlacementId);
13410
- const slabExpressId = writeSlabEntity(w, slab.guid, `Slab ${i + 1}`, slab.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13411
- idMap.set(slab.localId, slabExpressId);
13412
- placementMap.set(slab.localId, localPlacementId);
13413
- writeSlabCommonPset(w, ownerHistoryId, slabExpressId, slab.spec);
13414
- writeManufacturerPset(w, ownerHistoryId, slabExpressId, slab.spec);
13415
- if (slab.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, slabExpressId, slab.spec.customProperties);
13416
- writeSlabBaseQuantities(w, ownerHistoryId, slabExpressId, slab.spec, openingsBySlab.get(slab.localId) ?? [], densityByElement.get(slab.localId));
13417
- }
13418
- for (const [i, beam] of beams.entries()) {
13419
- const containingId = findContainerOf(beam.localId, relationships);
13420
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13421
- const { localPlacementId, productDefinitionShapeId } = writeBeamGeometry(w, beam.spec, geomSubContextId, storeyPlacementId);
13422
- const beamExpressId = writeBeamEntity(w, beam.guid, `Beam ${i + 1}`, beam.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13423
- idMap.set(beam.localId, beamExpressId);
13424
- placementMap.set(beam.localId, localPlacementId);
13425
- writeBeamCommonPset(w, ownerHistoryId, beamExpressId, beam.spec);
13426
- writeManufacturerPset(w, ownerHistoryId, beamExpressId, beam.spec);
13427
- if (beam.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, beamExpressId, beam.spec.customProperties);
13428
- writeBeamBaseQuantities(w, ownerHistoryId, beamExpressId, beam.spec);
13429
- }
13430
- for (const [i, column] of columns.entries()) {
13431
- const containingId = findContainerOf(column.localId, relationships);
13432
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13433
- const { localPlacementId, productDefinitionShapeId } = writeColumnGeometry(w, column.spec, geomSubContextId, storeyPlacementId);
13434
- const columnExpressId = writeColumnEntity(w, column.guid, `Column ${i + 1}`, column.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13435
- idMap.set(column.localId, columnExpressId);
13436
- placementMap.set(column.localId, localPlacementId);
13437
- writeColumnCommonPset(w, ownerHistoryId, columnExpressId, column.spec);
13438
- writeManufacturerPset(w, ownerHistoryId, columnExpressId, column.spec);
13439
- if (column.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, columnExpressId, column.spec.customProperties);
13440
- writeColumnBaseQuantities(w, ownerHistoryId, columnExpressId, column.spec);
13441
- }
13442
- for (const [i, proxy] of proxies.entries()) {
13443
- const containingId = findContainerOf(proxy.localId, relationships);
13444
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13445
- const { localPlacementId, productDefinitionShapeId } = writeProxyGeometry(w, proxy.spec, geomSubContextId, storeyPlacementId);
13446
- const proxyExpressId = writeProxyEntity(w, proxy.guid, proxy.spec.name || `Proxy ${i + 1}`, proxy.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13447
- idMap.set(proxy.localId, proxyExpressId);
13448
- placementMap.set(proxy.localId, localPlacementId);
13449
- if (proxy.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, proxyExpressId, proxy.spec.customProperties);
13450
- }
13451
- for (const fill of earthworksFills) {
13452
- const containingId = findContainerOf(fill.localId, relationships);
13453
- const parentPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13454
- const { localPlacementId, productDefinitionShapeId } = writeTessellatedProductGeometry(w, fill.geometry, geomSubContextId, parentPlacementId);
13455
- const expressId = writeEarthworksFillEntity(w, fill.guid, fill.spec, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13456
- idMap.set(fill.localId, expressId);
13457
- placementMap.set(fill.localId, localPlacementId);
13458
- if (fill.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, expressId, fill.spec.customProperties);
13459
- }
13460
- for (const [i, space] of spaces.entries()) {
13461
- const containingId = findContainerOf(space.localId, relationships);
13462
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13463
- const { localPlacementId, productDefinitionShapeId } = writeSpaceGeometry(w, space.spec, geomSubContextId, storeyPlacementId);
13464
- const spaceExpressId = writeSpaceEntity(w, space.guid, space.spec.name || `Space ${i + 1}`, space.spec.longName ?? null, space.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13465
- idMap.set(space.localId, spaceExpressId);
13466
- placementMap.set(space.localId, localPlacementId);
13467
- writeSpaceCommonPset(w, ownerHistoryId, spaceExpressId, space.spec);
13468
- if (space.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, spaceExpressId, space.spec.customProperties);
13469
- writeSpaceBaseQuantities(w, ownerHistoryId, spaceExpressId, space.spec);
13470
- }
13471
- for (const [i, roof] of roofs.entries()) {
13472
- const containingId = findContainerOf(roof.localId, relationships);
13473
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13474
- const { localPlacementId, productDefinitionShapeId, usedFallback } = writeRoofGeometry(w, roof.spec, roof.geometry, geomSubContextId, storeyPlacementId);
13475
- if (usedFallback) console.warn(`Roof ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
13476
- const roofExpressId = writeRoofEntity(w, roof.guid, `Roof ${i + 1}`, roof.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13477
- idMap.set(roof.localId, roofExpressId);
13478
- placementMap.set(roof.localId, localPlacementId);
13479
- writeRoofCommonPset(w, ownerHistoryId, roofExpressId, roof.spec);
13480
- writeManufacturerPset(w, ownerHistoryId, roofExpressId, roof.spec);
13481
- if (roof.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, roofExpressId, roof.spec.customProperties);
13482
- writeRoofBaseQuantities(w, ownerHistoryId, roofExpressId, roof.spec);
13483
- }
13484
- for (const [i, curtainWall] of curtainWalls.entries()) {
13485
- const containingId = findContainerOf(curtainWall.localId, relationships);
13486
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13487
- const { curtainWallId } = writeCurtainWall(w, curtainWall.spec, curtainWall.geometry, `elem:CURTAIN_WALL:${curtainWall.localId}`, `Curtain Wall ${i + 1}`, ownerHistoryId, geomSubContextId, storeyPlacementId);
13488
- idMap.set(curtainWall.localId, curtainWallId);
13489
- writeCurtainWallCommonPset(w, ownerHistoryId, curtainWallId, curtainWall.spec);
13490
- if (curtainWall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, curtainWallId, curtainWall.spec.customProperties);
13491
- }
13492
- for (const [i, footing] of footings.entries()) {
13493
- const containingId = findContainerOf(footing.localId, relationships);
13494
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13495
- const { localPlacementId, productDefinitionShapeId } = writeFootingGeometry(w, footing.spec, geomSubContextId, storeyPlacementId);
13496
- const footingExpressId = writeFootingEntity(w, footing.guid, `Footing ${i + 1}`, footing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13497
- idMap.set(footing.localId, footingExpressId);
13498
- placementMap.set(footing.localId, localPlacementId);
13499
- writeFootingCommonPset(w, ownerHistoryId, footingExpressId, footing.spec);
13500
- if (footing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, footingExpressId, footing.spec.customProperties);
13501
- writeFootingBaseQuantities(w, ownerHistoryId, footingExpressId, footing.spec);
13502
- }
13503
- for (const [i, pile] of piles.entries()) {
13504
- const containingId = findContainerOf(pile.localId, relationships);
13505
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13506
- const { localPlacementId, productDefinitionShapeId } = writePileGeometry(w, pile.spec, geomSubContextId, storeyPlacementId);
13507
- const pileExpressId = writePileEntity(w, pile.guid, `Pile ${i + 1}`, pile.spec.predefinedType ?? "NOTDEFINED", pile.spec.constructionType ?? null, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13508
- idMap.set(pile.localId, pileExpressId);
13509
- placementMap.set(pile.localId, localPlacementId);
13510
- writePileCommonPset(w, ownerHistoryId, pileExpressId, pile.spec);
13511
- if (pile.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, pileExpressId, pile.spec.customProperties);
13512
- writePileBaseQuantities(w, ownerHistoryId, pileExpressId, pile.spec);
13513
- }
13514
- for (const [i, assembly] of assemblies.entries()) {
13515
- const containingId = findContainerOf(assembly.localId, relationships);
13516
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13517
- const assemblyExpressId = writeElementAssemblyEntity(w, assembly.guid, assembly.spec.name ?? `Assembly ${i + 1}`, assembly.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, storeyPlacementId, null, assembly.spec.assemblyPlace ?? "NOTDEFINED");
13518
- idMap.set(assembly.localId, assemblyExpressId);
13519
- }
13520
- for (const zone of zones) {
13521
- const zoneExpressId = writeZoneEntity(w, zone.guid, zone.spec.name, zone.spec.longName ?? null, zone.spec.objectType ?? null, ownerHistoryId);
13522
- idMap.set(zone.localId, zoneExpressId);
13523
- }
13524
- for (const system of systems) {
13525
- const systemExpressId = writeSystemEntity(w, system.guid, system.spec.name, system.spec.longName ?? null, system.spec.objectType ?? null, ownerHistoryId);
13526
- idMap.set(system.localId, systemExpressId);
13527
- }
13528
- for (const stair of stairs) {
13529
- const containingId = findContainerOf(stair.localId, relationships);
13530
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13531
- const result = writeStairAssembly(w, stair.spec, `${stair.localId}`, stair.guid, ownerHistoryId, geomSubContextId, storeyPlacementId);
13532
- if (!result.ok) return err(result.error);
13533
- idMap.set(stair.localId, result.value.assemblyExpressId);
13534
- writeStairCommonPset(w, ownerHistoryId, result.value.assemblyExpressId, stair.spec);
13535
- }
13536
- for (const ramp of ramps) {
13537
- const containingId = findContainerOf(ramp.localId, relationships);
13538
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13539
- const result = writeRampAssembly(w, ramp.spec, `${ramp.localId}`, ramp.guid, ownerHistoryId, geomSubContextId, storeyPlacementId);
13540
- if (!result.ok) return err(result.error);
13541
- idMap.set(ramp.localId, result.value.assemblyExpressId);
13542
- writeRampCommonPset(w, ownerHistoryId, result.value.assemblyExpressId, ramp.spec);
13543
- }
13544
- for (const [i, railing] of railings.entries()) {
13545
- const containingId = findContainerOf(railing.localId, relationships);
13546
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13547
- const { localPlacementId, productDefinitionShapeId, bodyItemId, usedFallback } = writeRailingGeometry(w, railing.spec, railing.geometry, geomSubContextId, storeyPlacementId);
13548
- if (usedFallback) console.warn(`Railing ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
13549
- const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13550
- idMap.set(railing.localId, railingExpressId);
13551
- placementMap.set(railing.localId, localPlacementId);
13552
- writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
13553
- writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
13554
- if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
13555
- if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
13556
- }
13557
- for (const [i, covering] of coverings.entries()) {
13558
- const containingId = findContainerOf(covering.localId, relationships);
13559
- const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13560
- const { localPlacementId, productDefinitionShapeId, bodyItemId } = writeCoveringGeometry(w, covering.spec, geomSubContextId, storeyPlacementId);
13561
- const coveringExpressId = writeCoveringEntity(w, covering.guid, `Covering ${i + 1}`, covering.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13562
- idMap.set(covering.localId, coveringExpressId);
13563
- placementMap.set(covering.localId, localPlacementId);
13564
- writeCoveringCommonPset(w, ownerHistoryId, coveringExpressId, covering.spec);
13565
- writeManufacturerPset(w, ownerHistoryId, coveringExpressId, covering.spec);
13566
- if (covering.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, coveringExpressId, covering.spec.customProperties);
13567
- applySurfaceStyle(w, model, covering.localId, bodyItemId);
13568
- }
13569
- const openingPlacementMap = /* @__PURE__ */ new Map();
13570
- const openingEntityMap = /* @__PURE__ */ new Map();
13571
- for (const rel of relationships) {
13572
- if (rel.kind !== "VOIDS_WALL") continue;
13573
- const wallElement = elements.find((el) => el.category === "WALL" && el.localId === rel.wallLocalId);
13574
- if (wallElement === void 0) continue;
13575
- const wallExpressId = idMap.get(rel.wallLocalId);
13576
- const wallPlacementId = placementMap.get(rel.wallLocalId);
13577
- if (wallExpressId === void 0 || wallPlacementId === void 0) continue;
13578
- const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
13579
- if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
13580
- if (!isWallOpening(openingElement.spec)) continue;
13581
- const { openingEntityId, openingPlacementId } = writeOpeningGeometry(w, openingElement.guid, openingElement.spec, wallElement.spec, wallPlacementId, geomSubContextId, ownerHistoryId);
13582
- idMap.set(rel.openingLocalId, openingEntityId);
13583
- openingPlacementMap.set(rel.openingLocalId, openingPlacementId);
13584
- openingEntityMap.set(rel.openingLocalId, openingEntityId);
13585
- writeRelVoidsElement(w, rel.guid, ownerHistoryId, wallExpressId, openingEntityId);
13586
- }
13587
- for (const rel of relationships) {
13588
- if (rel.kind !== "VOIDS_SLAB") continue;
13589
- const slabElement = elements.find((el) => el.category === "SLAB" && el.localId === rel.slabLocalId);
13590
- if (slabElement === void 0) continue;
13591
- const slabExpressId = idMap.get(rel.slabLocalId);
13592
- const slabPlacementId = placementMap.get(rel.slabLocalId);
13593
- if (slabExpressId === void 0 || slabPlacementId === void 0) continue;
13594
- const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
13595
- if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
13596
- if (!isSlabOpening(openingElement.spec)) continue;
13597
- const { openingEntityId } = writeSlabOpeningGeometry(w, openingElement.guid, openingElement.spec, slabElement.spec, slabPlacementId, geomSubContextId, ownerHistoryId);
13598
- idMap.set(rel.openingLocalId, openingEntityId);
13599
- writeRelVoidsElement(w, rel.guid, ownerHistoryId, slabExpressId, openingEntityId);
13600
- }
13601
- for (const [i, door] of doors.entries()) {
13602
- const fillsRel = relationships.find((rel) => rel.kind === "FILLS_OPENING" && rel.fillerLocalId === door.localId);
13603
- if (fillsRel === void 0 || fillsRel.kind !== "FILLS_OPENING") continue;
13604
- const openingPlacementId = openingPlacementMap.get(fillsRel.openingLocalId);
13605
- const openingEntityId = openingEntityMap.get(fillsRel.openingLocalId);
13606
- if (openingPlacementId === void 0 || openingEntityId === void 0) continue;
13607
- const doorExpressId = writeDoorEntity(w, door.guid, `Door ${i + 1}`, ownerHistoryId, openingPlacementId, geomSubContextId, toIfcLengthM(door.spec.width), toIfcLengthM(door.spec.height));
13608
- idMap.set(door.localId, doorExpressId);
13609
- writeRelFillsElement(w, fillsRel.guid, ownerHistoryId, openingEntityId, doorExpressId);
13610
- writeDoorCommonPset(w, ownerHistoryId, doorExpressId, door.spec);
13611
- }
13612
- for (const [i, win] of windows.entries()) {
13613
- const fillsRel = relationships.find((rel) => rel.kind === "FILLS_OPENING" && rel.fillerLocalId === win.localId);
13614
- if (fillsRel === void 0 || fillsRel.kind !== "FILLS_OPENING") continue;
13615
- const openingPlacementId = openingPlacementMap.get(fillsRel.openingLocalId);
13616
- const openingEntityId = openingEntityMap.get(fillsRel.openingLocalId);
13617
- if (openingPlacementId === void 0 || openingEntityId === void 0) continue;
13618
- const windowExpressId = writeWindowEntity(w, win.guid, `Window ${i + 1}`, ownerHistoryId, openingPlacementId, geomSubContextId, toIfcLengthM(win.spec.width), toIfcLengthM(win.spec.height));
13619
- idMap.set(win.localId, windowExpressId);
13620
- writeRelFillsElement(w, fillsRel.guid, ownerHistoryId, openingEntityId, windowExpressId);
13621
- writeWindowCommonPset(w, ownerHistoryId, windowExpressId, win.spec);
13622
- }
13623
- for (const rel of relationships) {
13624
- if (rel.kind !== "AGGREGATES") continue;
13625
- const parentExpressId = idMap.get(rel.relatingObject);
13626
- const childExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13627
- if (parentExpressId === void 0 || childExpressIds.length === 0) continue;
13628
- writeRelAggregates(w, rel.guid, ownerHistoryId, parentExpressId, childExpressIds);
13629
- }
13630
- for (const rel of relationships) {
13631
- if (rel.kind !== "CONTAINED_IN") continue;
13632
- const structureExpressId = idMap.get(rel.relatingStructure);
13633
- const elementExpressIds = rel.relatedElements.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13634
- if (structureExpressId === void 0 || elementExpressIds.length === 0) continue;
13635
- writeRelContainedInSpatialStructure(w, rel.guid, ownerHistoryId, structureExpressId, elementExpressIds);
13636
- }
13637
- for (const rel of relationships) {
13638
- if (rel.kind !== "SPACE_BOUNDARY") continue;
13639
- const spaceExpressId = idMap.get(rel.spaceLocalId);
13640
- const elementExpressId = idMap.get(rel.elementLocalId);
13641
- if (spaceExpressId === void 0 || elementExpressId === void 0) continue;
13642
- writeRelSpaceBoundary(w, rel.guid, ownerHistoryId, spaceExpressId, elementExpressId, rel.connectionType);
13643
- }
13644
- for (const rel of relationships) {
13645
- if (rel.kind !== "NESTS") continue;
13646
- const parentExpressId = idMap.get(rel.relatingObject);
13647
- const childExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13648
- if (parentExpressId === void 0 || childExpressIds.length === 0) continue;
13649
- writeRelNests(w, rel.guid, ownerHistoryId, parentExpressId, childExpressIds);
13650
- }
13651
- for (const rel of relationships) {
13652
- if (rel.kind !== "COVERS_ELEMENT") continue;
13653
- const hostExpressId = idMap.get(rel.hostLocalId);
13654
- const coveringExpressId = idMap.get(rel.coveringLocalId);
13655
- if (hostExpressId === void 0 || coveringExpressId === void 0) continue;
13656
- writeRelCoversBldgElements(w, rel.guid, ownerHistoryId, hostExpressId, [coveringExpressId]);
13508
+ //#region src/serialize/exactBodyPreflight.ts
13509
+ var testItemPreparer = null;
13510
+ /** Prepares every exact Body item without writing IFC lines. Source solids remain borrowed. */
13511
+ function preflightExactBody(input) {
13512
+ const prepareItem = input.prepareItem ?? testItemPreparer ?? prepareTessellation;
13513
+ const prepareAt = (solid, itemIndex) => {
13514
+ const item = prepareItem(solid);
13515
+ if (!item.ok) return err(ifcError("EXACT_BODY_TESSELLATION_FAILED", `Exact Product Body item ${itemIndex} for ${input.localId} could not be tessellated: ${item.reason}`, item.cause, {
13516
+ localId: input.localId,
13517
+ itemIndex
13518
+ }));
13519
+ return ok(item.value);
13520
+ };
13521
+ const [firstSolid, ...remainingSolids] = input.solids;
13522
+ const first = prepareAt(firstSolid, 0);
13523
+ if (!first.ok) return first;
13524
+ const remainingPrepared = [];
13525
+ for (const [remainingIndex, solid] of remainingSolids.entries()) {
13526
+ const item = prepareAt(solid, remainingIndex + 1);
13527
+ if (!item.ok) return item;
13528
+ remainingPrepared.push(item.value);
13657
13529
  }
13658
- for (const rel of relationships) {
13659
- if (rel.kind !== "ASSIGNS_TO_GROUP") continue;
13660
- const groupExpressId = idMap.get(rel.groupLocalId);
13661
- const memberExpressIds = rel.memberLocalIds.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13662
- if (groupExpressId === void 0 || memberExpressIds.length === 0) continue;
13663
- writeRelAssignsToGroup(w, rel.guid, ownerHistoryId, groupExpressId, memberExpressIds);
13530
+ return ok([first.value, ...remainingPrepared]);
13531
+ }
13532
+ //#endregion
13533
+ //#region src/serialize/exactWallQuantities.ts
13534
+ function deriveExactWallQuantities(input) {
13535
+ const measure = input.dependencies?.measure ?? measureVolume;
13536
+ let volumeMm3;
13537
+ if (input.solids.length === 1) try {
13538
+ const measured = measure(input.solids[0]);
13539
+ if (!measured.ok) return exactVolumeError(measured.error);
13540
+ volumeMm3 = measured.value;
13541
+ } catch (cause) {
13542
+ return exactVolumeError(cause);
13664
13543
  }
13665
- for (const rel of relationships) {
13666
- if (rel.kind !== "CONNECTS_ELEMENTS") continue;
13667
- const relatingExpressId = idMap.get(rel.relatingElementLocalId);
13668
- const relatedExpressId = idMap.get(rel.relatedElementLocalId);
13669
- if (relatingExpressId === void 0 || relatedExpressId === void 0) continue;
13670
- writeRelConnectsElements(w, rel.guid, ownerHistoryId, relatingExpressId, relatedExpressId, rel.description ?? null);
13544
+ else {
13545
+ const fuse = input.dependencies?.fuse ?? fuseAll;
13546
+ let union = null;
13547
+ try {
13548
+ const fused = fuse([...input.solids]);
13549
+ if (!fused.ok) return exactVolumeError(fused.error);
13550
+ union = fused.value;
13551
+ const measured = measure(union);
13552
+ if (!measured.ok) return exactVolumeError(measured.error);
13553
+ volumeMm3 = measured.value;
13554
+ } catch (cause) {
13555
+ return exactVolumeError(cause);
13556
+ } finally {
13557
+ union?.[Symbol.dispose]();
13558
+ }
13671
13559
  }
13672
- for (const rel of relationships) {
13673
- if (rel.kind !== "CONNECTS_PATH_ELEMENTS") continue;
13674
- const relatingExpressId = idMap.get(rel.relatingElementLocalId);
13675
- const relatedExpressId = idMap.get(rel.relatedElementLocalId);
13676
- if (relatingExpressId === void 0 || relatedExpressId === void 0) continue;
13677
- writeRelConnectsPathElements(w, rel.guid, ownerHistoryId, relatingExpressId, relatedExpressId, rel.relatingConnectionType, rel.relatedConnectionType, rel.description ?? null);
13678
- }
13679
- const bySimpleMaterial = /* @__PURE__ */ new Map();
13680
- const layeredAssociations = [];
13681
- const categoryById = new Map(elements.map((e) => [e.localId, e.category]));
13682
- for (const rel of relationships) {
13683
- if (rel.kind !== "ASSOCIATES_MATERIAL") continue;
13684
- const objectExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13685
- if (objectExpressIds.length === 0) continue;
13686
- if (rel.materialLayers !== void 0 && rel.materialLayers.length > 0) {
13687
- const firstObj = rel.relatedObjects[0];
13688
- const hostCat = firstObj !== void 0 ? categoryById.get(firstObj) : void 0;
13689
- const direction = hostCat === "SLAB" || hostCat === "ROOF" || hostCat === "COVERING" ? "AXIS3" : "AXIS2";
13690
- layeredAssociations.push({
13691
- guid: rel.guid,
13692
- layerSetName: rel.layerSetName ?? rel.materialName,
13693
- layers: rel.materialLayers,
13694
- ids: objectExpressIds,
13695
- direction
13560
+ if (!Number.isFinite(volumeMm3) || volumeMm3 <= 0) return exactVolumeError(/* @__PURE__ */ new Error(`Measured exact wall volume was ${volumeMm3}`));
13561
+ return ok({
13562
+ lengthM: toIfcLengthM(input.spec.length),
13563
+ widthM: toIfcLengthM(input.spec.thickness),
13564
+ heightM: toIfcLengthM(input.spec.height),
13565
+ netVolumeM3: volumeMm3 / 1e9
13566
+ });
13567
+ }
13568
+ function exactVolumeError(cause) {
13569
+ return err(ifcError("IFC_EXACT_WALL_QUANTITY_DERIVATION_FAILED", "Failed to derive a positive finite NetVolume for an exact wall Product Body", cause));
13570
+ }
13571
+ //#endregion
13572
+ //#region src/serialize/toIfc.ts
13573
+ function preflightProductBodies(elements) {
13574
+ const preflighted = [];
13575
+ for (const element of elements) {
13576
+ const body = element.geometry;
13577
+ if (body.kind === "PARAMETRIC") {
13578
+ preflighted.push({
13579
+ kind: "PARAMETRIC",
13580
+ element,
13581
+ solid: body.solid
13696
13582
  });
13697
13583
  continue;
13698
13584
  }
13699
- const existing = bySimpleMaterial.get(rel.materialName);
13700
- if (existing !== void 0) bySimpleMaterial.set(rel.materialName, {
13701
- guid: existing.guid,
13702
- ids: [...existing.ids, ...objectExpressIds]
13585
+ const prepared = preflightExactBody({
13586
+ localId: element.localId,
13587
+ solids: body.solids
13703
13588
  });
13704
- else bySimpleMaterial.set(rel.materialName, {
13705
- guid: rel.guid,
13706
- ids: objectExpressIds
13589
+ if (!prepared.ok) return err(prepared.error);
13590
+ preflighted.push({
13591
+ kind: "EXACT",
13592
+ element,
13593
+ solids: body.solids,
13594
+ items: prepared.value
13707
13595
  });
13708
13596
  }
13709
- for (const [materialName, { guid, ids }] of bySimpleMaterial) {
13710
- if (ids.length === 0) continue;
13711
- writeMaterialSimple(w, guid, ownerHistoryId, materialName, ids);
13712
- }
13713
- for (const assoc of layeredAssociations) writeMaterialLayerSet(w, assoc.guid, ownerHistoryId, {
13714
- kind: "LAYER_SET",
13715
- layerSetName: assoc.layerSetName,
13716
- layers: assoc.layers
13717
- }, assoc.ids, assoc.direction);
13718
- const classByKey = /* @__PURE__ */ new Map();
13719
- for (const rel of relationships) {
13720
- if (rel.kind !== "ASSOCIATES_CLASSIFICATION") continue;
13721
- const objectExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13722
- if (objectExpressIds.length === 0) continue;
13723
- const key = `${rel.ref.system}:${rel.ref.code}`;
13724
- const entry = classByKey.get(key);
13725
- if (entry === void 0) classByKey.set(key, {
13726
- ref: rel.ref,
13727
- ids: [...objectExpressIds]
13597
+ return ok(preflighted);
13598
+ }
13599
+ async function toIfc(model, meta) {
13600
+ try {
13601
+ var _usingCtx$2 = _usingCtx();
13602
+ const project = model.getProject();
13603
+ if (!project) return err(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
13604
+ if ((model.getBridges().length > 0 || model.getBridgeParts().length > 0 || model.getEarthworksFills().length > 0) && (meta.ifcSchema ?? "IFC4") !== "IFC4X3") return err(ifcError("IFC4X3_REQUIRED", "Bridge, Bridge Part, and Earthworks Fill entities require IFC4X3 serialization"));
13605
+ const authorName = [meta.author?.givenName, meta.author?.familyName].filter((p) => Boolean(p)).join(" ");
13606
+ const writerResult = await IfcWriter.create(meta.mvdViewDefinition, meta.ifcSchema, {
13607
+ author: authorName,
13608
+ organization: meta.organizationName
13728
13609
  });
13729
- else entry.ids.push(...objectExpressIds);
13610
+ if (!writerResult.ok) return writerResult;
13611
+ const w = _usingCtx$2.u(writerResult.value);
13612
+ w.setModelScope(project.guid);
13613
+ const elements = model.getAllElements();
13614
+ const relationships = model.getAllRelationships();
13615
+ const walls = model.getWalls();
13616
+ const slabs = model.getSlabs();
13617
+ const beams = model.getBeams();
13618
+ const bridges = model.getBridges();
13619
+ const bridgeParts = model.getBridgeParts();
13620
+ const earthworksFills = model.getEarthworksFills();
13621
+ const columns = model.getColumns();
13622
+ const doors = model.getDoors();
13623
+ const windows = model.getWindows();
13624
+ const proxies = model.getProxies();
13625
+ const spaces = model.getSpaces();
13626
+ const roofs = model.getRoofs();
13627
+ const curtainWalls = model.getCurtainWalls();
13628
+ const footings = model.getFootings();
13629
+ const piles = model.getPiles();
13630
+ const stairs = model.getStairs();
13631
+ const ramps = model.getRamps();
13632
+ const railings = model.getRailings();
13633
+ const coverings = model.getCoverings();
13634
+ const assemblies = model.getElementAssemblies();
13635
+ const zones = model.getZones();
13636
+ const systems = model.getSystems();
13637
+ const preflightedWallsResult = preflightProductBodies(walls);
13638
+ if (!preflightedWallsResult.ok) return err(preflightedWallsResult.error);
13639
+ const preflightedRailingsResult = preflightProductBodies(railings);
13640
+ if (!preflightedRailingsResult.ok) return err(preflightedRailingsResult.error);
13641
+ const preflightedWalls = preflightedWallsResult.value;
13642
+ const preflightedRailings = preflightedRailingsResult.value;
13643
+ const { ownerHistoryId, geomContextId, geomSubContextId, unitAssignmentId, lengthUnitId } = writeHeader(w, meta);
13644
+ if (project.spec.crs !== void 0) writeMapConversion(w, project.spec.crs, geomContextId, lengthUnitId);
13645
+ const densityByElement = buildDensityMap(relationships);
13646
+ const idMap = /* @__PURE__ */ new Map();
13647
+ const placementMap = /* @__PURE__ */ new Map();
13648
+ const projectExpressId = writeProject$1(w, project.guid, project.spec.name, ownerHistoryId, unitAssignmentId, geomContextId);
13649
+ idMap.set(project.localId, projectExpressId);
13650
+ for (const el of elements) {
13651
+ if (el.category !== "SITE") continue;
13652
+ const parentId = findParentOf(el.localId, relationships);
13653
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13654
+ const { entityId, placementId } = writeSite(w, el.guid, el.spec, ownerHistoryId, parentPlacementId);
13655
+ idMap.set(el.localId, entityId);
13656
+ placementMap.set(el.localId, placementId);
13657
+ }
13658
+ for (const bridge of bridges) {
13659
+ const parentId = findParentOf(bridge.localId, relationships);
13660
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13661
+ const { entityId, placementId } = writeBridge(w, bridge.guid, bridge.spec, ownerHistoryId, parentPlacementId);
13662
+ idMap.set(bridge.localId, entityId);
13663
+ placementMap.set(bridge.localId, placementId);
13664
+ }
13665
+ const pendingParts = [...bridgeParts];
13666
+ while (pendingParts.length > 0) {
13667
+ const readyIndex = pendingParts.findIndex((candidate) => {
13668
+ const candidateParentId = findParentOf(candidate.localId, relationships);
13669
+ return candidateParentId === null || placementMap.has(candidateParentId);
13670
+ });
13671
+ const [part] = pendingParts.splice(readyIndex === -1 ? 0 : readyIndex, 1);
13672
+ if (part === void 0) break;
13673
+ const parentId = findParentOf(part.localId, relationships);
13674
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13675
+ const { entityId, placementId } = writeBridgePart(w, part.guid, part.spec, ownerHistoryId, parentPlacementId);
13676
+ idMap.set(part.localId, entityId);
13677
+ placementMap.set(part.localId, placementId);
13678
+ }
13679
+ for (const el of elements) {
13680
+ if (el.category !== "BUILDING") continue;
13681
+ const parentSiteId = findParentOf(el.localId, relationships);
13682
+ const parentPlacementId = parentSiteId !== null ? placementMap.get(parentSiteId) ?? null : null;
13683
+ const { entityId, placementId } = writeBuilding(w, el.guid, el.spec.name, ownerHistoryId, parentPlacementId);
13684
+ idMap.set(el.localId, entityId);
13685
+ placementMap.set(el.localId, placementId);
13686
+ }
13687
+ for (const el of elements) {
13688
+ if (el.category !== "STOREY") continue;
13689
+ const parentBuildingId = findParentOf(el.localId, relationships);
13690
+ const parentPlacementId = parentBuildingId !== null ? placementMap.get(parentBuildingId) ?? null : null;
13691
+ const { entityId, placementId } = writeStorey(w, el.guid, el.spec.name, el.spec.elevation, ownerHistoryId, parentPlacementId);
13692
+ idMap.set(el.localId, entityId);
13693
+ placementMap.set(el.localId, placementId);
13694
+ }
13695
+ const openingsByWall = /* @__PURE__ */ new Map();
13696
+ for (const rel of relationships) {
13697
+ if (rel.kind !== "VOIDS_WALL") continue;
13698
+ const opening = elements.find((el) => el.localId === rel.openingLocalId);
13699
+ if (opening === void 0 || opening.category !== "OPENING") continue;
13700
+ if (!isWallOpening(opening.spec)) continue;
13701
+ const list = openingsByWall.get(rel.wallLocalId) ?? [];
13702
+ list.push(opening.spec);
13703
+ openingsByWall.set(rel.wallLocalId, list);
13704
+ }
13705
+ const openingsBySlab = /* @__PURE__ */ new Map();
13706
+ for (const rel of relationships) {
13707
+ if (rel.kind !== "VOIDS_SLAB") continue;
13708
+ const opening = elements.find((el) => el.localId === rel.openingLocalId);
13709
+ if (opening === void 0 || opening.category !== "OPENING") continue;
13710
+ if (!isSlabOpening(opening.spec)) continue;
13711
+ const list = openingsBySlab.get(rel.slabLocalId) ?? [];
13712
+ list.push(opening.spec);
13713
+ openingsBySlab.set(rel.slabLocalId, list);
13714
+ }
13715
+ for (const [i, preflighted] of preflightedWalls.entries()) {
13716
+ const wall = preflighted.element;
13717
+ const containingId = findContainerOf(wall.localId, relationships);
13718
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13719
+ const exactQuantities = preflighted.kind === "EXACT" ? deriveExactWallQuantities({
13720
+ spec: wall.spec,
13721
+ solids: preflighted.solids
13722
+ }) : null;
13723
+ const { localPlacementId, productDefinitionShapeId } = preflighted.kind === "EXACT" ? writeExactBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId) : writeWallGeometry(w, wall.spec, geomSubContextId, storeyPlacementId);
13724
+ const wallExpressId = writeWallEntity(w, wall.guid, `Wall ${i + 1}`, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13725
+ idMap.set(wall.localId, wallExpressId);
13726
+ placementMap.set(wall.localId, localPlacementId);
13727
+ writeWallCommonPset(w, ownerHistoryId, wallExpressId, wall.spec);
13728
+ writeManufacturerPset(w, ownerHistoryId, wallExpressId, wall.spec);
13729
+ if (wall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, wallExpressId, wall.spec.customProperties);
13730
+ if (preflighted.kind === "EXACT") {
13731
+ if (exactQuantities !== null && exactQuantities.ok) writeExactWallBaseQuantities(w, ownerHistoryId, wallExpressId, exactQuantities.value);
13732
+ } else writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, wall.spec, openingsByWall.get(wall.localId) ?? [], densityByElement.get(wall.localId));
13733
+ }
13734
+ for (const [i, slab] of slabs.entries()) {
13735
+ const containingId = findContainerOf(slab.localId, relationships);
13736
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13737
+ const { localPlacementId, productDefinitionShapeId } = writeSlabGeometry(w, slab.spec, geomSubContextId, storeyPlacementId);
13738
+ const slabExpressId = writeSlabEntity(w, slab.guid, `Slab ${i + 1}`, slab.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13739
+ idMap.set(slab.localId, slabExpressId);
13740
+ placementMap.set(slab.localId, localPlacementId);
13741
+ writeSlabCommonPset(w, ownerHistoryId, slabExpressId, slab.spec);
13742
+ writeManufacturerPset(w, ownerHistoryId, slabExpressId, slab.spec);
13743
+ if (slab.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, slabExpressId, slab.spec.customProperties);
13744
+ writeSlabBaseQuantities(w, ownerHistoryId, slabExpressId, slab.spec, openingsBySlab.get(slab.localId) ?? [], densityByElement.get(slab.localId));
13745
+ }
13746
+ for (const [i, beam] of beams.entries()) {
13747
+ const containingId = findContainerOf(beam.localId, relationships);
13748
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13749
+ const { localPlacementId, productDefinitionShapeId } = writeBeamGeometry(w, beam.spec, geomSubContextId, storeyPlacementId);
13750
+ const beamExpressId = writeBeamEntity(w, beam.guid, `Beam ${i + 1}`, beam.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13751
+ idMap.set(beam.localId, beamExpressId);
13752
+ placementMap.set(beam.localId, localPlacementId);
13753
+ writeBeamCommonPset(w, ownerHistoryId, beamExpressId, beam.spec);
13754
+ writeManufacturerPset(w, ownerHistoryId, beamExpressId, beam.spec);
13755
+ if (beam.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, beamExpressId, beam.spec.customProperties);
13756
+ writeBeamBaseQuantities(w, ownerHistoryId, beamExpressId, beam.spec);
13757
+ }
13758
+ for (const [i, column] of columns.entries()) {
13759
+ const containingId = findContainerOf(column.localId, relationships);
13760
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13761
+ const { localPlacementId, productDefinitionShapeId } = writeColumnGeometry(w, column.spec, geomSubContextId, storeyPlacementId);
13762
+ const columnExpressId = writeColumnEntity(w, column.guid, `Column ${i + 1}`, column.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13763
+ idMap.set(column.localId, columnExpressId);
13764
+ placementMap.set(column.localId, localPlacementId);
13765
+ writeColumnCommonPset(w, ownerHistoryId, columnExpressId, column.spec);
13766
+ writeManufacturerPset(w, ownerHistoryId, columnExpressId, column.spec);
13767
+ if (column.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, columnExpressId, column.spec.customProperties);
13768
+ writeColumnBaseQuantities(w, ownerHistoryId, columnExpressId, column.spec);
13769
+ }
13770
+ for (const [i, proxy] of proxies.entries()) {
13771
+ const containingId = findContainerOf(proxy.localId, relationships);
13772
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13773
+ const { localPlacementId, productDefinitionShapeId } = writeProxyGeometry(w, proxy.spec, geomSubContextId, storeyPlacementId);
13774
+ const proxyExpressId = writeProxyEntity(w, proxy.guid, proxy.spec.name || `Proxy ${i + 1}`, proxy.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13775
+ idMap.set(proxy.localId, proxyExpressId);
13776
+ placementMap.set(proxy.localId, localPlacementId);
13777
+ if (proxy.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, proxyExpressId, proxy.spec.customProperties);
13778
+ }
13779
+ for (const fill of earthworksFills) {
13780
+ const containingId = findContainerOf(fill.localId, relationships);
13781
+ const parentPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13782
+ const { localPlacementId, productDefinitionShapeId } = writeTessellatedProductGeometry(w, fill.geometry, geomSubContextId, parentPlacementId);
13783
+ const expressId = writeEarthworksFillEntity(w, fill.guid, fill.spec, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13784
+ idMap.set(fill.localId, expressId);
13785
+ placementMap.set(fill.localId, localPlacementId);
13786
+ if (fill.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, expressId, fill.spec.customProperties);
13787
+ }
13788
+ for (const [i, space] of spaces.entries()) {
13789
+ const containingId = findContainerOf(space.localId, relationships);
13790
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13791
+ const { localPlacementId, productDefinitionShapeId } = writeSpaceGeometry(w, space.spec, geomSubContextId, storeyPlacementId);
13792
+ const spaceExpressId = writeSpaceEntity(w, space.guid, space.spec.name || `Space ${i + 1}`, space.spec.longName ?? null, space.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13793
+ idMap.set(space.localId, spaceExpressId);
13794
+ placementMap.set(space.localId, localPlacementId);
13795
+ writeSpaceCommonPset(w, ownerHistoryId, spaceExpressId, space.spec);
13796
+ if (space.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, spaceExpressId, space.spec.customProperties);
13797
+ writeSpaceBaseQuantities(w, ownerHistoryId, spaceExpressId, space.spec);
13798
+ }
13799
+ for (const [i, roof] of roofs.entries()) {
13800
+ const containingId = findContainerOf(roof.localId, relationships);
13801
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13802
+ const { localPlacementId, productDefinitionShapeId, usedFallback } = writeRoofGeometry(w, roof.spec, roof.geometry, geomSubContextId, storeyPlacementId);
13803
+ if (usedFallback) console.warn(`Roof ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
13804
+ const roofExpressId = writeRoofEntity(w, roof.guid, `Roof ${i + 1}`, roof.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13805
+ idMap.set(roof.localId, roofExpressId);
13806
+ placementMap.set(roof.localId, localPlacementId);
13807
+ writeRoofCommonPset(w, ownerHistoryId, roofExpressId, roof.spec);
13808
+ writeManufacturerPset(w, ownerHistoryId, roofExpressId, roof.spec);
13809
+ if (roof.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, roofExpressId, roof.spec.customProperties);
13810
+ writeRoofBaseQuantities(w, ownerHistoryId, roofExpressId, roof.spec);
13811
+ }
13812
+ for (const [i, curtainWall] of curtainWalls.entries()) {
13813
+ const containingId = findContainerOf(curtainWall.localId, relationships);
13814
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13815
+ const { curtainWallId } = writeCurtainWall(w, curtainWall.spec, curtainWall.geometry, `elem:CURTAIN_WALL:${curtainWall.localId}`, `Curtain Wall ${i + 1}`, ownerHistoryId, geomSubContextId, storeyPlacementId);
13816
+ idMap.set(curtainWall.localId, curtainWallId);
13817
+ writeCurtainWallCommonPset(w, ownerHistoryId, curtainWallId, curtainWall.spec);
13818
+ if (curtainWall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, curtainWallId, curtainWall.spec.customProperties);
13819
+ }
13820
+ for (const [i, footing] of footings.entries()) {
13821
+ const containingId = findContainerOf(footing.localId, relationships);
13822
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13823
+ const { localPlacementId, productDefinitionShapeId } = writeFootingGeometry(w, footing.spec, geomSubContextId, storeyPlacementId);
13824
+ const footingExpressId = writeFootingEntity(w, footing.guid, `Footing ${i + 1}`, footing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13825
+ idMap.set(footing.localId, footingExpressId);
13826
+ placementMap.set(footing.localId, localPlacementId);
13827
+ writeFootingCommonPset(w, ownerHistoryId, footingExpressId, footing.spec);
13828
+ if (footing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, footingExpressId, footing.spec.customProperties);
13829
+ writeFootingBaseQuantities(w, ownerHistoryId, footingExpressId, footing.spec);
13830
+ }
13831
+ for (const [i, pile] of piles.entries()) {
13832
+ const containingId = findContainerOf(pile.localId, relationships);
13833
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13834
+ const { localPlacementId, productDefinitionShapeId } = writePileGeometry(w, pile.spec, geomSubContextId, storeyPlacementId);
13835
+ const pileExpressId = writePileEntity(w, pile.guid, `Pile ${i + 1}`, pile.spec.predefinedType ?? "NOTDEFINED", pile.spec.constructionType ?? null, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13836
+ idMap.set(pile.localId, pileExpressId);
13837
+ placementMap.set(pile.localId, localPlacementId);
13838
+ writePileCommonPset(w, ownerHistoryId, pileExpressId, pile.spec);
13839
+ if (pile.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, pileExpressId, pile.spec.customProperties);
13840
+ writePileBaseQuantities(w, ownerHistoryId, pileExpressId, pile.spec);
13841
+ }
13842
+ for (const [i, assembly] of assemblies.entries()) {
13843
+ const containingId = findContainerOf(assembly.localId, relationships);
13844
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13845
+ const assemblyExpressId = writeElementAssemblyEntity(w, assembly.guid, assembly.spec.name ?? `Assembly ${i + 1}`, assembly.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, storeyPlacementId, null, assembly.spec.assemblyPlace ?? "NOTDEFINED");
13846
+ idMap.set(assembly.localId, assemblyExpressId);
13847
+ }
13848
+ for (const zone of zones) {
13849
+ const zoneExpressId = writeZoneEntity(w, zone.guid, zone.spec.name, zone.spec.longName ?? null, zone.spec.objectType ?? null, ownerHistoryId);
13850
+ idMap.set(zone.localId, zoneExpressId);
13851
+ }
13852
+ for (const system of systems) {
13853
+ const systemExpressId = writeSystemEntity(w, system.guid, system.spec.name, system.spec.longName ?? null, system.spec.objectType ?? null, ownerHistoryId);
13854
+ idMap.set(system.localId, systemExpressId);
13855
+ }
13856
+ for (const stair of stairs) {
13857
+ const containingId = findContainerOf(stair.localId, relationships);
13858
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13859
+ const result = writeStairAssembly(w, stair.spec, `${stair.localId}`, stair.guid, ownerHistoryId, geomSubContextId, storeyPlacementId);
13860
+ if (!result.ok) return err(result.error);
13861
+ idMap.set(stair.localId, result.value.assemblyExpressId);
13862
+ writeStairCommonPset(w, ownerHistoryId, result.value.assemblyExpressId, stair.spec);
13863
+ }
13864
+ for (const ramp of ramps) {
13865
+ const containingId = findContainerOf(ramp.localId, relationships);
13866
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13867
+ const result = writeRampAssembly(w, ramp.spec, `${ramp.localId}`, ramp.guid, ownerHistoryId, geomSubContextId, storeyPlacementId);
13868
+ if (!result.ok) return err(result.error);
13869
+ idMap.set(ramp.localId, result.value.assemblyExpressId);
13870
+ writeRampCommonPset(w, ownerHistoryId, result.value.assemblyExpressId, ramp.spec);
13871
+ }
13872
+ for (const [i, preflighted] of preflightedRailings.entries()) {
13873
+ const railing = preflighted.element;
13874
+ const containingId = findContainerOf(railing.localId, relationships);
13875
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13876
+ let representation;
13877
+ let exactBodyItemIds = [];
13878
+ if (preflighted.kind === "EXACT") {
13879
+ const exact = writeExactBodyGeometry(w, railing.spec, preflighted.items, geomSubContextId, storeyPlacementId);
13880
+ exactBodyItemIds = exact.bodyItemIds;
13881
+ representation = {
13882
+ ...exact,
13883
+ bodyItemId: null,
13884
+ usedFallback: false
13885
+ };
13886
+ } else representation = writeRailingGeometry(w, railing.spec, preflighted.solid, geomSubContextId, storeyPlacementId);
13887
+ const { localPlacementId, productDefinitionShapeId, bodyItemId, usedFallback } = representation;
13888
+ if (usedFallback) console.warn(`Railing ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
13889
+ const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13890
+ idMap.set(railing.localId, railingExpressId);
13891
+ placementMap.set(railing.localId, localPlacementId);
13892
+ writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
13893
+ writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
13894
+ if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
13895
+ if (preflighted.kind === "EXACT") for (const itemId of exactBodyItemIds) applySurfaceStyle(w, model, railing.localId, itemId);
13896
+ else if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
13897
+ }
13898
+ for (const [i, covering] of coverings.entries()) {
13899
+ const containingId = findContainerOf(covering.localId, relationships);
13900
+ const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13901
+ const { localPlacementId, productDefinitionShapeId, bodyItemId } = writeCoveringGeometry(w, covering.spec, geomSubContextId, storeyPlacementId);
13902
+ const coveringExpressId = writeCoveringEntity(w, covering.guid, `Covering ${i + 1}`, covering.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
13903
+ idMap.set(covering.localId, coveringExpressId);
13904
+ placementMap.set(covering.localId, localPlacementId);
13905
+ writeCoveringCommonPset(w, ownerHistoryId, coveringExpressId, covering.spec);
13906
+ writeManufacturerPset(w, ownerHistoryId, coveringExpressId, covering.spec);
13907
+ if (covering.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, coveringExpressId, covering.spec.customProperties);
13908
+ applySurfaceStyle(w, model, covering.localId, bodyItemId);
13909
+ }
13910
+ const openingPlacementMap = /* @__PURE__ */ new Map();
13911
+ const openingEntityMap = /* @__PURE__ */ new Map();
13912
+ for (const rel of relationships) {
13913
+ if (rel.kind !== "VOIDS_WALL") continue;
13914
+ const wallElement = elements.find((el) => el.category === "WALL" && el.localId === rel.wallLocalId);
13915
+ if (wallElement === void 0) continue;
13916
+ const wallExpressId = idMap.get(rel.wallLocalId);
13917
+ const wallPlacementId = placementMap.get(rel.wallLocalId);
13918
+ if (wallExpressId === void 0 || wallPlacementId === void 0) continue;
13919
+ const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
13920
+ if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
13921
+ if (!isWallOpening(openingElement.spec)) continue;
13922
+ const { openingEntityId, openingPlacementId } = writeOpeningGeometry(w, openingElement.guid, openingElement.spec, wallElement.spec, wallPlacementId, geomSubContextId, ownerHistoryId);
13923
+ idMap.set(rel.openingLocalId, openingEntityId);
13924
+ openingPlacementMap.set(rel.openingLocalId, openingPlacementId);
13925
+ openingEntityMap.set(rel.openingLocalId, openingEntityId);
13926
+ writeRelVoidsElement(w, rel.guid, ownerHistoryId, wallExpressId, openingEntityId);
13927
+ }
13928
+ for (const rel of relationships) {
13929
+ if (rel.kind !== "VOIDS_SLAB") continue;
13930
+ const slabElement = elements.find((el) => el.category === "SLAB" && el.localId === rel.slabLocalId);
13931
+ if (slabElement === void 0) continue;
13932
+ const slabExpressId = idMap.get(rel.slabLocalId);
13933
+ const slabPlacementId = placementMap.get(rel.slabLocalId);
13934
+ if (slabExpressId === void 0 || slabPlacementId === void 0) continue;
13935
+ const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
13936
+ if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
13937
+ if (!isSlabOpening(openingElement.spec)) continue;
13938
+ const { openingEntityId } = writeSlabOpeningGeometry(w, openingElement.guid, openingElement.spec, slabElement.spec, slabPlacementId, geomSubContextId, ownerHistoryId);
13939
+ idMap.set(rel.openingLocalId, openingEntityId);
13940
+ writeRelVoidsElement(w, rel.guid, ownerHistoryId, slabExpressId, openingEntityId);
13941
+ }
13942
+ for (const [i, door] of doors.entries()) {
13943
+ const fillsRel = relationships.find((rel) => rel.kind === "FILLS_OPENING" && rel.fillerLocalId === door.localId);
13944
+ if (fillsRel === void 0 || fillsRel.kind !== "FILLS_OPENING") continue;
13945
+ const openingPlacementId = openingPlacementMap.get(fillsRel.openingLocalId);
13946
+ const openingEntityId = openingEntityMap.get(fillsRel.openingLocalId);
13947
+ if (openingPlacementId === void 0 || openingEntityId === void 0) continue;
13948
+ const doorExpressId = writeDoorEntity(w, door.guid, `Door ${i + 1}`, ownerHistoryId, openingPlacementId, geomSubContextId, toIfcLengthM(door.spec.width), toIfcLengthM(door.spec.height));
13949
+ idMap.set(door.localId, doorExpressId);
13950
+ writeRelFillsElement(w, fillsRel.guid, ownerHistoryId, openingEntityId, doorExpressId);
13951
+ writeDoorCommonPset(w, ownerHistoryId, doorExpressId, door.spec);
13952
+ }
13953
+ for (const [i, win] of windows.entries()) {
13954
+ const fillsRel = relationships.find((rel) => rel.kind === "FILLS_OPENING" && rel.fillerLocalId === win.localId);
13955
+ if (fillsRel === void 0 || fillsRel.kind !== "FILLS_OPENING") continue;
13956
+ const openingPlacementId = openingPlacementMap.get(fillsRel.openingLocalId);
13957
+ const openingEntityId = openingEntityMap.get(fillsRel.openingLocalId);
13958
+ if (openingPlacementId === void 0 || openingEntityId === void 0) continue;
13959
+ const windowExpressId = writeWindowEntity(w, win.guid, `Window ${i + 1}`, ownerHistoryId, openingPlacementId, geomSubContextId, toIfcLengthM(win.spec.width), toIfcLengthM(win.spec.height));
13960
+ idMap.set(win.localId, windowExpressId);
13961
+ writeRelFillsElement(w, fillsRel.guid, ownerHistoryId, openingEntityId, windowExpressId);
13962
+ writeWindowCommonPset(w, ownerHistoryId, windowExpressId, win.spec);
13963
+ }
13964
+ for (const rel of relationships) {
13965
+ if (rel.kind !== "AGGREGATES") continue;
13966
+ const parentExpressId = idMap.get(rel.relatingObject);
13967
+ const childExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13968
+ if (parentExpressId === void 0 || childExpressIds.length === 0) continue;
13969
+ writeRelAggregates(w, rel.guid, ownerHistoryId, parentExpressId, childExpressIds);
13970
+ }
13971
+ for (const rel of relationships) {
13972
+ if (rel.kind !== "CONTAINED_IN") continue;
13973
+ const structureExpressId = idMap.get(rel.relatingStructure);
13974
+ const elementExpressIds = rel.relatedElements.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13975
+ if (structureExpressId === void 0 || elementExpressIds.length === 0) continue;
13976
+ writeRelContainedInSpatialStructure(w, rel.guid, ownerHistoryId, structureExpressId, elementExpressIds);
13977
+ }
13978
+ for (const rel of relationships) {
13979
+ if (rel.kind !== "SPACE_BOUNDARY") continue;
13980
+ const spaceExpressId = idMap.get(rel.spaceLocalId);
13981
+ const elementExpressId = idMap.get(rel.elementLocalId);
13982
+ if (spaceExpressId === void 0 || elementExpressId === void 0) continue;
13983
+ writeRelSpaceBoundary(w, rel.guid, ownerHistoryId, spaceExpressId, elementExpressId, rel.connectionType);
13984
+ }
13985
+ for (const rel of relationships) {
13986
+ if (rel.kind !== "NESTS") continue;
13987
+ const parentExpressId = idMap.get(rel.relatingObject);
13988
+ const childExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
13989
+ if (parentExpressId === void 0 || childExpressIds.length === 0) continue;
13990
+ writeRelNests(w, rel.guid, ownerHistoryId, parentExpressId, childExpressIds);
13991
+ }
13992
+ for (const rel of relationships) {
13993
+ if (rel.kind !== "COVERS_ELEMENT") continue;
13994
+ const hostExpressId = idMap.get(rel.hostLocalId);
13995
+ const coveringExpressId = idMap.get(rel.coveringLocalId);
13996
+ if (hostExpressId === void 0 || coveringExpressId === void 0) continue;
13997
+ writeRelCoversBldgElements(w, rel.guid, ownerHistoryId, hostExpressId, [coveringExpressId]);
13998
+ }
13999
+ for (const rel of relationships) {
14000
+ if (rel.kind !== "ASSIGNS_TO_GROUP") continue;
14001
+ const groupExpressId = idMap.get(rel.groupLocalId);
14002
+ const memberExpressIds = rel.memberLocalIds.map((id) => idMap.get(id)).filter((id) => id !== void 0);
14003
+ if (groupExpressId === void 0 || memberExpressIds.length === 0) continue;
14004
+ writeRelAssignsToGroup(w, rel.guid, ownerHistoryId, groupExpressId, memberExpressIds);
14005
+ }
14006
+ for (const rel of relationships) {
14007
+ if (rel.kind !== "CONNECTS_ELEMENTS") continue;
14008
+ const relatingExpressId = idMap.get(rel.relatingElementLocalId);
14009
+ const relatedExpressId = idMap.get(rel.relatedElementLocalId);
14010
+ if (relatingExpressId === void 0 || relatedExpressId === void 0) continue;
14011
+ writeRelConnectsElements(w, rel.guid, ownerHistoryId, relatingExpressId, relatedExpressId, rel.description ?? null);
14012
+ }
14013
+ for (const rel of relationships) {
14014
+ if (rel.kind !== "CONNECTS_PATH_ELEMENTS") continue;
14015
+ const relatingExpressId = idMap.get(rel.relatingElementLocalId);
14016
+ const relatedExpressId = idMap.get(rel.relatedElementLocalId);
14017
+ if (relatingExpressId === void 0 || relatedExpressId === void 0) continue;
14018
+ writeRelConnectsPathElements(w, rel.guid, ownerHistoryId, relatingExpressId, relatedExpressId, rel.relatingConnectionType, rel.relatedConnectionType, rel.description ?? null);
14019
+ }
14020
+ const bySimpleMaterial = /* @__PURE__ */ new Map();
14021
+ const layeredAssociations = [];
14022
+ const categoryById = new Map(elements.map((e) => [e.localId, e.category]));
14023
+ for (const rel of relationships) {
14024
+ if (rel.kind !== "ASSOCIATES_MATERIAL") continue;
14025
+ const objectExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
14026
+ if (objectExpressIds.length === 0) continue;
14027
+ if (rel.materialLayers !== void 0 && rel.materialLayers.length > 0) {
14028
+ const firstObj = rel.relatedObjects[0];
14029
+ const hostCat = firstObj !== void 0 ? categoryById.get(firstObj) : void 0;
14030
+ const direction = hostCat === "SLAB" || hostCat === "ROOF" || hostCat === "COVERING" ? "AXIS3" : "AXIS2";
14031
+ layeredAssociations.push({
14032
+ guid: rel.guid,
14033
+ layerSetName: rel.layerSetName ?? rel.materialName,
14034
+ layers: rel.materialLayers,
14035
+ ids: objectExpressIds,
14036
+ direction
14037
+ });
14038
+ continue;
14039
+ }
14040
+ const existing = bySimpleMaterial.get(rel.materialName);
14041
+ if (existing !== void 0) bySimpleMaterial.set(rel.materialName, {
14042
+ guid: existing.guid,
14043
+ ids: [...existing.ids, ...objectExpressIds]
14044
+ });
14045
+ else bySimpleMaterial.set(rel.materialName, {
14046
+ guid: rel.guid,
14047
+ ids: objectExpressIds
14048
+ });
14049
+ }
14050
+ for (const [materialName, { guid, ids }] of bySimpleMaterial) {
14051
+ if (ids.length === 0) continue;
14052
+ writeMaterialSimple(w, guid, ownerHistoryId, materialName, ids);
14053
+ }
14054
+ for (const assoc of layeredAssociations) writeMaterialLayerSet(w, assoc.guid, ownerHistoryId, {
14055
+ kind: "LAYER_SET",
14056
+ layerSetName: assoc.layerSetName,
14057
+ layers: assoc.layers
14058
+ }, assoc.ids, assoc.direction);
14059
+ const classByKey = /* @__PURE__ */ new Map();
14060
+ for (const rel of relationships) {
14061
+ if (rel.kind !== "ASSOCIATES_CLASSIFICATION") continue;
14062
+ const objectExpressIds = rel.relatedObjects.map((id) => idMap.get(id)).filter((id) => id !== void 0);
14063
+ if (objectExpressIds.length === 0) continue;
14064
+ const key = `${rel.ref.system}:${rel.ref.code}`;
14065
+ const entry = classByKey.get(key);
14066
+ if (entry === void 0) classByKey.set(key, {
14067
+ ref: rel.ref,
14068
+ ids: [...objectExpressIds]
14069
+ });
14070
+ else entry.ids.push(...objectExpressIds);
14071
+ }
14072
+ const byClassification = /* @__PURE__ */ new Map();
14073
+ for (const { ref, ids } of classByKey.values()) byClassification.set(ref, ids);
14074
+ if (byClassification.size > 0) writeClassificationRefs(w, ownerHistoryId, byClassification);
14075
+ writeTypeLayer(w, ownerHistoryId, model, idMap);
14076
+ return w.save();
14077
+ } catch (_) {
14078
+ _usingCtx$2.e = _;
14079
+ } finally {
14080
+ _usingCtx$2.d();
13730
14081
  }
13731
- const byClassification = /* @__PURE__ */ new Map();
13732
- for (const { ref, ids } of classByKey.values()) byClassification.set(ref, ids);
13733
- if (byClassification.size > 0) writeClassificationRefs(w, ownerHistoryId, byClassification);
13734
- writeTypeLayer(w, ownerHistoryId, model, idMap);
13735
- return w.save();
13736
14082
  }
13737
14083
  /**
13738
14084
  * Auto-derives one IfcType per (category, predefinedType) group of occurrences
@@ -13874,7 +14220,6 @@ async function toIfcValidated(model, meta) {
13874
14220
  function collectGeometryIssues(model) {
13875
14221
  const issues = [];
13876
14222
  const groups = [
13877
- ["Wall", model.getWalls()],
13878
14223
  ["Slab", model.getSlabs()],
13879
14224
  ["Beam", model.getBeams()],
13880
14225
  ["Column", model.getColumns()],
@@ -13884,13 +14229,19 @@ function collectGeometryIssues(model) {
13884
14229
  ["Roof", model.getRoofs()],
13885
14230
  ["Footing", model.getFootings()],
13886
14231
  ["Pile", model.getPiles()],
13887
- ["Railing", model.getRailings()],
13888
14232
  ["Covering", model.getCoverings()]
13889
14233
  ];
13890
14234
  for (const [label, elements] of groups) elements.forEach((el, index) => {
13891
14235
  const report = checkGeometryValidity(el.geometry, `${label} ${index + 1}`);
13892
14236
  issues.push(...report.issues);
13893
14237
  });
14238
+ const productGroups = [["Wall", model.getWalls()], ["Railing", model.getRailings()]];
14239
+ for (const [label, elements] of productGroups) elements.forEach((el, index) => {
14240
+ bodySolids(el.geometry).forEach((solid, itemIndex) => {
14241
+ const report = checkGeometryValidity(solid, `${label} ${index + 1} Body item ${itemIndex + 1}`);
14242
+ issues.push(...report.issues);
14243
+ });
14244
+ });
13894
14245
  model.getRamps().forEach((ramp, index) => {
13895
14246
  if (ramp.spec.flights.length === 0) return;
13896
14247
  issues.push(issue("info", "SIMPLIFIED_GEOMETRY", `Ramp ${index + 1} uses simplified inclined-slab flight geometry`));
@@ -14065,7 +14416,7 @@ function buildElementContainmentMap(reader) {
14065
14416
  //#endregion
14066
14417
  //#region src/import/geometryRead.ts
14067
14418
  var NONE = { kind: "NONE" };
14068
- var testHooks$1 = null;
14419
+ var testHooks$3 = null;
14069
14420
  /**
14070
14421
  * Reconstructs the `Body` (SweptSolid) representation of a product into a brepjs
14071
14422
  * solid, falling back to tessellated mesh import when no parametric path exists.
@@ -14309,7 +14660,7 @@ function reconstructTessellatedMesh(mesh, scaleToMm, itemExpressId, diagnostics)
14309
14660
  }
14310
14661
  if (solid !== null) {
14311
14662
  try {
14312
- testHooks$1?.afterItemSolid?.(itemExpressId, solid);
14663
+ testHooks$3?.afterItemSolid?.(itemExpressId, solid);
14313
14664
  } catch (cause) {
14314
14665
  solid[Symbol.dispose]();
14315
14666
  throw cause;
@@ -15270,7 +15621,7 @@ function readOwnerHistory(reader, ownerHistoryExpressId) {
15270
15621
  }
15271
15622
  //#endregion
15272
15623
  //#region src/import/fromIfc.ts
15273
- var testHooks = null;
15624
+ var testHooks$2 = null;
15274
15625
  /**
15275
15626
  * Each enumerated physical-element IFC type and the {@link ImportedElementCategory}
15276
15627
  * it maps to. Curtain-wall sub-components (IfcPlate/IfcMember) and stair/ramp
@@ -15334,7 +15685,7 @@ async function fromIfc(bytes, options = {}) {
15334
15685
  if (element === null) continue;
15335
15686
  elements.push(element);
15336
15687
  byExpressId.set(element.expressId, element);
15337
- testHooks?.afterElement?.(element, elements.length);
15688
+ testHooks$2?.afterElement?.(element, elements.length);
15338
15689
  }
15339
15690
  const report = appendIssues(emptyReport(), diagnostics);
15340
15691
  const applicationName = readApplicationName(reader);
@@ -15380,7 +15731,7 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
15380
15731
  bounds: null,
15381
15732
  volumeMm3: null
15382
15733
  } : reconstructGeometry(reader, expressId, scale, voidedBy, diagnostics);
15383
- testHooks?.afterGeometry?.(expressId, geometry);
15734
+ testHooks$2?.afterGeometry?.(expressId, geometry);
15384
15735
  const psets = readPsets(reader, expressId).map(toImportedPset);
15385
15736
  const material = readMaterial(reader, expressId, scale);
15386
15737
  const classification = readClassification(reader, expressId);
@@ -28521,13 +28872,151 @@ function isPureTranslation(f, eps = 1e-9) {
28521
28872
  return Math.abs((f[0] ?? 1) - 1) < eps && Math.abs((f[5] ?? 1) - 1) < eps && Math.abs((f[10] ?? 1) - 1) < eps && Math.abs(f[1] ?? 0) < eps && Math.abs(f[2] ?? 0) < eps && Math.abs(f[4] ?? 0) < eps && Math.abs(f[6] ?? 0) < eps && Math.abs(f[8] ?? 0) < eps && Math.abs(f[9] ?? 0) < eps;
28522
28873
  }
28523
28874
  //#endregion
28875
+ //#region src/familiesProductBody.ts
28876
+ var RELATIVE_VOLUME_TOLERANCE = 1e-6;
28877
+ var testHooks$1 = null;
28878
+ /**
28879
+ * Evaluates a civil Product Body, clones and localizes every borrowed source,
28880
+ * then proves whether the registered-opening parametric Body is coincident.
28881
+ * An EXACT result is caller-owned until takeExactProductBody() succeeds.
28882
+ */
28883
+ function selectCivilProductBody(input) {
28884
+ const evaluated = evaluateBody(input);
28885
+ if (!evaluated.ok) return evaluated;
28886
+ const sources = evaluated.value;
28887
+ if (sources.length === 0) return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_EMPTY", `evaluated to no solid Body items`));
28888
+ const localized = [];
28889
+ const inverse = decomposeFrame(frameInverse(input.productWorldFrame));
28890
+ for (const [itemIndex, source] of sources.entries()) {
28891
+ const copied = clone(source);
28892
+ if (!copied.ok) {
28893
+ disposeAll(localized);
28894
+ return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_COPY_FAILED", `Body item ${itemIndex} could not be copied`, copied.error, itemIndex));
28895
+ }
28896
+ try {
28897
+ testHooks$1?.afterCopy?.(itemIndex, copied.value, source);
28898
+ } catch (cause) {
28899
+ copied.value[Symbol.dispose]();
28900
+ disposeAll(localized);
28901
+ return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_COPY_FAILED", `Body item ${itemIndex} could not be copied`, cause, itemIndex));
28902
+ }
28903
+ let valid;
28904
+ try {
28905
+ valid = validSolid(copied.value);
28906
+ } catch (cause) {
28907
+ copied.value[Symbol.dispose]();
28908
+ disposeAll(localized);
28909
+ return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_INVALID", `Body item ${itemIndex} could not be validated as a solid`, cause, itemIndex));
28910
+ }
28911
+ if (!valid.ok) {
28912
+ copied.value[Symbol.dispose]();
28913
+ disposeAll(localized);
28914
+ return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_INVALID", `Body item ${itemIndex} is not a valid solid: ${valid.error}`, void 0, itemIndex));
28915
+ }
28916
+ let local = null;
28917
+ try {
28918
+ testHooks$1?.beforeLocalize?.(itemIndex, valid.value);
28919
+ local = locateShapeInFrame(valid.value, inverse);
28920
+ testHooks$1?.afterLocalized?.(itemIndex, local);
28921
+ localized.push(local);
28922
+ } catch (cause) {
28923
+ local?.[Symbol.dispose]();
28924
+ valid.value[Symbol.dispose]();
28925
+ disposeAll(localized);
28926
+ return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_LOCALIZE_FAILED", `Body item ${itemIndex} could not be moved into the Product-local frame`, cause, itemIndex));
28927
+ }
28928
+ valid.value[Symbol.dispose]();
28929
+ }
28930
+ const exactBody = {
28931
+ kind: "EXACT",
28932
+ solids: asNonEmpty(localized)
28933
+ };
28934
+ try {
28935
+ testHooks$1?.beforeCoincidence?.(exactBody, input.parametricBody);
28936
+ } catch (cause) {
28937
+ disposeAll(exactBody.solids);
28938
+ return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_COMPARISON_FAILED", `authored and parametric Bodies could not be compared`, cause));
28939
+ }
28940
+ if (!bodiesCoincident(exactBody, input.parametricBody)) return ok({
28941
+ kind: "EXACT",
28942
+ body: exactBody
28943
+ });
28944
+ disposeAll(exactBody.solids);
28945
+ return ok({ kind: "PARAMETRIC" });
28946
+ }
28947
+ function evaluateBody(input) {
28948
+ try {
28949
+ const evaluated = input.evaluator.evaluate(input.element.geometry);
28950
+ if (!evaluated.ok) return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_EVALUATION_FAILED", `authored Body evaluation failed: ${evaluated.error.message}`, evaluated.error));
28951
+ return ok(isSolid(evaluated.value) ? [evaluated.value] : getSolids(evaluated.value));
28952
+ } catch (cause) {
28953
+ return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_EVALUATION_FAILED", `authored Body evaluation threw`, cause));
28954
+ }
28955
+ }
28956
+ function bodiesCoincident(exact, parametric) {
28957
+ const exactVolume = measureBody(exact);
28958
+ const parametricVolume = measureBody(parametric);
28959
+ if (exactVolume === null || parametricVolume === null || !volumesClose(exactVolume, parametricVolume)) return false;
28960
+ let union = null;
28961
+ try {
28962
+ const fused = fuseAll([...bodySolids(exact), ...bodySolids(parametric)], {
28963
+ optimisation: "sameFace",
28964
+ simplify: true,
28965
+ strategy: "pairwise",
28966
+ trackEvolution: false
28967
+ });
28968
+ if (!fused.ok) return false;
28969
+ union = fused.value;
28970
+ const measured = measureVolume(union);
28971
+ return measured.ok && volumesClose(measured.value, exactVolume) && volumesClose(measured.value, parametricVolume);
28972
+ } catch {
28973
+ return false;
28974
+ } finally {
28975
+ union?.[Symbol.dispose]();
28976
+ }
28977
+ }
28978
+ function measureBody(body) {
28979
+ let total = 0;
28980
+ try {
28981
+ for (const solid of bodySolids(body)) {
28982
+ const measured = measureVolume(solid);
28983
+ if (!measured.ok || !Number.isFinite(measured.value) || measured.value <= 0) return null;
28984
+ total += measured.value;
28985
+ }
28986
+ return total;
28987
+ } catch {
28988
+ return null;
28989
+ }
28990
+ }
28991
+ function volumesClose(a, b) {
28992
+ return Math.abs(a - b) <= RELATIVE_VOLUME_TOLERANCE * Math.max(Math.abs(a), Math.abs(b), Number.EPSILON);
28993
+ }
28994
+ function asNonEmpty(solids) {
28995
+ const first = solids[0];
28996
+ if (first === void 0) throw new Error("Expected a non-empty exact Product Body");
28997
+ return [first, ...solids.slice(1)];
28998
+ }
28999
+ function disposeAll(solids) {
29000
+ for (const solid of solids) solid[Symbol.dispose]();
29001
+ }
29002
+ function productBodyError(input, code, detail, cause, itemIndex) {
29003
+ return {
29004
+ ...specError(code, `familiesToBim: '${input.element.keyPath}' (${input.category}) ${detail}`, cause),
29005
+ metadata: {
29006
+ keyPath: input.element.keyPath,
29007
+ category: input.category,
29008
+ ...itemIndex !== void 0 ? { itemIndex } : {}
29009
+ }
29010
+ };
29011
+ }
29012
+ //#endregion
28524
29013
  //#region src/familiesAdapter.ts
28525
29014
  /**
28526
29015
  * brepjs-families -> BimModel adapter. Consumes a resolved element tree and
28527
- * feeds each element's PRE-DESUGARED props into the parametric specs — the
28528
- * spec path stays authoritative for IFC (IfcExtrudedAreaSolid + placement),
28529
- * while the IR path serves the viewport and dedup. GlobalIds derive from
28530
- * families key paths (stable under reordering), not insertion order.
29016
+ * feeds each element's PRE-DESUGARED props into parametric specs. Civil wall
29017
+ * and railing Products additionally compare the evaluated authored Body with
29018
+ * that spec Body and retain an exact Body when they diverge. GlobalIds derive
29019
+ * from families key paths (stable under reordering), not insertion order.
28531
29020
  *
28532
29021
  * Scope: building Storey containers; civil Site/Bridge/recursive Bridge Part
28533
29022
  * structure and Earthworks Fill bodies; Wall/Slab/Column/Beam/Roof/Stair,
@@ -28539,6 +29028,7 @@ function isPureTranslation(f, eps = 1e-9) {
28539
29028
  * voids are rejected: they cut only the IR/viewport geometry, and exporting
28540
29029
  * the uncut spec body would silently diverge from what the user sees.
28541
29030
  */
29031
+ var testHooks = null;
28542
29032
  var SPEC_DEFAULTS = {
28543
29033
  origin: [
28544
29034
  0,
@@ -28719,6 +29209,12 @@ function civilProductArchetype(el) {
28719
29209
  if (definition === void 0 || !definition.roles.includes(el.semantics.role)) return void 0;
28720
29210
  return definition.archetype;
28721
29211
  }
29212
+ function civilProductBodyCategory(el, archetype) {
29213
+ if (el.semantics?.kind !== "product") return null;
29214
+ if (archetype === "wall") return "WALL";
29215
+ if (archetype === "railing") return "RAILING";
29216
+ return null;
29217
+ }
28722
29218
  function semanticDimension(el, ...names) {
28723
29219
  if (el.semantics?.kind !== "product") return void 0;
28724
29220
  for (const name of names) {
@@ -29424,33 +29920,55 @@ function addEarthworksFillElement(model, el, evaluator, spatialFrame) {
29424
29920
  if (!added.ok) localized.value[Symbol.dispose]();
29425
29921
  return added;
29426
29922
  }
29923
+ function installCivilProductBody(model, el, localId, category, evaluator, productWorldFrame) {
29924
+ const target = model.getElement(localId);
29925
+ if (target === null || target.category !== category || target.geometry.kind !== "PARAMETRIC") return err(specError("FAMILIES_PRODUCT_BODY_TARGET_INVALID", `familiesToBim: '${el.keyPath}' could not find its parametric ${category} Body after projection`));
29926
+ const selected = selectCivilProductBody({
29927
+ element: el,
29928
+ category,
29929
+ evaluator,
29930
+ productWorldFrame,
29931
+ parametricBody: target.geometry
29932
+ });
29933
+ if (!selected.ok) return selected;
29934
+ if (selected.value.kind === "PARAMETRIC") return ok(void 0);
29935
+ const takeover = model.takeExactProductBody(localId, selected.value.body);
29936
+ if (!takeover.ok) disposeProductBody(selected.value.body);
29937
+ return takeover;
29938
+ }
29427
29939
  /**
29428
29940
  * Project a resolved families tree into an eager BimModel. The caller owns
29429
29941
  * the returned model (`using`); families stays domain-neutral — this adapter
29430
29942
  * is where families types meet the IFC vocabulary.
29431
29943
  */
29432
29944
  function familiesToBim(root, options) {
29945
+ const model = new BimModel();
29946
+ let transferred = false;
29947
+ try {
29948
+ const projected = projectFamiliesToBim(root, options, model);
29949
+ transferred = projected.ok;
29950
+ return projected;
29951
+ } catch (cause) {
29952
+ return err(specError("FAMILIES_PROJECTION_FAILED", `familiesToBim: unexpected projection failure at '${root.keyPath}'`, cause));
29953
+ } finally {
29954
+ if (!transferred) model[Symbol.dispose]();
29955
+ }
29956
+ }
29957
+ function projectFamiliesToBim(root, options, model) {
29433
29958
  const usesAuthoredCivilHierarchy = hasCivilSpatialIntent(root);
29434
29959
  if (usesAuthoredCivilHierarchy) {
29435
29960
  const keyed = requireKeyed(root);
29436
29961
  if (!keyed.ok) return keyed;
29437
29962
  }
29438
- const model = new BimModel();
29439
29963
  const initResult = model.init(options.project, usesAuthoredCivilHierarchy ? { stableKey: civilSpatialKind(root) !== void 0 ? `${root.keyPath}#project` : root.keyPath } : void 0);
29440
29964
  if (!initResult.ok) return initResult;
29441
29965
  const projectId = initResult.value;
29442
29966
  let buildingId = null;
29443
29967
  if (!usesAuthoredCivilHierarchy) {
29444
29968
  const siteResult = model.addSite({ name: options.siteName ?? "Site" });
29445
- if (!siteResult.ok) {
29446
- model[Symbol.dispose]();
29447
- return siteResult;
29448
- }
29969
+ if (!siteResult.ok) return siteResult;
29449
29970
  const buildingResult = model.addBuilding({ name: options.buildingName ?? "Building" });
29450
- if (!buildingResult.ok) {
29451
- model[Symbol.dispose]();
29452
- return buildingResult;
29453
- }
29971
+ if (!buildingResult.ok) return buildingResult;
29454
29972
  buildingId = buildingResult.value;
29455
29973
  model.aggregate(projectId, siteResult.value);
29456
29974
  model.aggregate(siteResult.value, buildingId);
@@ -29480,8 +29998,7 @@ function familiesToBim(root, options) {
29480
29998
  if (el.geometry.kind !== "Empty") return err(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: civil spatial element '${el.keyPath}' carries its own geometry — Site/Bridge/Bridge Part export no body, so author it as a child Product (e.g. Earthworks Fill)`));
29481
29999
  const nodeFrame = civilNodeFrame(el, cumulativeFrameHere);
29482
30000
  const rotatedFrame = rotatedHere || hasRotatedAxes(el);
29483
- const input = rotatedFrame ? civilSpatialFrameInput(el, nodeFrame, state.spatialFrame) : civilSpatialInput(el, subtractTranslation(cumulativeTranslationHere, state.projectedSpatialTranslation));
29484
- const added = addCivilSpatialOccurrence(model, el, civilKind, input);
30001
+ const added = addCivilSpatialOccurrence(model, el, civilKind, rotatedFrame ? civilSpatialFrameInput(el, nodeFrame, state.spatialFrame) : civilSpatialInput(el, subtractTranslation(cumulativeTranslationHere, state.projectedSpatialTranslation)));
29485
30002
  if (!added.ok) return added;
29486
30003
  model.aggregate(state.spatialStructureId, added.value);
29487
30004
  idByKeyPath.set(el.keyPath, added.value);
@@ -29517,6 +30034,15 @@ function familiesToBim(root, options) {
29517
30034
  } else if (route !== void 0) {
29518
30035
  const keyed = requireKeyed(el);
29519
30036
  if (!keyed.ok) return keyed;
30037
+ const productBodyCategory = civilProductBodyCategory(el, effectiveArchetype);
30038
+ const productBodyEvaluator = productBodyCategory === null ? void 0 : options.bodyEvaluator ?? options.proxyEvaluator;
30039
+ if (productBodyCategory !== null && productBodyEvaluator === void 0) return err({
30040
+ ...specError("FAMILIES_PRODUCT_BODY_EVALUATOR_REQUIRED", `familiesToBim: civil Product '${el.keyPath}' mapped to ${productBodyCategory} needs bodyEvaluator or proxyEvaluator to verify its authoritative Body`),
30041
+ metadata: {
30042
+ keyPath: el.keyPath,
30043
+ category: productBodyCategory
30044
+ }
30045
+ });
29520
30046
  const voids = el.props["voids"];
29521
30047
  if (Array.isArray(voids)) {
29522
30048
  const openings = el.children.filter((c) => c.type === "Opening").length;
@@ -29536,6 +30062,11 @@ function familiesToBim(root, options) {
29536
30062
  const opened = addOpenings(model, el, added.value, nextSpatialStructureId, idByKeyPath);
29537
30063
  if (!opened.ok) return opened;
29538
30064
  }
30065
+ if (productBodyCategory !== null && productBodyEvaluator !== void 0) {
30066
+ const installed = installCivilProductBody(model, el, added.value, productBodyCategory, productBodyEvaluator, elementBodyFrame(el, cumulativeFrameHere, occurrenceTransformDepthHere));
30067
+ if (!installed.ok) return installed;
30068
+ testHooks?.afterCivilProductBody?.(model, added.value, el);
30069
+ }
29539
30070
  } else if (el.type === "Opening") return err(specError("FAMILIES_OPENING_OUTSIDE_WALL", `familiesToBim: opening '${el.keyPath}' is not hosted by a Wall — only wall openings are mapped`));
29540
30071
  else if (el.type !== "Group" && el.geometry.kind !== "Empty") {
29541
30072
  if (options.proxyEvaluator === void 0) return err(specError("FAMILIES_UNSUPPORTED_TYPE", `familiesToBim: no supported spec mapping for element type '${el.type}' at '${el.keyPath}' (archetype: ${el.archetype ?? "none"}) — declare a recognized archetype or civil Product category/role, add a spec route, or pass proxyEvaluator to export it as an IfcBuildingElementProxy`));
@@ -29579,10 +30110,7 @@ function familiesToBim(root, options) {
29579
30110
  cumulativeFrame: IDENTITY_FRAME,
29580
30111
  spatialFrame: IDENTITY_FRAME
29581
30112
  });
29582
- if (!walked.ok) {
29583
- model[Symbol.dispose]();
29584
- return walked;
29585
- }
30113
+ if (!walked.ok) return walked;
29586
30114
  return ok({
29587
30115
  model,
29588
30116
  idByKeyPath,
@@ -29590,4 +30118,4 @@ function familiesToBim(root, options) {
29590
30118
  });
29591
30119
  }
29592
30120
  //#endregion
29593
- export { BimModel, DEFAULT_IFC_SCHEMA, DEFAULT_UNITS, IFC_SCHEMAS, PSET_PROPERTY_TYPE_TABLE, PSET_TEMPLATES, SpfReader, bcfError, checkGeometryValidity, checkGherkinRules, checkIdsData as checkIds, checkIdsData, checkReferentialIntegrity, checkRoundTrip, checkSchema, countBySeverity, deriveCobieModel, deriveCobieModel as exportCobie, deriveIfcGuid, deriveIfcGuidSync, disposeImportedModel, emptyReport, extendedProfileArea, extendedProfileToFace, familiesToBim, fileSchemaString, fromBrepError, fromIfc, geometryError, hasErrors, idsError, ifcError, importError, isExtendedProfile, isIfcSchema, isSlabOpening, isValidIfcGuid, isWallOpening, issue, makeLocalIdCounter, measureTypeFor, newIfcGuid, parseBcfFiles, parseBeamSpec, parseBridgePartSpec, parseBridgeSpec, parseColumnSpec, parseCoveringSpec, parseCurtainWallSpec, parseDoorSpec, parseElementAssemblySpec, parseFootingSpec, parseIdsXml, parsePileSpec, parseProfile, parseRailingSpec, parseRampFlightSpec, parseRampSpec, parseRoofSpec, parseSlabOpeningInput, parseSlabSpec, parseSpaceSpec, parseStairFlightSpec, parseStairSpec, parseSurfaceStyleSpec, parseSystemSpec, parseWallSpec, parseWindowSpec, parseZoneSpec, placedSolids, schemaSupports, serializeBcfFiles, serializeCobieToCsv, serializeCobieToJson, setIfcWasmLocateFile, specError, templateFor, toIfc, toIfcLengthM, toIfcValidated, toLengthMm, writeClassificationRefs, writeElementAssemblyEntity, writeIfcType, writeMaterialLayerSet, writeMaterialProfileSet, writeMaterialSimple, writePresentationLayer, writeRelAggregatesElements, writeRelAssignsToGroup, writeRelConnectsElements, writeRelConnectsPathElements, writeRelNests, writeStyledItem, writeSurfaceStyle, writeSystemEntity, writeZoneEntity };
30121
+ export { BimModel, DEFAULT_IFC_SCHEMA, DEFAULT_UNITS, IFC_SCHEMAS, PSET_PROPERTY_TYPE_TABLE, PSET_TEMPLATES, SpfReader, bcfError, bodySolids, checkGeometryValidity, checkGherkinRules, checkIdsData as checkIds, checkIdsData, checkReferentialIntegrity, checkRoundTrip, checkSchema, countBySeverity, deriveCobieModel, deriveCobieModel as exportCobie, deriveIfcGuid, deriveIfcGuidSync, disposeImportedModel, emptyReport, extendedProfileArea, extendedProfileToFace, familiesToBim, fileSchemaString, fromBrepError, fromIfc, geometryError, hasErrors, idsError, ifcError, importError, isExtendedProfile, isIfcSchema, isSlabOpening, isValidIfcGuid, isWallOpening, issue, makeLocalIdCounter, measureTypeFor, newIfcGuid, parseBcfFiles, parseBeamSpec, parseBridgePartSpec, parseBridgeSpec, parseColumnSpec, parseCoveringSpec, parseCurtainWallSpec, parseDoorSpec, parseElementAssemblySpec, parseFootingSpec, parseIdsXml, parsePileSpec, parseProfile, parseRailingSpec, parseRampFlightSpec, parseRampSpec, parseRoofSpec, parseSlabOpeningInput, parseSlabSpec, parseSpaceSpec, parseStairFlightSpec, parseStairSpec, parseSurfaceStyleSpec, parseSystemSpec, parseWallSpec, parseWindowSpec, parseZoneSpec, placedSolids, schemaSupports, serializeBcfFiles, serializeCobieToCsv, serializeCobieToJson, setIfcWasmLocateFile, specError, templateFor, toIfc, toIfcLengthM, toIfcValidated, toLengthMm, writeClassificationRefs, writeElementAssemblyEntity, writeIfcType, writeMaterialLayerSet, writeMaterialProfileSet, writeMaterialSimple, writePresentationLayer, writeRelAggregatesElements, writeRelAssignsToGroup, writeRelConnectsElements, writeRelConnectsPathElements, writeRelNests, writeStyledItem, writeSurfaceStyle, writeSystemEntity, writeZoneEntity };