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.
@@ -6320,15 +6320,17 @@ var BimModel = class {
6320
6320
  #projectId = null;
6321
6321
  #modelScope = "";
6322
6322
  #usedStableKeys = /* @__PURE__ */ new Set();
6323
- init(spec) {
6323
+ init(spec, options) {
6324
6324
  if (this.#projectId !== null) return (0, brepjs.err)(specError("DUPLICATE_PROJECT", "BimModel.init() called twice — only one project per model"));
6325
6325
  this.#modelScope = spec.projectId ?? `${spec.name}::${spec.description ?? ""}`;
6326
- const id = this.#makeElement("PROJECT", spec, null);
6326
+ const keyCheck = this.#checkStableKey(options);
6327
+ if (!keyCheck.ok) return keyCheck;
6328
+ const id = this.#makeElement("PROJECT", spec, null, options?.stableKey);
6327
6329
  this.#projectId = id;
6328
6330
  return (0, brepjs.ok)(id);
6329
6331
  }
6330
6332
  [Symbol.dispose]() {
6331
- 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]();
6333
+ 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]();
6332
6334
  else if (el.category === "CURTAIN_WALL") {
6333
6335
  for (const panel of el.geometry.panels) panel.solid[Symbol.dispose]();
6334
6336
  for (const mullion of el.geometry.mullions) mullion.solid[Symbol.dispose]();
@@ -6339,6 +6341,26 @@ var BimModel = class {
6339
6341
  if (!keyCheck.ok) return keyCheck;
6340
6342
  return (0, brepjs.ok)(this.#makeElement("SITE", spec, null, options?.stableKey));
6341
6343
  }
6344
+ addBridge(spec, options) {
6345
+ const keyCheck = this.#checkStableKey(options);
6346
+ if (!keyCheck.ok) return keyCheck;
6347
+ return (0, brepjs.ok)(this.#makeElement("BRIDGE", spec, null, options?.stableKey));
6348
+ }
6349
+ addBridgePart(spec, options) {
6350
+ const keyCheck = this.#checkStableKey(options);
6351
+ if (!keyCheck.ok) return keyCheck;
6352
+ return (0, brepjs.ok)(this.#makeElement("BRIDGE_PART", spec, null, options?.stableKey));
6353
+ }
6354
+ /** Adds a typed IfcEarthworksFill body. Ownership of `spec.solid` transfers
6355
+ * to the model only when this call succeeds. */
6356
+ addEarthworksFill(spec, options) {
6357
+ const keyCheck = this.#checkStableKey(options);
6358
+ if (!keyCheck.ok) return keyCheck;
6359
+ if (spec.solid === null || spec.solid === void 0) return (0, brepjs.err)(specError("EARTHWORKS_FILL_NO_GEOMETRY", "EarthworksFillSpec.solid is required"));
6360
+ const id = this.#makeElement("EARTHWORKS_FILL", spec, spec.solid, options?.stableKey);
6361
+ this.#associateMaterial(id, spec);
6362
+ return (0, brepjs.ok)(id);
6363
+ }
6342
6364
  addBuilding(spec, options) {
6343
6365
  const keyCheck = this.#checkStableKey(options);
6344
6366
  if (!keyCheck.ok) return keyCheck;
@@ -6958,6 +6980,21 @@ var BimModel = class {
6958
6980
  for (const el of this.#elements.values()) if (el.category === "BEAM") beams.push(el);
6959
6981
  return beams;
6960
6982
  }
6983
+ getBridges() {
6984
+ const bridges = [];
6985
+ for (const el of this.#elements.values()) if (el.category === "BRIDGE") bridges.push(el);
6986
+ return bridges;
6987
+ }
6988
+ getBridgeParts() {
6989
+ const parts = [];
6990
+ for (const el of this.#elements.values()) if (el.category === "BRIDGE_PART") parts.push(el);
6991
+ return parts;
6992
+ }
6993
+ getEarthworksFills() {
6994
+ const fills = [];
6995
+ for (const el of this.#elements.values()) if (el.category === "EARTHWORKS_FILL") fills.push(el);
6996
+ return fills;
6997
+ }
6961
6998
  getColumns() {
6962
6999
  const columns = [];
6963
7000
  for (const el of this.#elements.values()) if (el.category === "COLUMN") columns.push(el);
@@ -7382,23 +7419,38 @@ function place(solid, frame) {
7382
7419
  function disposeAll(solids) {
7383
7420
  for (const s of solids) s[Symbol.dispose]();
7384
7421
  }
7422
+ function placeWithinParent(solid, localFrame, parentFrame) {
7423
+ try {
7424
+ var _usingCtx$3 = _usingCtx();
7425
+ const local = place(solid, localFrame);
7426
+ if (!local.ok || parentFrame === void 0) return local;
7427
+ return place(_usingCtx$3.u(local.value), parentFrame);
7428
+ } catch (_) {
7429
+ _usingCtx$3.e = _;
7430
+ } finally {
7431
+ _usingCtx$3.d();
7432
+ }
7433
+ }
7385
7434
  /**
7386
- * Returns each element's geometry transformed to its world placement, as fresh
7387
- * caller-owned solids, wrapped in a `Result` (Layer-2 code prefers `Result` over
7388
- * throwing). **Dispose the returned solids** (e.g. via `using` / `[Symbol.dispose]`)
7389
- * when you own their lifetime — they are independent of the model
7435
+ * Returns each element's geometry transformed to its element-local placement,
7436
+ * and optionally through the cumulative frame of its containing spatial
7437
+ * structure, as fresh caller-owned solids. Pass `parentFrame` to obtain world
7438
+ * coordinates for elements beneath a placed Site, Bridge, Bridge Part, or
7439
+ * other spatial structure. **Dispose the returned solids** (e.g. via `using` /
7440
+ * `[Symbol.dispose]`) when you own their lifetime — they are independent of the model
7390
7441
  * (`BimModel[Symbol.dispose]` frees only the stored, unplaced `.geometry`). On any
7391
7442
  * failure the solids already built for this call are disposed before the error is
7392
7443
  * returned, so no partial array is leaked.
7393
7444
  *
7394
7445
  * Stairs and ramps carry no element solid (`.geometry` is null), so flight
7395
7446
  * solids are built from `spec.flights` and placed per flight. Curtain walls
7396
- * return placed panels + mullions. Proxies are authored in world coordinates
7397
- * (no placement frame), so their solid is returned as a fresh identity-placed
7398
- * copy. Elements with no solid geometry (doors/windows/groups/spatial) return
7399
- * an empty array.
7447
+ * return placed panels + mullions. Proxy and Earthworks Fill bodies are stored
7448
+ * relative to their containing spatial structure, so their body is copied and
7449
+ * then transformed by `parentFrame` when supplied. Elements with no solid geometry
7450
+ * (doors/windows/groups/spatial) return an empty array.
7400
7451
  */
7401
- function placedSolids(el) {
7452
+ function placedSolids(el, options = {}) {
7453
+ const parentFrame = options.parentFrame;
7402
7454
  switch (el.category) {
7403
7455
  case "WALL":
7404
7456
  case "SLAB":
@@ -7410,56 +7462,57 @@ function placedSolids(el) {
7410
7462
  case "PILE":
7411
7463
  case "COVERING":
7412
7464
  case "RAILING": {
7413
- const placed = place(el.geometry, el.spec);
7465
+ const placed = placeWithinParent(el.geometry, el.spec, parentFrame);
7414
7466
  if (!placed.ok) return placed;
7415
7467
  return (0, brepjs.ok)([placed.value]);
7416
7468
  }
7417
7469
  case "STAIR": {
7418
7470
  const out = [];
7419
7471
  for (const flight of el.spec.flights) try {
7420
- var _usingCtx$3 = _usingCtx();
7472
+ var _usingCtx3 = _usingCtx();
7421
7473
  const built = stairFlightToSolid(flight);
7422
7474
  if (!built.ok) {
7423
7475
  disposeAll(out);
7424
7476
  return (0, brepjs.err)(built.error);
7425
7477
  }
7426
- const placed = place(_usingCtx$3.u(built.value.solid), flight);
7478
+ const placed = placeWithinParent(_usingCtx3.u(built.value.solid), flight, parentFrame);
7427
7479
  if (!placed.ok) {
7428
7480
  disposeAll(out);
7429
7481
  return placed;
7430
7482
  }
7431
7483
  out.push(placed.value);
7432
7484
  } catch (_) {
7433
- _usingCtx$3.e = _;
7485
+ _usingCtx3.e = _;
7434
7486
  } finally {
7435
- _usingCtx$3.d();
7487
+ _usingCtx3.d();
7436
7488
  }
7437
7489
  return (0, brepjs.ok)(out);
7438
7490
  }
7439
7491
  case "RAMP": {
7440
7492
  const out = [];
7441
7493
  for (const flight of el.spec.flights) try {
7442
- var _usingCtx3 = _usingCtx();
7494
+ var _usingCtx4 = _usingCtx();
7443
7495
  const built = rampFlightToSolid(flight);
7444
7496
  if (!built.ok) {
7445
7497
  disposeAll(out);
7446
7498
  return (0, brepjs.err)(built.error);
7447
7499
  }
7448
- const placed = place(_usingCtx3.u(built.value.solid), flight);
7500
+ const placed = placeWithinParent(_usingCtx4.u(built.value.solid), flight, parentFrame);
7449
7501
  if (!placed.ok) {
7450
7502
  disposeAll(out);
7451
7503
  return placed;
7452
7504
  }
7453
7505
  out.push(placed.value);
7454
7506
  } catch (_) {
7455
- _usingCtx3.e = _;
7507
+ _usingCtx4.e = _;
7456
7508
  } finally {
7457
- _usingCtx3.d();
7509
+ _usingCtx4.d();
7458
7510
  }
7459
7511
  return (0, brepjs.ok)(out);
7460
7512
  }
7461
- case "PROXY": {
7462
- const placed = place(el.geometry, {
7513
+ case "PROXY":
7514
+ case "EARTHWORKS_FILL": {
7515
+ const placed = placeWithinParent(el.geometry, {
7463
7516
  origin: [
7464
7517
  0,
7465
7518
  0,
@@ -7475,14 +7528,14 @@ function placedSolids(el) {
7475
7528
  0,
7476
7529
  1
7477
7530
  ]
7478
- });
7531
+ }, parentFrame);
7479
7532
  if (!placed.ok) return placed;
7480
7533
  return (0, brepjs.ok)([placed.value]);
7481
7534
  }
7482
7535
  case "CURTAIN_WALL": {
7483
7536
  const out = [];
7484
7537
  for (const c of [...el.geometry.panels, ...el.geometry.mullions]) try {
7485
- var _usingCtx4 = _usingCtx();
7538
+ var _usingCtx5 = _usingCtx();
7486
7539
  const componentLocal = place(c.solid, {
7487
7540
  origin: c.origin,
7488
7541
  axisX: [
@@ -7500,16 +7553,16 @@ function placedSolids(el) {
7500
7553
  disposeAll(out);
7501
7554
  return componentLocal;
7502
7555
  }
7503
- const placed = place(_usingCtx4.u(componentLocal.value), el.spec);
7556
+ const placed = placeWithinParent(_usingCtx5.u(componentLocal.value), el.spec, parentFrame);
7504
7557
  if (!placed.ok) {
7505
7558
  disposeAll(out);
7506
7559
  return placed;
7507
7560
  }
7508
7561
  out.push(placed.value);
7509
7562
  } catch (_) {
7510
- _usingCtx4.e = _;
7563
+ _usingCtx5.e = _;
7511
7564
  } finally {
7512
- _usingCtx4.d();
7565
+ _usingCtx5.d();
7513
7566
  }
7514
7567
  return (0, brepjs.ok)(out);
7515
7568
  }
@@ -7564,6 +7617,8 @@ var IFC4X3_ONLY_ENTITIES = /* @__PURE__ */ new Set([
7564
7617
  "IfcRoad",
7565
7618
  "IfcRailway",
7566
7619
  "IfcBridge",
7620
+ "IfcBridgePart",
7621
+ "IfcEarthworksFill",
7567
7622
  "IfcMarineFacility"
7568
7623
  ]);
7569
7624
  /**
@@ -8022,34 +8077,23 @@ function writeProject$1(w, guid, name, ownerHistoryId, unitAssignmentId, geomCon
8022
8077
  });
8023
8078
  return id;
8024
8079
  }
8025
- function writeSite(w, guid, name, ownerHistoryId) {
8026
- const placement3DId = writeAxis2Placement3D(w, [
8027
- 0,
8028
- 0,
8029
- 0
8030
- ]);
8031
- const localPlacementId = w.nextId();
8032
- w.writeLine({
8033
- expressID: localPlacementId,
8034
- type: web_ifc.IFCLOCALPLACEMENT,
8035
- PlacementRelTo: null,
8036
- RelativePlacement: w.ref(placement3DId)
8037
- });
8080
+ function writeSite(w, guid, spec, ownerHistoryId, parentPlacementId = null) {
8081
+ const localPlacementId = writeSpatialLocalPlacement(w, spec, parentPlacementId);
8038
8082
  const entityId = w.nextId();
8039
8083
  w.writeLine({
8040
8084
  expressID: entityId,
8041
8085
  type: web_ifc.IFCSITE,
8042
8086
  GlobalId: w.mkType(web_ifc.IFCGLOBALLYUNIQUEID, guid),
8043
8087
  OwnerHistory: w.ref(ownerHistoryId),
8044
- Name: w.mkType(web_ifc.IFCLABEL, name),
8045
- Description: null,
8088
+ Name: w.mkType(web_ifc.IFCLABEL, spec.name),
8089
+ Description: spec.description ? w.mkType(web_ifc.IFCTEXT, spec.description) : null,
8046
8090
  ObjectType: null,
8047
8091
  ObjectPlacement: w.ref(localPlacementId),
8048
8092
  Representation: null,
8049
8093
  LongName: null,
8050
8094
  CompositionType: {
8051
8095
  type: 3,
8052
- value: "ELEMENT"
8096
+ value: spec.compositionType ?? "ELEMENT"
8053
8097
  },
8054
8098
  RefLatitude: null,
8055
8099
  RefLongitude: null,
@@ -8062,6 +8106,34 @@ function writeSite(w, guid, name, ownerHistoryId) {
8062
8106
  placementId: localPlacementId
8063
8107
  };
8064
8108
  }
8109
+ function writeSpatialLocalPlacement(w, spec, parentPlacementId) {
8110
+ const toMetres = (value) => [
8111
+ toIfcLengthM(value[0]),
8112
+ toIfcLengthM(value[1]),
8113
+ toIfcLengthM(value[2])
8114
+ ];
8115
+ const placement3DId = writeAxis2Placement3D(w, toMetres(spec.origin ?? [
8116
+ 0,
8117
+ 0,
8118
+ 0
8119
+ ]), spec.axisZ ?? [
8120
+ 0,
8121
+ 0,
8122
+ 1
8123
+ ], spec.axisX ?? [
8124
+ 1,
8125
+ 0,
8126
+ 0
8127
+ ]);
8128
+ const localPlacementId = w.nextId();
8129
+ w.writeLine({
8130
+ expressID: localPlacementId,
8131
+ type: web_ifc.IFCLOCALPLACEMENT,
8132
+ PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
8133
+ RelativePlacement: w.ref(placement3DId)
8134
+ });
8135
+ return localPlacementId;
8136
+ }
8065
8137
  function writeBuilding(w, guid, name, ownerHistoryId, parentPlacementId) {
8066
8138
  const placement3DId = writeAxis2Placement3D(w, [
8067
8139
  0,
@@ -10181,6 +10253,111 @@ function writeRelNests(w, guid, ownerHistoryId, relatingObjectId, relatedObjectI
10181
10253
  });
10182
10254
  }
10183
10255
  //#endregion
10256
+ //#region src/ifc-writer/infrastructureWriter.ts
10257
+ function writeBridge(w, guid, spec, ownerHistoryId, parentPlacementId) {
10258
+ const placementId = writeSpatialLocalPlacement(w, spec, parentPlacementId);
10259
+ const entityId = w.nextId();
10260
+ w.writeLine({
10261
+ expressID: entityId,
10262
+ type: web_ifc.IFCBRIDGE,
10263
+ GlobalId: w.mkType(web_ifc.IFCGLOBALLYUNIQUEID, guid),
10264
+ OwnerHistory: w.ref(ownerHistoryId),
10265
+ Name: w.mkType(web_ifc.IFCLABEL, spec.name),
10266
+ Description: spec.description ? w.mkType(web_ifc.IFCTEXT, spec.description) : null,
10267
+ ObjectType: null,
10268
+ ObjectPlacement: w.ref(placementId),
10269
+ Representation: null,
10270
+ LongName: null,
10271
+ CompositionType: {
10272
+ type: 3,
10273
+ value: spec.compositionType ?? "ELEMENT"
10274
+ },
10275
+ PredefinedType: {
10276
+ type: 3,
10277
+ value: spec.predefinedType ?? "NOTDEFINED"
10278
+ }
10279
+ });
10280
+ return {
10281
+ entityId,
10282
+ placementId
10283
+ };
10284
+ }
10285
+ function writeBridgePart(w, guid, spec, ownerHistoryId, parentPlacementId) {
10286
+ const placementId = writeSpatialLocalPlacement(w, spec, parentPlacementId);
10287
+ const entityId = w.nextId();
10288
+ w.writeLine({
10289
+ expressID: entityId,
10290
+ type: web_ifc.IFCBRIDGEPART,
10291
+ GlobalId: w.mkType(web_ifc.IFCGLOBALLYUNIQUEID, guid),
10292
+ OwnerHistory: w.ref(ownerHistoryId),
10293
+ Name: w.mkType(web_ifc.IFCLABEL, spec.name),
10294
+ Description: spec.description ? w.mkType(web_ifc.IFCTEXT, spec.description) : null,
10295
+ ObjectType: null,
10296
+ ObjectPlacement: w.ref(placementId),
10297
+ Representation: null,
10298
+ LongName: null,
10299
+ CompositionType: {
10300
+ type: 3,
10301
+ value: spec.compositionType ?? "ELEMENT"
10302
+ },
10303
+ UsageType: {
10304
+ type: 3,
10305
+ value: spec.usageType
10306
+ },
10307
+ PredefinedType: {
10308
+ type: 3,
10309
+ value: spec.predefinedType ?? "NOTDEFINED"
10310
+ }
10311
+ });
10312
+ return {
10313
+ entityId,
10314
+ placementId
10315
+ };
10316
+ }
10317
+ function writeEarthworksFillEntity(w, guid, spec, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
10318
+ const entityId = w.nextId();
10319
+ w.writeLine({
10320
+ expressID: entityId,
10321
+ type: web_ifc.IFCEARTHWORKSFILL,
10322
+ GlobalId: w.mkType(web_ifc.IFCGLOBALLYUNIQUEID, guid),
10323
+ OwnerHistory: w.ref(ownerHistoryId),
10324
+ Name: w.mkType(web_ifc.IFCLABEL, spec.name),
10325
+ Description: null,
10326
+ ObjectType: null,
10327
+ ObjectPlacement: w.ref(localPlacementId),
10328
+ Representation: w.ref(productDefinitionShapeId),
10329
+ Tag: null,
10330
+ PredefinedType: {
10331
+ type: 3,
10332
+ value: spec.predefinedType ?? "NOTDEFINED"
10333
+ }
10334
+ });
10335
+ return entityId;
10336
+ }
10337
+ //#endregion
10338
+ //#region src/ifc-writer/tessellatedProductWriter.ts
10339
+ /** Write a tessellated body in the coordinate frame supplied by the caller. */
10340
+ function writeTessellatedProductGeometry(w, solid, geomSubContextId, parentPlacementId) {
10341
+ const placement3DId = writeAxis2Placement3D(w, [
10342
+ 0,
10343
+ 0,
10344
+ 0
10345
+ ]);
10346
+ const localPlacementId = w.nextId();
10347
+ w.writeLine({
10348
+ expressID: localPlacementId,
10349
+ type: web_ifc.IFCLOCALPLACEMENT,
10350
+ PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
10351
+ RelativePlacement: w.ref(placement3DId)
10352
+ });
10353
+ const tessellation = writeTessellation(w, solid, geomSubContextId, localPlacementId);
10354
+ return {
10355
+ localPlacementId,
10356
+ productDefinitionShapeId: tessellation.productDefinitionShapeId,
10357
+ tessellation
10358
+ };
10359
+ }
10360
+ //#endregion
10184
10361
  //#region src/ifc-writer/groupWriter.ts
10185
10362
  /**
10186
10363
  * Emits an IfcZone grouping object. `longName` (a descriptive label such as
@@ -11659,29 +11836,12 @@ function writeWindowCommonPset(w, ownerHistoryId, windowEntityId, spec) {
11659
11836
  //#region src/ifc-writer/proxyWriter.ts
11660
11837
  /**
11661
11838
  * Writes the IfcLocalPlacement (at origin, relative to parentPlacementId) and a
11662
- * tessellated body (IfcTriangulatedFaceSet) for a proxy's solid. The solid is
11663
- * exported in its authored coordinate frame; placement is identity because proxy
11664
- * solids carry their own world positions in the brepjs geometry.
11839
+ * tessellated body (IfcTriangulatedFaceSet) for a proxy's solid. Solid
11840
+ * coordinates are emitted as supplied; the caller is responsible for matching
11841
+ * them to the containing spatial structure's frame.
11665
11842
  */
11666
11843
  function writeProxyGeometry(w, spec, geomSubContextId, parentPlacementId) {
11667
- const placement3DId = writeAxis2Placement3D(w, [
11668
- 0,
11669
- 0,
11670
- 0
11671
- ]);
11672
- const localPlacementId = w.nextId();
11673
- w.writeLine({
11674
- expressID: localPlacementId,
11675
- type: web_ifc.IFCLOCALPLACEMENT,
11676
- PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
11677
- RelativePlacement: w.ref(placement3DId)
11678
- });
11679
- const tessellation = writeTessellation(w, spec.solid, geomSubContextId, localPlacementId);
11680
- return {
11681
- localPlacementId,
11682
- productDefinitionShapeId: tessellation.productDefinitionShapeId,
11683
- tessellation
11684
- };
11844
+ return writeTessellatedProductGeometry(w, spec.solid, geomSubContextId, parentPlacementId);
11685
11845
  }
11686
11846
  function writeProxyEntity(w, guid, name, predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
11687
11847
  const id = w.nextId();
@@ -11932,6 +12092,7 @@ var CONTAINABLE_CATEGORIES = /* @__PURE__ */ new Set([
11932
12092
  "SLAB",
11933
12093
  "BEAM",
11934
12094
  "COLUMN",
12095
+ "EARTHWORKS_FILL",
11935
12096
  "DOOR",
11936
12097
  "WINDOW"
11937
12098
  ]);
@@ -12051,7 +12212,9 @@ function hasSpatialStructure(api, modelId) {
12051
12212
  return [
12052
12213
  web_ifc.IFCSITE,
12053
12214
  web_ifc.IFCBUILDING,
12054
- web_ifc.IFCBUILDINGSTOREY
12215
+ web_ifc.IFCBUILDINGSTOREY,
12216
+ web_ifc.IFCBRIDGE,
12217
+ web_ifc.IFCBRIDGEPART
12055
12218
  ].some((t) => api.GetLineIDsWithType(modelId, t).size() > 0);
12056
12219
  }
12057
12220
  function checkGlobalIds(api, modelId, lineIds, lineCount) {
@@ -12095,6 +12258,7 @@ var KEY_ENTITY_NAMES = [
12095
12258
  "IfcSlab",
12096
12259
  "IfcBeam",
12097
12260
  "IfcColumn",
12261
+ "IfcEarthworksFill",
12098
12262
  "IfcRelContainedInSpatialStructure",
12099
12263
  "IfcRelAggregates",
12100
12264
  "IfcPropertySet"
@@ -12105,6 +12269,7 @@ var KEY_ENTITY_TYPES = [
12105
12269
  ["IfcSlab", web_ifc.IFCSLAB],
12106
12270
  ["IfcBeam", web_ifc.IFCBEAM],
12107
12271
  ["IfcColumn", web_ifc.IFCCOLUMN],
12272
+ ["IfcEarthworksFill", web_ifc.IFCEARTHWORKSFILL],
12108
12273
  ["IfcRelContainedInSpatialStructure", web_ifc.IFCRELCONTAINEDINSPATIALSTRUCTURE],
12109
12274
  ["IfcRelAggregates", web_ifc.IFCRELAGGREGATES],
12110
12275
  ["IfcPropertySet", web_ifc.IFCPROPERTYSET]
@@ -13097,7 +13262,7 @@ function checkQuantitySets(reader) {
13097
13262
  return issues;
13098
13263
  }
13099
13264
  function checkGeoreferencing(reader) {
13100
- if (!(reader.getLinesOfType(web_ifc.IFCBUILDING).length > 0)) return [];
13265
+ if (!(reader.getLinesOfType(web_ifc.IFCBUILDING).length > 0 || reader.getLinesOfType(web_ifc.IFCBRIDGE).length > 0)) return [];
13101
13266
  if (reader.getLinesOfType(web_ifc.IFCPROJECTEDCRS).length > 0 || reader.getLinesOfType(web_ifc.IFCGEOGRAPHICCRS).length > 0) return [];
13102
13267
  return [issue("warning", "NO_GEOREFERENCING", "Model contains a facility but no IfcProjectedCRS/IfcGeographicCRS (GRF003) — set ProjectSpec.crs")];
13103
13268
  }
@@ -13106,6 +13271,7 @@ function checkGeoreferencing(reader) {
13106
13271
  async function toIfc(model, meta) {
13107
13272
  const project = model.getProject();
13108
13273
  if (!project) return (0, brepjs.err)(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
13274
+ if ((model.getBridges().length > 0 || model.getBridgeParts().length > 0 || model.getEarthworksFills().length > 0) && (meta.ifcSchema ?? "IFC4") !== "IFC4X3") return (0, brepjs.err)(ifcError("IFC4X3_REQUIRED", "Bridge, Bridge Part, and Earthworks Fill entities require IFC4X3 serialization"));
13109
13275
  const authorName = [meta.author?.givenName, meta.author?.familyName].filter((p) => Boolean(p)).join(" ");
13110
13276
  const writerResult = await IfcWriter.create(meta.mvdViewDefinition, meta.ifcSchema, {
13111
13277
  author: authorName,
@@ -13119,6 +13285,9 @@ async function toIfc(model, meta) {
13119
13285
  const walls = model.getWalls();
13120
13286
  const slabs = model.getSlabs();
13121
13287
  const beams = model.getBeams();
13288
+ const bridges = model.getBridges();
13289
+ const bridgeParts = model.getBridgeParts();
13290
+ const earthworksFills = model.getEarthworksFills();
13122
13291
  const columns = model.getColumns();
13123
13292
  const doors = model.getDoors();
13124
13293
  const windows = model.getWindows();
@@ -13144,10 +13313,33 @@ async function toIfc(model, meta) {
13144
13313
  idMap.set(project.localId, projectExpressId);
13145
13314
  for (const el of elements) {
13146
13315
  if (el.category !== "SITE") continue;
13147
- const { entityId, placementId } = writeSite(w, el.guid, el.spec.name, ownerHistoryId);
13316
+ const parentId = findParentOf(el.localId, relationships);
13317
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13318
+ const { entityId, placementId } = writeSite(w, el.guid, el.spec, ownerHistoryId, parentPlacementId);
13148
13319
  idMap.set(el.localId, entityId);
13149
13320
  placementMap.set(el.localId, placementId);
13150
13321
  }
13322
+ for (const bridge of bridges) {
13323
+ const parentId = findParentOf(bridge.localId, relationships);
13324
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13325
+ const { entityId, placementId } = writeBridge(w, bridge.guid, bridge.spec, ownerHistoryId, parentPlacementId);
13326
+ idMap.set(bridge.localId, entityId);
13327
+ placementMap.set(bridge.localId, placementId);
13328
+ }
13329
+ const pendingParts = [...bridgeParts];
13330
+ while (pendingParts.length > 0) {
13331
+ const readyIndex = pendingParts.findIndex((candidate) => {
13332
+ const candidateParentId = findParentOf(candidate.localId, relationships);
13333
+ return candidateParentId === null || placementMap.has(candidateParentId);
13334
+ });
13335
+ const [part] = pendingParts.splice(readyIndex === -1 ? 0 : readyIndex, 1);
13336
+ if (part === void 0) break;
13337
+ const parentId = findParentOf(part.localId, relationships);
13338
+ const parentPlacementId = parentId !== null ? placementMap.get(parentId) ?? null : null;
13339
+ const { entityId, placementId } = writeBridgePart(w, part.guid, part.spec, ownerHistoryId, parentPlacementId);
13340
+ idMap.set(part.localId, entityId);
13341
+ placementMap.set(part.localId, placementId);
13342
+ }
13151
13343
  for (const el of elements) {
13152
13344
  if (el.category !== "BUILDING") continue;
13153
13345
  const parentSiteId = findParentOf(el.localId, relationships);
@@ -13241,6 +13433,15 @@ async function toIfc(model, meta) {
13241
13433
  placementMap.set(proxy.localId, localPlacementId);
13242
13434
  if (proxy.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, proxyExpressId, proxy.spec.customProperties);
13243
13435
  }
13436
+ for (const fill of earthworksFills) {
13437
+ const containingId = findContainerOf(fill.localId, relationships);
13438
+ const parentPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
13439
+ const { localPlacementId, productDefinitionShapeId } = writeTessellatedProductGeometry(w, fill.geometry, geomSubContextId, parentPlacementId);
13440
+ const expressId = writeEarthworksFillEntity(w, fill.guid, fill.spec, ownerHistoryId, localPlacementId, productDefinitionShapeId);
13441
+ idMap.set(fill.localId, expressId);
13442
+ placementMap.set(fill.localId, localPlacementId);
13443
+ if (fill.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, expressId, fill.spec.customProperties);
13444
+ }
13244
13445
  for (const [i, space] of spaces.entries()) {
13245
13446
  const containingId = findContainerOf(space.localId, relationships);
13246
13447
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
@@ -13663,6 +13864,7 @@ function collectGeometryIssues(model) {
13663
13864
  ["Beam", model.getBeams()],
13664
13865
  ["Column", model.getColumns()],
13665
13866
  ["Proxy", model.getProxies()],
13867
+ ["Earthworks Fill", model.getEarthworksFills()],
13666
13868
  ["Space", model.getSpaces()],
13667
13869
  ["Roof", model.getRoofs()],
13668
13870
  ["Footing", model.getFootings()],
@@ -13733,7 +13935,9 @@ var CATEGORY_BY_TYPE = /* @__PURE__ */ new Map([
13733
13935
  [web_ifc.IFCPROJECT, "PROJECT"],
13734
13936
  [web_ifc.IFCSITE, "SITE"],
13735
13937
  [web_ifc.IFCBUILDING, "BUILDING"],
13736
- [web_ifc.IFCBUILDINGSTOREY, "STOREY"]
13938
+ [web_ifc.IFCBUILDINGSTOREY, "STOREY"],
13939
+ [web_ifc.IFCBRIDGE, "BRIDGE"],
13940
+ [web_ifc.IFCBRIDGEPART, "BRIDGE_PART"]
13737
13941
  ]);
13738
13942
  function refId$1(ref) {
13739
13943
  const v = ref?.value;
@@ -14816,6 +15020,7 @@ var ELEMENT_TYPES = [
14816
15020
  [web_ifc.IFCRAILING, "RAILING"],
14817
15021
  [web_ifc.IFCCOVERING, "COVERING"],
14818
15022
  [web_ifc.IFCELEMENTASSEMBLY, "ELEMENT_ASSEMBLY"],
15023
+ [web_ifc.IFCEARTHWORKSFILL, "EARTHWORKS_FILL"],
14819
15024
  [web_ifc.IFCBUILDINGELEMENTPROXY, "PROXY"]
14820
15025
  ];
14821
15026
  /**
@@ -14893,14 +15098,17 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
14893
15098
  const psets = readPsets(reader, expressId).map(toImportedPset);
14894
15099
  const material = readMaterial(reader, expressId, scale);
14895
15100
  const classification = readClassification(reader, expressId);
14896
- const storeyExpressId = containment.get(expressId);
15101
+ const spatialStructureExpressId = containment.get(expressId);
14897
15102
  return {
14898
15103
  expressId,
14899
15104
  guid,
14900
15105
  name,
14901
15106
  category,
14902
15107
  ...predefinedType !== void 0 ? { predefinedType } : {},
14903
- ...storeyExpressId !== void 0 ? { storeyExpressId } : {},
15108
+ ...spatialStructureExpressId !== void 0 ? {
15109
+ spatialStructureExpressId,
15110
+ storeyExpressId: spatialStructureExpressId
15111
+ } : {},
14904
15112
  geometry,
14905
15113
  psets,
14906
15114
  material,
@@ -15900,6 +16108,115 @@ function parseSlabOpeningInput(input) {
15900
16108
  return (0, brepjs.ok)(result.data);
15901
16109
  }
15902
16110
  //#endregion
16111
+ //#region src/specs/spatialSpec.ts
16112
+ var unitVector = tuple([
16113
+ number(),
16114
+ number(),
16115
+ number()
16116
+ ]).refine((value) => Math.abs(value[0] ** 2 + value[1] ** 2 + value[2] ** 2 - 1) < 1e-6, { error: "must be a unit vector" });
16117
+ var spatialPlacementFields = {
16118
+ origin: tuple([
16119
+ number(),
16120
+ number(),
16121
+ number()
16122
+ ]).optional(),
16123
+ axisX: unitVector.optional(),
16124
+ axisZ: unitVector.optional()
16125
+ };
16126
+ function validateSpatialAxes(data, ctx) {
16127
+ const axisX = data.axisX ?? [
16128
+ 1,
16129
+ 0,
16130
+ 0
16131
+ ];
16132
+ const axisZ = data.axisZ ?? [
16133
+ 0,
16134
+ 0,
16135
+ 1
16136
+ ];
16137
+ const dot = axisX[0] * axisZ[0] + axisX[1] * axisZ[1] + axisX[2] * axisZ[2];
16138
+ if (Math.abs(dot) > 1e-6) ctx.addIssue({
16139
+ code: "custom",
16140
+ message: "axisX and axisZ must be orthogonal",
16141
+ path: ["axisZ"]
16142
+ });
16143
+ }
16144
+ var SiteSpecSchema = object({
16145
+ name: string().min(1),
16146
+ description: string().optional(),
16147
+ ...spatialPlacementFields,
16148
+ compositionType: _enum([
16149
+ "COMPLEX",
16150
+ "ELEMENT",
16151
+ "PARTIAL"
16152
+ ]).optional()
16153
+ }).superRefine(validateSpatialAxes);
16154
+ function parseSiteSpec(input) {
16155
+ const result = SiteSpecSchema.safeParse(input);
16156
+ return result.success ? (0, brepjs.ok)(result.data) : (0, brepjs.err)(specError("INVALID_SITE_SPEC", result.error.message, result.error));
16157
+ }
16158
+ //#endregion
16159
+ //#region src/specs/infrastructureSpec.ts
16160
+ var composition = _enum([
16161
+ "COMPLEX",
16162
+ "ELEMENT",
16163
+ "PARTIAL"
16164
+ ]).optional();
16165
+ var BridgeSpecSchema = object({
16166
+ name: string().min(1),
16167
+ description: string().optional(),
16168
+ ...spatialPlacementFields,
16169
+ compositionType: composition,
16170
+ predefinedType: _enum([
16171
+ "ARCHED",
16172
+ "CABLE_STAYED",
16173
+ "CANTILEVER",
16174
+ "CULVERT",
16175
+ "FRAMEWORK",
16176
+ "GIRDER",
16177
+ "SUSPENSION",
16178
+ "TRUSS",
16179
+ "USERDEFINED",
16180
+ "NOTDEFINED"
16181
+ ]).optional()
16182
+ }).superRefine(validateSpatialAxes);
16183
+ var BridgePartSpecSchema = object({
16184
+ name: string().min(1),
16185
+ description: string().optional(),
16186
+ ...spatialPlacementFields,
16187
+ compositionType: composition,
16188
+ usageType: _enum([
16189
+ "LATERAL",
16190
+ "LONGITUDINAL",
16191
+ "REGION",
16192
+ "VERTICAL",
16193
+ "USERDEFINED",
16194
+ "NOTDEFINED"
16195
+ ]),
16196
+ predefinedType: _enum([
16197
+ "ABUTMENT",
16198
+ "DECK",
16199
+ "DECK_SEGMENT",
16200
+ "FOUNDATION",
16201
+ "PIER",
16202
+ "PIER_SEGMENT",
16203
+ "PYLON",
16204
+ "SUBSTRUCTURE",
16205
+ "SUPERSTRUCTURE",
16206
+ "SURFACESTRUCTURE",
16207
+ "USERDEFINED",
16208
+ "NOTDEFINED"
16209
+ ]).optional()
16210
+ }).superRefine(validateSpatialAxes);
16211
+ function parseBridgeSpec(input) {
16212
+ const result = BridgeSpecSchema.safeParse(input);
16213
+ return result.success ? (0, brepjs.ok)(result.data) : (0, brepjs.err)(specError("INVALID_BRIDGE_SPEC", result.error.message, result.error));
16214
+ }
16215
+ function parseBridgePartSpec(input) {
16216
+ const result = BridgePartSpecSchema.safeParse(input);
16217
+ return result.success ? (0, brepjs.ok)(result.data) : (0, brepjs.err)(specError("INVALID_BRIDGE_PART_SPEC", result.error.message, result.error));
16218
+ }
16219
+ //#endregion
15903
16220
  //#region src/cobie/cobieExport.ts
15904
16221
  /**
15905
16222
  * Physical-element categories that become COBie Component rows. Spatial
@@ -15921,7 +16238,8 @@ var COMPONENT_CATEGORIES = /* @__PURE__ */ new Set([
15921
16238
  "STAIR",
15922
16239
  "RAMP",
15923
16240
  "RAILING",
15924
- "COVERING"
16241
+ "COVERING",
16242
+ "EARTHWORKS_FILL"
15925
16243
  ]);
15926
16244
  /**
15927
16245
  * Title-cased label used as the COBie Type/Component name prefix for a category,
@@ -15943,7 +16261,8 @@ var CATEGORY_LABEL = {
15943
16261
  STAIR: "Stair",
15944
16262
  RAMP: "Ramp",
15945
16263
  RAILING: "Railing",
15946
- COVERING: "Covering"
16264
+ COVERING: "Covering",
16265
+ EARTHWORKS_FILL: "EarthworksFill"
15947
16266
  };
15948
16267
  function categoryLabel(category) {
15949
16268
  return CATEGORY_LABEL[category] ?? category;
@@ -27620,8 +27939,11 @@ function assignNum(target, key, value) {
27620
27939
  * while the IR path serves the viewport and dedup. GlobalIds derive from
27621
27940
  * families key paths (stable under reordering), not insertion order.
27622
27941
  *
27623
- * Scope: Storey containers, Wall/Slab/Column/Beam/Roof/Stair elements, and wall openings — a
27624
- * fill-role void (Door/Window family) maps onto addDoor/addWindow, which cut
27942
+ * Scope: building Storey containers; civil Site/Bridge/recursive Bridge Part
27943
+ * structure and Earthworks Fill bodies; Wall/Slab/Column/Beam/Roof/Stair,
27944
+ * Footing/Pile, Railing/Ramp, Covering/CurtainWall, and Space elements; and
27945
+ * wall openings — a fill-role void (Door/Window family) maps onto
27946
+ * addDoor/addWindow, which cut
27625
27947
  * the wall and wire IfcRelVoidsElement + IfcRelFillsElement; the opening and
27626
27948
  * filler GlobalIds derive from the synthesized key paths. Anonymous (non-fill)
27627
27949
  * voids are rejected: they cut only the IR/viewport geometry, and exporting
@@ -27675,12 +27997,71 @@ var SPEC_ROUTES = {
27675
27997
  stair: {
27676
27998
  parse: parseStairSpec,
27677
27999
  add: (m, spec, key) => m.addStair(spec, { stableKey: key }),
27678
- input: stairSpecInput
28000
+ input: flightsSpecInput
28001
+ },
28002
+ footing: {
28003
+ parse: parseFootingSpec,
28004
+ add: (m, spec, key) => m.addFooting(spec, { stableKey: key })
28005
+ },
28006
+ pile: {
28007
+ parse: parsePileSpec,
28008
+ add: (m, spec, key) => m.addPile(spec, { stableKey: key })
28009
+ },
28010
+ railing: {
28011
+ parse: parseRailingSpec,
28012
+ add: (m, spec, key) => m.addRailing(spec, { stableKey: key })
28013
+ },
28014
+ ramp: {
28015
+ parse: parseRampSpec,
28016
+ add: (m, spec, key) => m.addRamp(spec, { stableKey: key }),
28017
+ input: flightsSpecInput
28018
+ },
28019
+ covering: {
28020
+ parse: parseCoveringSpec,
28021
+ add: (m, spec, key) => m.addCovering(spec, void 0, { stableKey: key })
28022
+ },
28023
+ curtainWall: {
28024
+ parse: parseCurtainWallSpec,
28025
+ add: (m, spec, key) => m.addCurtainWall(spec, { stableKey: key })
28026
+ },
28027
+ space: {
28028
+ parse: parseSpaceSpec,
28029
+ add: (m, spec, key) => m.addSpace(spec, { stableKey: key })
28030
+ }
28031
+ };
28032
+ var CIVIL_PRODUCT_ROUTES = {
28033
+ beam: {
28034
+ archetype: "beam",
28035
+ roles: [
28036
+ "beam",
28037
+ "cross-girder",
28038
+ "girder"
28039
+ ]
28040
+ },
28041
+ column: {
28042
+ archetype: "column",
28043
+ roles: ["pier-stem"]
28044
+ },
28045
+ footing: {
28046
+ archetype: "footing",
28047
+ roles: ["pad"]
28048
+ },
28049
+ railing: {
28050
+ archetype: "railing",
28051
+ roles: ["guardrail"]
28052
+ },
28053
+ slab: {
28054
+ archetype: "slab",
28055
+ roles: ["deck"]
28056
+ },
28057
+ wall: {
28058
+ archetype: "wall",
28059
+ roles: ["wall"]
27679
28060
  }
27680
28061
  };
27681
- /** StairSpec has no top-level origin — placement lives per flight — so the
27682
- * element's folded translate lands on every flight's origin instead. */
27683
- function stairSpecInput(el) {
28062
+ /** Stair and ramp specs have no top-level origin — placement lives per flight —
28063
+ * so the element's folded translate lands on every flight's origin instead. */
28064
+ function flightsSpecInput(el) {
27684
28065
  const base = specInput(el);
27685
28066
  const fold = base["origin"] ?? [
27686
28067
  0,
@@ -27722,14 +28103,145 @@ var NAME_ARCHETYPES = {
27722
28103
  Roof: "roof",
27723
28104
  Stair: "stair",
27724
28105
  Door: "door",
27725
- Window: "window"
28106
+ Window: "window",
28107
+ Footing: "footing",
28108
+ Pile: "pile",
28109
+ Railing: "railing",
28110
+ Ramp: "ramp",
28111
+ Covering: "covering",
28112
+ CurtainWall: "curtainWall",
28113
+ Space: "space"
27726
28114
  };
27727
28115
  function archetypeFor(el) {
27728
- return el.archetype ?? NAME_ARCHETYPES[el.type];
28116
+ return el.archetype ?? lookup(NAME_ARCHETYPES, el.type);
27729
28117
  }
27730
28118
  function specRoute(archetype) {
27731
28119
  return archetype !== void 0 && Object.hasOwn(SPEC_ROUTES, archetype) ? SPEC_ROUTES[archetype] : void 0;
27732
28120
  }
28121
+ /** Own-property lookup: semantics categories and roles are free-form author
28122
+ * strings, so Object.prototype keys must never resolve to a route. */
28123
+ function lookup(map, key) {
28124
+ return Object.hasOwn(map, key) ? map[key] : void 0;
28125
+ }
28126
+ function civilProductArchetype(el) {
28127
+ if (el.semantics?.kind !== "product") return void 0;
28128
+ const definition = lookup(CIVIL_PRODUCT_ROUTES, el.semantics.category);
28129
+ if (definition === void 0 || !definition.roles.includes(el.semantics.role)) return void 0;
28130
+ return definition.archetype;
28131
+ }
28132
+ function semanticDimension(el, ...names) {
28133
+ if (el.semantics?.kind !== "product") return void 0;
28134
+ for (const name of names) {
28135
+ const value = el.semantics.dimensionsMm[name];
28136
+ if (value !== void 0) return value;
28137
+ }
28138
+ }
28139
+ /** Beam/column solids centre their cross-section on the placement axis, while
28140
+ * the semantics envelope sits corner-anchored at the folded IR origin — shift
28141
+ * the origin to the envelope centre so the exported body occupies the same
28142
+ * space as the viewport body. */
28143
+ function centreOrigin(base, shift) {
28144
+ const origin = base["origin"] ?? [
28145
+ 0,
28146
+ 0,
28147
+ 0
28148
+ ];
28149
+ return [
28150
+ origin[0] + shift[0],
28151
+ origin[1] + shift[1],
28152
+ origin[2] + shift[2]
28153
+ ];
28154
+ }
28155
+ /**
28156
+ * Adapts target-independent civil envelope dimensions onto existing typed BIM
28157
+ * spec inputs. Reference infrastructure Families intentionally author domain
28158
+ * props such as `depth`, `capOffset`, and compound railing profiles rather than
28159
+ * BIM `profile`/`thickness` fields; semantics is the stable projection seam.
28160
+ */
28161
+ function civilProductSpecInput(el) {
28162
+ const base = specInput(el);
28163
+ if (el.semantics?.kind !== "product") return base;
28164
+ const length = semanticDimension(el, "length");
28165
+ const width = semanticDimension(el, "width");
28166
+ const height = semanticDimension(el, "height");
28167
+ switch (el.semantics.category) {
28168
+ case "beam": {
28169
+ const synthesized = base["profile"] === void 0 && width !== void 0 && height !== void 0 ? {
28170
+ profile: {
28171
+ kind: "RECTANGULAR",
28172
+ width,
28173
+ height
28174
+ },
28175
+ shift: [
28176
+ 0,
28177
+ width / 2,
28178
+ height / 2
28179
+ ]
28180
+ } : void 0;
28181
+ return {
28182
+ ...base,
28183
+ ...synthesized !== void 0 ? {
28184
+ origin: centreOrigin(base, synthesized.shift),
28185
+ profile: synthesized.profile
28186
+ } : {},
28187
+ length: length ?? base["length"],
28188
+ predefinedType: base["predefinedType"] ?? "BEAM"
28189
+ };
28190
+ }
28191
+ case "column": {
28192
+ const synthesized = base["profile"] === void 0 && length !== void 0 && width !== void 0 ? {
28193
+ profile: {
28194
+ kind: "RECTANGULAR",
28195
+ width: length,
28196
+ height: width
28197
+ },
28198
+ shift: [
28199
+ length / 2,
28200
+ width / 2,
28201
+ 0
28202
+ ]
28203
+ } : void 0;
28204
+ return {
28205
+ ...base,
28206
+ ...synthesized !== void 0 ? {
28207
+ origin: centreOrigin(base, synthesized.shift),
28208
+ profile: synthesized.profile
28209
+ } : {},
28210
+ height: height ?? base["height"],
28211
+ predefinedType: base["predefinedType"] ?? "COLUMN"
28212
+ };
28213
+ }
28214
+ case "footing": return {
28215
+ ...base,
28216
+ length: length ?? base["length"],
28217
+ width: width ?? base["width"],
28218
+ thickness: height ?? base["thickness"],
28219
+ predefinedType: base["predefinedType"] ?? "PAD_FOOTING"
28220
+ };
28221
+ case "railing": return {
28222
+ ...base,
28223
+ length: length ?? base["length"],
28224
+ thickness: width ?? base["thickness"],
28225
+ height: height ?? base["height"],
28226
+ predefinedType: base["predefinedType"] ?? "GUARDRAIL"
28227
+ };
28228
+ case "slab": return {
28229
+ ...base,
28230
+ length: length ?? base["length"],
28231
+ width: width ?? base["width"],
28232
+ thickness: height ?? base["thickness"],
28233
+ predefinedType: base["predefinedType"] ?? "FLOOR"
28234
+ };
28235
+ case "wall": return {
28236
+ ...base,
28237
+ length: length ?? base["length"],
28238
+ thickness: width ?? base["thickness"],
28239
+ height: height ?? base["height"],
28240
+ predefinedType: base["predefinedType"] ?? "NOTDEFINED"
28241
+ };
28242
+ default: return base;
28243
+ }
28244
+ }
27733
28245
  /** Total of the resolved geometry's OUTER literal translate chain. The
27734
28246
  * transform vocabulary is translate-only, so frame differences are exact
27735
28247
  * subtractions. Parameter-driven translations stop the peel. */
@@ -27755,15 +28267,14 @@ function peelTranslates(node) {
27755
28267
  moved
27756
28268
  };
27757
28269
  }
27758
- /** True when the element's transform PROP carries a rotation. The spec path
28270
+ /** True when the element's rendered local transform carries a rotation. The spec path
27759
28271
  * folds only translations into IfcLocalPlacement (walls orient via `axisX`),
27760
28272
  * so a tRotate placement would export un-rotated while the viewport shows it
27761
28273
  * rotated — reject instead of diverging. Rotations a family render bakes
27762
28274
  * into its own body geometry (e.g. a circular beam oriented along axisX) are
27763
28275
  * fine: the spec rebuilds that body parametrically from props. */
27764
28276
  function hasRotateOp(el) {
27765
- const ops = el.props["transform"];
27766
- return Array.isArray(ops) && ops.some((op) => typeof op === "object" && op !== null && op.op === "rotate");
28277
+ return el.localTransforms.some((op) => op.op === "rotate");
27767
28278
  }
27768
28279
  /** Fold the resolved geometry's outer translate chain into the spec placement
27769
28280
  * origin, so IfcLocalPlacement matches the IR world frame no matter where the
@@ -27809,10 +28320,14 @@ var COMMON_PSET_FIELDS = {
27809
28320
  function collectSpecProps(el) {
27810
28321
  const out = {};
27811
28322
  const material = el.attributes["material"];
27812
- if (typeof material === "string" && el.props["materialName"] === void 0) out["materialName"] = material;
28323
+ const semanticsMaterial = el.semantics?.kind === "product" ? el.semantics.material : void 0;
28324
+ if (el.props["materialName"] === void 0) {
28325
+ if (typeof material === "string") out["materialName"] = material;
28326
+ else if (semanticsMaterial !== void 0) out["materialName"] = semanticsMaterial;
28327
+ }
27813
28328
  const psets = el.attributes["psets"];
28329
+ const custom = {};
27814
28330
  if (psets && typeof psets === "object") {
27815
- const custom = {};
27816
28331
  const ownCommonPset = `Pset_${el.type}Common`;
27817
28332
  for (const [psetName, fields] of Object.entries(psets)) {
27818
28333
  if (!fields || typeof fields !== "object") continue;
@@ -27825,14 +28340,15 @@ function collectSpecProps(el) {
27825
28340
  if (Object.keys(values).length > 0) custom[psetName] = values;
27826
28341
  }
27827
28342
  }
27828
- if (Object.keys(custom).length > 0) {
27829
- const declared = el.props["customProperties"];
27830
- out["customProperties"] = declared && typeof declared === "object" ? {
27831
- ...custom,
27832
- ...declared
27833
- } : custom;
27834
- }
27835
28343
  }
28344
+ const declared = el.props["customProperties"];
28345
+ if (declared && typeof declared === "object" && !Array.isArray(declared)) for (const [psetName, fields] of Object.entries(declared)) {
28346
+ if (!fields || typeof fields !== "object" || Array.isArray(fields)) continue;
28347
+ const values = {};
28348
+ for (const [field, value] of Object.entries(fields)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") values[field] = value;
28349
+ if (Object.keys(values).length > 0) custom[psetName] = values;
28350
+ }
28351
+ if (Object.keys(custom).length > 0) out["customProperties"] = custom;
27836
28352
  return out;
27837
28353
  }
27838
28354
  function addFill(model, fill, input, identity) {
@@ -27849,35 +28365,49 @@ function addFill(model, fill, input, identity) {
27849
28365
  }
27850
28366
  return (0, brepjs.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'`));
27851
28367
  }
27852
- /** Materialize an unrouted element's IR and add it as a proxy. The body is
27853
- * authoritative for a proxy (no parametric spec to diverge from), so baked
27854
- * transforms — rotations included — are fine here. */
27855
- function addProxyElement(model, el, evaluator) {
28368
+ function materializeOwnedSolid(el, evaluator, errors) {
27856
28369
  const evaluated = evaluator.evaluate(el.geometry);
27857
- if (!evaluated.ok) return (0, brepjs.err)(specError("FAMILIES_PROXY_EVAL_FAILED", `familiesToBim: '${el.keyPath}' failed to materialize for the proxy route: ${evaluated.error.message}`, evaluated.error));
28370
+ if (!evaluated.ok) return (0, brepjs.err)(specError(errors.evalCode, `familiesToBim: '${el.keyPath}' failed to materialize for the ${errors.routeName} route: ${evaluated.error.message}`, evaluated.error));
27858
28371
  let source = evaluated.value;
27859
28372
  if (!(0, brepjs.isSolid)(source)) {
27860
28373
  const solids = (0, brepjs.getSolids)(source);
27861
28374
  const only = solids.length === 1 ? solids[0] : void 0;
27862
- if (only === void 0) return (0, brepjs.err)(specError("FAMILIES_PROXY_NOT_SOLID", `familiesToBim: '${el.keyPath}' materialized to ${solids.length} solids — a proxy body must be exactly one`));
28375
+ if (only === void 0) return (0, brepjs.err)(specError(errors.notSolidCode, `familiesToBim: '${el.keyPath}' materialized to ${solids.length} solids — the ${errors.routeName} body must be exactly one`));
27863
28376
  source = only;
27864
28377
  }
27865
28378
  const copy = (0, brepjs.clone)(source);
27866
- if (!copy.ok) return (0, brepjs.err)(specError("FAMILIES_PROXY_EVAL_FAILED", `familiesToBim: '${el.keyPath}' could not copy the materialized body`, copy.error));
28379
+ if (!copy.ok) return (0, brepjs.err)(specError(errors.evalCode, `familiesToBim: '${el.keyPath}' could not copy the materialized body`, copy.error));
27867
28380
  const valid = (0, brepjs.validSolid)(copy.value);
27868
28381
  if (!valid.ok) {
27869
28382
  copy.value[Symbol.dispose]();
27870
- return (0, brepjs.err)(specError("FAMILIES_PROXY_INVALID", `familiesToBim: '${el.keyPath}' materialized to an invalid solid: ${valid.error}`));
28383
+ return (0, brepjs.err)(specError(errors.invalidCode, `familiesToBim: '${el.keyPath}' materialized to an invalid solid: ${valid.error}`));
27871
28384
  }
28385
+ return (0, brepjs.ok)(valid.value);
28386
+ }
28387
+ /** Materialize an unrouted element's IR and add it as a proxy. The body is
28388
+ * authoritative for a proxy (no parametric spec to diverge from), so baked
28389
+ * transforms — rotations included — are fine here. */
28390
+ function addProxyElement(model, el, evaluator, projectedSpatialTranslation) {
28391
+ const body = materializeOwnedSolid(el, evaluator, {
28392
+ evalCode: "FAMILIES_PROXY_EVAL_FAILED",
28393
+ notSolidCode: "FAMILIES_PROXY_NOT_SOLID",
28394
+ invalidCode: "FAMILIES_PROXY_INVALID",
28395
+ routeName: "proxy"
28396
+ });
28397
+ if (!body.ok) return body;
28398
+ const localized = localizeOwnedSolid(el, body.value, projectedSpatialTranslation, "FAMILIES_PROXY_LOCALIZE_FAILED", "spatial parent");
28399
+ if (!localized.ok) return localized;
27872
28400
  const nameAttr = el.attributes["name"];
27873
28401
  const materialProp = el.props["materialName"];
27874
28402
  const specProps = collectSpecProps(el);
27875
- return model.addProxy({
28403
+ const added = model.addProxy({
27876
28404
  name: typeof nameAttr === "string" ? nameAttr : el.type,
27877
- solid: valid.value,
28405
+ solid: localized.value,
27878
28406
  materialName: typeof materialProp === "string" ? materialProp : specProps["materialName"],
27879
28407
  customProperties: specProps["customProperties"]
27880
28408
  }, { stableKey: el.keyPath });
28409
+ if (!added.ok) localized.value[Symbol.dispose]();
28410
+ return added;
27881
28411
  }
27882
28412
  /** Map a wall's synthesized Opening children onto addDoor/addWindow. The
27883
28413
  * wall-relative offsets come from the void geometry's frame minus the host's:
@@ -27927,38 +28457,296 @@ function requireKeyed(el) {
27927
28457
  if (el.keyed) return (0, brepjs.ok)(void 0);
27928
28458
  return (0, brepjs.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)`));
27929
28459
  }
28460
+ var BRIDGE_ROLE = {
28461
+ arched: "ARCHED",
28462
+ "cable-stayed": "CABLE_STAYED",
28463
+ cantilever: "CANTILEVER",
28464
+ culvert: "CULVERT",
28465
+ framework: "FRAMEWORK",
28466
+ girder: "GIRDER",
28467
+ suspension: "SUSPENSION",
28468
+ truss: "TRUSS"
28469
+ };
28470
+ var BRIDGE_PART_ROLE = {
28471
+ abutment: "ABUTMENT",
28472
+ deck: "DECK",
28473
+ "deck-segment": "DECK_SEGMENT",
28474
+ foundation: "FOUNDATION",
28475
+ pier: "PIER",
28476
+ "pier-segment": "PIER_SEGMENT",
28477
+ pylon: "PYLON",
28478
+ substructure: "SUBSTRUCTURE",
28479
+ superstructure: "SUPERSTRUCTURE",
28480
+ "surface-structure": "SURFACESTRUCTURE"
28481
+ };
28482
+ var CIVIL_COMPOSITION = {
28483
+ collection: "COMPLEX",
28484
+ element: "ELEMENT",
28485
+ partial: "PARTIAL"
28486
+ };
28487
+ var CIVIL_USAGE = {
28488
+ lateral: "LATERAL",
28489
+ longitudinal: "LONGITUDINAL",
28490
+ regional: "REGION",
28491
+ vertical: "VERTICAL"
28492
+ };
28493
+ var EARTHWORKS_FILL_ROLE = {
28494
+ backfill: "BACKFILL",
28495
+ counterweight: "COUNTERWEIGHT",
28496
+ embankment: "EMBANKMENT",
28497
+ "slope-fill": "SLOPEFILL",
28498
+ subgrade: "SUBGRADE",
28499
+ "subgrade-bed": "SUBGRADEBED",
28500
+ "transition-section": "TRANSITIONSECTION"
28501
+ };
28502
+ function unsupportedCivilRole(el, entity) {
28503
+ return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: unsupported ${entity} role '${el.semantics?.role ?? ""}' at '${el.keyPath}'`));
28504
+ }
28505
+ function civilSpatialKind(el) {
28506
+ const semantics = el.semantics;
28507
+ if (semantics?.kind === "site" && semantics.category === "site") return "site";
28508
+ if (semantics?.kind === "facility" && semantics.category === "bridge") return "bridge";
28509
+ if (semantics?.kind === "spatial-part" && semantics.category === "bridge-part") return "bridge-part";
28510
+ }
28511
+ function isCivilSpatialIntent(el) {
28512
+ return el.semantics?.kind === "site" || el.semantics?.kind === "facility" || el.semantics?.kind === "spatial-part";
28513
+ }
28514
+ function hasCivilSpatialIntent(el) {
28515
+ return isCivilSpatialIntent(el) || el.children.some(hasCivilSpatialIntent);
28516
+ }
28517
+ function isEarthworksFillOccurrence(el) {
28518
+ return el.semantics?.kind === "product" && el.semantics.category === "earthworks-fill";
28519
+ }
28520
+ function semanticName(el) {
28521
+ const name = el.attributes["name"];
28522
+ if (typeof name === "string" && name.trim().length > 0) return name;
28523
+ const semanticNameValue = el.semantics?.properties?.["name"];
28524
+ return typeof semanticNameValue === "string" && semanticNameValue.trim().length > 0 ? semanticNameValue : el.keyPath;
28525
+ }
28526
+ var ZERO_TRANSLATION = [
28527
+ 0,
28528
+ 0,
28529
+ 0
28530
+ ];
28531
+ function addTranslation(a, b) {
28532
+ return [
28533
+ a[0] + b[0],
28534
+ a[1] + b[1],
28535
+ a[2] + b[2]
28536
+ ];
28537
+ }
28538
+ function subtractTranslation(a, b) {
28539
+ return [
28540
+ a[0] - b[0],
28541
+ a[1] - b[1],
28542
+ a[2] - b[2]
28543
+ ];
28544
+ }
28545
+ function hasTranslation(value) {
28546
+ return value[0] !== 0 || value[1] !== 0 || value[2] !== 0;
28547
+ }
28548
+ var DEFAULT_AXIS_X = [
28549
+ 1,
28550
+ 0,
28551
+ 0
28552
+ ];
28553
+ var DEFAULT_AXIS_Z = [
28554
+ 0,
28555
+ 0,
28556
+ 1
28557
+ ];
28558
+ function axisEquals(value, axis) {
28559
+ return Array.isArray(value) && value.length === 3 && value.every((component, i) => typeof component === "number" && component === axis[i]);
28560
+ }
28561
+ /** Civil descendant relativization is translation-only, so a civil frame with
28562
+ * non-default axes would rotate the exported hierarchy away from the IR. */
28563
+ function hasRotatedAxes(el) {
28564
+ const axisX = el.props["axisX"];
28565
+ const axisZ = el.props["axisZ"];
28566
+ return axisX !== void 0 && !axisEquals(axisX, DEFAULT_AXIS_X) || axisZ !== void 0 && !axisEquals(axisZ, DEFAULT_AXIS_Z);
28567
+ }
28568
+ function authoredSpecOrigin(el) {
28569
+ const origin = el.props["origin"];
28570
+ return Array.isArray(origin) && origin.length === 3 && origin.every((c) => typeof c === "number") ? origin : ZERO_TRANSLATION;
28571
+ }
28572
+ function localizeOwnedSolid(el, body, projectedSpatialTranslation, errorCode, frameName) {
28573
+ if (!hasTranslation(projectedSpatialTranslation)) return (0, brepjs.ok)(body);
28574
+ try {
28575
+ const localized = (0, brepjs.translate)(body, [
28576
+ -projectedSpatialTranslation[0],
28577
+ -projectedSpatialTranslation[1],
28578
+ -projectedSpatialTranslation[2]
28579
+ ]);
28580
+ body[Symbol.dispose]();
28581
+ return (0, brepjs.ok)(localized);
28582
+ } catch (cause) {
28583
+ body[Symbol.dispose]();
28584
+ return (0, brepjs.err)(specError(errorCode, `familiesToBim: '${el.keyPath}' could not move its body into the ${frameName} frame`, cause));
28585
+ }
28586
+ }
28587
+ function authoredTranslation(el) {
28588
+ const total = [
28589
+ 0,
28590
+ 0,
28591
+ 0
28592
+ ];
28593
+ for (const op of el.localTransforms) {
28594
+ if (op.op !== "translate") continue;
28595
+ total[0] += op.v[0];
28596
+ total[1] += op.v[1];
28597
+ total[2] += op.v[2];
28598
+ }
28599
+ return total;
28600
+ }
28601
+ function civilSpatialInput(el, localTranslation) {
28602
+ const semantics = el.semantics;
28603
+ const composition = semantics !== void 0 && "composition" in semantics ? CIVIL_COMPOSITION[semantics.composition] : void 0;
28604
+ const origin = addTranslation(el.props["origin"] ?? ZERO_TRANSLATION, localTranslation);
28605
+ return {
28606
+ name: semanticName(el),
28607
+ ...el.props["origin"] !== void 0 || hasTranslation(localTranslation) ? { origin } : {},
28608
+ ...el.props["axisX"] !== void 0 ? { axisX: el.props["axisX"] } : {},
28609
+ ...el.props["axisZ"] !== void 0 ? { axisZ: el.props["axisZ"] } : {},
28610
+ ...composition !== void 0 ? { compositionType: composition } : {}
28611
+ };
28612
+ }
28613
+ function civilParentAccepts(kind, parent) {
28614
+ if (kind === "site") return parent === "project";
28615
+ if (kind === "bridge") return parent === "site";
28616
+ return parent === "bridge" || parent === "bridge-part";
28617
+ }
28618
+ function addCivilSpatialOccurrence(model, el, kind, localTranslation) {
28619
+ if (kind === "site") {
28620
+ if (el.semantics?.role !== "transport-site") return unsupportedCivilRole(el, "Site");
28621
+ const parsed = parseSiteSpec(civilSpatialInput(el, localTranslation));
28622
+ return parsed.ok ? model.addSite(parsed.value, { stableKey: el.keyPath }) : parsed;
28623
+ }
28624
+ if (kind === "bridge") {
28625
+ const predefinedType = lookup(BRIDGE_ROLE, el.semantics?.role ?? "");
28626
+ if (predefinedType === void 0) return unsupportedCivilRole(el, "Bridge");
28627
+ const parsed = parseBridgeSpec({
28628
+ ...civilSpatialInput(el, localTranslation),
28629
+ predefinedType
28630
+ });
28631
+ return parsed.ok ? model.addBridge(parsed.value, { stableKey: el.keyPath }) : parsed;
28632
+ }
28633
+ const subdivision = el.semantics?.kind === "spatial-part" ? el.semantics.subdivision : void 0;
28634
+ const predefinedType = lookup(BRIDGE_PART_ROLE, el.semantics?.role ?? "");
28635
+ if (predefinedType === void 0) return unsupportedCivilRole(el, "Bridge Part");
28636
+ const parsed = parseBridgePartSpec({
28637
+ ...civilSpatialInput(el, localTranslation),
28638
+ predefinedType,
28639
+ usageType: subdivision !== void 0 ? CIVIL_USAGE[subdivision] : "NOTDEFINED"
28640
+ });
28641
+ return parsed.ok ? model.addBridgePart(parsed.value, { stableKey: el.keyPath }) : parsed;
28642
+ }
28643
+ function relativeSpecInput(input, projectedSpatialTranslation) {
28644
+ const relativeOrigin = (origin) => Array.isArray(origin) && origin.length === 3 && origin.every((value) => typeof value === "number") ? subtractTranslation(origin, projectedSpatialTranslation) : origin;
28645
+ if (Array.isArray(input["flights"])) {
28646
+ const flights = input["flights"];
28647
+ return {
28648
+ ...input,
28649
+ flights: flights.map((flight) => {
28650
+ if (typeof flight !== "object" || flight === null) return flight;
28651
+ const flightInput = flight;
28652
+ return {
28653
+ ...flightInput,
28654
+ origin: relativeOrigin(flightInput["origin"])
28655
+ };
28656
+ })
28657
+ };
28658
+ }
28659
+ return {
28660
+ ...input,
28661
+ origin: relativeOrigin(input["origin"])
28662
+ };
28663
+ }
28664
+ function addEarthworksFillElement(model, el, evaluator, projectedSpatialTranslation) {
28665
+ if (el.semantics?.kind !== "product") return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: '${el.keyPath}' is not authored as a civil Product`));
28666
+ const predefinedType = lookup(EARTHWORKS_FILL_ROLE, el.semantics.role);
28667
+ if (predefinedType === void 0) return unsupportedCivilRole(el, "Earthworks Fill");
28668
+ const body = materializeOwnedSolid(el, evaluator, {
28669
+ evalCode: "FAMILIES_EARTHWORKS_EVAL_FAILED",
28670
+ notSolidCode: "FAMILIES_EARTHWORKS_NOT_SOLID",
28671
+ invalidCode: "FAMILIES_EARTHWORKS_INVALID",
28672
+ routeName: "Earthworks Fill"
28673
+ });
28674
+ if (!body.ok) return body;
28675
+ const localized = localizeOwnedSolid(el, body.value, projectedSpatialTranslation, "FAMILIES_EARTHWORKS_LOCALIZE_FAILED", "Bridge Part");
28676
+ if (!localized.ok) return localized;
28677
+ const specProps = collectSpecProps(el);
28678
+ const authoredMaterial = el.props["materialName"];
28679
+ const added = model.addEarthworksFill({
28680
+ name: semanticName(el),
28681
+ solid: localized.value,
28682
+ materialName: typeof authoredMaterial === "string" ? authoredMaterial : specProps["materialName"] ?? el.semantics.material,
28683
+ predefinedType,
28684
+ customProperties: specProps["customProperties"]
28685
+ }, { stableKey: el.keyPath });
28686
+ if (!added.ok) localized.value[Symbol.dispose]();
28687
+ return added;
28688
+ }
27930
28689
  /**
27931
28690
  * Project a resolved families tree into an eager BimModel. The caller owns
27932
28691
  * the returned model (`using`); families stays domain-neutral — this adapter
27933
28692
  * is where families types meet the IFC vocabulary.
27934
28693
  */
27935
28694
  function familiesToBim(root, options) {
28695
+ const usesAuthoredCivilHierarchy = hasCivilSpatialIntent(root);
28696
+ if (usesAuthoredCivilHierarchy) {
28697
+ const keyed = requireKeyed(root);
28698
+ if (!keyed.ok) return keyed;
28699
+ }
27936
28700
  const model = new BimModel();
27937
- const initResult = model.init(options.project);
28701
+ const initResult = model.init(options.project, usesAuthoredCivilHierarchy ? { stableKey: civilSpatialKind(root) !== void 0 ? `${root.keyPath}#project` : root.keyPath } : void 0);
27938
28702
  if (!initResult.ok) return initResult;
27939
- const siteResult = model.addSite({ name: options.siteName ?? "Site" });
27940
- if (!siteResult.ok) {
27941
- model[Symbol.dispose]();
27942
- return siteResult;
27943
- }
27944
- const buildingResult = model.addBuilding({ name: options.buildingName ?? "Building" });
27945
- if (!buildingResult.ok) {
27946
- model[Symbol.dispose]();
27947
- return buildingResult;
27948
- }
27949
- const buildingId = buildingResult.value;
27950
- const project = model.getProject();
27951
- if (project !== null) model.aggregate(project.localId, siteResult.value);
27952
- model.aggregate(siteResult.value, buildingId);
27953
- const idByKeyPath = /* @__PURE__ */ new Map();
28703
+ const projectId = initResult.value;
28704
+ let buildingId = null;
28705
+ if (!usesAuthoredCivilHierarchy) {
28706
+ const siteResult = model.addSite({ name: options.siteName ?? "Site" });
28707
+ if (!siteResult.ok) {
28708
+ model[Symbol.dispose]();
28709
+ return siteResult;
28710
+ }
28711
+ const buildingResult = model.addBuilding({ name: options.buildingName ?? "Building" });
28712
+ if (!buildingResult.ok) {
28713
+ model[Symbol.dispose]();
28714
+ return buildingResult;
28715
+ }
28716
+ buildingId = buildingResult.value;
28717
+ model.aggregate(projectId, siteResult.value);
28718
+ model.aggregate(siteResult.value, buildingId);
28719
+ }
28720
+ const idByKeyPath = new Map(usesAuthoredCivilHierarchy ? [[root.keyPath, projectId]] : []);
27954
28721
  const proxied = [];
27955
- const walk = (el, storeyId, rotated) => {
27956
- const rotatedHere = rotated || hasRotateOp(el);
28722
+ const walk = (el, state) => {
28723
+ const rotatedHere = state.rotated || hasRotateOp(el);
28724
+ const cumulativeTranslationHere = addTranslation(state.cumulativeTranslation, authoredTranslation(el));
27957
28725
  let proxiedHere = false;
27958
- let containerId = storeyId;
28726
+ let nextSpatialStructureId = state.spatialStructureId;
28727
+ let nextCivilParent = state.civilParent;
28728
+ let nextProjectedSpatialTranslation = state.projectedSpatialTranslation;
27959
28729
  const archetype = archetypeFor(el);
27960
- const route = specRoute(archetype);
27961
- if (archetype === "storey") {
28730
+ const effectiveArchetype = el.semantics?.kind === "product" ? civilProductArchetype(el) : archetype;
28731
+ const route = specRoute(effectiveArchetype);
28732
+ const civilKind = civilSpatialKind(el);
28733
+ if (civilKind !== void 0) {
28734
+ if (!usesAuthoredCivilHierarchy || !civilParentAccepts(civilKind, state.civilParent) || state.spatialStructureId === null) return (0, brepjs.err)(specError("FAMILIES_INVALID_CIVIL_HIERARCHY", `familiesToBim: civil '${civilKind}' at '${el.keyPath}' cannot occur under '${state.civilParent}'`));
28735
+ const keyed = requireKeyed(el);
28736
+ if (!keyed.ok) return keyed;
28737
+ if (rotatedHere || hasRotatedAxes(el)) return (0, brepjs.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`));
28738
+ if (el.geometry.kind !== "Empty") return (0, brepjs.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)`));
28739
+ const localTranslation = subtractTranslation(cumulativeTranslationHere, state.projectedSpatialTranslation);
28740
+ const added = addCivilSpatialOccurrence(model, el, civilKind, localTranslation);
28741
+ if (!added.ok) return added;
28742
+ model.aggregate(state.spatialStructureId, added.value);
28743
+ idByKeyPath.set(el.keyPath, added.value);
28744
+ nextSpatialStructureId = added.value;
28745
+ nextCivilParent = civilKind;
28746
+ nextProjectedSpatialTranslation = addTranslation(cumulativeTranslationHere, authoredSpecOrigin(el));
28747
+ } else if (isCivilSpatialIntent(el)) return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: unsupported civil '${el.semantics?.kind ?? "unknown"}' category '${el.semantics?.category ?? ""}' at '${el.keyPath}'`));
28748
+ else if (archetype === "storey") {
28749
+ if (buildingId === null) return (0, brepjs.err)(specError("FAMILIES_INVALID_CIVIL_HIERARCHY", `familiesToBim: Storey '${el.keyPath}' is not part of the civil Bridge hierarchy`));
27962
28750
  const keyed = requireKeyed(el);
27963
28751
  if (!keyed.ok) return keyed;
27964
28752
  const storeyResult = model.addStorey({
@@ -27968,7 +28756,17 @@ function familiesToBim(root, options) {
27968
28756
  if (!storeyResult.ok) return storeyResult;
27969
28757
  model.aggregate(buildingId, storeyResult.value);
27970
28758
  idByKeyPath.set(el.keyPath, storeyResult.value);
27971
- containerId = storeyResult.value;
28759
+ nextSpatialStructureId = storeyResult.value;
28760
+ } else if (isEarthworksFillOccurrence(el)) {
28761
+ if (!usesAuthoredCivilHierarchy || nextSpatialStructureId === null || state.civilParent !== "bridge-part") return (0, brepjs.err)(specError("FAMILIES_INVALID_CIVIL_HIERARCHY", `familiesToBim: Earthworks Fill '${el.keyPath}' needs a Bridge Part ancestor`));
28762
+ const bodyEvaluator = options.bodyEvaluator ?? options.proxyEvaluator;
28763
+ if (bodyEvaluator === void 0) return (0, brepjs.err)(specError("FAMILIES_EARTHWORKS_EVALUATOR_REQUIRED", `familiesToBim: Earthworks Fill '${el.keyPath}' needs bodyEvaluator to materialize its exact Product Body`));
28764
+ const keyed = requireKeyed(el);
28765
+ if (!keyed.ok) return keyed;
28766
+ const added = addEarthworksFillElement(model, el, bodyEvaluator, state.projectedSpatialTranslation);
28767
+ if (!added.ok) return added;
28768
+ model.placeIn(added.value, nextSpatialStructureId);
28769
+ idByKeyPath.set(el.keyPath, added.value);
27972
28770
  } else if (route !== void 0) {
27973
28771
  const keyed = requireKeyed(el);
27974
28772
  if (!keyed.ok) return keyed;
@@ -27978,26 +28776,27 @@ function familiesToBim(root, options) {
27978
28776
  const openings = el.children.filter((c) => c.type === "Opening").length;
27979
28777
  if (voids.length > openings) return (0, brepjs.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`));
27980
28778
  }
27981
- const parsed = route.parse(("input" in route ? route.input : specInput)(el));
28779
+ const routedInput = el.semantics?.kind === "product" ? civilProductSpecInput(el) : ("input" in route ? route.input : specInput)(el);
28780
+ const parsed = route.parse(usesAuthoredCivilHierarchy ? relativeSpecInput(routedInput, state.projectedSpatialTranslation) : routedInput);
27982
28781
  if (!parsed.ok) return parsed;
27983
28782
  const added = route.add(model, parsed.value, el.keyPath);
27984
28783
  if (!added.ok) return added;
27985
28784
  idByKeyPath.set(el.keyPath, added.value);
27986
- if (containerId === null) return (0, brepjs.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`));
27987
- model.placeIn(added.value, containerId);
27988
- if (archetype === "wall") {
27989
- const opened = addOpenings(model, el, added.value, containerId, idByKeyPath);
28785
+ if (nextSpatialStructureId === null || usesAuthoredCivilHierarchy && state.civilParent !== "bridge-part") return (0, brepjs.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`));
28786
+ model.placeIn(added.value, nextSpatialStructureId);
28787
+ if (effectiveArchetype === "wall") {
28788
+ const opened = addOpenings(model, el, added.value, nextSpatialStructureId, idByKeyPath);
27990
28789
  if (!opened.ok) return opened;
27991
28790
  }
27992
28791
  } else if (el.type === "Opening") return (0, brepjs.err)(specError("FAMILIES_OPENING_OUTSIDE_WALL", `familiesToBim: opening '${el.keyPath}' is not hosted by a Wall — only wall openings are mapped`));
27993
28792
  else if (el.type !== "Group" && el.geometry.kind !== "Empty") {
27994
- if (options.proxyEvaluator === void 0) return (0, brepjs.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`));
28793
+ if (options.proxyEvaluator === void 0) return (0, brepjs.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`));
27995
28794
  const keyed = requireKeyed(el);
27996
28795
  if (!keyed.ok) return keyed;
27997
- if (containerId === null) return (0, brepjs.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`));
27998
- const added = addProxyElement(model, el, options.proxyEvaluator);
28796
+ if (nextSpatialStructureId === null || usesAuthoredCivilHierarchy && state.civilParent !== "bridge-part") return (0, brepjs.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`));
28797
+ const added = addProxyElement(model, el, options.proxyEvaluator, state.projectedSpatialTranslation);
27999
28798
  if (!added.ok) return added;
28000
- model.placeIn(added.value, containerId);
28799
+ model.placeIn(added.value, nextSpatialStructureId);
28001
28800
  idByKeyPath.set(el.keyPath, added.value);
28002
28801
  proxied.push({
28003
28802
  keyPath: el.keyPath,
@@ -28007,13 +28806,25 @@ function familiesToBim(root, options) {
28007
28806
  proxiedHere = true;
28008
28807
  }
28009
28808
  for (const child of el.children) {
28010
- if ((archetype === "wall" || proxiedHere) && child.type === "Opening") continue;
28011
- const r = walk(child, containerId, rotatedHere);
28809
+ if ((effectiveArchetype === "wall" || proxiedHere) && child.type === "Opening") continue;
28810
+ const r = walk(child, {
28811
+ spatialStructureId: nextSpatialStructureId,
28812
+ civilParent: nextCivilParent,
28813
+ rotated: rotatedHere,
28814
+ cumulativeTranslation: cumulativeTranslationHere,
28815
+ projectedSpatialTranslation: nextProjectedSpatialTranslation
28816
+ });
28012
28817
  if (!r.ok) return r;
28013
28818
  }
28014
28819
  return (0, brepjs.ok)(void 0);
28015
28820
  };
28016
- const walked = walk(root, null, false);
28821
+ const walked = walk(root, {
28822
+ spatialStructureId: usesAuthoredCivilHierarchy ? projectId : null,
28823
+ civilParent: "project",
28824
+ rotated: false,
28825
+ cumulativeTranslation: ZERO_TRANSLATION,
28826
+ projectedSpatialTranslation: ZERO_TRANSLATION
28827
+ });
28017
28828
  if (!walked.ok) {
28018
28829
  model[Symbol.dispose]();
28019
28830
  return walked;
@@ -28069,6 +28880,8 @@ exports.measureTypeFor = measureTypeFor;
28069
28880
  exports.newIfcGuid = newIfcGuid;
28070
28881
  exports.parseBcfFiles = parseBcfFiles;
28071
28882
  exports.parseBeamSpec = parseBeamSpec;
28883
+ exports.parseBridgePartSpec = parseBridgePartSpec;
28884
+ exports.parseBridgeSpec = parseBridgeSpec;
28072
28885
  exports.parseColumnSpec = parseColumnSpec;
28073
28886
  exports.parseCoveringSpec = parseCoveringSpec;
28074
28887
  exports.parseCurtainWallSpec = parseCurtainWallSpec;