brepjs-bim 0.24.5 → 0.25.1

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.
package/README.md CHANGED
@@ -210,3 +210,18 @@ If measurement or temporary cleanup fails, serialization omits optional Wall qua
210
210
  Opening reconstruction retains every surviving solid in source-item order, including split results. Complete removal produces no imported solids and null aggregate bounds and volume. The singleton `.solid` field remains a borrowed alias only for a complete one-solid result.
211
211
 
212
212
  Complete imported aggregates use shared occupied-material measurement and all-item bounds. Overlaps count once. Failed measurement leaves both aggregates unavailable with an import diagnostic. Later opening failures clean the current host list, including every earlier survivor, and never retry an uncertain release.
213
+
214
+ ### Retained Body IFC geometry
215
+
216
+ Wall and Railing exports preflight and tessellate every retained item in order, preserving placement, styles, and metadata.
217
+
218
+ Wall openings export as IFC `Reference` geometry because the retained Wall Body already
219
+ contains its cuts. Void/fill relationships and opening placements remain intact. IFC4 defines
220
+ Reference openings as non-subtractive; gross recipe exports such as Slab openings still use
221
+ subtractive `Body` geometry. The importer keeps Reference opening records and relationships
222
+ without treating their reference shape as a display Body or cutting tool.
223
+
224
+ IfcOpenShell 0.8.5 still subtracts Reference openings in its default geometry engine, even with
225
+ a separate Reference context. If a replacement Body adds material inside a retained opening's
226
+ region, that engine can remove the added material. Schema validation and shape generation alone
227
+ do not detect this difference; check the representation semantics and retained item geometry.
@@ -1719,7 +1719,7 @@ var Doc = class {
1719
1719
  var version = {
1720
1720
  major: 4,
1721
1721
  minor: 6,
1722
- patch: 4
1722
+ patch: 5
1723
1723
  };
1724
1724
  //#endregion
1725
1725
  //#region ../../node_modules/zod/v4/core/schemas.js
@@ -9765,11 +9765,21 @@ function writeHeader(w, meta) {
9765
9765
  WorldCoordinateSystem: w.ref(writeAxis2Placement3D(w)),
9766
9766
  TrueNorth: null
9767
9767
  });
9768
+ return {
9769
+ ownerHistoryId,
9770
+ geomContextId,
9771
+ geomSubContextId: writeGeometricSubContext(w, geomContextId, "Body"),
9772
+ unitAssignmentId,
9773
+ lengthUnitId
9774
+ };
9775
+ }
9776
+ /** Separates display Body geometry from non-subtractive Reference geometry. */
9777
+ function writeGeometricSubContext(w, geomContextId, identifier) {
9768
9778
  const geomSubContextId = w.nextId();
9769
9779
  w.writeLine({
9770
9780
  expressID: geomSubContextId,
9771
9781
  type: web_ifc.IFCGEOMETRICREPRESENTATIONSUBCONTEXT,
9772
- ContextIdentifier: w.mkType(web_ifc.IFCLABEL, "Body"),
9782
+ ContextIdentifier: w.mkType(web_ifc.IFCLABEL, identifier),
9773
9783
  ContextType: w.mkType(web_ifc.IFCLABEL, "Model"),
9774
9784
  CoordinateSpaceDimension: null,
9775
9785
  Precision: null,
@@ -9783,13 +9793,7 @@ function writeHeader(w, meta) {
9783
9793
  },
9784
9794
  UserDefinedTargetView: null
9785
9795
  });
9786
- return {
9787
- ownerHistoryId,
9788
- geomContextId,
9789
- geomSubContextId,
9790
- unitAssignmentId,
9791
- lengthUnitId
9792
- };
9796
+ return geomSubContextId;
9793
9797
  }
