brepjs-bim 0.23.0 → 0.23.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.
@@ -206,7 +206,7 @@ function fromBrepError(inner, code, message) {
206
206
  };
207
207
  }
208
208
  //#endregion
209
- //#region \0@oxc-project+runtime@0.143.0/helpers/esm/usingCtx.js
209
+ //#region \0@oxc-project+runtime@0.146.0/helpers/esm/usingCtx.js
210
210
  function _usingCtx() {
211
211
  var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
212
212
  var n = Error();
@@ -8779,7 +8779,7 @@ function writeWallGeometry(w, spec, geomSubContextId, parentPlacementId) {
8779
8779
  value: "AREA"
8780
8780
  },
8781
8781
  ProfileName: null,
8782
- Position: w.ref(writeAxis2Placement2D$1(w)),
8782
+ Position: w.ref(writeAxis2Placement2D$1(w, [thicknessM / 2, heightM / 2])),
8783
8783
  XDim: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, thicknessM),
8784
8784
  YDim: w.mkType(web_ifc.IFCPOSITIVELENGTHMEASURE, heightM)
8785
8785
  });
@@ -9000,12 +9000,12 @@ function writeRoofGeometry(w, spec, solid, geomSubContextId, parentPlacementId)
9000
9000
  usedFallback: false
9001
9001
  };
9002
9002
  }
