brepjs-bim 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { addHoles, applyMatrix, autoHeal, castShape, cut, err, extrude, getKernel, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, validSolid } from "brepjs";
1
+ import { addHoles, applyMatrix, autoHeal, box, castShape, convexHull, cut, err, extrude, fuse, getKernel, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, 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
@@ -242,7 +242,7 @@ function _usingCtx() {
242
242
  //#region src/elementFns/wallFns.ts
243
243
  function wallToSolid(spec) {
244
244
  try {
245
- var _usingCtx$18 = _usingCtx();
245
+ var _usingCtx$19 = _usingCtx();
246
246
  if (spec.length <= 0) return err(specError("WALL_ZERO_LENGTH", "Wall length must be positive"));
247
247
  if (spec.height <= 0) return err(specError("WALL_ZERO_HEIGHT", "Wall height must be positive"));
248
248
  if (spec.thickness <= 0) return err(specError("WALL_ZERO_THICKNESS", "Wall thickness must be positive"));
@@ -270,7 +270,7 @@ function wallToSolid(spec) {
270
270
  ]
271
271
  ]);
272
272
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "WALL_PROFILE_FAILED", "Failed to create wall profile"));
273
- const solidResult = extrude(_usingCtx$18.u(profileResult.value), [
273
+ const solidResult = extrude(_usingCtx$19.u(profileResult.value), [
274
274
  length,
275
275
  0,
276
276
  0
@@ -283,16 +283,16 @@ function wallToSolid(spec) {
283
283
  }
284
284
  return ok(solid);
285
285
  } catch (_) {
286
- _usingCtx$18.e = _;
286
+ _usingCtx$19.e = _;
287
287
  } finally {
288
- _usingCtx$18.d();
288
+ _usingCtx$19.d();
289
289
  }
290
290
  }
291
291
  //#endregion
292
292
  //#region src/elementFns/slabFns.ts
293
293
  function slabToSolid(spec) {
294
294
  try {
295
- var _usingCtx$17 = _usingCtx();
295
+ var _usingCtx$18 = _usingCtx();
296
296
  if (spec.length <= 0) return err(specError("SLAB_ZERO_LENGTH", "Slab length must be positive"));
297
297
  if (spec.width <= 0) return err(specError("SLAB_ZERO_WIDTH", "Slab width must be positive"));
298
298
  if (spec.thickness <= 0) return err(specError("SLAB_ZERO_THICKNESS", "Slab thickness must be positive"));
@@ -320,7 +320,7 @@ function slabToSolid(spec) {
320
320
  ]
321
321
  ]);
322
322
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SLAB_PROFILE_FAILED", "Failed to create slab profile"));
323
- const solidResult = extrude(_usingCtx$17.u(profileResult.value), [
323
+ const solidResult = extrude(_usingCtx$18.u(profileResult.value), [
324
324
  0,
325
325
  0,
326
326
  thickness
@@ -333,9 +333,9 @@ function slabToSolid(spec) {
333
333
  }
334
334
  return ok(solid);
335
335
  } catch (_) {
336
- _usingCtx$17.e = _;
336
+ _usingCtx$18.e = _;
337
337
  } finally {
338
- _usingCtx$17.d();
338
+ _usingCtx$18.d();
339
339
  }
340
340
  }
341
341
  //#endregion
@@ -5012,14 +5012,14 @@ function to3D(points) {
5012
5012
  }
5013
5013
  function extendedProfileToFace(profile) {
5014
5014
  try {
5015
- var _usingCtx$16 = _usingCtx();
5015
+ var _usingCtx$17 = _usingCtx();
5016
5016
  const invalid = validateProfile(profile);
5017
5017
  if (invalid !== null) return err(invalid);
5018
5018
  const outerResult = polygon(to3D(outerLoop(profile)));
5019
5019
  if (!outerResult.ok) return err(fromBrepError(outerResult.error, "PROFILE_FACE_FAILED", "Failed to build profile outer face"));
5020
5020
  const holes = holeLoops(profile);
5021
5021
  if (holes.length === 0) return ok(outerResult.value);
5022
- const outerFace = _usingCtx$16.u(outerResult.value);
5022
+ const outerFace = _usingCtx$17.u(outerResult.value);
5023
5023
  const holeFaces = [];
5024
5024
  const holeWires = [];
5025
5025
  const disposeHoleFaces = () => {
@@ -5044,9 +5044,9 @@ function extendedProfileToFace(profile) {
5044
5044
  disposeHoleFaces();
5045
5045
  return ok(faceWithHoles);
5046
5046
  } catch (_) {
5047
- _usingCtx$16.e = _;
5047
+ _usingCtx$17.e = _;
5048
5048
  } finally {
5049
- _usingCtx$16.d();
5049
+ _usingCtx$17.d();
5050
5050
  }
5051
5051
  }
5052
5052
  //#endregion
@@ -5367,7 +5367,7 @@ function profileToPolygon(profile, circleSegments = 32) {
5367
5367
  //#region src/elementFns/beamFns.ts
5368
5368
  function beamToSolid(spec) {
5369
5369
  try {
5370
- var _usingCtx$15 = _usingCtx();
5370
+ var _usingCtx$16 = _usingCtx();
5371
5371
  if (spec.length <= 0) return err(specError("BEAM_ZERO_LENGTH", "Beam length must be positive"));
5372
5372
  if (isExtendedProfile(spec.profile)) try {
5373
5373
  var _usingCtx3 = _usingCtx();
@@ -5402,7 +5402,7 @@ function beamToSolid(spec) {
5402
5402
  py
5403
5403
  ]));
5404
5404
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "BEAM_PROFILE_FAILED", "Failed to create beam profile"));
5405
- const solidResult = extrude(_usingCtx$15.u(profileResult.value), [
5405
+ const solidResult = extrude(_usingCtx$16.u(profileResult.value), [
5406
5406
  spec.length,
5407
5407
  0,
5408
5408
  0
@@ -5415,16 +5415,16 @@ function beamToSolid(spec) {
5415
5415
  }
5416
5416
  return ok(solid);
5417
5417
  } catch (_) {
5418
- _usingCtx$15.e = _;
5418
+ _usingCtx$16.e = _;
5419
5419
  } finally {
5420
- _usingCtx$15.d();
5420
+ _usingCtx$16.d();
5421
5421
  }
5422
5422
  }
5423
5423
  //#endregion
5424
5424
  //#region src/elementFns/columnFns.ts
5425
5425
  function columnToSolid(spec) {
5426
5426
  try {
5427
- var _usingCtx$14 = _usingCtx();
5427
+ var _usingCtx$15 = _usingCtx();
5428
5428
  if (spec.height <= 0) return err(specError("COLUMN_ZERO_HEIGHT", "Column height must be positive"));
5429
5429
  if (isExtendedProfile(spec.profile)) try {
5430
5430
  var _usingCtx3 = _usingCtx();
@@ -5452,7 +5452,7 @@ function columnToSolid(spec) {
5452
5452
  const profilePts = profilePtsResult.value;
5453
5453
  const profileResult = polygon(profilePts);
5454
5454
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "COLUMN_PROFILE_FAILED", "Failed to create column profile"));
5455
- const solidResult = extrude(_usingCtx$14.u(profileResult.value), [
5455
+ const solidResult = extrude(_usingCtx$15.u(profileResult.value), [
5456
5456
  0,
5457
5457
  0,
5458
5458
  spec.height
@@ -5465,9 +5465,9 @@ function columnToSolid(spec) {
5465
5465
  }
5466
5466
  return ok(solid);
5467
5467
  } catch (_) {
5468
- _usingCtx$14.e = _;
5468
+ _usingCtx$15.e = _;
5469
5469
  } finally {
5470
- _usingCtx$14.d();
5470
+ _usingCtx$15.d();
5471
5471
  }
5472
5472
  }
5473
5473
  //#endregion
@@ -5475,7 +5475,7 @@ function columnToSolid(spec) {
5475
5475
  var EPSILON_MM$1 = 1;
5476
5476
  function openingToSolid(spec, wallThickness) {
5477
5477
  try {
5478
- var _usingCtx$13 = _usingCtx();
5478
+ var _usingCtx$14 = _usingCtx();
5479
5479
  if (spec.width <= 0) return err(specError("OPENING_ZERO_WIDTH", "Opening width must be positive"));
5480
5480
  if (spec.height <= 0) return err(specError("OPENING_ZERO_HEIGHT", "Opening height must be positive"));
5481
5481
  if (wallThickness <= 0) return err(specError("OPENING_ZERO_WALL_THICKNESS", "Wall thickness must be positive"));
@@ -5507,7 +5507,7 @@ function openingToSolid(spec, wallThickness) {
5507
5507
  ]
5508
5508
  ]);
5509
5509
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "OPENING_PROFILE_FAILED", "Failed to create opening profile"));
5510
- const solidResult = extrude(_usingCtx$13.u(profileResult.value), [
5510
+ const solidResult = extrude(_usingCtx$14.u(profileResult.value), [
5511
5511
  spec.width,
5512
5512
  0,
5513
5513
  0
@@ -5520,9 +5520,9 @@ function openingToSolid(spec, wallThickness) {
5520
5520
  }
5521
5521
  return ok(solid);
5522
5522
  } catch (_) {
5523
- _usingCtx$13.e = _;
5523
+ _usingCtx$14.e = _;
5524
5524
  } finally {
5525
- _usingCtx$13.d();
5525
+ _usingCtx$14.d();
5526
5526
  }
5527
5527
  }
5528
5528
  //#endregion
@@ -5530,7 +5530,7 @@ function openingToSolid(spec, wallThickness) {
5530
5530
  var EPSILON_MM = 1;
5531
5531
  function slabOpeningToSolid(spec, slabThickness) {
5532
5532
  try {
5533
- var _usingCtx$12 = _usingCtx();
5533
+ var _usingCtx$13 = _usingCtx();
5534
5534
  if (spec.sizeX <= 0) return err(specError("SLAB_OPENING_ZERO_SIZE_X", "Slab opening sizeX must be positive"));
5535
5535
  if (spec.sizeY <= 0) return err(specError("SLAB_OPENING_ZERO_SIZE_Y", "Slab opening sizeY must be positive"));
5536
5536
  if (slabThickness <= 0) return err(specError("SLAB_OPENING_ZERO_SLAB_THICKNESS", "Slab thickness must be positive"));
@@ -5562,7 +5562,7 @@ function slabOpeningToSolid(spec, slabThickness) {
5562
5562
  ]
5563
5563
  ]);
5564
5564
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SLAB_OPENING_PROFILE_FAILED", "Failed to create slab opening profile"));
5565
- const solidResult = extrude(_usingCtx$12.u(profileResult.value), [
5565
+ const solidResult = extrude(_usingCtx$13.u(profileResult.value), [
5566
5566
  0,
5567
5567
  0,
5568
5568
  slabThickness + 2 * EPSILON_MM
@@ -5575,16 +5575,16 @@ function slabOpeningToSolid(spec, slabThickness) {
5575
5575
  }
5576
5576
  return ok(solid);
5577
5577
  } catch (_) {
5578
- _usingCtx$12.e = _;
5578
+ _usingCtx$13.e = _;
5579
5579
  } finally {
5580
- _usingCtx$12.d();
5580
+ _usingCtx$13.d();
5581
5581
  }
5582
5582
  }
5583
5583
  //#endregion
5584
5584
  //#region src/elementFns/spaceFns.ts
5585
5585
  function spaceToSolid(spec) {
5586
5586
  try {
5587
- var _usingCtx$11 = _usingCtx();
5587
+ var _usingCtx$12 = _usingCtx();
5588
5588
  if (spec.length <= 0) return err(specError("SPACE_ZERO_LENGTH", "Space length must be positive"));
5589
5589
  if (spec.width <= 0) return err(specError("SPACE_ZERO_WIDTH", "Space width must be positive"));
5590
5590
  if (spec.height <= 0) return err(specError("SPACE_ZERO_HEIGHT", "Space height must be positive"));
@@ -5612,7 +5612,7 @@ function spaceToSolid(spec) {
5612
5612
  ]
5613
5613
  ]);
5614
5614
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SPACE_PROFILE_FAILED", "Failed to create space footprint"));
5615
- const solidResult = extrude(_usingCtx$11.u(profileResult.value), [
5615
+ const solidResult = extrude(_usingCtx$12.u(profileResult.value), [
5616
5616
  0,
5617
5617
  0,
5618
5618
  height
@@ -5625,21 +5625,26 @@ function spaceToSolid(spec) {
5625
5625
  }
5626
5626
  return ok(solid);
5627
5627
  } catch (_) {
5628
- _usingCtx$11.e = _;
5628
+ _usingCtx$12.e = _;
5629
5629
  } finally {
5630
- _usingCtx$11.d();
5630
+ _usingCtx$12.d();
5631
5631
  }
5632
5632
  }
5633
5633
  //#endregion
5634
5634
  //#region src/elementFns/roofFns.ts
5635
- function roofToSolid(spec) {
5635
+ var DEG2RAD = Math.PI / 180;
5636
+ function gate(solid, code) {
5637
+ if (!isValidSolid(solid)) {
5638
+ solid[Symbol.dispose]();
5639
+ return err(geometryError(code, "Roof solid failed validity check"));
5640
+ }
5641
+ return ok(solid);
5642
+ }
5643
+ function flatRoof(spec) {
5636
5644
  try {
5637
- var _usingCtx$10 = _usingCtx();
5638
- if (spec.length <= 0) return err(specError("ROOF_ZERO_LENGTH", "Roof length must be positive"));
5639
- if (spec.width <= 0) return err(specError("ROOF_ZERO_WIDTH", "Roof width must be positive"));
5640
- if (spec.thickness <= 0) return err(specError("ROOF_ZERO_THICKNESS", "Roof thickness must be positive"));
5645
+ var _usingCtx$11 = _usingCtx();
5641
5646
  const { length, width, thickness } = spec;
5642
- const profileResult = polygon([
5647
+ const face = polygon([
5643
5648
  [
5644
5649
  0,
5645
5650
  0,
@@ -5661,30 +5666,212 @@ function roofToSolid(spec) {
5661
5666
  0
5662
5667
  ]
5663
5668
  ]);
5664
- if (!profileResult.ok) return err(fromBrepError(profileResult.error, "ROOF_PROFILE_FAILED", "Failed to create roof profile"));
5665
- const solidResult = extrude(_usingCtx$10.u(profileResult.value), [
5669
+ if (!face.ok) return err(fromBrepError(face.error, "ROOF_PROFILE_FAILED", "Failed to create roof profile"));
5670
+ const solid = extrude(_usingCtx$11.u(face.value), [
5666
5671
  0,
5667
5672
  0,
5668
5673
  thickness
5669
5674
  ]);
5670
- if (!solidResult.ok) return err(fromBrepError(solidResult.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude roof profile"));
5671
- const solid = solidResult.value;
5672
- if (!isValidSolid(solid)) {
5673
- solid[Symbol.dispose]();
5674
- return err(geometryError("ROOF_INVALID_SOLID", "Extruded roof solid failed validity check"));
5675
- }
5676
- return ok(solid);
5675
+ if (!solid.ok) return err(fromBrepError(solid.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude roof profile"));
5676
+ return gate(solid.value, "ROOF_INVALID_SOLID");
5677
5677
  } catch (_) {
5678
- _usingCtx$10.e = _;
5678
+ _usingCtx$11.e = _;
5679
5679
  } finally {
5680
- _usingCtx$10.d();
5680
+ _usingCtx$11.d();
5681
+ }
5682
+ }
5683
+ function shedRoof(spec, pitch) {
5684
+ try {
5685
+ var _usingCtx3 = _usingCtx();
5686
+ const { length, width, thickness } = spec;
5687
+ const rise = width * Math.tan(pitch * DEG2RAD);
5688
+ const face = polygon([
5689
+ [
5690
+ 0,
5691
+ 0,
5692
+ 0
5693
+ ],
5694
+ [
5695
+ 0,
5696
+ width,
5697
+ 0
5698
+ ],
5699
+ [
5700
+ 0,
5701
+ width,
5702
+ thickness + rise
5703
+ ],
5704
+ [
5705
+ 0,
5706
+ 0,
5707
+ thickness
5708
+ ]
5709
+ ]);
5710
+ if (!face.ok) return err(fromBrepError(face.error, "ROOF_PROFILE_FAILED", "Failed to create shed profile"));
5711
+ const solid = extrude(_usingCtx3.u(face.value), [
5712
+ length,
5713
+ 0,
5714
+ 0
5715
+ ]);
5716
+ if (!solid.ok) return err(fromBrepError(solid.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude shed roof"));
5717
+ return gate(solid.value, "ROOF_INVALID_SOLID");
5718
+ } catch (_) {
5719
+ _usingCtx3.e = _;
5720
+ } finally {
5721
+ _usingCtx3.d();
5722
+ }
5723
+ }
5724
+ function gableRoof(spec, pitch) {
5725
+ try {
5726
+ var _usingCtx4 = _usingCtx();
5727
+ const { length, width, thickness } = spec;
5728
+ const ridge = width / 2 * Math.tan(pitch * DEG2RAD);
5729
+ const face = polygon([
5730
+ [
5731
+ 0,
5732
+ 0,
5733
+ 0
5734
+ ],
5735
+ [
5736
+ 0,
5737
+ width,
5738
+ 0
5739
+ ],
5740
+ [
5741
+ 0,
5742
+ width,
5743
+ thickness
5744
+ ],
5745
+ [
5746
+ 0,
5747
+ width / 2,
5748
+ thickness + ridge
5749
+ ],
5750
+ [
5751
+ 0,
5752
+ 0,
5753
+ thickness
5754
+ ]
5755
+ ]);
5756
+ if (!face.ok) return err(fromBrepError(face.error, "ROOF_PROFILE_FAILED", "Failed to create gable profile"));
5757
+ const solid = extrude(_usingCtx4.u(face.value), [
5758
+ length,
5759
+ 0,
5760
+ 0
5761
+ ]);
5762
+ if (!solid.ok) return err(fromBrepError(solid.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude gable roof"));
5763
+ return gate(solid.value, "ROOF_INVALID_SOLID");
5764
+ } catch (_) {
5765
+ _usingCtx4.e = _;
5766
+ } finally {
5767
+ _usingCtx4.d();
5768
+ }
5769
+ }
5770
+ function hipRoof(spec, pitch) {
5771
+ const { length: l, width: w } = spec;
5772
+ const tan = Math.tan(pitch * DEG2RAD);
5773
+ const base = [
5774
+ [
5775
+ 0,
5776
+ 0,
5777
+ 0
5778
+ ],
5779
+ [
5780
+ l,
5781
+ 0,
5782
+ 0
5783
+ ],
5784
+ [
5785
+ l,
5786
+ w,
5787
+ 0
5788
+ ],
5789
+ [
5790
+ 0,
5791
+ w,
5792
+ 0
5793
+ ]
5794
+ ];
5795
+ let ridge;
5796
+ if (l >= w) {
5797
+ const h = w / 2 * tan;
5798
+ ridge = [[
5799
+ w / 2,
5800
+ w / 2,
5801
+ h
5802
+ ], [
5803
+ l - w / 2,
5804
+ w / 2,
5805
+ h
5806
+ ]];
5807
+ } else {
5808
+ const h = l / 2 * tan;
5809
+ ridge = [[
5810
+ l / 2,
5811
+ l / 2,
5812
+ h
5813
+ ], [
5814
+ l / 2,
5815
+ w - l / 2,
5816
+ h
5817
+ ]];
5818
+ }
5819
+ const solid = convexHull([...base, ...ridge]);
5820
+ if (!solid.ok) return err(fromBrepError(solid.error, "ROOF_HIP_FAILED", "Failed to build hip roof"));
5821
+ return gate(solid.value, "ROOF_INVALID_SOLID");
5822
+ }
5823
+ function domeRoof(spec) {
5824
+ const { length, width } = spec;
5825
+ const r = Math.min(length, width) / 2;
5826
+ const cx = length / 2;
5827
+ const cy = width / 2;
5828
+ const segments = 24;
5829
+ const rings = [
5830
+ 0,
5831
+ .4,
5832
+ .7,
5833
+ .9
5834
+ ];
5835
+ const pts = [];
5836
+ for (const h of rings) {
5837
+ const z = r * h;
5838
+ const ringR = r * Math.sqrt(1 - h * h);
5839
+ for (let i = 0; i < segments; i++) {
5840
+ const a = 2 * Math.PI * i / segments;
5841
+ pts.push([
5842
+ cx + ringR * Math.cos(a),
5843
+ cy + ringR * Math.sin(a),
5844
+ z
5845
+ ]);
5846
+ }
5847
+ }
5848
+ pts.push([
5849
+ cx,
5850
+ cy,
5851
+ r
5852
+ ]);
5853
+ const solid = convexHull(pts);
5854
+ if (!solid.ok) return err(fromBrepError(solid.error, "ROOF_DOME_FAILED", "Failed to build dome roof"));
5855
+ return gate(solid.value, "ROOF_INVALID_SOLID");
5856
+ }
5857
+ function roofToSolid(spec) {
5858
+ if (spec.length <= 0) return err(specError("ROOF_ZERO_LENGTH", "Roof length must be positive"));
5859
+ if (spec.width <= 0) return err(specError("ROOF_ZERO_WIDTH", "Roof width must be positive"));
5860
+ if (spec.thickness <= 0) return err(specError("ROOF_ZERO_THICKNESS", "Roof thickness must be positive"));
5861
+ if (spec.pitch === void 0) return flatRoof(spec);
5862
+ switch (spec.predefinedType) {
5863
+ case "SHED_ROOF": return shedRoof(spec, spec.pitch);
5864
+ case "GABLE_ROOF": return gableRoof(spec, spec.pitch);
5865
+ case "HIP_ROOF": return hipRoof(spec, spec.pitch);
5866
+ case "DOME_ROOF": return domeRoof(spec);
5867
+ default: return flatRoof(spec);
5681
5868
  }
5682
5869
  }
5683
5870
  //#endregion
5684
5871
  //#region src/elementFns/curtainWallFns.ts
5685
5872
  function boxSolid(sizeX, sizeY, sizeZ) {
5686
5873
  try {
5687
- var _usingCtx$9 = _usingCtx();
5874
+ var _usingCtx$10 = _usingCtx();
5688
5875
  const profileResult = polygon([
5689
5876
  [
5690
5877
  0,
@@ -5708,7 +5895,7 @@ function boxSolid(sizeX, sizeY, sizeZ) {
5708
5895
  ]
5709
5896
  ]);
5710
5897
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "CURTAIN_WALL_PROFILE_FAILED", "Failed to create component profile"));
5711
- const solidResult = extrude(_usingCtx$9.u(profileResult.value), [
5898
+ const solidResult = extrude(_usingCtx$10.u(profileResult.value), [
5712
5899
  0,
5713
5900
  0,
5714
5901
  sizeZ
@@ -5721,9 +5908,9 @@ function boxSolid(sizeX, sizeY, sizeZ) {
5721
5908
  }
5722
5909
  return ok(solid);
5723
5910
  } catch (_) {
5724
- _usingCtx$9.e = _;
5911
+ _usingCtx$10.e = _;
5725
5912
  } finally {
5726
- _usingCtx$9.d();
5913
+ _usingCtx$10.d();
5727
5914
  }
5728
5915
  }
5729
5916
  function disposeComponents(components) {
@@ -5822,7 +6009,7 @@ function curtainWallToGrid(spec) {
5822
6009
  //#region src/elementFns/foundationFns.ts
5823
6010
  function footingToSolid(spec) {
5824
6011
  try {
5825
- var _usingCtx$8 = _usingCtx();
6012
+ var _usingCtx$9 = _usingCtx();
5826
6013
  if (spec.length <= 0) return err(specError("FOOTING_ZERO_LENGTH", "Footing length must be positive"));
5827
6014
  if (spec.width <= 0) return err(specError("FOOTING_ZERO_WIDTH", "Footing width must be positive"));
5828
6015
  if (spec.thickness <= 0) return err(specError("FOOTING_ZERO_THICKNESS", "Footing thickness must be positive"));
@@ -5850,7 +6037,7 @@ function footingToSolid(spec) {
5850
6037
  ]
5851
6038
  ]);
5852
6039
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "FOOTING_PROFILE_FAILED", "Failed to create footing profile"));
5853
- const solidResult = extrude(_usingCtx$8.u(profileResult.value), [
6040
+ const solidResult = extrude(_usingCtx$9.u(profileResult.value), [
5854
6041
  0,
5855
6042
  0,
5856
6043
  thickness
@@ -5863,9 +6050,9 @@ function footingToSolid(spec) {
5863
6050
  }
5864
6051
  return ok(solid);
5865
6052
  } catch (_) {
5866
- _usingCtx$8.e = _;
6053
+ _usingCtx$9.e = _;
5867
6054
  } finally {
5868
- _usingCtx$8.d();
6055
+ _usingCtx$9.d();
5869
6056
  }
5870
6057
  }
5871
6058
  function pileToSolid(spec) {
@@ -5918,12 +6105,9 @@ function pileToSolid(spec) {
5918
6105
  }
5919
6106
  //#endregion
5920
6107
  //#region src/elementFns/railingFns.ts
5921
- function railingToSolid(spec) {
6108
+ function panelRailing(spec) {
5922
6109
  try {
5923
- var _usingCtx$7 = _usingCtx();
5924
- if (spec.length <= 0) return err(specError("RAILING_ZERO_LENGTH", "Railing length must be positive"));
5925
- if (spec.height <= 0) return err(specError("RAILING_ZERO_HEIGHT", "Railing height must be positive"));
5926
- if (spec.thickness <= 0) return err(specError("RAILING_ZERO_THICKNESS", "Railing thickness must be positive"));
6110
+ var _usingCtx$8 = _usingCtx();
5927
6111
  const { length, height, thickness } = spec;
5928
6112
  const profileResult = polygon([
5929
6113
  [
@@ -5948,7 +6132,7 @@ function railingToSolid(spec) {
5948
6132
  ]
5949
6133
  ]);
5950
6134
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "RAILING_PROFILE_FAILED", "Failed to create railing profile"));
5951
- const solidResult = extrude(_usingCtx$7.u(profileResult.value), [
6135
+ const solidResult = extrude(_usingCtx$8.u(profileResult.value), [
5952
6136
  length,
5953
6137
  0,
5954
6138
  0
@@ -5961,16 +6145,72 @@ function railingToSolid(spec) {
5961
6145
  }
5962
6146
  return ok(solid);
5963
6147
  } catch (_) {
5964
- _usingCtx$7.e = _;
6148
+ _usingCtx$8.e = _;
5965
6149
  } finally {
5966
- _usingCtx$7.d();
6150
+ _usingCtx$8.d();
6151
+ }
6152
+ }
6153
+ function postedRailing(spec) {
6154
+ const { length, height, thickness: t } = spec;
6155
+ const boxes = [];
6156
+ const postCount = Math.max(2, Math.round(length / 1e3) + 1);
6157
+ for (let i = 0; i < postCount; i++) {
6158
+ const x = t / 2 + (length - t) * (i / (postCount - 1));
6159
+ boxes.push(box(t, t, height, {
6160
+ at: [
6161
+ x,
6162
+ t / 2,
6163
+ height / 2
6164
+ ],
6165
+ centered: true
6166
+ }));
6167
+ }
6168
+ for (const z of [height - t / 2, t / 2]) boxes.push(box(length, t, t, {
6169
+ at: [
6170
+ length / 2,
6171
+ t / 2,
6172
+ z
6173
+ ],
6174
+ centered: true
6175
+ }));
6176
+ const scratch = [...boxes];
6177
+ let acc;
6178
+ let failure;
6179
+ for (const b of boxes) {
6180
+ if (acc === void 0) {
6181
+ acc = b;
6182
+ continue;
6183
+ }
6184
+ const fused = fuse(acc, b);
6185
+ if (!fused.ok) {
6186
+ failure = fromBrepError(fused.error, "RAILING_FUSE_FAILED", "Failed to fuse railing parts");
6187
+ break;
6188
+ }
6189
+ acc = fused.value;
6190
+ scratch.push(acc);
6191
+ }
6192
+ const survivor = failure ? void 0 : acc;
6193
+ for (const s of scratch) if (s !== survivor) s[Symbol.dispose]();
6194
+ if (failure) return err(failure);
6195
+ if (survivor === void 0) return err(geometryError("RAILING_INVALID_SOLID", "Posted railing produced no solid"));
6196
+ const result = survivor;
6197
+ if (!isValidSolid(result)) {
6198
+ result[Symbol.dispose]();
6199
+ return err(geometryError("RAILING_INVALID_SOLID", "Posted railing solid failed validity check"));
5967
6200
  }
6201
+ return ok(result);
6202
+ }
6203
+ function railingToSolid(spec) {
6204
+ if (spec.length <= 0) return err(specError("RAILING_ZERO_LENGTH", "Railing length must be positive"));
6205
+ if (spec.height <= 0) return err(specError("RAILING_ZERO_HEIGHT", "Railing height must be positive"));
6206
+ if (spec.thickness <= 0) return err(specError("RAILING_ZERO_THICKNESS", "Railing thickness must be positive"));
6207
+ return spec.infill === "POSTED" ? postedRailing(spec) : panelRailing(spec);
5968
6208
  }
5969
6209
  //#endregion
5970
6210
  //#region src/elementFns/coveringFns.ts
5971
6211
  function coveringToSolid(spec) {
5972
6212
  try {
5973
- var _usingCtx$6 = _usingCtx();
6213
+ var _usingCtx$7 = _usingCtx();
5974
6214
  if (spec.length <= 0) return err(specError("COVERING_ZERO_LENGTH", "Covering length must be positive"));
5975
6215
  if (spec.width <= 0) return err(specError("COVERING_ZERO_WIDTH", "Covering width must be positive"));
5976
6216
  if (spec.thickness <= 0) return err(specError("COVERING_ZERO_THICKNESS", "Covering thickness must be positive"));
@@ -5998,7 +6238,7 @@ function coveringToSolid(spec) {
5998
6238
  ]
5999
6239
  ]);
6000
6240
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "COVERING_PROFILE_FAILED", "Failed to create covering profile"));
6001
- const solidResult = extrude(_usingCtx$6.u(profileResult.value), [
6241
+ const solidResult = extrude(_usingCtx$7.u(profileResult.value), [
6002
6242
  0,
6003
6243
  0,
6004
6244
  thickness
@@ -6011,9 +6251,9 @@ function coveringToSolid(spec) {
6011
6251
  }
6012
6252
  return ok(solid);
6013
6253
  } catch (_) {
6014
- _usingCtx$6.e = _;
6254
+ _usingCtx$7.e = _;
6015
6255
  } finally {
6016
- _usingCtx$6.d();
6256
+ _usingCtx$7.d();
6017
6257
  }
6018
6258
  }
6019
6259
  //#endregion
@@ -6443,17 +6683,17 @@ var BimModel = class {
6443
6683
  }
6444
6684
  #cutWallGeometry(wall, openingSpec) {
6445
6685
  try {
6446
- var _usingCtx$5 = _usingCtx();
6686
+ var _usingCtx$6 = _usingCtx();
6447
6687
  const toolResult = openingToSolid(openingSpec, wall.spec.thickness);
6448
6688
  if (!toolResult.ok) return err(toolResult.error);
6449
- const tool = _usingCtx$5.u(toolResult.value);
6689
+ const tool = _usingCtx$6.u(toolResult.value);
6450
6690
  const cutResult = cut(wall.geometry, tool);
6451
6691
  if (!cutResult.ok) return err(fromBrepError(cutResult.error, "WALL_CUT_FAILED", "Boolean cut of wall with opening failed"));
6452
6692
  return ok(cutResult.value);
6453
6693
  } catch (_) {
6454
- _usingCtx$5.e = _;
6694
+ _usingCtx$6.e = _;
6455
6695
  } finally {
6456
- _usingCtx$5.d();
6696
+ _usingCtx$6.d();
6457
6697
  }
6458
6698
  }
6459
6699
  #replaceWallGeometry(wall, newGeometry) {
@@ -6703,41 +6943,388 @@ var BimModel = class {
6703
6943
  }
6704
6944
  };
6705
6945
  //#endregion
6706
- //#region src/ifc-writer/schemaVersion.ts
6707
- /**
6708
- * IFC schema-version abstraction for the writer.
6709
- *
6710
- * The writer targets a single IFC schema per model. This module is the single
6711
- * source of truth for which schemas are supported, the FILE_SCHEMA token that
6712
- * goes into the STEP header (and into web-ifc's `CreateModel({ schema })`), and
6713
- * a guard helper for entities that exist only in a given schema.
6714
- *
6715
- * Selection is wired in by the writer integrator via `BimModelMeta.ifcSchema`;
6716
- * the default is {@link DEFAULT_IFC_SCHEMA}.
6717
- */
6718
- /** Writer-supported IFC schemas, in declared order. */
6719
- var IFC_SCHEMAS = ["IFC4", "IFC4X3"];
6720
- /** Schema used when none is specified in model meta. */
6721
- var DEFAULT_IFC_SCHEMA = "IFC4";
6946
+ //#region src/import/placement.ts
6722
6947
  /**
6723
- * The FILE_SCHEMA token for the STEP header and `CreateModel({ schema })`.
6724
- *
6725
- * web-ifc identifies schemas by these exact strings, and the STEP serializer
6726
- * emits `FILE_SCHEMA(('<token>'));`. For the supported set the token equals the
6727
- * schema identifier itself, but callers should route through this function so a
6728
- * future schema whose header token diverges from its identifier stays correct.
6948
+ * Metres-per-file-unit length scale read from the IfcUnitAssignment's
6949
+ * LENGTHUNIT. Multiply a file-unit length by this to get metres, then by 1000
6950
+ * for brepjs millimetres. Returns 1.0 (assume metres) when no length unit is
6951
+ * declared.
6729
6952
  */
6730
- function fileSchemaString(schema) {
6731
- return schema;
6732
- }
6733
- /** Type guard narrowing an unknown value to a supported {@link IfcSchema}. */
6734
- function isIfcSchema(value) {
6735
- return typeof value === "string" && IFC_SCHEMAS.includes(value);
6953
+ function readLengthScale(reader) {
6954
+ const assignments = reader.getLinesOfType(WebIFC.IFCUNITASSIGNMENT);
6955
+ for (const assignmentId of assignments) {
6956
+ const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
6957
+ for (const unitId of units) {
6958
+ const scale = lengthScaleFromUnit(reader, unitId);
6959
+ if (scale !== null) return scale;
6960
+ }
6961
+ }
6962
+ return 1;
6736
6963
  }
6737
6964
  /**
6738
- * Entity names that are valid only in IFC4X3 (a non-exhaustive, additive set of
6739
- * linear-infrastructure entities). Entities absent from every per-schema set are
6740
- * treated as schema-agnostic and supported everywhere (open-world default).
6965
+ * Radians-per-file-unit plane-angle scale read from the IfcUnitAssignment's
6966
+ * PLANEANGLEUNIT (1 for RADIAN, ~0.0174533 for DEGREE). Defaults to 1 (the IFC
6967
+ * default plane-angle unit is the radian).
6968
+ */
6969
+ function readPlaneAngleScale(reader) {
6970
+ for (const assignmentId of reader.getLinesOfType(WebIFC.IFCUNITASSIGNMENT)) {
6971
+ const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
6972
+ for (const unitId of units) {
6973
+ const s = planeAngleScaleFromUnit(reader, unitId);
6974
+ if (s !== null) return s;
6975
+ }
6976
+ }
6977
+ return 1;
6978
+ }
6979
+ function planeAngleScaleFromUnit(reader, unitId) {
6980
+ const unit = reader.getLine(unitId);
6981
+ if (unit === null) return null;
6982
+ const type = reader.getLineType(unitId);
6983
+ if (type === WebIFC.IFCSIUNIT) {
6984
+ if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
6985
+ if (enumValue(unit["Name"]) !== "RADIAN") return null;
6986
+ return 1;
6987
+ }
6988
+ if (type === WebIFC.IFCCONVERSIONBASEDUNIT) {
6989
+ if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
6990
+ const measureId = refValue$2(unit["ConversionFactor"]);
6991
+ if (measureId === null) return null;
6992
+ const factor = numericValue(reader.getLine(measureId)?.["ValueComponent"]);
6993
+ if (factor === null) return null;
6994
+ return factor;
6995
+ }
6996
+ return null;
6997
+ }
6998
+ function lengthScaleFromUnit(reader, unitId) {
6999
+ const unit = reader.getLine(unitId);
7000
+ if (unit === null) return null;
7001
+ const type = reader.getLineType(unitId);
7002
+ if (type === WebIFC.IFCSIUNIT) {
7003
+ if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
7004
+ if (enumValue(unit["Name"]) !== "METRE") return null;
7005
+ return siPrefixFactor(enumValue(unit["Prefix"]));
7006
+ }
7007
+ if (type === WebIFC.IFCCONVERSIONBASEDUNIT) {
7008
+ if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
7009
+ const measureId = refValue$2(unit["ConversionFactor"]);
7010
+ if (measureId === null) return null;
7011
+ const measure = reader.getLine(measureId);
7012
+ const factor = numericValue(measure?.["ValueComponent"]);
7013
+ if (factor === null) return null;
7014
+ const baseId = refValue$2(measure?.["UnitComponent"]);
7015
+ return factor * (baseId !== null ? lengthScaleFromUnit(reader, baseId) ?? 1 : 1);
7016
+ }
7017
+ return null;
7018
+ }
7019
+ function siPrefixFactor(prefix) {
7020
+ switch (prefix) {
7021
+ case null: return 1;
7022
+ case "KILO": return 1e3;
7023
+ case "HECTO": return 100;
7024
+ case "DECA": return 10;
7025
+ case "DECI": return .1;
7026
+ case "CENTI": return .01;
7027
+ case "MILLI": return .001;
7028
+ case "MICRO": return 1e-6;
7029
+ default: return 1;
7030
+ }
7031
+ }
7032
+ /**
7033
+ * Builds a row-major MatrixTransform (for brepjs `applyMatrix`) from an
7034
+ * (origin, axisX, axisZ) frame, using the SAME IFC orthonormalization as
7035
+ * {@link readAxis2Placement3D}: z = normalize(axisZ); x = normalize(axisX
7036
+ * projected onto the plane ⊥ z); y = z × x. The basis vectors are the matrix
7037
+ * columns, so the row-major linear array is [Xx,Yx,Zx, Xy,Yy,Zy, Xz,Yz,Zz];
7038
+ * translation = origin (mm). This is the display-side counterpart to the IFC
7039
+ * writer's Axis2Placement3D (Axis=Z, RefDirection=X) so on-screen placement and
7040
+ * the IFC export agree.
7041
+ */
7042
+ function placementToMatrix(f) {
7043
+ const z = normalize$1(f.axisZ);
7044
+ const dot = z[0] * f.axisX[0] + z[1] * f.axisX[1] + z[2] * f.axisX[2];
7045
+ const projX = [
7046
+ f.axisX[0] - dot * z[0],
7047
+ f.axisX[1] - dot * z[1],
7048
+ f.axisX[2] - dot * z[2]
7049
+ ];
7050
+ const x = lengthSq(projX) < 1e-12 ? normalize$1(orthogonal$2(z)) : normalize$1(projX);
7051
+ const y = cross$1(z, x);
7052
+ return {
7053
+ linear: [
7054
+ x[0],
7055
+ y[0],
7056
+ z[0],
7057
+ x[1],
7058
+ y[1],
7059
+ z[1],
7060
+ x[2],
7061
+ y[2],
7062
+ z[2]
7063
+ ],
7064
+ translation: [
7065
+ f.origin[0],
7066
+ f.origin[1],
7067
+ f.origin[2]
7068
+ ]
7069
+ };
7070
+ }
7071
+ function cross$1(a, b) {
7072
+ return [
7073
+ a[1] * b[2] - a[2] * b[1],
7074
+ a[2] * b[0] - a[0] * b[2],
7075
+ a[0] * b[1] - a[1] * b[0]
7076
+ ];
7077
+ }
7078
+ function lengthSq(v) {
7079
+ return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
7080
+ }
7081
+ function normalize$1(v) {
7082
+ const len = Math.sqrt(lengthSq(v));
7083
+ if (len < 1e-12) return [
7084
+ 0,
7085
+ 0,
7086
+ 1
7087
+ ];
7088
+ return [
7089
+ v[0] / len,
7090
+ v[1] / len,
7091
+ v[2] / len
7092
+ ];
7093
+ }
7094
+ function orthogonal$2(v) {
7095
+ return Math.abs(v[0]) < .9 ? cross$1(v, [
7096
+ 1,
7097
+ 0,
7098
+ 0
7099
+ ]) : cross$1(v, [
7100
+ 0,
7101
+ 1,
7102
+ 0
7103
+ ]);
7104
+ }
7105
+ function refValue$2(v) {
7106
+ if (v === null || v === void 0) return null;
7107
+ if (typeof v === "number") return v;
7108
+ const value = v.value;
7109
+ return typeof value === "number" ? value : null;
7110
+ }
7111
+ function asRefArray$1(v) {
7112
+ if (!Array.isArray(v)) return [];
7113
+ const out = [];
7114
+ for (const item of v) {
7115
+ const id = refValue$2(item);
7116
+ if (id !== null) out.push(id);
7117
+ }
7118
+ return out;
7119
+ }
7120
+ function numericValue(v) {
7121
+ if (typeof v === "number") return v;
7122
+ if (v === null || v === void 0) return null;
7123
+ const value = v.value;
7124
+ return typeof value === "number" ? value : null;
7125
+ }
7126
+ function enumValue(v) {
7127
+ if (typeof v === "string") return v;
7128
+ if (v === null || v === void 0) return null;
7129
+ const value = v.value;
7130
+ return typeof value === "string" ? value : null;
7131
+ }
7132
+ //#endregion
7133
+ //#region src/elementFns/stairFns.ts
7134
+ function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
7135
+ const pts = [];
7136
+ pts.push([
7137
+ 0,
7138
+ 0,
7139
+ 0
7140
+ ]);
7141
+ let x = 0;
7142
+ let z = 0;
7143
+ for (let i = 0; i < numberOfRisers; i++) {
7144
+ z += riserHeight;
7145
+ pts.push([
7146
+ x,
7147
+ 0,
7148
+ z
7149
+ ]);
7150
+ x += treadLength;
7151
+ pts.push([
7152
+ x,
7153
+ 0,
7154
+ z
7155
+ ]);
7156
+ }
7157
+ pts.push([
7158
+ x,
7159
+ 0,
7160
+ 0
7161
+ ]);
7162
+ return pts;
7163
+ }
7164
+ function stairFlightToSolid(spec) {
7165
+ try {
7166
+ var _usingCtx$5 = _usingCtx();
7167
+ if (spec.width <= 0) return err(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
7168
+ if (spec.riserHeight <= 0) return err(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
7169
+ if (spec.treadLength <= 0) return err(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
7170
+ if (!Number.isInteger(spec.numberOfRisers) || spec.numberOfRisers < 1) return err(specError("STAIR_FLIGHT_BAD_RISERS", "Stair flight numberOfRisers must be a positive integer"));
7171
+ const profileResult = polygon(buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength));
7172
+ if (!profileResult.ok) return err(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
7173
+ const solidResult = extrude(_usingCtx$5.u(profileResult.value), [
7174
+ 0,
7175
+ spec.width,
7176
+ 0
7177
+ ]);
7178
+ if (!solidResult.ok) return err(fromBrepError(solidResult.error, "STAIR_FLIGHT_EXTRUDE_FAILED", "Failed to extrude stair flight silhouette"));
7179
+ const solid = solidResult.value;
7180
+ if (!isValidSolid(solid)) {
7181
+ solid[Symbol.dispose]();
7182
+ return err(geometryError("STAIR_FLIGHT_INVALID_SOLID", "Stair flight solid failed validity check"));
7183
+ }
7184
+ return ok({
7185
+ solid,
7186
+ geometrySimplified: false
7187
+ });
7188
+ } catch (_) {
7189
+ _usingCtx$5.e = _;
7190
+ } finally {
7191
+ _usingCtx$5.d();
7192
+ }
7193
+ }
7194
+ //#endregion
7195
+ //#region src/elementFns/placedGeometry.ts
7196
+ function place(solid, frame) {
7197
+ const result = applyMatrix(solid, placementToMatrix(frame));
7198
+ if (!result.ok) return err(fromBrepError(result.error, "PLACED_GEOMETRY_FAILED", "Failed to place element geometry"));
7199
+ return ok(result.value);
7200
+ }
7201
+ function disposeAll(solids) {
7202
+ for (const s of solids) s[Symbol.dispose]();
7203
+ }
7204
+ /**
7205
+ * Returns each element's geometry transformed to its world placement, as fresh
7206
+ * caller-owned solids, wrapped in a `Result` (Layer-2 code prefers `Result` over
7207
+ * throwing). **Dispose the returned solids** (e.g. via `using` / `[Symbol.dispose]`)
7208
+ * when you own their lifetime — they are independent of the model
7209
+ * (`BimModel[Symbol.dispose]` frees only the stored, unplaced `.geometry`). On any
7210
+ * failure the solids already built for this call are disposed before the error is
7211
+ * returned, so no partial array is leaked.
7212
+ *
7213
+ * Stairs carry no element solid (`.geometry` is null), so flight solids are built
7214
+ * from `spec.flights` and placed per flight. Curtain walls return placed panels +
7215
+ * mullions. Elements with no solid geometry (doors/windows/ramps/groups/spatial)
7216
+ * return an empty array.
7217
+ */
7218
+ function placedSolids(el) {
7219
+ switch (el.category) {
7220
+ case "WALL":
7221
+ case "SLAB":
7222
+ case "BEAM":
7223
+ case "COLUMN":
7224
+ case "SPACE":
7225
+ case "ROOF":
7226
+ case "FOOTING":
7227
+ case "PILE":
7228
+ case "RAILING": {
7229
+ const placed = place(el.geometry, el.spec);
7230
+ if (!placed.ok) return placed;
7231
+ return ok([placed.value]);
7232
+ }
7233
+ case "STAIR": {
7234
+ const out = [];
7235
+ for (const flight of el.spec.flights) try {
7236
+ var _usingCtx$4 = _usingCtx();
7237
+ const built = stairFlightToSolid(flight);
7238
+ if (!built.ok) {
7239
+ disposeAll(out);
7240
+ return err(built.error);
7241
+ }
7242
+ const placed = place(_usingCtx$4.u(built.value.solid), flight);
7243
+ if (!placed.ok) {
7244
+ disposeAll(out);
7245
+ return placed;
7246
+ }
7247
+ out.push(placed.value);
7248
+ } catch (_) {
7249
+ _usingCtx$4.e = _;
7250
+ } finally {
7251
+ _usingCtx$4.d();
7252
+ }
7253
+ return ok(out);
7254
+ }
7255
+ case "CURTAIN_WALL": {
7256
+ const out = [];
7257
+ for (const c of [...el.geometry.panels, ...el.geometry.mullions]) try {
7258
+ var _usingCtx3 = _usingCtx();
7259
+ const componentLocal = place(c.solid, {
7260
+ origin: c.origin,
7261
+ axisX: [
7262
+ 1,
7263
+ 0,
7264
+ 0
7265
+ ],
7266
+ axisZ: [
7267
+ 0,
7268
+ 0,
7269
+ 1
7270
+ ]
7271
+ });
7272
+ if (!componentLocal.ok) {
7273
+ disposeAll(out);
7274
+ return componentLocal;
7275
+ }
7276
+ const placed = place(_usingCtx3.u(componentLocal.value), el.spec);
7277
+ if (!placed.ok) {
7278
+ disposeAll(out);
7279
+ return placed;
7280
+ }
7281
+ out.push(placed.value);
7282
+ } catch (_) {
7283
+ _usingCtx3.e = _;
7284
+ } finally {
7285
+ _usingCtx3.d();
7286
+ }
7287
+ return ok(out);
7288
+ }
7289
+ default: return ok([]);
7290
+ }
7291
+ }
7292
+ //#endregion
7293
+ //#region src/ifc-writer/schemaVersion.ts
7294
+ /**
7295
+ * IFC schema-version abstraction for the writer.
7296
+ *
7297
+ * The writer targets a single IFC schema per model. This module is the single
7298
+ * source of truth for which schemas are supported, the FILE_SCHEMA token that
7299
+ * goes into the STEP header (and into web-ifc's `CreateModel({ schema })`), and
7300
+ * a guard helper for entities that exist only in a given schema.
7301
+ *
7302
+ * Selection is wired in by the writer integrator via `BimModelMeta.ifcSchema`;
7303
+ * the default is {@link DEFAULT_IFC_SCHEMA}.
7304
+ */
7305
+ /** Writer-supported IFC schemas, in declared order. */
7306
+ var IFC_SCHEMAS = ["IFC4", "IFC4X3"];
7307
+ /** Schema used when none is specified in model meta. */
7308
+ var DEFAULT_IFC_SCHEMA = "IFC4";
7309
+ /**
7310
+ * The FILE_SCHEMA token for the STEP header and `CreateModel({ schema })`.
7311
+ *
7312
+ * web-ifc identifies schemas by these exact strings, and the STEP serializer
7313
+ * emits `FILE_SCHEMA(('<token>'));`. For the supported set the token equals the
7314
+ * schema identifier itself, but callers should route through this function so a
7315
+ * future schema whose header token diverges from its identifier stays correct.
7316
+ */
7317
+ function fileSchemaString(schema) {
7318
+ return schema;
7319
+ }
7320
+ /** Type guard narrowing an unknown value to a supported {@link IfcSchema}. */
7321
+ function isIfcSchema(value) {
7322
+ return typeof value === "string" && IFC_SCHEMAS.includes(value);
7323
+ }
7324
+ /**
7325
+ * Entity names that are valid only in IFC4X3 (a non-exhaustive, additive set of
7326
+ * linear-infrastructure entities). Entities absent from every per-schema set are
7327
+ * treated as schema-agnostic and supported everywhere (open-world default).
6741
7328
  */
6742
7329
  var IFC4X3_ONLY_ENTITIES = new Set([
6743
7330
  "IfcAlignment",
@@ -7972,7 +8559,7 @@ function writeSlabGeometry(w, spec, geomSubContextId, parentPlacementId) {
7972
8559
  productDefinitionShapeId
7973
8560
  };
7974
8561
  }
7975
- function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
8562
+ function writeRoofGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
7976
8563
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
7977
8564
  const localPlacementId = w.nextId();
7978
8565
  w.writeLine({
@@ -7981,6 +8568,14 @@ function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
7981
8568
  PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
7982
8569
  RelativePlacement: w.ref(placement3DId)
7983
8570
  });
8571
+ if (spec.pitch !== void 0) {
8572
+ const tess = writeTessellation(w, solid, geomSubContextId, localPlacementId);
8573
+ return {
8574
+ localPlacementId,
8575
+ productDefinitionShapeId: tess.productDefinitionShapeId,
8576
+ usedFallback: tess.usedFallback
8577
+ };
8578
+ }
7984
8579
  const lengthM = toIfcLengthM(spec.length);
7985
8580
  const widthM = toIfcLengthM(spec.width);
7986
8581
  const thicknessM = toIfcLengthM(spec.thickness);
@@ -8048,7 +8643,8 @@ function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
8048
8643
  });
8049
8644
  return {
8050
8645
  localPlacementId,
8051
- productDefinitionShapeId
8646
+ productDefinitionShapeId,
8647
+ usedFallback: false
8052
8648
  };
8053
8649
  }
8054
8650
  function writeAxis2Placement2D$1(w) {
@@ -8751,68 +9347,6 @@ function writeRelContainedInSpatialStructure(w, guid, ownerHistoryId, relatingSt
8751
9347
  });
8752
9348
  }
8753
9349
  //#endregion
8754
- //#region src/elementFns/stairFns.ts
8755
- function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
8756
- const pts = [];
8757
- pts.push([
8758
- 0,
8759
- 0,
8760
- 0
8761
- ]);
8762
- let x = 0;
8763
- let z = 0;
8764
- for (let i = 0; i < numberOfRisers; i++) {
8765
- z += riserHeight;
8766
- pts.push([
8767
- x,
8768
- 0,
8769
- z
8770
- ]);
8771
- x += treadLength;
8772
- pts.push([
8773
- x,
8774
- 0,
8775
- z
8776
- ]);
8777
- }
8778
- pts.push([
8779
- x,
8780
- 0,
8781
- 0
8782
- ]);
8783
- return pts;
8784
- }
8785
- function stairFlightToSolid(spec) {
8786
- try {
8787
- var _usingCtx$4 = _usingCtx();
8788
- if (spec.width <= 0) return err(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
8789
- if (spec.riserHeight <= 0) return err(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
8790
- if (spec.treadLength <= 0) return err(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
8791
- if (!Number.isInteger(spec.numberOfRisers) || spec.numberOfRisers < 1) return err(specError("STAIR_FLIGHT_BAD_RISERS", "Stair flight numberOfRisers must be a positive integer"));
8792
- const profileResult = polygon(buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength));
8793
- if (!profileResult.ok) return err(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
8794
- const solidResult = extrude(_usingCtx$4.u(profileResult.value), [
8795
- 0,
8796
- spec.width,
8797
- 0
8798
- ]);
8799
- if (!solidResult.ok) return err(fromBrepError(solidResult.error, "STAIR_FLIGHT_EXTRUDE_FAILED", "Failed to extrude stair flight silhouette"));
8800
- const solid = solidResult.value;
8801
- if (!isValidSolid(solid)) {
8802
- solid[Symbol.dispose]();
8803
- return err(geometryError("STAIR_FLIGHT_INVALID_SOLID", "Stair flight solid failed validity check"));
8804
- }
8805
- return ok({
8806
- solid,
8807
- geometrySimplified: false
8808
- });
8809
- } catch (_) {
8810
- _usingCtx$4.e = _;
8811
- } finally {
8812
- _usingCtx$4.d();
8813
- }
8814
- }
8815
- //#endregion
8816
9350
  //#region src/elementFns/rampFns.ts
8817
9351
  function buildSilhouette(length, rise, thickness) {
8818
9352
  return [
@@ -9123,7 +9657,7 @@ function writeRampAssembly(w, spec, rampKey, ownerHistoryId, geomSubContextId, p
9123
9657
  }
9124
9658
  //#endregion
9125
9659
  //#region src/ifc-writer/railingWriter.ts
9126
- function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9660
+ function writeRailingGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
9127
9661
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
9128
9662
  const localPlacementId = w.nextId();
9129
9663
  w.writeLine({
@@ -9132,6 +9666,15 @@ function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9132
9666
  PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
9133
9667
  RelativePlacement: w.ref(placement3DId)
9134
9668
  });
9669
+ if (spec.infill === "POSTED") {
9670
+ const tess = writeTessellation(w, solid, geomSubContextId, localPlacementId);
9671
+ return {
9672
+ localPlacementId,
9673
+ productDefinitionShapeId: tess.productDefinitionShapeId,
9674
+ bodyItemId: null,
9675
+ usedFallback: tess.usedFallback
9676
+ };
9677
+ }
9135
9678
  const thicknessM = toIfcLengthM(spec.thickness);
9136
9679
  const heightM = toIfcLengthM(spec.height);
9137
9680
  const lengthM = toIfcLengthM(spec.length);
@@ -9208,7 +9751,8 @@ function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9208
9751
  return {
9209
9752
  localPlacementId,
9210
9753
  productDefinitionShapeId,
9211
- bodyItemId: extrusionId
9754
+ bodyItemId: extrusionId,
9755
+ usedFallback: false
9212
9756
  };
9213
9757
  }
9214
9758
  function writeRailingEntity(w, guid, name, predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
@@ -11579,7 +12123,8 @@ async function toIfc(model, meta) {
11579
12123
  for (const [i, roof] of roofs.entries()) {
11580
12124
  const containingId = findContainerOf(roof.localId, relationships);
11581
12125
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
11582
- const { localPlacementId, productDefinitionShapeId } = writeRoofGeometry(w, roof.spec, geomSubContextId, storeyPlacementId);
12126
+ const { localPlacementId, productDefinitionShapeId, usedFallback } = writeRoofGeometry(w, roof.spec, roof.geometry, geomSubContextId, storeyPlacementId);
12127
+ if (usedFallback) console.warn(`Roof ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
11583
12128
  const roofExpressId = writeRoofEntity(w, roof.guid, `Roof ${i + 1}`, roof.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
11584
12129
  idMap.set(roof.localId, roofExpressId);
11585
12130
  placementMap.set(roof.localId, localPlacementId);
@@ -11651,14 +12196,15 @@ async function toIfc(model, meta) {
11651
12196
  for (const [i, railing] of railings.entries()) {
11652
12197
  const containingId = findContainerOf(railing.localId, relationships);
11653
12198
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
11654
- const { localPlacementId, productDefinitionShapeId, bodyItemId } = writeRailingGeometry(w, railing.spec, geomSubContextId, storeyPlacementId);
12199
+ const { localPlacementId, productDefinitionShapeId, bodyItemId, usedFallback } = writeRailingGeometry(w, railing.spec, railing.geometry, geomSubContextId, storeyPlacementId);
12200
+ if (usedFallback) console.warn(`Railing ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
11655
12201
  const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
11656
12202
  idMap.set(railing.localId, railingExpressId);
11657
12203
  placementMap.set(railing.localId, localPlacementId);
11658
12204
  writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
11659
12205
  writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
11660
12206
  if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
11661
- applySurfaceStyle(w, model, railing.localId, bodyItemId);
12207
+ if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
11662
12208
  }
11663
12209
  for (const [i, covering] of coverings.entries()) {
11664
12210
  const containingId = findContainerOf(covering.localId, relationships);
@@ -12186,120 +12732,6 @@ var SpfReader = class SpfReader {
12186
12732
  }
12187
12733
  };
12188
12734
  //#endregion
12189
- //#region src/import/placement.ts
12190
- /**
12191
- * Metres-per-file-unit length scale read from the IfcUnitAssignment's
12192
- * LENGTHUNIT. Multiply a file-unit length by this to get metres, then by 1000
12193
- * for brepjs millimetres. Returns 1.0 (assume metres) when no length unit is
12194
- * declared.
12195
- */
12196
- function readLengthScale(reader) {
12197
- const assignments = reader.getLinesOfType(WebIFC.IFCUNITASSIGNMENT);
12198
- for (const assignmentId of assignments) {
12199
- const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
12200
- for (const unitId of units) {
12201
- const scale = lengthScaleFromUnit(reader, unitId);
12202
- if (scale !== null) return scale;
12203
- }
12204
- }
12205
- return 1;
12206
- }
12207
- /**
12208
- * Radians-per-file-unit plane-angle scale read from the IfcUnitAssignment's
12209
- * PLANEANGLEUNIT (1 for RADIAN, ~0.0174533 for DEGREE). Defaults to 1 (the IFC
12210
- * default plane-angle unit is the radian).
12211
- */
12212
- function readPlaneAngleScale(reader) {
12213
- for (const assignmentId of reader.getLinesOfType(WebIFC.IFCUNITASSIGNMENT)) {
12214
- const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
12215
- for (const unitId of units) {
12216
- const s = planeAngleScaleFromUnit(reader, unitId);
12217
- if (s !== null) return s;
12218
- }
12219
- }
12220
- return 1;
12221
- }
12222
- function planeAngleScaleFromUnit(reader, unitId) {
12223
- const unit = reader.getLine(unitId);
12224
- if (unit === null) return null;
12225
- const type = reader.getLineType(unitId);
12226
- if (type === WebIFC.IFCSIUNIT) {
12227
- if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
12228
- if (enumValue(unit["Name"]) !== "RADIAN") return null;
12229
- return 1;
12230
- }
12231
- if (type === WebIFC.IFCCONVERSIONBASEDUNIT) {
12232
- if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
12233
- const measureId = refValue$2(unit["ConversionFactor"]);
12234
- if (measureId === null) return null;
12235
- const factor = numericValue(reader.getLine(measureId)?.["ValueComponent"]);
12236
- if (factor === null) return null;
12237
- return factor;
12238
- }
12239
- return null;
12240
- }
12241
- function lengthScaleFromUnit(reader, unitId) {
12242
- const unit = reader.getLine(unitId);
12243
- if (unit === null) return null;
12244
- const type = reader.getLineType(unitId);
12245
- if (type === WebIFC.IFCSIUNIT) {
12246
- if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
12247
- if (enumValue(unit["Name"]) !== "METRE") return null;
12248
- return siPrefixFactor(enumValue(unit["Prefix"]));
12249
- }
12250
- if (type === WebIFC.IFCCONVERSIONBASEDUNIT) {
12251
- if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
12252
- const measureId = refValue$2(unit["ConversionFactor"]);
12253
- if (measureId === null) return null;
12254
- const measure = reader.getLine(measureId);
12255
- const factor = numericValue(measure?.["ValueComponent"]);
12256
- if (factor === null) return null;
12257
- const baseId = refValue$2(measure?.["UnitComponent"]);
12258
- return factor * (baseId !== null ? lengthScaleFromUnit(reader, baseId) ?? 1 : 1);
12259
- }
12260
- return null;
12261
- }
12262
- function siPrefixFactor(prefix) {
12263
- switch (prefix) {
12264
- case null: return 1;
12265
- case "KILO": return 1e3;
12266
- case "HECTO": return 100;
12267
- case "DECA": return 10;
12268
- case "DECI": return .1;
12269
- case "CENTI": return .01;
12270
- case "MILLI": return .001;
12271
- case "MICRO": return 1e-6;
12272
- default: return 1;
12273
- }
12274
- }
12275
- function refValue$2(v) {
12276
- if (v === null || v === void 0) return null;
12277
- if (typeof v === "number") return v;
12278
- const value = v.value;
12279
- return typeof value === "number" ? value : null;
12280
- }
12281
- function asRefArray$1(v) {
12282
- if (!Array.isArray(v)) return [];
12283
- const out = [];
12284
- for (const item of v) {
12285
- const id = refValue$2(item);
12286
- if (id !== null) out.push(id);
12287
- }
12288
- return out;
12289
- }
12290
- function numericValue(v) {
12291
- if (typeof v === "number") return v;
12292
- if (v === null || v === void 0) return null;
12293
- const value = v.value;
12294
- return typeof value === "number" ? value : null;
12295
- }
12296
- function enumValue(v) {
12297
- if (typeof v === "string") return v;
12298
- if (v === null || v === void 0) return null;
12299
- const value = v.value;
12300
- return typeof value === "string" ? value : null;
12301
- }
12302
- //#endregion
12303
12735
  //#region src/import/spatialTree.ts
12304
12736
  var CATEGORY_BY_TYPE = new Map([
12305
12737
  [WebIFC.IFCPROJECT, "PROJECT"],
@@ -13925,6 +14357,7 @@ var RoofSpecSchema = object({
13925
14357
  fireRating: string().optional(),
13926
14358
  thermalTransmittance: number().positive().optional(),
13927
14359
  status: string().optional(),
14360
+ pitch: number().positive().max(89).optional(),
13928
14361
  materialLayers: array(MaterialLayerSchema).optional(),
13929
14362
  layerSetName: string().optional(),
13930
14363
  classification: ClassificationRefSchema.optional(),
@@ -14256,6 +14689,7 @@ var RailingSpecSchema = object({
14256
14689
  "HANDRAIL",
14257
14690
  "NOTDEFINED"
14258
14691
  ]).optional(),
14692
+ infill: _enum(["PANEL", "POSTED"]).optional(),
14259
14693
  materialName: string().min(1),
14260
14694
  isExternal: boolean().optional(),
14261
14695
  fireRating: string().optional(),
@@ -15908,4 +16342,4 @@ function assignNum(target, key, value) {
15908
16342
  if (value !== void 0) target[key] = Number(value);
15909
16343
  }
15910
16344
  //#endregion
15911
- export { BimModel, DEFAULT_IFC_SCHEMA, DEFAULT_UNITS, IFC_SCHEMAS, PSET_PROPERTY_TYPE_TABLE, PSET_TEMPLATES, SpfReader, bcfError, checkGeometryValidity, checkModelAgainstIds as checkIds, checkModelAgainstIds, checkReferentialIntegrity, checkRoundTrip, checkSchema, countBySeverity, deriveCobieModel, deriveCobieModel as exportCobie, deriveIfcGuid, deriveIfcGuidSync, disposeImportedModel, emptyReport, extendedProfileArea, extendedProfileToFace, fileSchemaString, fromBrepError, fromIfc, geometryError, hasErrors, idsError, ifcError, importError, isExtendedProfile, isIfcSchema, isSlabOpening, isValidIfcGuid, isWallOpening, issue, makeLocalIdCounter, measureTypeFor, newIfcGuid, parseBcfFiles, parseBeamSpec, parseColumnSpec, parseCoveringSpec, parseCurtainWallSpec, parseDoorSpec, parseElementAssemblySpec, parseFootingSpec, parseIdsXml, parsePileSpec, parseProfile, parseRailingSpec, parseRampFlightSpec, parseRampSpec, parseRoofSpec, parseSlabOpeningInput, parseSlabSpec, parseSpaceSpec, parseStairFlightSpec, parseStairSpec, parseSurfaceStyleSpec, parseSystemSpec, parseWallSpec, parseWindowSpec, parseZoneSpec, 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 };
16345
+ export { BimModel, DEFAULT_IFC_SCHEMA, DEFAULT_UNITS, IFC_SCHEMAS, PSET_PROPERTY_TYPE_TABLE, PSET_TEMPLATES, SpfReader, bcfError, checkGeometryValidity, checkModelAgainstIds as checkIds, checkModelAgainstIds, checkReferentialIntegrity, checkRoundTrip, checkSchema, countBySeverity, deriveCobieModel, deriveCobieModel as exportCobie, deriveIfcGuid, deriveIfcGuidSync, disposeImportedModel, emptyReport, extendedProfileArea, extendedProfileToFace, fileSchemaString, fromBrepError, fromIfc, geometryError, hasErrors, idsError, ifcError, importError, isExtendedProfile, isIfcSchema, isSlabOpening, isValidIfcGuid, isWallOpening, issue, makeLocalIdCounter, measureTypeFor, newIfcGuid, parseBcfFiles, parseBeamSpec, 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 };