9794
9798
  /**
9795
9799
  * Writes IfcProjectedCRS + IfcMapConversion georeferencing the model context
@@ -10197,7 +10201,7 @@ function writePreparedTessellationBody(w, items, geomSubContextId) {
10197
10201
  bodyItemIds
10198
10202
  };
10199
10203
  }
10200
- function writeExactBodyGeometry(w, placement, items, geomSubContextId, parentPlacementId) {
10204
+ function writeProductBodyGeometry(w, placement, items, geomSubContextId, parentPlacementId) {
10201
10205
  const placement3DId = writeAxis2Placement3D(w, [
10202
10206
  toIfcLengthM(placement.origin[0]),
10203
10207
  toIfcLengthM(placement.origin[1]),
@@ -10243,47 +10247,6 @@ function writePreparedTessellationItem(w, prepared) {
10243
10247
  });
10244
10248
  return faceSetId;
10245
10249
  }
10246
- /**
10247
- * Builds a wall 'Axis' IfcShapeRepresentation: an IfcPolyline from (0,0) to
10248
- * (lengthM, 0) in the wall's local XY plane, wrapped in an IfcShapeRepresentation
10249
- * with RepresentationIdentifier='Axis', RepresentationType='Curve2D'. Returns
10250
- * the IfcShapeRepresentation express ID so callers can add it alongside the Body
10251
- * representation in an IfcProductDefinitionShape.
10252
- *
10253
- * wallLengthMm is the wall length in mm (brepjs native units); it is converted
10254
- * to metres for IFC.
10255
- */
10256
- function writeWallAxisRepresentation(w, wallLengthMm, geomSubContextId) {
10257
- const lengthM = toIfcLengthM(wallLengthMm);
10258
- const startId = w.nextId();
10259
- w.writeLine({
10260
- expressID: startId,
10261
- type: web_ifc.IFCCARTESIANPOINT,
10262
- Coordinates: [w.mkType(web_ifc.IFCLENGTHMEASURE, 0), w.mkType(web_ifc.IFCLENGTHMEASURE, 0)]
10263
- });
10264
- const endId = w.nextId();
10265
- w.writeLine({
10266
- expressID: endId,
10267
- type: web_ifc.IFCCARTESIANPOINT,
10268
- Coordinates: [w.mkType(web_ifc.IFCLENGTHMEASURE, lengthM), w.mkType(web_ifc.IFCLENGTHMEASURE, 0)]
10269
- });
10270
- const polylineId = w.nextId();
10271
- w.writeLine({
10272
- expressID: polylineId,
10273
- type: web_ifc.IFCPOLYLINE,
10274
- Points: [w.ref(startId), w.ref(endId)]
10275
- });
10276
- const shapeRepId = w.nextId();
10277
- w.writeLine({
10278
- expressID: shapeRepId,
10279
- type: web_ifc.IFCSHAPEREPRESENTATION,
10280
- ContextOfItems: w.ref(geomSubContextId),
10281
- RepresentationIdentifier: w.mkType(web_ifc.IFCLABEL, "Axis"),
10282
- RepresentationType: w.mkType(web_ifc.IFCLABEL, "Curve2D"),
10283
- Items: [w.ref(polylineId)]
10284
- });
10285
- return shapeRepId;
10286
- }
10287
10250
  function writeFacetedBrepFallback(w, geomSubContextId, reason) {
10288
10251
  const originId = w.nextId();
10289
10252
  w.writeLine({
@@ -10614,81 +10577,6 @@ function writePolyline(w, points) {
10614
10577
  }
10615
10578
  //#endregion
10616
10579
  //#region src/ifc-writer/geometryWriter.ts
10617
- function writeWallGeometry(w, spec, geomSubContextId, parentPlacementId) {
10618
- const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
10619
- const localPlacementId = w.nextId();
10620
- w.writeLine({
10621
- expressID: localPlacementId,
10622
- type: web_ifc.IFCLOCALPLACEMENT,
10623
- PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
10624
- RelativePlacement: w.ref(placement3DId)
10625
- });
10626
- const thicknessM = toIfcLengthM(spec.thickness);
10627
- const heightM = toIfcLengthM(spec.height);
10628
- const lengthM = toIfcLengthM(spec.length);
10629
- const profileId = w.nextId();
10630
- w.writeLine({
10631
- expressID: profileId,
10632
- type: web_ifc.IFCRECTANGLEPROFILEDEF,
10633
- ProfileType: {
10634
- type: 3,
10635
- value: "AREA"
10636
- },
10637
- ProfileName: null,
10638
- Position: w.ref(writeAxis2Placement2D$1(w, [thicknessM / 2, heightM / 2])),
10639
- XDim: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, thicknessM),
10640
- YDim: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, heightM)
10641
- });
10642
- const extrusionPosId = writeAxis2Placement3D(w, [
10643
- 0,
10644
- 0,
10645
- 0
10646
- ], [
10647
- 1,
10648
- 0,
10649
- 0
10650
- ], [
10651
- 0,
10652
- 1,
10653
- 0
10654
- ]);
10655
- const extrusionDirId = writeDirection(w, [
10656
- 0,
10657
- 0,
10658
- 1
10659
- ]);
10660
- const extrusionId = w.nextId();
10661
- w.writeLine({
10662
- expressID: extrusionId,
10663
- type: web_ifc.IFCEXTRUDEDAREASOLID,
10664
- SweptArea: w.ref(profileId),
10665
- Position: w.ref(extrusionPosId),
10666
- ExtrudedDirection: w.ref(extrusionDirId),
10667
- Depth: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, lengthM)
10668
- });
10669
- const shapeRepId = w.nextId();
10670
- w.writeLine({
10671
- expressID: shapeRepId,
10672
- type: web_ifc.IFCSHAPEREPRESENTATION,
10673
- ContextOfItems: w.ref(geomSubContextId),
10674
- RepresentationIdentifier: w.mkType(web_ifc.IFCLABEL, "Body"),
10675
- RepresentationType: w.mkType(web_ifc.IFCLABEL, "SweptSolid"),
10676
- Items: [w.ref(extrusionId)]
10677
- });
10678
- const axisRepId = writeWallAxisRepresentation(w, spec.length, geomSubContextId);
10679
- const productDefinitionShapeId = w.nextId();
10680
- w.writeLine({
10681
- expressID: productDefinitionShapeId,
10682
- type: web_ifc.IFCPRODUCTDEFINITIONSHAPE,
10683
- Name: null,
10684
- Description: null,
10685
- Representations: [w.ref(axisRepId), w.ref(shapeRepId)]
10686
- });
10687
- return {
10688
- localPlacementId,
10689
- productDefinitionShapeId
10690
- };
10691
- }
10692
10580
  function writeSlabGeometry(w, spec, geomSubContextId, parentPlacementId) {
10693
10581
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
10694
10582
  const localPlacementId = w.nextId();
@@ -11831,104 +11719,6 @@ function writeRampAssembly(w, spec, rampKey, assemblyGuid, ownerHistoryId, geomS
11831
11719
  }
11832
11720
  //#endregion
11833
11721
  //#region src/ifc-writer/railingWriter.ts
11834
- function writeRailingGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
11835
- const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
11836
- const localPlacementId = w.nextId();
11837
- w.writeLine({
11838
- expressID: localPlacementId,
11839
- type: web_ifc.IFCLOCALPLACEMENT,
11840
- PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
11841
- RelativePlacement: w.ref(placement3DId)
11842
- });
11843
- if (spec.infill === "POSTED") {
11844
- const tess = writeTessellation(w, solid, geomSubContextId, localPlacementId);
11845
- return {
11846
- localPlacementId,
11847
- productDefinitionShapeId: tess.productDefinitionShapeId,
11848
- bodyItemId: null,
11849
- usedFallback: tess.usedFallback
11850
- };
11851
- }
11852
- const thicknessM = toIfcLengthM(spec.thickness);
11853
- const heightM = toIfcLengthM(spec.height);
11854
- const lengthM = toIfcLengthM(spec.length);
11855
- const profileOriginId = w.nextId();
11856
- w.writeLine({
11857
- expressID: profileOriginId,
11858
- type: web_ifc.IFCCARTESIANPOINT,
11859
- Coordinates: [w.mkType(web_ifc.IFCLENGTHMEASURE, thicknessM / 2), w.mkType(web_ifc.IFCLENGTHMEASURE, heightM / 2)]
11860
- });
11861
- const profilePosId = w.nextId();
11862
- w.writeLine({
11863
- expressID: profilePosId,
11864
- type: web_ifc.IFCAXIS2PLACEMENT2D,
11865
- Location: w.ref(profileOriginId),
11866
- RefDirection: null
11867
- });
11868
- const profileId = w.nextId();
11869
- w.writeLine({
11870
- expressID: profileId,
11871
- type: web_ifc.IFCRECTANGLEPROFILEDEF,
11872
- ProfileType: {
11873
- type: 3,
11874
- value: "AREA"
11875
- },
11876
- ProfileName: null,
11877
- Position: w.ref(profilePosId),
11878
- XDim: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, thicknessM),
11879
- YDim: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, heightM)
11880
- });
11881
- const extrusionPosId = writeAxis2Placement3D(w, [
11882
- 0,
11883
- 0,
11884
- 0
11885
- ], [
11886
- 1,
11887
- 0,
11888
- 0
11889
- ], [
11890
- 0,
11891
- 1,
11892
- 0
11893
- ]);
11894
- const extrusionDirId = writeDirection(w, [
11895
- 0,
11896
- 0,
11897
- 1
11898
- ]);
11899
- const extrusionId = w.nextId();
11900
- w.writeLine({
11901
- expressID: extrusionId,
11902
- type: web_ifc.IFCEXTRUDEDAREASOLID,
11903
- SweptArea: w.ref(profileId),
11904
- Position: w.ref(extrusionPosId),
11905
- ExtrudedDirection: w.ref(extrusionDirId),
11906
- Depth: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, lengthM)
11907
- });
11908
- const shapeRepId = w.nextId();
11909
- w.writeLine({
11910
- expressID: shapeRepId,
11911
- type: web_ifc.IFCSHAPEREPRESENTATION,
11912
- ContextOfItems: w.ref(geomSubContextId),
11913
- RepresentationIdentifier: w.mkType(web_ifc.IFCLABEL, "Body"),
11914
- RepresentationType: w.mkType(web_ifc.IFCLABEL, "SweptSolid"),
11915
- Items: [w.ref(extrusionId)]
11916
- });
11917
- const productDefinitionShapeId = w.nextId();
11918
- w.writeLine({
11919
- expressID: productDefinitionShapeId,
11920
- type: web_ifc.IFCPRODUCTDEFINITIONSHAPE,
11921
- Name: null,
11922
- Description: null,
11923
- Representations: [w.ref(shapeRepId)]
11924
- });
11925
- return {
11926
- localPlacementId,
11927
- productDefinitionShapeId,
11928
- bodyItemId: extrusionId,
11929
- usedFallback: false
11930
- };
11931
- }
11932
11722
  function writeRailingEntity(w, guid, name, _predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
11933
11723
  const id = w.nextId();
11934
11724
  w.writeLine({
@@ -13367,7 +13157,7 @@ function writeAxis2Placement2D(w) {
13367
13157
  });
13368
13158
  return id;
13369
13159
  }
13370
- function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, geomSubContextId, ownerHistoryId) {
13160
+ function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, geomSubContextId, ownerHistoryId, representationIdentifier) {
13371
13161
  const widthM = toIfcLengthM(openingSpec.width);
13372
13162
  const heightM = toIfcLengthM(openingSpec.height);
13373
13163
  const offsetAlongWallM = toIfcLengthM(openingSpec.offsetAlongWall);
@@ -13430,7 +13220,7 @@ function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, g
13430
13220
  expressID: shapeRepId,
13431
13221
  type: web_ifc.IFCSHAPEREPRESENTATION,
13432
13222
  ContextOfItems: w.ref(geomSubContextId),
13433
- RepresentationIdentifier: w.mkType(web_ifc.IFCLABEL, "Body"),
13223
+ RepresentationIdentifier: w.mkType(web_ifc.IFCLABEL, representationIdentifier),
13434
13224
  RepresentationType: w.mkType(web_ifc.IFCLABEL, "SweptSolid"),
13435
13225
  Items: [w.ref(extrusionId)]
13436
13226
  });