9003
- function writeAxis2Placement2D$1(w) {
9003
+ function writeAxis2Placement2D$1(w, location = [0, 0]) {
9004
9004
  const originId = w.nextId();
9005
9005
  w.writeLine({
9006
9006
  expressID: originId,
9007
9007
  type: web_ifc.IFCCARTESIANPOINT,
9008
- Coordinates: [w.mkType(web_ifc.IFCLENGTHMEASURE, 0), w.mkType(web_ifc.IFCLENGTHMEASURE, 0)]
9008
+ Coordinates: [w.mkType(web_ifc.IFCLENGTHMEASURE, location[0]), w.mkType(web_ifc.IFCLENGTHMEASURE, location[1])]
9009
9009
  });
9010
9010
  const id = w.nextId();
9011
9011
  w.writeLine({
@@ -11510,11 +11510,10 @@ function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, g
11510
11510
  const offsetAlongWallM = toIfcLengthM(openingSpec.offsetAlongWall);
11511
11511
  const offsetFromFloorM = toIfcLengthM(openingSpec.offsetFromFloor);
11512
11512
  const thicknessM = toIfcLengthM(wallSpec.thickness);
11513
- const wallHeightM = toIfcLengthM(wallSpec.height);
11514
11513
  const placement3DId = writeAxis2Placement3D(w, [
11515
11514
  offsetAlongWallM + widthM / 2,
11516
- thicknessM / 2,
11517
- -wallHeightM / 2 + offsetFromFloorM + heightM / 2
11515
+ thicknessM,
11516
+ offsetFromFloorM + heightM / 2
11518
11517
  ], [
11519
11518
  0,
11520
11519
  -1,
@@ -12390,6 +12389,24 @@ var SUPPORTED_SCHEMAS = [
12390
12389
  * MUST call {@link SpfReader.close} (it always issues `CloseModel`, preventing
12391
12390
  * WASM handle leaks). The handle is `using`-friendly via `Symbol.dispose`.
12392
12391
  */
12392
+ var IFC_Z_UP_TRANSFORMATION = [
12393
+ 1,
12394
+ 0,
12395
+ 0,
12396
+ 0,
12397
+ 0,
12398
+ 0,
12399
+ 1,
12400
+ 0,
12401
+ 0,
12402
+ -1,
12403
+ 0,
12404
+ 0,
12405
+ 0,
12406
+ 0,
12407
+ 0,
12408
+ 1
12409
+ ];
12393
12410
  function deleteVector(vec) {
12394
12411
  vec.delete?.();
12395
12412
  }
@@ -12422,6 +12439,7 @@ var SpfReader = class SpfReader {
12422
12439
  if (modelId >= 0) api.CloseModel(modelId);
12423
12440
  return (0, brepjs.err)(importError("OPEN_MODEL_FAILED", "web-ifc OpenModel returned an invalid model id"));
12424
12441
  }
12442
+ api.SetGeometryTransformation(modelId, IFC_Z_UP_TRANSFORMATION);
12425
12443
  const schemaRaw = api.GetModelSchema(modelId);
12426
12444
  if (!SUPPORTED_SCHEMAS.includes(schemaRaw)) {
12427
12445
  api.CloseModel(modelId);
@@ -14121,12 +14139,14 @@ function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagno
14121
14139
  diagnostics.push(issue("warning", faceResult.error.code, faceResult.error.message, extrusionId));
14122
14140
  return NONE;
14123
14141
  }
14142
+ const profileFrame = readProfilePosition(reader, sweptArea.value, scale);
14124
14143
  const depthMm = depth * scale * 1e3;
14125
- const extrudeDir = readDirection(reader, ext["ExtrudedDirection"]) ?? [
14144
+ const sweptDir = readDirection(reader, ext["ExtrudedDirection"]) ?? [
14126
14145
  0,
14127
14146
  0,
14128
14147
  1
14129
14148
  ];
14149
+ const extrudeDir = profileFrame === null ? sweptDir : rotateByTranspose(profileFrame.linear, sweptDir);
14130
14150
  const extrudeVec = [
14131
14151
  extrudeDir[0] * depthMm,
14132
14152
  extrudeDir[1] * depthMm,
@@ -14148,7 +14168,11 @@ function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagno
14148
14168
  return NONE;
14149
14169
  }
14150
14170
  const localFrame = readAxis2Placement3D(reader, ext["Position"], scale);
14151
- const placed = placeSolid(solidResult.value, localFrame, worldTransform);
14171
+ const placed = placeSolid(solidResult.value, [
14172
+ worldTransform,
14173
+ localFrame,
14174
+ profileFrame
14175
+ ]);
14152
14176
  if (!placed.ok) {
14153
14177
  diagnostics.push(issue("warning", placed.error.code, placed.error.message, extrusionId));
14154
14178
  return NONE;
@@ -14176,16 +14200,19 @@ function reconstructRevolution(reader, revolutionId, scale, worldTransform, diag
14176
14200
  return NONE;
14177
14201
  }
14178
14202
  const axis1 = reader.getLine(axisRef.value);
14179
- const center = (axis1 === null ? void 0 : readPoint(reader, axis1["Location"], scale)) ?? [
14203
+ const sweptCenter = (axis1 === null ? void 0 : readPoint(reader, axis1["Location"], scale)) ?? [
14180
14204
  0,
14181
14205
  0,
14182
14206
  0
14183
14207
  ];
14184
- const direction = (axis1 === null ? void 0 : readDirection(reader, axis1["Axis"])) ?? [
14208
+ const sweptDirection = (axis1 === null ? void 0 : readDirection(reader, axis1["Axis"])) ?? [
14185
14209
  0,
14186
14210
  0,
14187
14211
  1
14188
14212
  ];
14213
+ const profileFrame = readProfilePosition(reader, sweptArea.value, scale);
14214
+ const center = profileFrame === null ? sweptCenter : inverseRigidPoint(profileFrame, sweptCenter);
14215
+ const direction = profileFrame === null ? sweptDirection : rotateByTranspose(profileFrame.linear, sweptDirection);
14189
14216
  const revolved = (() => {
14190
14217
  try {
14191
14218
  var _usingCtx3 = _usingCtx();
@@ -14212,7 +14239,11 @@ function reconstructRevolution(reader, revolutionId, scale, worldTransform, diag
14212
14239
  return NONE;
14213
14240
  }
14214
14241
  const localFrame = readAxis2Placement3D(reader, rev["Position"], scale);
14215
- const placed = placeSolid(revolved.value, localFrame, worldTransform);
14242
+ const placed = placeSolid(revolved.value, [
14243
+ worldTransform,
14244
+ localFrame,
14245
+ profileFrame
14246
+ ]);
14216
14247
  if (!placed.ok) {
14217
14248
  diagnostics.push(issue("warning", placed.error.code, placed.error.message, revolutionId));
14218
14249
  return NONE;
@@ -14225,20 +14256,9 @@ function reconstructTessellated(reader, productExpressId, diagnostics) {
14225
14256
  diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", "Tessellated geometry yielded no triangles", productExpressId));
14226
14257
  return NONE;
14227
14258
  }
14228
- const stl = packBinaryStl(mesh.vertices, mesh.indices, 1e3);
14229
- let solid = null;
14230
- try {
14231
- const cast = (0, brepjs.castShape)((0, brepjs.getKernel)().importSTL(stl.buffer));
14232
- if ((0, brepjs.isSolid)(cast)) {
14233
- const valid = (0, brepjs.validSolid)(cast);
14234
- if (valid.ok) solid = valid.value;
14235
- else cast[Symbol.dispose]();
14236
- } else cast[Symbol.dispose]();
14237
- } catch (e) {
14238
- diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `STL round-trip failed: ${errMsg$1(e)}`, productExpressId));
14239
- }
14259
+ const solid = sewMeshToSolid(mesh, 1e3, productExpressId, diagnostics);
14240
14260
  if (solid !== null) {
14241
- diagnostics.push(issue("info", "TESSELLATED_MANIFOLD", "Tessellated mesh recovered as a closed solid via STL round-trip", productExpressId));
14261
+ diagnostics.push(issue("info", "TESSELLATED_MANIFOLD", "Tessellated mesh recovered as a closed solid by sewing its triangles", productExpressId));
14242
14262
  return {
14243
14263
  kind: "SOLID",
14244
14264
  solid,
@@ -14253,6 +14273,52 @@ function reconstructTessellated(reader, productExpressId, diagnostics) {
14253
14273
  diagnostic: "TESSELLATED_LOSSY"
14254
14274
  };
14255
14275
  }
14276
+ /** Loose enough to weld float32 vertices web-ifc emits per face, in mm. */
14277
+ var MESH_SEW_TOLERANCE_MM = .001;
14278
+ /**
14279
+ * Brands a fresh kernel result and releases its pre-downcast arena slot when
14280
+ * the cast moved to a new one (occt-wasm); in-place kernels share the slot and
14281
+ * are left alone. Mirrors brepjs's internal castResultShape.
14282
+ */
14283
+ function castFreshResult(raw) {
14284
+ const cast = (0, brepjs.castShape)(raw);
14285
+ const rawId = raw.id;
14286
+ const castId = cast.wrapped.id;
14287
+ if (!(rawId !== void 0 && castId !== void 0 ? rawId === castId : cast.wrapped === raw)) (0, brepjs.getKernel)().dispose(raw);
14288
+ return cast;
14289
+ }
14290
+ /**
14291
+ * Sews the streamed triangles into a closed solid through the kernel's mesh
14292
+ * builder, the same path brepjs's mesh importers use. Null when the mesh is
14293
+ * open or the kernel rejects it.
14294
+ */
14295
+ function sewMeshToSolid(mesh, scaleToMm, productExpressId, diagnostics) {
14296
+ const kernel = (0, brepjs.getKernel)();
14297
+ const { vertices, indices } = mesh;
14298
+ const point = (index) => [
14299
+ (vertices[index * 3] ?? 0) * scaleToMm,
14300
+ (vertices[index * 3 + 1] ?? 0) * scaleToMm,
14301
+ (vertices[index * 3 + 2] ?? 0) * scaleToMm
14302
+ ];
14303
+ const triangles = [];
14304
+ for (let t = 0; t + 2 < indices.length; t += 3) {
14305
+ const face = kernel.buildTriFace(point(indices[t] ?? 0), point(indices[t + 1] ?? 0), point(indices[t + 2] ?? 0));
14306
+ if (face !== null) triangles.push(face);
14307
+ }
14308
+ if (triangles.length === 0) return null;
14309
+ try {
14310
+ const cast = castFreshResult(kernel.sewAndSolidify(triangles, MESH_SEW_TOLERANCE_MM));
14311
+ const valid = (0, brepjs.isSolid)(cast) ? (0, brepjs.validSolid)(cast) : null;
14312
+ if (valid !== null && valid.ok) return valid.value;
14313
+ cast[Symbol.dispose]();
14314
+ return null;
14315
+ } catch (e) {
14316
+ diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `Mesh sewing failed: ${errMsg$1(e)}`, productExpressId));
14317
+ return null;
14318
+ } finally {
14319
+ for (const face of triangles) kernel.dispose(face);
14320
+ }
14321
+ }
14256
14322
  function collectMesh(reader, productExpressId) {
14257
14323
  const vertices = [];
14258
14324
  const indices = [];
@@ -14281,7 +14347,8 @@ function collectMesh(reader, productExpressId) {
14281
14347
  }
14282
14348
  }
14283
14349
  } finally {
14284
- flatMesh.delete();
14350
+ releaseEmbind(flatMesh.geometries);
14351
+ releaseEmbind(flatMesh);
14285
14352
  }
14286
14353
  });
14287
14354
  if (vertices.length === 0) return null;
@@ -14290,25 +14357,6 @@ function collectMesh(reader, productExpressId) {
14290
14357
  indices: new Uint32Array(indices)
14291
14358
  };
14292
14359
  }
14293
- function packBinaryStl(vertices, indices, scaleToMm) {
14294
- const triCount = Math.floor(indices.length / 3);
14295
- const buffer = /* @__PURE__ */ new ArrayBuffer(84 + triCount * 50);
14296
- const view = new DataView(buffer);
14297
- view.setUint32(80, triCount, true);
14298
- let offset = 84;
14299
- for (let t = 0; t < triCount; t++) {
14300
- offset += 12;
14301
- for (let c = 0; c < 3; c++) {
14302
- const vi = (indices[t * 3 + c] ?? 0) * 3;
14303
- view.setFloat32(offset, (vertices[vi] ?? 0) * scaleToMm, true);
14304
- view.setFloat32(offset + 4, (vertices[vi + 1] ?? 0) * scaleToMm, true);
14305
- view.setFloat32(offset + 8, (vertices[vi + 2] ?? 0) * scaleToMm, true);
14306
- offset += 12;
14307
- }
14308
- offset += 2;
14309
- }
14310
- return new Uint8Array(buffer);
14311
- }
14312
14360
  var PARAMETRIC_PROFILE_BUILDERS = /* @__PURE__ */ new Map([
14313
14361
  [web_ifc.IFCRECTANGLEPROFILEDEF, (f) => ({
14314
14362
  kind: "RECTANGULAR",
@@ -14452,19 +14500,99 @@ function profileToFace(profile) {
14452
14500
  if (!face.ok) return (0, brepjs.err)(importError("GEOMETRY_RECONSTRUCTION_FAILED", `Profile face build failed: ${face.error.message}`));
14453
14501
  return (0, brepjs.ok)(face.value);
14454
14502
  }
14455
- function placeSolid(solid, localFrame, worldTransform) {
14456
- let current = solid;
14457
- for (const transform of [localFrame, worldTransform]) {
14458
- if (transform === null || isIdentity(transform)) continue;
14459
- const applied = (0, brepjs.applyMatrix)(current, transform);
14460
- if (!applied.ok) {
14461
- current[Symbol.dispose]();
14462
- return (0, brepjs.err)(importError("PLACEMENT_READ_FAILED", `Placement transform failed: ${applied.error.message}`));
14463
- }
14464
- current[Symbol.dispose]();
14465
- current = applied.value;
14466
- }
14467
- return (0, brepjs.ok)(current);
14503
+ /**
14504
+ * IfcParameterizedProfileDef.Position places the profile inside the swept
14505
+ * solid's XY plane; the writers rely on it to corner-anchor centred rectangle
14506
+ * profiles. Absent on arbitrary (polyline) profiles.
14507
+ */
14508
+ function readProfilePosition(reader, profileExpressId, scale) {
14509
+ const def = reader.getLine(profileExpressId);
14510
+ const positionRef = def === null ? void 0 : asRef(def["Position"]);
14511
+ if (positionRef === void 0) return null;
14512
+ const position = reader.getLine(positionRef.value);
14513
+ if (position === null) return null;
14514
+ const [x, y] = readPoint2D(reader, position["Location"], scale) ?? [0, 0];
14515
+ const [dx, dy] = readDirection2D(reader, position["RefDirection"]) ?? [1, 0];
14516
+ const len = Math.hypot(dx, dy);
14517
+ const c = len < 1e-12 ? 1 : dx / len;
14518
+ const s = len < 1e-12 ? 0 : dy / len;
14519
+ return {
14520
+ linear: [
14521
+ c,
14522
+ -s,
14523
+ 0,
14524
+ s,
14525
+ c,
14526
+ 0,
14527
+ 0,
14528
+ 0,
14529
+ 1
14530
+ ],
14531
+ translation: [
14532
+ x,
14533
+ y,
14534
+ 0
14535
+ ]
14536
+ };
14537
+ }
14538
+ /**
14539
+ * Applies the placement chain (outermost first) as one composed rigid motion.
14540
+ * A second applyMatrix on an already-transformed solid can fail BRepCheck on
14541
+ * occt-wasm, so the frames are never applied one after another.
14542
+ */
14543
+ function placeSolid(solid, frames) {
14544
+ let composed = null;
14545
+ for (const frame of frames) {
14546
+ if (frame === null) continue;
14547
+ composed = composed === null ? frame : composeRigid(composed, frame);
14548
+ }
14549
+ if (composed === null || isIdentity(composed)) return (0, brepjs.ok)(solid);
14550
+ const applied = (0, brepjs.applyMatrix)(solid, composed);
14551
+ solid[Symbol.dispose]();
14552
+ if (!applied.ok) return (0, brepjs.err)(importError("PLACEMENT_READ_FAILED", `Placement transform failed: ${applied.error.message}`));
14553
+ return (0, brepjs.ok)(applied.value);
14554
+ }
14555
+ /** `outer ∘ inner`: the motion that applies `inner` first, then `outer`. */
14556
+ function composeRigid(outer, inner) {
14557
+ const [a00, a01, a02, a10, a11, a12, a20, a21, a22] = outer.linear;
14558
+ const [b00, b01, b02, b10, b11, b12, b20, b21, b22] = inner.linear;
14559
+ const [tx, ty, tz] = inner.translation;
14560
+ const [ux, uy, uz] = outer.translation;
14561
+ return {
14562
+ linear: [
14563
+ a00 * b00 + a01 * b10 + a02 * b20,
14564
+ a00 * b01 + a01 * b11 + a02 * b21,
14565
+ a00 * b02 + a01 * b12 + a02 * b22,
14566
+ a10 * b00 + a11 * b10 + a12 * b20,
14567
+ a10 * b01 + a11 * b11 + a12 * b21,
14568
+ a10 * b02 + a11 * b12 + a12 * b22,
14569
+ a20 * b00 + a21 * b10 + a22 * b20,
14570
+ a20 * b01 + a21 * b11 + a22 * b21,
14571
+ a20 * b02 + a21 * b12 + a22 * b22
14572
+ ],
14573
+ translation: [
14574
+ a00 * tx + a01 * ty + a02 * tz + ux,
14575
+ a10 * tx + a11 * ty + a12 * tz + uy,
14576
+ a20 * tx + a21 * ty + a22 * tz + uz
14577
+ ]
14578
+ };
14579
+ }
14580
+ /** Rᵀ·v for a rigid frame's rotation part. */
14581
+ function rotateByTranspose(linear, v) {
14582
+ return [
14583
+ linear[0] * v[0] + linear[3] * v[1] + linear[6] * v[2],
14584
+ linear[1] * v[0] + linear[4] * v[1] + linear[7] * v[2],
14585
+ linear[2] * v[0] + linear[5] * v[1] + linear[8] * v[2]
14586
+ ];
14587
+ }
14588
+ /** Maps a point from the frame's parent space into the frame: Rᵀ·(p − t). */
14589
+ function inverseRigidPoint(frame, p) {
14590
+ const t = frame.translation;
14591
+ return rotateByTranspose(frame.linear, [
14592
+ p[0] - t[0],
14593
+ p[1] - t[1],
14594
+ p[2] - t[2]
14595
+ ]);
14468
14596
  }
14469
14597
  function finalizeSolid(solid, entity, diagnostics) {
14470
14598
  if (!(0, brepjs.isSolid)(solid)) {
@@ -14613,6 +14741,24 @@ function readPolylinePoints(reader, ref, scale) {
14613
14741
  }
14614
14742
  return out.length >= 3 ? out : void 0;
14615
14743
  }
14744
+ function readPoint2D(reader, ref, scale) {
14745
+ const pointRef = asRef(ref);
14746
+ if (pointRef === void 0) return void 0;
14747
+ const pt = reader.getLine(pointRef.value);
14748
+ if (pt === null) return void 0;
14749
+ const coords = asMeasureArray(pt["Coordinates"]);
14750
+ if (coords.length < 2) return void 0;
14751
+ return [(coords[0] ?? 0) * scale * 1e3, (coords[1] ?? 0) * scale * 1e3];
14752
+ }
14753
+ function readDirection2D(reader, ref) {
14754
+ const dirRef = asRef(ref);
14755
+ if (dirRef === void 0) return void 0;
14756
+ const dir = reader.getLine(dirRef.value);
14757
+ if (dir === null) return void 0;
14758
+ const ratios = asMeasureArray(dir["DirectionRatios"]);
14759
+ if (ratios.length < 2) return void 0;
14760
+ return [ratios[0] ?? 0, ratios[1] ?? 0];
14761
+ }
14616
14762
  function readPoint(reader, ref, scale) {
14617
14763
  const pointRef = asRef(ref);
14618
14764
  if (pointRef === void 0) return void 0;
@@ -14639,6 +14785,9 @@ function readDirection(reader, ref) {
14639
14785
  ratios[2] ?? 0
14640
14786
  ];
14641
14787
  }
14788
+ function releaseEmbind(value) {
14789
+ value.delete?.();
14790
+ }
14642
14791
  function asRef(value) {
14643
14792
  if (value !== null && typeof value === "object" && "value" in value) {
14644
14793
  const v = value.value;
@@ -28855,13 +29004,25 @@ function specLocalShift(el) {
28855
29004
  0
28856
29005
  ];
28857
29006
  }
29007
+ /** Skip the resolver occurrence wrappers already owned by `cumulativeFrame`,
29008
+ * then recover only the Body's outer literal Datum translations. An unexpected
29009
+ * wrapper shape returns zero rather than risking an occurrence transform twice. */
29010
+ function bodyLocalTranslation(el, occurrenceTransformDepth) {
29011
+ let body = el.geometry;
29012
+ for (let i = 0; i < occurrenceTransformDepth; i++) {
29013
+ if (body.kind !== "Translate" && body.kind !== "Rotate") return ZERO_TRANSLATION;
29014
+ body = body.target;
29015
+ }
29016
+ return peelTranslates(body).total;
29017
+ }
28858
29018
  /** World frame of an element's own body: the walk's cumulative frame (all
28859
29019
  * authored transforms, ancestors + own) composed with the body's authored axes
28860
- * (`axisX`/`axisZ` props) and its local origin (`origin` prop + centring
28861
- * shift). Under no rotation this reduces to `composedOrigin` + shift. */
28862
- function elementBodyFrame(el, cumulativeFrame) {
29020
+ * (`axisX`/`axisZ` props) and its local origin (`origin` prop + literal Body
29021
+ * Datum + centring shift). Under no rotation this reduces to `composedOrigin`
29022
+ * + shift. */
29023
+ function elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth) {
28863
29024
  return frameMul(frameMul(cumulativeFrame, frameFromPlacement({
28864
- origin: authoredSpecOrigin(el),
29025
+ origin: addTranslation(authoredSpecOrigin(el), bodyLocalTranslation(el, occurrenceTransformDepth)),
28865
29026
  axisX: authoredAxisX(el),
28866
29027
  axisZ: authoredAxisZ(el)
28867
29028
  })), translationFrame(specLocalShift(el)));
@@ -28880,10 +29041,10 @@ function civilNodeFrame(el, cumulativeFrame) {
28880
29041
  * `axisZ` are recomputed from the element's world frame relative to its spatial
28881
29042
  * container. Stair/ramp flights, which carry their own per-flight frame, are
28882
29043
  * each re-placed the same way. */
28883
- function rotatedRoutedInput(flatInput, el, cumulativeFrame, spatialFrame) {
29044
+ function rotatedRoutedInput(flatInput, el, cumulativeFrame, occurrenceTransformDepth, spatialFrame) {
28884
29045
  const toSpatial = frameInverse(spatialFrame);
28885
29046
  if (Array.isArray(flatInput["flights"])) {
28886
- const elementFrame = elementBodyFrame(el, cumulativeFrame);
29047
+ const elementFrame = elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth);
28887
29048
  const flights = flatInput["flights"];
28888
29049
  return {
28889
29050
  ...flatInput,
@@ -28924,7 +29085,7 @@ function rotatedRoutedInput(flatInput, el, cumulativeFrame, spatialFrame) {
28924
29085
  })
28925
29086
  };
28926
29087
  }
28927
- const placed = decomposeFrame(frameMul(toSpatial, elementBodyFrame(el, cumulativeFrame)));
29088
+ const placed = decomposeFrame(frameMul(toSpatial, elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth)));
28928
29089
  return {
28929
29090
  ...flatInput,
28930
29091
  origin: placed.origin,
@@ -29089,6 +29250,7 @@ function familiesToBim(root, options) {
29089
29250
  const walk = (el, state) => {
29090
29251
  const rotatedHere = state.rotated || hasRotateOp(el);
29091
29252
  const cumulativeTranslationHere = addTranslation(state.cumulativeTranslation, authoredTranslation(el));
29253
+ const occurrenceTransformDepthHere = state.occurrenceTransformDepth + el.localTransforms.length;
29092
29254
  const cumulativeFrameHere = frameMul(state.cumulativeFrame, frameFromOps(el.localTransforms));
29093
29255
  let proxiedHere = false;
29094
29256
  let nextRotated = rotatedHere;
@@ -29152,7 +29314,7 @@ function familiesToBim(root, options) {
29152
29314
  }
29153
29315
  const routedInput = el.semantics?.kind === "product" ? civilProductSpecInput(el) : ("input" in route ? route.input : specInput)(el);
29154
29316
  const rotatedBase = Array.isArray(routedInput["flights"]) ? specInput(el) : routedInput;
29155
- const placedInput = rotatedHere ? rotatedRoutedInput(rotatedBase, el, cumulativeFrameHere, state.spatialFrame) : usesAuthoredCivilHierarchy ? relativeSpecInput(routedInput, state.projectedSpatialTranslation) : routedInput;
29317
+ const placedInput = rotatedHere ? rotatedRoutedInput(rotatedBase, el, cumulativeFrameHere, occurrenceTransformDepthHere, state.spatialFrame) : usesAuthoredCivilHierarchy ? relativeSpecInput(routedInput, state.projectedSpatialTranslation) : routedInput;
29156
29318
  const parsed = route.parse(placedInput);
29157
29319
  if (!parsed.ok) return parsed;
29158
29320
  const added = route.add(model, parsed.value, el.keyPath);
@@ -29189,6 +29351,7 @@ function familiesToBim(root, options) {
29189
29351
  rotated: nextRotated,
29190
29352
  cumulativeTranslation: cumulativeTranslationHere,
29191
29353
  projectedSpatialTranslation: nextProjectedSpatialTranslation,
29354
+ occurrenceTransformDepth: occurrenceTransformDepthHere,
29192
29355
  cumulativeFrame: nextCumulativeFrame,
29193
29356
  spatialFrame: nextSpatialFrame
29194
29357
  });
@@ -29202,6 +29365,7 @@ function familiesToBim(root, options) {
29202
29365
  rotated: false,
29203
29366
  cumulativeTranslation: ZERO_TRANSLATION,
29204
29367
  projectedSpatialTranslation: ZERO_TRANSLATION,
29368
+ occurrenceTransformDepth: 0,
29205
29369
  cumulativeFrame: IDENTITY_FRAME,
29206
29370
  spatialFrame: IDENTITY_FRAME
29207
29371
  });
@@ -183,7 +183,7 @@ function fromBrepError(inner, code, message) {
183
183
  };
184
184
  }
185
185
  //#endregion
186
- //#region \0@oxc-project+runtime@0.143.0/helpers/esm/usingCtx.js
186
+ //#region \0@oxc-project+runtime@0.146.0/helpers/esm/usingCtx.js
187
187
  function _usingCtx() {
188
188
  var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
189
189
  var n = Error();
@@ -8756,7 +8756,7 @@ function writeWallGeometry(w, spec, geomSubContextId, parentPlacementId) {
8756
8756
  value: "AREA"
8757
8757
  },
8758
8758
  ProfileName: null,
8759
- Position: w.ref(writeAxis2Placement2D$1(w)),
8759
+ Position: w.ref(writeAxis2Placement2D$1(w, [thicknessM / 2, heightM / 2])),
8760
8760
  XDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, thicknessM),
8761
8761
  YDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, heightM)
8762
8762
  });
@@ -8977,12 +8977,12 @@ function writeRoofGeometry(w, spec, solid, geomSubContextId, parentPlacementId)
8977
8977
  usedFallback: false
8978
8978
  };
8979
8979
  }
8980
- function writeAxis2Placement2D$1(w) {
8980
+ function writeAxis2Placement2D$1(w, location = [0, 0]) {
8981
8981
  const originId = w.nextId();
8982
8982
  w.writeLine({
8983
8983
  expressID: originId,
8984
8984
  type: WebIFC.IFCCARTESIANPOINT,
8985
- Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, 0), w.mkType(WebIFC.IFCLENGTHMEASURE, 0)]
8985
+ Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, location[0]), w.mkType(WebIFC.IFCLENGTHMEASURE, location[1])]
8986
8986
  });
8987
8987
  const id = w.nextId();
8988
8988
  w.writeLine({
@@ -11487,11 +11487,10 @@ function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, g
11487
11487
  const offsetAlongWallM = toIfcLengthM(openingSpec.offsetAlongWall);
11488
11488
  const offsetFromFloorM = toIfcLengthM(openingSpec.offsetFromFloor);
11489
11489
  const thicknessM = toIfcLengthM(wallSpec.thickness);
11490
- const wallHeightM = toIfcLengthM(wallSpec.height);
11491
11490
  const placement3DId = writeAxis2Placement3D(w, [
11492
11491
  offsetAlongWallM + widthM / 2,
11493
- thicknessM / 2,
11494
- -wallHeightM / 2 + offsetFromFloorM + heightM / 2
11492
+ thicknessM,
11493
+ offsetFromFloorM + heightM / 2
11495
11494
  ], [
11496
11495
  0,
11497
11496
  -1,
@@ -12367,6 +12366,24 @@ var SUPPORTED_SCHEMAS = [
12367
12366
  * MUST call {@link SpfReader.close} (it always issues `CloseModel`, preventing
12368
12367
  * WASM handle leaks). The handle is `using`-friendly via `Symbol.dispose`.
12369
12368
  */
12369
+ var IFC_Z_UP_TRANSFORMATION = [
12370
+ 1,
12371
+ 0,
12372
+ 0,
12373
+ 0,
12374
+ 0,
12375
+ 0,
12376
+ 1,
12377
+ 0,
12378
+ 0,
12379
+ -1,
12380
+ 0,
12381
+ 0,
12382
+ 0,
12383
+ 0,
12384
+ 0,
12385
+ 1
12386
+ ];
12370
12387
  function deleteVector(vec) {
12371
12388
  vec.delete?.();
12372
12389
  }
@@ -12399,6 +12416,7 @@ var SpfReader = class SpfReader {
12399
12416
  if (modelId >= 0) api.CloseModel(modelId);
12400
12417
  return err(importError("OPEN_MODEL_FAILED", "web-ifc OpenModel returned an invalid model id"));
12401
12418
  }
12419
+ api.SetGeometryTransformation(modelId, IFC_Z_UP_TRANSFORMATION);
12402
12420
  const schemaRaw = api.GetModelSchema(modelId);
12403
12421
  if (!SUPPORTED_SCHEMAS.includes(schemaRaw)) {
12404
12422
  api.CloseModel(modelId);
@@ -14098,12 +14116,14 @@ function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagno
14098
14116
  diagnostics.push(issue("warning", faceResult.error.code, faceResult.error.message, extrusionId));
14099
14117
  return NONE;
14100
14118
  }
14119
+ const profileFrame = readProfilePosition(reader, sweptArea.value, scale);
14101
14120
  const depthMm = depth * scale * 1e3;
14102
- const extrudeDir = readDirection(reader, ext["ExtrudedDirection"]) ?? [
14121
+ const sweptDir = readDirection(reader, ext["ExtrudedDirection"]) ?? [
14103
14122
  0,
14104
14123
  0,
14105
14124
  1
14106
14125
  ];
14126
+ const extrudeDir = profileFrame === null ? sweptDir : rotateByTranspose(profileFrame.linear, sweptDir);
14107
14127
  const extrudeVec = [
14108
14128
  extrudeDir[0] * depthMm,
14109
14129
  extrudeDir[1] * depthMm,
@@ -14125,7 +14145,11 @@ function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagno
14125
14145
  return NONE;
14126
14146
  }
14127
14147
  const localFrame = readAxis2Placement3D(reader, ext["Position"], scale);
14128
- const placed = placeSolid(solidResult.value, localFrame, worldTransform);
14148
+ const placed = placeSolid(solidResult.value, [
14149
+ worldTransform,
14150
+ localFrame,
14151
+ profileFrame
14152
+ ]);
14129
14153
  if (!placed.ok) {
14130
14154
  diagnostics.push(issue("warning", placed.error.code, placed.error.message, extrusionId));
14131
14155
  return NONE;
@@ -14153,16 +14177,19 @@ function reconstructRevolution(reader, revolutionId, scale, worldTransform, diag
14153
14177
  return NONE;
14154
14178
  }
14155
14179
  const axis1 = reader.getLine(axisRef.value);
14156
- const center = (axis1 === null ? void 0 : readPoint(reader, axis1["Location"], scale)) ?? [
14180
+ const sweptCenter = (axis1 === null ? void 0 : readPoint(reader, axis1["Location"], scale)) ?? [
14157
14181
  0,
14158
14182
  0,
14159
14183
  0
14160
14184
  ];
14161
- const direction = (axis1 === null ? void 0 : readDirection(reader, axis1["Axis"])) ?? [
14185
+ const sweptDirection = (axis1 === null ? void 0 : readDirection(reader, axis1["Axis"])) ?? [
14162
14186
  0,
14163
14187
  0,
14164
14188
  1
14165
14189
  ];
14190
+ const profileFrame = readProfilePosition(reader, sweptArea.value, scale);
14191
+ const center = profileFrame === null ? sweptCenter : inverseRigidPoint(profileFrame, sweptCenter);
14192
+ const direction = profileFrame === null ? sweptDirection : rotateByTranspose(profileFrame.linear, sweptDirection);
14166
14193
  const revolved = (() => {
14167
14194
  try {
14168
14195
  var _usingCtx3 = _usingCtx();
@@ -14189,7 +14216,11 @@ function reconstructRevolution(reader, revolutionId, scale, worldTransform, diag
14189
14216
  return NONE;
14190
14217
  }
14191
14218
  const localFrame = readAxis2Placement3D(reader, rev["Position"], scale);
14192
- const placed = placeSolid(revolved.value, localFrame, worldTransform);
14219
+ const placed = placeSolid(revolved.value, [
14220
+ worldTransform,
14221
+ localFrame,
14222
+ profileFrame
14223
+ ]);
14193
14224
  if (!placed.ok) {
14194
14225
  diagnostics.push(issue("warning", placed.error.code, placed.error.message, revolutionId));
14195
14226
  return NONE;
@@ -14202,20 +14233,9 @@ function reconstructTessellated(reader, productExpressId, diagnostics) {
14202
14233
  diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", "Tessellated geometry yielded no triangles", productExpressId));
14203
14234
  return NONE;
14204
14235
  }
14205
- const stl = packBinaryStl(mesh.vertices, mesh.indices, 1e3);
14206
- let solid = null;
14207
- try {
14208
- const cast = castShape(getKernel().importSTL(stl.buffer));
14209
- if (isSolid(cast)) {
14210
- const valid = validSolid(cast);
14211
- if (valid.ok) solid = valid.value;
14212
- else cast[Symbol.dispose]();
14213
- } else cast[Symbol.dispose]();
14214
- } catch (e) {
14215
- diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `STL round-trip failed: ${errMsg$1(e)}`, productExpressId));
14216
- }
14236
+ const solid = sewMeshToSolid(mesh, 1e3, productExpressId, diagnostics);
14217
14237
  if (solid !== null) {
14218
- diagnostics.push(issue("info", "TESSELLATED_MANIFOLD", "Tessellated mesh recovered as a closed solid via STL round-trip", productExpressId));
14238
+ diagnostics.push(issue("info", "TESSELLATED_MANIFOLD", "Tessellated mesh recovered as a closed solid by sewing its triangles", productExpressId));
14219
14239
  return {
14220
14240
  kind: "SOLID",
14221
14241
  solid,
@@ -14230,6 +14250,52 @@ function reconstructTessellated(reader, productExpressId, diagnostics) {
14230
14250
  diagnostic: "TESSELLATED_LOSSY"
14231
14251
  };
14232
14252
  }
14253
+ /** Loose enough to weld float32 vertices web-ifc emits per face, in mm. */
14254
+ var MESH_SEW_TOLERANCE_MM = .001;
14255
+ /**
14256
+ * Brands a fresh kernel result and releases its pre-downcast arena slot when
14257
+ * the cast moved to a new one (occt-wasm); in-place kernels share the slot and
14258
+ * are left alone. Mirrors brepjs's internal castResultShape.
14259
+ */
14260
+ function castFreshResult(raw) {
14261
+ const cast = castShape(raw);
14262
+ const rawId = raw.id;
14263
+ const castId = cast.wrapped.id;
14264
+ if (!(rawId !== void 0 && castId !== void 0 ? rawId === castId : cast.wrapped === raw)) getKernel().dispose(raw);
14265
+ return cast;
14266
+ }
14267
+ /**
14268
+ * Sews the streamed triangles into a closed solid through the kernel's mesh
14269
+ * builder, the same path brepjs's mesh importers use. Null when the mesh is
14270
+ * open or the kernel rejects it.
14271
+ */
14272
+ function sewMeshToSolid(mesh, scaleToMm, productExpressId, diagnostics) {
14273
+ const kernel = getKernel();
14274
+ const { vertices, indices } = mesh;
14275
+ const point = (index) => [
14276
+ (vertices[index * 3] ?? 0) * scaleToMm,
14277
+ (vertices[index * 3 + 1] ?? 0) * scaleToMm,
14278
+ (vertices[index * 3 + 2] ?? 0) * scaleToMm
14279
+ ];
14280
+ const triangles = [];
14281
+ for (let t = 0; t + 2 < indices.length; t += 3) {
14282
+ const face = kernel.buildTriFace(point(indices[t] ?? 0), point(indices[t + 1] ?? 0), point(indices[t + 2] ?? 0));
14283
+ if (face !== null) triangles.push(face);
14284
+ }
14285
+ if (triangles.length === 0) return null;
14286
+ try {
14287
+ const cast = castFreshResult(kernel.sewAndSolidify(triangles, MESH_SEW_TOLERANCE_MM));
14288
+ const valid = isSolid(cast) ? validSolid(cast) : null;
14289
+ if (valid !== null && valid.ok) return valid.value;
14290
+ cast[Symbol.dispose]();
14291
+ return null;
14292
+ } catch (e) {
14293
+ diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `Mesh sewing failed: ${errMsg$1(e)}`, productExpressId));
14294
+ return null;
14295
+ } finally {
14296
+ for (const face of triangles) kernel.dispose(face);
14297
+ }
14298
+ }
14233
14299
  function collectMesh(reader, productExpressId) {
14234
14300
  const vertices = [];
14235
14301
  const indices = [];
@@ -14258,7 +14324,8 @@ function collectMesh(reader, productExpressId) {
14258
14324
  }
14259
14325
  }
14260
14326
  } finally {
14261
- flatMesh.delete();
14327
+ releaseEmbind(flatMesh.geometries);
14328
+ releaseEmbind(flatMesh);
14262
14329
  }
14263
14330
  });
14264
14331
  if (vertices.length === 0) return null;
@@ -14267,25 +14334,6 @@ function collectMesh(reader, productExpressId) {
14267
14334
  indices: new Uint32Array(indices)
14268
14335
  };
14269
14336
  }
14270
- function packBinaryStl(vertices, indices, scaleToMm) {
14271
- const triCount = Math.floor(indices.length / 3);
14272
- const buffer = /* @__PURE__ */ new ArrayBuffer(84 + triCount * 50);
14273
- const view = new DataView(buffer);
14274
- view.setUint32(80, triCount, true);
14275
- let offset = 84;
14276
- for (let t = 0; t < triCount; t++) {
14277
- offset += 12;
14278
- for (let c = 0; c < 3; c++) {
14279
- const vi = (indices[t * 3 + c] ?? 0) * 3;
14280
- view.setFloat32(offset, (vertices[vi] ?? 0) * scaleToMm, true);
14281
- view.setFloat32(offset + 4, (vertices[vi + 1] ?? 0) * scaleToMm, true);
14282
- view.setFloat32(offset + 8, (vertices[vi + 2] ?? 0) * scaleToMm, true);
14283
- offset += 12;
14284
- }
14285
- offset += 2;
14286
- }
14287
- return new Uint8Array(buffer);
14288
- }
14289
14337
  var PARAMETRIC_PROFILE_BUILDERS = /* @__PURE__ */ new Map([
14290
14338
  [WebIFC.IFCRECTANGLEPROFILEDEF, (f) => ({
14291
14339
  kind: "RECTANGULAR",
@@ -14429,19 +14477,99 @@ function profileToFace(profile) {
14429
14477
  if (!face.ok) return err(importError("GEOMETRY_RECONSTRUCTION_FAILED", `Profile face build failed: ${face.error.message}`));
14430
14478
  return ok(face.value);
14431
14479
  }
14432
- function placeSolid(solid, localFrame, worldTransform) {
14433
- let current = solid;
14434
- for (const transform of [localFrame, worldTransform]) {
14435
- if (transform === null || isIdentity(transform)) continue;
14436
- const applied = applyMatrix(current, transform);
14437
- if (!applied.ok) {
14438
- current[Symbol.dispose]();
14439
- return err(importError("PLACEMENT_READ_FAILED", `Placement transform failed: ${applied.error.message}`));
14440
- }
14441
- current[Symbol.dispose]();
14442
- current = applied.value;
14443
- }
14444
- return ok(current);
14480
+ /**
14481
+ * IfcParameterizedProfileDef.Position places the profile inside the swept
14482
+ * solid's XY plane; the writers rely on it to corner-anchor centred rectangle
14483
+ * profiles. Absent on arbitrary (polyline) profiles.
14484
+ */
14485
+ function readProfilePosition(reader, profileExpressId, scale) {
14486
+ const def = reader.getLine(profileExpressId);
14487
+ const positionRef = def === null ? void 0 : asRef(def["Position"]);
14488
+ if (positionRef === void 0) return null;
14489
+ const position = reader.getLine(positionRef.value);
14490
+ if (position === null) return null;
14491
+ const [x, y] = readPoint2D(reader, position["Location"], scale) ?? [0, 0];
14492
+ const [dx, dy] = readDirection2D(reader, position["RefDirection"]) ?? [1, 0];
14493
+ const len = Math.hypot(dx, dy);
14494
+ const c = len < 1e-12 ? 1 : dx / len;
14495
+ const s = len < 1e-12 ? 0 : dy / len;
14496
+ return {
14497
+ linear: [
14498
+ c,
14499
+ -s,
14500
+ 0,
14501
+ s,
14502
+ c,
14503
+ 0,
14504
+ 0,
14505
+ 0,
14506
+ 1
14507
+ ],
14508
+ translation: [
14509
+ x,
14510
+ y,
14511
+ 0
14512
+ ]
14513
+ };
14514
+ }
14515
+ /**
14516
+ * Applies the placement chain (outermost first) as one composed rigid motion.
14517
+ * A second applyMatrix on an already-transformed solid can fail BRepCheck on
14518
+ * occt-wasm, so the frames are never applied one after another.
14519
+ */
14520
+ function placeSolid(solid, frames) {
14521
+ let composed = null;
14522
+ for (const frame of frames) {
14523
+ if (frame === null) continue;
14524
+ composed = composed === null ? frame : composeRigid(composed, frame);
14525
+ }
14526
+ if (composed === null || isIdentity(composed)) return ok(solid);
14527
+ const applied = applyMatrix(solid, composed);
14528
+ solid[Symbol.dispose]();
14529
+ if (!applied.ok) return err(importError("PLACEMENT_READ_FAILED", `Placement transform failed: ${applied.error.message}`));
14530
+ return ok(applied.value);
14531
+ }
14532
+ /** `outer ∘ inner`: the motion that applies `inner` first, then `outer`. */
14533
+ function composeRigid(outer, inner) {
14534
+ const [a00, a01, a02, a10, a11, a12, a20, a21, a22] = outer.linear;
14535
+ const [b00, b01, b02, b10, b11, b12, b20, b21, b22] = inner.linear;
14536
+ const [tx, ty, tz] = inner.translation;
14537
+ const [ux, uy, uz] = outer.translation;
14538
+ return {
14539
+ linear: [
14540
+ a00 * b00 + a01 * b10 + a02 * b20,
14541
+ a00 * b01 + a01 * b11 + a02 * b21,
14542
+ a00 * b02 + a01 * b12 + a02 * b22,
14543
+ a10 * b00 + a11 * b10 + a12 * b20,
14544
+ a10 * b01 + a11 * b11 + a12 * b21,
14545
+ a10 * b02 + a11 * b12 + a12 * b22,
14546
+ a20 * b00 + a21 * b10 + a22 * b20,
14547
+ a20 * b01 + a21 * b11 + a22 * b21,
14548
+ a20 * b02 + a21 * b12 + a22 * b22
14549
+ ],
14550
+ translation: [
14551
+ a00 * tx + a01 * ty + a02 * tz + ux,
14552
+ a10 * tx + a11 * ty + a12 * tz + uy,
14553
+ a20 * tx + a21 * ty + a22 * tz + uz
14554
+ ]
14555
+ };
14556
+ }
14557
+ /** Rᵀ·v for a rigid frame's rotation part. */
14558
+ function rotateByTranspose(linear, v) {
14559
+ return [
14560
+ linear[0] * v[0] + linear[3] * v[1] + linear[6] * v[2],
14561
+ linear[1] * v[0] + linear[4] * v[1] + linear[7] * v[2],
14562
+ linear[2] * v[0] + linear[5] * v[1] + linear[8] * v[2]
14563
+ ];
14564
+ }
14565
+ /** Maps a point from the frame's parent space into the frame: Rᵀ·(p − t). */
14566
+ function inverseRigidPoint(frame, p) {
14567
+ const t = frame.translation;
14568
+ return rotateByTranspose(frame.linear, [
14569
+ p[0] - t[0],
14570
+ p[1] - t[1],
14571
+ p[2] - t[2]
14572
+ ]);
14445
14573
  }
14446
14574
  function finalizeSolid(solid, entity, diagnostics) {
14447
14575
  if (!isSolid(solid)) {
@@ -14590,6 +14718,24 @@ function readPolylinePoints(reader, ref, scale) {
14590
14718
  }
14591
14719
  return out.length >= 3 ? out : void 0;
14592
14720
  }
14721
+ function readPoint2D(reader, ref, scale) {
14722
+ const pointRef = asRef(ref);
14723
+ if (pointRef === void 0) return void 0;
14724
+ const pt = reader.getLine(pointRef.value);
14725
+ if (pt === null) return void 0;
14726
+ const coords = asMeasureArray(pt["Coordinates"]);
14727
+ if (coords.length < 2) return void 0;
14728
+ return [(coords[0] ?? 0) * scale * 1e3, (coords[1] ?? 0) * scale * 1e3];
14729
+ }
14730
+ function readDirection2D(reader, ref) {
14731
+ const dirRef = asRef(ref);
14732
+ if (dirRef === void 0) return void 0;
14733
+ const dir = reader.getLine(dirRef.value);
14734
+ if (dir === null) return void 0;
14735
+ const ratios = asMeasureArray(dir["DirectionRatios"]);
14736
+ if (ratios.length < 2) return void 0;
14737
+ return [ratios[0] ?? 0, ratios[1] ?? 0];
14738
+ }
14593
14739
  function readPoint(reader, ref, scale) {
14594
14740
  const pointRef = asRef(ref);
14595
14741
  if (pointRef === void 0) return void 0;
@@ -14616,6 +14762,9 @@ function readDirection(reader, ref) {
14616
14762
  ratios[2] ?? 0
14617
14763
  ];
14618
14764
  }
14765
+ function releaseEmbind(value) {
14766
+ value.delete?.();
14767
+ }
14619
14768
  function asRef(value) {
14620
14769
  if (value !== null && typeof value === "object" && "value" in value) {
14621
14770
  const v = value.value;
@@ -28832,13 +28981,25 @@ function specLocalShift(el) {
28832
28981
  0
28833
28982
  ];
28834
28983
  }
28984
+ /** Skip the resolver occurrence wrappers already owned by `cumulativeFrame`,
28985
+ * then recover only the Body's outer literal Datum translations. An unexpected
28986
+ * wrapper shape returns zero rather than risking an occurrence transform twice. */
28987
+ function bodyLocalTranslation(el, occurrenceTransformDepth) {
28988
+ let body = el.geometry;
28989
+ for (let i = 0; i < occurrenceTransformDepth; i++) {
28990
+ if (body.kind !== "Translate" && body.kind !== "Rotate") return ZERO_TRANSLATION;
28991
+ body = body.target;
28992
+ }
28993
+ return peelTranslates(body).total;
28994
+ }
28835
28995
  /** World frame of an element's own body: the walk's cumulative frame (all
28836
28996
  * authored transforms, ancestors + own) composed with the body's authored axes
28837
- * (`axisX`/`axisZ` props) and its local origin (`origin` prop + centring
28838
- * shift). Under no rotation this reduces to `composedOrigin` + shift. */
28839
- function elementBodyFrame(el, cumulativeFrame) {
28997
+ * (`axisX`/`axisZ` props) and its local origin (`origin` prop + literal Body
28998
+ * Datum + centring shift). Under no rotation this reduces to `composedOrigin`
28999
+ * + shift. */
29000
+ function elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth) {
28840
29001
  return frameMul(frameMul(cumulativeFrame, frameFromPlacement({
28841
- origin: authoredSpecOrigin(el),
29002
+ origin: addTranslation(authoredSpecOrigin(el), bodyLocalTranslation(el, occurrenceTransformDepth)),
28842
29003
  axisX: authoredAxisX(el),
28843
29004
  axisZ: authoredAxisZ(el)
28844
29005
  })), translationFrame(specLocalShift(el)));
@@ -28857,10 +29018,10 @@ function civilNodeFrame(el, cumulativeFrame) {
28857
29018
  * `axisZ` are recomputed from the element's world frame relative to its spatial
28858
29019
  * container. Stair/ramp flights, which carry their own per-flight frame, are
28859
29020
  * each re-placed the same way. */
28860
- function rotatedRoutedInput(flatInput, el, cumulativeFrame, spatialFrame) {
29021
+ function rotatedRoutedInput(flatInput, el, cumulativeFrame, occurrenceTransformDepth, spatialFrame) {
28861
29022
  const toSpatial = frameInverse(spatialFrame);
28862
29023
  if (Array.isArray(flatInput["flights"])) {
28863
- const elementFrame = elementBodyFrame(el, cumulativeFrame);
29024
+ const elementFrame = elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth);
28864
29025
  const flights = flatInput["flights"];
28865
29026
  return {
28866
29027
  ...flatInput,
@@ -28901,7 +29062,7 @@ function rotatedRoutedInput(flatInput, el, cumulativeFrame, spatialFrame) {
28901
29062
  })
28902
29063
  };
28903
29064
  }
28904
- const placed = decomposeFrame(frameMul(toSpatial, elementBodyFrame(el, cumulativeFrame)));
29065
+ const placed = decomposeFrame(frameMul(toSpatial, elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth)));
28905
29066
  return {
28906
29067
  ...flatInput,
28907
29068
  origin: placed.origin,
@@ -29066,6 +29227,7 @@ function familiesToBim(root, options) {
29066
29227
  const walk = (el, state) => {
29067
29228
  const rotatedHere = state.rotated || hasRotateOp(el);
29068
29229
  const cumulativeTranslationHere = addTranslation(state.cumulativeTranslation, authoredTranslation(el));
29230
+ const occurrenceTransformDepthHere = state.occurrenceTransformDepth + el.localTransforms.length;
29069
29231
  const cumulativeFrameHere = frameMul(state.cumulativeFrame, frameFromOps(el.localTransforms));
29070
29232
  let proxiedHere = false;
29071
29233
  let nextRotated = rotatedHere;
@@ -29129,7 +29291,7 @@ function familiesToBim(root, options) {
29129
29291
  }
29130
29292
  const routedInput = el.semantics?.kind === "product" ? civilProductSpecInput(el) : ("input" in route ? route.input : specInput)(el);
29131
29293
  const rotatedBase = Array.isArray(routedInput["flights"]) ? specInput(el) : routedInput;
29132
- const placedInput = rotatedHere ? rotatedRoutedInput(rotatedBase, el, cumulativeFrameHere, state.spatialFrame) : usesAuthoredCivilHierarchy ? relativeSpecInput(routedInput, state.projectedSpatialTranslation) : routedInput;
29294
+ const placedInput = rotatedHere ? rotatedRoutedInput(rotatedBase, el, cumulativeFrameHere, occurrenceTransformDepthHere, state.spatialFrame) : usesAuthoredCivilHierarchy ? relativeSpecInput(routedInput, state.projectedSpatialTranslation) : routedInput;
29133
29295
  const parsed = route.parse(placedInput);
29134
29296
  if (!parsed.ok) return parsed;
29135
29297
  const added = route.add(model, parsed.value, el.keyPath);
@@ -29166,6 +29328,7 @@ function familiesToBim(root, options) {
29166
29328
  rotated: nextRotated,
29167
29329
  cumulativeTranslation: cumulativeTranslationHere,
29168
29330
  projectedSpatialTranslation: nextProjectedSpatialTranslation,
29331
+ occurrenceTransformDepth: occurrenceTransformDepthHere,
29169
29332
  cumulativeFrame: nextCumulativeFrame,
29170
29333
  spatialFrame: nextSpatialFrame
29171
29334
  });
@@ -29179,6 +29342,7 @@ function familiesToBim(root, options) {
29179
29342
  rotated: false,
29180
29343
  cumulativeTranslation: ZERO_TRANSLATION,
29181
29344
  projectedSpatialTranslation: ZERO_TRANSLATION,
29345
+ occurrenceTransformDepth: 0,
29182
29346
  cumulativeFrame: IDENTITY_FRAME,
29183
29347
  spatialFrame: IDENTITY_FRAME
29184
29348
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brepjs-bim",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
4
4
  "description": "BIM layer for brepjs — IFC4-aligned parametric building elements",
5
5
  "keywords": [
6
6
  "bim",