brepjs-bim 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, cut, err, extrude, fuse, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, validSolid } from "brepjs";
1
+ import { addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, cut, err, extrude, fuse, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, translate, validSolid } from "brepjs";
2
2
  import * as WebIFC from "web-ifc";
3
3
  import { Handle, IfcAPI } from "web-ifc";
4
4
  //#region src/identity/ifcGuid.ts
@@ -6297,15 +6297,17 @@ var BimModel = class {
6297
6297
  #projectId = null;
6298
6298
  #modelScope = "";
6299
6299
  #usedStableKeys = /* @__PURE__ */ new Set();
6300
- init(spec) {
6300
+ init(spec, options) {
6301
6301
  if (this.#projectId !== null) return err(specError("DUPLICATE_PROJECT", "BimModel.init() called twice — only one project per model"));
6302
6302
  this.#modelScope = spec.projectId ?? `${spec.name}::${spec.description ?? ""}`;
6303
- const id = this.#makeElement("PROJECT", spec, null);
6303
+ const keyCheck = this.#checkStableKey(options);
6304
+ if (!keyCheck.ok) return keyCheck;
6305
+ const id = this.#makeElement("PROJECT", spec, null, options?.stableKey);
6304
6306
  this.#projectId = id;
6305
6307
  return ok(id);
6306
6308
  }
6307
6309
  [Symbol.dispose]() {
6308
- for (const el of this.#elements.values()) if (el.category === "WALL" || el.category === "SLAB" || el.category === "BEAM" || el.category === "COLUMN" || el.category === "PROXY" || el.category === "SPACE" || el.category === "ROOF" || el.category === "FOOTING" || el.category === "PILE" || el.category === "RAILING" || el.category === "COVERING") el.geometry[Symbol.dispose]();
6310
+ for (const el of this.#elements.values()) if (el.category === "WALL" || el.category === "SLAB" || el.category === "BEAM" || el.category === "COLUMN" || el.category === "PROXY" || el.category === "EARTHWORKS_FILL" || el.category === "SPACE" || el.category === "ROOF" || el.category === "FOOTING" || el.category === "PILE" || el.category === "RAILING" || el.category === "COVERING") el.geometry[Symbol.dispose]();
6309
6311
  else if (el.category === "CURTAIN_WALL") {
6310
6312
  for (const panel of el.geometry.panels) panel.solid[Symbol.dispose]();
6311
6313
  for (const mullion of el.geometry.mullions) mullion.solid[Symbol.dispose]();
@@ -6316,6 +6318,26 @@ var BimModel = class {
6316
6318
  if (!keyCheck.ok) return keyCheck;
6317
6319
  return ok(this.#makeElement("SITE", spec, null, options?.stableKey));
6318
6320
  }
6321
+ addBridge(spec, options) {
6322
+ const keyCheck = this.#checkStableKey(options);
6323
+ if (!keyCheck.ok) return keyCheck;
6324
+ return ok(this.#makeElement("BRIDGE", spec, null, options?.stableKey));
6325
+ }
6326
+ addBridgePart(spec, options) {
6327
+ const keyCheck = this.#checkStableKey(options);
6328
+ if (!keyCheck.ok) return keyCheck;
6329
+ return ok(this.#makeElement("BRIDGE_PART", spec, null, options?.stableKey));
6330
+ }
6331
+ /** Adds a typed IfcEarthworksFill body. Ownership of `spec.solid` transfers
6332
+ * to the model only when this call succeeds. */
6333
+ addEarthworksFill(spec, options) {
6334
+ const keyCheck = this.#checkStableKey(options);
6335
+ if (!keyCheck.ok) return keyCheck;
6336
+ if (spec.solid === null || spec.solid === void 0) return err(specError("EARTHWORKS_FILL_NO_GEOMETRY", "EarthworksFillSpec.solid is required"));
6337
+ const id = this.#makeElement("EARTHWORKS_FILL", spec, spec.solid, options?.stableKey);
6338
+ this.#associateMaterial(id, spec);
6339
+ return ok(id);
6340
+ }
6319
6341
  addBuilding(spec, options) {
6320
6342
  const keyCheck = this.#checkStableKey(options);
6321
6343
  if (!keyCheck.ok) return keyCheck;
@@ -6935,6 +6957,21 @@ var BimModel = class {
6935
6957
  for (const el of this.#elements.values()) if (el.category === "BEAM") beams.push(el);
6936
6958
  return beams;
6937
6959
  }
6960
+ getBridges() {
6961
+ const bridges = [];
6962
+ for (const el of this.#elements.values()) if (el.category === "BRIDGE") bridges.push(el);
6963
+ return bridges;
6964
+ }
6965
+ getBridgeParts() {
6966
+ const parts = [];
6967
+ for (const el of this.#elements.values()) if (el.category === "BRIDGE_PART") parts.push(el);
6968
+ return parts;
6969
+ }
6970
+ getEarthworksFills() {
6971
+ const fills = [];
6972
+ for (const el of this.#elements.values()) if (el.category === "EARTHWORKS_FILL") fills.push(el);
6973
+ return fills;
6974
+ }
6938
6975
  getColumns() {
6939
6976
  const columns = [];
6940
6977
  for (const el of this.#elements.values()) if (el.category === "COLUMN") columns.push(el);
@@ -7359,23 +7396,38 @@ function place(solid, frame) {
7359
7396
  function disposeAll(solids) {
7360
7397
  for (const s of solids) s[Symbol.dispose]();
7361
7398
  }
7399
+ function placeWithinParent(solid, localFrame, parentFrame) {
7400
+ try {
7401
+ var _usingCtx$3 = _usingCtx();
7402
+ const local = place(solid, localFrame);
7403
+ if (!local.ok || parentFrame === void 0) return local;
7404
+ return place(_usingCtx$3.u(local.value), parentFrame);
7405
+ } catch (_) {
7406
+ _usingCtx$3.e = _;
7407
+ } finally {
7408
+ _usingCtx$3.d();
7409
+ }
7410
+ }
7362
7411
  /**
7363
- * Returns each element's geometry transformed to its world placement, as fresh
7364
- * caller-owned solids, wrapped in a `Result` (Layer-2 code prefers `Result` over
7365
- * throwing). **Dispose the returned solids** (e.g. via `using` / `[Symbol.dispose]`)
7366
- * when you own their lifetime — they are independent of the model
7412
+ * Returns each element's geometry transformed to its element-local placement,
7413
+ * and optionally through the cumulative frame of its containing spatial
7414
+ * structure, as fresh caller-owned solids. Pass `parentFrame` to obtain world
7415
+ * coordinates for elements beneath a placed Site, Bridge, Bridge Part, or
7416
+ * other spatial structure. **Dispose the returned solids** (e.g. via `using` /
7417
+ * `[Symbol.dispose]`) when you own their lifetime — they are independent of the model
7367
7418
  * (`BimModel[Symbol.dispose]` frees only the stored, unplaced `.geometry`). On any
7368
7419
  * failure the solids already built for this call are disposed before the error is
7369
7420
  * returned, so no partial array is leaked.
7370
7421
  *
7371
7422
  * Stairs and ramps carry no element solid (`.geometry` is null), so flight
7372
7423
  * solids are built from `spec.flights` and placed per flight. Curtain walls
7373
- * return placed panels + mullions. Proxies are authored in world coordinates
7374
- * (no placement frame), so their solid is returned as a fresh identity-placed
7375
- * copy. Elements with no solid geometry (doors/windows/groups/spatial) return
7376
- * an empty array.
7424
+ * return placed panels + mullions. Proxy and Earthworks Fill bodies are stored
7425
+ * relative to their containing spatial structure, so their body is copied and
7426
+ * then transformed by `parentFrame` when supplied. Elements with no solid geometry
7427
+ * (doors/windows/groups/spatial) return an empty array.
7377
7428
  */
7378
- function placedSolids(el) {
7429
+ function placedSolids(el, options = {}) {
7430
+ const parentFrame = options.parentFrame;
7379
7431
  switch (el.category) {
7380
7432
  case "WALL":
7381
7433
  case "SLAB":
@@ -7387,56 +7439,57 @@ function placedSolids(el) {
7387
7439
  case "PILE":
7388
7440
  case "COVERING":
7389
7441
  case "RAILING": {
7390
- const placed = place(el.geometry, el.spec);
7442
+ const placed = placeWithinParent(el.geometry, el.spec, parentFrame);
7391
7443
  if (!placed.ok) return placed;
7392
7444
  return ok([placed.value]);
7393
7445
  }
7394
7446
  case "STAIR": {
7395
7447
  const out = [];
7396
7448
  for (const flight of el.spec.flights) try {
7397
- var _usingCtx$3 = _usingCtx();
7449
+ var _usingCtx3 = _usingCtx();
7398
7450
  const built = stairFlightToSolid(flight);
7399
7451
  if (!built.ok) {
7400
7452
  disposeAll(out);
7401
7453
  return err(built.error);
7402
7454
  }
7403
- const placed = place(_usingCtx$3.u(built.value.solid), flight);
7455
+ const placed = placeWithinParent(_usingCtx3.u(built.value.solid), flight, parentFrame);
7404
7456
  if (!placed.ok) {
7405
7457
  disposeAll(out);
7406
7458
  return placed;
7407
7459
  }
7408
7460
  out.push(placed.value);
7409
7461
  } catch (_) {
7410
- _usingCtx$3.e = _;
7462
+ _usingCtx3.e = _;
7411
7463
  } finally {
7412
- _usingCtx$3.d();
7464
+ _usingCtx3.d();
7413
7465
  }
7414
7466
  return ok(out);
7415
7467
  }
7416
7468
  case "RAMP": {
7417
7469
  const out = [];
7418
7470
  for (const flight of el.spec.flights) try {
7419
- var _usingCtx3 = _usingCtx();
7471
+ var _usingCtx4 = _usingCtx();
7420
7472
  const built = rampFlightToSolid(flight);
7421
7473
  if (!built.ok) {
7422
7474
  disposeAll(out);
7423
7475
  return err(built.error);
7424
7476
  }
7425
- const placed = place(_usingCtx3.u(built.value.solid), flight);
7477
+ const placed = placeWithinParent(_usingCtx4.u(built.value.solid), flight, parentFrame);
7426
7478
  if (!placed.ok) {
7427
7479
  disposeAll(out);
7428
7480
  return placed;
7429
7481
  }
7430
7482
  out.push(placed.value);
7431
7483
  } catch (_) {
7432
- _usingCtx3.e = _;
7484
+ _usingCtx4.e = _;
7433
7485
  } finally {
7434
- _usingCtx3.d();
7486
+ _usingCtx4.d();
7435
7487
  }
7436
7488
  return ok(out);
7437
7489
  }
7438
- case "PROXY": {
7439
- const placed = place(el.geometry, {
7490
+ case "PROXY":
7491
+ case "EARTHWORKS_FILL": {
7492
+ const placed = placeWithinParent(el.geometry, {
7440
7493
  origin: [
7441
7494
  0,
7442
7495
  0,
@@ -7452,14 +7505,14 @@ function placedSolids(el) {
7452
7505
  0,
7453
7506
  1
7454
7507
  ]
7455
- });
7508
+ }, parentFrame);
7456
7509
  if (!placed.ok) return placed;
7457
7510
  return ok([placed.value]);
7458
7511
  }
7459
7512
  case "CURTAIN_WALL": {
7460
7513
  const out = [];
7461
7514
  for (const c of [...el.geometry.panels, ...el.geometry.mullions]) try {
7462
- var _usingCtx4 = _usingCtx();
7515
+ var _usingCtx5 = _usingCtx();
7463
7516
  const componentLocal = place(c.solid, {
7464
7517
  origin: c.origin,
7465
7518
  axisX: [
@@ -7477,16 +7530,16 @@ function placedSolids(el) {
7477
7530
  disposeAll(out);
7478
7531
  return componentLocal;
7479
7532
  }
7480
- const placed = place(_usingCtx4.u(componentLocal.value), el.spec);
7533
+ const placed = placeWithinParent(_usingCtx5.u(componentLocal.value), el.spec, parentFrame);
7481
7534
  if (!placed.ok) {
7482
7535
  disposeAll(out);
7483
7536
  return placed;
7484
7537
  }
7485
7538
  out.push(placed.value);
7486
7539
  } catch (_) {
7487
- _usingCtx4.e = _;
7540
+ _usingCtx5.e = _;
7488
7541
  } finally {
7489
- _usingCtx4.d();
7542
+ _usingCtx5.d();
7490
7543
  }
7491
7544
  return ok(out);
7492
7545
  }
@@ -7541,6 +7594,8 @@ var IFC4X3_ONLY_ENTITIES = /* @__PURE__ */ new Set([
7541
7594
  "IfcRoad",
7542
7595
  "IfcRailway",
7543
7596
  "IfcBridge",
7597
+ "IfcBridgePart",
7598
+ "IfcEarthworksFill",
7544
7599
  "IfcMarineFacility"
7545
7600
  ]);
7546
7601
  /**
@@ -7999,34 +8054,23 @@ function writeProject$1(w, guid, name, ownerHistoryId, unitAssignmentId, geomCon
7999
8054
  });
8000
8055
  return id;
8001
8056
  }
8002
- function writeSite(w, guid, name, ownerHistoryId) {
8003
- const placement3DId = writeAxis2Placement3D(w, [
8004
- 0,
8005
- 0,
8006
- 0
8007
- ]);
8008
- const localPlacementId = w.nextId();
8009
- w.writeLine({
8010
- expressID: localPlacementId,
8011
- type: WebIFC.IFCLOCALPLACEMENT,
8012
- PlacementRelTo: null,
8013
- RelativePlacement: w.ref(placement3DId)
8014
- });
8057
+ function writeSite(w, guid, spec, ownerHistoryId, parentPlacementId = null) {
8058
+ const localPlacementId = writeSpatialLocalPlacement(w, spec, parentPlacementId);
8015
8059
  const entityId = w.nextId();
8016
8060
  w.writeLine({
8017
8061
  expressID: entityId,
8018
8062
  type: WebIFC.IFCSITE,
8019
8063
  GlobalId: w.mkType(WebIFC.IFCGLOBALLYUNIQUEID, guid),
8020
8064
  OwnerHistory: w.ref(ownerHistoryId),
8021
- Name: w.mkType(WebIFC.IFCLABEL, name),
8022
- Description: null,
8065
+ Name: w.mkType(WebIFC.IFCLABEL, spec.name),
8066
+ Description: spec.description ? w.mkType(WebIFC.IFCTEXT, spec.description) : null,
8023
8067
  ObjectType: null,
8024
8068
  ObjectPlacement: w.ref(localPlacementId),
8025
8069
  Representation: null,
8026
8070
  LongName: null,
8027
8071
  CompositionType: {
8028
8072
  type: 3,
8029
- value: "ELEMENT"
8073
+ value: spec.compositionType ?? "ELEMENT"
8030
8074
  },
8031
8075
  RefLatitude: null,
8032
8076
  RefLongitude: null,
@@ -8039,6 +8083,34 @@ function writeSite(w, guid, name, ownerHistoryId) {
8039
8083
  placementId: localPlacementId
8040
8084
  };
8041
8085
  }
8086
+ function writeSpatialLocalPlacement(w, spec, parentPlacementId) {
8087
+ const toMetres = (value) => [
8088
+ toIfcLengthM(value[0]),
8089
+ toIfcLengthM(value[1]),
8090
+ toIfcLengthM(value[2])
8091
+ ];
8092
+ const placement3DId = writeAxis2Placement3D(w, toMetres(spec.origin ?? [
8093
+ 0,
8094
+ 0,
8095
+ 0
8096
+ ]), spec.axisZ ?? [
8097
+ 0,
8098
+ 0,
8099
+ 1
8100
+ ], spec.axisX ?? [
8101
+ 1,
8102
+ 0,
8103
+ 0
8104
+ ]);
8105
+ const localPlacementId = w.nextId();
8106
+ w.writeLine({
8107
+ expressID: localPlacementId,
8108
+ type: WebIFC.IFCLOCALPLACEMENT,
8109
+ PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
8110
+ RelativePlacement: w.ref(placement3DId)
8111
+ });
8112
+ return localPlacementId;
8113
+ }
8042
8114
  function writeBuilding(w, guid, name, ownerHistoryId, parentPlacementId) {
8043
8115
  const placement3DId = writeAxis2Placement3D(w, [
8044
8116
  0,
@@ -10158,6 +10230,111 @@ function writeRelNests(w, guid, ownerHistoryId, relatingObjectId, relatedObjectI
10158
10230
  });
10159
10231
  }
10160
10232
  //#endregion
10233
+ //#region src/ifc-writer/infrastructureWriter.ts
10234
+ function writeBridge(w, guid, spec, ownerHistoryId, parentPlacementId) {
10235
+ const placementId = writeSpatialLocalPlacement(w, spec, parentPlacementId);
10236
+ const entityId = w.nextId();
10237
+ w.writeLine({
10238
+ expressID: entityId,
10239
+ type: WebIFC.IFCBRIDGE,
10240
+ GlobalId: w.mkType(WebIFC.IFCGLOBALLYUNIQUEID, guid),
10241
+ OwnerHistory: w.ref(ownerHistoryId),
10242
+ Name: w.mkType(WebIFC.IFCLABEL, spec.name),
10243
+ Description: spec.description ? w.mkType(WebIFC.IFCTEXT, spec.description) : null,
10244
+ ObjectType: null,
10245
+ ObjectPlacement: w.ref(placementId),
10246
+ Representation: null,
10247
+ LongName: null,
10248
+ CompositionType: {
10249
+ type: 3,
10250
+ value: spec.compositionType ?? "ELEMENT"
10251
+ },
10252
+ PredefinedType: {
10253
+ type: 3,
10254
+ value: spec.predefinedType ?? "NOTDEFINED"
10255
+ }
10256
+ });
10257
+ return {
10258
+ entityId,
10259
+ placementId
10260
+ };
10261
+ }
10262
+ function writeBridgePart(w, guid, spec, ownerHistoryId, parentPlacementId) {
10263
+ const placementId = writeSpatialLocalPlacement(w, spec, parentPlacementId);
10264
+ const entityId = w.nextId();
10265
+ w.writeLine({
10266
+ expressID: entityId,
10267
+ type: WebIFC.IFCBRIDGEPART,
10268
+ GlobalId: w.mkType(WebIFC.IFCGLOBALLYUNIQUEID, guid),
10269
+ OwnerHistory: w.ref(ownerHistoryId),
10270
+ Name: w.mkType(WebIFC.IFCLABEL, spec.name),
10271
+ Description: spec.description ? w.mkType(WebIFC.IFCTEXT, spec.description) : null,
10272
+ ObjectType: null,
10273
+ ObjectPlacement: w.ref(placementId),
10274
+ Representation: null,
10275
+ LongName: null,
10276
+ CompositionType: {
10277
+ type: 3,
10278
+ value: spec.compositionType ?? "ELEMENT"
10279
+ },
10280
+ UsageType: {
10281
+ type: 3,
10282
+ value: spec.usageType
10283
+ },
10284
+ PredefinedType: {
10285
+ type: 3,
10286
+ value: spec.predefinedType ?? "NOTDEFINED"
10287
+ }
10288
+ });
10289
+ return {
10290
+ entityId,
10291
+ placementId
10292
+ };
10293
+ }
10294
+ function writeEarthworksFillEntity(w, guid, spec, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
10295
+ const entityId = w.nextId();
10296
+ w.writeLine({
10297
+ expressID: entityId,
10298
+ type: WebIFC.IFCEARTHWORKSFILL,
10299
+ GlobalId: w.mkType(WebIFC.IFCGLOBALLYUNIQUEID, guid),
10300
+ OwnerHistory: w.ref(ownerHistoryId),
10301
+ Name: w.mkType(WebIFC.IFCLABEL, spec.name),
10302
+ Description: null,
10303
+ ObjectType: null,
10304
+ ObjectPlacement: w.ref(localPlacementId),
10305
+ Representation: w.ref(productDefinitionShapeId),
10306
+ Tag: null,
10307
+ PredefinedType: {
10308
+ type: 3,
10309
+ value: spec.predefinedType ?? "NOTDEFINED"
10310
+ }
10311
+ });
10312
+ return entityId;
10313
+ }
10314
+ //#endregion
10315
+ //#region src/ifc-writer/tessellatedProductWriter.ts
10316
+ /** Write a tessellated body in the coordinate frame supplied by the caller. */
10317
+ function writeTessellatedProductGeometry(w, solid, geomSubContextId, parentPlacementId) {
10318
+ const placement3DId = writeAxis2Placement3D(w, [
10319
+ 0,
10320
+ 0,
10321
+ 0
10322
+ ]);
10323
+ const localPlacementId = w.nextId();
10324
+ w.writeLine({
10325
+ expressID: localPlacementId,
10326
+ type: WebIFC.IFCLOCALPLACEMENT,
10327
+ PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
10328
+ RelativePlacement: w.ref(placement3DId)
10329
+ });
10330
+ const tessellation = writeTessellation(w, solid, geomSubContextId, localPlacementId);
10331
+ return {
10332
+ localPlacementId,
10333
+ productDefinitionShapeId: tessellation.productDefinitionShapeId,
10334
+ tessellation
10335
+ };
10336
+ }
10337
+ //#endregion
10161
10338
  //#region src/ifc-writer/groupWriter.ts
10162
10339
  /**
10163
10340
  * Emits an IfcZone grouping object. `longName` (a descriptive label such as
@@ -11636,29 +11813,12 @@ function writeWindowCommonPset(w, ownerHistoryId, windowEntityId, spec) {
11636
11813
  //#region src/ifc-writer/proxyWriter.ts
11637
11814
  /**
11638
11815
  * Writes the IfcLocalPlacement (at origin, relative to parentPlacementId) and a
11639
- * tessellated body (IfcTriangulatedFaceSet) for a proxy's solid. The solid is
11640
- * exported in its authored coordinate frame; placement is identity because proxy
11641
- * solids carry their own world positions in the brepjs geometry.
11816
+ * tessellated body (IfcTriangulatedFaceSet) for a proxy's solid. Solid
11817
+ * coordinates are emitted as supplied; the caller is responsible for matching
11818
+ * them to the containing spatial structure's frame.
11642
11819
  */
11643
11820
  function writeProxyGeometry(w, spec, geomSubContextId, parentPlacementId) {
11644
- const placement3DId = writeAxis2Placement3D(w, [
11645
- 0,
11646
- 0,
11647
- 0
11648
- ]);
11649
- const localPlacementId = w.nextId();
11650
- w.writeLine({
11651
- expressID: localPlacementId,
11652
- type: WebIFC.IFCLOCALPLACEMENT,
11653
- PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
11654
- RelativePlacement: w.ref(placement3DId)
11655
- });
11656
- const tessellation = writeTessellation(w, spec.solid, geomSubContextId, localPlacementId);
11657
- return {
11658
- localPlacementId,
11659
- productDefinitionShapeId: tessellation.productDefinitionShapeId,
11660
- tessellation
11661
- };
11821
+ return writeTessellatedProductGeometry(w, spec.solid, geomSubContextId, parentPlacementId);
11662
11822
  }
11663
11823
  function writeProxyEntity(w, guid, name, predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
11664
11824
  const id = w.nextId();
@@ -11909,6 +12069,7 @@ var CONTAINABLE_CATEGORIES = /* @__PURE__ */ new Set([
11909
12069
  "SLAB",
11910
12070
  "BEAM",
11911
12071
  "COLUMN",
12072
+ "EARTHWORKS_FILL",
11912
12073
  "DOOR",
11913
12074
  "WINDOW"
11914
12075
  ]);
@@ -12028,7 +12189,9 @@ function hasSpatialStructure(api, modelId) {
12028
12189
  return [
12029
12190
  WebIFC.IFCSITE,
12030
12191
  WebIFC.IFCBUILDING,
12031
- WebIFC.IFCBUILDINGSTOREY
12192
+ WebIFC.IFCBUILDINGSTOREY,
12193
+ WebIFC.IFCBRIDGE,
12194
+ WebIFC.IFCBRIDGEPART
12032
12195
  ].some((t) => api.GetLineIDsWithType(modelId, t).size() > 0);
12033
12196
  }
12034
12197
  function checkGlobalIds(api, modelId, lineIds, lineCount) {
@@ -12072,6 +12235,7 @@ var KEY_ENTITY_NAMES = [
12072
12235
  "IfcSlab",
12073
12236
  "IfcBeam",
12074
12237
  "IfcColumn",
12238
+ "IfcEarthworksFill",
12075
12239
  "IfcRelContainedInSpatialStructure",
12076
12240
  "IfcRelAggregates",
12077
12241
  "IfcPropertySet"
@@ -12082,6 +12246,7 @@ var KEY_ENTITY_TYPES = [
12082
12246
  ["IfcSlab", WebIFC.IFCSLAB],
12083
12247
  ["IfcBeam", WebIFC.IFCBEAM],
12084
12248
  ["IfcColumn", WebIFC.IFCCOLUMN],
12249
+ ["IfcEarthworksFill", WebIFC.IFCEARTHWORKSFILL],
12085
12250
  ["IfcRelContainedInSpatialStructure", WebIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE],
12086
12251
  ["IfcRelAggregates", WebIFC.IFCRELAGGREGATES],
12087
12252
  ["IfcPropertySet", WebIFC.IFCPROPERTYSET]
@@ -13074,7 +13239,7 @@ function checkQuantitySets(reader) {
13074
13239
  return issues;
13075
13240
  }
13076
13241
  function checkGeoreferencing(reader) {
13077
- if (!(reader.getLinesOfType(WebIFC.IFCBUILDING).length > 0)) return [];
13242
+ if (!(reader.getLinesOfType(WebIFC.IFCBUILDING).length > 0 || reader.getLinesOfType(WebIFC.IFCBRIDGE).length > 0)) return [];
13078
13243
  if (reader.getLinesOfType(WebIFC.IFCPROJECTEDCRS).length > 0 || reader.getLinesOfType(WebIFC.IFCGEOGRAPHICCRS).length > 0) return [];
13079
13244
  return [issue("warning", "NO_GEOREFERENCING", "Model contains a facility but no IfcProjectedCRS/IfcGeographicCRS (GRF003) — set ProjectSpec.crs")];
13080
13245
  }
@@ -13083,6 +13248,7 @@ function checkGeoreferencing(reader) {
13083
13248
  async function toIfc(model, meta) {
13084
13249
  const project = model.getProject();
13085
13250
  if (!project) return err(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
13251
+ if ((model.getBridges().length > 0 || model.getBridgeParts().length > 0 || model.getEarthworksFills().length > 0) && (meta.ifcSchema ?? "IFC4") !== "IFC4X3") return err(ifcError("IFC4X3_REQUIRED", "Bridge, Bridge Part, and Earthworks Fill entities require IFC4X3 serialization"));
13086
13252
  const authorName = [meta.author?.givenName, meta.author?.familyName].filter((p) => Boolean(p)).join(" ");
13087
13253
  const writerResult = await IfcWriter.create(meta.mvdViewDefinition, meta.ifcSchema, {
13088
13254
  author: authorName,
@@ -13096,6 +13262,9 @@ async function toIfc(model, meta) {
13096
13262
  const walls = model.getWalls();
13097
13263
  const slabs = model.getSlabs();
13098
13264
  const beams = model.getBeams();
13265
+ const bridges = model.getBridges();
13266
+ const bridgeParts = model.getBridgeParts();
13267
+ const earthworksFills = model.getEarthworksFills();
13099
13268
  const columns = model.getColumns();
13100
13269
  const doors = model.getDoors();
13101
13270
  const windows = model.getWindows();
@@ -13121,10 +13290,33 @@ async function toIfc(model, meta) {
13121
13290
  idMap.set(project.localId, projectExpressId);
13122
13291
  for (const el of elements) {
13123
13292
  if (el.category !== "SITE") continue;
13124
- const { entityId, placementId } = writeSite(w, el.guid, el.spec.name, ownerHistoryId);
13293
+ const parentId = findParentOf(el.localId, relationships);
13294
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13295
+ const { entityId, placementId } = writeSite(w, el.guid, el.spec, ownerHistoryId, parentPlacementId);
13125
13296
  idMap.set(el.localId, entityId);
13126
13297
  placementMap.set(el.localId, placementId);
13127
13298
  }
13299
+ for (const bridge of bridges) {
13300
+ const parentId = findParentOf(bridge.localId, relationships);
13301
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13302
+ const { entityId, placementId } = writeBridge(w, bridge.guid, bridge.spec, ownerHistoryId, parentPlacementId);
13303
+ idMap.set(bridge.localId, entityId);
13304
+ placementMap.set(bridge.localId, placementId);
13305
+ }
13306
+ const pendingParts = [...bridgeParts];
13307
+ while (pendingParts.length > 0) {
13308
+ const readyIndex = pendingParts.findIndex((candidate) => {
13309
+ const candidateParentId = findParentOf(candidate.localId, relationships);
13310
+ return candidateParentId === null || placementMap.has(candidateParentId);
13311
+ });
13312
+ const [part] = pendingParts.splice(readyIndex === -1 ? 0 : readyIndex, 1);
13313
+ if (part === void 0) break;
13314
+ const parentId = findParentOf(part.localId, relationships);
13315
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13316
+ const { entityId, placementId } = writeBridgePart(w, part.guid, part.spec, ownerHistoryId, parentPlacementId);
13317
+ idMap.set(part.localId, entityId);
13318
+ placementMap.set(part.localId, placementId);
13319
+ }
13128
13320
  for (const el of elements) {
13129
13321
  if (el.category !== "BUILDING") continue;
13130
13322
  const parentSiteId = findParentOf(el.localId, relationships);
@@ -13218,6 +13410,15 @@ async function toIfc(model, meta) {
13218
13410
  placementMap.set(proxy.localId, localPlacementId);
13219
13411
  if (proxy.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, proxyExpressId, proxy.spec.customProperties);
13220
13412
  }
13413
+ for (const fill of earthworksFills) {
13414
+ const containingId = findContainerOf(fill.localId, relationships);
13415
+ const parentPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13416
+ const { localPlacementId, productDefinitionShapeId } = writeTessellatedProductGeometry(w, fill.geometry, geomSubContextId, parentPlacementId);
13417
+ const expressId = writeEarthworksFillEntity(w, fill.guid, fill.spec, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13418
+ idMap.set(fill.localId, expressId);
13419
+ placementMap.set(fill.localId, localPlacementId);
13420
+ if (fill.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, expressId, fill.spec.customProperties);
13421
+ }
13221
13422
  for (const [i, space] of spaces.entries()) {
13222
13423
  const containingId = findContainerOf(space.localId, relationships);
13223
13424
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
@@ -13640,6 +13841,7 @@ function collectGeometryIssues(model) {
13640
13841
  ["Beam", model.getBeams()],
13641
13842
  ["Column", model.getColumns()],
13642
13843
  ["Proxy", model.getProxies()],
13844
+ ["Earthworks Fill", model.getEarthworksFills()],
13643
13845
  ["Space", model.getSpaces()],
13644
13846
  ["Roof", model.getRoofs()],
13645
13847
  ["Footing", model.getFootings()],
@@ -13710,7 +13912,9 @@ var CATEGORY_BY_TYPE = /* @__PURE__ */ new Map([
13710
13912
  [WebIFC.IFCPROJECT, "PROJECT"],
13711
13913
  [WebIFC.IFCSITE, "SITE"],
13712
13914
  [WebIFC.IFCBUILDING, "BUILDING"],
13713
- [WebIFC.IFCBUILDINGSTOREY, "STOREY"]
13915
+ [WebIFC.IFCBUILDINGSTOREY, "STOREY"],
13916
+ [WebIFC.IFCBRIDGE, "BRIDGE"],
13917
+ [WebIFC.IFCBRIDGEPART, "BRIDGE_PART"]
13714
13918
  ]);
13715
13919
  function refId$1(ref) {
13716
13920
  const v = ref?.value;
@@ -14793,6 +14997,7 @@ var ELEMENT_TYPES = [
14793
14997
  [WebIFC.IFCRAILING, "RAILING"],
14794
14998
  [WebIFC.IFCCOVERING, "COVERING"],
14795
14999
  [WebIFC.IFCELEMENTASSEMBLY, "ELEMENT_ASSEMBLY"],
15000
+ [WebIFC.IFCEARTHWORKSFILL, "EARTHWORKS_FILL"],
14796
15001
  [WebIFC.IFCBUILDINGELEMENTPROXY, "PROXY"]
14797
15002
  ];
14798
15003
  /**
@@ -14870,14 +15075,17 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
14870
15075
  const psets = readPsets(reader, expressId).map(toImportedPset);
14871
15076
  const material = readMaterial(reader, expressId, scale);
14872
15077
  const classification = readClassification(reader, expressId);
14873
- const storeyExpressId = containment.get(expressId);
15078
+ const spatialStructureExpressId = containment.get(expressId);
14874
15079
  return {
14875
15080
  expressId,
14876
15081
  guid,
14877
15082
  name,
14878
15083
  category,
14879
15084
  ...predefinedType !== void 0 ? { predefinedType } : {},
14880
- ...storeyExpressId !== void 0 ? { storeyExpressId } : {},
15085
+ ...spatialStructureExpressId !== void 0 ? {
15086
+ spatialStructureExpressId,
15087
+ storeyExpressId: spatialStructureExpressId
15088
+ } : {},
14881
15089
  geometry,
14882
15090
  psets,
14883
15091
  material,
@@ -15877,6 +16085,115 @@ function parseSlabOpeningInput(input) {
15877
16085
  return ok(result.data);
15878
16086
  }
15879
16087
  //#endregion
16088
+ //#region src/specs/spatialSpec.ts
16089
+ var unitVector = tuple([
16090
+ number(),
16091
+ number(),
16092
+ number()
16093
+ ]).refine((value) => Math.abs(value[0] ** 2 + value[1] ** 2 + value[2] ** 2 - 1) < 1e-6, { error: "must be a unit vector" });
16094
+ var spatialPlacementFields = {
16095
+ origin: tuple([
16096
+ number(),
16097
+ number(),
16098
+ number()
16099
+ ]).optional(),
16100
+ axisX: unitVector.optional(),
16101
+ axisZ: unitVector.optional()
16102
+ };
16103
+ function validateSpatialAxes(data, ctx) {
16104
+ const axisX = data.axisX ?? [
16105
+ 1,
16106
+ 0,
16107
+ 0
16108
+ ];
16109
+ const axisZ = data.axisZ ?? [
16110
+ 0,
16111
+ 0,
16112
+ 1
16113
+ ];
16114
+ const dot = axisX[0] * axisZ[0] + axisX[1] * axisZ[1] + axisX[2] * axisZ[2];
16115
+ if (Math.abs(dot) > 1e-6) ctx.addIssue({
16116
+ code: "custom",
16117
+ message: "axisX and axisZ must be orthogonal",
16118
+ path: ["axisZ"]
16119
+ });
16120
+ }
16121
+ var SiteSpecSchema = object({
16122
+ name: string().min(1),
16123
+ description: string().optional(),
16124
+ ...spatialPlacementFields,
16125
+ compositionType: _enum([
16126
+ "COMPLEX",
16127
+ "ELEMENT",
16128
+ "PARTIAL"
16129
+ ]).optional()
16130
+ }).superRefine(validateSpatialAxes);
16131
+ function parseSiteSpec(input) {
16132
+ const result = SiteSpecSchema.safeParse(input);
16133
+ return result.success ? ok(result.data) : err(specError("INVALID_SITE_SPEC", result.error.message, result.error));
16134
+ }
16135
+ //#endregion
16136
+ //#region src/specs/infrastructureSpec.ts
16137
+ var composition = _enum([
16138
+ "COMPLEX",
16139
+ "ELEMENT",
16140
+ "PARTIAL"
16141
+ ]).optional();
16142
+ var BridgeSpecSchema = object({
16143
+ name: string().min(1),
16144
+ description: string().optional(),
16145
+ ...spatialPlacementFields,
16146
+ compositionType: composition,
16147
+ predefinedType: _enum([
16148
+ "ARCHED",
16149
+ "CABLE_STAYED",
16150
+ "CANTILEVER",
16151
+ "CULVERT",
16152
+ "FRAMEWORK",
16153
+ "GIRDER",
16154
+ "SUSPENSION",
16155
+ "TRUSS",
16156
+ "USERDEFINED",
16157
+ "NOTDEFINED"
16158
+ ]).optional()
16159
+ }).superRefine(validateSpatialAxes);
16160
+ var BridgePartSpecSchema = object({
16161
+ name: string().min(1),
16162
+ description: string().optional(),
16163
+ ...spatialPlacementFields,
16164
+ compositionType: composition,
16165
+ usageType: _enum([
16166
+ "LATERAL",
16167
+ "LONGITUDINAL",
16168
+ "REGION",
16169
+ "VERTICAL",
16170
+ "USERDEFINED",
16171
+ "NOTDEFINED"
16172
+ ]),
16173
+ predefinedType: _enum([
16174
+ "ABUTMENT",
16175
+ "DECK",
16176
+ "DECK_SEGMENT",
16177
+ "FOUNDATION",
16178
+ "PIER",
16179
+ "PIER_SEGMENT",
16180
+ "PYLON",
16181
+ "SUBSTRUCTURE",
16182
+ "SUPERSTRUCTURE",
16183
+ "SURFACESTRUCTURE",
16184
+ "USERDEFINED",
16185
+ "NOTDEFINED"
16186
+ ]).optional()
16187
+ }).superRefine(validateSpatialAxes);
16188
+ function parseBridgeSpec(input) {
16189
+ const result = BridgeSpecSchema.safeParse(input);
16190
+ return result.success ? ok(result.data) : err(specError("INVALID_BRIDGE_SPEC", result.error.message, result.error));
16191
+ }
16192
+ function parseBridgePartSpec(input) {
16193
+ const result = BridgePartSpecSchema.safeParse(input);
16194
+ return result.success ? ok(result.data) : err(specError("INVALID_BRIDGE_PART_SPEC", result.error.message, result.error));
16195
+ }
16196
+ //#endregion
15880
16197
  //#region src/cobie/cobieExport.ts
15881
16198
  /**
15882
16199
  * Physical-element categories that become COBie Component rows. Spatial
@@ -15898,7 +16215,8 @@ var COMPONENT_CATEGORIES = /* @__PURE__ */ new Set([
15898
16215
  "STAIR",
15899
16216
  "RAMP",
15900
16217
  "RAILING",
15901
- "COVERING"
16218
+ "COVERING",
16219
+ "EARTHWORKS_FILL"
15902
16220
  ]);
15903
16221
  /**
15904
16222
  * Title-cased label used as the COBie Type/Component name prefix for a category,
@@ -15920,7 +16238,8 @@ var CATEGORY_LABEL = {
15920
16238
  STAIR: "Stair",
15921
16239
  RAMP: "Ramp",
15922
16240
  RAILING: "Railing",
15923
- COVERING: "Covering"
16241
+ COVERING: "Covering",
16242
+ EARTHWORKS_FILL: "EarthworksFill"
15924
16243
  };
15925
16244
  function categoryLabel(category) {
15926
16245
  return CATEGORY_LABEL[category] ?? category;
@@ -27597,8 +27916,11 @@ function assignNum(target, key, value) {
27597
27916
  * while the IR path serves the viewport and dedup. GlobalIds derive from
27598
27917
  * families key paths (stable under reordering), not insertion order.
27599
27918
  *
27600
- * Scope: Storey containers, Wall/Slab/Column/Beam/Roof/Stair elements, and wall openings — a
27601
- * fill-role void (Door/Window family) maps onto addDoor/addWindow, which cut
27919
+ * Scope: building Storey containers; civil Site/Bridge/recursive Bridge Part
27920
+ * structure and Earthworks Fill bodies; Wall/Slab/Column/Beam/Roof/Stair,
27921
+ * Footing/Pile, Railing/Ramp, Covering/CurtainWall, and Space elements; and
27922
+ * wall openings — a fill-role void (Door/Window family) maps onto
27923
+ * addDoor/addWindow, which cut
27602
27924
  * the wall and wire IfcRelVoidsElement + IfcRelFillsElement; the opening and
27603
27925
  * filler GlobalIds derive from the synthesized key paths. Anonymous (non-fill)
27604
27926
  * voids are rejected: they cut only the IR/viewport geometry, and exporting
@@ -27652,12 +27974,71 @@ var SPEC_ROUTES = {
27652
27974
  stair: {
27653
27975
  parse: parseStairSpec,
27654
27976
  add: (m, spec, key) => m.addStair(spec, { stableKey: key }),
27655
- input: stairSpecInput
27977
+ input: flightsSpecInput
27978
+ },
27979
+ footing: {
27980
+ parse: parseFootingSpec,
27981
+ add: (m, spec, key) => m.addFooting(spec, { stableKey: key })
27982
+ },
27983
+ pile: {
27984
+ parse: parsePileSpec,
27985
+ add: (m, spec, key) => m.addPile(spec, { stableKey: key })
27986
+ },
27987
+ railing: {
27988
+ parse: parseRailingSpec,
27989
+ add: (m, spec, key) => m.addRailing(spec, { stableKey: key })
27990
+ },
27991
+ ramp: {
27992
+ parse: parseRampSpec,
27993
+ add: (m, spec, key) => m.addRamp(spec, { stableKey: key }),
27994
+ input: flightsSpecInput
27995
+ },
27996
+ covering: {
27997
+ parse: parseCoveringSpec,
27998
+ add: (m, spec, key) => m.addCovering(spec, void 0, { stableKey: key })
27999
+ },
28000
+ curtainWall: {
28001
+ parse: parseCurtainWallSpec,
28002
+ add: (m, spec, key) => m.addCurtainWall(spec, { stableKey: key })
28003
+ },
28004
+ space: {
28005
+ parse: parseSpaceSpec,
28006
+ add: (m, spec, key) => m.addSpace(spec, { stableKey: key })
28007
+ }
28008
+ };
28009
+ var CIVIL_PRODUCT_ROUTES = {
28010
+ beam: {
28011
+ archetype: "beam",
28012
+ roles: [
28013
+ "beam",
28014
+ "cross-girder",
28015
+ "girder"
28016
+ ]
28017
+ },
28018
+ column: {
28019
+ archetype: "column",
28020
+ roles: ["pier-stem"]
28021
+ },
28022
+ footing: {
28023
+ archetype: "footing",
28024
+ roles: ["pad"]
28025
+ },
28026
+ railing: {
28027
+ archetype: "railing",
28028
+ roles: ["guardrail"]
28029
+ },
28030
+ slab: {
28031
+ archetype: "slab",
28032
+ roles: ["deck"]
28033
+ },
28034
+ wall: {
28035
+ archetype: "wall",
28036
+ roles: ["wall"]
27656
28037
  }
27657
28038
  };
27658
- /** StairSpec has no top-level origin — placement lives per flight — so the
27659
- * element's folded translate lands on every flight's origin instead. */
27660
- function stairSpecInput(el) {
28039
+ /** Stair and ramp specs have no top-level origin — placement lives per flight —
28040
+ * so the element's folded translate lands on every flight's origin instead. */
28041
+ function flightsSpecInput(el) {
27661
28042
  const base = specInput(el);
27662
28043
  const fold = base["origin"] ?? [
27663
28044
  0,
@@ -27699,14 +28080,145 @@ var NAME_ARCHETYPES = {
27699
28080
  Roof: "roof",
27700
28081
  Stair: "stair",
27701
28082
  Door: "door",
27702
- Window: "window"
28083
+ Window: "window",
28084
+ Footing: "footing",
28085
+ Pile: "pile",
28086
+ Railing: "railing",
28087
+ Ramp: "ramp",
28088
+ Covering: "covering",
28089
+ CurtainWall: "curtainWall",
28090
+ Space: "space"
27703
28091
  };
27704
28092
  function archetypeFor(el) {
27705
- return el.archetype ?? NAME_ARCHETYPES[el.type];
28093
+ return el.archetype ?? lookup(NAME_ARCHETYPES, el.type);
27706
28094
  }
27707
28095
  function specRoute(archetype) {
27708
28096
  return archetype !== void 0 && Object.hasOwn(SPEC_ROUTES, archetype) ? SPEC_ROUTES[archetype] : void 0;
27709
28097
  }
28098
+ /** Own-property lookup: semantics categories and roles are free-form author
28099
+ * strings, so Object.prototype keys must never resolve to a route. */
28100
+ function lookup(map, key) {
28101
+ return Object.hasOwn(map, key) ? map[key] : void 0;
28102
+ }
28103
+ function civilProductArchetype(el) {
28104
+ if (el.semantics?.kind !== "product") return void 0;
28105
+ const definition = lookup(CIVIL_PRODUCT_ROUTES, el.semantics.category);
28106
+ if (definition === void 0 || !definition.roles.includes(el.semantics.role)) return void 0;
28107
+ return definition.archetype;
28108
+ }
28109
+ function semanticDimension(el, ...names) {
28110
+ if (el.semantics?.kind !== "product") return void 0;
28111
+ for (const name of names) {
28112
+ const value = el.semantics.dimensionsMm[name];
28113
+ if (value !== void 0) return value;
28114
+ }
28115
+ }
28116
+ /** Beam/column solids centre their cross-section on the placement axis, while
28117
+ * the semantics envelope sits corner-anchored at the folded IR origin — shift
28118
+ * the origin to the envelope centre so the exported body occupies the same
28119
+ * space as the viewport body. */
28120
+ function centreOrigin(base, shift) {
28121
+ const origin = base["origin"] ?? [
28122
+ 0,
28123
+ 0,
28124
+ 0
28125
+ ];
28126
+ return [
28127
+ origin[0] + shift[0],
28128
+ origin[1] + shift[1],
28129
+ origin[2] + shift[2]
28130
+ ];
28131
+ }
28132
+ /**
28133
+ * Adapts target-independent civil envelope dimensions onto existing typed BIM
28134
+ * spec inputs. Reference infrastructure Families intentionally author domain
28135
+ * props such as `depth`, `capOffset`, and compound railing profiles rather than
28136
+ * BIM `profile`/`thickness` fields; semantics is the stable projection seam.
28137
+ */
28138
+ function civilProductSpecInput(el) {
28139
+ const base = specInput(el);
28140
+ if (el.semantics?.kind !== "product") return base;
28141
+ const length = semanticDimension(el, "length");
28142
+ const width = semanticDimension(el, "width");
28143
+ const height = semanticDimension(el, "height");
28144
+ switch (el.semantics.category) {
28145
+ case "beam": {
28146
+ const synthesized = base["profile"] === void 0 && width !== void 0 && height !== void 0 ? {
28147
+ profile: {
28148
+ kind: "RECTANGULAR",
28149
+ width,
28150
+ height
28151
+ },
28152
+ shift: [
28153
+ 0,
28154
+ width / 2,
28155
+ height / 2
28156
+ ]
28157
+ } : void 0;
28158
+ return {
28159
+ ...base,
28160
+ ...synthesized !== void 0 ? {
28161
+ origin: centreOrigin(base, synthesized.shift),
28162
+ profile: synthesized.profile
28163
+ } : {},
28164
+ length: length ?? base["length"],
28165
+ predefinedType: base["predefinedType"] ?? "BEAM"
28166
+ };
28167
+ }
28168
+ case "column": {
28169
+ const synthesized = base["profile"] === void 0 && length !== void 0 && width !== void 0 ? {
28170
+ profile: {
28171
+ kind: "RECTANGULAR",
28172
+ width: length,
28173
+ height: width
28174
+ },
28175
+ shift: [
28176
+ length / 2,
28177
+ width / 2,
28178
+ 0
28179
+ ]
28180
+ } : void 0;
28181
+ return {
28182
+ ...base,
28183
+ ...synthesized !== void 0 ? {
28184
+ origin: centreOrigin(base, synthesized.shift),
28185
+ profile: synthesized.profile
28186
+ } : {},
28187
+ height: height ?? base["height"],
28188
+ predefinedType: base["predefinedType"] ?? "COLUMN"
28189
+ };
28190
+ }
28191
+ case "footing": return {
28192
+ ...base,
28193
+ length: length ?? base["length"],
28194
+ width: width ?? base["width"],
28195
+ thickness: height ?? base["thickness"],
28196
+ predefinedType: base["predefinedType"] ?? "PAD_FOOTING"
28197
+ };
28198
+ case "railing": return {
28199
+ ...base,
28200
+ length: length ?? base["length"],
28201
+ thickness: width ?? base["thickness"],
28202
+ height: height ?? base["height"],
28203
+ predefinedType: base["predefinedType"] ?? "GUARDRAIL"
28204
+ };
28205
+ case "slab": return {
28206
+ ...base,
28207
+ length: length ?? base["length"],
28208
+ width: width ?? base["width"],
28209
+ thickness: height ?? base["thickness"],
28210
+ predefinedType: base["predefinedType"] ?? "FLOOR"
28211
+ };
28212
+ case "wall": return {
28213
+ ...base,
28214
+ length: length ?? base["length"],
28215
+ thickness: width ?? base["thickness"],
28216
+ height: height ?? base["height"],
28217
+ predefinedType: base["predefinedType"] ?? "NOTDEFINED"
28218
+ };
28219
+ default: return base;
28220
+ }
28221
+ }
27710
28222
  /** Total of the resolved geometry's OUTER literal translate chain. The
27711
28223
  * transform vocabulary is translate-only, so frame differences are exact
27712
28224
  * subtractions. Parameter-driven translations stop the peel. */
@@ -27732,15 +28244,14 @@ function peelTranslates(node) {
27732
28244
  moved
27733
28245
  };
27734
28246
  }
27735
- /** True when the element's transform PROP carries a rotation. The spec path
28247
+ /** True when the element's rendered local transform carries a rotation. The spec path
27736
28248
  * folds only translations into IfcLocalPlacement (walls orient via `axisX`),
27737
28249
  * so a tRotate placement would export un-rotated while the viewport shows it
27738
28250
  * rotated — reject instead of diverging. Rotations a family render bakes
27739
28251
  * into its own body geometry (e.g. a circular beam oriented along axisX) are
27740
28252
  * fine: the spec rebuilds that body parametrically from props. */
27741
28253
  function hasRotateOp(el) {
27742
- const ops = el.props["transform"];
27743
- return Array.isArray(ops) && ops.some((op) => typeof op === "object" && op !== null && op.op === "rotate");
28254
+ return el.localTransforms.some((op) => op.op === "rotate");
27744
28255
  }
27745
28256
  /** Fold the resolved geometry's outer translate chain into the spec placement
27746
28257
  * origin, so IfcLocalPlacement matches the IR world frame no matter where the
@@ -27786,10 +28297,14 @@ var COMMON_PSET_FIELDS = {
27786
28297
  function collectSpecProps(el) {
27787
28298
  const out = {};
27788
28299
  const material = el.attributes["material"];
27789
- if (typeof material === "string" && el.props["materialName"] === void 0) out["materialName"] = material;
28300
+ const semanticsMaterial = el.semantics?.kind === "product" ? el.semantics.material : void 0;
28301
+ if (el.props["materialName"] === void 0) {
28302
+ if (typeof material === "string") out["materialName"] = material;
28303
+ else if (semanticsMaterial !== void 0) out["materialName"] = semanticsMaterial;
28304
+ }
27790
28305
  const psets = el.attributes["psets"];
28306
+ const custom = {};
27791
28307
  if (psets && typeof psets === "object") {
27792
- const custom = {};
27793
28308
  const ownCommonPset = `Pset_${el.type}Common`;
27794
28309
  for (const [psetName, fields] of Object.entries(psets)) {
27795
28310
  if (!fields || typeof fields !== "object") continue;
@@ -27802,14 +28317,15 @@ function collectSpecProps(el) {
27802
28317
  if (Object.keys(values).length > 0) custom[psetName] = values;
27803
28318
  }
27804
28319
  }
27805
- if (Object.keys(custom).length > 0) {
27806
- const declared = el.props["customProperties"];
27807
- out["customProperties"] = declared && typeof declared === "object" ? {
27808
- ...custom,
27809
- ...declared
27810
- } : custom;
27811
- }
27812
28320
  }
28321
+ const declared = el.props["customProperties"];
28322
+ if (declared && typeof declared === "object" && !Array.isArray(declared)) for (const [psetName, fields] of Object.entries(declared)) {
28323
+ if (!fields || typeof fields !== "object" || Array.isArray(fields)) continue;
28324
+ const values = {};
28325
+ for (const [field, value] of Object.entries(fields)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") values[field] = value;
28326
+ if (Object.keys(values).length > 0) custom[psetName] = values;
28327
+ }
28328
+ if (Object.keys(custom).length > 0) out["customProperties"] = custom;
27813
28329
  return out;
27814
28330
  }
27815
28331
  function addFill(model, fill, input, identity) {
@@ -27826,35 +28342,49 @@ function addFill(model, fill, input, identity) {
27826
28342
  }
27827
28343
  return err(specError("FAMILIES_UNSUPPORTED_FILL", `familiesToBim: no fill mapping for element type '${fill.type}' at '${fill.keyPath}' (archetype: ${archetype ?? "none"}) — a filler needs archetype: 'door' or 'window'`));
27828
28344
  }
27829
- /** Materialize an unrouted element's IR and add it as a proxy. The body is
27830
- * authoritative for a proxy (no parametric spec to diverge from), so baked
27831
- * transforms — rotations included — are fine here. */
27832
- function addProxyElement(model, el, evaluator) {
28345
+ function materializeOwnedSolid(el, evaluator, errors) {
27833
28346
  const evaluated = evaluator.evaluate(el.geometry);
27834
- if (!evaluated.ok) return err(specError("FAMILIES_PROXY_EVAL_FAILED", `familiesToBim: '${el.keyPath}' failed to materialize for the proxy route: ${evaluated.error.message}`, evaluated.error));
28347
+ if (!evaluated.ok) return err(specError(errors.evalCode, `familiesToBim: '${el.keyPath}' failed to materialize for the ${errors.routeName} route: ${evaluated.error.message}`, evaluated.error));
27835
28348
  let source = evaluated.value;
27836
28349
  if (!isSolid(source)) {
27837
28350
  const solids = getSolids(source);
27838
28351
  const only = solids.length === 1 ? solids[0] : void 0;
27839
- if (only === void 0) return err(specError("FAMILIES_PROXY_NOT_SOLID", `familiesToBim: '${el.keyPath}' materialized to ${solids.length} solids — a proxy body must be exactly one`));
28352
+ if (only === void 0) return err(specError(errors.notSolidCode, `familiesToBim: '${el.keyPath}' materialized to ${solids.length} solids — the ${errors.routeName} body must be exactly one`));
27840
28353
  source = only;
27841
28354
  }
27842
28355
  const copy = clone(source);
27843
- if (!copy.ok) return err(specError("FAMILIES_PROXY_EVAL_FAILED", `familiesToBim: '${el.keyPath}' could not copy the materialized body`, copy.error));
28356
+ if (!copy.ok) return err(specError(errors.evalCode, `familiesToBim: '${el.keyPath}' could not copy the materialized body`, copy.error));
27844
28357
  const valid = validSolid(copy.value);
27845
28358
  if (!valid.ok) {
27846
28359
  copy.value[Symbol.dispose]();
27847
- return err(specError("FAMILIES_PROXY_INVALID", `familiesToBim: '${el.keyPath}' materialized to an invalid solid: ${valid.error}`));
28360
+ return err(specError(errors.invalidCode, `familiesToBim: '${el.keyPath}' materialized to an invalid solid: ${valid.error}`));
27848
28361
  }
28362
+ return ok(valid.value);
28363
+ }
28364
+ /** Materialize an unrouted element's IR and add it as a proxy. The body is
28365
+ * authoritative for a proxy (no parametric spec to diverge from), so baked
28366
+ * transforms — rotations included — are fine here. */
28367
+ function addProxyElement(model, el, evaluator, projectedSpatialTranslation) {
28368
+ const body = materializeOwnedSolid(el, evaluator, {
28369
+ evalCode: "FAMILIES_PROXY_EVAL_FAILED",
28370
+ notSolidCode: "FAMILIES_PROXY_NOT_SOLID",
28371
+ invalidCode: "FAMILIES_PROXY_INVALID",
28372
+ routeName: "proxy"
28373
+ });
28374
+ if (!body.ok) return body;
28375
+ const localized = localizeOwnedSolid(el, body.value, projectedSpatialTranslation, "FAMILIES_PROXY_LOCALIZE_FAILED", "spatial parent");
28376
+ if (!localized.ok) return localized;
27849
28377
  const nameAttr = el.attributes["name"];
27850
28378
  const materialProp = el.props["materialName"];
27851
28379
  const specProps = collectSpecProps(el);
27852
- return model.addProxy({
28380
+ const added = model.addProxy({
27853
28381
  name: typeof nameAttr === "string" ? nameAttr : el.type,
27854
- solid: valid.value,
28382
+ solid: localized.value,
27855
28383
  materialName: typeof materialProp === "string" ? materialProp : specProps["materialName"],
27856
28384
  customProperties: specProps["customProperties"]
27857
28385
  }, { stableKey: el.keyPath });
28386
+ if (!added.ok) localized.value[Symbol.dispose]();
28387
+ return added;
27858
28388
  }
27859
28389
  /** Map a wall's synthesized Opening children onto addDoor/addWindow. The
27860
28390
  * wall-relative offsets come from the void geometry's frame minus the host's:
@@ -27904,38 +28434,296 @@ function requireKeyed(el) {
27904
28434
  if (el.keyed) return ok(void 0);
27905
28435
  return err(specError("FAMILIES_UNKEYED_ELEMENT", `familiesToBim: '${el.keyPath}' has no explicit key — IFC identity needs order-independent key paths (add a key to the element)`));
27906
28436
  }
28437
+ var BRIDGE_ROLE = {
28438
+ arched: "ARCHED",
28439
+ "cable-stayed": "CABLE_STAYED",
28440
+ cantilever: "CANTILEVER",
28441
+ culvert: "CULVERT",
28442
+ framework: "FRAMEWORK",
28443
+ girder: "GIRDER",
28444
+ suspension: "SUSPENSION",
28445
+ truss: "TRUSS"
28446
+ };
28447
+ var BRIDGE_PART_ROLE = {
28448
+ abutment: "ABUTMENT",
28449
+ deck: "DECK",
28450
+ "deck-segment": "DECK_SEGMENT",
28451
+ foundation: "FOUNDATION",
28452
+ pier: "PIER",
28453
+ "pier-segment": "PIER_SEGMENT",
28454
+ pylon: "PYLON",
28455
+ substructure: "SUBSTRUCTURE",
28456
+ superstructure: "SUPERSTRUCTURE",
28457
+ "surface-structure": "SURFACESTRUCTURE"
28458
+ };
28459
+ var CIVIL_COMPOSITION = {
28460
+ collection: "COMPLEX",
28461
+ element: "ELEMENT",
28462
+ partial: "PARTIAL"
28463
+ };
28464
+ var CIVIL_USAGE = {
28465
+ lateral: "LATERAL",
28466
+ longitudinal: "LONGITUDINAL",
28467
+ regional: "REGION",
28468
+ vertical: "VERTICAL"
28469
+ };
28470
+ var EARTHWORKS_FILL_ROLE = {
28471
+ backfill: "BACKFILL",
28472
+ counterweight: "COUNTERWEIGHT",
28473
+ embankment: "EMBANKMENT",
28474
+ "slope-fill": "SLOPEFILL",
28475
+ subgrade: "SUBGRADE",
28476
+ "subgrade-bed": "SUBGRADEBED",
28477
+ "transition-section": "TRANSITIONSECTION"
28478
+ };
28479
+ function unsupportedCivilRole(el, entity) {
28480
+ return err(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: unsupported ${entity} role '${el.semantics?.role ?? ""}' at '${el.keyPath}'`));
28481
+ }
28482
+ function civilSpatialKind(el) {
28483
+ const semantics = el.semantics;
28484
+ if (semantics?.kind === "site" && semantics.category === "site") return "site";
28485
+ if (semantics?.kind === "facility" && semantics.category === "bridge") return "bridge";
28486
+ if (semantics?.kind === "spatial-part" && semantics.category === "bridge-part") return "bridge-part";
28487
+ }
28488
+ function isCivilSpatialIntent(el) {
28489
+ return el.semantics?.kind === "site" || el.semantics?.kind === "facility" || el.semantics?.kind === "spatial-part";
28490
+ }
28491
+ function hasCivilSpatialIntent(el) {
28492
+ return isCivilSpatialIntent(el) || el.children.some(hasCivilSpatialIntent);
28493
+ }
28494
+ function isEarthworksFillOccurrence(el) {
28495
+ return el.semantics?.kind === "product" && el.semantics.category === "earthworks-fill";
28496
+ }
28497
+ function semanticName(el) {
28498
+ const name = el.attributes["name"];
28499
+ if (typeof name === "string" && name.trim().length > 0) return name;
28500
+ const semanticNameValue = el.semantics?.properties?.["name"];
28501
+ return typeof semanticNameValue === "string" && semanticNameValue.trim().length > 0 ? semanticNameValue : el.keyPath;
28502
+ }
28503
+ var ZERO_TRANSLATION = [
28504
+ 0,
28505
+ 0,
28506
+ 0
28507
+ ];
28508
+ function addTranslation(a, b) {
28509
+ return [
28510
+ a[0] + b[0],
28511
+ a[1] + b[1],
28512
+ a[2] + b[2]
28513
+ ];
28514
+ }
28515
+ function subtractTranslation(a, b) {
28516
+ return [
28517
+ a[0] - b[0],
28518
+ a[1] - b[1],
28519
+ a[2] - b[2]
28520
+ ];
28521
+ }
28522
+ function hasTranslation(value) {
28523
+ return value[0] !== 0 || value[1] !== 0 || value[2] !== 0;
28524
+ }
28525
+ var DEFAULT_AXIS_X = [
28526
+ 1,
28527
+ 0,
28528
+ 0
28529
+ ];
28530
+ var DEFAULT_AXIS_Z = [
28531
+ 0,
28532
+ 0,
28533
+ 1
28534
+ ];
28535
+ function axisEquals(value, axis) {
28536
+ return Array.isArray(value) && value.length === 3 && value.every((component, i) => typeof component === "number" && component === axis[i]);
28537
+ }
28538
+ /** Civil descendant relativization is translation-only, so a civil frame with
28539
+ * non-default axes would rotate the exported hierarchy away from the IR. */
28540
+ function hasRotatedAxes(el) {
28541
+ const axisX = el.props["axisX"];
28542
+ const axisZ = el.props["axisZ"];
28543
+ return axisX !== void 0 && !axisEquals(axisX, DEFAULT_AXIS_X) || axisZ !== void 0 && !axisEquals(axisZ, DEFAULT_AXIS_Z);
28544
+ }
28545
+ function authoredSpecOrigin(el) {
28546
+ const origin = el.props["origin"];
28547
+ return Array.isArray(origin) && origin.length === 3 && origin.every((c) => typeof c === "number") ? origin : ZERO_TRANSLATION;
28548
+ }
28549
+ function localizeOwnedSolid(el, body, projectedSpatialTranslation, errorCode, frameName) {
28550
+ if (!hasTranslation(projectedSpatialTranslation)) return ok(body);
28551
+ try {
28552
+ const localized = translate(body, [
28553
+ -projectedSpatialTranslation[0],
28554
+ -projectedSpatialTranslation[1],
28555
+ -projectedSpatialTranslation[2]
28556
+ ]);
28557
+ body[Symbol.dispose]();
28558
+ return ok(localized);
28559
+ } catch (cause) {
28560
+ body[Symbol.dispose]();
28561
+ return err(specError(errorCode, `familiesToBim: '${el.keyPath}' could not move its body into the ${frameName} frame`, cause));
28562
+ }
28563
+ }
28564
+ function authoredTranslation(el) {
28565
+ const total = [
28566
+ 0,
28567
+ 0,
28568
+ 0
28569
+ ];
28570
+ for (const op of el.localTransforms) {
28571
+ if (op.op !== "translate") continue;
28572
+ total[0] += op.v[0];
28573
+ total[1] += op.v[1];
28574
+ total[2] += op.v[2];
28575
+ }
28576
+ return total;
28577
+ }
28578
+ function civilSpatialInput(el, localTranslation) {
28579
+ const semantics = el.semantics;
28580
+ const composition = semantics !== void 0 && "composition" in semantics ? CIVIL_COMPOSITION[semantics.composition] : void 0;
28581
+ const origin = addTranslation(el.props["origin"] ?? ZERO_TRANSLATION, localTranslation);
28582
+ return {
28583
+ name: semanticName(el),
28584
+ ...el.props["origin"] !== void 0 || hasTranslation(localTranslation) ? { origin } : {},
28585
+ ...el.props["axisX"] !== void 0 ? { axisX: el.props["axisX"] } : {},
28586
+ ...el.props["axisZ"] !== void 0 ? { axisZ: el.props["axisZ"] } : {},
28587
+ ...composition !== void 0 ? { compositionType: composition } : {}
28588
+ };
28589
+ }
28590
+ function civilParentAccepts(kind, parent) {
28591
+ if (kind === "site") return parent === "project";
28592
+ if (kind === "bridge") return parent === "site";
28593
+ return parent === "bridge" || parent === "bridge-part";
28594
+ }
28595
+ function addCivilSpatialOccurrence(model, el, kind, localTranslation) {
28596
+ if (kind === "site") {
28597
+ if (el.semantics?.role !== "transport-site") return unsupportedCivilRole(el, "Site");
28598
+ const parsed = parseSiteSpec(civilSpatialInput(el, localTranslation));
28599
+ return parsed.ok ? model.addSite(parsed.value, { stableKey: el.keyPath }) : parsed;
28600
+ }
28601
+ if (kind === "bridge") {
28602
+ const predefinedType = lookup(BRIDGE_ROLE, el.semantics?.role ?? "");
28603
+ if (predefinedType === void 0) return unsupportedCivilRole(el, "Bridge");
28604
+ const parsed = parseBridgeSpec({
28605
+ ...civilSpatialInput(el, localTranslation),
28606
+ predefinedType
28607
+ });
28608
+ return parsed.ok ? model.addBridge(parsed.value, { stableKey: el.keyPath }) : parsed;
28609
+ }
28610
+ const subdivision = el.semantics?.kind === "spatial-part" ? el.semantics.subdivision : void 0;
28611
+ const predefinedType = lookup(BRIDGE_PART_ROLE, el.semantics?.role ?? "");
28612
+ if (predefinedType === void 0) return unsupportedCivilRole(el, "Bridge Part");
28613
+ const parsed = parseBridgePartSpec({
28614
+ ...civilSpatialInput(el, localTranslation),
28615
+ predefinedType,
28616
+ usageType: subdivision !== void 0 ? CIVIL_USAGE[subdivision] : "NOTDEFINED"
28617
+ });
28618
+ return parsed.ok ? model.addBridgePart(parsed.value, { stableKey: el.keyPath }) : parsed;
28619
+ }
28620
+ function relativeSpecInput(input, projectedSpatialTranslation) {
28621
+ const relativeOrigin = (origin) => Array.isArray(origin) && origin.length === 3 && origin.every((value) => typeof value === "number") ? subtractTranslation(origin, projectedSpatialTranslation) : origin;
28622
+ if (Array.isArray(input["flights"])) {
28623
+ const flights = input["flights"];
28624
+ return {
28625
+ ...input,
28626
+ flights: flights.map((flight) => {
28627
+ if (typeof flight !== "object" || flight === null) return flight;
28628
+ const flightInput = flight;
28629
+ return {
28630
+ ...flightInput,
28631
+ origin: relativeOrigin(flightInput["origin"])
28632
+ };
28633
+ })
28634
+ };
28635
+ }
28636
+ return {
28637
+ ...input,
28638
+ origin: relativeOrigin(input["origin"])
28639
+ };
28640
+ }
28641
+ function addEarthworksFillElement(model, el, evaluator, projectedSpatialTranslation) {
28642
+ if (el.semantics?.kind !== "product") return err(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: '${el.keyPath}' is not authored as a civil Product`));
28643
+ const predefinedType = lookup(EARTHWORKS_FILL_ROLE, el.semantics.role);
28644
+ if (predefinedType === void 0) return unsupportedCivilRole(el, "Earthworks Fill");
28645
+ const body = materializeOwnedSolid(el, evaluator, {
28646
+ evalCode: "FAMILIES_EARTHWORKS_EVAL_FAILED",
28647
+ notSolidCode: "FAMILIES_EARTHWORKS_NOT_SOLID",
28648
+ invalidCode: "FAMILIES_EARTHWORKS_INVALID",
28649
+ routeName: "Earthworks Fill"
28650
+ });
28651
+ if (!body.ok) return body;
28652
+ const localized = localizeOwnedSolid(el, body.value, projectedSpatialTranslation, "FAMILIES_EARTHWORKS_LOCALIZE_FAILED", "Bridge Part");
28653
+ if (!localized.ok) return localized;
28654
+ const specProps = collectSpecProps(el);
28655
+ const authoredMaterial = el.props["materialName"];
28656
+ const added = model.addEarthworksFill({
28657
+ name: semanticName(el),
28658
+ solid: localized.value,
28659
+ materialName: typeof authoredMaterial === "string" ? authoredMaterial : specProps["materialName"] ?? el.semantics.material,
28660
+ predefinedType,
28661
+ customProperties: specProps["customProperties"]
28662
+ }, { stableKey: el.keyPath });
28663
+ if (!added.ok) localized.value[Symbol.dispose]();
28664
+ return added;
28665
+ }
27907
28666
  /**
27908
28667
  * Project a resolved families tree into an eager BimModel. The caller owns
27909
28668
  * the returned model (`using`); families stays domain-neutral — this adapter
27910
28669
  * is where families types meet the IFC vocabulary.
27911
28670
  */
27912
28671
  function familiesToBim(root, options) {
28672
+ const usesAuthoredCivilHierarchy = hasCivilSpatialIntent(root);
28673
+ if (usesAuthoredCivilHierarchy) {
28674
+ const keyed = requireKeyed(root);
28675
+ if (!keyed.ok) return keyed;
28676
+ }
27913
28677
  const model = new BimModel();
27914
- const initResult = model.init(options.project);
28678
+ const initResult = model.init(options.project, usesAuthoredCivilHierarchy ? { stableKey: civilSpatialKind(root) !== void 0 ? `${root.keyPath}#project` : root.keyPath } : void 0);
27915
28679
  if (!initResult.ok) return initResult;
27916
- const siteResult = model.addSite({ name: options.siteName ?? "Site" });
27917
- if (!siteResult.ok) {
27918
- model[Symbol.dispose]();
27919
- return siteResult;
27920
- }
27921
- const buildingResult = model.addBuilding({ name: options.buildingName ?? "Building" });
27922
- if (!buildingResult.ok) {
27923
- model[Symbol.dispose]();
27924
- return buildingResult;
27925
- }
27926
- const buildingId = buildingResult.value;
27927
- const project = model.getProject();
27928
- if (project !== null) model.aggregate(project.localId, siteResult.value);
27929
- model.aggregate(siteResult.value, buildingId);
27930
- const idByKeyPath = /* @__PURE__ */ new Map();
28680
+ const projectId = initResult.value;
28681
+ let buildingId = null;
28682
+ if (!usesAuthoredCivilHierarchy) {
28683
+ const siteResult = model.addSite({ name: options.siteName ?? "Site" });
28684
+ if (!siteResult.ok) {
28685
+ model[Symbol.dispose]();
28686
+ return siteResult;
28687
+ }
28688
+ const buildingResult = model.addBuilding({ name: options.buildingName ?? "Building" });
28689
+ if (!buildingResult.ok) {
28690
+ model[Symbol.dispose]();
28691
+ return buildingResult;
28692
+ }
28693
+ buildingId = buildingResult.value;
28694
+ model.aggregate(projectId, siteResult.value);
28695
+ model.aggregate(siteResult.value, buildingId);
28696
+ }
28697
+ const idByKeyPath = new Map(usesAuthoredCivilHierarchy ? [[root.keyPath, projectId]] : []);
27931
28698
  const proxied = [];
27932
- const walk = (el, storeyId, rotated) => {
27933
- const rotatedHere = rotated || hasRotateOp(el);
28699
+ const walk = (el, state) => {
28700
+ const rotatedHere = state.rotated || hasRotateOp(el);
28701
+ const cumulativeTranslationHere = addTranslation(state.cumulativeTranslation, authoredTranslation(el));
27934
28702
  let proxiedHere = false;
27935
- let containerId = storeyId;
28703
+ let nextSpatialStructureId = state.spatialStructureId;
28704
+ let nextCivilParent = state.civilParent;
28705
+ let nextProjectedSpatialTranslation = state.projectedSpatialTranslation;
27936
28706
  const archetype = archetypeFor(el);
27937
- const route = specRoute(archetype);
27938
- if (archetype === "storey") {
28707
+ const effectiveArchetype = el.semantics?.kind === "product" ? civilProductArchetype(el) : archetype;
28708
+ const route = specRoute(effectiveArchetype);
28709
+ const civilKind = civilSpatialKind(el);
28710
+ if (civilKind !== void 0) {
28711
+ if (!usesAuthoredCivilHierarchy || !civilParentAccepts(civilKind, state.civilParent) || state.spatialStructureId === null) return err(specError("FAMILIES_INVALID_CIVIL_HIERARCHY", `familiesToBim: civil '${civilKind}' at '${el.keyPath}' cannot occur under '${state.civilParent}'`));
28712
+ const keyed = requireKeyed(el);
28713
+ if (!keyed.ok) return keyed;
28714
+ if (rotatedHere || hasRotatedAxes(el)) return err(specError("FAMILIES_UNSUPPORTED_TRANSFORM", `familiesToBim: civil spatial element '${el.keyPath}' carries a rotated frame — descendants are relativized by translation only, so bake the orientation into child geometry`));
28715
+ if (el.geometry.kind !== "Empty") return err(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: civil spatial element '${el.keyPath}' carries its own geometry — Site/Bridge/Bridge Part export no body, so author it as a child Product (e.g. Earthworks Fill)`));
28716
+ const localTranslation = subtractTranslation(cumulativeTranslationHere, state.projectedSpatialTranslation);
28717
+ const added = addCivilSpatialOccurrence(model, el, civilKind, localTranslation);
28718
+ if (!added.ok) return added;
28719
+ model.aggregate(state.spatialStructureId, added.value);
28720
+ idByKeyPath.set(el.keyPath, added.value);
28721
+ nextSpatialStructureId = added.value;
28722
+ nextCivilParent = civilKind;
28723
+ nextProjectedSpatialTranslation = addTranslation(cumulativeTranslationHere, authoredSpecOrigin(el));
28724
+ } else if (isCivilSpatialIntent(el)) return err(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: unsupported civil '${el.semantics?.kind ?? "unknown"}' category '${el.semantics?.category ?? ""}' at '${el.keyPath}'`));
28725
+ else if (archetype === "storey") {
28726
+ if (buildingId === null) return err(specError("FAMILIES_INVALID_CIVIL_HIERARCHY", `familiesToBim: Storey '${el.keyPath}' is not part of the civil Bridge hierarchy`));
27939
28727
  const keyed = requireKeyed(el);
27940
28728
  if (!keyed.ok) return keyed;
27941
28729
  const storeyResult = model.addStorey({
@@ -27945,7 +28733,17 @@ function familiesToBim(root, options) {
27945
28733
  if (!storeyResult.ok) return storeyResult;
27946
28734
  model.aggregate(buildingId, storeyResult.value);
27947
28735
  idByKeyPath.set(el.keyPath, storeyResult.value);
27948
- containerId = storeyResult.value;
28736
+ nextSpatialStructureId = storeyResult.value;
28737
+ } else if (isEarthworksFillOccurrence(el)) {
28738
+ if (!usesAuthoredCivilHierarchy || nextSpatialStructureId === null || state.civilParent !== "bridge-part") return err(specError("FAMILIES_INVALID_CIVIL_HIERARCHY", `familiesToBim: Earthworks Fill '${el.keyPath}' needs a Bridge Part ancestor`));
28739
+ const bodyEvaluator = options.bodyEvaluator ?? options.proxyEvaluator;
28740
+ if (bodyEvaluator === void 0) return err(specError("FAMILIES_EARTHWORKS_EVALUATOR_REQUIRED", `familiesToBim: Earthworks Fill '${el.keyPath}' needs bodyEvaluator to materialize its exact Product Body`));
28741
+ const keyed = requireKeyed(el);
28742
+ if (!keyed.ok) return keyed;
28743
+ const added = addEarthworksFillElement(model, el, bodyEvaluator, state.projectedSpatialTranslation);
28744
+ if (!added.ok) return added;
28745
+ model.placeIn(added.value, nextSpatialStructureId);
28746
+ idByKeyPath.set(el.keyPath, added.value);
27949
28747
  } else if (route !== void 0) {
27950
28748
  const keyed = requireKeyed(el);
27951
28749
  if (!keyed.ok) return keyed;
@@ -27955,26 +28753,27 @@ function familiesToBim(root, options) {
27955
28753
  const openings = el.children.filter((c) => c.type === "Opening").length;
27956
28754
  if (voids.length > openings) return err(specError("FAMILIES_ANONYMOUS_VOID", `familiesToBim: '${el.keyPath}' has ${voids.length - openings} anonymous void(s) the IFC body cannot carry — use a fill-role family (Door/Window) for each void`));
27957
28755
  }
27958
- const parsed = route.parse(("input" in route ? route.input : specInput)(el));
28756
+ const routedInput = el.semantics?.kind === "product" ? civilProductSpecInput(el) : ("input" in route ? route.input : specInput)(el);
28757
+ const parsed = route.parse(usesAuthoredCivilHierarchy ? relativeSpecInput(routedInput, state.projectedSpatialTranslation) : routedInput);
27959
28758
  if (!parsed.ok) return parsed;
27960
28759
  const added = route.add(model, parsed.value, el.keyPath);
27961
28760
  if (!added.ok) return added;
27962
28761
  idByKeyPath.set(el.keyPath, added.value);
27963
- if (containerId === null) return err(specError("FAMILIES_NO_STOREY", `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment; a container family needs archetype: 'storey' to be recognised under any name`));
27964
- model.placeIn(added.value, containerId);
27965
- if (archetype === "wall") {
27966
- const opened = addOpenings(model, el, added.value, containerId, idByKeyPath);
28762
+ if (nextSpatialStructureId === null || usesAuthoredCivilHierarchy && state.civilParent !== "bridge-part") return err(specError(usesAuthoredCivilHierarchy ? "FAMILIES_INVALID_CIVIL_HIERARCHY" : "FAMILIES_NO_STOREY", usesAuthoredCivilHierarchy ? `familiesToBim: physical product '${el.keyPath}' needs a Bridge Part ancestor` : `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment; a container family needs archetype: 'storey' to be recognised under any name`));
28763
+ model.placeIn(added.value, nextSpatialStructureId);
28764
+ if (effectiveArchetype === "wall") {
28765
+ const opened = addOpenings(model, el, added.value, nextSpatialStructureId, idByKeyPath);
27967
28766
  if (!opened.ok) return opened;
27968
28767
  }
27969
28768
  } else if (el.type === "Opening") return err(specError("FAMILIES_OPENING_OUTSIDE_WALL", `familiesToBim: opening '${el.keyPath}' is not hosted by a Wall — only wall openings are mapped`));
27970
28769
  else if (el.type !== "Group" && el.geometry.kind !== "Empty") {
27971
- if (options.proxyEvaluator === void 0) return err(specError("FAMILIES_UNSUPPORTED_TYPE", `familiesToBim: no spec mapping for element type '${el.type}' at '${el.keyPath}' (archetype: ${el.archetype ?? "none"}) — routing is by archetype, so declare one on the family, add a spec route, or pass proxyEvaluator to export it as an IfcBuildingElementProxy`));
28770
+ if (options.proxyEvaluator === void 0) return err(specError("FAMILIES_UNSUPPORTED_TYPE", `familiesToBim: no supported spec mapping for element type '${el.type}' at '${el.keyPath}' (archetype: ${el.archetype ?? "none"}) — declare a recognized archetype or civil Product category/role, add a spec route, or pass proxyEvaluator to export it as an IfcBuildingElementProxy`));
27972
28771
  const keyed = requireKeyed(el);
27973
28772
  if (!keyed.ok) return keyed;
27974
- if (containerId === null) return err(specError("FAMILIES_NO_STOREY", `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment; a container family needs archetype: 'storey' to be recognised under any name`));
27975
- const added = addProxyElement(model, el, options.proxyEvaluator);
28773
+ if (nextSpatialStructureId === null || usesAuthoredCivilHierarchy && state.civilParent !== "bridge-part") return err(specError(usesAuthoredCivilHierarchy ? "FAMILIES_INVALID_CIVIL_HIERARCHY" : "FAMILIES_NO_STOREY", usesAuthoredCivilHierarchy ? `familiesToBim: physical product '${el.keyPath}' needs a Bridge Part ancestor` : `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment; a container family needs archetype: 'storey' to be recognised under any name`));
28774
+ const added = addProxyElement(model, el, options.proxyEvaluator, state.projectedSpatialTranslation);
27976
28775
  if (!added.ok) return added;
27977
- model.placeIn(added.value, containerId);
28776
+ model.placeIn(added.value, nextSpatialStructureId);
27978
28777
  idByKeyPath.set(el.keyPath, added.value);
27979
28778
  proxied.push({
27980
28779
  keyPath: el.keyPath,
@@ -27984,13 +28783,25 @@ function familiesToBim(root, options) {
27984
28783
  proxiedHere = true;
27985
28784
  }
27986
28785
  for (const child of el.children) {
27987
- if ((archetype === "wall" || proxiedHere) && child.type === "Opening") continue;
27988
- const r = walk(child, containerId, rotatedHere);
28786
+ if ((effectiveArchetype === "wall" || proxiedHere) && child.type === "Opening") continue;
28787
+ const r = walk(child, {
28788
+ spatialStructureId: nextSpatialStructureId,
28789
+ civilParent: nextCivilParent,
28790
+ rotated: rotatedHere,
28791
+ cumulativeTranslation: cumulativeTranslationHere,
28792
+ projectedSpatialTranslation: nextProjectedSpatialTranslation
28793
+ });
27989
28794
  if (!r.ok) return r;
27990
28795
  }
27991
28796
  return ok(void 0);
27992
28797
  };
27993
- const walked = walk(root, null, false);
28798
+ const walked = walk(root, {
28799
+ spatialStructureId: usesAuthoredCivilHierarchy ? projectId : null,
28800
+ civilParent: "project",
28801
+ rotated: false,
28802
+ cumulativeTranslation: ZERO_TRANSLATION,
28803
+ projectedSpatialTranslation: ZERO_TRANSLATION
28804
+ });
27994
28805
  if (!walked.ok) {
27995
28806
  model[Symbol.dispose]();
27996
28807
  return walked;
@@ -28002,4 +28813,4 @@ function familiesToBim(root, options) {
28002
28813
  });
28003
28814
  }
28004
28815
  //#endregion
28005
- export { BimModel, DEFAULT_IFC_SCHEMA, DEFAULT_UNITS, IFC_SCHEMAS, PSET_PROPERTY_TYPE_TABLE, PSET_TEMPLATES, SpfReader, bcfError, checkGeometryValidity, checkGherkinRules, checkIdsData as checkIds, checkIdsData, checkReferentialIntegrity, checkRoundTrip, checkSchema, countBySeverity, deriveCobieModel, deriveCobieModel as exportCobie, deriveIfcGuid, deriveIfcGuidSync, disposeImportedModel, emptyReport, extendedProfileArea, extendedProfileToFace, familiesToBim, fileSchemaString, fromBrepError, fromIfc, geometryError, hasErrors, idsError, ifcError, importError, isExtendedProfile, isIfcSchema, isSlabOpening, isValidIfcGuid, isWallOpening, issue, makeLocalIdCounter, measureTypeFor, newIfcGuid, parseBcfFiles, parseBeamSpec, 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 };
28816
+ export { BimModel, DEFAULT_IFC_SCHEMA, DEFAULT_UNITS, IFC_SCHEMAS, PSET_PROPERTY_TYPE_TABLE, PSET_TEMPLATES, SpfReader, bcfError, checkGeometryValidity, checkGherkinRules, checkIdsData as checkIds, checkIdsData, checkReferentialIntegrity, checkRoundTrip, checkSchema, countBySeverity, deriveCobieModel, deriveCobieModel as exportCobie, deriveIfcGuid, deriveIfcGuidSync, disposeImportedModel, emptyReport, extendedProfileArea, extendedProfileToFace, familiesToBim, fileSchemaString, fromBrepError, fromIfc, geometryError, hasErrors, idsError, ifcError, importError, isExtendedProfile, isIfcSchema, isSlabOpening, isValidIfcGuid, isWallOpening, issue, makeLocalIdCounter, measureTypeFor, newIfcGuid, parseBcfFiles, parseBeamSpec, parseBridgePartSpec, parseBridgeSpec, parseColumnSpec, parseCoveringSpec, parseCurtainWallSpec, parseDoorSpec, parseElementAssemblySpec, parseFootingSpec, parseIdsXml, parsePileSpec, parseProfile, parseRailingSpec, parseRampFlightSpec, parseRampSpec, parseRoofSpec, parseSlabOpeningInput, parseSlabSpec, parseSpaceSpec, parseStairFlightSpec, parseStairSpec, parseSurfaceStyleSpec, parseSystemSpec, parseWallSpec, parseWindowSpec, parseZoneSpec, placedSolids, schemaSupports, serializeBcfFiles, serializeCobieToCsv, serializeCobieToJson, setIfcWasmLocateFile, specError, templateFor, toIfc, toIfcLengthM, toIfcValidated, toLengthMm, writeClassificationRefs, writeElementAssemblyEntity, writeIfcType, writeMaterialLayerSet, writeMaterialProfileSet, writeMaterialSimple, writePresentationLayer, writeRelAggregatesElements, writeRelAssignsToGroup, writeRelConnectsElements, writeRelConnectsPathElements, writeRelNests, writeStyledItem, writeSurfaceStyle, writeSystemEntity, writeZoneEntity };