brepjs-bim 0.2.0 → 0.3.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.
@@ -265,7 +265,7 @@ function _usingCtx() {
265
265
  //#region src/elementFns/wallFns.ts
266
266
  function wallToSolid(spec) {
267
267
  try {
268
- var _usingCtx$18 = _usingCtx();
268
+ var _usingCtx$19 = _usingCtx();
269
269
  if (spec.length <= 0) return (0, brepjs.err)(specError("WALL_ZERO_LENGTH", "Wall length must be positive"));
270
270
  if (spec.height <= 0) return (0, brepjs.err)(specError("WALL_ZERO_HEIGHT", "Wall height must be positive"));
271
271
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("WALL_ZERO_THICKNESS", "Wall thickness must be positive"));
@@ -293,7 +293,7 @@ function wallToSolid(spec) {
293
293
  ]
294
294
  ]);
295
295
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "WALL_PROFILE_FAILED", "Failed to create wall profile"));
296
- const solidResult = (0, brepjs.extrude)(_usingCtx$18.u(profileResult.value), [
296
+ const solidResult = (0, brepjs.extrude)(_usingCtx$19.u(profileResult.value), [
297
297
  length,
298
298
  0,
299
299
  0
@@ -306,16 +306,16 @@ function wallToSolid(spec) {
306
306
  }
307
307
  return (0, brepjs.ok)(solid);
308
308
  } catch (_) {
309
- _usingCtx$18.e = _;
309
+ _usingCtx$19.e = _;
310
310
  } finally {
311
- _usingCtx$18.d();
311
+ _usingCtx$19.d();
312
312
  }
313
313
  }
314
314
  //#endregion
315
315
  //#region src/elementFns/slabFns.ts
316
316
  function slabToSolid(spec) {
317
317
  try {
318
- var _usingCtx$17 = _usingCtx();
318
+ var _usingCtx$18 = _usingCtx();
319
319
  if (spec.length <= 0) return (0, brepjs.err)(specError("SLAB_ZERO_LENGTH", "Slab length must be positive"));
320
320
  if (spec.width <= 0) return (0, brepjs.err)(specError("SLAB_ZERO_WIDTH", "Slab width must be positive"));
321
321
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("SLAB_ZERO_THICKNESS", "Slab thickness must be positive"));
@@ -343,7 +343,7 @@ function slabToSolid(spec) {
343
343
  ]
344
344
  ]);
345
345
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "SLAB_PROFILE_FAILED", "Failed to create slab profile"));
346
- const solidResult = (0, brepjs.extrude)(_usingCtx$17.u(profileResult.value), [
346
+ const solidResult = (0, brepjs.extrude)(_usingCtx$18.u(profileResult.value), [
347
347
  0,
348
348
  0,
349
349
  thickness
@@ -356,9 +356,9 @@ function slabToSolid(spec) {
356
356
  }
357
357
  return (0, brepjs.ok)(solid);
358
358
  } catch (_) {
359
- _usingCtx$17.e = _;
359
+ _usingCtx$18.e = _;
360
360
  } finally {
361
- _usingCtx$17.d();
361
+ _usingCtx$18.d();
362
362
  }
363
363
  }
364
364
  //#endregion
@@ -5035,14 +5035,14 @@ function to3D(points) {
5035
5035
  }
5036
5036
  function extendedProfileToFace(profile) {
5037
5037
  try {
5038
- var _usingCtx$16 = _usingCtx();
5038
+ var _usingCtx$17 = _usingCtx();
5039
5039
  const invalid = validateProfile(profile);
5040
5040
  if (invalid !== null) return (0, brepjs.err)(invalid);
5041
5041
  const outerResult = (0, brepjs.polygon)(to3D(outerLoop(profile)));
5042
5042
  if (!outerResult.ok) return (0, brepjs.err)(fromBrepError(outerResult.error, "PROFILE_FACE_FAILED", "Failed to build profile outer face"));
5043
5043
  const holes = holeLoops(profile);
5044
5044
  if (holes.length === 0) return (0, brepjs.ok)(outerResult.value);
5045
- const outerFace = _usingCtx$16.u(outerResult.value);
5045
+ const outerFace = _usingCtx$17.u(outerResult.value);
5046
5046
  const holeFaces = [];
5047
5047
  const holeWires = [];
5048
5048
  const disposeHoleFaces = () => {
@@ -5067,9 +5067,9 @@ function extendedProfileToFace(profile) {
5067
5067
  disposeHoleFaces();
5068
5068
  return (0, brepjs.ok)(faceWithHoles);
5069
5069
  } catch (_) {
5070
- _usingCtx$16.e = _;
5070
+ _usingCtx$17.e = _;
5071
5071
  } finally {
5072
- _usingCtx$16.d();
5072
+ _usingCtx$17.d();
5073
5073
  }
5074
5074
  }
5075
5075
  //#endregion
@@ -5390,7 +5390,7 @@ function profileToPolygon(profile, circleSegments = 32) {
5390
5390
  //#region src/elementFns/beamFns.ts
5391
5391
  function beamToSolid(spec) {
5392
5392
  try {
5393
- var _usingCtx$15 = _usingCtx();
5393
+ var _usingCtx$16 = _usingCtx();
5394
5394
  if (spec.length <= 0) return (0, brepjs.err)(specError("BEAM_ZERO_LENGTH", "Beam length must be positive"));
5395
5395
  if (isExtendedProfile(spec.profile)) try {
5396
5396
  var _usingCtx3 = _usingCtx();
@@ -5425,7 +5425,7 @@ function beamToSolid(spec) {
5425
5425
  py
5426
5426
  ]));
5427
5427
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "BEAM_PROFILE_FAILED", "Failed to create beam profile"));
5428
- const solidResult = (0, brepjs.extrude)(_usingCtx$15.u(profileResult.value), [
5428
+ const solidResult = (0, brepjs.extrude)(_usingCtx$16.u(profileResult.value), [
5429
5429
  spec.length,
5430
5430
  0,
5431
5431
  0
@@ -5438,16 +5438,16 @@ function beamToSolid(spec) {
5438
5438
  }
5439
5439
  return (0, brepjs.ok)(solid);
5440
5440
  } catch (_) {
5441
- _usingCtx$15.e = _;
5441
+ _usingCtx$16.e = _;
5442
5442
  } finally {
5443
- _usingCtx$15.d();
5443
+ _usingCtx$16.d();
5444
5444
  }
5445
5445
  }
5446
5446
  //#endregion
5447
5447
  //#region src/elementFns/columnFns.ts
5448
5448
  function columnToSolid(spec) {
5449
5449
  try {
5450
- var _usingCtx$14 = _usingCtx();
5450
+ var _usingCtx$15 = _usingCtx();
5451
5451
  if (spec.height <= 0) return (0, brepjs.err)(specError("COLUMN_ZERO_HEIGHT", "Column height must be positive"));
5452
5452
  if (isExtendedProfile(spec.profile)) try {
5453
5453
  var _usingCtx3 = _usingCtx();
@@ -5475,7 +5475,7 @@ function columnToSolid(spec) {
5475
5475
  const profilePts = profilePtsResult.value;
5476
5476
  const profileResult = (0, brepjs.polygon)(profilePts);
5477
5477
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "COLUMN_PROFILE_FAILED", "Failed to create column profile"));
5478
- const solidResult = (0, brepjs.extrude)(_usingCtx$14.u(profileResult.value), [
5478
+ const solidResult = (0, brepjs.extrude)(_usingCtx$15.u(profileResult.value), [
5479
5479
  0,
5480
5480
  0,
5481
5481
  spec.height
@@ -5488,9 +5488,9 @@ function columnToSolid(spec) {
5488
5488
  }
5489
5489
  return (0, brepjs.ok)(solid);
5490
5490
  } catch (_) {
5491
- _usingCtx$14.e = _;
5491
+ _usingCtx$15.e = _;
5492
5492
  } finally {
5493
- _usingCtx$14.d();
5493
+ _usingCtx$15.d();
5494
5494
  }
5495
5495
  }
5496
5496
  //#endregion
@@ -5498,7 +5498,7 @@ function columnToSolid(spec) {
5498
5498
  var EPSILON_MM$1 = 1;
5499
5499
  function openingToSolid(spec, wallThickness) {
5500
5500
  try {
5501
- var _usingCtx$13 = _usingCtx();
5501
+ var _usingCtx$14 = _usingCtx();
5502
5502
  if (spec.width <= 0) return (0, brepjs.err)(specError("OPENING_ZERO_WIDTH", "Opening width must be positive"));
5503
5503
  if (spec.height <= 0) return (0, brepjs.err)(specError("OPENING_ZERO_HEIGHT", "Opening height must be positive"));
5504
5504
  if (wallThickness <= 0) return (0, brepjs.err)(specError("OPENING_ZERO_WALL_THICKNESS", "Wall thickness must be positive"));
@@ -5530,7 +5530,7 @@ function openingToSolid(spec, wallThickness) {
5530
5530
  ]
5531
5531
  ]);
5532
5532
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "OPENING_PROFILE_FAILED", "Failed to create opening profile"));
5533
- const solidResult = (0, brepjs.extrude)(_usingCtx$13.u(profileResult.value), [
5533
+ const solidResult = (0, brepjs.extrude)(_usingCtx$14.u(profileResult.value), [
5534
5534
  spec.width,
5535
5535
  0,
5536
5536
  0
@@ -5543,9 +5543,9 @@ function openingToSolid(spec, wallThickness) {
5543
5543
  }
5544
5544
  return (0, brepjs.ok)(solid);
5545
5545
  } catch (_) {
5546
- _usingCtx$13.e = _;
5546
+ _usingCtx$14.e = _;
5547
5547
  } finally {
5548
- _usingCtx$13.d();
5548
+ _usingCtx$14.d();
5549
5549
  }
5550
5550
  }
5551
5551
  //#endregion
@@ -5553,7 +5553,7 @@ function openingToSolid(spec, wallThickness) {
5553
5553
  var EPSILON_MM = 1;
5554
5554
  function slabOpeningToSolid(spec, slabThickness) {
5555
5555
  try {
5556
- var _usingCtx$12 = _usingCtx();
5556
+ var _usingCtx$13 = _usingCtx();
5557
5557
  if (spec.sizeX <= 0) return (0, brepjs.err)(specError("SLAB_OPENING_ZERO_SIZE_X", "Slab opening sizeX must be positive"));
5558
5558
  if (spec.sizeY <= 0) return (0, brepjs.err)(specError("SLAB_OPENING_ZERO_SIZE_Y", "Slab opening sizeY must be positive"));
5559
5559
  if (slabThickness <= 0) return (0, brepjs.err)(specError("SLAB_OPENING_ZERO_SLAB_THICKNESS", "Slab thickness must be positive"));
@@ -5585,7 +5585,7 @@ function slabOpeningToSolid(spec, slabThickness) {
5585
5585
  ]
5586
5586
  ]);
5587
5587
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "SLAB_OPENING_PROFILE_FAILED", "Failed to create slab opening profile"));
5588
- const solidResult = (0, brepjs.extrude)(_usingCtx$12.u(profileResult.value), [
5588
+ const solidResult = (0, brepjs.extrude)(_usingCtx$13.u(profileResult.value), [
5589
5589
  0,
5590
5590
  0,
5591
5591
  slabThickness + 2 * EPSILON_MM
@@ -5598,16 +5598,16 @@ function slabOpeningToSolid(spec, slabThickness) {
5598
5598
  }
5599
5599
  return (0, brepjs.ok)(solid);
5600
5600
  } catch (_) {
5601
- _usingCtx$12.e = _;
5601
+ _usingCtx$13.e = _;
5602
5602
  } finally {
5603
- _usingCtx$12.d();
5603
+ _usingCtx$13.d();
5604
5604
  }
5605
5605
  }
5606
5606
  //#endregion
5607
5607
  //#region src/elementFns/spaceFns.ts
5608
5608
  function spaceToSolid(spec) {
5609
5609
  try {
5610
- var _usingCtx$11 = _usingCtx();
5610
+ var _usingCtx$12 = _usingCtx();
5611
5611
  if (spec.length <= 0) return (0, brepjs.err)(specError("SPACE_ZERO_LENGTH", "Space length must be positive"));
5612
5612
  if (spec.width <= 0) return (0, brepjs.err)(specError("SPACE_ZERO_WIDTH", "Space width must be positive"));
5613
5613
  if (spec.height <= 0) return (0, brepjs.err)(specError("SPACE_ZERO_HEIGHT", "Space height must be positive"));
@@ -5635,7 +5635,7 @@ function spaceToSolid(spec) {
5635
5635
  ]
5636
5636
  ]);
5637
5637
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "SPACE_PROFILE_FAILED", "Failed to create space footprint"));
5638
- const solidResult = (0, brepjs.extrude)(_usingCtx$11.u(profileResult.value), [
5638
+ const solidResult = (0, brepjs.extrude)(_usingCtx$12.u(profileResult.value), [
5639
5639
  0,
5640
5640
  0,
5641
5641
  height
@@ -5648,21 +5648,26 @@ function spaceToSolid(spec) {
5648
5648
  }
5649
5649
  return (0, brepjs.ok)(solid);
5650
5650
  } catch (_) {
5651
- _usingCtx$11.e = _;
5651
+ _usingCtx$12.e = _;
5652
5652
  } finally {
5653
- _usingCtx$11.d();
5653
+ _usingCtx$12.d();
5654
5654
  }
5655
5655
  }
5656
5656
  //#endregion
5657
5657
  //#region src/elementFns/roofFns.ts
5658
- function roofToSolid(spec) {
5658
+ var DEG2RAD = Math.PI / 180;
5659
+ function gate(solid, code) {
5660
+ if (!(0, brepjs.isValidSolid)(solid)) {
5661
+ solid[Symbol.dispose]();
5662
+ return (0, brepjs.err)(geometryError(code, "Roof solid failed validity check"));
5663
+ }
5664
+ return (0, brepjs.ok)(solid);
5665
+ }
5666
+ function flatRoof(spec) {
5659
5667
  try {
5660
- var _usingCtx$10 = _usingCtx();
5661
- if (spec.length <= 0) return (0, brepjs.err)(specError("ROOF_ZERO_LENGTH", "Roof length must be positive"));
5662
- if (spec.width <= 0) return (0, brepjs.err)(specError("ROOF_ZERO_WIDTH", "Roof width must be positive"));
5663
- if (spec.thickness <= 0) return (0, brepjs.err)(specError("ROOF_ZERO_THICKNESS", "Roof thickness must be positive"));
5668
+ var _usingCtx$11 = _usingCtx();
5664
5669
  const { length, width, thickness } = spec;
5665
- const profileResult = (0, brepjs.polygon)([
5670
+ const face = (0, brepjs.polygon)([
5666
5671
  [
5667
5672
  0,
5668
5673
  0,
@@ -5684,30 +5689,212 @@ function roofToSolid(spec) {
5684
5689
  0
5685
5690
  ]
5686
5691
  ]);
5687
- if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "ROOF_PROFILE_FAILED", "Failed to create roof profile"));
5688
- const solidResult = (0, brepjs.extrude)(_usingCtx$10.u(profileResult.value), [
5692
+ if (!face.ok) return (0, brepjs.err)(fromBrepError(face.error, "ROOF_PROFILE_FAILED", "Failed to create roof profile"));
5693
+ const solid = (0, brepjs.extrude)(_usingCtx$11.u(face.value), [
5689
5694
  0,
5690
5695
  0,
5691
5696
  thickness
5692
5697
  ]);
5693
- if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude roof profile"));
5694
- const solid = solidResult.value;
5695
- if (!(0, brepjs.isValidSolid)(solid)) {
5696
- solid[Symbol.dispose]();
5697
- return (0, brepjs.err)(geometryError("ROOF_INVALID_SOLID", "Extruded roof solid failed validity check"));
5698
- }
5699
- return (0, brepjs.ok)(solid);
5698
+ if (!solid.ok) return (0, brepjs.err)(fromBrepError(solid.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude roof profile"));
5699
+ return gate(solid.value, "ROOF_INVALID_SOLID");
5700
5700
  } catch (_) {
5701
- _usingCtx$10.e = _;
5701
+ _usingCtx$11.e = _;
5702
5702
  } finally {
5703
- _usingCtx$10.d();
5703
+ _usingCtx$11.d();
5704
+ }
5705
+ }
5706
+ function shedRoof(spec, pitch) {
5707
+ try {
5708
+ var _usingCtx3 = _usingCtx();
5709
+ const { length, width, thickness } = spec;
5710
+ const rise = width * Math.tan(pitch * DEG2RAD);
5711
+ const face = (0, brepjs.polygon)([
5712
+ [
5713
+ 0,
5714
+ 0,
5715
+ 0
5716
+ ],
5717
+ [
5718
+ 0,
5719
+ width,
5720
+ 0
5721
+ ],
5722
+ [
5723
+ 0,
5724
+ width,
5725
+ thickness + rise
5726
+ ],
5727
+ [
5728
+ 0,
5729
+ 0,
5730
+ thickness
5731
+ ]
5732
+ ]);
5733
+ if (!face.ok) return (0, brepjs.err)(fromBrepError(face.error, "ROOF_PROFILE_FAILED", "Failed to create shed profile"));
5734
+ const solid = (0, brepjs.extrude)(_usingCtx3.u(face.value), [
5735
+ length,
5736
+ 0,
5737
+ 0
5738
+ ]);
5739
+ if (!solid.ok) return (0, brepjs.err)(fromBrepError(solid.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude shed roof"));
5740
+ return gate(solid.value, "ROOF_INVALID_SOLID");
5741
+ } catch (_) {
5742
+ _usingCtx3.e = _;
5743
+ } finally {
5744
+ _usingCtx3.d();
5745
+ }
5746
+ }
5747
+ function gableRoof(spec, pitch) {
5748
+ try {
5749
+ var _usingCtx4 = _usingCtx();
5750
+ const { length, width, thickness } = spec;
5751
+ const ridge = width / 2 * Math.tan(pitch * DEG2RAD);
5752
+ const face = (0, brepjs.polygon)([
5753
+ [
5754
+ 0,
5755
+ 0,
5756
+ 0
5757
+ ],
5758
+ [
5759
+ 0,
5760
+ width,
5761
+ 0
5762
+ ],
5763
+ [
5764
+ 0,
5765
+ width,
5766
+ thickness
5767
+ ],
5768
+ [
5769
+ 0,
5770
+ width / 2,
5771
+ thickness + ridge
5772
+ ],
5773
+ [
5774
+ 0,
5775
+ 0,
5776
+ thickness
5777
+ ]
5778
+ ]);
5779
+ if (!face.ok) return (0, brepjs.err)(fromBrepError(face.error, "ROOF_PROFILE_FAILED", "Failed to create gable profile"));
5780
+ const solid = (0, brepjs.extrude)(_usingCtx4.u(face.value), [
5781
+ length,
5782
+ 0,
5783
+ 0
5784
+ ]);
5785
+ if (!solid.ok) return (0, brepjs.err)(fromBrepError(solid.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude gable roof"));
5786
+ return gate(solid.value, "ROOF_INVALID_SOLID");
5787
+ } catch (_) {
5788
+ _usingCtx4.e = _;
5789
+ } finally {
5790
+ _usingCtx4.d();
5791
+ }
5792
+ }
5793
+ function hipRoof(spec, pitch) {
5794
+ const { length: l, width: w } = spec;
5795
+ const tan = Math.tan(pitch * DEG2RAD);
5796
+ const base = [
5797
+ [
5798
+ 0,
5799
+ 0,
5800
+ 0
5801
+ ],
5802
+ [
5803
+ l,
5804
+ 0,
5805
+ 0
5806
+ ],
5807
+ [
5808
+ l,
5809
+ w,
5810
+ 0
5811
+ ],
5812
+ [
5813
+ 0,
5814
+ w,
5815
+ 0
5816
+ ]
5817
+ ];
5818
+ let ridge;
5819
+ if (l >= w) {
5820
+ const h = w / 2 * tan;
5821
+ ridge = [[
5822
+ w / 2,
5823
+ w / 2,
5824
+ h
5825
+ ], [
5826
+ l - w / 2,
5827
+ w / 2,
5828
+ h
5829
+ ]];
5830
+ } else {
5831
+ const h = l / 2 * tan;
5832
+ ridge = [[
5833
+ l / 2,
5834
+ l / 2,
5835
+ h
5836
+ ], [
5837
+ l / 2,
5838
+ w - l / 2,
5839
+ h
5840
+ ]];
5841
+ }
5842
+ const solid = (0, brepjs.convexHull)([...base, ...ridge]);
5843
+ if (!solid.ok) return (0, brepjs.err)(fromBrepError(solid.error, "ROOF_HIP_FAILED", "Failed to build hip roof"));
5844
+ return gate(solid.value, "ROOF_INVALID_SOLID");
5845
+ }
5846
+ function domeRoof(spec) {
5847
+ const { length, width } = spec;
5848
+ const r = Math.min(length, width) / 2;
5849
+ const cx = length / 2;
5850
+ const cy = width / 2;
5851
+ const segments = 24;
5852
+ const rings = [
5853
+ 0,
5854
+ .4,
5855
+ .7,
5856
+ .9
5857
+ ];
5858
+ const pts = [];
5859
+ for (const h of rings) {
5860
+ const z = r * h;
5861
+ const ringR = r * Math.sqrt(1 - h * h);
5862
+ for (let i = 0; i < segments; i++) {
5863
+ const a = 2 * Math.PI * i / segments;
5864
+ pts.push([
5865
+ cx + ringR * Math.cos(a),
5866
+ cy + ringR * Math.sin(a),
5867
+ z
5868
+ ]);
5869
+ }
5870
+ }
5871
+ pts.push([
5872
+ cx,
5873
+ cy,
5874
+ r
5875
+ ]);
5876
+ const solid = (0, brepjs.convexHull)(pts);
5877
+ if (!solid.ok) return (0, brepjs.err)(fromBrepError(solid.error, "ROOF_DOME_FAILED", "Failed to build dome roof"));
5878
+ return gate(solid.value, "ROOF_INVALID_SOLID");
5879
+ }
5880
+ function roofToSolid(spec) {
5881
+ if (spec.length <= 0) return (0, brepjs.err)(specError("ROOF_ZERO_LENGTH", "Roof length must be positive"));
5882
+ if (spec.width <= 0) return (0, brepjs.err)(specError("ROOF_ZERO_WIDTH", "Roof width must be positive"));
5883
+ if (spec.thickness <= 0) return (0, brepjs.err)(specError("ROOF_ZERO_THICKNESS", "Roof thickness must be positive"));
5884
+ if (spec.pitch === void 0) return flatRoof(spec);
5885
+ switch (spec.predefinedType) {
5886
+ case "SHED_ROOF": return shedRoof(spec, spec.pitch);
5887
+ case "GABLE_ROOF": return gableRoof(spec, spec.pitch);
5888
+ case "HIP_ROOF": return hipRoof(spec, spec.pitch);
5889
+ case "DOME_ROOF": return domeRoof(spec);
5890
+ default: return flatRoof(spec);
5704
5891
  }
5705
5892
  }
5706
5893
  //#endregion
5707
5894
  //#region src/elementFns/curtainWallFns.ts
5708
5895
  function boxSolid(sizeX, sizeY, sizeZ) {
5709
5896
  try {
5710
- var _usingCtx$9 = _usingCtx();
5897
+ var _usingCtx$10 = _usingCtx();
5711
5898
  const profileResult = (0, brepjs.polygon)([
5712
5899
  [
5713
5900
  0,
@@ -5731,7 +5918,7 @@ function boxSolid(sizeX, sizeY, sizeZ) {
5731
5918
  ]
5732
5919
  ]);
5733
5920
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "CURTAIN_WALL_PROFILE_FAILED", "Failed to create component profile"));
5734
- const solidResult = (0, brepjs.extrude)(_usingCtx$9.u(profileResult.value), [
5921
+ const solidResult = (0, brepjs.extrude)(_usingCtx$10.u(profileResult.value), [
5735
5922
  0,
5736
5923
  0,
5737
5924
  sizeZ
@@ -5744,9 +5931,9 @@ function boxSolid(sizeX, sizeY, sizeZ) {
5744
5931
  }
5745
5932
  return (0, brepjs.ok)(solid);
5746
5933
  } catch (_) {
5747
- _usingCtx$9.e = _;
5934
+ _usingCtx$10.e = _;
5748
5935
  } finally {
5749
- _usingCtx$9.d();
5936
+ _usingCtx$10.d();
5750
5937
  }
5751
5938
  }
5752
5939
  function disposeComponents(components) {
@@ -5845,7 +6032,7 @@ function curtainWallToGrid(spec) {
5845
6032
  //#region src/elementFns/foundationFns.ts
5846
6033
  function footingToSolid(spec) {
5847
6034
  try {
5848
- var _usingCtx$8 = _usingCtx();
6035
+ var _usingCtx$9 = _usingCtx();
5849
6036
  if (spec.length <= 0) return (0, brepjs.err)(specError("FOOTING_ZERO_LENGTH", "Footing length must be positive"));
5850
6037
  if (spec.width <= 0) return (0, brepjs.err)(specError("FOOTING_ZERO_WIDTH", "Footing width must be positive"));
5851
6038
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("FOOTING_ZERO_THICKNESS", "Footing thickness must be positive"));
@@ -5873,7 +6060,7 @@ function footingToSolid(spec) {
5873
6060
  ]
5874
6061
  ]);
5875
6062
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "FOOTING_PROFILE_FAILED", "Failed to create footing profile"));
5876
- const solidResult = (0, brepjs.extrude)(_usingCtx$8.u(profileResult.value), [
6063
+ const solidResult = (0, brepjs.extrude)(_usingCtx$9.u(profileResult.value), [
5877
6064
  0,
5878
6065
  0,
5879
6066
  thickness
@@ -5886,9 +6073,9 @@ function footingToSolid(spec) {
5886
6073
  }
5887
6074
  return (0, brepjs.ok)(solid);
5888
6075
  } catch (_) {
5889
- _usingCtx$8.e = _;
6076
+ _usingCtx$9.e = _;
5890
6077
  } finally {
5891
- _usingCtx$8.d();
6078
+ _usingCtx$9.d();
5892
6079
  }
5893
6080
  }
5894
6081
  function pileToSolid(spec) {
@@ -5941,12 +6128,9 @@ function pileToSolid(spec) {
5941
6128
  }
5942
6129
  //#endregion
5943
6130
  //#region src/elementFns/railingFns.ts
5944
- function railingToSolid(spec) {
6131
+ function panelRailing(spec) {
5945
6132
  try {
5946
- var _usingCtx$7 = _usingCtx();
5947
- if (spec.length <= 0) return (0, brepjs.err)(specError("RAILING_ZERO_LENGTH", "Railing length must be positive"));
5948
- if (spec.height <= 0) return (0, brepjs.err)(specError("RAILING_ZERO_HEIGHT", "Railing height must be positive"));
5949
- if (spec.thickness <= 0) return (0, brepjs.err)(specError("RAILING_ZERO_THICKNESS", "Railing thickness must be positive"));
6133
+ var _usingCtx$8 = _usingCtx();
5950
6134
  const { length, height, thickness } = spec;
5951
6135
  const profileResult = (0, brepjs.polygon)([
5952
6136
  [
@@ -5971,7 +6155,7 @@ function railingToSolid(spec) {
5971
6155
  ]
5972
6156
  ]);
5973
6157
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "RAILING_PROFILE_FAILED", "Failed to create railing profile"));
5974
- const solidResult = (0, brepjs.extrude)(_usingCtx$7.u(profileResult.value), [
6158
+ const solidResult = (0, brepjs.extrude)(_usingCtx$8.u(profileResult.value), [
5975
6159
  length,
5976
6160
  0,
5977
6161
  0
@@ -5984,16 +6168,72 @@ function railingToSolid(spec) {
5984
6168
  }
5985
6169
  return (0, brepjs.ok)(solid);
5986
6170
  } catch (_) {
5987
- _usingCtx$7.e = _;
6171
+ _usingCtx$8.e = _;
5988
6172
  } finally {
5989
- _usingCtx$7.d();
6173
+ _usingCtx$8.d();
6174
+ }
6175
+ }
6176
+ function postedRailing(spec) {
6177
+ const { length, height, thickness: t } = spec;
6178
+ const boxes = [];
6179
+ const postCount = Math.max(2, Math.round(length / 1e3) + 1);
6180
+ for (let i = 0; i < postCount; i++) {
6181
+ const x = t / 2 + (length - t) * (i / (postCount - 1));
6182
+ boxes.push((0, brepjs.box)(t, t, height, {
6183
+ at: [
6184
+ x,
6185
+ t / 2,
6186
+ height / 2
6187
+ ],
6188
+ centered: true
6189
+ }));
6190
+ }
6191
+ for (const z of [height - t / 2, t / 2]) boxes.push((0, brepjs.box)(length, t, t, {
6192
+ at: [
6193
+ length / 2,
6194
+ t / 2,
6195
+ z
6196
+ ],
6197
+ centered: true
6198
+ }));
6199
+ const scratch = [...boxes];
6200
+ let acc;
6201
+ let failure;
6202
+ for (const b of boxes) {
6203
+ if (acc === void 0) {
6204
+ acc = b;
6205
+ continue;
6206
+ }
6207
+ const fused = (0, brepjs.fuse)(acc, b);
6208
+ if (!fused.ok) {
6209
+ failure = fromBrepError(fused.error, "RAILING_FUSE_FAILED", "Failed to fuse railing parts");
6210
+ break;
6211
+ }
6212
+ acc = fused.value;
6213
+ scratch.push(acc);
5990
6214
  }
6215
+ const survivor = failure ? void 0 : acc;
6216
+ for (const s of scratch) if (s !== survivor) s[Symbol.dispose]();
6217
+ if (failure) return (0, brepjs.err)(failure);
6218
+ if (survivor === void 0) return (0, brepjs.err)(geometryError("RAILING_INVALID_SOLID", "Posted railing produced no solid"));
6219
+ const result = survivor;
6220
+ if (!(0, brepjs.isValidSolid)(result)) {
6221
+ result[Symbol.dispose]();
6222
+ return (0, brepjs.err)(geometryError("RAILING_INVALID_SOLID", "Posted railing solid failed validity check"));
6223
+ }
6224
+ return (0, brepjs.ok)(result);
6225
+ }
6226
+ function railingToSolid(spec) {
6227
+ if (spec.length <= 0) return (0, brepjs.err)(specError("RAILING_ZERO_LENGTH", "Railing length must be positive"));
6228
+ if (spec.height <= 0) return (0, brepjs.err)(specError("RAILING_ZERO_HEIGHT", "Railing height must be positive"));
6229
+ if (spec.thickness <= 0) return (0, brepjs.err)(specError("RAILING_ZERO_THICKNESS", "Railing thickness must be positive"));
6230
+ return spec.infill === "POSTED" ? postedRailing(spec) : panelRailing(spec);
5991
6231
  }
5992
6232
  //#endregion
5993
6233
  //#region src/elementFns/coveringFns.ts
5994
6234
  function coveringToSolid(spec) {
5995
6235
  try {
5996
- var _usingCtx$6 = _usingCtx();
6236
+ var _usingCtx$7 = _usingCtx();
5997
6237
  if (spec.length <= 0) return (0, brepjs.err)(specError("COVERING_ZERO_LENGTH", "Covering length must be positive"));
5998
6238
  if (spec.width <= 0) return (0, brepjs.err)(specError("COVERING_ZERO_WIDTH", "Covering width must be positive"));
5999
6239
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("COVERING_ZERO_THICKNESS", "Covering thickness must be positive"));
@@ -6021,7 +6261,7 @@ function coveringToSolid(spec) {
6021
6261
  ]
6022
6262
  ]);
6023
6263
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "COVERING_PROFILE_FAILED", "Failed to create covering profile"));
6024
- const solidResult = (0, brepjs.extrude)(_usingCtx$6.u(profileResult.value), [
6264
+ const solidResult = (0, brepjs.extrude)(_usingCtx$7.u(profileResult.value), [
6025
6265
  0,
6026
6266
  0,
6027
6267
  thickness
@@ -6034,9 +6274,9 @@ function coveringToSolid(spec) {
6034
6274
  }
6035
6275
  return (0, brepjs.ok)(solid);
6036
6276
  } catch (_) {
6037
- _usingCtx$6.e = _;
6277
+ _usingCtx$7.e = _;
6038
6278
  } finally {
6039
- _usingCtx$6.d();
6279
+ _usingCtx$7.d();
6040
6280
  }
6041
6281
  }
6042
6282
  //#endregion
@@ -6466,17 +6706,17 @@ var BimModel = class {
6466
6706
  }
6467
6707
  #cutWallGeometry(wall, openingSpec) {
6468
6708
  try {
6469
- var _usingCtx$5 = _usingCtx();
6709
+ var _usingCtx$6 = _usingCtx();
6470
6710
  const toolResult = openingToSolid(openingSpec, wall.spec.thickness);
6471
6711
  if (!toolResult.ok) return (0, brepjs.err)(toolResult.error);
6472
- const tool = _usingCtx$5.u(toolResult.value);
6712
+ const tool = _usingCtx$6.u(toolResult.value);
6473
6713
  const cutResult = (0, brepjs.cut)(wall.geometry, tool);
6474
6714
  if (!cutResult.ok) return (0, brepjs.err)(fromBrepError(cutResult.error, "WALL_CUT_FAILED", "Boolean cut of wall with opening failed"));
6475
6715
  return (0, brepjs.ok)(cutResult.value);
6476
6716
  } catch (_) {
6477
- _usingCtx$5.e = _;
6717
+ _usingCtx$6.e = _;
6478
6718
  } finally {
6479
- _usingCtx$5.d();
6719
+ _usingCtx$6.d();
6480
6720
  }
6481
6721
  }
6482
6722
  #replaceWallGeometry(wall, newGeometry) {
@@ -6713,18 +6953,365 @@ var BimModel = class {
6713
6953
  this.#elements.set(localId, el);
6714
6954
  return localId;
6715
6955
  }
6716
- #makeRel(fields) {
6717
- const localId = this.#counter.next();
6718
- const guid = deriveIfcGuidSync(makeRelKey(this.#modelScope, fields.kind, localId));
6719
- const rel = {
6720
- ...fields,
6721
- guid,
6722
- localId
6723
- };
6724
- this.#relationships.set(localId, rel);
6725
- return localId;
6956
+ #makeRel(fields) {
6957
+ const localId = this.#counter.next();
6958
+ const guid = deriveIfcGuidSync(makeRelKey(this.#modelScope, fields.kind, localId));
6959
+ const rel = {
6960
+ ...fields,
6961
+ guid,
6962
+ localId
6963
+ };
6964
+ this.#relationships.set(localId, rel);
6965
+ return localId;
6966
+ }
6967
+ };
6968
+ //#endregion
6969
+ //#region src/import/placement.ts
6970
+ /**
6971
+ * Metres-per-file-unit length scale read from the IfcUnitAssignment's
6972
+ * LENGTHUNIT. Multiply a file-unit length by this to get metres, then by 1000
6973
+ * for brepjs millimetres. Returns 1.0 (assume metres) when no length unit is
6974
+ * declared.
6975
+ */
6976
+ function readLengthScale(reader) {
6977
+ const assignments = reader.getLinesOfType(web_ifc.IFCUNITASSIGNMENT);
6978
+ for (const assignmentId of assignments) {
6979
+ const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
6980
+ for (const unitId of units) {
6981
+ const scale = lengthScaleFromUnit(reader, unitId);
6982
+ if (scale !== null) return scale;
6983
+ }
6984
+ }
6985
+ return 1;
6986
+ }
6987
+ /**
6988
+ * Radians-per-file-unit plane-angle scale read from the IfcUnitAssignment's
6989
+ * PLANEANGLEUNIT (1 for RADIAN, ~0.0174533 for DEGREE). Defaults to 1 (the IFC
6990
+ * default plane-angle unit is the radian).
6991
+ */
6992
+ function readPlaneAngleScale(reader) {
6993
+ for (const assignmentId of reader.getLinesOfType(web_ifc.IFCUNITASSIGNMENT)) {
6994
+ const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
6995
+ for (const unitId of units) {
6996
+ const s = planeAngleScaleFromUnit(reader, unitId);
6997
+ if (s !== null) return s;
6998
+ }
6999
+ }
7000
+ return 1;
7001
+ }
7002
+ function planeAngleScaleFromUnit(reader, unitId) {
7003
+ const unit = reader.getLine(unitId);
7004
+ if (unit === null) return null;
7005
+ const type = reader.getLineType(unitId);
7006
+ if (type === web_ifc.IFCSIUNIT) {
7007
+ if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
7008
+ if (enumValue(unit["Name"]) !== "RADIAN") return null;
7009
+ return 1;
7010
+ }
7011
+ if (type === web_ifc.IFCCONVERSIONBASEDUNIT) {
7012
+ if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
7013
+ const measureId = refValue$2(unit["ConversionFactor"]);
7014
+ if (measureId === null) return null;
7015
+ const factor = numericValue(reader.getLine(measureId)?.["ValueComponent"]);
7016
+ if (factor === null) return null;
7017
+ return factor;
7018
+ }
7019
+ return null;
7020
+ }
7021
+ function lengthScaleFromUnit(reader, unitId) {
7022
+ const unit = reader.getLine(unitId);
7023
+ if (unit === null) return null;
7024
+ const type = reader.getLineType(unitId);
7025
+ if (type === web_ifc.IFCSIUNIT) {
7026
+ if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
7027
+ if (enumValue(unit["Name"]) !== "METRE") return null;
7028
+ return siPrefixFactor(enumValue(unit["Prefix"]));
7029
+ }
7030
+ if (type === web_ifc.IFCCONVERSIONBASEDUNIT) {
7031
+ if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
7032
+ const measureId = refValue$2(unit["ConversionFactor"]);
7033
+ if (measureId === null) return null;
7034
+ const measure = reader.getLine(measureId);
7035
+ const factor = numericValue(measure?.["ValueComponent"]);
7036
+ if (factor === null) return null;
7037
+ const baseId = refValue$2(measure?.["UnitComponent"]);
7038
+ return factor * (baseId !== null ? lengthScaleFromUnit(reader, baseId) ?? 1 : 1);
7039
+ }
7040
+ return null;
7041
+ }
7042
+ function siPrefixFactor(prefix) {
7043
+ switch (prefix) {
7044
+ case null: return 1;
7045
+ case "KILO": return 1e3;
7046
+ case "HECTO": return 100;
7047
+ case "DECA": return 10;
7048
+ case "DECI": return .1;
7049
+ case "CENTI": return .01;
7050
+ case "MILLI": return .001;
7051
+ case "MICRO": return 1e-6;
7052
+ default: return 1;
7053
+ }
7054
+ }
7055
+ /**
7056
+ * Builds a row-major MatrixTransform (for brepjs `applyMatrix`) from an
7057
+ * (origin, axisX, axisZ) frame, using the SAME IFC orthonormalization as
7058
+ * {@link readAxis2Placement3D}: z = normalize(axisZ); x = normalize(axisX
7059
+ * projected onto the plane ⊥ z); y = z × x. The basis vectors are the matrix
7060
+ * columns, so the row-major linear array is [Xx,Yx,Zx, Xy,Yy,Zy, Xz,Yz,Zz];
7061
+ * translation = origin (mm). This is the display-side counterpart to the IFC
7062
+ * writer's Axis2Placement3D (Axis=Z, RefDirection=X) so on-screen placement and
7063
+ * the IFC export agree.
7064
+ */
7065
+ function placementToMatrix(f) {
7066
+ const z = normalize$1(f.axisZ);
7067
+ const dot = z[0] * f.axisX[0] + z[1] * f.axisX[1] + z[2] * f.axisX[2];
7068
+ const projX = [
7069
+ f.axisX[0] - dot * z[0],
7070
+ f.axisX[1] - dot * z[1],
7071
+ f.axisX[2] - dot * z[2]
7072
+ ];
7073
+ const x = lengthSq(projX) < 1e-12 ? normalize$1(orthogonal$2(z)) : normalize$1(projX);
7074
+ const y = cross$1(z, x);
7075
+ return {
7076
+ linear: [
7077
+ x[0],
7078
+ y[0],
7079
+ z[0],
7080
+ x[1],
7081
+ y[1],
7082
+ z[1],
7083
+ x[2],
7084
+ y[2],
7085
+ z[2]
7086
+ ],
7087
+ translation: [
7088
+ f.origin[0],
7089
+ f.origin[1],
7090
+ f.origin[2]
7091
+ ]
7092
+ };
7093
+ }
7094
+ function cross$1(a, b) {
7095
+ return [
7096
+ a[1] * b[2] - a[2] * b[1],
7097
+ a[2] * b[0] - a[0] * b[2],
7098
+ a[0] * b[1] - a[1] * b[0]
7099
+ ];
7100
+ }
7101
+ function lengthSq(v) {
7102
+ return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
7103
+ }
7104
+ function normalize$1(v) {
7105
+ const len = Math.sqrt(lengthSq(v));
7106
+ if (len < 1e-12) return [
7107
+ 0,
7108
+ 0,
7109
+ 1
7110
+ ];
7111
+ return [
7112
+ v[0] / len,
7113
+ v[1] / len,
7114
+ v[2] / len
7115
+ ];
7116
+ }
7117
+ function orthogonal$2(v) {
7118
+ return Math.abs(v[0]) < .9 ? cross$1(v, [
7119
+ 1,
7120
+ 0,
7121
+ 0
7122
+ ]) : cross$1(v, [
7123
+ 0,
7124
+ 1,
7125
+ 0
7126
+ ]);
7127
+ }
7128
+ function refValue$2(v) {
7129
+ if (v === null || v === void 0) return null;
7130
+ if (typeof v === "number") return v;
7131
+ const value = v.value;
7132
+ return typeof value === "number" ? value : null;
7133
+ }
7134
+ function asRefArray$1(v) {
7135
+ if (!Array.isArray(v)) return [];
7136
+ const out = [];
7137
+ for (const item of v) {
7138
+ const id = refValue$2(item);
7139
+ if (id !== null) out.push(id);
7140
+ }
7141
+ return out;
7142
+ }
7143
+ function numericValue(v) {
7144
+ if (typeof v === "number") return v;
7145
+ if (v === null || v === void 0) return null;
7146
+ const value = v.value;
7147
+ return typeof value === "number" ? value : null;
7148
+ }
7149
+ function enumValue(v) {
7150
+ if (typeof v === "string") return v;
7151
+ if (v === null || v === void 0) return null;
7152
+ const value = v.value;
7153
+ return typeof value === "string" ? value : null;
7154
+ }
7155
+ //#endregion
7156
+ //#region src/elementFns/stairFns.ts
7157
+ function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
7158
+ const pts = [];
7159
+ pts.push([
7160
+ 0,
7161
+ 0,
7162
+ 0
7163
+ ]);
7164
+ let x = 0;
7165
+ let z = 0;
7166
+ for (let i = 0; i < numberOfRisers; i++) {
7167
+ z += riserHeight;
7168
+ pts.push([
7169
+ x,
7170
+ 0,
7171
+ z
7172
+ ]);
7173
+ x += treadLength;
7174
+ pts.push([
7175
+ x,
7176
+ 0,
7177
+ z
7178
+ ]);
7179
+ }
7180
+ pts.push([
7181
+ x,
7182
+ 0,
7183
+ 0
7184
+ ]);
7185
+ return pts;
7186
+ }
7187
+ function stairFlightToSolid(spec) {
7188
+ try {
7189
+ var _usingCtx$5 = _usingCtx();
7190
+ if (spec.width <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
7191
+ if (spec.riserHeight <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
7192
+ if (spec.treadLength <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
7193
+ if (!Number.isInteger(spec.numberOfRisers) || spec.numberOfRisers < 1) return (0, brepjs.err)(specError("STAIR_FLIGHT_BAD_RISERS", "Stair flight numberOfRisers must be a positive integer"));
7194
+ const profileResult = (0, brepjs.polygon)(buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength));
7195
+ if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
7196
+ const solidResult = (0, brepjs.extrude)(_usingCtx$5.u(profileResult.value), [
7197
+ 0,
7198
+ spec.width,
7199
+ 0
7200
+ ]);
7201
+ if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "STAIR_FLIGHT_EXTRUDE_FAILED", "Failed to extrude stair flight silhouette"));
7202
+ const solid = solidResult.value;
7203
+ if (!(0, brepjs.isValidSolid)(solid)) {
7204
+ solid[Symbol.dispose]();
7205
+ return (0, brepjs.err)(geometryError("STAIR_FLIGHT_INVALID_SOLID", "Stair flight solid failed validity check"));
7206
+ }
7207
+ return (0, brepjs.ok)({
7208
+ solid,
7209
+ geometrySimplified: false
7210
+ });
7211
+ } catch (_) {
7212
+ _usingCtx$5.e = _;
7213
+ } finally {
7214
+ _usingCtx$5.d();
7215
+ }
7216
+ }
7217
+ //#endregion
7218
+ //#region src/elementFns/placedGeometry.ts
7219
+ function place(solid, frame) {
7220
+ const result = (0, brepjs.applyMatrix)(solid, placementToMatrix(frame));
7221
+ if (!result.ok) return (0, brepjs.err)(fromBrepError(result.error, "PLACED_GEOMETRY_FAILED", "Failed to place element geometry"));
7222
+ return (0, brepjs.ok)(result.value);
7223
+ }
7224
+ function disposeAll(solids) {
7225
+ for (const s of solids) s[Symbol.dispose]();
7226
+ }
7227
+ /**
7228
+ * Returns each element's geometry transformed to its world placement, as fresh
7229
+ * caller-owned solids, wrapped in a `Result` (Layer-2 code prefers `Result` over
7230
+ * throwing). **Dispose the returned solids** (e.g. via `using` / `[Symbol.dispose]`)
7231
+ * when you own their lifetime — they are independent of the model
7232
+ * (`BimModel[Symbol.dispose]` frees only the stored, unplaced `.geometry`). On any
7233
+ * failure the solids already built for this call are disposed before the error is
7234
+ * returned, so no partial array is leaked.
7235
+ *
7236
+ * Stairs carry no element solid (`.geometry` is null), so flight solids are built
7237
+ * from `spec.flights` and placed per flight. Curtain walls return placed panels +
7238
+ * mullions. Elements with no solid geometry (doors/windows/ramps/groups/spatial)
7239
+ * return an empty array.
7240
+ */
7241
+ function placedSolids(el) {
7242
+ switch (el.category) {
7243
+ case "WALL":
7244
+ case "SLAB":
7245
+ case "BEAM":
7246
+ case "COLUMN":
7247
+ case "SPACE":
7248
+ case "ROOF":
7249
+ case "FOOTING":
7250
+ case "PILE":
7251
+ case "RAILING": {
7252
+ const placed = place(el.geometry, el.spec);
7253
+ if (!placed.ok) return placed;
7254
+ return (0, brepjs.ok)([placed.value]);
7255
+ }
7256
+ case "STAIR": {
7257
+ const out = [];
7258
+ for (const flight of el.spec.flights) try {
7259
+ var _usingCtx$4 = _usingCtx();
7260
+ const built = stairFlightToSolid(flight);
7261
+ if (!built.ok) {
7262
+ disposeAll(out);
7263
+ return (0, brepjs.err)(built.error);
7264
+ }
7265
+ const placed = place(_usingCtx$4.u(built.value.solid), flight);
7266
+ if (!placed.ok) {
7267
+ disposeAll(out);
7268
+ return placed;
7269
+ }
7270
+ out.push(placed.value);
7271
+ } catch (_) {
7272
+ _usingCtx$4.e = _;
7273
+ } finally {
7274
+ _usingCtx$4.d();
7275
+ }
7276
+ return (0, brepjs.ok)(out);
7277
+ }
7278
+ case "CURTAIN_WALL": {
7279
+ const out = [];
7280
+ for (const c of [...el.geometry.panels, ...el.geometry.mullions]) try {
7281
+ var _usingCtx3 = _usingCtx();
7282
+ const componentLocal = place(c.solid, {
7283
+ origin: c.origin,
7284
+ axisX: [
7285
+ 1,
7286
+ 0,
7287
+ 0
7288
+ ],
7289
+ axisZ: [
7290
+ 0,
7291
+ 0,
7292
+ 1
7293
+ ]
7294
+ });
7295
+ if (!componentLocal.ok) {
7296
+ disposeAll(out);
7297
+ return componentLocal;
7298
+ }
7299
+ const placed = place(_usingCtx3.u(componentLocal.value), el.spec);
7300
+ if (!placed.ok) {
7301
+ disposeAll(out);
7302
+ return placed;
7303
+ }
7304
+ out.push(placed.value);
7305
+ } catch (_) {
7306
+ _usingCtx3.e = _;
7307
+ } finally {
7308
+ _usingCtx3.d();
7309
+ }
7310
+ return (0, brepjs.ok)(out);
7311
+ }
7312
+ default: return (0, brepjs.ok)([]);
6726
7313
  }
6727
- };
7314
+ }
6728
7315
  //#endregion
6729
7316
  //#region src/ifc-writer/schemaVersion.ts
6730
7317
  /**
@@ -7995,7 +8582,7 @@ function writeSlabGeometry(w, spec, geomSubContextId, parentPlacementId) {
7995
8582
  productDefinitionShapeId
7996
8583
  };
7997
8584
  }
7998
- function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
8585
+ function writeRoofGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
7999
8586
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
8000
8587
  const localPlacementId = w.nextId();
8001
8588
  w.writeLine({
@@ -8004,6 +8591,14 @@ function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
8004
8591
  PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
8005
8592
  RelativePlacement: w.ref(placement3DId)
8006
8593
  });
8594
+ if (spec.pitch !== void 0) {
8595
+ const tess = writeTessellation(w, solid, geomSubContextId, localPlacementId);
8596
+ return {
8597
+ localPlacementId,
8598
+ productDefinitionShapeId: tess.productDefinitionShapeId,
8599
+ usedFallback: tess.usedFallback
8600
+ };
8601
+ }
8007
8602
  const lengthM = toIfcLengthM(spec.length);
8008
8603
  const widthM = toIfcLengthM(spec.width);
8009
8604
  const thicknessM = toIfcLengthM(spec.thickness);
@@ -8071,7 +8666,8 @@ function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
8071
8666
  });
8072
8667
  return {
8073
8668
  localPlacementId,
8074
- productDefinitionShapeId
8669
+ productDefinitionShapeId,
8670
+ usedFallback: false
8075
8671
  };
8076
8672
  }
8077
8673
  function writeAxis2Placement2D$1(w) {
@@ -8774,68 +9370,6 @@ function writeRelContainedInSpatialStructure(w, guid, ownerHistoryId, relatingSt
8774
9370
  });
8775
9371
  }
8776
9372
  //#endregion
8777
- //#region src/elementFns/stairFns.ts
8778
- function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
8779
- const pts = [];
8780
- pts.push([
8781
- 0,
8782
- 0,
8783
- 0
8784
- ]);
8785
- let x = 0;
8786
- let z = 0;
8787
- for (let i = 0; i < numberOfRisers; i++) {
8788
- z += riserHeight;
8789
- pts.push([
8790
- x,
8791
- 0,
8792
- z
8793
- ]);
8794
- x += treadLength;
8795
- pts.push([
8796
- x,
8797
- 0,
8798
- z
8799
- ]);
8800
- }
8801
- pts.push([
8802
- x,
8803
- 0,
8804
- 0
8805
- ]);
8806
- return pts;
8807
- }
8808
- function stairFlightToSolid(spec) {
8809
- try {
8810
- var _usingCtx$4 = _usingCtx();
8811
- if (spec.width <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
8812
- if (spec.riserHeight <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
8813
- if (spec.treadLength <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
8814
- if (!Number.isInteger(spec.numberOfRisers) || spec.numberOfRisers < 1) return (0, brepjs.err)(specError("STAIR_FLIGHT_BAD_RISERS", "Stair flight numberOfRisers must be a positive integer"));
8815
- const profileResult = (0, brepjs.polygon)(buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength));
8816
- if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
8817
- const solidResult = (0, brepjs.extrude)(_usingCtx$4.u(profileResult.value), [
8818
- 0,
8819
- spec.width,
8820
- 0
8821
- ]);
8822
- if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "STAIR_FLIGHT_EXTRUDE_FAILED", "Failed to extrude stair flight silhouette"));
8823
- const solid = solidResult.value;
8824
- if (!(0, brepjs.isValidSolid)(solid)) {
8825
- solid[Symbol.dispose]();
8826
- return (0, brepjs.err)(geometryError("STAIR_FLIGHT_INVALID_SOLID", "Stair flight solid failed validity check"));
8827
- }
8828
- return (0, brepjs.ok)({
8829
- solid,
8830
- geometrySimplified: false
8831
- });
8832
- } catch (_) {
8833
- _usingCtx$4.e = _;
8834
- } finally {
8835
- _usingCtx$4.d();
8836
- }
8837
- }
8838
- //#endregion
8839
9373
  //#region src/elementFns/rampFns.ts
8840
9374
  function buildSilhouette(length, rise, thickness) {
8841
9375
  return [
@@ -9146,7 +9680,7 @@ function writeRampAssembly(w, spec, rampKey, ownerHistoryId, geomSubContextId, p
9146
9680
  }
9147
9681
  //#endregion
9148
9682
  //#region src/ifc-writer/railingWriter.ts
9149
- function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9683
+ function writeRailingGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
9150
9684
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
9151
9685
  const localPlacementId = w.nextId();
9152
9686
  w.writeLine({
@@ -9155,6 +9689,15 @@ function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9155
9689
  PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
9156
9690
  RelativePlacement: w.ref(placement3DId)
9157
9691
  });
9692
+ if (spec.infill === "POSTED") {
9693
+ const tess = writeTessellation(w, solid, geomSubContextId, localPlacementId);
9694
+ return {
9695
+ localPlacementId,
9696
+ productDefinitionShapeId: tess.productDefinitionShapeId,
9697
+ bodyItemId: null,
9698
+ usedFallback: tess.usedFallback
9699
+ };
9700
+ }
9158
9701
  const thicknessM = toIfcLengthM(spec.thickness);
9159
9702
  const heightM = toIfcLengthM(spec.height);
9160
9703
  const lengthM = toIfcLengthM(spec.length);
@@ -9231,7 +9774,8 @@ function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9231
9774
  return {
9232
9775
  localPlacementId,
9233
9776
  productDefinitionShapeId,
9234
- bodyItemId: extrusionId
9777
+ bodyItemId: extrusionId,
9778
+ usedFallback: false
9235
9779
  };
9236
9780
  }
9237
9781
  function writeRailingEntity(w, guid, name, predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
@@ -11602,7 +12146,8 @@ async function toIfc(model, meta) {
11602
12146
  for (const [i, roof] of roofs.entries()) {
11603
12147
  const containingId = findContainerOf(roof.localId, relationships);
11604
12148
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
11605
- const { localPlacementId, productDefinitionShapeId } = writeRoofGeometry(w, roof.spec, geomSubContextId, storeyPlacementId);
12149
+ const { localPlacementId, productDefinitionShapeId, usedFallback } = writeRoofGeometry(w, roof.spec, roof.geometry, geomSubContextId, storeyPlacementId);
12150
+ if (usedFallback) console.warn(`Roof ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
11606
12151
  const roofExpressId = writeRoofEntity(w, roof.guid, `Roof ${i + 1}`, roof.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
11607
12152
  idMap.set(roof.localId, roofExpressId);
11608
12153
  placementMap.set(roof.localId, localPlacementId);
@@ -11674,14 +12219,15 @@ async function toIfc(model, meta) {
11674
12219
  for (const [i, railing] of railings.entries()) {
11675
12220
  const containingId = findContainerOf(railing.localId, relationships);
11676
12221
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
11677
- const { localPlacementId, productDefinitionShapeId, bodyItemId } = writeRailingGeometry(w, railing.spec, geomSubContextId, storeyPlacementId);
12222
+ const { localPlacementId, productDefinitionShapeId, bodyItemId, usedFallback } = writeRailingGeometry(w, railing.spec, railing.geometry, geomSubContextId, storeyPlacementId);
12223
+ if (usedFallback) console.warn(`Railing ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
11678
12224
  const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
11679
12225
  idMap.set(railing.localId, railingExpressId);
11680
12226
  placementMap.set(railing.localId, localPlacementId);
11681
12227
  writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
11682
12228
  writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
11683
12229
  if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
11684
- applySurfaceStyle(w, model, railing.localId, bodyItemId);
12230
+ if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
11685
12231
  }
11686
12232
  for (const [i, covering] of coverings.entries()) {
11687
12233
  const containingId = findContainerOf(covering.localId, relationships);
@@ -12209,120 +12755,6 @@ var SpfReader = class SpfReader {
12209
12755
  }
12210
12756
  };
12211
12757
  //#endregion
12212
- //#region src/import/placement.ts
12213
- /**
12214
- * Metres-per-file-unit length scale read from the IfcUnitAssignment's
12215
- * LENGTHUNIT. Multiply a file-unit length by this to get metres, then by 1000
12216
- * for brepjs millimetres. Returns 1.0 (assume metres) when no length unit is
12217
- * declared.
12218
- */
12219
- function readLengthScale(reader) {
12220
- const assignments = reader.getLinesOfType(web_ifc.IFCUNITASSIGNMENT);
12221
- for (const assignmentId of assignments) {
12222
- const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
12223
- for (const unitId of units) {
12224
- const scale = lengthScaleFromUnit(reader, unitId);
12225
- if (scale !== null) return scale;
12226
- }
12227
- }
12228
- return 1;
12229
- }
12230
- /**
12231
- * Radians-per-file-unit plane-angle scale read from the IfcUnitAssignment's
12232
- * PLANEANGLEUNIT (1 for RADIAN, ~0.0174533 for DEGREE). Defaults to 1 (the IFC
12233
- * default plane-angle unit is the radian).
12234
- */
12235
- function readPlaneAngleScale(reader) {
12236
- for (const assignmentId of reader.getLinesOfType(web_ifc.IFCUNITASSIGNMENT)) {
12237
- const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
12238
- for (const unitId of units) {
12239
- const s = planeAngleScaleFromUnit(reader, unitId);
12240
- if (s !== null) return s;
12241
- }
12242
- }
12243
- return 1;
12244
- }
12245
- function planeAngleScaleFromUnit(reader, unitId) {
12246
- const unit = reader.getLine(unitId);
12247
- if (unit === null) return null;
12248
- const type = reader.getLineType(unitId);
12249
- if (type === web_ifc.IFCSIUNIT) {
12250
- if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
12251
- if (enumValue(unit["Name"]) !== "RADIAN") return null;
12252
- return 1;
12253
- }
12254
- if (type === web_ifc.IFCCONVERSIONBASEDUNIT) {
12255
- if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
12256
- const measureId = refValue$2(unit["ConversionFactor"]);
12257
- if (measureId === null) return null;
12258
- const factor = numericValue(reader.getLine(measureId)?.["ValueComponent"]);
12259
- if (factor === null) return null;
12260
- return factor;
12261
- }
12262
- return null;
12263
- }
12264
- function lengthScaleFromUnit(reader, unitId) {
12265
- const unit = reader.getLine(unitId);
12266
- if (unit === null) return null;
12267
- const type = reader.getLineType(unitId);
12268
- if (type === web_ifc.IFCSIUNIT) {
12269
- if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
12270
- if (enumValue(unit["Name"]) !== "METRE") return null;
12271
- return siPrefixFactor(enumValue(unit["Prefix"]));
12272
- }
12273
- if (type === web_ifc.IFCCONVERSIONBASEDUNIT) {
12274
- if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
12275
- const measureId = refValue$2(unit["ConversionFactor"]);
12276
- if (measureId === null) return null;
12277
- const measure = reader.getLine(measureId);
12278
- const factor = numericValue(measure?.["ValueComponent"]);
12279
- if (factor === null) return null;
12280
- const baseId = refValue$2(measure?.["UnitComponent"]);
12281
- return factor * (baseId !== null ? lengthScaleFromUnit(reader, baseId) ?? 1 : 1);
12282
- }
12283
- return null;
12284
- }
12285
- function siPrefixFactor(prefix) {
12286
- switch (prefix) {
12287
- case null: return 1;
12288
- case "KILO": return 1e3;
12289
- case "HECTO": return 100;
12290
- case "DECA": return 10;
12291
- case "DECI": return .1;
12292
- case "CENTI": return .01;
12293
- case "MILLI": return .001;
12294
- case "MICRO": return 1e-6;
12295
- default: return 1;
12296
- }
12297
- }
12298
- function refValue$2(v) {
12299
- if (v === null || v === void 0) return null;
12300
- if (typeof v === "number") return v;
12301
- const value = v.value;
12302
- return typeof value === "number" ? value : null;
12303
- }
12304
- function asRefArray$1(v) {
12305
- if (!Array.isArray(v)) return [];
12306
- const out = [];
12307
- for (const item of v) {
12308
- const id = refValue$2(item);
12309
- if (id !== null) out.push(id);
12310
- }
12311
- return out;
12312
- }
12313
- function numericValue(v) {
12314
- if (typeof v === "number") return v;
12315
- if (v === null || v === void 0) return null;
12316
- const value = v.value;
12317
- return typeof value === "number" ? value : null;
12318
- }
12319
- function enumValue(v) {
12320
- if (typeof v === "string") return v;
12321
- if (v === null || v === void 0) return null;
12322
- const value = v.value;
12323
- return typeof value === "string" ? value : null;
12324
- }
12325
- //#endregion
12326
12758
  //#region src/import/spatialTree.ts
12327
12759
  var CATEGORY_BY_TYPE = new Map([
12328
12760
  [web_ifc.IFCPROJECT, "PROJECT"],
@@ -13948,6 +14380,7 @@ var RoofSpecSchema = object({
13948
14380
  fireRating: string().optional(),
13949
14381
  thermalTransmittance: number().positive().optional(),
13950
14382
  status: string().optional(),
14383
+ pitch: number().positive().max(89).optional(),
13951
14384
  materialLayers: array(MaterialLayerSchema).optional(),
13952
14385
  layerSetName: string().optional(),
13953
14386
  classification: ClassificationRefSchema.optional(),
@@ -14279,6 +14712,7 @@ var RailingSpecSchema = object({
14279
14712
  "HANDRAIL",
14280
14713
  "NOTDEFINED"
14281
14714
  ]).optional(),
14715
+ infill: _enum(["PANEL", "POSTED"]).optional(),
14282
14716
  materialName: string().min(1),
14283
14717
  isExternal: boolean().optional(),
14284
14718
  fireRating: string().optional(),
@@ -15545,24 +15979,24 @@ var XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
15545
15979
  function xmlDocument(rootXml) {
15546
15980
  return `${XML_DECLARATION}\n${rootXml}\n`;
15547
15981
  }
15548
- function parseAttrs(raw) {
15549
- const attrs = {};
15550
- const attrRe = /([\w:.-]+)\s*=\s*"([^"]*)"/g;
15551
- let m;
15552
- while ((m = attrRe.exec(raw)) !== null) {
15553
- const key = m[1];
15554
- const val = m[2];
15555
- if (key !== void 0 && val !== void 0) attrs[key] = unescapeXml(val);
15556
- }
15557
- return attrs;
15982
+ function isNameChar(c) {
15983
+ return /[\w:.-]/.test(c);
15984
+ }
15985
+ function isWhitespace(c) {
15986
+ return /\s/.test(c);
15558
15987
  }
15559
15988
  /**
15560
- * Parse an XML string into a tree. Tolerant of the XML declaration, comments,
15561
- * whitespace, self-closing tags, and CDATA-free text. Throws on malformed
15562
- * structure (unbalanced tags); callers wrap this in a `Result`.
15989
+ * Parse an XML string into a tree. Tolerant of the XML declaration, processing
15990
+ * instructions, comments, whitespace, self-closing tags, and CDATA-free text.
15991
+ * Throws on malformed or unbalanced structure; callers wrap this in a `Result`.
15992
+ *
15993
+ * This is a hand-written cursor scan rather than a single tokenizing regex: the
15994
+ * input is an untrusted `.bcfzip` payload, and a backtracking regex over
15995
+ * uncontrolled data is a polynomial-ReDoS vector. Every construct here is
15996
+ * consumed by an `indexOf` or a single-character advance, so the parse is linear
15997
+ * in the input length. The sibling `ids/idsXml.ts` parser scans the same way.
15563
15998
  */
15564
15999
  function parseXml(xml) {
15565
- const tokenRe = /<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<\/([\w:.-]+)\s*>|<([\w:.-]+)((?:\s+[\w:.-]+\s*=\s*"[^"]*")*)\s*(\/?)>|([^<]+)/g;
15566
16000
  const root = {
15567
16001
  tag: "#root",
15568
16002
  attrs: {},
@@ -15570,36 +16004,84 @@ function parseXml(xml) {
15570
16004
  text: ""
15571
16005
  };
15572
16006
  const stack = [root];
15573
- let m;
15574
- while ((m = tokenRe.exec(xml)) !== null) {
15575
- const [full, closeTag, openTag, attrsRaw, selfClose, textRun] = m;
15576
- if (full.startsWith("<!--") || full.startsWith("<?")) continue;
15577
- if (closeTag !== void 0) {
15578
- const top = stack[stack.length - 1];
15579
- if (top === void 0 || top.tag !== closeTag) throw new Error(`Unbalanced XML: unexpected </${closeTag}>`);
15580
- stack.pop();
15581
- continue;
15582
- }
15583
- if (openTag !== void 0) {
15584
- const node = {
15585
- tag: openTag,
15586
- attrs: parseAttrs(attrsRaw ?? ""),
15587
- children: [],
15588
- text: ""
15589
- };
15590
- const parent = stack[stack.length - 1];
15591
- if (parent === void 0) throw new Error("Unbalanced XML: empty stack");
15592
- parent.children.push(node);
15593
- if (selfClose !== "/") stack.push(node);
15594
- continue;
16007
+ const len = xml.length;
16008
+ let i = 0;
16009
+ const fail = (msg) => {
16010
+ throw new Error(`Malformed XML: ${msg} at offset ${String(i)}`);
16011
+ };
16012
+ const skipWhitespace = () => {
16013
+ while (i < len && isWhitespace(xml.charAt(i))) i += 1;
16014
+ };
16015
+ const readName = () => {
16016
+ const start = i;
16017
+ while (i < len && isNameChar(xml.charAt(i))) i += 1;
16018
+ return xml.slice(start, i);
16019
+ };
16020
+ const readAttrs = () => {
16021
+ const attrs = {};
16022
+ for (;;) {
16023
+ skipWhitespace();
16024
+ const c = xml.charAt(i);
16025
+ if (i >= len || c === ">" || c === "/") return attrs;
16026
+ const name = readName();
16027
+ if (name.length === 0) fail("expected attribute name");
16028
+ skipWhitespace();
16029
+ if (xml.charAt(i) !== "=") fail(`expected '=' after attribute "${name}"`);
16030
+ i += 1;
16031
+ skipWhitespace();
16032
+ if (xml.charAt(i) !== "\"") fail(`expected '"' opening attribute "${name}"`);
16033
+ i += 1;
16034
+ const end = xml.indexOf("\"", i);
16035
+ if (end === -1) fail(`unterminated value for attribute "${name}"`);
16036
+ attrs[name] = unescapeXml(xml.slice(i, end));
16037
+ i = end + 1;
15595
16038
  }
15596
- if (textRun !== void 0) {
15597
- const decoded = unescapeXml(textRun);
15598
- if (decoded.trim().length > 0) {
15599
- const top = stack[stack.length - 1];
15600
- if (top !== void 0) top.text += decoded;
15601
- }
16039
+ };
16040
+ while (i < len) if (xml.startsWith("<!--", i)) {
16041
+ const end = xml.indexOf("-->", i + 4);
16042
+ if (end === -1) fail("unterminated comment");
16043
+ i = end + 3;
16044
+ } else if (xml.startsWith("<?", i)) {
16045
+ const end = xml.indexOf("?>", i + 2);
16046
+ if (end === -1) fail("unterminated processing instruction");
16047
+ i = end + 2;
16048
+ } else if (xml.startsWith("</", i)) {
16049
+ i += 2;
16050
+ const name = readName();
16051
+ skipWhitespace();
16052
+ if (xml.charAt(i) !== ">") fail(`expected '>' closing </${name}>`);
16053
+ i += 1;
16054
+ const top = stack[stack.length - 1];
16055
+ if (top === void 0 || top.tag !== name) throw new Error(`Unbalanced XML: unexpected </${name}>`);
16056
+ stack.pop();
16057
+ } else if (xml.charAt(i) === "<") {
16058
+ i += 1;
16059
+ const tag = readName();
16060
+ if (tag.length === 0) fail("expected element name");
16061
+ const node = {
16062
+ tag,
16063
+ attrs: readAttrs(),
16064
+ children: [],
16065
+ text: ""
16066
+ };
16067
+ const parent = stack[stack.length - 1];
16068
+ if (parent === void 0) throw new Error("Unbalanced XML: empty stack");
16069
+ parent.children.push(node);
16070
+ skipWhitespace();
16071
+ if (xml.startsWith("/>", i)) i += 2;
16072
+ else if (xml.charAt(i) === ">") {
16073
+ i += 1;
16074
+ stack.push(node);
16075
+ } else fail(`expected '>' in <${tag}>`);
16076
+ } else {
16077
+ const next = xml.indexOf("<", i);
16078
+ const end = next === -1 ? len : next;
16079
+ const decoded = unescapeXml(xml.slice(i, end));
16080
+ if (decoded.trim().length > 0) {
16081
+ const top = stack[stack.length - 1];
16082
+ if (top !== void 0) top.text += decoded;
15602
16083
  }
16084
+ i = end;
15603
16085
  }
15604
16086
  if (stack.length !== 1) throw new Error("Unbalanced XML: unclosed elements remain");
15605
16087
  const top = root.children[0];
@@ -15996,6 +16478,7 @@ exports.parseSystemSpec = parseSystemSpec;
15996
16478
  exports.parseWallSpec = parseWallSpec;
15997
16479
  exports.parseWindowSpec = parseWindowSpec;
15998
16480
  exports.parseZoneSpec = parseZoneSpec;
16481
+ exports.placedSolids = placedSolids;
15999
16482
  exports.schemaSupports = schemaSupports;
16000
16483
  exports.serializeBcfFiles = serializeBcfFiles;
16001
16484
  exports.serializeCobieToCsv = serializeCobieToCsv;