@@ -15187,18 +14977,22 @@ function checkGeoreferencing(reader) {
15187
14977
  return [issue("warning", "NO_GEOREFERENCING", "Model contains a facility but no IfcProjectedCRS/IfcGeographicCRS (GRF003) — set ProjectSpec.crs")];
15188
14978
  }
15189
14979
  //#endregion
15190
- //#region src/serialize/exactBodyPreflight.ts
14980
+ //#region src/serialize/productBodyPreflight.ts
15191
14981
  var testItemPreparer = null;
15192
- /** Prepares every exact Body item without writing IFC lines. Source solids remain borrowed. */
15193
- function preflightExactBody(input) {
14982
+ /** Prepares every retained Body item without writing IFC lines. Source solids remain borrowed. */
14983
+ function preflightProductBody(input) {
15194
14984
  const prepareItem = input.prepareItem ?? testItemPreparer ?? prepareTessellation;
14985
+ const failed = (itemIndex, reason, cause) => (0, brepjs.err)(ifcError("BODY_TESSELLATION_FAILED", `Product Body item ${itemIndex} for ${input.localId} could not be tessellated: ${reason}`, cause, {
14986
+ localId: input.localId,
14987
+ itemIndex
14988
+ }));
15195
14989
  const prepareAt = (solid, itemIndex) => {
15196
- const item = prepareItem(solid);
15197
- if (!item.ok) return (0, brepjs.err)(ifcError("EXACT_BODY_TESSELLATION_FAILED", `Exact Product Body item ${itemIndex} for ${input.localId} could not be tessellated: ${item.reason}`, item.cause, {
15198
- localId: input.localId,
15199
- itemIndex
15200
- }));
15201
- return (0, brepjs.ok)(item.value);
14990
+ try {
14991
+ const item = prepareItem(solid);
14992
+ return item.ok ? (0, brepjs.ok)(item.value) : failed(itemIndex, item.reason, item.cause);
14993
+ } catch (cause) {
14994
+ return failed(itemIndex, cause instanceof Error ? cause.message : String(cause), cause);
14995
+ }
15202
14996
  };
15203
14997
  const [firstSolid, ...remainingSolids] = input.solids;
15204
14998
  const first = prepareAt(firstSolid, 0);
@@ -15266,24 +15060,13 @@ function wallVolumeError(cause) {
15266
15060
  function preflightProductBodies(elements) {
15267
15061
  const preflighted = [];
15268
15062
  for (const element of elements) {
15269
- const body = element.geometry;
15270
- if (body.kind === "PARAMETRIC") {
15271
- preflighted.push({
15272
- kind: "PARAMETRIC",
15273
- element,
15274
- solid: body.solid
15275
- });
15276
- continue;
15277
- }
15278
- const prepared = preflightExactBody({
15063
+ const prepared = preflightProductBody({
15279
15064
  localId: element.localId,
15280
- solids: body.solids
15065
+ solids: bodySolids(element.geometry)
15281
15066
  });
15282
15067
  if (!prepared.ok) return (0, brepjs.err)(prepared.error);
15283
15068
  preflighted.push({
15284
- kind: "EXACT",
15285
15069
  element,
15286
- solids: body.solids,
15287
15070
  items: prepared.value
15288
15071
  });
15289
15072
  }
@@ -15418,13 +15201,14 @@ async function serializeIfc(model, meta) {
15418
15201
  spec: wall.spec,
15419
15202
  solids: bodySolids(wall.geometry)
15420
15203
  });
15421
- const { localPlacementId, productDefinitionShapeId } = preflighted.kind === "EXACT" ? writeExactBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId) : writeWallGeometry(w, wall.spec, geomSubContextId, storeyPlacementId);
15204
+ const { localPlacementId, productDefinitionShapeId, bodyItemIds } = writeProductBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId);
15422
15205
  const wallExpressId = writeWallEntity(w, wall.guid, `Wall ${i + 1}`, ownerHistoryId, localPlacementId, productDefinitionShapeId);
15423
15206
  idMap.set(wall.localId, wallExpressId);
15424
15207
  placementMap.set(wall.localId, localPlacementId);
15425
15208
  writeWallCommonPset(w, ownerHistoryId, wallExpressId, wall.spec);
15426
15209
  writeManufacturerPset(w, ownerHistoryId, wallExpressId, wall.spec);
15427
15210
  if (wall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, wallExpressId, wall.spec.customProperties);
15211
+ for (const itemId of bodyItemIds) applySurfaceStyle(w, model, wall.localId, itemId);
15428
15212
  if (!bodyQuantities.ok) issues.push(issue("warning", "WALL_QUANTITY_OMITTED", "Wall quantities omitted because occupied material volume could not be measured", wall.localId, {
15429
15213
  guid: wall.guid,
15430
15214
  cause: bodyQuantities.error
@@ -15574,27 +15358,14 @@ async function serializeIfc(model, meta) {
15574
15358
  const railing = preflighted.element;
15575
15359
  const containingId = findContainerOf(railing.localId, relationships);
15576
15360
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
15577
- let representation;
15578
- let exactBodyItemIds = [];
15579
- if (preflighted.kind === "EXACT") {
15580
- const exact = writeExactBodyGeometry(w, railing.spec, preflighted.items, geomSubContextId, storeyPlacementId);
15581
- exactBodyItemIds = exact.bodyItemIds;
15582
- representation = {
15583
- ...exact,
15584
- bodyItemId: null,
15585
- usedFallback: false
15586
- };
15587
- } else representation = writeRailingGeometry(w, railing.spec, preflighted.solid, geomSubContextId, storeyPlacementId);
15588
- const { localPlacementId, productDefinitionShapeId, bodyItemId, usedFallback } = representation;
15589
- if (usedFallback) console.warn(`Railing ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
15361
+ const { localPlacementId, productDefinitionShapeId, bodyItemIds } = writeProductBodyGeometry(w, railing.spec, preflighted.items, geomSubContextId, storeyPlacementId);
15590
15362
  const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
15591
15363
  idMap.set(railing.localId, railingExpressId);
15592
15364
  placementMap.set(railing.localId, localPlacementId);
15593
15365
  writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
15594
15366
  writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
15595
15367
  if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
15596
- if (preflighted.kind === "EXACT") for (const itemId of exactBodyItemIds) applySurfaceStyle(w, model, railing.localId, itemId);
15597
- else if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
15368
+ for (const itemId of bodyItemIds) applySurfaceStyle(w, model, railing.localId, itemId);
15598
15369
  }
15599
15370
  for (const [i, covering] of coverings.entries()) {
15600
15371
  const containingId = findContainerOf(covering.localId, relationships);
@@ -15610,6 +15381,7 @@ async function serializeIfc(model, meta) {
15610
15381
  }
15611
15382
  const openingPlacementMap = /* @__PURE__ */ new Map();
15612
15383
  const openingEntityMap = /* @__PURE__ */ new Map();
15384
+ let referenceContextId;
15613
15385
  for (const rel of relationships) {
15614
15386
  if (rel.kind !== "VOIDS_WALL") continue;
15615
15387
  const wallElement = elements.find((el) => el.category === "WALL" && el.localId === rel.wallLocalId);
@@ -15620,7 +15392,8 @@ async function serializeIfc(model, meta) {
15620
15392
  const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
15621
15393
  if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
15622
15394
  if (!isWallOpening(openingElement.spec)) continue;
15623
- const { openingEntityId, openingPlacementId } = writeOpeningGeometry(w, openingElement.guid, openingElement.spec, wallElement.spec, wallPlacementId, geomSubContextId, ownerHistoryId);
15395
+ referenceContextId ??= writeGeometricSubContext(w, geomContextId, "Reference");
15396
+ const { openingEntityId, openingPlacementId } = writeOpeningGeometry(w, openingElement.guid, openingElement.spec, wallElement.spec, wallPlacementId, referenceContextId, ownerHistoryId, "Reference");
15624
15397
  idMap.set(rel.openingLocalId, openingEntityId);
15625
15398
  openingPlacementMap.set(rel.openingLocalId, openingPlacementId);
15626
15399
  openingEntityMap.set(rel.openingLocalId, openingEntityId);
@@ -30892,7 +30665,6 @@ function bodiesCoincident(exact, parametric) {
30892
30665
  let union = null;
30893
30666
  try {
30894
30667
  const fused = (0, brepjs.fuseAll)([...bodySolids(exact), ...bodySolids(parametric)], {
30895
- optimisation: "sameFace",
30896
30668
  simplify: true,
30897
30669
  strategy: "pairwise",
30898
30670
  trackEvolution: false
@@ -1696,7 +1696,7 @@ var Doc = class {
1696
1696
  var version = {
1697
1697
  major: 4,
1698
1698
  minor: 6,
1699
- patch: 4
1699
+ patch: 5
1700
1700
  };
1701
1701
  //#endregion
1702
1702
  //#region ../../node_modules/zod/v4/core/schemas.js
@@ -9742,11 +9742,21 @@ function writeHeader(w, meta) {
9742
9742
  WorldCoordinateSystem: w.ref(writeAxis2Placement3D(w)),
9743
9743
  TrueNorth: null
9744
9744
  });
9745
+ return {
9746
+ ownerHistoryId,
9747
+ geomContextId,
9748
+ geomSubContextId: writeGeometricSubContext(w, geomContextId, "Body"),
9749
+ unitAssignmentId,
9750
+ lengthUnitId
9751
+ };
9752
+ }
9753
+ /** Separates display Body geometry from non-subtractive Reference geometry. */
9754
+ function writeGeometricSubContext(w, geomContextId, identifier) {
9745
9755
  const geomSubContextId = w.nextId();
9746
9756
  w.writeLine({
9747
9757
  expressID: geomSubContextId,
9748
9758
  type: WebIFC.IFCGEOMETRICREPRESENTATIONSUBCONTEXT,
9749
- ContextIdentifier: w.mkType(WebIFC.IFCLABEL, "Body"),
9759
+ ContextIdentifier: w.mkType(WebIFC.IFCLABEL, identifier),
9750
9760
  ContextType: w.mkType(WebIFC.IFCLABEL, "Model"),
9751
9761
  CoordinateSpaceDimension: null,
9752
9762
  Precision: null,
@@ -9760,13 +9770,7 @@ function writeHeader(w, meta) {
9760
9770
  },
9761
9771
  UserDefinedTargetView: null
9762
9772
  });
9763
- return {
9764
- ownerHistoryId,
9765
- geomContextId,
9766
- geomSubContextId,
9767
- unitAssignmentId,
9768
- lengthUnitId
9769
- };
9773
+ return geomSubContextId;
9770
9774
  }
9771
9775
  /**
9772
9776
  * Writes IfcProjectedCRS + IfcMapConversion georeferencing the model context
@@ -10174,7 +10178,7 @@ function writePreparedTessellationBody(w, items, geomSubContextId) {
10174
10178
  bodyItemIds
10175
10179
  };
10176
10180
  }
10177
- function writeExactBodyGeometry(w, placement, items, geomSubContextId, parentPlacementId) {
10181
+ function writeProductBodyGeometry(w, placement, items, geomSubContextId, parentPlacementId) {
10178
10182
  const placement3DId = writeAxis2Placement3D(w, [
10179
10183
  toIfcLengthM(placement.origin[0]),
10180
10184
  toIfcLengthM(placement.origin[1]),
@@ -10220,47 +10224,6 @@ function writePreparedTessellationItem(w, prepared) {
10220
10224
  });
10221
10225
  return faceSetId;
10222
10226
  }
10223
- /**
10224
- * Builds a wall 'Axis' IfcShapeRepresentation: an IfcPolyline from (0,0) to
10225
- * (lengthM, 0) in the wall's local XY plane, wrapped in an IfcShapeRepresentation
10226
- * with RepresentationIdentifier='Axis', RepresentationType='Curve2D'. Returns
10227
- * the IfcShapeRepresentation express ID so callers can add it alongside the Body
10228
- * representation in an IfcProductDefinitionShape.
10229
- *
10230
- * wallLengthMm is the wall length in mm (brepjs native units); it is converted
10231
- * to metres for IFC.
10232
- */
10233
- function writeWallAxisRepresentation(w, wallLengthMm, geomSubContextId) {
10234
- const lengthM = toIfcLengthM(wallLengthMm);
10235
- const startId = w.nextId();
10236
- w.writeLine({
10237
- expressID: startId,
10238
- type: WebIFC.IFCCARTESIANPOINT,
10239
- Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, 0), w.mkType(WebIFC.IFCLENGTHMEASURE, 0)]
10240
- });
10241
- const endId = w.nextId();
10242
- w.writeLine({
10243
- expressID: endId,
10244
- type: WebIFC.IFCCARTESIANPOINT,
10245
- Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, lengthM), w.mkType(WebIFC.IFCLENGTHMEASURE, 0)]
10246
- });
10247
- const polylineId = w.nextId();
10248
- w.writeLine({
10249
- expressID: polylineId,
10250
- type: WebIFC.IFCPOLYLINE,
10251
- Points: [w.ref(startId), w.ref(endId)]
10252
- });
10253
- const shapeRepId = w.nextId();
10254
- w.writeLine({
10255
- expressID: shapeRepId,
10256
- type: WebIFC.IFCSHAPEREPRESENTATION,
10257
- ContextOfItems: w.ref(geomSubContextId),
10258
- RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, "Axis"),
10259
- RepresentationType: w.mkType(WebIFC.IFCLABEL, "Curve2D"),
10260
- Items: [w.ref(polylineId)]
10261
- });
10262
- return shapeRepId;
10263
- }
10264
10227
  function writeFacetedBrepFallback(w, geomSubContextId, reason) {
10265
10228
  const originId = w.nextId();
10266
10229
  w.writeLine({
@@ -10591,81 +10554,6 @@ function writePolyline(w, points) {
10591
10554
  }
10592
10555
  //#endregion
10593
10556
  //#region src/ifc-writer/geometryWriter.ts
10594
- function writeWallGeometry(w, spec, geomSubContextId, parentPlacementId) {
10595
- const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
10596
- const localPlacementId = w.nextId();
10597
- w.writeLine({
10598
- expressID: localPlacementId,
10599
- type: WebIFC.IFCLOCALPLACEMENT,
10600
- PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
10601
- RelativePlacement: w.ref(placement3DId)
10602
- });
10603
- const thicknessM = toIfcLengthM(spec.thickness);
10604
- const heightM = toIfcLengthM(spec.height);
10605
- const lengthM = toIfcLengthM(spec.length);
10606
- const profileId = w.nextId();
10607
- w.writeLine({
10608
- expressID: profileId,
10609
- type: WebIFC.IFCRECTANGLEPROFILEDEF,
10610
- ProfileType: {
10611
- type: 3,
10612
- value: "AREA"
10613
- },
10614
- ProfileName: null,
10615
- Position: w.ref(writeAxis2Placement2D$1(w, [thicknessM / 2, heightM / 2])),
10616
- XDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, thicknessM),
10617
- YDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, heightM)
10618
- });
10619
- const extrusionPosId = writeAxis2Placement3D(w, [
10620
- 0,
10621
- 0,
10622
- 0
10623
- ], [
10624
- 1,
10625
- 0,
10626
- 0
10627
- ], [
10628
- 0,
10629
- 1,
10630
- 0
10631
- ]);
10632
- const extrusionDirId = writeDirection(w, [
10633
- 0,
10634
- 0,
10635
- 1
10636
- ]);
10637
- const extrusionId = w.nextId();
10638
- w.writeLine({
10639
- expressID: extrusionId,
10640
- type: WebIFC.IFCEXTRUDEDAREASOLID,
10641
- SweptArea: w.ref(profileId),
10642
- Position: w.ref(extrusionPosId),
10643
- ExtrudedDirection: w.ref(extrusionDirId),
10644
- Depth: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, lengthM)
10645
- });
10646
- const shapeRepId = w.nextId();
10647
- w.writeLine({
10648
- expressID: shapeRepId,
10649
- type: WebIFC.IFCSHAPEREPRESENTATION,
10650
- ContextOfItems: w.ref(geomSubContextId),
10651
- RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, "Body"),
10652
- RepresentationType: w.mkType(WebIFC.IFCLABEL, "SweptSolid"),
10653
- Items: [w.ref(extrusionId)]
10654
- });
10655
- const axisRepId = writeWallAxisRepresentation(w, spec.length, geomSubContextId);
10656
- const productDefinitionShapeId = w.nextId();
10657
- w.writeLine({
10658
- expressID: productDefinitionShapeId,
10659
- type: WebIFC.IFCPRODUCTDEFINITIONSHAPE,
10660
- Name: null,
10661
- Description: null,
10662
- Representations: [w.ref(axisRepId), w.ref(shapeRepId)]
10663
- });
10664
- return {
10665
- localPlacementId,
10666
- productDefinitionShapeId
10667
- };
10668
- }
10669
10557
  function writeSlabGeometry(w, spec, geomSubContextId, parentPlacementId) {
10670
10558
  const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
10671
10559
  const localPlacementId = w.nextId();
@@ -11808,104 +11696,6 @@ function writeRampAssembly(w, spec, rampKey, assemblyGuid, ownerHistoryId, geomS
11808
11696
  }
11809
11697
  //#endregion
11810
11698
  //#region src/ifc-writer/railingWriter.ts
11811
- function writeRailingGeometry(w, spec, solid, geomSubContextId, parentPlacementId) {
11812
- const placement3DId = writeAxis2Placement3D(w, spec.origin.map(toIfcLengthM), spec.axisZ, spec.axisX);
11813
- const localPlacementId = w.nextId();
11814
- w.writeLine({
11815
- expressID: localPlacementId,
11816
- type: WebIFC.IFCLOCALPLACEMENT,
11817
- PlacementRelTo: parentPlacementId !== null ? w.ref(parentPlacementId) : null,
11818
- RelativePlacement: w.ref(placement3DId)
11819
- });
11820
- if (spec.infill === "POSTED") {
11821
- const tess = writeTessellation(w, solid, geomSubContextId, localPlacementId);
11822
- return {
11823
- localPlacementId,
11824
- productDefinitionShapeId: tess.productDefinitionShapeId,
11825
- bodyItemId: null,
11826
- usedFallback: tess.usedFallback
11827
- };
11828
- }
11829
- const thicknessM = toIfcLengthM(spec.thickness);
11830
- const heightM = toIfcLengthM(spec.height);
11831
- const lengthM = toIfcLengthM(spec.length);
11832
- const profileOriginId = w.nextId();
11833
- w.writeLine({
11834
- expressID: profileOriginId,
11835
- type: WebIFC.IFCCARTESIANPOINT,
11836
- Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, thicknessM / 2), w.mkType(WebIFC.IFCLENGTHMEASURE, heightM / 2)]
11837
- });
11838
- const profilePosId = w.nextId();
11839
- w.writeLine({
11840
- expressID: profilePosId,
11841
- type: WebIFC.IFCAXIS2PLACEMENT2D,
11842
- Location: w.ref(profileOriginId),
11843
- RefDirection: null
11844
- });
11845
- const profileId = w.nextId();
11846
- w.writeLine({
11847
- expressID: profileId,
11848
- type: WebIFC.IFCRECTANGLEPROFILEDEF,
11849
- ProfileType: {
11850
- type: 3,
11851
- value: "AREA"
11852
- },
11853
- ProfileName: null,
11854
- Position: w.ref(profilePosId),
11855
- XDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, thicknessM),
11856
- YDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, heightM)
11857
- });
11858
- const extrusionPosId = writeAxis2Placement3D(w, [
11859
- 0,
11860
- 0,
11861
- 0
11862
- ], [
11863
- 1,
11864
- 0,
11865
- 0
11866
- ], [
11867
- 0,
11868
- 1,
11869
- 0
11870
- ]);
11871
- const extrusionDirId = writeDirection(w, [
11872
- 0,
11873
- 0,
11874
- 1
11875
- ]);
11876
- const extrusionId = w.nextId();
11877
- w.writeLine({
11878
- expressID: extrusionId,
11879
- type: WebIFC.IFCEXTRUDEDAREASOLID,
11880
- SweptArea: w.ref(profileId),
11881
- Position: w.ref(extrusionPosId),
11882
- ExtrudedDirection: w.ref(extrusionDirId),
11883
- Depth: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, lengthM)
11884
- });
11885
- const shapeRepId = w.nextId();
11886
- w.writeLine({
11887
- expressID: shapeRepId,
11888
- type: WebIFC.IFCSHAPEREPRESENTATION,
11889
- ContextOfItems: w.ref(geomSubContextId),
11890
- RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, "Body"),
11891
- RepresentationType: w.mkType(WebIFC.IFCLABEL, "SweptSolid"),
11892
- Items: [w.ref(extrusionId)]
11893
- });
11894
- const productDefinitionShapeId = w.nextId();
11895
- w.writeLine({
11896
- expressID: productDefinitionShapeId,
11897
- type: WebIFC.IFCPRODUCTDEFINITIONSHAPE,
11898
- Name: null,
11899
- Description: null,
11900
- Representations: [w.ref(shapeRepId)]
11901
- });
11902
- return {
11903
- localPlacementId,
11904
- productDefinitionShapeId,
11905
- bodyItemId: extrusionId,
11906
- usedFallback: false
11907
- };
11908
- }
11909
11699
  function writeRailingEntity(w, guid, name, _predefinedType, ownerHistoryId, localPlacementId, productDefinitionShapeId) {
11910
11700
  const id = w.nextId();
11911
11701
  w.writeLine({
@@ -13344,7 +13134,7 @@ function writeAxis2Placement2D(w) {
13344
13134
  });
13345
13135
  return id;
13346
13136
  }
13347
- function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, geomSubContextId, ownerHistoryId) {
13137
+ function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, geomSubContextId, ownerHistoryId, representationIdentifier) {
13348
13138
  const widthM = toIfcLengthM(openingSpec.width);
13349
13139
  const heightM = toIfcLengthM(openingSpec.height);
13350
13140
  const offsetAlongWallM = toIfcLengthM(openingSpec.offsetAlongWall);
@@ -13407,7 +13197,7 @@ function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, g
13407
13197
  expressID: shapeRepId,
13408
13198
  type: WebIFC.IFCSHAPEREPRESENTATION,
13409
13199
  ContextOfItems: w.ref(geomSubContextId),
13410
- RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, "Body"),
13200
+ RepresentationIdentifier: w.mkType(WebIFC.IFCLABEL, representationIdentifier),
13411
13201
  RepresentationType: w.mkType(WebIFC.IFCLABEL, "SweptSolid"),
13412
13202
  Items: [w.ref(extrusionId)]
13413
13203
  });
@@ -15164,18 +14954,22 @@ function checkGeoreferencing(reader) {
15164
14954
  return [issue("warning", "NO_GEOREFERENCING", "Model contains a facility but no IfcProjectedCRS/IfcGeographicCRS (GRF003) — set ProjectSpec.crs")];
15165
14955
  }
15166
14956
  //#endregion
15167
- //#region src/serialize/exactBodyPreflight.ts
14957
+ //#region src/serialize/productBodyPreflight.ts
15168
14958
  var testItemPreparer = null;
15169
- /** Prepares every exact Body item without writing IFC lines. Source solids remain borrowed. */
15170
- function preflightExactBody(input) {
14959
+ /** Prepares every retained Body item without writing IFC lines. Source solids remain borrowed. */
14960
+ function preflightProductBody(input) {
15171
14961
  const prepareItem = input.prepareItem ?? testItemPreparer ?? prepareTessellation;
14962
+ const failed = (itemIndex, reason, cause) => err(ifcError("BODY_TESSELLATION_FAILED", `Product Body item ${itemIndex} for ${input.localId} could not be tessellated: ${reason}`, cause, {
14963
+ localId: input.localId,
14964
+ itemIndex
14965
+ }));
15172
14966
  const prepareAt = (solid, itemIndex) => {
15173
- const item = prepareItem(solid);
15174
- if (!item.ok) return err(ifcError("EXACT_BODY_TESSELLATION_FAILED", `Exact Product Body item ${itemIndex} for ${input.localId} could not be tessellated: ${item.reason}`, item.cause, {
15175
- localId: input.localId,
15176
- itemIndex
15177
- }));
15178
- return ok(item.value);
14967
+ try {
14968
+ const item = prepareItem(solid);
14969
+ return item.ok ? ok(item.value) : failed(itemIndex, item.reason, item.cause);
14970
+ } catch (cause) {
14971
+ return failed(itemIndex, cause instanceof Error ? cause.message : String(cause), cause);
14972
+ }
15179
14973
  };
15180
14974
  const [firstSolid, ...remainingSolids] = input.solids;
15181
14975
  const first = prepareAt(firstSolid, 0);
@@ -15243,24 +15037,13 @@ function wallVolumeError(cause) {
15243
15037
  function preflightProductBodies(elements) {
15244
15038
  const preflighted = [];
15245
15039
  for (const element of elements) {
15246
- const body = element.geometry;
15247
- if (body.kind === "PARAMETRIC") {
15248
- preflighted.push({
15249
- kind: "PARAMETRIC",
15250
- element,
15251
- solid: body.solid
15252
- });
15253
- continue;
15254
- }
15255
- const prepared = preflightExactBody({
15040
+ const prepared = preflightProductBody({
15256
15041
  localId: element.localId,
15257
- solids: body.solids
15042
+ solids: bodySolids(element.geometry)
15258
15043
  });
15259
15044
  if (!prepared.ok) return err(prepared.error);
15260
15045
  preflighted.push({
15261
- kind: "EXACT",
15262
15046
  element,
15263
- solids: body.solids,
15264
15047
  items: prepared.value
15265
15048
  });
15266
15049
  }
@@ -15395,13 +15178,14 @@ async function serializeIfc(model, meta) {
15395
15178
  spec: wall.spec,
15396
15179
  solids: bodySolids(wall.geometry)
15397
15180
  });
15398
- const { localPlacementId, productDefinitionShapeId } = preflighted.kind === "EXACT" ? writeExactBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId) : writeWallGeometry(w, wall.spec, geomSubContextId, storeyPlacementId);
15181
+ const { localPlacementId, productDefinitionShapeId, bodyItemIds } = writeProductBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId);
15399
15182
  const wallExpressId = writeWallEntity(w, wall.guid, `Wall ${i + 1}`, ownerHistoryId, localPlacementId, productDefinitionShapeId);
15400
15183
  idMap.set(wall.localId, wallExpressId);
15401
15184
  placementMap.set(wall.localId, localPlacementId);
15402
15185
  writeWallCommonPset(w, ownerHistoryId, wallExpressId, wall.spec);
15403
15186
  writeManufacturerPset(w, ownerHistoryId, wallExpressId, wall.spec);
15404
15187
  if (wall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, wallExpressId, wall.spec.customProperties);
15188
+ for (const itemId of bodyItemIds) applySurfaceStyle(w, model, wall.localId, itemId);
15405
15189
  if (!bodyQuantities.ok) issues.push(issue("warning", "WALL_QUANTITY_OMITTED", "Wall quantities omitted because occupied material volume could not be measured", wall.localId, {
15406
15190
  guid: wall.guid,
15407
15191
  cause: bodyQuantities.error
@@ -15551,27 +15335,14 @@ async function serializeIfc(model, meta) {
15551
15335
  const railing = preflighted.element;
15552
15336
  const containingId = findContainerOf(railing.localId, relationships);
15553
15337
  const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
15554
- let representation;
15555
- let exactBodyItemIds = [];
15556
- if (preflighted.kind === "EXACT") {
15557
- const exact = writeExactBodyGeometry(w, railing.spec, preflighted.items, geomSubContextId, storeyPlacementId);
15558
- exactBodyItemIds = exact.bodyItemIds;
15559
- representation = {
15560
- ...exact,
15561
- bodyItemId: null,
15562
- usedFallback: false
15563
- };
15564
- } else representation = writeRailingGeometry(w, railing.spec, preflighted.solid, geomSubContextId, storeyPlacementId);
15565
- const { localPlacementId, productDefinitionShapeId, bodyItemId, usedFallback } = representation;
15566
- if (usedFallback) console.warn(`Railing ${i + 1} tessellation failed; IFC body is a degenerate fallback.`);
15338
+ const { localPlacementId, productDefinitionShapeId, bodyItemIds } = writeProductBodyGeometry(w, railing.spec, preflighted.items, geomSubContextId, storeyPlacementId);
15567
15339
  const railingExpressId = writeRailingEntity(w, railing.guid, `Railing ${i + 1}`, railing.spec.predefinedType ?? "NOTDEFINED", ownerHistoryId, localPlacementId, productDefinitionShapeId);
15568
15340
  idMap.set(railing.localId, railingExpressId);
15569
15341
  placementMap.set(railing.localId, localPlacementId);
15570
15342
  writeRailingCommonPset(w, ownerHistoryId, railingExpressId, railing.spec);
15571
15343
  writeManufacturerPset(w, ownerHistoryId, railingExpressId, railing.spec);
15572
15344
  if (railing.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, railingExpressId, railing.spec.customProperties);
15573
- if (preflighted.kind === "EXACT") for (const itemId of exactBodyItemIds) applySurfaceStyle(w, model, railing.localId, itemId);
15574
- else if (bodyItemId !== null) applySurfaceStyle(w, model, railing.localId, bodyItemId);
15345
+ for (const itemId of bodyItemIds) applySurfaceStyle(w, model, railing.localId, itemId);
15575
15346
  }
15576
15347
  for (const [i, covering] of coverings.entries()) {
15577
15348
  const containingId = findContainerOf(covering.localId, relationships);
@@ -15587,6 +15358,7 @@ async function serializeIfc(model, meta) {
15587
15358
  }
15588
15359
  const openingPlacementMap = /* @__PURE__ */ new Map();
15589
15360
  const openingEntityMap = /* @__PURE__ */ new Map();
15361
+ let referenceContextId;
15590
15362
  for (const rel of relationships) {
15591
15363
  if (rel.kind !== "VOIDS_WALL") continue;
15592
15364
  const wallElement = elements.find((el) => el.category === "WALL" && el.localId === rel.wallLocalId);
@@ -15597,7 +15369,8 @@ async function serializeIfc(model, meta) {
15597
15369
  const openingElement = elements.find((el) => el.localId === rel.openingLocalId);
15598
15370
  if (openingElement === void 0 || openingElement.category !== "OPENING") continue;
15599
15371
  if (!isWallOpening(openingElement.spec)) continue;
15600
- const { openingEntityId, openingPlacementId } = writeOpeningGeometry(w, openingElement.guid, openingElement.spec, wallElement.spec, wallPlacementId, geomSubContextId, ownerHistoryId);
15372
+ referenceContextId ??= writeGeometricSubContext(w, geomContextId, "Reference");
15373
+ const { openingEntityId, openingPlacementId } = writeOpeningGeometry(w, openingElement.guid, openingElement.spec, wallElement.spec, wallPlacementId, referenceContextId, ownerHistoryId, "Reference");
15601
15374
  idMap.set(rel.openingLocalId, openingEntityId);
15602
15375
  openingPlacementMap.set(rel.openingLocalId, openingPlacementId);
15603
15376
  openingEntityMap.set(rel.openingLocalId, openingEntityId);
@@ -30869,7 +30642,6 @@ function bodiesCoincident(exact, parametric) {
30869
30642
  let union = null;
30870
30643
  try {
30871
30644
  const fused = fuseAll([...bodySolids(exact), ...bodySolids(parametric)], {
30872
- optimisation: "sameFace",
30873
30645
  simplify: true,
30874
30646
  strategy: "pairwise",
30875
30647
  trackEvolution: false
@@ -1,15 +1,10 @@
1
1
  import { ValidSolid } from 'brepjs';
2
2
  import { IfcWriter } from './ifcWriter.js';
3
- import { WallSpec } from '../specs/wallSpec.js';
4
3
  import { SlabSpec } from '../specs/slabSpec.js';
5
4
  import { BeamSpec } from '../specs/beamSpec.js';
6
5
  import { ColumnSpec } from '../specs/columnSpec.js';
7
6
  import { RoofSpec } from '../specs/roofSpec.js';
8
7
  import { Profile } from '../specs/profile.js';
9
- export interface WallRepresentationIds {
10
- localPlacementId: number;
11
- productDefinitionShapeId: number;
12
- }
13
8
  export interface SlabRepresentationIds {
14
9
  localPlacementId: number;
15
10
  productDefinitionShapeId: number;
@@ -18,7 +13,6 @@ export interface LinearElementRepresentationIds {
18
13
  localPlacementId: number;
19
14
  productDefinitionShapeId: number;
20
15
  }
21
- export declare function writeWallGeometry(w: IfcWriter, spec: WallSpec, geomSubContextId: number, parentPlacementId: number | null): WallRepresentationIds;
22
16
  export declare function writeSlabGeometry(w: IfcWriter, spec: SlabSpec, geomSubContextId: number, parentPlacementId: number | null): SlabRepresentationIds;
23
17
  export declare function writeRoofGeometry(w: IfcWriter, spec: RoofSpec, solid: ValidSolid, geomSubContextId: number, parentPlacementId: number | null): SlabRepresentationIds & {
24
18
  usedFallback: boolean;
@@ -28,6 +28,8 @@ export interface BimModelMeta {
28
28
  ifcSchema?: IfcSchema | undefined;
29
29
  }
30
30
  export declare function writeHeader(w: IfcWriter, meta: BimModelMeta): HeaderIds;
31
+ /** Separates display Body geometry from non-subtractive Reference geometry. */
32
+ export declare function writeGeometricSubContext(w: IfcWriter, geomContextId: number, identifier: 'Body' | 'Reference'): number;
31
33
  /**
32
34
  * Writes IfcProjectedCRS + IfcMapConversion georeferencing the model context
33
35
  * (GRF003). Coordinates are metres; rotation defaults to identity.
@@ -13,7 +13,7 @@ type OpeningPsetSpec = {
13
13
  readonly acousticRating?: string | undefined;
14
14
  readonly thermalTransmittance?: number | undefined;
15
15
  };
16
- export declare function writeOpeningGeometry(w: IfcWriter, guid: IfcGuid, openingSpec: WallOpeningSpec, wallSpec: WallSpec, wallPlacementId: number, geomSubContextId: number, ownerHistoryId: number): OpeningIds;
16
+ export declare function writeOpeningGeometry(w: IfcWriter, guid: IfcGuid, openingSpec: WallOpeningSpec, wallSpec: WallSpec, wallPlacementId: number, geomSubContextId: number, ownerHistoryId: number, representationIdentifier: 'Body' | 'Reference'): OpeningIds;
17
17
  export declare function writeSlabOpeningGeometry(w: IfcWriter, guid: IfcGuid, openingSpec: SlabOpeningSpec, slabSpec: SlabSpec, slabPlacementId: number, geomSubContextId: number, ownerHistoryId: number): OpeningIds;
18
18
  export declare function writeDoorEntity(w: IfcWriter, guid: IfcGuid, name: string, ownerHistoryId: number, openingPlacementId: number, geomSubContextId: number, overallWidthM: number, overallHeightM: number, nominalDepthMm?: number): number;
19
19
  export declare function writeWindowEntity(w: IfcWriter, guid: IfcGuid, name: string, ownerHistoryId: number, openingPlacementId: number, geomSubContextId: number, overallWidthM: number, overallHeightM: number, nominalDepthMm?: number): number;
@@ -25,7 +25,7 @@ export type TessellationPreparation = {
25
25
  readonly reason: string;
26
26
  readonly cause?: unknown;
27
27
  };
28
- export interface ExactBodyRepresentationIds {
28
+ export interface ProductBodyRepresentationIds {
29
29
  readonly localPlacementId: number;
30
30
  readonly productDefinitionShapeId: number;
31
31
  readonly bodyItemIds: readonly number[];
@@ -53,7 +53,7 @@ export declare function writePreparedTessellationBody(w: IfcWriter, items: NonEm
53
53
  readonly productDefinitionShapeId: number;
54
54
  readonly bodyItemIds: readonly number[];
55
55
  };
56
- export declare function writeExactBodyGeometry(w: IfcWriter, placement: FrameInput, items: NonEmpty<PreparedTessellation>, geomSubContextId: number, parentPlacementId: number | null): ExactBodyRepresentationIds;
56
+ export declare function writeProductBodyGeometry(w: IfcWriter, placement: FrameInput, items: NonEmpty<PreparedTessellation>, geomSubContextId: number, parentPlacementId: number | null): ProductBodyRepresentationIds;
57
57
  export declare function writePreparedTessellationItem(w: IfcWriter, prepared: PreparedTessellation): number;
58
58
  /**
59
59
  * Builds a wall 'Axis' IfcShapeRepresentation: an IfcPolyline from (0,0) to
@@ -0,0 +1,15 @@
1
+ import { Result, ValidSolid } from 'brepjs';
2
+ import { BimError } from '../errors/bimError.js';
3
+ import { LocalId } from '../identity/localId.js';
4
+ import { NonEmpty } from '../types/productBody.js';
5
+ import { PreparedTessellation, TessellationPreparation } from '../ifc-writer/tessellationWriter.js';
6
+ export type ProductBodyItemPreparer = (solid: ValidSolid) => TessellationPreparation;
7
+ /** Package-internal deterministic failure seam for serialization cleanup tests. */
8
+ export declare function setProductBodyItemPreparerForTesting(prepareItem: ProductBodyItemPreparer | null): void;
9
+ export interface ProductBodyPreflightInput {
10
+ readonly localId: LocalId;
11
+ readonly solids: NonEmpty<ValidSolid>;
12
+ readonly prepareItem?: ProductBodyItemPreparer | undefined;
13
+ }
14
+ /** Prepares every retained Body item without writing IFC lines. Source solids remain borrowed. */
15
+ export declare function preflightProductBody(input: ProductBodyPreflightInput): Result<NonEmpty<PreparedTessellation>, BimError>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brepjs-bim",
3
- "version": "0.24.5",
3
+ "version": "0.25.1",
4
4
  "description": "BIM layer for brepjs — IFC4-aligned parametric building elements",
5
5
  "keywords": [
6
6
  "bim",
@@ -59,7 +59,7 @@
59
59
  "vite-plugin-dts": "^5.0.0",
60
60
  "vitest": "^5.0.0",
61
61
  "web-ifc": "^0.0.77",
62
- "zod": "4.6.4"
62
+ "zod": "4.6.5"
63
63
  },
64
64
  "dependencies": {
65
65
  "brepjs-families": ">=0.1.0 <1.0.0"
@@ -1,15 +0,0 @@
1
- import { Result, ValidSolid } from 'brepjs';
2
- import { BimError } from '../errors/bimError.js';
3
- import { LocalId } from '../identity/localId.js';
4
- import { NonEmpty } from '../types/productBody.js';
5
- import { PreparedTessellation, TessellationPreparation } from '../ifc-writer/tessellationWriter.js';
6
- export type ExactBodyItemPreparer = (solid: ValidSolid) => TessellationPreparation;
7
- /** Package-internal deterministic failure seam for serialization cleanup tests. */
8
- export declare function setExactBodyItemPreparerForTesting(prepareItem: ExactBodyItemPreparer | null): void;
9
- export interface ExactBodyPreflightInput {
10
- readonly localId: LocalId;
11
- readonly solids: NonEmpty<ValidSolid>;
12
- readonly prepareItem?: ExactBodyItemPreparer | undefined;
13
- }
14
- /** Prepares every exact Body item without writing IFC lines. Source solids remain borrowed. */
15
- export declare function preflightExactBody(input: ExactBodyPreflightInput): Result<NonEmpty<PreparedTessellation>, BimError>;