brepjs-bim 0.1.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.
@@ -24,8 +24,37 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  let brepjs = require("brepjs");
25
25
  let web_ifc = require("web-ifc");
26
26
  web_ifc = __toESM(web_ifc, 1);
27
+ //#region src/identity/ifcGuid.ts
28
+ var IFC_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$";
29
+ function newIfcGuid() {
30
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
31
+ bytes[6] = (bytes[6] ?? 0) & 15 | 64;
32
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
33
+ return encodeIfcGuid(bytes);
34
+ }
35
+ function isValidIfcGuid(s) {
36
+ if (s.length !== 22) return false;
37
+ if (!"0123".includes(s[0] ?? "")) return false;
38
+ for (const ch of s) if (!IFC_CHARS.includes(ch)) return false;
39
+ return true;
40
+ }
41
+ function encodeIfcGuid(bytes) {
42
+ let result = "";
43
+ let acc = 0;
44
+ let bits = 4;
45
+ for (const byte of bytes) {
46
+ acc = acc << 8 | byte;
47
+ bits += 8;
48
+ while (bits >= 6) {
49
+ bits -= 6;
50
+ result += IFC_CHARS[acc >> bits & 63] ?? "";
51
+ }
52
+ }
53
+ if (bits > 0) result += IFC_CHARS[acc << 6 - bits & 63] ?? "";
54
+ return result;
55
+ }
56
+ //#endregion
27
57
  //#region src/identity/guidDerivation.ts
28
- var IFC_CHARS$1 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$";
29
58
  var NAMESPACE = "brepjs-bim:v1";
30
59
  var FNV_OFFSET = 2166136261;
31
60
  var FNV_PRIME = 16777619;
