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.
@@ -1,8 +1,37 @@
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
+ //#region src/identity/ifcGuid.ts
5
+ var IFC_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$";
6
+ function newIfcGuid() {
7
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
8
+ bytes[6] = (bytes[6] ?? 0) & 15 | 64;
9
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
10
+ return encodeIfcGuid(bytes);
11
+ }
12
+ function isValidIfcGuid(s) {
13
+ if (s.length !== 22) return false;
14
+ if (!"0123".includes(s[0] ?? "")) return false;
15
+ for (const ch of s) if (!IFC_CHARS.includes(ch)) return false;
16
+ return true;
17
+ }
18
+ function encodeIfcGuid(bytes) {
19
+ let result = "";
20
+ let acc = 0;
21
+ let bits = 4;
22
+ for (const byte of bytes) {
23
+ acc = acc << 8 | byte;
24
+ bits += 8;
25
+ while (bits >= 6) {
26
+ bits -= 6;
27
+ result += IFC_CHARS[acc >> bits & 63] ?? "";
28
+ }
29
+ }
30
+ if (bits > 0) result += IFC_CHARS[acc << 6 - bits & 63] ?? "";
31
+ return result;
32
+ }
33
+ //#endregion
4
34
  //#region src/identity/guidDerivation.ts
5
- var IFC_CHARS$1 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$";
6
35
  var NAMESPACE = "brepjs-bim:v1";
7
36
  var FNV_OFFSET = 2166136261;
8
37
  var FNV_PRIME = 16777619;