@@ -59,7 +88,7 @@ function digest16(stableKey) {
59
88
  * keys yield distinct, format-valid (22-char) GlobalIds.
60
89
  */
61
90
  function deriveIfcGuidSync(stableKey) {
62
- return encodeIfcGuid$1(digest16(stableKey));
91
+ return encodeIfcGuid(digest16(stableKey));
63
92
  }
64
93
  /**
65
94
  * Async wrapper over {@link deriveIfcGuidSync} for callers that prefer a Promise
@@ -86,21 +115,6 @@ function makeRelKey(modelScope, kind, localId) {
86
115
  function makeLineKey(modelScope, expressId) {
87
116
  return `line:${modelScope}:${expressId}`;
88
117
  }
89
- function encodeIfcGuid$1(bytes) {
90
- let result = "";
91
- let acc = 0;
92
- let bits = 0;
93
- for (const byte of bytes) {
94
- acc = acc << 8 | byte;
95
- bits += 8;
96
- while (bits >= 6) {
97
- bits -= 6;
98
- result += IFC_CHARS$1[acc >> bits & 63] ?? "";
99
- }
100
- }
101
- if (bits > 0) result += IFC_CHARS$1[acc << 6 - bits & 63] ?? "";
102
- return result;
103
- }
104
118
  //#endregion
105
119
  //#region src/identity/localId.ts
106
120
  function makeLocalIdCounter(start = 1) {
@@ -251,7 +265,7 @@ function _usingCtx() {
251
265
  //#region src/elementFns/wallFns.ts
252
266
  function wallToSolid(spec) {
253
267
  try {
254
- var _usingCtx$18 = _usingCtx();
268
+ var _usingCtx$19 = _usingCtx();
255
269
  if (spec.length <= 0) return (0, brepjs.err)(specError("WALL_ZERO_LENGTH", "Wall length must be positive"));
256
270
  if (spec.height <= 0) return (0, brepjs.err)(specError("WALL_ZERO_HEIGHT", "Wall height must be positive"));
257
271
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("WALL_ZERO_THICKNESS", "Wall thickness must be positive"));
@@ -279,7 +293,7 @@ function wallToSolid(spec) {
279
293
  ]
280
294
  ]);
281
295
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "WALL_PROFILE_FAILED", "Failed to create wall profile"));
282
- const solidResult = (0, brepjs.extrude)(_usingCtx$18.u(profileResult.value), [
296
+ const solidResult = (0, brepjs.extrude)(_usingCtx$19.u(profileResult.value), [
283
297
  length,
284
298
  0,
285
299
  0
@@ -292,16 +306,16 @@ function wallToSolid(spec) {
292
306
  }
293
307
  return (0, brepjs.ok)(solid);
294
308
  } catch (_) {
295
- _usingCtx$18.e = _;
309
+ _usingCtx$19.e = _;
296
310
  } finally {
297
- _usingCtx$18.d();
311
+ _usingCtx$19.d();
298
312
  }
299
313
  }
300
314
  //#endregion
301
315
  //#region src/elementFns/slabFns.ts
302
316
  function slabToSolid(spec) {
303
317
  try {
304
- var _usingCtx$17 = _usingCtx();
318
+ var _usingCtx$18 = _usingCtx();
305
319
  if (spec.length <= 0) return (0, brepjs.err)(specError("SLAB_ZERO_LENGTH", "Slab length must be positive"));
306
320
  if (spec.width <= 0) return (0, brepjs.err)(specError("SLAB_ZERO_WIDTH", "Slab width must be positive"));
307
321
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("SLAB_ZERO_THICKNESS", "Slab thickness must be positive"));
@@ -329,7 +343,7 @@ function slabToSolid(spec) {
329
343
  ]
330
344
  ]);
331
345
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "SLAB_PROFILE_FAILED", "Failed to create slab profile"));
332
- const solidResult = (0, brepjs.extrude)(_usingCtx$17.u(profileResult.value), [
346
+ const solidResult = (0, brepjs.extrude)(_usingCtx$18.u(profileResult.value), [
333
347
  0,
334
348
  0,
335
349
  thickness
@@ -342,9 +356,9 @@ function slabToSolid(spec) {
342
356
  }
343
357
  return (0, brepjs.ok)(solid);
344
358
  } catch (_) {
345
- _usingCtx$17.e = _;
359
+ _usingCtx$18.e = _;
346
360
  } finally {
347
- _usingCtx$17.d();
361
+ _usingCtx$18.d();
348
362
  }
349
363
  }
350
364
  //#endregion
@@ -5021,14 +5035,14 @@ function to3D(points) {
5021
5035
  }
5022
5036
  function extendedProfileToFace(profile) {
5023
5037
  try {
5024
- var _usingCtx$16 = _usingCtx();
5038
+ var _usingCtx$17 = _usingCtx();
5025
5039
  const invalid = validateProfile(profile);
5026
5040
  if (invalid !== null) return (0, brepjs.err)(invalid);
5027
5041
  const outerResult = (0, brepjs.polygon)(to3D(outerLoop(profile)));
5028
5042
  if (!outerResult.ok) return (0, brepjs.err)(fromBrepError(outerResult.error, "PROFILE_FACE_FAILED", "Failed to build profile outer face"));
5029
5043
  const holes = holeLoops(profile);
5030
5044
  if (holes.length === 0) return (0, brepjs.ok)(outerResult.value);
5031
- const outerFace = _usingCtx$16.u(outerResult.value);
5045
+ const outerFace = _usingCtx$17.u(outerResult.value);
5032
5046
  const holeFaces = [];
5033
5047
  const holeWires = [];
5034
5048
  const disposeHoleFaces = () => {
@@ -5053,9 +5067,9 @@ function extendedProfileToFace(profile) {
5053
5067
  disposeHoleFaces();
5054
5068
  return (0, brepjs.ok)(faceWithHoles);
5055
5069
  } catch (_) {
5056
- _usingCtx$16.e = _;
5070
+ _usingCtx$17.e = _;
5057
5071
  } finally {
5058
- _usingCtx$16.d();
5072
+ _usingCtx$17.d();
5059
5073
  }
5060
5074
  }
5061
5075
  //#endregion
@@ -5093,7 +5107,8 @@ var CoreProfileSchema = discriminatedUnion("kind", [
5093
5107
  overallWidth: number().positive(),
5094
5108
  overallDepth: number().positive(),
5095
5109
  flangeThickness: number().positive(),
5096
- webThickness: number().positive()
5110
+ webThickness: number().positive(),
5111
+ filletRadius: number().positive().optional()
5097
5112
  })
5098
5113
  ]);
5099
5114
  var Pt2Schema = tuple([number(), number()]);
@@ -5225,6 +5240,11 @@ function parseProfile(input) {
5225
5240
  if (profile.kind === "I_BEAM") {
5226
5241
  if (2 * profile.flangeThickness >= profile.overallDepth) return (0, brepjs.err)(specError("INVALID_PROFILE", "I-beam flangeThickness × 2 must be less than overallDepth"));
5227
5242
  if (profile.webThickness >= profile.overallWidth) return (0, brepjs.err)(specError("INVALID_PROFILE", "I-beam webThickness must be less than overallWidth"));
5243
+ if (profile.filletRadius !== void 0) {
5244
+ const clearHeight = profile.overallDepth / 2 - profile.flangeThickness;
5245
+ const clearSpan = (profile.overallWidth - profile.webThickness) / 2;
5246
+ if (profile.filletRadius >= clearHeight || profile.filletRadius >= clearSpan) return (0, brepjs.err)(specError("INVALID_PROFILE", "I-beam filletRadius must be smaller than the clear web height and the clear span beside the web"));
5247
+ }
5228
5248
  }
5229
5249
  return (0, brepjs.ok)(profile);
5230
5250
  }
@@ -5235,8 +5255,42 @@ function profileCrossSectionArea(profile) {
5235
5255
  switch (profile.kind) {
5236
5256
  case "RECTANGULAR": return profile.width * profile.height;
5237
5257
  case "CIRCULAR": return Math.PI * profile.radius * profile.radius;
5238
- case "I_BEAM": return 2 * profile.overallWidth * profile.flangeThickness + (profile.overallDepth - 2 * profile.flangeThickness) * profile.webThickness;
5258
+ case "I_BEAM": {
5259
+ const flangeArea = 2 * profile.overallWidth * profile.flangeThickness;
5260
+ const webArea = (profile.overallDepth - 2 * profile.flangeThickness) * profile.webThickness;
5261
+ const r = profile.filletRadius ?? 0;
5262
+ const filletArea = 4 * r * r * (1 - Math.PI / 4);
5263
+ return flangeArea + webArea + filletArea;
5264
+ }
5265
+ }
5266
+ }
5267
+ var FILLET_SEGMENTS = 8;
5268
+ var FILLET_MIN_ANGLE = .001;
5269
+ function filletArc(prev, v, next, r) {
5270
+ const aLen = Math.hypot(prev[0] - v[0], prev[1] - v[1]);
5271
+ const bLen = Math.hypot(next[0] - v[0], next[1] - v[1]);
5272
+ const a = [(prev[0] - v[0]) / aLen, (prev[1] - v[1]) / aLen];
5273
+ const b = [(next[0] - v[0]) / bLen, (next[1] - v[1]) / bLen];
5274
+ const alpha = Math.acos(Math.max(-1, Math.min(1, a[0] * b[0] + a[1] * b[1])));
5275
+ if (alpha < FILLET_MIN_ANGLE || alpha > Math.PI - FILLET_MIN_ANGLE) return [[v[0], v[1]]];
5276
+ const setback = r / Math.tan(alpha / 2);
5277
+ const center = r / Math.sin(alpha / 2);
5278
+ const bisLen = Math.hypot(a[0] + b[0], a[1] + b[1]);
5279
+ const bis = [(a[0] + b[0]) / bisLen, (a[1] + b[1]) / bisLen];
5280
+ const cx = v[0] + bis[0] * center;
5281
+ const cy = v[1] + bis[1] * center;
5282
+ const p1 = [v[0] + a[0] * setback, v[1] + a[1] * setback];
5283
+ const p2 = [v[0] + b[0] * setback, v[1] + b[1] * setback];
5284
+ const a1 = Math.atan2(p1[1] - cy, p1[0] - cx);
5285
+ let delta = Math.atan2(p2[1] - cy, p2[0] - cx) - a1;
5286
+ while (delta <= -Math.PI) delta += 2 * Math.PI;
5287
+ while (delta > Math.PI) delta -= 2 * Math.PI;
5288
+ const out = [];
5289
+ for (let i = 0; i <= FILLET_SEGMENTS; i++) {
5290
+ const ang = a1 + delta * i / FILLET_SEGMENTS;
5291
+ out.push([cx + r * Math.cos(ang), cy + r * Math.sin(ang)]);
5239
5292
  }
5293
+ return out;
5240
5294
  }
5241
5295
  function profileToPolygon(profile, circleSegments = 32) {
5242
5296
  if (isExtendedProfile(profile)) return (0, brepjs.err)(specError("EXTENDED_PROFILE_NO_POLYGON", `profileToPolygon: extended profile kind '${profile.kind}' has no single-polygon outline; use extendedProfileToFace()`));
@@ -5285,68 +5339,50 @@ function profileToPolygon(profile, circleSegments = 32) {
5285
5339
  const halfD = profile.overallDepth / 2;
5286
5340
  const halfWeb = profile.webThickness / 2;
5287
5341
  const flangeInnerY = halfD - profile.flangeThickness;
5288
- return (0, brepjs.ok)([
5289
- [
5290
- -halfW,
5291
- -halfD,
5292
- 0
5293
- ],
5294
- [
5295
- halfW,
5296
- -halfD,
5297
- 0
5298
- ],
5299
- [
5300
- halfW,
5301
- -flangeInnerY,
5302
- 0
5303
- ],
5304
- [
5305
- halfWeb,
5306
- -flangeInnerY,
5307
- 0
5308
- ],
5309
- [
5310
- halfWeb,
5311
- flangeInnerY,
5312
- 0
5313
- ],
5314
- [
5315
- halfW,
5316
- flangeInnerY,
5317
- 0
5318
- ],
5319
- [
5320
- halfW,
5321
- halfD,
5322
- 0
5323
- ],
5324
- [
5325
- -halfW,
5326
- halfD,
5327
- 0
5328
- ],
5329
- [
5330
- -halfW,
5331
- flangeInnerY,
5332
- 0
5333
- ],
5334
- [
5335
- -halfWeb,
5336
- flangeInnerY,
5337
- 0
5338
- ],
5339
- [
5340
- -halfWeb,
5341
- -flangeInnerY,
5342
- 0
5343
- ],
5344
- [
5345
- -halfW,
5346
- -flangeInnerY,
5347
- 0
5348
- ]
5342
+ const v = [
5343
+ [-halfW, -halfD],
5344
+ [halfW, -halfD],
5345
+ [halfW, -flangeInnerY],
5346
+ [halfWeb, -flangeInnerY],
5347
+ [halfWeb, flangeInnerY],
5348
+ [halfW, flangeInnerY],
5349
+ [halfW, halfD],
5350
+ [-halfW, halfD],
5351
+ [-halfW, flangeInnerY],
5352
+ [-halfWeb, flangeInnerY],
5353
+ [-halfWeb, -flangeInnerY],
5354
+ [-halfW, -flangeInnerY]
5355
+ ];
5356
+ const r = profile.filletRadius ?? 0;
5357
+ const rootCorners = new Set([
5358
+ 3,
5359
+ 4,
5360
+ 9,
5361
+ 10
5349
5362
  ]);
5363
+ const pts = [];
5364
+ for (let i = 0; i < v.length; i++) {
5365
+ const cur = v[i];
5366
+ if (cur === void 0) continue;
5367
+ if (r > 0 && rootCorners.has(i)) {
5368
+ const prev = v[(i - 1 + v.length) % v.length];
5369
+ const next = v[(i + 1) % v.length];
5370
+ if (prev !== void 0 && next !== void 0) {
5371
+ for (const [ax, ay] of filletArc(prev, cur, next, r)) pts.push([
5372
+ ax,
5373
+ ay,
5374
+ 0
5375
+ ]);
5376
+ continue;
5377
+ }
5378
+ }
5379
+ pts.push([
5380
+ cur[0],
5381
+ cur[1],
5382
+ 0
5383
+ ]);
5384
+ }
5385
+ return (0, brepjs.ok)(pts);
5350
5386
  }
5351
5387
  }
5352
5388
  }
@@ -5354,7 +5390,7 @@ function profileToPolygon(profile, circleSegments = 32) {
5354
5390
  //#region src/elementFns/beamFns.ts
5355
5391
  function beamToSolid(spec) {
5356
5392
  try {
5357
- var _usingCtx$15 = _usingCtx();
5393
+ var _usingCtx$16 = _usingCtx();
5358
5394
  if (spec.length <= 0) return (0, brepjs.err)(specError("BEAM_ZERO_LENGTH", "Beam length must be positive"));
5359
5395
  if (isExtendedProfile(spec.profile)) try {
5360
5396
  var _usingCtx3 = _usingCtx();
@@ -5389,7 +5425,7 @@ function beamToSolid(spec) {
5389
5425
  py
5390
5426
  ]));
5391
5427
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "BEAM_PROFILE_FAILED", "Failed to create beam profile"));
5392
- const solidResult = (0, brepjs.extrude)(_usingCtx$15.u(profileResult.value), [
5428
+ const solidResult = (0, brepjs.extrude)(_usingCtx$16.u(profileResult.value), [
5393
5429
  spec.length,
5394
5430
  0,
5395
5431
  0
@@ -5402,16 +5438,16 @@ function beamToSolid(spec) {
5402
5438
  }
5403
5439
  return (0, brepjs.ok)(solid);
5404
5440
  } catch (_) {
5405
- _usingCtx$15.e = _;
5441
+ _usingCtx$16.e = _;
5406
5442
  } finally {
5407
- _usingCtx$15.d();
5443
+ _usingCtx$16.d();
5408
5444
  }
5409
5445
  }
5410
5446
  //#endregion
5411
5447
  //#region src/elementFns/columnFns.ts
5412
5448
  function columnToSolid(spec) {
5413
5449
  try {
5414
- var _usingCtx$14 = _usingCtx();
5450
+ var _usingCtx$15 = _usingCtx();
5415
5451
  if (spec.height <= 0) return (0, brepjs.err)(specError("COLUMN_ZERO_HEIGHT", "Column height must be positive"));
5416
5452
  if (isExtendedProfile(spec.profile)) try {
5417
5453
  var _usingCtx3 = _usingCtx();
@@ -5439,7 +5475,7 @@ function columnToSolid(spec) {
5439
5475
  const profilePts = profilePtsResult.value;
5440
5476
  const profileResult = (0, brepjs.polygon)(profilePts);
5441
5477
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "COLUMN_PROFILE_FAILED", "Failed to create column profile"));
5442
- const solidResult = (0, brepjs.extrude)(_usingCtx$14.u(profileResult.value), [
5478
+ const solidResult = (0, brepjs.extrude)(_usingCtx$15.u(profileResult.value), [
5443
5479
  0,
5444
5480
  0,
5445
5481
  spec.height
@@ -5452,9 +5488,9 @@ function columnToSolid(spec) {
5452
5488
  }
5453
5489
  return (0, brepjs.ok)(solid);
5454
5490
  } catch (_) {
5455
- _usingCtx$14.e = _;
5491
+ _usingCtx$15.e = _;
5456
5492
  } finally {
5457
- _usingCtx$14.d();
5493
+ _usingCtx$15.d();
5458
5494
  }
5459
5495
  }
5460
5496
  //#endregion
@@ -5462,7 +5498,7 @@ function columnToSolid(spec) {
5462
5498
  var EPSILON_MM$1 = 1;
5463
5499
  function openingToSolid(spec, wallThickness) {
5464
5500
  try {
5465
- var _usingCtx$13 = _usingCtx();
5501
+ var _usingCtx$14 = _usingCtx();
5466
5502
  if (spec.width <= 0) return (0, brepjs.err)(specError("OPENING_ZERO_WIDTH", "Opening width must be positive"));
5467
5503
  if (spec.height <= 0) return (0, brepjs.err)(specError("OPENING_ZERO_HEIGHT", "Opening height must be positive"));
5468
5504
  if (wallThickness <= 0) return (0, brepjs.err)(specError("OPENING_ZERO_WALL_THICKNESS", "Wall thickness must be positive"));
@@ -5494,7 +5530,7 @@ function openingToSolid(spec, wallThickness) {
5494
5530
  ]
5495
5531
  ]);
5496
5532
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "OPENING_PROFILE_FAILED", "Failed to create opening profile"));
5497
- const solidResult = (0, brepjs.extrude)(_usingCtx$13.u(profileResult.value), [
5533
+ const solidResult = (0, brepjs.extrude)(_usingCtx$14.u(profileResult.value), [
5498
5534
  spec.width,
5499
5535
  0,
5500
5536
  0
@@ -5507,9 +5543,9 @@ function openingToSolid(spec, wallThickness) {
5507
5543
  }
5508
5544
  return (0, brepjs.ok)(solid);
5509
5545
  } catch (_) {
5510
- _usingCtx$13.e = _;
5546
+ _usingCtx$14.e = _;
5511
5547
  } finally {
5512
- _usingCtx$13.d();
5548
+ _usingCtx$14.d();
5513
5549
  }
5514
5550
  }
5515
5551
  //#endregion
@@ -5517,7 +5553,7 @@ function openingToSolid(spec, wallThickness) {
5517
5553
  var EPSILON_MM = 1;
5518
5554
  function slabOpeningToSolid(spec, slabThickness) {
5519
5555
  try {
5520
- var _usingCtx$12 = _usingCtx();
5556
+ var _usingCtx$13 = _usingCtx();
5521
5557
  if (spec.sizeX <= 0) return (0, brepjs.err)(specError("SLAB_OPENING_ZERO_SIZE_X", "Slab opening sizeX must be positive"));
5522
5558
  if (spec.sizeY <= 0) return (0, brepjs.err)(specError("SLAB_OPENING_ZERO_SIZE_Y", "Slab opening sizeY must be positive"));
5523
5559
  if (slabThickness <= 0) return (0, brepjs.err)(specError("SLAB_OPENING_ZERO_SLAB_THICKNESS", "Slab thickness must be positive"));
@@ -5549,7 +5585,7 @@ function slabOpeningToSolid(spec, slabThickness) {
5549
5585
  ]
5550
5586
  ]);
5551
5587
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "SLAB_OPENING_PROFILE_FAILED", "Failed to create slab opening profile"));
5552
- const solidResult = (0, brepjs.extrude)(_usingCtx$12.u(profileResult.value), [
5588
+ const solidResult = (0, brepjs.extrude)(_usingCtx$13.u(profileResult.value), [
5553
5589
  0,
5554
5590
  0,
5555
5591
  slabThickness + 2 * EPSILON_MM
@@ -5562,16 +5598,16 @@ function slabOpeningToSolid(spec, slabThickness) {
5562
5598
  }
5563
5599
  return (0, brepjs.ok)(solid);
5564
5600
  } catch (_) {
5565
- _usingCtx$12.e = _;
5601
+ _usingCtx$13.e = _;
5566
5602
  } finally {
5567
- _usingCtx$12.d();
5603
+ _usingCtx$13.d();
5568
5604
  }
5569
5605
  }
5570
5606
  //#endregion
5571
5607
  //#region src/elementFns/spaceFns.ts
5572
5608
  function spaceToSolid(spec) {
5573
5609
  try {
5574
- var _usingCtx$11 = _usingCtx();
5610
+ var _usingCtx$12 = _usingCtx();
5575
5611
  if (spec.length <= 0) return (0, brepjs.err)(specError("SPACE_ZERO_LENGTH", "Space length must be positive"));
5576
5612
  if (spec.width <= 0) return (0, brepjs.err)(specError("SPACE_ZERO_WIDTH", "Space width must be positive"));
5577
5613
  if (spec.height <= 0) return (0, brepjs.err)(specError("SPACE_ZERO_HEIGHT", "Space height must be positive"));
@@ -5599,7 +5635,7 @@ function spaceToSolid(spec) {
5599
5635
  ]
5600
5636
  ]);
5601
5637
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "SPACE_PROFILE_FAILED", "Failed to create space footprint"));
5602
- const solidResult = (0, brepjs.extrude)(_usingCtx$11.u(profileResult.value), [
5638
+ const solidResult = (0, brepjs.extrude)(_usingCtx$12.u(profileResult.value), [
5603
5639
  0,
5604
5640
  0,
5605
5641
  height
@@ -5612,21 +5648,26 @@ function spaceToSolid(spec) {
5612
5648
  }
5613
5649
  return (0, brepjs.ok)(solid);
5614
5650
  } catch (_) {
5615
- _usingCtx$11.e = _;
5651
+ _usingCtx$12.e = _;
5616
5652
  } finally {
5617
- _usingCtx$11.d();
5653
+ _usingCtx$12.d();
5618
5654
  }
5619
5655
  }
5620
5656
  //#endregion
5621
5657
  //#region src/elementFns/roofFns.ts
5622
- 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) {
5623
5667
  try {
5624
- var _usingCtx$10 = _usingCtx();
5625
- if (spec.length <= 0) return (0, brepjs.err)(specError("ROOF_ZERO_LENGTH", "Roof length must be positive"));
5626
- if (spec.width <= 0) return (0, brepjs.err)(specError("ROOF_ZERO_WIDTH", "Roof width must be positive"));
5627
- if (spec.thickness <= 0) return (0, brepjs.err)(specError("ROOF_ZERO_THICKNESS", "Roof thickness must be positive"));
5668
+ var _usingCtx$11 = _usingCtx();
5628
5669
  const { length, width, thickness } = spec;
5629
- const profileResult = (0, brepjs.polygon)([
5670
+ const face = (0, brepjs.polygon)([
5630
5671
  [
5631
5672
  0,
5632
5673
  0,
@@ -5648,112 +5689,294 @@ function roofToSolid(spec) {
5648
5689
  0
5649
5690
  ]
5650
5691
  ]);
5651
- if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "ROOF_PROFILE_FAILED", "Failed to create roof profile"));
5652
- 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), [
5653
5694
  0,
5654
5695
  0,
5655
5696
  thickness
5656
5697
  ]);
5657
- if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude roof profile"));
5658
- const solid = solidResult.value;
5659
- if (!(0, brepjs.isValidSolid)(solid)) {
5660
- solid[Symbol.dispose]();
5661
- return (0, brepjs.err)(geometryError("ROOF_INVALID_SOLID", "Extruded roof solid failed validity check"));
5662
- }
5663
- 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");
5664
5700
  } catch (_) {
5665
- _usingCtx$10.e = _;
5701
+ _usingCtx$11.e = _;
5666
5702
  } finally {
5667
- _usingCtx$10.d();
5703
+ _usingCtx$11.d();
5668
5704
  }
5669
5705
  }
5670
- //#endregion
5671
- //#region src/elementFns/curtainWallFns.ts
5672
- function boxSolid(sizeX, sizeY, sizeZ) {
5706
+ function shedRoof(spec, pitch) {
5673
5707
  try {
5674
- var _usingCtx$9 = _usingCtx();
5675
- const profileResult = (0, brepjs.polygon)([
5708
+ var _usingCtx3 = _usingCtx();
5709
+ const { length, width, thickness } = spec;
5710
+ const rise = width * Math.tan(pitch * DEG2RAD);
5711
+ const face = (0, brepjs.polygon)([
5676
5712
  [
5677
5713
  0,
5678
5714
  0,
5679
5715
  0
5680
5716
  ],
5681
5717
  [
5682
- sizeX,
5683
5718
  0,
5719
+ width,
5684
5720
  0
5685
5721
  ],
5686
5722
  [
5687
- sizeX,
5688
- sizeY,
5689
- 0
5723
+ 0,
5724
+ width,
5725
+ thickness + rise
5690
5726
  ],
5691
5727
  [
5692
5728
  0,
5693
- sizeY,
5694
- 0
5729
+ 0,
5730
+ thickness
5695
5731
  ]
5696
5732
  ]);
5697
- if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "CURTAIN_WALL_PROFILE_FAILED", "Failed to create component profile"));
5698
- const solidResult = (0, brepjs.extrude)(_usingCtx$9.u(profileResult.value), [
5699
- 0,
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,
5700
5736
  0,
5701
- sizeZ
5737
+ 0
5702
5738
  ]);
5703
- if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "CURTAIN_WALL_EXTRUDE_FAILED", "Failed to extrude component"));
5704
- const solid = solidResult.value;
5705
- if (!(0, brepjs.isValidSolid)(solid)) {
5706
- solid[Symbol.dispose]();
5707
- return (0, brepjs.err)(geometryError("CURTAIN_WALL_INVALID_SOLID", "Extruded curtain wall component failed validity check"));
5708
- }
5709
- return (0, brepjs.ok)(solid);
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");
5710
5741
  } catch (_) {
5711
- _usingCtx$9.e = _;
5742
+ _usingCtx3.e = _;
5712
5743
  } finally {
5713
- _usingCtx$9.d();
5744
+ _usingCtx3.d();
5714
5745
  }
5715
5746
  }
5716
- function disposeComponents(components) {
5717
- for (const c of components) c.solid[Symbol.dispose]();
5718
- }
5719
- /**
5720
- * Decomposes a curtain wall spec into its panel (plate) and mullion (member)
5721
- * geometry. Mullions run along every vertical grid line (columns + 1 of them)
5722
- * and every horizontal grid line (rows + 1); panels fill the cells between
5723
- * adjacent mullions. The mullion section is centred on each grid line.
5724
- *
5725
- * Geometry is laid out in the wall's local frame: X across the wall, Z up, Y
5726
- * through the depth. Each component's local-origin solid is returned alongside
5727
- * its placement origin so the IFC writer can emit one IfcLocalPlacement per
5728
- * component.
5729
- */
5730
- function curtainWallToGrid(spec) {
5731
- const { width, height, columns, rows, panelThickness, mullionWidth, mullionDepth } = spec;
5732
- const cellWidth = width / columns;
5733
- const cellHeight = height / rows;
5734
- const halfMullion = mullionWidth / 2;
5735
- const panelWidth = cellWidth - mullionWidth;
5736
- const panelHeight = cellHeight - mullionWidth;
5737
- if (panelWidth <= 0 || panelHeight <= 0) return (0, brepjs.err)(geometryError("CURTAIN_WALL_DEGENERATE_PANEL", "mullionWidth leaves no room for panels in the grid"));
5738
- const panels = [];
5739
- const mullions = [];
5740
- for (let c = 0; c < columns; c++) for (let r = 0; r < rows; r++) {
5741
- const solidResult = boxSolid(panelWidth, panelThickness, panelHeight);
5742
- if (!solidResult.ok) {
5743
- disposeComponents(panels);
5744
- disposeComponents(mullions);
5745
- return (0, brepjs.err)(solidResult.error);
5746
- }
5747
- panels.push({
5748
- origin: [
5749
- c * cellWidth + halfMullion,
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
+ [
5750
5754
  0,
5751
- r * cellHeight + halfMullion
5755
+ 0,
5756
+ 0
5752
5757
  ],
5753
- size: [
5754
- panelWidth,
5755
- panelThickness,
5756
- panelHeight
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);
5891
+ }
5892
+ }
5893
+ //#endregion
5894
+ //#region src/elementFns/curtainWallFns.ts
5895
+ function boxSolid(sizeX, sizeY, sizeZ) {
5896
+ try {
5897
+ var _usingCtx$10 = _usingCtx();
5898
+ const profileResult = (0, brepjs.polygon)([
5899
+ [
5900
+ 0,
5901
+ 0,
5902
+ 0
5903
+ ],
5904
+ [
5905
+ sizeX,
5906
+ 0,
5907
+ 0
5908
+ ],
5909
+ [
5910
+ sizeX,
5911
+ sizeY,
5912
+ 0
5913
+ ],
5914
+ [
5915
+ 0,
5916
+ sizeY,
5917
+ 0
5918
+ ]
5919
+ ]);
5920
+ if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "CURTAIN_WALL_PROFILE_FAILED", "Failed to create component profile"));
5921
+ const solidResult = (0, brepjs.extrude)(_usingCtx$10.u(profileResult.value), [
5922
+ 0,
5923
+ 0,
5924
+ sizeZ
5925
+ ]);
5926
+ if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "CURTAIN_WALL_EXTRUDE_FAILED", "Failed to extrude component"));
5927
+ const solid = solidResult.value;
5928
+ if (!(0, brepjs.isValidSolid)(solid)) {
5929
+ solid[Symbol.dispose]();
5930
+ return (0, brepjs.err)(geometryError("CURTAIN_WALL_INVALID_SOLID", "Extruded curtain wall component failed validity check"));
5931
+ }
5932
+ return (0, brepjs.ok)(solid);
5933
+ } catch (_) {
5934
+ _usingCtx$10.e = _;
5935
+ } finally {
5936
+ _usingCtx$10.d();
5937
+ }
5938
+ }
5939
+ function disposeComponents(components) {
5940
+ for (const c of components) c.solid[Symbol.dispose]();
5941
+ }
5942
+ /**
5943
+ * Decomposes a curtain wall spec into its panel (plate) and mullion (member)
5944
+ * geometry. Mullions run along every vertical grid line (columns + 1 of them)
5945
+ * and every horizontal grid line (rows + 1); panels fill the cells between
5946
+ * adjacent mullions. The mullion section is centred on each grid line.
5947
+ *
5948
+ * Geometry is laid out in the wall's local frame: X across the wall, Z up, Y
5949
+ * through the depth. Each component's local-origin solid is returned alongside
5950
+ * its placement origin so the IFC writer can emit one IfcLocalPlacement per
5951
+ * component.
5952
+ */
5953
+ function curtainWallToGrid(spec) {
5954
+ const { width, height, columns, rows, panelThickness, mullionWidth, mullionDepth } = spec;
5955
+ const cellWidth = width / columns;
5956
+ const cellHeight = height / rows;
5957
+ const halfMullion = mullionWidth / 2;
5958
+ const panelWidth = cellWidth - mullionWidth;
5959
+ const panelHeight = cellHeight - mullionWidth;
5960
+ if (panelWidth <= 0 || panelHeight <= 0) return (0, brepjs.err)(geometryError("CURTAIN_WALL_DEGENERATE_PANEL", "mullionWidth leaves no room for panels in the grid"));
5961
+ const panels = [];
5962
+ const mullions = [];
5963
+ for (let c = 0; c < columns; c++) for (let r = 0; r < rows; r++) {
5964
+ const solidResult = boxSolid(panelWidth, panelThickness, panelHeight);
5965
+ if (!solidResult.ok) {
5966
+ disposeComponents(panels);
5967
+ disposeComponents(mullions);
5968
+ return (0, brepjs.err)(solidResult.error);
5969
+ }
5970
+ panels.push({
5971
+ origin: [
5972
+ c * cellWidth + halfMullion,
5973
+ 0,
5974
+ r * cellHeight + halfMullion
5975
+ ],
5976
+ size: [
5977
+ panelWidth,
5978
+ panelThickness,
5979
+ panelHeight
5757
5980
  ],
5758
5981
  solid: solidResult.value
5759
5982
  });
@@ -5809,7 +6032,7 @@ function curtainWallToGrid(spec) {
5809
6032
  //#region src/elementFns/foundationFns.ts
5810
6033
  function footingToSolid(spec) {
5811
6034
  try {
5812
- var _usingCtx$8 = _usingCtx();
6035
+ var _usingCtx$9 = _usingCtx();
5813
6036
  if (spec.length <= 0) return (0, brepjs.err)(specError("FOOTING_ZERO_LENGTH", "Footing length must be positive"));
5814
6037
  if (spec.width <= 0) return (0, brepjs.err)(specError("FOOTING_ZERO_WIDTH", "Footing width must be positive"));
5815
6038
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("FOOTING_ZERO_THICKNESS", "Footing thickness must be positive"));
@@ -5837,7 +6060,7 @@ function footingToSolid(spec) {
5837
6060
  ]
5838
6061
  ]);
5839
6062
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "FOOTING_PROFILE_FAILED", "Failed to create footing profile"));
5840
- const solidResult = (0, brepjs.extrude)(_usingCtx$8.u(profileResult.value), [
6063
+ const solidResult = (0, brepjs.extrude)(_usingCtx$9.u(profileResult.value), [
5841
6064
  0,
5842
6065
  0,
5843
6066
  thickness
@@ -5850,9 +6073,9 @@ function footingToSolid(spec) {
5850
6073
  }
5851
6074
  return (0, brepjs.ok)(solid);
5852
6075
  } catch (_) {
5853
- _usingCtx$8.e = _;
6076
+ _usingCtx$9.e = _;
5854
6077
  } finally {
5855
- _usingCtx$8.d();
6078
+ _usingCtx$9.d();
5856
6079
  }
5857
6080
  }
5858
6081
  function pileToSolid(spec) {
@@ -5905,12 +6128,9 @@ function pileToSolid(spec) {
5905
6128
  }
5906
6129
  //#endregion
5907
6130
  //#region src/elementFns/railingFns.ts
5908
- function railingToSolid(spec) {
6131
+ function panelRailing(spec) {
5909
6132
  try {
5910
- var _usingCtx$7 = _usingCtx();
5911
- if (spec.length <= 0) return (0, brepjs.err)(specError("RAILING_ZERO_LENGTH", "Railing length must be positive"));
5912
- if (spec.height <= 0) return (0, brepjs.err)(specError("RAILING_ZERO_HEIGHT", "Railing height must be positive"));
5913
- if (spec.thickness <= 0) return (0, brepjs.err)(specError("RAILING_ZERO_THICKNESS", "Railing thickness must be positive"));
6133
+ var _usingCtx$8 = _usingCtx();
5914
6134
  const { length, height, thickness } = spec;
5915
6135
  const profileResult = (0, brepjs.polygon)([
5916
6136
  [
@@ -5935,7 +6155,7 @@ function railingToSolid(spec) {
5935
6155
  ]
5936
6156
  ]);
5937
6157
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "RAILING_PROFILE_FAILED", "Failed to create railing profile"));
5938
- const solidResult = (0, brepjs.extrude)(_usingCtx$7.u(profileResult.value), [
6158
+ const solidResult = (0, brepjs.extrude)(_usingCtx$8.u(profileResult.value), [
5939
6159
  length,
5940
6160
  0,
5941
6161
  0
@@ -5948,16 +6168,72 @@ function railingToSolid(spec) {
5948
6168
  }
5949
6169
  return (0, brepjs.ok)(solid);
5950
6170
  } catch (_) {
5951
- _usingCtx$7.e = _;
6171
+ _usingCtx$8.e = _;
5952
6172
  } finally {
5953
- _usingCtx$7.d();
6173
+ _usingCtx$8.d();
5954
6174
  }
5955
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);
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);
6231
+ }
5956
6232
  //#endregion
5957
6233
  //#region src/elementFns/coveringFns.ts
5958
6234
  function coveringToSolid(spec) {
5959
6235
  try {
5960
- var _usingCtx$6 = _usingCtx();
6236
+ var _usingCtx$7 = _usingCtx();
5961
6237
  if (spec.length <= 0) return (0, brepjs.err)(specError("COVERING_ZERO_LENGTH", "Covering length must be positive"));
5962
6238
  if (spec.width <= 0) return (0, brepjs.err)(specError("COVERING_ZERO_WIDTH", "Covering width must be positive"));
5963
6239
  if (spec.thickness <= 0) return (0, brepjs.err)(specError("COVERING_ZERO_THICKNESS", "Covering thickness must be positive"));
@@ -5985,7 +6261,7 @@ function coveringToSolid(spec) {
5985
6261
  ]
5986
6262
  ]);
5987
6263
  if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "COVERING_PROFILE_FAILED", "Failed to create covering profile"));
5988
- const solidResult = (0, brepjs.extrude)(_usingCtx$6.u(profileResult.value), [
6264
+ const solidResult = (0, brepjs.extrude)(_usingCtx$7.u(profileResult.value), [
5989
6265
  0,
5990
6266
  0,
5991
6267
  thickness
@@ -5998,9 +6274,9 @@ function coveringToSolid(spec) {
5998
6274
  }
5999
6275
  return (0, brepjs.ok)(solid);
6000
6276
  } catch (_) {
6001
- _usingCtx$6.e = _;
6277
+ _usingCtx$7.e = _;
6002
6278
  } finally {
6003
- _usingCtx$6.d();
6279
+ _usingCtx$7.d();
6004
6280
  }
6005
6281
  }
6006
6282
  //#endregion
@@ -6430,17 +6706,17 @@ var BimModel = class {
6430
6706
  }
6431
6707
  #cutWallGeometry(wall, openingSpec) {
6432
6708
  try {
6433
- var _usingCtx$5 = _usingCtx();
6709
+ var _usingCtx$6 = _usingCtx();
6434
6710
  const toolResult = openingToSolid(openingSpec, wall.spec.thickness);
6435
6711
  if (!toolResult.ok) return (0, brepjs.err)(toolResult.error);
6436
- const tool = _usingCtx$5.u(toolResult.value);
6712
+ const tool = _usingCtx$6.u(toolResult.value);
6437
6713
  const cutResult = (0, brepjs.cut)(wall.geometry, tool);
6438
6714
  if (!cutResult.ok) return (0, brepjs.err)(fromBrepError(cutResult.error, "WALL_CUT_FAILED", "Boolean cut of wall with opening failed"));
6439
6715
  return (0, brepjs.ok)(cutResult.value);
6440
6716
  } catch (_) {
6441
- _usingCtx$5.e = _;
6717
+ _usingCtx$6.e = _;
6442
6718
  } finally {
6443
- _usingCtx$5.d();
6719
+ _usingCtx$6.d();
6444
6720
  }
6445
6721
  }
6446
6722
  #replaceWallGeometry(wall, newGeometry) {
@@ -6530,6 +6806,50 @@ var BimModel = class {
6530
6806
  getElement(id) {
6531
6807
  return this.#elements.get(id) ?? null;
6532
6808
  }
6809
+ /**
6810
+ * A serializable summary of the model's structure, rooted at the project and
6811
+ * walking the IFC spatial hierarchy (AGGREGATES: project → site → building →
6812
+ * storey) plus the elements contained in each storey (placeIn). Useful for a
6813
+ * read-only tree view of the model across a worker boundary.
6814
+ */
6815
+ toTreeSummary() {
6816
+ const aggregated = /* @__PURE__ */ new Map();
6817
+ const contained = /* @__PURE__ */ new Map();
6818
+ for (const rel of this.#relationships.values()) if (rel.kind === "AGGREGATES") {
6819
+ const list = aggregated.get(rel.relatingObject) ?? [];
6820
+ list.push(...rel.relatedObjects);
6821
+ aggregated.set(rel.relatingObject, list);
6822
+ } else if (rel.kind === "CONTAINED_IN") {
6823
+ const list = contained.get(rel.relatingStructure) ?? [];
6824
+ list.push(...rel.relatedElements);
6825
+ contained.set(rel.relatingStructure, list);
6826
+ }
6827
+ const labelFor = (el) => {
6828
+ const spec = el.spec;
6829
+ const base = typeof spec.name === "string" && spec.name.length > 0 ? spec.name : el.category;
6830
+ return el.category === "STOREY" && typeof spec.elevation === "number" ? `${base} (+${spec.elevation} mm)` : base;
6831
+ };
6832
+ const seen = /* @__PURE__ */ new Set();
6833
+ const build = (id) => {
6834
+ if (seen.has(id)) return null;
6835
+ seen.add(id);
6836
+ const el = this.#elements.get(id);
6837
+ if (el === void 0) return null;
6838
+ const children = [...aggregated.get(id) ?? [], ...contained.get(id) ?? []].map(build).filter((n) => n !== null);
6839
+ return {
6840
+ id,
6841
+ label: labelFor(el),
6842
+ category: el.category,
6843
+ children
6844
+ };
6845
+ };
6846
+ const root = this.#projectId !== null ? build(this.#projectId) : null;
6847
+ const countNodes = (node) => 1 + node.children.reduce((sum, c) => sum + countNodes(c), 0);
6848
+ return {
6849
+ root,
6850
+ elementCount: root ? countNodes(root) : 0
6851
+ };
6852
+ }
6533
6853
  getWalls() {
6534
6854
  const walls = [];
6535
6855
  for (const el of this.#elements.values()) if (el.category === "WALL") walls.push(el);
@@ -6633,18 +6953,365 @@ var BimModel = class {
6633
6953
  this.#elements.set(localId, el);
6634
6954
  return localId;
6635
6955
  }
6636
- #makeRel(fields) {
6637
- const localId = this.#counter.next();
6638
- const guid = deriveIfcGuidSync(makeRelKey(this.#modelScope, fields.kind, localId));
6639
- const rel = {
6640
- ...fields,
6641
- guid,
6642
- localId
6643
- };
6644
- this.#relationships.set(localId, rel);
6645
- 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)([]);
6646
7313
  }
6647
- };
7314
+ }
6648
7315
  //#endregion
6649
7316
  //#region src/ifc-writer/schemaVersion.ts
6650
7317
  /**
@@ -6707,27 +7374,62 @@ function schemaSupports(schema, entityName) {
6707
7374
  return true;
6708
7375
  }
6709
7376
  //#endregion
7377
+ //#region src/ifcRuntime.ts
7378
+ var wasmLocateFile;
7379
+ /**
7380
+ * Override how web-ifc finds its `.wasm` file. Applied by every web-ifc entry
7381
+ * point in this package — IFC export ({@link toIfc}), import ({@link fromIfc})
7382
+ * and validation. Required when brepjs-bim is bundled into a worker that serves
7383
+ * the wasm itself; not needed in Node.
7384
+ */
7385
+ function setIfcWasmLocateFile(locate) {
7386
+ wasmLocateFile = locate;
7387
+ }
7388
+ /**
7389
+ * Initialize a web-ifc API instance the way this package always wants it: with
7390
+ * the host-provided wasm locator and forced single-threaded.
7391
+ *
7392
+ * Single-threaded matters in a cross-origin-isolated context (e.g. a page that
7393
+ * sets COOP/COEP for another WASM kernel): web-ifc would otherwise load its
7394
+ * pthread build and spawn a sub-Worker, which fails when brepjs-bim is itself
7395
+ * bundled inside a Web Worker. In Node the flag is a no-op (web-ifc is already
7396
+ * single-threaded there), and multithreading only speeds up parsing/geometry,
7397
+ * not the one-shot serialize/read this package does.
7398
+ */
7399
+ async function initIfcApi(api) {
7400
+ await api.Init(wasmLocateFile, true);
7401
+ }
7402
+ //#endregion
6710
7403
  //#region src/ifc-writer/ifcWriter.ts
6711
7404
  /** Default MVD ViewDefinition declared in the STEP FILE_DESCRIPTION header. */
6712
7405
  var DEFAULT_MVD_VIEW_DEFINITION = "ReferenceView_v1.2";
6713
7406
  var VIEW_DEFINITION_RE = /ViewDefinition \[[^\]]*\]/;
7407
+ var FILE_NAME_RE = /(FILE_NAME\('[^']*','[^']*',)(?:\$|\(\$\)),(?:\$|\(\$\)),('[^']*','[^']*'),\$\)/;
7408
+ /** STEP single-quoted string literal with embedded quotes doubled per ISO 10303-21. */
7409
+ function stepString(value) {
7410
+ return `'${value.replace(/'/g, "''")}'`;
7411
+ }
6714
7412
  var IfcWriter = class IfcWriter {
6715
7413
  #api;
6716
7414
  #modelId;
6717
7415
  #mvdViewDefinition;
7416
+ #author;
7417
+ #organization;
6718
7418
  #nextExpressId = 1;
6719
7419
  #closed = false;
6720
7420
  #modelScope = "";
6721
- constructor(api, modelId, mvdViewDefinition) {
7421
+ constructor(api, modelId, mvdViewDefinition, header) {
6722
7422
  this.#api = api;
6723
7423
  this.#modelId = modelId;
6724
7424
  this.#mvdViewDefinition = mvdViewDefinition;
7425
+ this.#author = header.author ?? "";
7426
+ this.#organization = header.organization ?? "";
6725
7427
  }
6726
- static async create(mvdViewDefinition = DEFAULT_MVD_VIEW_DEFINITION, ifcSchema = DEFAULT_IFC_SCHEMA) {
7428
+ static async create(mvdViewDefinition = DEFAULT_MVD_VIEW_DEFINITION, ifcSchema = DEFAULT_IFC_SCHEMA, header = {}) {
6727
7429
  try {
6728
7430
  const api = new web_ifc.IfcAPI();
6729
- await api.Init();
6730
- return (0, brepjs.ok)(new IfcWriter(api, api.CreateModel({ schema: fileSchemaString(ifcSchema) }), mvdViewDefinition));
7431
+ await initIfcApi(api);
7432
+ return (0, brepjs.ok)(new IfcWriter(api, api.CreateModel({ schema: fileSchemaString(ifcSchema) }), mvdViewDefinition, header));
6731
7433
  } catch (e) {
6732
7434
  return (0, brepjs.err)(ifcError("IFC_INIT_FAILED", "Failed to initialize web-ifc", e));
6733
7435
  }
@@ -6761,7 +7463,7 @@ var IfcWriter = class IfcWriter {
6761
7463
  if (this.#closed) return (0, brepjs.err)(ifcError("IFC_ALREADY_SAVED", "Model has already been saved and closed"));
6762
7464
  try {
6763
7465
  const bytes = this.#api.SaveModel(this.#modelId);
6764
- return (0, brepjs.ok)(this.#patchMvd(bytes));
7466
+ return (0, brepjs.ok)(this.#patchHeader(bytes));
6765
7467
  } catch (e) {
6766
7468
  return (0, brepjs.err)(ifcError("IFC_SAVE_FAILED", "Failed to serialize IFC model", e));
6767
7469
  } finally {
@@ -6770,21 +7472,20 @@ var IfcWriter = class IfcWriter {
6770
7472
  }
6771
7473
  }
6772
7474
  /**
6773
- * Injects the declared MVD into the STEP FILE_DESCRIPTION header. web-ifc does
6774
- * not expose the header's ViewDefinition for configuration, so we rewrite the
6775
- * empty default in the ASCII header region. If the expected pattern is absent
6776
- * (e.g. a future web-ifc default change) the bytes are returned unchanged.
7475
+ * Rewrites the STEP header in the ASCII region web-ifc emits: declares the MVD
7476
+ * in FILE_DESCRIPTION and makes FILE_NAME's author/organization/authorization
7477
+ * spec-conformant (web-ifc leaves them as bare `$`). web-ifc exposes neither
7478
+ * for configuration. If an expected pattern is absent (e.g. a future web-ifc
7479
+ * default change) that part is skipped and the bytes returned unchanged.
6777
7480
  */
6778
- #patchMvd(bytes) {
6779
- if (this.#mvdViewDefinition.length === 0) return bytes;
7481
+ #patchHeader(bytes) {
6780
7482
  const HEADER_SCAN = Math.min(bytes.byteLength, 2048);
6781
- const head = new TextDecoder().decode(bytes.subarray(0, HEADER_SCAN));
6782
- if (!VIEW_DEFINITION_RE.test(head)) {
6783
- console.warn(`IfcWriter: FILE_DESCRIPTION ViewDefinition not found; MVD "${this.#mvdViewDefinition}" not declared`);
6784
- return bytes;
6785
- }
6786
- const patchedHead = head.replace(VIEW_DEFINITION_RE, `ViewDefinition [${this.#mvdViewDefinition}]`);
6787
- const patchedHeadBytes = new TextEncoder().encode(patchedHead);
7483
+ let head = new TextDecoder().decode(bytes.subarray(0, HEADER_SCAN));
7484
+ if (FILE_NAME_RE.test(head)) head = head.replace(FILE_NAME_RE, (_m, prefix, systems) => `${prefix}(${stepString(this.#author)}),(${stepString(this.#organization)}),${systems},${stepString("")})`);
7485
+ else console.warn("IfcWriter: FILE_NAME null-field pattern not found; author/organization/authorization left unpatched");
7486
+ if (this.#mvdViewDefinition.length > 0) if (VIEW_DEFINITION_RE.test(head)) head = head.replace(VIEW_DEFINITION_RE, `ViewDefinition [${this.#mvdViewDefinition}]`);
7487
+ else console.warn(`IfcWriter: FILE_DESCRIPTION ViewDefinition not found; MVD "${this.#mvdViewDefinition}" not declared`);
7488
+ const patchedHeadBytes = new TextEncoder().encode(head);
6788
7489
  const tail = bytes.subarray(HEADER_SCAN);
6789
7490
  const out = new Uint8Array(patchedHeadBytes.byteLength + tail.byteLength);
6790
7491
  out.set(patchedHeadBytes, 0);
@@ -7881,7 +8582,7 @@ function writeSlabGeometry(w, spec, geomSubContextId, parentPlacementId) {
7881
8582
  productDefinitionShapeId
7882
8583
  };
7883
8584
  }
7884
- function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
8585
+ function writeRoofGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
7885
8586
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
7886
8587
  const localPlacementId = w.nextId();
7887
8588
  w.writeLine({
@@ -7890,6 +8591,14 @@ function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
7890
8591
  PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
7891
8592
  RelativePlacement: w.ref(placement3DId)
7892
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
+ }
7893
8602
  const lengthM = toIfcLengthM(spec.length);
7894
8603
  const widthM = toIfcLengthM(spec.width);
7895
8604
  const thicknessM = toIfcLengthM(spec.thickness);
@@ -7957,7 +8666,8 @@ function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
7957
8666
  });
7958
8667
  return {
7959
8668
  localPlacementId,
7960
- productDefinitionShapeId
8669
+ productDefinitionShapeId,
8670
+ usedFallback: false
7961
8671
  };
7962
8672
  }
7963
8673
  function writeAxis2Placement2D$1(w) {
@@ -8022,7 +8732,7 @@ function writeProfile(w, profile) {
8022
8732
  OverallDepth: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, toIfcLengthM(profile.overallDepth)),
8023
8733
  WebThickness: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, toIfcLengthM(profile.webThickness)),
8024
8734
  FlangeThickness: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, toIfcLengthM(profile.flangeThickness)),
8025
- FilletRadius: null,
8735
+ FilletRadius: profile.filletRadius === void 0 ? null : w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, toIfcLengthM(profile.filletRadius)),
8026
8736
  FlangeEdgeRadius: null,
8027
8737
  FlangeSlope: null
8028
8738
  });
@@ -8660,68 +9370,6 @@ function writeRelContainedInSpatialStructure(w, guid, ownerHistoryId, relatingSt
8660
9370
  });
8661
9371
  }
8662
9372
  //#endregion
8663
- //#region src/elementFns/stairFns.ts
8664
- function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
8665
- const pts = [];
8666
- pts.push([
8667
- 0,
8668
- 0,
8669
- 0
8670
- ]);
8671
- let x = 0;
8672
- let z = 0;
8673
- for (let i = 0; i < numberOfRisers; i++) {
8674
- z += riserHeight;
8675
- pts.push([
8676
- x,
8677
- 0,
8678
- z
8679
- ]);
8680
- x += treadLength;
8681
- pts.push([
8682
- x,
8683
- 0,
8684
- z
8685
- ]);
8686
- }
8687
- pts.push([
8688
- x,
8689
- 0,
8690
- 0
8691
- ]);
8692
- return pts;
8693
- }
8694
- function stairFlightToSolid(spec) {
8695
- try {
8696
- var _usingCtx$4 = _usingCtx();
8697
- if (spec.width <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
8698
- if (spec.riserHeight <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
8699
- if (spec.treadLength <= 0) return (0, brepjs.err)(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
8700
- 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"));
8701
- const profileResult = (0, brepjs.polygon)(buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength));
8702
- if (!profileResult.ok) return (0, brepjs.err)(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
8703
- const solidResult = (0, brepjs.extrude)(_usingCtx$4.u(profileResult.value), [
8704
- 0,
8705
- spec.width,
8706
- 0
8707
- ]);
8708
- if (!solidResult.ok) return (0, brepjs.err)(fromBrepError(solidResult.error, "STAIR_FLIGHT_EXTRUDE_FAILED", "Failed to extrude stair flight silhouette"));
8709
- const solid = solidResult.value;
8710
- if (!(0, brepjs.isValidSolid)(solid)) {
8711
- solid[Symbol.dispose]();
8712
- return (0, brepjs.err)(geometryError("STAIR_FLIGHT_INVALID_SOLID", "Stair flight solid failed validity check"));
8713
- }
8714
- return (0, brepjs.ok)({
8715
- solid,
8716
- geometrySimplified: false
8717
- });
8718
- } catch (_) {
8719
- _usingCtx$4.e = _;
8720
- } finally {
8721
- _usingCtx$4.d();
8722
- }
8723
- }
8724
- //#endregion
8725
9373
  //#region src/elementFns/rampFns.ts
8726
9374
  function buildSilhouette(length, rise, thickness) {
8727
9375
  return [
@@ -9032,7 +9680,7 @@ function writeRampAssembly(w, spec, rampKey, ownerHistoryId, geomSubContextId, p
9032
9680
  }
9033
9681
  //#endregion
9034
9682
  //#region src/ifc-writer/railingWriter.ts
9035
- function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9683
+ function writeRailingGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
9036
9684
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
9037
9685
  const localPlacementId = w.nextId();
9038
9686
  w.writeLine({
@@ -9041,6 +9689,15 @@ function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9041
9689
  PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
9042
9690
  RelativePlacement: w.ref(placement3DId)
9043
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
+ }
9044
9701
  const thicknessM = toIfcLengthM(spec.thickness);
9045
9702
  const heightM = toIfcLengthM(spec.height);
9046
9703
  const lengthM = toIfcLengthM(spec.length);
@@ -9117,7 +9774,8 @@ function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9117
9774
  return {
9118
9775
  localPlacementId,
9119
9776
  productDefinitionShapeId,
9120
- bodyItemId: extrusionId
9777
+ bodyItemId: extrusionId,
9778
+ usedFallback: false
9121
9779
  };
9122
9780
  }
9123
9781
  function writeRailingEntity(w, guid, name, predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
@@ -11152,35 +11810,6 @@ function checkOpeningExists(issues, elementsById, openingId, code) {
11152
11810
  if (opening.category !== "OPENING") issues.push(issue("error", code === "VOID_OPENING_MISSING" ? "VOID_OPENING_WRONG_CATEGORY" : "FILL_OPENING_WRONG_CATEGORY", `References opening localId ${openingId}, expected OPENING but found ${opening.category}`, openingId, { actual: opening.category }));
11153
11811
  }
11154
11812
  //#endregion
11155
- //#region src/identity/ifcGuid.ts
11156
- var IFC_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$";
11157
- function newIfcGuid() {
11158
- const bytes = crypto.getRandomValues(new Uint8Array(16));
11159
- bytes[6] = (bytes[6] ?? 0) & 15 | 64;
11160
- bytes[8] = (bytes[8] ?? 0) & 63 | 128;
11161
- return encodeIfcGuid(bytes);
11162
- }
11163
- function isValidIfcGuid(s) {
11164
- if (s.length !== 22) return false;
11165
- for (const ch of s) if (!IFC_CHARS.includes(ch)) return false;
11166
- return true;
11167
- }
11168
- function encodeIfcGuid(bytes) {
11169
- let result = "";
11170
- let acc = 0;
11171
- let bits = 0;
11172
- for (const byte of bytes) {
11173
- acc = acc << 8 | byte;
11174
- bits += 8;
11175
- while (bits >= 6) {
11176
- bits -= 6;
11177
- result += IFC_CHARS[acc >> bits & 63] ?? "";
11178
- }
11179
- }
11180
- if (bits > 0) result += IFC_CHARS[acc << 6 - bits & 63] ?? "";
11181
- return result;
11182
- }
11183
- //#endregion
11184
11813
  //#region src/validation/schemaCheck.ts
11185
11814
  /**
11186
11815
  * EXPRESS/STEP self-validation gate.
@@ -11197,7 +11826,7 @@ function encodeIfcGuid(bytes) {
11197
11826
  async function checkSchema(bytes) {
11198
11827
  if (bytes.byteLength === 0) return appendIssue(emptyReport(), issue("error", "EMPTY_MODEL", "IFC byte buffer is empty; nothing to validate"));
11199
11828
  const api = new web_ifc.IfcAPI();
11200
- await api.Init();
11829
+ await initIfcApi(api);
11201
11830
  let modelId;
11202
11831
  try {
11203
11832
  modelId = api.OpenModel(bytes);
@@ -11291,7 +11920,7 @@ var KEY_ENTITY_TYPES = [
11291
11920
  */
11292
11921
  async function firstPassCounts(bytes) {
11293
11922
  const api = new web_ifc.IfcAPI();
11294
- await api.Init();
11923
+ await initIfcApi(api);
11295
11924
  const modelId = api.OpenModel(bytes);
11296
11925
  try {
11297
11926
  return collectCounts(api, modelId);
@@ -11305,7 +11934,7 @@ async function firstPassCounts(bytes) {
11305
11934
  */
11306
11935
  async function secondPassCounts(bytes) {
11307
11936
  const api = new web_ifc.IfcAPI();
11308
- await api.Init();
11937
+ await initIfcApi(api);
11309
11938
  const sourceModelId = api.OpenModel(bytes);
11310
11939
  let resaved;
11311
11940
  try {
@@ -11369,7 +11998,11 @@ async function checkRoundTrip(bytes) {
11369
11998
  async function toIfc(model, meta) {
11370
11999
  const project = model.getProject();
11371
12000
  if (!project) return (0, brepjs.err)(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
11372
- const writerResult = await IfcWriter.create(meta.mvdViewDefinition, meta.ifcSchema);
12001
+ const authorName = [meta.author?.givenName, meta.author?.familyName].filter((p) => Boolean(p)).join(" ");
12002
+ const writerResult = await IfcWriter.create(meta.mvdViewDefinition, meta.ifcSchema, {
12003
+ author: authorName,
12004
+ organization: meta.organizationName
12005
+ });
11373
12006
  if (!writerResult.ok) return writerResult;
11374
12007
  const w = writerResult.value;
11375
12008
  w.setModelScope(project.guid);
@@ -11513,7 +12146,8 @@ async function toIfc(model, meta) {
11513
12146
  for (const [i, roof] of roofs.entries()) {
11514
12147
  const containingId = findContainerOf(roof.localId, relationships);
11515
12148
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
11516
- 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.`);
11517
12151
  const roofExpressId = writeRoofEntity(w, roof.guid, `Roof ${i + 1}`, roof.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
11518
12152
  idMap.set(roof.localId, roofExpressId);
11519
12153
  placementMap.set(roof.localId, localPlacementId);
@@ -11585,14 +12219,15 @@ async function toIfc(model, meta) {
11585
12219
  for (const [i, railing] of railings.entries()) {
11586
12220
  const containingId = findContainerOf(railing.localId, relationships);
11587
12221
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
11588
- 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.`);
11589
12224
  const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
11590
12225
  idMap.set(railing.localId, railingExpressId);
11591
12226
  placementMap.set(railing.localId, localPlacementId);
11592
12227
  writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
11593
12228
  writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
11594
12229
  if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
11595
- applySurfaceStyle(w, model, railing.localId, bodyItemId);
12230
+ if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
11596
12231
  }
11597
12232
  for (const [i, covering] of coverings.entries()) {
11598
12233
  const containingId = findContainerOf(covering.localId, relationships);
@@ -12014,7 +12649,7 @@ var SpfReader = class SpfReader {
12014
12649
  let api;
12015
12650
  try {
12016
12651
  api = new web_ifc.IfcAPI();
12017
- await api.Init();
12652
+ await initIfcApi(api);
12018
12653
  } catch (e) {
12019
12654
  return (0, brepjs.err)(importError("OPEN_MODEL_FAILED", "Failed to initialize web-ifc", e));
12020
12655
  }
@@ -12120,120 +12755,6 @@ var SpfReader = class SpfReader {
12120
12755
  }
12121
12756
  };
12122
12757
  //#endregion
12123
- //#region src/import/placement.ts
12124
- /**
12125
- * Metres-per-file-unit length scale read from the IfcUnitAssignment's
12126
- * LENGTHUNIT. Multiply a file-unit length by this to get metres, then by 1000
12127
- * for brepjs millimetres. Returns 1.0 (assume metres) when no length unit is
12128
- * declared.
12129
- */
12130
- function readLengthScale(reader) {
12131
- const assignments = reader.getLinesOfType(web_ifc.IFCUNITASSIGNMENT);
12132
- for (const assignmentId of assignments) {
12133
- const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
12134
- for (const unitId of units) {
12135
- const scale = lengthScaleFromUnit(reader, unitId);
12136
- if (scale !== null) return scale;
12137
- }
12138
- }
12139
- return 1;
12140
- }
12141
- /**
12142
- * Radians-per-file-unit plane-angle scale read from the IfcUnitAssignment's
12143
- * PLANEANGLEUNIT (1 for RADIAN, ~0.0174533 for DEGREE). Defaults to 1 (the IFC
12144
- * default plane-angle unit is the radian).
12145
- */
12146
- function readPlaneAngleScale(reader) {
12147
- for (const assignmentId of reader.getLinesOfType(web_ifc.IFCUNITASSIGNMENT)) {
12148
- const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
12149
- for (const unitId of units) {
12150
- const s = planeAngleScaleFromUnit(reader, unitId);
12151
- if (s !== null) return s;
12152
- }
12153
- }
12154
- return 1;
12155
- }
12156
- function planeAngleScaleFromUnit(reader, unitId) {
12157
- const unit = reader.getLine(unitId);
12158
- if (unit === null) return null;
12159
- const type = reader.getLineType(unitId);
12160
- if (type === web_ifc.IFCSIUNIT) {
12161
- if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
12162
- if (enumValue(unit["Name"]) !== "RADIAN") return null;
12163
- return 1;
12164
- }
12165
- if (type === web_ifc.IFCCONVERSIONBASEDUNIT) {
12166
- if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
12167
- const measureId = refValue$2(unit["ConversionFactor"]);
12168
- if (measureId === null) return null;
12169
- const factor = numericValue(reader.getLine(measureId)?.["ValueComponent"]);
12170
- if (factor === null) return null;
12171
- return factor;
12172
- }
12173
- return null;
12174
- }
12175
- function lengthScaleFromUnit(reader, unitId) {
12176
- const unit = reader.getLine(unitId);
12177
- if (unit === null) return null;
12178
- const type = reader.getLineType(unitId);
12179
- if (type === web_ifc.IFCSIUNIT) {
12180
- if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
12181
- if (enumValue(unit["Name"]) !== "METRE") return null;
12182
- return siPrefixFactor(enumValue(unit["Prefix"]));
12183
- }
12184
- if (type === web_ifc.IFCCONVERSIONBASEDUNIT) {
12185
- if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
12186
- const measureId = refValue$2(unit["ConversionFactor"]);
12187
- if (measureId === null) return null;
12188
- const measure = reader.getLine(measureId);
12189
- const factor = numericValue(measure?.["ValueComponent"]);
12190
- if (factor === null) return null;
12191
- const baseId = refValue$2(measure?.["UnitComponent"]);
12192
- return factor * (baseId !== null ? lengthScaleFromUnit(reader, baseId) ?? 1 : 1);
12193
- }
12194
- return null;
12195
- }
12196
- function siPrefixFactor(prefix) {
12197
- switch (prefix) {
12198
- case null: return 1;
12199
- case "KILO": return 1e3;
12200
- case "HECTO": return 100;
12201
- case "DECA": return 10;
12202
- case "DECI": return .1;
12203
- case "CENTI": return .01;
12204
- case "MILLI": return .001;
12205
- case "MICRO": return 1e-6;
12206
- default: return 1;
12207
- }
12208
- }
12209
- function refValue$2(v) {
12210
- if (v === null || v === void 0) return null;
12211
- if (typeof v === "number") return v;
12212
- const value = v.value;
12213
- return typeof value === "number" ? value : null;
12214
- }
12215
- function asRefArray$1(v) {
12216
- if (!Array.isArray(v)) return [];
12217
- const out = [];
12218
- for (const item of v) {
12219
- const id = refValue$2(item);
12220
- if (id !== null) out.push(id);
12221
- }
12222
- return out;
12223
- }
12224
- function numericValue(v) {
12225
- if (typeof v === "number") return v;
12226
- if (v === null || v === void 0) return null;
12227
- const value = v.value;
12228
- return typeof value === "number" ? value : null;
12229
- }
12230
- function enumValue(v) {
12231
- if (typeof v === "string") return v;
12232
- if (v === null || v === void 0) return null;
12233
- const value = v.value;
12234
- return typeof value === "string" ? value : null;
12235
- }
12236
- //#endregion
12237
12758
  //#region src/import/spatialTree.ts
12238
12759
  var CATEGORY_BY_TYPE = new Map([
12239
12760
  [web_ifc.IFCPROJECT, "PROJECT"],
@@ -13859,6 +14380,7 @@ var RoofSpecSchema = object({
13859
14380
  fireRating: string().optional(),
13860
14381
  thermalTransmittance: number().positive().optional(),
13861
14382
  status: string().optional(),
14383
+ pitch: number().positive().max(89).optional(),
13862
14384
  materialLayers: array(MaterialLayerSchema).optional(),
13863
14385
  layerSetName: string().optional(),
13864
14386
  classification: ClassificationRefSchema.optional(),
@@ -14190,6 +14712,7 @@ var RailingSpecSchema = object({
14190
14712
  "HANDRAIL",
14191
14713
  "NOTDEFINED"
14192
14714
  ]).optional(),
14715
+ infill: _enum(["PANEL", "POSTED"]).optional(),
14193
14716
  materialName: string().min(1),
14194
14717
  isExternal: boolean().optional(),
14195
14718
  fireRating: string().optional(),
@@ -15907,10 +16430,12 @@ exports.parseSystemSpec = parseSystemSpec;
15907
16430
  exports.parseWallSpec = parseWallSpec;
15908
16431
  exports.parseWindowSpec = parseWindowSpec;
15909
16432
  exports.parseZoneSpec = parseZoneSpec;
16433
+ exports.placedSolids = placedSolids;
15910
16434
  exports.schemaSupports = schemaSupports;
15911
16435
  exports.serializeBcfFiles = serializeBcfFiles;
15912
16436
  exports.serializeCobieToCsv = serializeCobieToCsv;
15913
16437
  exports.serializeCobieToJson = serializeCobieToJson;
16438
+ exports.setIfcWasmLocateFile = setIfcWasmLocateFile;
15914
16439
  exports.specError = specError;
15915
16440
  exports.templateFor = templateFor;
15916
16441
  exports.toIfc = toIfc;