@@ -36,7 +65,7 @@ function digest16(stableKey) {
36
65
  * keys yield distinct, format-valid (22-char) GlobalIds.
37
66
  */
38
67
  function deriveIfcGuidSync(stableKey) {
39
- return encodeIfcGuid$1(digest16(stableKey));
68
+ return encodeIfcGuid(digest16(stableKey));
40
69
  }
41
70
  /**
42
71
  * Async wrapper over {@link deriveIfcGuidSync} for callers that prefer a Promise
@@ -63,21 +92,6 @@ function makeRelKey(modelScope, kind, localId) {
63
92
  function makeLineKey(modelScope, expressId) {
64
93
  return `line:${modelScope}:${expressId}`;
65
94
  }
66
- function encodeIfcGuid$1(bytes) {
67
- let result = "";
68
- let acc = 0;
69
- let bits = 0;
70
- for (const byte of bytes) {
71
- acc = acc << 8 | byte;
72
- bits += 8;
73
- while (bits >= 6) {
74
- bits -= 6;
75
- result += IFC_CHARS$1[acc >> bits & 63] ?? "";
76
- }
77
- }
78
- if (bits > 0) result += IFC_CHARS$1[acc << 6 - bits & 63] ?? "";
79
- return result;
80
- }
81
95
  //#endregion
82
96
  //#region src/identity/localId.ts
83
97
  function makeLocalIdCounter(start = 1) {
@@ -228,7 +242,7 @@ function _usingCtx() {
228
242
  //#region src/elementFns/wallFns.ts
229
243
  function wallToSolid(spec) {
230
244
  try {
231
- var _usingCtx$18 = _usingCtx();
245
+ var _usingCtx$19 = _usingCtx();
232
246
  if (spec.length <= 0) return err(specError("WALL_ZERO_LENGTH", "Wall length must be positive"));
233
247
  if (spec.height <= 0) return err(specError("WALL_ZERO_HEIGHT", "Wall height must be positive"));
234
248
  if (spec.thickness <= 0) return err(specError("WALL_ZERO_THICKNESS", "Wall thickness must be positive"));
@@ -256,7 +270,7 @@ function wallToSolid(spec) {
256
270
  ]
257
271
  ]);
258
272
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "WALL_PROFILE_FAILED", "Failed to create wall profile"));
259
- const solidResult = extrude(_usingCtx$18.u(profileResult.value), [
273
+ const solidResult = extrude(_usingCtx$19.u(profileResult.value), [
260
274
  length,
261
275
  0,
262
276
  0
@@ -269,16 +283,16 @@ function wallToSolid(spec) {
269
283
  }
270
284
  return ok(solid);
271
285
  } catch (_) {
272
- _usingCtx$18.e = _;
286
+ _usingCtx$19.e = _;
273
287
  } finally {
274
- _usingCtx$18.d();
288
+ _usingCtx$19.d();
275
289
  }
276
290
  }
277
291
  //#endregion
278
292
  //#region src/elementFns/slabFns.ts
279
293
  function slabToSolid(spec) {
280
294
  try {
281
- var _usingCtx$17 = _usingCtx();
295
+ var _usingCtx$18 = _usingCtx();
282
296
  if (spec.length <= 0) return err(specError("SLAB_ZERO_LENGTH", "Slab length must be positive"));
283
297
  if (spec.width <= 0) return err(specError("SLAB_ZERO_WIDTH", "Slab width must be positive"));
284
298
  if (spec.thickness <= 0) return err(specError("SLAB_ZERO_THICKNESS", "Slab thickness must be positive"));
@@ -306,7 +320,7 @@ function slabToSolid(spec) {
306
320
  ]
307
321
  ]);
308
322
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SLAB_PROFILE_FAILED", "Failed to create slab profile"));
309
- const solidResult = extrude(_usingCtx$17.u(profileResult.value), [
323
+ const solidResult = extrude(_usingCtx$18.u(profileResult.value), [
310
324
  0,
311
325
  0,
312
326
  thickness
@@ -319,9 +333,9 @@ function slabToSolid(spec) {
319
333
  }
320
334
  return ok(solid);
321
335
  } catch (_) {
322
- _usingCtx$17.e = _;
336
+ _usingCtx$18.e = _;
323
337
  } finally {
324
- _usingCtx$17.d();
338
+ _usingCtx$18.d();
325
339
  }
326
340
  }
327
341
  //#endregion
@@ -4998,14 +5012,14 @@ function to3D(points) {
4998
5012
  }
4999
5013
  function extendedProfileToFace(profile) {
5000
5014
  try {
5001
- var _usingCtx$16 = _usingCtx();
5015
+ var _usingCtx$17 = _usingCtx();
5002
5016
  const invalid = validateProfile(profile);
5003
5017
  if (invalid !== null) return err(invalid);
5004
5018
  const outerResult = polygon(to3D(outerLoop(profile)));
5005
5019
  if (!outerResult.ok) return err(fromBrepError(outerResult.error, "PROFILE_FACE_FAILED", "Failed to build profile outer face"));
5006
5020
  const holes = holeLoops(profile);
5007
5021
  if (holes.length === 0) return ok(outerResult.value);
5008
- const outerFace = _usingCtx$16.u(outerResult.value);
5022
+ const outerFace = _usingCtx$17.u(outerResult.value);
5009
5023
  const holeFaces = [];
5010
5024
  const holeWires = [];
5011
5025
  const disposeHoleFaces = () => {
@@ -5030,9 +5044,9 @@ function extendedProfileToFace(profile) {
5030
5044
  disposeHoleFaces();
5031
5045
  return ok(faceWithHoles);
5032
5046
  } catch (_) {
5033
- _usingCtx$16.e = _;
5047
+ _usingCtx$17.e = _;
5034
5048
  } finally {
5035
- _usingCtx$16.d();
5049
+ _usingCtx$17.d();
5036
5050
  }
5037
5051
  }
5038
5052
  //#endregion
@@ -5070,7 +5084,8 @@ var CoreProfileSchema = discriminatedUnion("kind", [
5070
5084
  overallWidth: number().positive(),
5071
5085
  overallDepth: number().positive(),
5072
5086
  flangeThickness: number().positive(),
5073
- webThickness: number().positive()
5087
+ webThickness: number().positive(),
5088
+ filletRadius: number().positive().optional()
5074
5089
  })
5075
5090
  ]);
5076
5091
  var Pt2Schema = tuple([number(), number()]);
@@ -5202,6 +5217,11 @@ function parseProfile(input) {
5202
5217
  if (profile.kind === "I_BEAM") {
5203
5218
  if (2 * profile.flangeThickness >= profile.overallDepth) return err(specError("INVALID_PROFILE", "I-beam flangeThickness × 2 must be less than overallDepth"));
5204
5219
  if (profile.webThickness >= profile.overallWidth) return err(specError("INVALID_PROFILE", "I-beam webThickness must be less than overallWidth"));
5220
+ if (profile.filletRadius !== void 0) {
5221
+ const clearHeight = profile.overallDepth / 2 - profile.flangeThickness;
5222
+ const clearSpan = (profile.overallWidth - profile.webThickness) / 2;
5223
+ if (profile.filletRadius >= clearHeight || profile.filletRadius >= clearSpan) return err(specError("INVALID_PROFILE", "I-beam filletRadius must be smaller than the clear web height and the clear span beside the web"));
5224
+ }
5205
5225
  }
5206
5226
  return ok(profile);
5207
5227
  }
@@ -5212,8 +5232,42 @@ function profileCrossSectionArea(profile) {
5212
5232
  switch (profile.kind) {
5213
5233
  case "RECTANGULAR": return profile.width * profile.height;
5214
5234
  case "CIRCULAR": return Math.PI * profile.radius * profile.radius;
5215
- case "I_BEAM": return 2 * profile.overallWidth * profile.flangeThickness + (profile.overallDepth - 2 * profile.flangeThickness) * profile.webThickness;
5235
+ case "I_BEAM": {
5236
+ const flangeArea = 2 * profile.overallWidth * profile.flangeThickness;
5237
+ const webArea = (profile.overallDepth - 2 * profile.flangeThickness) * profile.webThickness;
5238
+ const r = profile.filletRadius ?? 0;
5239
+ const filletArea = 4 * r * r * (1 - Math.PI / 4);
5240
+ return flangeArea + webArea + filletArea;
5241
+ }
5242
+ }
5243
+ }
5244
+ var FILLET_SEGMENTS = 8;
5245
+ var FILLET_MIN_ANGLE = .001;
5246
+ function filletArc(prev, v, next, r) {
5247
+ const aLen = Math.hypot(prev[0] - v[0], prev[1] - v[1]);
5248
+ const bLen = Math.hypot(next[0] - v[0], next[1] - v[1]);
5249
+ const a = [(prev[0] - v[0]) / aLen, (prev[1] - v[1]) / aLen];
5250
+ const b = [(next[0] - v[0]) / bLen, (next[1] - v[1]) / bLen];
5251
+ const alpha = Math.acos(Math.max(-1, Math.min(1, a[0] * b[0] + a[1] * b[1])));
5252
+ if (alpha < FILLET_MIN_ANGLE || alpha > Math.PI - FILLET_MIN_ANGLE) return [[v[0], v[1]]];
5253
+ const setback = r / Math.tan(alpha / 2);
5254
+ const center = r / Math.sin(alpha / 2);
5255
+ const bisLen = Math.hypot(a[0] + b[0], a[1] + b[1]);
5256
+ const bis = [(a[0] + b[0]) / bisLen, (a[1] + b[1]) / bisLen];
5257
+ const cx = v[0] + bis[0] * center;
5258
+ const cy = v[1] + bis[1] * center;
5259
+ const p1 = [v[0] + a[0] * setback, v[1] + a[1] * setback];
5260
+ const p2 = [v[0] + b[0] * setback, v[1] + b[1] * setback];
5261
+ const a1 = Math.atan2(p1[1] - cy, p1[0] - cx);
5262
+ let delta = Math.atan2(p2[1] - cy, p2[0] - cx) - a1;
5263
+ while (delta <= -Math.PI) delta += 2 * Math.PI;
5264
+ while (delta > Math.PI) delta -= 2 * Math.PI;
5265
+ const out = [];
5266
+ for (let i = 0; i <= FILLET_SEGMENTS; i++) {
5267
+ const ang = a1 + delta * i / FILLET_SEGMENTS;
5268
+ out.push([cx + r * Math.cos(ang), cy + r * Math.sin(ang)]);
5216
5269
  }
5270
+ return out;
5217
5271
  }
5218
5272
  function profileToPolygon(profile, circleSegments = 32) {
5219
5273
  if (isExtendedProfile(profile)) return err(specError("EXTENDED_PROFILE_NO_POLYGON", `profileToPolygon: extended profile kind '${profile.kind}' has no single-polygon outline; use extendedProfileToFace()`));
@@ -5262,68 +5316,50 @@ function profileToPolygon(profile, circleSegments = 32) {
5262
5316
  const halfD = profile.overallDepth / 2;
5263
5317
  const halfWeb = profile.webThickness / 2;
5264
5318
  const flangeInnerY = halfD - profile.flangeThickness;
5265
- return ok([
5266
- [
5267
- -halfW,
5268
- -halfD,
5269
- 0
5270
- ],
5271
- [
5272
- halfW,
5273
- -halfD,
5274
- 0
5275
- ],
5276
- [
5277
- halfW,
5278
- -flangeInnerY,
5279
- 0
5280
- ],
5281
- [
5282
- halfWeb,
5283
- -flangeInnerY,
5284
- 0
5285
- ],
5286
- [
5287
- halfWeb,
5288
- flangeInnerY,
5289
- 0
5290
- ],
5291
- [
5292
- halfW,
5293
- flangeInnerY,
5294
- 0
5295
- ],
5296
- [
5297
- halfW,
5298
- halfD,
5299
- 0
5300
- ],
5301
- [
5302
- -halfW,
5303
- halfD,
5304
- 0
5305
- ],
5306
- [
5307
- -halfW,
5308
- flangeInnerY,
5309
- 0
5310
- ],
5311
- [
5312
- -halfWeb,
5313
- flangeInnerY,
5314
- 0
5315
- ],
5316
- [
5317
- -halfWeb,
5318
- -flangeInnerY,
5319
- 0
5320
- ],
5321
- [
5322
- -halfW,
5323
- -flangeInnerY,
5324
- 0
5325
- ]
5319
+ const v = [
5320
+ [-halfW, -halfD],
5321
+ [halfW, -halfD],
5322
+ [halfW, -flangeInnerY],
5323
+ [halfWeb, -flangeInnerY],
5324
+ [halfWeb, flangeInnerY],
5325
+ [halfW, flangeInnerY],
5326
+ [halfW, halfD],
5327
+ [-halfW, halfD],
5328
+ [-halfW, flangeInnerY],
5329
+ [-halfWeb, flangeInnerY],
5330
+ [-halfWeb, -flangeInnerY],
5331
+ [-halfW, -flangeInnerY]
5332
+ ];
5333
+ const r = profile.filletRadius ?? 0;
5334
+ const rootCorners = new Set([
5335
+ 3,
5336
+ 4,
5337
+ 9,
5338
+ 10
5326
5339
  ]);
5340
+ const pts = [];
5341
+ for (let i = 0; i < v.length; i++) {
5342
+ const cur = v[i];
5343
+ if (cur === void 0) continue;
5344
+ if (r > 0 && rootCorners.has(i)) {
5345
+ const prev = v[(i - 1 + v.length) % v.length];
5346
+ const next = v[(i + 1) % v.length];
5347
+ if (prev !== void 0 && next !== void 0) {
5348
+ for (const [ax, ay] of filletArc(prev, cur, next, r)) pts.push([
5349
+ ax,
5350
+ ay,
5351
+ 0
5352
+ ]);
5353
+ continue;
5354
+ }
5355
+ }
5356
+ pts.push([
5357
+ cur[0],
5358
+ cur[1],
5359
+ 0
5360
+ ]);
5361
+ }
5362
+ return ok(pts);
5327
5363
  }
5328
5364
  }
5329
5365
  }
@@ -5331,7 +5367,7 @@ function profileToPolygon(profile, circleSegments = 32) {
5331
5367
  //#region src/elementFns/beamFns.ts
5332
5368
  function beamToSolid(spec) {
5333
5369
  try {
5334
- var _usingCtx$15 = _usingCtx();
5370
+ var _usingCtx$16 = _usingCtx();
5335
5371
  if (spec.length <= 0) return err(specError("BEAM_ZERO_LENGTH", "Beam length must be positive"));
5336
5372
  if (isExtendedProfile(spec.profile)) try {
5337
5373
  var _usingCtx3 = _usingCtx();
@@ -5366,7 +5402,7 @@ function beamToSolid(spec) {
5366
5402
  py
5367
5403
  ]));
5368
5404
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "BEAM_PROFILE_FAILED", "Failed to create beam profile"));
5369
- const solidResult = extrude(_usingCtx$15.u(profileResult.value), [
5405
+ const solidResult = extrude(_usingCtx$16.u(profileResult.value), [
5370
5406
  spec.length,
5371
5407
  0,
5372
5408
  0
@@ -5379,16 +5415,16 @@ function beamToSolid(spec) {
5379
5415
  }
5380
5416
  return ok(solid);
5381
5417
  } catch (_) {
5382
- _usingCtx$15.e = _;
5418
+ _usingCtx$16.e = _;
5383
5419
  } finally {
5384
- _usingCtx$15.d();
5420
+ _usingCtx$16.d();
5385
5421
  }
5386
5422
  }
5387
5423
  //#endregion
5388
5424
  //#region src/elementFns/columnFns.ts
5389
5425
  function columnToSolid(spec) {
5390
5426
  try {
5391
- var _usingCtx$14 = _usingCtx();
5427
+ var _usingCtx$15 = _usingCtx();
5392
5428
  if (spec.height <= 0) return err(specError("COLUMN_ZERO_HEIGHT", "Column height must be positive"));
5393
5429
  if (isExtendedProfile(spec.profile)) try {
5394
5430
  var _usingCtx3 = _usingCtx();
@@ -5416,7 +5452,7 @@ function columnToSolid(spec) {
5416
5452
  const profilePts = profilePtsResult.value;
5417
5453
  const profileResult = polygon(profilePts);
5418
5454
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "COLUMN_PROFILE_FAILED", "Failed to create column profile"));
5419
- const solidResult = extrude(_usingCtx$14.u(profileResult.value), [
5455
+ const solidResult = extrude(_usingCtx$15.u(profileResult.value), [
5420
5456
  0,
5421
5457
  0,
5422
5458
  spec.height
@@ -5429,9 +5465,9 @@ function columnToSolid(spec) {
5429
5465
  }
5430
5466
  return ok(solid);
5431
5467
  } catch (_) {
5432
- _usingCtx$14.e = _;
5468
+ _usingCtx$15.e = _;
5433
5469
  } finally {
5434
- _usingCtx$14.d();
5470
+ _usingCtx$15.d();
5435
5471
  }
5436
5472
  }
5437
5473
  //#endregion
@@ -5439,7 +5475,7 @@ function columnToSolid(spec) {
5439
5475
  var EPSILON_MM$1 = 1;
5440
5476
  function openingToSolid(spec, wallThickness) {
5441
5477
  try {
5442
- var _usingCtx$13 = _usingCtx();
5478
+ var _usingCtx$14 = _usingCtx();
5443
5479
  if (spec.width <= 0) return err(specError("OPENING_ZERO_WIDTH", "Opening width must be positive"));
5444
5480
  if (spec.height <= 0) return err(specError("OPENING_ZERO_HEIGHT", "Opening height must be positive"));
5445
5481
  if (wallThickness <= 0) return err(specError("OPENING_ZERO_WALL_THICKNESS", "Wall thickness must be positive"));
@@ -5471,7 +5507,7 @@ function openingToSolid(spec, wallThickness) {
5471
5507
  ]
5472
5508
  ]);
5473
5509
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "OPENING_PROFILE_FAILED", "Failed to create opening profile"));
5474
- const solidResult = extrude(_usingCtx$13.u(profileResult.value), [
5510
+ const solidResult = extrude(_usingCtx$14.u(profileResult.value), [
5475
5511
  spec.width,
5476
5512
  0,
5477
5513
  0
@@ -5484,9 +5520,9 @@ function openingToSolid(spec, wallThickness) {
5484
5520
  }
5485
5521
  return ok(solid);
5486
5522
  } catch (_) {
5487
- _usingCtx$13.e = _;
5523
+ _usingCtx$14.e = _;
5488
5524
  } finally {
5489
- _usingCtx$13.d();
5525
+ _usingCtx$14.d();
5490
5526
  }
5491
5527
  }
5492
5528
  //#endregion
@@ -5494,7 +5530,7 @@ function openingToSolid(spec, wallThickness) {
5494
5530
  var EPSILON_MM = 1;
5495
5531
  function slabOpeningToSolid(spec, slabThickness) {
5496
5532
  try {
5497
- var _usingCtx$12 = _usingCtx();
5533
+ var _usingCtx$13 = _usingCtx();
5498
5534
  if (spec.sizeX <= 0) return err(specError("SLAB_OPENING_ZERO_SIZE_X", "Slab opening sizeX must be positive"));
5499
5535
  if (spec.sizeY <= 0) return err(specError("SLAB_OPENING_ZERO_SIZE_Y", "Slab opening sizeY must be positive"));
5500
5536
  if (slabThickness <= 0) return err(specError("SLAB_OPENING_ZERO_SLAB_THICKNESS", "Slab thickness must be positive"));
@@ -5526,7 +5562,7 @@ function slabOpeningToSolid(spec, slabThickness) {
5526
5562
  ]
5527
5563
  ]);
5528
5564
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SLAB_OPENING_PROFILE_FAILED", "Failed to create slab opening profile"));
5529
- const solidResult = extrude(_usingCtx$12.u(profileResult.value), [
5565
+ const solidResult = extrude(_usingCtx$13.u(profileResult.value), [
5530
5566
  0,
5531
5567
  0,
5532
5568
  slabThickness + 2 * EPSILON_MM
@@ -5539,16 +5575,16 @@ function slabOpeningToSolid(spec, slabThickness) {
5539
5575
  }
5540
5576
  return ok(solid);
5541
5577
  } catch (_) {
5542
- _usingCtx$12.e = _;
5578
+ _usingCtx$13.e = _;
5543
5579
  } finally {
5544
- _usingCtx$12.d();
5580
+ _usingCtx$13.d();
5545
5581
  }
5546
5582
  }
5547
5583
  //#endregion
5548
5584
  //#region src/elementFns/spaceFns.ts
5549
5585
  function spaceToSolid(spec) {
5550
5586
  try {
5551
- var _usingCtx$11 = _usingCtx();
5587
+ var _usingCtx$12 = _usingCtx();
5552
5588
  if (spec.length <= 0) return err(specError("SPACE_ZERO_LENGTH", "Space length must be positive"));
5553
5589
  if (spec.width <= 0) return err(specError("SPACE_ZERO_WIDTH", "Space width must be positive"));
5554
5590
  if (spec.height <= 0) return err(specError("SPACE_ZERO_HEIGHT", "Space height must be positive"));
@@ -5576,7 +5612,7 @@ function spaceToSolid(spec) {
5576
5612
  ]
5577
5613
  ]);
5578
5614
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "SPACE_PROFILE_FAILED", "Failed to create space footprint"));
5579
- const solidResult = extrude(_usingCtx$11.u(profileResult.value), [
5615
+ const solidResult = extrude(_usingCtx$12.u(profileResult.value), [
5580
5616
  0,
5581
5617
  0,
5582
5618
  height
@@ -5589,21 +5625,26 @@ function spaceToSolid(spec) {
5589
5625
  }
5590
5626
  return ok(solid);
5591
5627
  } catch (_) {
5592
- _usingCtx$11.e = _;
5628
+ _usingCtx$12.e = _;
5593
5629
  } finally {
5594
- _usingCtx$11.d();
5630
+ _usingCtx$12.d();
5595
5631
  }
5596
5632
  }
5597
5633
  //#endregion
5598
5634
  //#region src/elementFns/roofFns.ts
5599
- 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) {
5600
5644
  try {
5601
- var _usingCtx$10 = _usingCtx();
5602
- if (spec.length <= 0) return err(specError("ROOF_ZERO_LENGTH", "Roof length must be positive"));
5603
- if (spec.width <= 0) return err(specError("ROOF_ZERO_WIDTH", "Roof width must be positive"));
5604
- if (spec.thickness <= 0) return err(specError("ROOF_ZERO_THICKNESS", "Roof thickness must be positive"));
5645
+ var _usingCtx$11 = _usingCtx();
5605
5646
  const { length, width, thickness } = spec;
5606
- const profileResult = polygon([
5647
+ const face = polygon([
5607
5648
  [
5608
5649
  0,
5609
5650
  0,
@@ -5625,115 +5666,297 @@ function roofToSolid(spec) {
5625
5666
  0
5626
5667
  ]
5627
5668
  ]);
5628
- if (!profileResult.ok) return err(fromBrepError(profileResult.error, "ROOF_PROFILE_FAILED", "Failed to create roof profile"));
5629
- 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), [
5630
5671
  0,
5631
5672
  0,
5632
5673
  thickness
5633
5674
  ]);
5634
- if (!solidResult.ok) return err(fromBrepError(solidResult.error, "ROOF_EXTRUDE_FAILED", "Failed to extrude roof profile"));
5635
- const solid = solidResult.value;
5636
- if (!isValidSolid(solid)) {
5637
- solid[Symbol.dispose]();
5638
- return err(geometryError("ROOF_INVALID_SOLID", "Extruded roof solid failed validity check"));
5639
- }
5640
- 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");
5641
5677
  } catch (_) {
5642
- _usingCtx$10.e = _;
5678
+ _usingCtx$11.e = _;
5643
5679
  } finally {
5644
- _usingCtx$10.d();
5680
+ _usingCtx$11.d();
5645
5681
  }
5646
5682
  }
5647
- //#endregion
5648
- //#region src/elementFns/curtainWallFns.ts
5649
- function boxSolid(sizeX, sizeY, sizeZ) {
5683
+ function shedRoof(spec, pitch) {
5650
5684
  try {
5651
- var _usingCtx$9 = _usingCtx();
5652
- const profileResult = polygon([
5685
+ var _usingCtx3 = _usingCtx();
5686
+ const { length, width, thickness } = spec;
5687
+ const rise = width * Math.tan(pitch * DEG2RAD);
5688
+ const face = polygon([
5653
5689
  [
5654
5690
  0,
5655
5691
  0,
5656
5692
  0
5657
5693
  ],
5658
5694
  [
5659
- sizeX,
5660
5695
  0,
5696
+ width,
5661
5697
  0
5662
5698
  ],
5663
5699
  [
5664
- sizeX,
5665
- sizeY,
5666
- 0
5700
+ 0,
5701
+ width,
5702
+ thickness + rise
5667
5703
  ],
5668
5704
  [
5669
5705
  0,
5670
- sizeY,
5671
- 0
5706
+ 0,
5707
+ thickness
5672
5708
  ]
5673
5709
  ]);
5674
- if (!profileResult.ok) return err(fromBrepError(profileResult.error, "CURTAIN_WALL_PROFILE_FAILED", "Failed to create component profile"));
5675
- const solidResult = extrude(_usingCtx$9.u(profileResult.value), [
5676
- 0,
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,
5677
5713
  0,
5678
- sizeZ
5714
+ 0
5679
5715
  ]);
5680
- if (!solidResult.ok) return err(fromBrepError(solidResult.error, "CURTAIN_WALL_EXTRUDE_FAILED", "Failed to extrude component"));
5681
- const solid = solidResult.value;
5682
- if (!isValidSolid(solid)) {
5683
- solid[Symbol.dispose]();
5684
- return err(geometryError("CURTAIN_WALL_INVALID_SOLID", "Extruded curtain wall component failed validity check"));
5685
- }
5686
- return ok(solid);
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");
5687
5718
  } catch (_) {
5688
- _usingCtx$9.e = _;
5719
+ _usingCtx3.e = _;
5689
5720
  } finally {
5690
- _usingCtx$9.d();
5721
+ _usingCtx3.d();
5691
5722
  }
5692
5723
  }
5693
- function disposeComponents(components) {
5694
- for (const c of components) c.solid[Symbol.dispose]();
5695
- }
5696
- /**
5697
- * Decomposes a curtain wall spec into its panel (plate) and mullion (member)
5698
- * geometry. Mullions run along every vertical grid line (columns + 1 of them)
5699
- * and every horizontal grid line (rows + 1); panels fill the cells between
5700
- * adjacent mullions. The mullion section is centred on each grid line.
5701
- *
5702
- * Geometry is laid out in the wall's local frame: X across the wall, Z up, Y
5703
- * through the depth. Each component's local-origin solid is returned alongside
5704
- * its placement origin so the IFC writer can emit one IfcLocalPlacement per
5705
- * component.
5706
- */
5707
- function curtainWallToGrid(spec) {
5708
- const { width, height, columns, rows, panelThickness, mullionWidth, mullionDepth } = spec;
5709
- const cellWidth = width / columns;
5710
- const cellHeight = height / rows;
5711
- const halfMullion = mullionWidth / 2;
5712
- const panelWidth = cellWidth - mullionWidth;
5713
- const panelHeight = cellHeight - mullionWidth;
5714
- if (panelWidth <= 0 || panelHeight <= 0) return err(geometryError("CURTAIN_WALL_DEGENERATE_PANEL", "mullionWidth leaves no room for panels in the grid"));
5715
- const panels = [];
5716
- const mullions = [];
5717
- for (let c = 0; c < columns; c++) for (let r = 0; r < rows; r++) {
5718
- const solidResult = boxSolid(panelWidth, panelThickness, panelHeight);
5719
- if (!solidResult.ok) {
5720
- disposeComponents(panels);
5721
- disposeComponents(mullions);
5722
- return err(solidResult.error);
5723
- }
5724
- panels.push({
5725
- origin: [
5726
- c * cellWidth + halfMullion,
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
+ [
5727
5731
  0,
5728
- r * cellHeight + halfMullion
5729
- ],
5730
- size: [
5731
- panelWidth,
5732
- panelThickness,
5733
- panelHeight
5732
+ 0,
5733
+ 0
5734
5734
  ],
5735
- solid: solidResult.value
5736
- });
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);
5868
+ }
5869
+ }
5870
+ //#endregion
5871
+ //#region src/elementFns/curtainWallFns.ts
5872
+ function boxSolid(sizeX, sizeY, sizeZ) {
5873
+ try {
5874
+ var _usingCtx$10 = _usingCtx();
5875
+ const profileResult = polygon([
5876
+ [
5877
+ 0,
5878
+ 0,
5879
+ 0
5880
+ ],
5881
+ [
5882
+ sizeX,
5883
+ 0,
5884
+ 0
5885
+ ],
5886
+ [
5887
+ sizeX,
5888
+ sizeY,
5889
+ 0
5890
+ ],
5891
+ [
5892
+ 0,
5893
+ sizeY,
5894
+ 0
5895
+ ]
5896
+ ]);
5897
+ if (!profileResult.ok) return err(fromBrepError(profileResult.error, "CURTAIN_WALL_PROFILE_FAILED", "Failed to create component profile"));
5898
+ const solidResult = extrude(_usingCtx$10.u(profileResult.value), [
5899
+ 0,
5900
+ 0,
5901
+ sizeZ
5902
+ ]);
5903
+ if (!solidResult.ok) return err(fromBrepError(solidResult.error, "CURTAIN_WALL_EXTRUDE_FAILED", "Failed to extrude component"));
5904
+ const solid = solidResult.value;
5905
+ if (!isValidSolid(solid)) {
5906
+ solid[Symbol.dispose]();
5907
+ return err(geometryError("CURTAIN_WALL_INVALID_SOLID", "Extruded curtain wall component failed validity check"));
5908
+ }
5909
+ return ok(solid);
5910
+ } catch (_) {
5911
+ _usingCtx$10.e = _;
5912
+ } finally {
5913
+ _usingCtx$10.d();
5914
+ }
5915
+ }
5916
+ function disposeComponents(components) {
5917
+ for (const c of components) c.solid[Symbol.dispose]();
5918
+ }
5919
+ /**
5920
+ * Decomposes a curtain wall spec into its panel (plate) and mullion (member)
5921
+ * geometry. Mullions run along every vertical grid line (columns + 1 of them)
5922
+ * and every horizontal grid line (rows + 1); panels fill the cells between
5923
+ * adjacent mullions. The mullion section is centred on each grid line.
5924
+ *
5925
+ * Geometry is laid out in the wall's local frame: X across the wall, Z up, Y
5926
+ * through the depth. Each component's local-origin solid is returned alongside
5927
+ * its placement origin so the IFC writer can emit one IfcLocalPlacement per
5928
+ * component.
5929
+ */
5930
+ function curtainWallToGrid(spec) {
5931
+ const { width, height, columns, rows, panelThickness, mullionWidth, mullionDepth } = spec;
5932
+ const cellWidth = width / columns;
5933
+ const cellHeight = height / rows;
5934
+ const halfMullion = mullionWidth / 2;
5935
+ const panelWidth = cellWidth - mullionWidth;
5936
+ const panelHeight = cellHeight - mullionWidth;
5937
+ if (panelWidth <= 0 || panelHeight <= 0) return err(geometryError("CURTAIN_WALL_DEGENERATE_PANEL", "mullionWidth leaves no room for panels in the grid"));
5938
+ const panels = [];
5939
+ const mullions = [];
5940
+ for (let c = 0; c < columns; c++) for (let r = 0; r < rows; r++) {
5941
+ const solidResult = boxSolid(panelWidth, panelThickness, panelHeight);
5942
+ if (!solidResult.ok) {
5943
+ disposeComponents(panels);
5944
+ disposeComponents(mullions);
5945
+ return err(solidResult.error);
5946
+ }
5947
+ panels.push({
5948
+ origin: [
5949
+ c * cellWidth + halfMullion,
5950
+ 0,
5951
+ r * cellHeight + halfMullion
5952
+ ],
5953
+ size: [
5954
+ panelWidth,
5955
+ panelThickness,
5956
+ panelHeight
5957
+ ],
5958
+ solid: solidResult.value
5959
+ });
5737
5960
  }
5738
5961
  for (let c = 0; c <= columns; c++) {
5739
5962
  const solidResult = boxSolid(mullionWidth, mullionDepth, height);
@@ -5786,7 +6009,7 @@ function curtainWallToGrid(spec) {
5786
6009
  //#region src/elementFns/foundationFns.ts
5787
6010
  function footingToSolid(spec) {
5788
6011
  try {
5789
- var _usingCtx$8 = _usingCtx();
6012
+ var _usingCtx$9 = _usingCtx();
5790
6013
  if (spec.length <= 0) return err(specError("FOOTING_ZERO_LENGTH", "Footing length must be positive"));
5791
6014
  if (spec.width <= 0) return err(specError("FOOTING_ZERO_WIDTH", "Footing width must be positive"));
5792
6015
  if (spec.thickness <= 0) return err(specError("FOOTING_ZERO_THICKNESS", "Footing thickness must be positive"));
@@ -5814,7 +6037,7 @@ function footingToSolid(spec) {
5814
6037
  ]
5815
6038
  ]);
5816
6039
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "FOOTING_PROFILE_FAILED", "Failed to create footing profile"));
5817
- const solidResult = extrude(_usingCtx$8.u(profileResult.value), [
6040
+ const solidResult = extrude(_usingCtx$9.u(profileResult.value), [
5818
6041
  0,
5819
6042
  0,
5820
6043
  thickness
@@ -5827,9 +6050,9 @@ function footingToSolid(spec) {
5827
6050
  }
5828
6051
  return ok(solid);
5829
6052
  } catch (_) {
5830
- _usingCtx$8.e = _;
6053
+ _usingCtx$9.e = _;
5831
6054
  } finally {
5832
- _usingCtx$8.d();
6055
+ _usingCtx$9.d();
5833
6056
  }
5834
6057
  }
5835
6058
  function pileToSolid(spec) {
@@ -5882,12 +6105,9 @@ function pileToSolid(spec) {
5882
6105
  }
5883
6106
  //#endregion
5884
6107
  //#region src/elementFns/railingFns.ts
5885
- function railingToSolid(spec) {
6108
+ function panelRailing(spec) {
5886
6109
  try {
5887
- var _usingCtx$7 = _usingCtx();
5888
- if (spec.length <= 0) return err(specError("RAILING_ZERO_LENGTH", "Railing length must be positive"));
5889
- if (spec.height <= 0) return err(specError("RAILING_ZERO_HEIGHT", "Railing height must be positive"));
5890
- if (spec.thickness <= 0) return err(specError("RAILING_ZERO_THICKNESS", "Railing thickness must be positive"));
6110
+ var _usingCtx$8 = _usingCtx();
5891
6111
  const { length, height, thickness } = spec;
5892
6112
  const profileResult = polygon([
5893
6113
  [
@@ -5912,7 +6132,7 @@ function railingToSolid(spec) {
5912
6132
  ]
5913
6133
  ]);
5914
6134
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "RAILING_PROFILE_FAILED", "Failed to create railing profile"));
5915
- const solidResult = extrude(_usingCtx$7.u(profileResult.value), [
6135
+ const solidResult = extrude(_usingCtx$8.u(profileResult.value), [
5916
6136
  length,
5917
6137
  0,
5918
6138
  0
@@ -5925,16 +6145,72 @@ function railingToSolid(spec) {
5925
6145
  }
5926
6146
  return ok(solid);
5927
6147
  } catch (_) {
5928
- _usingCtx$7.e = _;
6148
+ _usingCtx$8.e = _;
5929
6149
  } finally {
5930
- _usingCtx$7.d();
6150
+ _usingCtx$8.d();
5931
6151
  }
5932
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"));
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);
6208
+ }
5933
6209
  //#endregion
5934
6210
  //#region src/elementFns/coveringFns.ts
5935
6211
  function coveringToSolid(spec) {
5936
6212
  try {
5937
- var _usingCtx$6 = _usingCtx();
6213
+ var _usingCtx$7 = _usingCtx();
5938
6214
  if (spec.length <= 0) return err(specError("COVERING_ZERO_LENGTH", "Covering length must be positive"));
5939
6215
  if (spec.width <= 0) return err(specError("COVERING_ZERO_WIDTH", "Covering width must be positive"));
5940
6216
  if (spec.thickness <= 0) return err(specError("COVERING_ZERO_THICKNESS", "Covering thickness must be positive"));
@@ -5962,7 +6238,7 @@ function coveringToSolid(spec) {
5962
6238
  ]
5963
6239
  ]);
5964
6240
  if (!profileResult.ok) return err(fromBrepError(profileResult.error, "COVERING_PROFILE_FAILED", "Failed to create covering profile"));
5965
- const solidResult = extrude(_usingCtx$6.u(profileResult.value), [
6241
+ const solidResult = extrude(_usingCtx$7.u(profileResult.value), [
5966
6242
  0,
5967
6243
  0,
5968
6244
  thickness
@@ -5975,9 +6251,9 @@ function coveringToSolid(spec) {
5975
6251
  }
5976
6252
  return ok(solid);
5977
6253
  } catch (_) {
5978
- _usingCtx$6.e = _;
6254
+ _usingCtx$7.e = _;
5979
6255
  } finally {
5980
- _usingCtx$6.d();
6256
+ _usingCtx$7.d();
5981
6257
  }
5982
6258
  }
5983
6259
  //#endregion
@@ -6407,17 +6683,17 @@ var BimModel = class {
6407
6683
  }
6408
6684
  #cutWallGeometry(wall, openingSpec) {
6409
6685
  try {
6410
- var _usingCtx$5 = _usingCtx();
6686
+ var _usingCtx$6 = _usingCtx();
6411
6687
  const toolResult = openingToSolid(openingSpec, wall.spec.thickness);
6412
6688
  if (!toolResult.ok) return err(toolResult.error);
6413
- const tool = _usingCtx$5.u(toolResult.value);
6689
+ const tool = _usingCtx$6.u(toolResult.value);
6414
6690
  const cutResult = cut(wall.geometry, tool);
6415
6691
  if (!cutResult.ok) return err(fromBrepError(cutResult.error, "WALL_CUT_FAILED", "Boolean cut of wall with opening failed"));
6416
6692
  return ok(cutResult.value);
6417
6693
  } catch (_) {
6418
- _usingCtx$5.e = _;
6694
+ _usingCtx$6.e = _;
6419
6695
  } finally {
6420
- _usingCtx$5.d();
6696
+ _usingCtx$6.d();
6421
6697
  }
6422
6698
  }
6423
6699
  #replaceWallGeometry(wall, newGeometry) {
@@ -6507,6 +6783,50 @@ var BimModel = class {
6507
6783
  getElement(id) {
6508
6784
  return this.#elements.get(id) ?? null;
6509
6785
  }
6786
+ /**
6787
+ * A serializable summary of the model's structure, rooted at the project and
6788
+ * walking the IFC spatial hierarchy (AGGREGATES: project → site → building →
6789
+ * storey) plus the elements contained in each storey (placeIn). Useful for a
6790
+ * read-only tree view of the model across a worker boundary.
6791
+ */
6792
+ toTreeSummary() {
6793
+ const aggregated = /* @__PURE__ */ new Map();
6794
+ const contained = /* @__PURE__ */ new Map();
6795
+ for (const rel of this.#relationships.values()) if (rel.kind === "AGGREGATES") {
6796
+ const list = aggregated.get(rel.relatingObject) ?? [];
6797
+ list.push(...rel.relatedObjects);
6798
+ aggregated.set(rel.relatingObject, list);
6799
+ } else if (rel.kind === "CONTAINED_IN") {
6800
+ const list = contained.get(rel.relatingStructure) ?? [];
6801
+ list.push(...rel.relatedElements);
6802
+ contained.set(rel.relatingStructure, list);
6803
+ }
6804
+ const labelFor = (el) => {
6805
+ const spec = el.spec;
6806
+ const base = typeof spec.name === "string" && spec.name.length > 0 ? spec.name : el.category;
6807
+ return el.category === "STOREY" && typeof spec.elevation === "number" ? `${base} (+${spec.elevation} mm)` : base;
6808
+ };
6809
+ const seen = /* @__PURE__ */ new Set();
6810
+ const build = (id) => {
6811
+ if (seen.has(id)) return null;
6812
+ seen.add(id);
6813
+ const el = this.#elements.get(id);
6814
+ if (el === void 0) return null;
6815
+ const children = [...aggregated.get(id) ?? [], ...contained.get(id) ?? []].map(build).filter((n) => n !== null);
6816
+ return {
6817
+ id,
6818
+ label: labelFor(el),
6819
+ category: el.category,
6820
+ children
6821
+ };
6822
+ };
6823
+ const root = this.#projectId !== null ? build(this.#projectId) : null;
6824
+ const countNodes = (node) => 1 + node.children.reduce((sum, c) => sum + countNodes(c), 0);
6825
+ return {
6826
+ root,
6827
+ elementCount: root ? countNodes(root) : 0
6828
+ };
6829
+ }
6510
6830
  getWalls() {
6511
6831
  const walls = [];
6512
6832
  for (const el of this.#elements.values()) if (el.category === "WALL") walls.push(el);
@@ -6610,18 +6930,365 @@ var BimModel = class {
6610
6930
  this.#elements.set(localId, el);
6611
6931
  return localId;
6612
6932
  }
6613
- #makeRel(fields) {
6614
- const localId = this.#counter.next();
6615
- const guid = deriveIfcGuidSync(makeRelKey(this.#modelScope, fields.kind, localId));
6616
- const rel = {
6617
- ...fields,
6618
- guid,
6619
- localId
6620
- };
6621
- this.#relationships.set(localId, rel);
6622
- return localId;
6933
+ #makeRel(fields) {
6934
+ const localId = this.#counter.next();
6935
+ const guid = deriveIfcGuidSync(makeRelKey(this.#modelScope, fields.kind, localId));
6936
+ const rel = {
6937
+ ...fields,
6938
+ guid,
6939
+ localId
6940
+ };
6941
+ this.#relationships.set(localId, rel);
6942
+ return localId;
6943
+ }
6944
+ };
6945
+ //#endregion
6946
+ //#region src/import/placement.ts
6947
+ /**
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.
6952
+ */
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;
6963
+ }
6964
+ /**
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([]);
6623
7290
  }
6624
- };
7291
+ }
6625
7292
  //#endregion
6626
7293
  //#region src/ifc-writer/schemaVersion.ts
6627
7294
  /**
@@ -6684,27 +7351,62 @@ function schemaSupports(schema, entityName) {
6684
7351
  return true;
6685
7352
  }
6686
7353
  //#endregion
7354
+ //#region src/ifcRuntime.ts
7355
+ var wasmLocateFile;
7356
+ /**
7357
+ * Override how web-ifc finds its `.wasm` file. Applied by every web-ifc entry
7358
+ * point in this package — IFC export ({@link toIfc}), import ({@link fromIfc})
7359
+ * and validation. Required when brepjs-bim is bundled into a worker that serves
7360
+ * the wasm itself; not needed in Node.
7361
+ */
7362
+ function setIfcWasmLocateFile(locate) {
7363
+ wasmLocateFile = locate;
7364
+ }
7365
+ /**
7366
+ * Initialize a web-ifc API instance the way this package always wants it: with
7367
+ * the host-provided wasm locator and forced single-threaded.
7368
+ *
7369
+ * Single-threaded matters in a cross-origin-isolated context (e.g. a page that
7370
+ * sets COOP/COEP for another WASM kernel): web-ifc would otherwise load its
7371
+ * pthread build and spawn a sub-Worker, which fails when brepjs-bim is itself
7372
+ * bundled inside a Web Worker. In Node the flag is a no-op (web-ifc is already
7373
+ * single-threaded there), and multithreading only speeds up parsing/geometry,
7374
+ * not the one-shot serialize/read this package does.
7375
+ */
7376
+ async function initIfcApi(api) {
7377
+ await api.Init(wasmLocateFile, true);
7378
+ }
7379
+ //#endregion
6687
7380
  //#region src/ifc-writer/ifcWriter.ts
6688
7381
  /** Default MVD ViewDefinition declared in the STEP FILE_DESCRIPTION header. */
6689
7382
  var DEFAULT_MVD_VIEW_DEFINITION = "ReferenceView_v1.2";
6690
7383
  var VIEW_DEFINITION_RE = /ViewDefinition \[[^\]]*\]/;
7384
+ var FILE_NAME_RE = /(FILE_NAME\('[^']*','[^']*',)(?:\$|\(\$\)),(?:\$|\(\$\)),('[^']*','[^']*'),\$\)/;
7385
+ /** STEP single-quoted string literal with embedded quotes doubled per ISO 10303-21. */
7386
+ function stepString(value) {
7387
+ return `'${value.replace(/'/g, "''")}'`;
7388
+ }
6691
7389
  var IfcWriter = class IfcWriter {
6692
7390
  #api;
6693
7391
  #modelId;
6694
7392
  #mvdViewDefinition;
7393
+ #author;
7394
+ #organization;
6695
7395
  #nextExpressId = 1;
6696
7396
  #closed = false;
6697
7397
  #modelScope = "";
6698
- constructor(api, modelId, mvdViewDefinition) {
7398
+ constructor(api, modelId, mvdViewDefinition, header) {
6699
7399
  this.#api = api;
6700
7400
  this.#modelId = modelId;
6701
7401
  this.#mvdViewDefinition = mvdViewDefinition;
7402
+ this.#author = header.author ?? "";
7403
+ this.#organization = header.organization ?? "";
6702
7404
  }
6703
- static async create(mvdViewDefinition = DEFAULT_MVD_VIEW_DEFINITION, ifcSchema = DEFAULT_IFC_SCHEMA) {
7405
+ static async create(mvdViewDefinition = DEFAULT_MVD_VIEW_DEFINITION, ifcSchema = DEFAULT_IFC_SCHEMA, header = {}) {
6704
7406
  try {
6705
7407
  const api = new IfcAPI();
6706
- await api.Init();
6707
- return ok(new IfcWriter(api, api.CreateModel({ schema: fileSchemaString(ifcSchema) }), mvdViewDefinition));
7408
+ await initIfcApi(api);
7409
+ return ok(new IfcWriter(api, api.CreateModel({ schema: fileSchemaString(ifcSchema) }), mvdViewDefinition, header));
6708
7410
  } catch (e) {
6709
7411
  return err(ifcError("IFC_INIT_FAILED", "Failed to initialize web-ifc", e));
6710
7412
  }
@@ -6738,7 +7440,7 @@ var IfcWriter = class IfcWriter {
6738
7440
  if (this.#closed) return err(ifcError("IFC_ALREADY_SAVED", "Model has already been saved and closed"));
6739
7441
  try {
6740
7442
  const bytes = this.#api.SaveModel(this.#modelId);
6741
- return ok(this.#patchMvd(bytes));
7443
+ return ok(this.#patchHeader(bytes));
6742
7444
  } catch (e) {
6743
7445
  return err(ifcError("IFC_SAVE_FAILED", "Failed to serialize IFC model", e));
6744
7446
  } finally {
@@ -6747,21 +7449,20 @@ var IfcWriter = class IfcWriter {
6747
7449
  }
6748
7450
  }
6749
7451
  /**
6750
- * Injects the declared MVD into the STEP FILE_DESCRIPTION header. web-ifc does
6751
- * not expose the header's ViewDefinition for configuration, so we rewrite the
6752
- * empty default in the ASCII header region. If the expected pattern is absent
6753
- * (e.g. a future web-ifc default change) the bytes are returned unchanged.
7452
+ * Rewrites the STEP header in the ASCII region web-ifc emits: declares the MVD
7453
+ * in FILE_DESCRIPTION and makes FILE_NAME's author/organization/authorization
7454
+ * spec-conformant (web-ifc leaves them as bare `$`). web-ifc exposes neither
7455
+ * for configuration. If an expected pattern is absent (e.g. a future web-ifc
7456
+ * default change) that part is skipped and the bytes returned unchanged.
6754
7457
  */
6755
- #patchMvd(bytes) {
6756
- if (this.#mvdViewDefinition.length === 0) return bytes;
7458
+ #patchHeader(bytes) {
6757
7459
  const HEADER_SCAN = Math.min(bytes.byteLength, 2048);
6758
- const head = new TextDecoder().decode(bytes.subarray(0, HEADER_SCAN));
6759
- if (!VIEW_DEFINITION_RE.test(head)) {
6760
- console.warn(`IfcWriter: FILE_DESCRIPTION ViewDefinition not found; MVD "${this.#mvdViewDefinition}" not declared`);
6761
- return bytes;
6762
- }
6763
- const patchedHead = head.replace(VIEW_DEFINITION_RE, `ViewDefinition [${this.#mvdViewDefinition}]`);
6764
- const patchedHeadBytes = new TextEncoder().encode(patchedHead);
7460
+ let head = new TextDecoder().decode(bytes.subarray(0, HEADER_SCAN));
7461
+ if (FILE_NAME_RE.test(head)) head = head.replace(FILE_NAME_RE, (_m, prefix, systems) => `${prefix}(${stepString(this.#author)}),(${stepString(this.#organization)}),${systems},${stepString("")})`);
7462
+ else console.warn("IfcWriter: FILE_NAME null-field pattern not found; author/organization/authorization left unpatched");
7463
+ if (this.#mvdViewDefinition.length > 0) if (VIEW_DEFINITION_RE.test(head)) head = head.replace(VIEW_DEFINITION_RE, `ViewDefinition [${this.#mvdViewDefinition}]`);
7464
+ else console.warn(`IfcWriter: FILE_DESCRIPTION ViewDefinition not found; MVD "${this.#mvdViewDefinition}" not declared`);
7465
+ const patchedHeadBytes = new TextEncoder().encode(head);
6765
7466
  const tail = bytes.subarray(HEADER_SCAN);
6766
7467
  const out = new Uint8Array(patchedHeadBytes.byteLength + tail.byteLength);
6767
7468
  out.set(patchedHeadBytes, 0);
@@ -7858,7 +8559,7 @@ function writeSlabGeometry(w, spec, geomSubContextId, parentPlacementId) {
7858
8559
  productDefinitionShapeId
7859
8560
  };
7860
8561
  }
7861
- function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
8562
+ function writeRoofGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
7862
8563
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
7863
8564
  const localPlacementId = w.nextId();
7864
8565
  w.writeLine({
@@ -7867,6 +8568,14 @@ function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
7867
8568
  PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
7868
8569
  RelativePlacement: w.ref(placement3DId)
7869
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
+ }
7870
8579
  const lengthM = toIfcLengthM(spec.length);
7871
8580
  const widthM = toIfcLengthM(spec.width);
7872
8581
  const thicknessM = toIfcLengthM(spec.thickness);
@@ -7934,7 +8643,8 @@ function writeRoofGeometry(w, spec, geomSubContextId, parentPlacementId) {
7934
8643
  });
7935
8644
  return {
7936
8645
  localPlacementId,
7937
- productDefinitionShapeId
8646
+ productDefinitionShapeId,
8647
+ usedFallback: false
7938
8648
  };
7939
8649
  }
7940
8650
  function writeAxis2Placement2D$1(w) {
@@ -7999,7 +8709,7 @@ function writeProfile(w, profile) {
7999
8709
  OverallDepth: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, toIfcLengthM(profile.overallDepth)),
8000
8710
  WebThickness: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, toIfcLengthM(profile.webThickness)),
8001
8711
  FlangeThickness: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, toIfcLengthM(profile.flangeThickness)),
8002
- FilletRadius: null,
8712
+ FilletRadius: profile.filletRadius === void 0 ? null : w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, toIfcLengthM(profile.filletRadius)),
8003
8713
  FlangeEdgeRadius: null,
8004
8714
  FlangeSlope: null
8005
8715
  });
@@ -8637,68 +9347,6 @@ function writeRelContainedInSpatialStructure(w, guid, ownerHistoryId, relatingSt
8637
9347
  });
8638
9348
  }
8639
9349
  //#endregion
8640
- //#region src/elementFns/stairFns.ts
8641
- function buildSilhouette$1(numberOfRisers, riserHeight, treadLength) {
8642
- const pts = [];
8643
- pts.push([
8644
- 0,
8645
- 0,
8646
- 0
8647
- ]);
8648
- let x = 0;
8649
- let z = 0;
8650
- for (let i = 0; i < numberOfRisers; i++) {
8651
- z += riserHeight;
8652
- pts.push([
8653
- x,
8654
- 0,
8655
- z
8656
- ]);
8657
- x += treadLength;
8658
- pts.push([
8659
- x,
8660
- 0,
8661
- z
8662
- ]);
8663
- }
8664
- pts.push([
8665
- x,
8666
- 0,
8667
- 0
8668
- ]);
8669
- return pts;
8670
- }
8671
- function stairFlightToSolid(spec) {
8672
- try {
8673
- var _usingCtx$4 = _usingCtx();
8674
- if (spec.width <= 0) return err(specError("STAIR_FLIGHT_ZERO_WIDTH", "Stair flight width must be positive"));
8675
- if (spec.riserHeight <= 0) return err(specError("STAIR_FLIGHT_ZERO_RISER", "Stair flight riserHeight must be positive"));
8676
- if (spec.treadLength <= 0) return err(specError("STAIR_FLIGHT_ZERO_TREAD", "Stair flight treadLength must be positive"));
8677
- if (!Number.isInteger(spec.numberOfRisers) || spec.numberOfRisers < 1) return err(specError("STAIR_FLIGHT_BAD_RISERS", "Stair flight numberOfRisers must be a positive integer"));
8678
- const profileResult = polygon(buildSilhouette$1(spec.numberOfRisers, spec.riserHeight, spec.treadLength));
8679
- if (!profileResult.ok) return err(fromBrepError(profileResult.error, "STAIR_FLIGHT_PROFILE_FAILED", "Failed to create stair flight silhouette profile"));
8680
- const solidResult = extrude(_usingCtx$4.u(profileResult.value), [
8681
- 0,
8682
- spec.width,
8683
- 0
8684
- ]);
8685
- if (!solidResult.ok) return err(fromBrepError(solidResult.error, "STAIR_FLIGHT_EXTRUDE_FAILED", "Failed to extrude stair flight silhouette"));
8686
- const solid = solidResult.value;
8687
- if (!isValidSolid(solid)) {
8688
- solid[Symbol.dispose]();
8689
- return err(geometryError("STAIR_FLIGHT_INVALID_SOLID", "Stair flight solid failed validity check"));
8690
- }
8691
- return ok({
8692
- solid,
8693
- geometrySimplified: false
8694
- });
8695
- } catch (_) {
8696
- _usingCtx$4.e = _;
8697
- } finally {
8698
- _usingCtx$4.d();
8699
- }
8700
- }
8701
- //#endregion
8702
9350
  //#region src/elementFns/rampFns.ts
8703
9351
  function buildSilhouette(length, rise, thickness) {
8704
9352
  return [
@@ -9009,7 +9657,7 @@ function writeRampAssembly(w, spec, rampKey, ownerHistoryId, geomSubContextId, p
9009
9657
  }
9010
9658
  //#endregion
9011
9659
  //#region src/ifc-writer/railingWriter.ts
9012
- function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9660
+ function writeRailingGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
9013
9661
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
9014
9662
  const localPlacementId = w.nextId();
9015
9663
  w.writeLine({
@@ -9018,6 +9666,15 @@ function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9018
9666
  PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
9019
9667
  RelativePlacement: w.ref(placement3DId)
9020
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
+ }
9021
9678
  const thicknessM = toIfcLengthM(spec.thickness);
9022
9679
  const heightM = toIfcLengthM(spec.height);
9023
9680
  const lengthM = toIfcLengthM(spec.length);
@@ -9094,7 +9751,8 @@ function writeRailingGeometry(w, spec, geomSubContextId, parentPlacementId) {
9094
9751
  return {
9095
9752
  localPlacementId,
9096
9753
  productDefinitionShapeId,
9097
- bodyItemId: extrusionId
9754
+ bodyItemId: extrusionId,
9755
+ usedFallback: false
9098
9756
  };
9099
9757
  }
9100
9758
  function writeRailingEntity(w, guid, name, predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
@@ -11129,35 +11787,6 @@ function checkOpeningExists(issues, elementsById, openingId, code) {
11129
11787
  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 }));
11130
11788
  }
11131
11789
  //#endregion
11132
- //#region src/identity/ifcGuid.ts
11133
- var IFC_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$";
11134
- function newIfcGuid() {
11135
- const bytes = crypto.getRandomValues(new Uint8Array(16));
11136
- bytes[6] = (bytes[6] ?? 0) & 15 | 64;
11137
- bytes[8] = (bytes[8] ?? 0) & 63 | 128;
11138
- return encodeIfcGuid(bytes);
11139
- }
11140
- function isValidIfcGuid(s) {
11141
- if (s.length !== 22) return false;
11142
- for (const ch of s) if (!IFC_CHARS.includes(ch)) return false;
11143
- return true;
11144
- }
11145
- function encodeIfcGuid(bytes) {
11146
- let result = "";
11147
- let acc = 0;
11148
- let bits = 0;
11149
- for (const byte of bytes) {
11150
- acc = acc << 8 | byte;
11151
- bits += 8;
11152
- while (bits >= 6) {
11153
- bits -= 6;
11154
- result += IFC_CHARS[acc >> bits & 63] ?? "";
11155
- }
11156
- }
11157
- if (bits > 0) result += IFC_CHARS[acc << 6 - bits & 63] ?? "";
11158
- return result;
11159
- }
11160
- //#endregion
11161
11790
  //#region src/validation/schemaCheck.ts
11162
11791
  /**
11163
11792
  * EXPRESS/STEP self-validation gate.
@@ -11174,7 +11803,7 @@ function encodeIfcGuid(bytes) {
11174
11803
  async function checkSchema(bytes) {
11175
11804
  if (bytes.byteLength === 0) return appendIssue(emptyReport(), issue("error", "EMPTY_MODEL", "IFC byte buffer is empty; nothing to validate"));
11176
11805
  const api = new WebIFC.IfcAPI();
11177
- await api.Init();
11806
+ await initIfcApi(api);
11178
11807
  let modelId;
11179
11808
  try {
11180
11809
  modelId = api.OpenModel(bytes);
@@ -11268,7 +11897,7 @@ var KEY_ENTITY_TYPES = [
11268
11897
  */
11269
11898
  async function firstPassCounts(bytes) {
11270
11899
  const api = new IfcAPI();
11271
- await api.Init();
11900
+ await initIfcApi(api);
11272
11901
  const modelId = api.OpenModel(bytes);
11273
11902
  try {
11274
11903
  return collectCounts(api, modelId);
@@ -11282,7 +11911,7 @@ async function firstPassCounts(bytes) {
11282
11911
  */
11283
11912
  async function secondPassCounts(bytes) {
11284
11913
  const api = new IfcAPI();
11285
- await api.Init();
11914
+ await initIfcApi(api);
11286
11915
  const sourceModelId = api.OpenModel(bytes);
11287
11916
  let resaved;
11288
11917
  try {
@@ -11346,7 +11975,11 @@ async function checkRoundTrip(bytes) {
11346
11975
  async function toIfc(model, meta) {
11347
11976
  const project = model.getProject();
11348
11977
  if (!project) return err(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
11349
- const writerResult = await IfcWriter.create(meta.mvdViewDefinition, meta.ifcSchema);
11978
+ const authorName = [meta.author?.givenName, meta.author?.familyName].filter((p) => Boolean(p)).join(" ");
11979
+ const writerResult = await IfcWriter.create(meta.mvdViewDefinition, meta.ifcSchema, {
11980
+ author: authorName,
11981
+ organization: meta.organizationName
11982
+ });
11350
11983
  if (!writerResult.ok) return writerResult;
11351
11984
  const w = writerResult.value;
11352
11985
  w.setModelScope(project.guid);
@@ -11490,7 +12123,8 @@ async function toIfc(model, meta) {
11490
12123
  for (const [i, roof] of roofs.entries()) {
11491
12124
  const containingId = findContainerOf(roof.localId, relationships);
11492
12125
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
11493
- 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.`);
11494
12128
  const roofExpressId = writeRoofEntity(w, roof.guid, `Roof ${i + 1}`, roof.spec.predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId);
11495
12129
  idMap.set(roof.localId, roofExpressId);
11496
12130
  placementMap.set(roof.localId, localPlacementId);
@@ -11562,14 +12196,15 @@ async function toIfc(model, meta) {
11562
12196
  for (const [i, railing] of railings.entries()) {
11563
12197
  const containingId = findContainerOf(railing.localId, relationships);
11564
12198
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
11565
- 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.`);
11566
12201
  const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
11567
12202
  idMap.set(railing.localId, railingExpressId);
11568
12203
  placementMap.set(railing.localId, localPlacementId);
11569
12204
  writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
11570
12205
  writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
11571
12206
  if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
11572
- applySurfaceStyle(w, model, railing.localId, bodyItemId);
12207
+ if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
11573
12208
  }
11574
12209
  for (const [i, covering] of coverings.entries()) {
11575
12210
  const containingId = findContainerOf(covering.localId, relationships);
@@ -11991,7 +12626,7 @@ var SpfReader = class SpfReader {
11991
12626
  let api;
11992
12627
  try {
11993
12628
  api = new IfcAPI();
11994
- await api.Init();
12629
+ await initIfcApi(api);
11995
12630
  } catch (e) {
11996
12631
  return err(importError("OPEN_MODEL_FAILED", "Failed to initialize web-ifc", e));
11997
12632
  }
@@ -12097,120 +12732,6 @@ var SpfReader = class SpfReader {
12097
12732
  }
12098
12733
  };
12099
12734
  //#endregion
12100
- //#region src/import/placement.ts
12101
- /**
12102
- * Metres-per-file-unit length scale read from the IfcUnitAssignment's
12103
- * LENGTHUNIT. Multiply a file-unit length by this to get metres, then by 1000
12104
- * for brepjs millimetres. Returns 1.0 (assume metres) when no length unit is
12105
- * declared.
12106
- */
12107
- function readLengthScale(reader) {
12108
- const assignments = reader.getLinesOfType(WebIFC.IFCUNITASSIGNMENT);
12109
- for (const assignmentId of assignments) {
12110
- const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
12111
- for (const unitId of units) {
12112
- const scale = lengthScaleFromUnit(reader, unitId);
12113
- if (scale !== null) return scale;
12114
- }
12115
- }
12116
- return 1;
12117
- }
12118
- /**
12119
- * Radians-per-file-unit plane-angle scale read from the IfcUnitAssignment's
12120
- * PLANEANGLEUNIT (1 for RADIAN, ~0.0174533 for DEGREE). Defaults to 1 (the IFC
12121
- * default plane-angle unit is the radian).
12122
- */
12123
- function readPlaneAngleScale(reader) {
12124
- for (const assignmentId of reader.getLinesOfType(WebIFC.IFCUNITASSIGNMENT)) {
12125
- const units = asRefArray$1(reader.getLine(assignmentId)?.["Units"]);
12126
- for (const unitId of units) {
12127
- const s = planeAngleScaleFromUnit(reader, unitId);
12128
- if (s !== null) return s;
12129
- }
12130
- }
12131
- return 1;
12132
- }
12133
- function planeAngleScaleFromUnit(reader, unitId) {
12134
- const unit = reader.getLine(unitId);
12135
- if (unit === null) return null;
12136
- const type = reader.getLineType(unitId);
12137
- if (type === WebIFC.IFCSIUNIT) {
12138
- if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
12139
- if (enumValue(unit["Name"]) !== "RADIAN") return null;
12140
- return 1;
12141
- }
12142
- if (type === WebIFC.IFCCONVERSIONBASEDUNIT) {
12143
- if (enumValue(unit["UnitType"]) !== "PLANEANGLEUNIT") return null;
12144
- const measureId = refValue$2(unit["ConversionFactor"]);
12145
- if (measureId === null) return null;
12146
- const factor = numericValue(reader.getLine(measureId)?.["ValueComponent"]);
12147
- if (factor === null) return null;
12148
- return factor;
12149
- }
12150
- return null;
12151
- }
12152
- function lengthScaleFromUnit(reader, unitId) {
12153
- const unit = reader.getLine(unitId);
12154
- if (unit === null) return null;
12155
- const type = reader.getLineType(unitId);
12156
- if (type === WebIFC.IFCSIUNIT) {
12157
- if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
12158
- if (enumValue(unit["Name"]) !== "METRE") return null;
12159
- return siPrefixFactor(enumValue(unit["Prefix"]));
12160
- }
12161
- if (type === WebIFC.IFCCONVERSIONBASEDUNIT) {
12162
- if (enumValue(unit["UnitType"]) !== "LENGTHUNIT") return null;
12163
- const measureId = refValue$2(unit["ConversionFactor"]);
12164
- if (measureId === null) return null;
12165
- const measure = reader.getLine(measureId);
12166
- const factor = numericValue(measure?.["ValueComponent"]);
12167
- if (factor === null) return null;
12168
- const baseId = refValue$2(measure?.["UnitComponent"]);
12169
- return factor * (baseId !== null ? lengthScaleFromUnit(reader, baseId) ?? 1 : 1);
12170
- }
12171
- return null;
12172
- }
12173
- function siPrefixFactor(prefix) {
12174
- switch (prefix) {
12175
- case null: return 1;
12176
- case "KILO": return 1e3;
12177
- case "HECTO": return 100;
12178
- case "DECA": return 10;
12179
- case "DECI": return .1;
12180
- case "CENTI": return .01;
12181
- case "MILLI": return .001;
12182
- case "MICRO": return 1e-6;
12183
- default: return 1;
12184
- }
12185
- }
12186
- function refValue$2(v) {
12187
- if (v === null || v === void 0) return null;
12188
- if (typeof v === "number") return v;
12189
- const value = v.value;
12190
- return typeof value === "number" ? value : null;
12191
- }
12192
- function asRefArray$1(v) {
12193
- if (!Array.isArray(v)) return [];
12194
- const out = [];
12195
- for (const item of v) {
12196
- const id = refValue$2(item);
12197
- if (id !== null) out.push(id);
12198
- }
12199
- return out;
12200
- }
12201
- function numericValue(v) {
12202
- if (typeof v === "number") return v;
12203
- if (v === null || v === void 0) return null;
12204
- const value = v.value;
12205
- return typeof value === "number" ? value : null;
12206
- }
12207
- function enumValue(v) {
12208
- if (typeof v === "string") return v;
12209
- if (v === null || v === void 0) return null;
12210
- const value = v.value;
12211
- return typeof value === "string" ? value : null;
12212
- }
12213
- //#endregion
12214
12735
  //#region src/import/spatialTree.ts
12215
12736
  var CATEGORY_BY_TYPE = new Map([
12216
12737
  [WebIFC.IFCPROJECT, "PROJECT"],
@@ -13836,6 +14357,7 @@ var RoofSpecSchema = object({
13836
14357
  fireRating: string().optional(),
13837
14358
  thermalTransmittance: number().positive().optional(),
13838
14359
  status: string().optional(),
14360
+ pitch: number().positive().max(89).optional(),
13839
14361
  materialLayers: array(MaterialLayerSchema).optional(),
13840
14362
  layerSetName: string().optional(),
13841
14363
  classification: ClassificationRefSchema.optional(),
@@ -14167,6 +14689,7 @@ var RailingSpecSchema = object({
14167
14689
  "HANDRAIL",
14168
14690
  "NOTDEFINED"
14169
14691
  ]).optional(),
14692
+ infill: _enum(["PANEL", "POSTED"]).optional(),
14170
14693
  materialName: string().min(1),
14171
14694
  isExternal: boolean().optional(),
14172
14695
  fireRating: string().optional(),
@@ -15819,4 +16342,4 @@ function assignNum(target, key, value) {
15819
16342
  if (value !== void 0) target[key] = Number(value);
15820
16343
  }
15821
16344
  //#endregion
15822
- 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, 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 };