brepjs-bim 0.22.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.
- package/dist/brepjs-bim.cjs +660 -116
- package/dist/brepjs-bim.js +660 -116
- package/dist/placementFrame.d.ts +36 -0
- package/package.json +1 -1
package/dist/brepjs-bim.js
CHANGED
|
@@ -183,7 +183,7 @@ function fromBrepError(inner, code, message) {
|
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
185
|
//#endregion
|
|
186
|
-
//#region \0@oxc-project+runtime@0.
|
|
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();
|
|
@@ -7182,7 +7182,7 @@ function placementToMatrix(f) {
|
|
|
7182
7182
|
f.axisX[2] - dot * z[2]
|
|
7183
7183
|
];
|
|
7184
7184
|
const x = lengthSq(projX) < 1e-12 ? normalize$1(orthogonal$2(z)) : normalize$1(projX);
|
|
7185
|
-
const y = cross$
|
|
7185
|
+
const y = cross$2(z, x);
|
|
7186
7186
|
return {
|
|
7187
7187
|
linear: [
|
|
7188
7188
|
x[0],
|
|
@@ -7202,7 +7202,27 @@ function placementToMatrix(f) {
|
|
|
7202
7202
|
]
|
|
7203
7203
|
};
|
|
7204
7204
|
}
|
|
7205
|
-
|
|
7205
|
+
/** Decomposes a column-major matrix into origin (mm) + IFC axes (Z, X). */
|
|
7206
|
+
function decomposePlacement(m) {
|
|
7207
|
+
return {
|
|
7208
|
+
axisX: normalize$1([
|
|
7209
|
+
m[0],
|
|
7210
|
+
m[1],
|
|
7211
|
+
m[2]
|
|
7212
|
+
]),
|
|
7213
|
+
axisZ: normalize$1([
|
|
7214
|
+
m[8],
|
|
7215
|
+
m[9],
|
|
7216
|
+
m[10]
|
|
7217
|
+
]),
|
|
7218
|
+
origin: [
|
|
7219
|
+
m[12],
|
|
7220
|
+
m[13],
|
|
7221
|
+
m[14]
|
|
7222
|
+
]
|
|
7223
|
+
};
|
|
7224
|
+
}
|
|
7225
|
+
function cross$2(a, b) {
|
|
7206
7226
|
return [
|
|
7207
7227
|
a[1] * b[2] - a[2] * b[1],
|
|
7208
7228
|
a[2] * b[0] - a[0] * b[2],
|
|
@@ -7226,11 +7246,11 @@ function normalize$1(v) {
|
|
|
7226
7246
|
];
|
|
7227
7247
|
}
|
|
7228
7248
|
function orthogonal$2(v) {
|
|
7229
|
-
return Math.abs(v[0]) < .9 ? cross$
|
|
7249
|
+
return Math.abs(v[0]) < .9 ? cross$2(v, [
|
|
7230
7250
|
1,
|
|
7231
7251
|
0,
|
|
7232
7252
|
0
|
|
7233
|
-
]) : cross$
|
|
7253
|
+
]) : cross$2(v, [
|
|
7234
7254
|
0,
|
|
7235
7255
|
1,
|
|
7236
7256
|
0
|
|
@@ -8736,7 +8756,7 @@ function writeWallGeometry(w, spec, geomSubContextId, parentPlacementId) {
|
|
|
8736
8756
|
value: "AREA"
|
|
8737
8757
|
},
|
|
8738
8758
|
ProfileName: null,
|
|
8739
|
-
Position: w.ref(writeAxis2Placement2D$1(w)),
|
|
8759
|
+
Position: w.ref(writeAxis2Placement2D$1(w, [thicknessM / 2, heightM / 2])),
|
|
8740
8760
|
XDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, thicknessM),
|
|
8741
8761
|
YDim: w.mkType(WebIFC.IFCPOSITIVELENGTHMEASURE, heightM)
|
|
8742
8762
|
});
|
|
@@ -8957,12 +8977,12 @@ function writeRoofGeometry(w, spec, solid, geomSubContextId, parentPlacementId)
|
|
|
8957
8977
|
usedFallback: false
|
|
8958
8978
|
};
|
|
8959
8979
|
}
|
|
8960
|
-
function writeAxis2Placement2D$1(w) {
|
|
8980
|
+
function writeAxis2Placement2D$1(w, location = [0, 0]) {
|
|
8961
8981
|
const originId = w.nextId();
|
|
8962
8982
|
w.writeLine({
|
|
8963
8983
|
expressID: originId,
|
|
8964
8984
|
type: WebIFC.IFCCARTESIANPOINT,
|
|
8965
|
-
Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, 0), w.mkType(WebIFC.IFCLENGTHMEASURE,
|
|
8985
|
+
Coordinates: [w.mkType(WebIFC.IFCLENGTHMEASURE, location[0]), w.mkType(WebIFC.IFCLENGTHMEASURE, location[1])]
|
|
8966
8986
|
});
|
|
8967
8987
|
const id = w.nextId();
|
|
8968
8988
|
w.writeLine({
|
|
@@ -11467,11 +11487,10 @@ function writeOpeningGeometry(w, guid, openingSpec, wallSpec, wallPlacementId, g
|
|
|
11467
11487
|
const offsetAlongWallM = toIfcLengthM(openingSpec.offsetAlongWall);
|
|
11468
11488
|
const offsetFromFloorM = toIfcLengthM(openingSpec.offsetFromFloor);
|
|
11469
11489
|
const thicknessM = toIfcLengthM(wallSpec.thickness);
|
|
11470
|
-
const wallHeightM = toIfcLengthM(wallSpec.height);
|
|
11471
11490
|
const placement3DId = writeAxis2Placement3D(w, [
|
|
11472
11491
|
offsetAlongWallM + widthM / 2,
|
|
11473
|
-
thicknessM
|
|
11474
|
-
|
|
11492
|
+
thicknessM,
|
|
11493
|
+
offsetFromFloorM + heightM / 2
|
|
11475
11494
|
], [
|
|
11476
11495
|
0,
|
|
11477
11496
|
-1,
|
|
@@ -12347,6 +12366,24 @@ var SUPPORTED_SCHEMAS = [
|
|
|
12347
12366
|
* MUST call {@link SpfReader.close} (it always issues `CloseModel`, preventing
|
|
12348
12367
|
* WASM handle leaks). The handle is `using`-friendly via `Symbol.dispose`.
|
|
12349
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
|
+
];
|
|
12350
12387
|
function deleteVector(vec) {
|
|
12351
12388
|
vec.delete?.();
|
|
12352
12389
|
}
|
|
@@ -12379,6 +12416,7 @@ var SpfReader = class SpfReader {
|
|
|
12379
12416
|
if (modelId >= 0) api.CloseModel(modelId);
|
|
12380
12417
|
return err(importError("OPEN_MODEL_FAILED", "web-ifc OpenModel returned an invalid model id"));
|
|
12381
12418
|
}
|
|
12419
|
+
api.SetGeometryTransformation(modelId, IFC_Z_UP_TRANSFORMATION);
|
|
12382
12420
|
const schemaRaw = api.GetModelSchema(modelId);
|
|
12383
12421
|
if (!SUPPORTED_SCHEMAS.includes(schemaRaw)) {
|
|
12384
12422
|
api.CloseModel(modelId);
|
|
@@ -14078,12 +14116,14 @@ function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagno
|
|
|
14078
14116
|
diagnostics.push(issue("warning", faceResult.error.code, faceResult.error.message, extrusionId));
|
|
14079
14117
|
return NONE;
|
|
14080
14118
|
}
|
|
14119
|
+
const profileFrame = readProfilePosition(reader, sweptArea.value, scale);
|
|
14081
14120
|
const depthMm = depth * scale * 1e3;
|
|
14082
|
-
const
|
|
14121
|
+
const sweptDir = readDirection(reader, ext["ExtrudedDirection"]) ?? [
|
|
14083
14122
|
0,
|
|
14084
14123
|
0,
|
|
14085
14124
|
1
|
|
14086
14125
|
];
|
|
14126
|
+
const extrudeDir = profileFrame === null ? sweptDir : rotateByTranspose(profileFrame.linear, sweptDir);
|
|
14087
14127
|
const extrudeVec = [
|
|
14088
14128
|
extrudeDir[0] * depthMm,
|
|
14089
14129
|
extrudeDir[1] * depthMm,
|
|
@@ -14105,7 +14145,11 @@ function reconstructExtrusion(reader, extrusionId, scale, worldTransform, diagno
|
|
|
14105
14145
|
return NONE;
|
|
14106
14146
|
}
|
|
14107
14147
|
const localFrame = readAxis2Placement3D(reader, ext["Position"], scale);
|
|
14108
|
-
const placed = placeSolid(solidResult.value,
|
|
14148
|
+
const placed = placeSolid(solidResult.value, [
|
|
14149
|
+
worldTransform,
|
|
14150
|
+
localFrame,
|
|
14151
|
+
profileFrame
|
|
14152
|
+
]);
|
|
14109
14153
|
if (!placed.ok) {
|
|
14110
14154
|
diagnostics.push(issue("warning", placed.error.code, placed.error.message, extrusionId));
|
|
14111
14155
|
return NONE;
|
|
@@ -14133,16 +14177,19 @@ function reconstructRevolution(reader, revolutionId, scale, worldTransform, diag
|
|
|
14133
14177
|
return NONE;
|
|
14134
14178
|
}
|
|
14135
14179
|
const axis1 = reader.getLine(axisRef.value);
|
|
14136
|
-
const
|
|
14180
|
+
const sweptCenter = (axis1 === null ? void 0 : readPoint(reader, axis1["Location"], scale)) ?? [
|
|
14137
14181
|
0,
|
|
14138
14182
|
0,
|
|
14139
14183
|
0
|
|
14140
14184
|
];
|
|
14141
|
-
const
|
|
14185
|
+
const sweptDirection = (axis1 === null ? void 0 : readDirection(reader, axis1["Axis"])) ?? [
|
|
14142
14186
|
0,
|
|
14143
14187
|
0,
|
|
14144
14188
|
1
|
|
14145
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);
|
|
14146
14193
|
const revolved = (() => {
|
|
14147
14194
|
try {
|
|
14148
14195
|
var _usingCtx3 = _usingCtx();
|
|
@@ -14169,7 +14216,11 @@ function reconstructRevolution(reader, revolutionId, scale, worldTransform, diag
|
|
|
14169
14216
|
return NONE;
|
|
14170
14217
|
}
|
|
14171
14218
|
const localFrame = readAxis2Placement3D(reader, rev["Position"], scale);
|
|
14172
|
-
const placed = placeSolid(revolved.value,
|
|
14219
|
+
const placed = placeSolid(revolved.value, [
|
|
14220
|
+
worldTransform,
|
|
14221
|
+
localFrame,
|
|
14222
|
+
profileFrame
|
|
14223
|
+
]);
|
|
14173
14224
|
if (!placed.ok) {
|
|
14174
14225
|
diagnostics.push(issue("warning", placed.error.code, placed.error.message, revolutionId));
|
|
14175
14226
|
return NONE;
|
|
@@ -14182,20 +14233,9 @@ function reconstructTessellated(reader, productExpressId, diagnostics) {
|
|
|
14182
14233
|
diagnostics.push(issue("warning", "GEOMETRY_RECONSTRUCTION_FAILED", "Tessellated geometry yielded no triangles", productExpressId));
|
|
14183
14234
|
return NONE;
|
|
14184
14235
|
}
|
|
14185
|
-
const
|
|
14186
|
-
let solid = null;
|
|
14187
|
-
try {
|
|
14188
|
-
const cast = castShape(getKernel().importSTL(stl.buffer));
|
|
14189
|
-
if (isSolid(cast)) {
|
|
14190
|
-
const valid = validSolid(cast);
|
|
14191
|
-
if (valid.ok) solid = valid.value;
|
|
14192
|
-
else cast[Symbol.dispose]();
|
|
14193
|
-
} else cast[Symbol.dispose]();
|
|
14194
|
-
} catch (e) {
|
|
14195
|
-
diagnostics.push(issue("info", "TESSELLATION_NOT_MANIFOLD", `STL round-trip failed: ${errMsg$1(e)}`, productExpressId));
|
|
14196
|
-
}
|
|
14236
|
+
const solid = sewMeshToSolid(mesh, 1e3, productExpressId, diagnostics);
|
|
14197
14237
|
if (solid !== null) {
|
|
14198
|
-
diagnostics.push(issue("info", "TESSELLATED_MANIFOLD", "Tessellated mesh recovered as a closed solid
|
|
14238
|
+
diagnostics.push(issue("info", "TESSELLATED_MANIFOLD", "Tessellated mesh recovered as a closed solid by sewing its triangles", productExpressId));
|
|
14199
14239
|
return {
|
|
14200
14240
|
kind: "SOLID",
|
|
14201
14241
|
solid,
|
|
@@ -14210,6 +14250,52 @@ function reconstructTessellated(reader, productExpressId, diagnostics) {
|
|
|
14210
14250
|
diagnostic: "TESSELLATED_LOSSY"
|
|
14211
14251
|
};
|
|
14212
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
|
+
}
|
|
14213
14299
|
function collectMesh(reader, productExpressId) {
|
|
14214
14300
|
const vertices = [];
|
|
14215
14301
|
const indices = [];
|
|
@@ -14238,7 +14324,8 @@ function collectMesh(reader, productExpressId) {
|
|
|
14238
14324
|
}
|
|
14239
14325
|
}
|
|
14240
14326
|
} finally {
|
|
14241
|
-
flatMesh.
|
|
14327
|
+
releaseEmbind(flatMesh.geometries);
|
|
14328
|
+
releaseEmbind(flatMesh);
|
|
14242
14329
|
}
|
|
14243
14330
|
});
|
|
14244
14331
|
if (vertices.length === 0) return null;
|
|
@@ -14247,25 +14334,6 @@ function collectMesh(reader, productExpressId) {
|
|
|
14247
14334
|
indices: new Uint32Array(indices)
|
|
14248
14335
|
};
|
|
14249
14336
|
}
|
|
14250
|
-
function packBinaryStl(vertices, indices, scaleToMm) {
|
|
14251
|
-
const triCount = Math.floor(indices.length / 3);
|
|
14252
|
-
const buffer = /* @__PURE__ */ new ArrayBuffer(84 + triCount * 50);
|
|
14253
|
-
const view = new DataView(buffer);
|
|
14254
|
-
view.setUint32(80, triCount, true);
|
|
14255
|
-
let offset = 84;
|
|
14256
|
-
for (let t = 0; t < triCount; t++) {
|
|
14257
|
-
offset += 12;
|
|
14258
|
-
for (let c = 0; c < 3; c++) {
|
|
14259
|
-
const vi = (indices[t * 3 + c] ?? 0) * 3;
|
|
14260
|
-
view.setFloat32(offset, (vertices[vi] ?? 0) * scaleToMm, true);
|
|
14261
|
-
view.setFloat32(offset + 4, (vertices[vi + 1] ?? 0) * scaleToMm, true);
|
|
14262
|
-
view.setFloat32(offset + 8, (vertices[vi + 2] ?? 0) * scaleToMm, true);
|
|
14263
|
-
offset += 12;
|
|
14264
|
-
}
|
|
14265
|
-
offset += 2;
|
|
14266
|
-
}
|
|
14267
|
-
return new Uint8Array(buffer);
|
|
14268
|
-
}
|
|
14269
14337
|
var PARAMETRIC_PROFILE_BUILDERS = /* @__PURE__ */ new Map([
|
|
14270
14338
|
[WebIFC.IFCRECTANGLEPROFILEDEF, (f) => ({
|
|
14271
14339
|
kind: "RECTANGULAR",
|
|
@@ -14409,19 +14477,99 @@ function profileToFace(profile) {
|
|
|
14409
14477
|
if (!face.ok) return err(importError("GEOMETRY_RECONSTRUCTION_FAILED", `Profile face build failed: ${face.error.message}`));
|
|
14410
14478
|
return ok(face.value);
|
|
14411
14479
|
}
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
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
|
+
]);
|
|
14425
14573
|
}
|
|
14426
14574
|
function finalizeSolid(solid, entity, diagnostics) {
|
|
14427
14575
|
if (!isSolid(solid)) {
|
|
@@ -14476,17 +14624,17 @@ function readAxis2Placement3D(reader, ref, scale) {
|
|
|
14476
14624
|
refDir[2] - dotXZ * zN[2]
|
|
14477
14625
|
];
|
|
14478
14626
|
const xRawLenSq = xRaw[0] * xRaw[0] + xRaw[1] * xRaw[1] + xRaw[2] * xRaw[2];
|
|
14479
|
-
const orthoZ = Math.abs(zN[0]) < .9 ? cross(zN, [
|
|
14627
|
+
const orthoZ = Math.abs(zN[0]) < .9 ? cross$1(zN, [
|
|
14480
14628
|
1,
|
|
14481
14629
|
0,
|
|
14482
14630
|
0
|
|
14483
|
-
]) : cross(zN, [
|
|
14631
|
+
]) : cross$1(zN, [
|
|
14484
14632
|
0,
|
|
14485
14633
|
1,
|
|
14486
14634
|
0
|
|
14487
14635
|
]);
|
|
14488
14636
|
const xN = xRawLenSq < 1e-12 ? normalize(orthoZ) : normalize(xRaw);
|
|
14489
|
-
const yN = cross(zN, xN);
|
|
14637
|
+
const yN = cross$1(zN, xN);
|
|
14490
14638
|
return {
|
|
14491
14639
|
linear: [
|
|
14492
14640
|
xN[0],
|
|
@@ -14570,6 +14718,24 @@ function readPolylinePoints(reader, ref, scale) {
|
|
|
14570
14718
|
}
|
|
14571
14719
|
return out.length >= 3 ? out : void 0;
|
|
14572
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
|
+
}
|
|
14573
14739
|
function readPoint(reader, ref, scale) {
|
|
14574
14740
|
const pointRef = asRef(ref);
|
|
14575
14741
|
if (pointRef === void 0) return void 0;
|
|
@@ -14596,6 +14762,9 @@ function readDirection(reader, ref) {
|
|
|
14596
14762
|
ratios[2] ?? 0
|
|
14597
14763
|
];
|
|
14598
14764
|
}
|
|
14765
|
+
function releaseEmbind(value) {
|
|
14766
|
+
value.delete?.();
|
|
14767
|
+
}
|
|
14599
14768
|
function asRef(value) {
|
|
14600
14769
|
if (value !== null && typeof value === "object" && "value" in value) {
|
|
14601
14770
|
const v = value.value;
|
|
@@ -14646,7 +14815,7 @@ function normalize(v) {
|
|
|
14646
14815
|
v[2] / len
|
|
14647
14816
|
];
|
|
14648
14817
|
}
|
|
14649
|
-
function cross(a, b) {
|
|
14818
|
+
function cross$1(a, b) {
|
|
14650
14819
|
return [
|
|
14651
14820
|
a[1] * b[2] - a[2] * b[1],
|
|
14652
14821
|
a[2] * b[0] - a[0] * b[2],
|
|
@@ -27908,6 +28077,217 @@ function assignNum(target, key, value) {
|
|
|
27908
28077
|
if (value !== void 0) target[key] = Number(value);
|
|
27909
28078
|
}
|
|
27910
28079
|
//#endregion
|
|
28080
|
+
//#region src/placementFrame.ts
|
|
28081
|
+
var IDENTITY_FRAME = [
|
|
28082
|
+
1,
|
|
28083
|
+
0,
|
|
28084
|
+
0,
|
|
28085
|
+
0,
|
|
28086
|
+
0,
|
|
28087
|
+
1,
|
|
28088
|
+
0,
|
|
28089
|
+
0,
|
|
28090
|
+
0,
|
|
28091
|
+
0,
|
|
28092
|
+
1,
|
|
28093
|
+
0,
|
|
28094
|
+
0,
|
|
28095
|
+
0,
|
|
28096
|
+
0,
|
|
28097
|
+
1
|
|
28098
|
+
];
|
|
28099
|
+
var DEFAULT_AXIS = [
|
|
28100
|
+
0,
|
|
28101
|
+
0,
|
|
28102
|
+
1
|
|
28103
|
+
];
|
|
28104
|
+
var ORIGIN = [
|
|
28105
|
+
0,
|
|
28106
|
+
0,
|
|
28107
|
+
0
|
|
28108
|
+
];
|
|
28109
|
+
/** Column-major 4x4 multiply: `a . b` (apply b first, then a). */
|
|
28110
|
+
function frameMul(a, b) {
|
|
28111
|
+
const out = new Array(16).fill(0);
|
|
28112
|
+
for (let col = 0; col < 4; col++) for (let row = 0; row < 4; row++) {
|
|
28113
|
+
let sum = 0;
|
|
28114
|
+
for (let k = 0; k < 4; k++) sum += (a[k * 4 + row] ?? 0) * (b[col * 4 + k] ?? 0);
|
|
28115
|
+
out[col * 4 + row] = sum;
|
|
28116
|
+
}
|
|
28117
|
+
return out;
|
|
28118
|
+
}
|
|
28119
|
+
function translationFrame(v) {
|
|
28120
|
+
return [
|
|
28121
|
+
1,
|
|
28122
|
+
0,
|
|
28123
|
+
0,
|
|
28124
|
+
0,
|
|
28125
|
+
0,
|
|
28126
|
+
1,
|
|
28127
|
+
0,
|
|
28128
|
+
0,
|
|
28129
|
+
0,
|
|
28130
|
+
0,
|
|
28131
|
+
1,
|
|
28132
|
+
0,
|
|
28133
|
+
v[0],
|
|
28134
|
+
v[1],
|
|
28135
|
+
v[2],
|
|
28136
|
+
1
|
|
28137
|
+
];
|
|
28138
|
+
}
|
|
28139
|
+
function normalizeAxis(axis) {
|
|
28140
|
+
const len = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
|
|
28141
|
+
if (len < 1e-12) return DEFAULT_AXIS;
|
|
28142
|
+
return [
|
|
28143
|
+
axis[0] / len,
|
|
28144
|
+
axis[1] / len,
|
|
28145
|
+
axis[2] / len
|
|
28146
|
+
];
|
|
28147
|
+
}
|
|
28148
|
+
function cross(a, b) {
|
|
28149
|
+
return [
|
|
28150
|
+
a[1] * b[2] - a[2] * b[1],
|
|
28151
|
+
a[2] * b[0] - a[0] * b[2],
|
|
28152
|
+
a[0] * b[1] - a[1] * b[0]
|
|
28153
|
+
];
|
|
28154
|
+
}
|
|
28155
|
+
/**
|
|
28156
|
+
* Builds a frame from an authored `(origin, axisX, axisZ)` placement, using the
|
|
28157
|
+
* same IFC orthonormalization as `placementToMatrix`/`readAxis2Placement3D`:
|
|
28158
|
+
* z = normalize(axisZ); x = normalize(axisX projected perpendicular to z);
|
|
28159
|
+
* y = z x. Lets a civil node authored with explicit axis props enter the same
|
|
28160
|
+
* frame pipeline as a `tRotate` chain.
|
|
28161
|
+
*/
|
|
28162
|
+
function frameFromPlacement(f) {
|
|
28163
|
+
const z = normalizeAxis(f.axisZ);
|
|
28164
|
+
const dot = z[0] * f.axisX[0] + z[1] * f.axisX[1] + z[2] * f.axisX[2];
|
|
28165
|
+
const x = normalizeAxis([
|
|
28166
|
+
f.axisX[0] - dot * z[0],
|
|
28167
|
+
f.axisX[1] - dot * z[1],
|
|
28168
|
+
f.axisX[2] - dot * z[2]
|
|
28169
|
+
]);
|
|
28170
|
+
const y = cross(z, x);
|
|
28171
|
+
return [
|
|
28172
|
+
x[0],
|
|
28173
|
+
x[1],
|
|
28174
|
+
x[2],
|
|
28175
|
+
0,
|
|
28176
|
+
y[0],
|
|
28177
|
+
y[1],
|
|
28178
|
+
y[2],
|
|
28179
|
+
0,
|
|
28180
|
+
z[0],
|
|
28181
|
+
z[1],
|
|
28182
|
+
z[2],
|
|
28183
|
+
0,
|
|
28184
|
+
f.origin[0],
|
|
28185
|
+
f.origin[1],
|
|
28186
|
+
f.origin[2],
|
|
28187
|
+
1
|
|
28188
|
+
];
|
|
28189
|
+
}
|
|
28190
|
+
/**
|
|
28191
|
+
* Rotation by `angleDeg` about `axis` (default +Z) through pivot `at` (default
|
|
28192
|
+
* origin), matching `csg.rotate` (Rodrigues; right-handed about the axis). With
|
|
28193
|
+
* a pivot the motion is `T(at) . R . T(-at)`, i.e. `p -> at + R(p - at)`.
|
|
28194
|
+
*/
|
|
28195
|
+
function rotationFrame(angleDeg, axis = DEFAULT_AXIS, at = ORIGIN) {
|
|
28196
|
+
const [x, y, z] = normalizeAxis(axis);
|
|
28197
|
+
const t = angleDeg * Math.PI / 180;
|
|
28198
|
+
const c = Math.cos(t);
|
|
28199
|
+
const s = Math.sin(t);
|
|
28200
|
+
const C = 1 - c;
|
|
28201
|
+
const r00 = c + x * x * C;
|
|
28202
|
+
const r01 = x * y * C - z * s;
|
|
28203
|
+
const r02 = x * z * C + y * s;
|
|
28204
|
+
const r10 = y * x * C + z * s;
|
|
28205
|
+
const r11 = c + y * y * C;
|
|
28206
|
+
const r12 = y * z * C - x * s;
|
|
28207
|
+
const r20 = z * x * C - y * s;
|
|
28208
|
+
const r21 = z * y * C + x * s;
|
|
28209
|
+
const r22 = c + z * z * C;
|
|
28210
|
+
const rat0 = r00 * at[0] + r01 * at[1] + r02 * at[2];
|
|
28211
|
+
const rat1 = r10 * at[0] + r11 * at[1] + r12 * at[2];
|
|
28212
|
+
const rat2 = r20 * at[0] + r21 * at[1] + r22 * at[2];
|
|
28213
|
+
return [
|
|
28214
|
+
r00,
|
|
28215
|
+
r10,
|
|
28216
|
+
r20,
|
|
28217
|
+
0,
|
|
28218
|
+
r01,
|
|
28219
|
+
r11,
|
|
28220
|
+
r21,
|
|
28221
|
+
0,
|
|
28222
|
+
r02,
|
|
28223
|
+
r12,
|
|
28224
|
+
r22,
|
|
28225
|
+
0,
|
|
28226
|
+
at[0] - rat0,
|
|
28227
|
+
at[1] - rat1,
|
|
28228
|
+
at[2] - rat2,
|
|
28229
|
+
1
|
|
28230
|
+
];
|
|
28231
|
+
}
|
|
28232
|
+
function opFrame(op) {
|
|
28233
|
+
if (op.op === "translate") return translationFrame(op.v);
|
|
28234
|
+
return rotationFrame(op.angleDeg, op.axis ?? DEFAULT_AXIS, op.at ?? ORIGIN);
|
|
28235
|
+
}
|
|
28236
|
+
/**
|
|
28237
|
+
* Composes an authored `TransformOp` chain into one frame, matching families'
|
|
28238
|
+
* `applyOps`: `ops[0]` is innermost (applied first). For `[A, B]` the composed
|
|
28239
|
+
* motion is `B . A`, so a point transforms as `B(A(p))`.
|
|
28240
|
+
*/
|
|
28241
|
+
function frameFromOps(ops) {
|
|
28242
|
+
let m = IDENTITY_FRAME;
|
|
28243
|
+
for (const op of ops) m = frameMul(opFrame(op), m);
|
|
28244
|
+
return m;
|
|
28245
|
+
}
|
|
28246
|
+
/** Rigid inverse of `[R | t]`: `[R^T | -R^T t]`. */
|
|
28247
|
+
function frameInverse(f) {
|
|
28248
|
+
const t0 = f[12] ?? 0;
|
|
28249
|
+
const t1 = f[13] ?? 0;
|
|
28250
|
+
const t2 = f[14] ?? 0;
|
|
28251
|
+
const inv0 = -(f[0] * t0 + f[1] * t1 + f[2] * t2);
|
|
28252
|
+
const inv1 = -(f[4] * t0 + f[5] * t1 + f[6] * t2);
|
|
28253
|
+
const inv2 = -(f[8] * t0 + f[9] * t1 + f[10] * t2);
|
|
28254
|
+
return [
|
|
28255
|
+
f[0],
|
|
28256
|
+
f[4],
|
|
28257
|
+
f[8],
|
|
28258
|
+
0,
|
|
28259
|
+
f[1],
|
|
28260
|
+
f[5],
|
|
28261
|
+
f[9],
|
|
28262
|
+
0,
|
|
28263
|
+
f[2],
|
|
28264
|
+
f[6],
|
|
28265
|
+
f[10],
|
|
28266
|
+
0,
|
|
28267
|
+
inv0,
|
|
28268
|
+
inv1,
|
|
28269
|
+
inv2,
|
|
28270
|
+
1
|
|
28271
|
+
];
|
|
28272
|
+
}
|
|
28273
|
+
/** Decomposes a frame into origin (mm) + IFC axes (axisX, axisZ). */
|
|
28274
|
+
function decomposeFrame(f) {
|
|
28275
|
+
return decomposePlacement(f);
|
|
28276
|
+
}
|
|
28277
|
+
/** Translation column of a frame. */
|
|
28278
|
+
function frameOrigin(f) {
|
|
28279
|
+
return [
|
|
28280
|
+
f[12] ?? 0,
|
|
28281
|
+
f[13] ?? 0,
|
|
28282
|
+
f[14] ?? 0
|
|
28283
|
+
];
|
|
28284
|
+
}
|
|
28285
|
+
/** True when the rotation part is the identity (within tolerance): a pure
|
|
28286
|
+
* translation, so the existing translation-only placement path suffices. */
|
|
28287
|
+
function isPureTranslation(f, eps = 1e-9) {
|
|
28288
|
+
return Math.abs((f[0] ?? 1) - 1) < eps && Math.abs((f[5] ?? 1) - 1) < eps && Math.abs((f[10] ?? 1) - 1) < eps && Math.abs(f[1] ?? 0) < eps && Math.abs(f[2] ?? 0) < eps && Math.abs(f[4] ?? 0) < eps && Math.abs(f[6] ?? 0) < eps && Math.abs(f[8] ?? 0) < eps && Math.abs(f[9] ?? 0) < eps;
|
|
28289
|
+
}
|
|
28290
|
+
//#endregion
|
|
27911
28291
|
//#region src/familiesAdapter.ts
|
|
27912
28292
|
/**
|
|
27913
28293
|
* brepjs-families -> BimModel adapter. Consumes a resolved element tree and
|
|
@@ -28129,6 +28509,36 @@ function centreOrigin(base, shift) {
|
|
|
28129
28509
|
origin[2] + shift[2]
|
|
28130
28510
|
];
|
|
28131
28511
|
}
|
|
28512
|
+
function synthesizedProfile(el) {
|
|
28513
|
+
if (el.semantics?.kind !== "product" || el.props["profile"] !== void 0) return void 0;
|
|
28514
|
+
const length = semanticDimension(el, "length");
|
|
28515
|
+
const width = semanticDimension(el, "width");
|
|
28516
|
+
const height = semanticDimension(el, "height");
|
|
28517
|
+
if (el.semantics.category === "beam" && width !== void 0 && height !== void 0) return {
|
|
28518
|
+
profile: {
|
|
28519
|
+
kind: "RECTANGULAR",
|
|
28520
|
+
width,
|
|
28521
|
+
height
|
|
28522
|
+
},
|
|
28523
|
+
shift: [
|
|
28524
|
+
0,
|
|
28525
|
+
width / 2,
|
|
28526
|
+
height / 2
|
|
28527
|
+
]
|
|
28528
|
+
};
|
|
28529
|
+
if (el.semantics.category === "column" && length !== void 0 && width !== void 0) return {
|
|
28530
|
+
profile: {
|
|
28531
|
+
kind: "RECTANGULAR",
|
|
28532
|
+
width: length,
|
|
28533
|
+
height: width
|
|
28534
|
+
},
|
|
28535
|
+
shift: [
|
|
28536
|
+
length / 2,
|
|
28537
|
+
width / 2,
|
|
28538
|
+
0
|
|
28539
|
+
]
|
|
28540
|
+
};
|
|
28541
|
+
}
|
|
28132
28542
|
/**
|
|
28133
28543
|
* Adapts target-independent civil envelope dimensions onto existing typed BIM
|
|
28134
28544
|
* spec inputs. Reference infrastructure Families intentionally author domain
|
|
@@ -28143,18 +28553,7 @@ function civilProductSpecInput(el) {
|
|
|
28143
28553
|
const height = semanticDimension(el, "height");
|
|
28144
28554
|
switch (el.semantics.category) {
|
|
28145
28555
|
case "beam": {
|
|
28146
|
-
const synthesized =
|
|
28147
|
-
profile: {
|
|
28148
|
-
kind: "RECTANGULAR",
|
|
28149
|
-
width,
|
|
28150
|
-
height
|
|
28151
|
-
},
|
|
28152
|
-
shift: [
|
|
28153
|
-
0,
|
|
28154
|
-
width / 2,
|
|
28155
|
-
height / 2
|
|
28156
|
-
]
|
|
28157
|
-
} : void 0;
|
|
28556
|
+
const synthesized = synthesizedProfile(el);
|
|
28158
28557
|
return {
|
|
28159
28558
|
...base,
|
|
28160
28559
|
...synthesized !== void 0 ? {
|
|
@@ -28166,18 +28565,7 @@ function civilProductSpecInput(el) {
|
|
|
28166
28565
|
};
|
|
28167
28566
|
}
|
|
28168
28567
|
case "column": {
|
|
28169
|
-
const synthesized =
|
|
28170
|
-
profile: {
|
|
28171
|
-
kind: "RECTANGULAR",
|
|
28172
|
-
width: length,
|
|
28173
|
-
height: width
|
|
28174
|
-
},
|
|
28175
|
-
shift: [
|
|
28176
|
-
length / 2,
|
|
28177
|
-
width / 2,
|
|
28178
|
-
0
|
|
28179
|
-
]
|
|
28180
|
-
} : void 0;
|
|
28568
|
+
const synthesized = synthesizedProfile(el);
|
|
28181
28569
|
return {
|
|
28182
28570
|
...base,
|
|
28183
28571
|
...synthesized !== void 0 ? {
|
|
@@ -28244,12 +28632,12 @@ function peelTranslates(node) {
|
|
|
28244
28632
|
moved
|
|
28245
28633
|
};
|
|
28246
28634
|
}
|
|
28247
|
-
/** True when the element's rendered local transform carries a rotation
|
|
28248
|
-
*
|
|
28249
|
-
*
|
|
28250
|
-
*
|
|
28251
|
-
*
|
|
28252
|
-
*
|
|
28635
|
+
/** True when the element's rendered local transform carries a rotation, which
|
|
28636
|
+
* switches the walk to the rigid-frame placement path (the composed rotation
|
|
28637
|
+
* folds into origin + axisX + axisZ). Only authored `transform` rotations
|
|
28638
|
+
* count: a rotation a family render bakes into its own body geometry (e.g. a
|
|
28639
|
+
* circular beam oriented along axisX) stays in the body, which the spec
|
|
28640
|
+
* rebuilds parametrically from props. */
|
|
28253
28641
|
function hasRotateOp(el) {
|
|
28254
28642
|
return el.localTransforms.some((op) => op.op === "rotate");
|
|
28255
28643
|
}
|
|
@@ -28364,7 +28752,7 @@ function materializeOwnedSolid(el, evaluator, errors) {
|
|
|
28364
28752
|
/** Materialize an unrouted element's IR and add it as a proxy. The body is
|
|
28365
28753
|
* authoritative for a proxy (no parametric spec to diverge from), so baked
|
|
28366
28754
|
* transforms — rotations included — are fine here. */
|
|
28367
|
-
function addProxyElement(model, el, evaluator,
|
|
28755
|
+
function addProxyElement(model, el, evaluator, spatialFrame) {
|
|
28368
28756
|
const body = materializeOwnedSolid(el, evaluator, {
|
|
28369
28757
|
evalCode: "FAMILIES_PROXY_EVAL_FAILED",
|
|
28370
28758
|
notSolidCode: "FAMILIES_PROXY_NOT_SOLID",
|
|
@@ -28372,7 +28760,7 @@ function addProxyElement(model, el, evaluator, projectedSpatialTranslation) {
|
|
|
28372
28760
|
routeName: "proxy"
|
|
28373
28761
|
});
|
|
28374
28762
|
if (!body.ok) return body;
|
|
28375
|
-
const localized =
|
|
28763
|
+
const localized = localizeBodyToFrame(el, body.value, spatialFrame, "FAMILIES_PROXY_LOCALIZE_FAILED", "spatial parent");
|
|
28376
28764
|
if (!localized.ok) return localized;
|
|
28377
28765
|
const nameAttr = el.attributes["name"];
|
|
28378
28766
|
const materialProp = el.props["materialName"];
|
|
@@ -28535,8 +28923,9 @@ var DEFAULT_AXIS_Z = [
|
|
|
28535
28923
|
function axisEquals(value, axis) {
|
|
28536
28924
|
return Array.isArray(value) && value.length === 3 && value.every((component, i) => typeof component === "number" && component === axis[i]);
|
|
28537
28925
|
}
|
|
28538
|
-
/**
|
|
28539
|
-
*
|
|
28926
|
+
/** True when a civil node authors non-default `axisX`/`axisZ` props: like a
|
|
28927
|
+
* tRotate, this puts the node (and its subtree) on the rigid-frame placement
|
|
28928
|
+
* path so descendants relativize against the rotated parent frame. */
|
|
28540
28929
|
function hasRotatedAxes(el) {
|
|
28541
28930
|
const axisX = el.props["axisX"];
|
|
28542
28931
|
const axisZ = el.props["axisZ"];
|
|
@@ -28575,6 +28964,131 @@ function authoredTranslation(el) {
|
|
|
28575
28964
|
}
|
|
28576
28965
|
return total;
|
|
28577
28966
|
}
|
|
28967
|
+
function authoredAxisX(el) {
|
|
28968
|
+
const v = el.props["axisX"];
|
|
28969
|
+
return Array.isArray(v) && v.length === 3 ? v : DEFAULT_AXIS_X;
|
|
28970
|
+
}
|
|
28971
|
+
function authoredAxisZ(el) {
|
|
28972
|
+
const v = el.props["axisZ"];
|
|
28973
|
+
return Array.isArray(v) && v.length === 3 ? v : DEFAULT_AXIS_Z;
|
|
28974
|
+
}
|
|
28975
|
+
/** The placement shift that centres a synthesized beam/column cross-section on
|
|
28976
|
+
* its axis, in the element's own (body) frame. Zero for every other route. */
|
|
28977
|
+
function specLocalShift(el) {
|
|
28978
|
+
return synthesizedProfile(el)?.shift ?? [
|
|
28979
|
+
0,
|
|
28980
|
+
0,
|
|
28981
|
+
0
|
|
28982
|
+
];
|
|
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
|
+
}
|
|
28995
|
+
/** World frame of an element's own body: the walk's cumulative frame (all
|
|
28996
|
+
* authored transforms, ancestors + own) composed with the body's authored axes
|
|
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) {
|
|
29001
|
+
return frameMul(frameMul(cumulativeFrame, frameFromPlacement({
|
|
29002
|
+
origin: addTranslation(authoredSpecOrigin(el), bodyLocalTranslation(el, occurrenceTransformDepth)),
|
|
29003
|
+
axisX: authoredAxisX(el),
|
|
29004
|
+
axisZ: authoredAxisZ(el)
|
|
29005
|
+
})), translationFrame(specLocalShift(el)));
|
|
29006
|
+
}
|
|
29007
|
+
/** World frame of a civil spatial node: cumulative transforms composed with any
|
|
29008
|
+
* authored `origin`/`axisX`/`axisZ` props (identity when default). */
|
|
29009
|
+
function civilNodeFrame(el, cumulativeFrame) {
|
|
29010
|
+
return frameMul(cumulativeFrame, frameFromPlacement({
|
|
29011
|
+
origin: authoredSpecOrigin(el),
|
|
29012
|
+
axisX: authoredAxisX(el),
|
|
29013
|
+
axisZ: authoredAxisZ(el)
|
|
29014
|
+
}));
|
|
29015
|
+
}
|
|
29016
|
+
/** Places a routed element's spec input through the composed placement frame:
|
|
29017
|
+
* the flat spec input keeps its dimensions/profile/psets, but `origin`/`axisX`/
|
|
29018
|
+
* `axisZ` are recomputed from the element's world frame relative to its spatial
|
|
29019
|
+
* container. Stair/ramp flights, which carry their own per-flight frame, are
|
|
29020
|
+
* each re-placed the same way. */
|
|
29021
|
+
function rotatedRoutedInput(flatInput, el, cumulativeFrame, occurrenceTransformDepth, spatialFrame) {
|
|
29022
|
+
const toSpatial = frameInverse(spatialFrame);
|
|
29023
|
+
if (Array.isArray(flatInput["flights"])) {
|
|
29024
|
+
const elementFrame = elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth);
|
|
29025
|
+
const flights = flatInput["flights"];
|
|
29026
|
+
return {
|
|
29027
|
+
...flatInput,
|
|
29028
|
+
origin: [
|
|
29029
|
+
0,
|
|
29030
|
+
0,
|
|
29031
|
+
0
|
|
29032
|
+
],
|
|
29033
|
+
axisX: [
|
|
29034
|
+
1,
|
|
29035
|
+
0,
|
|
29036
|
+
0
|
|
29037
|
+
],
|
|
29038
|
+
axisZ: [
|
|
29039
|
+
0,
|
|
29040
|
+
0,
|
|
29041
|
+
1
|
|
29042
|
+
],
|
|
29043
|
+
flights: flights.map((flight) => {
|
|
29044
|
+
if (typeof flight !== "object" || flight === null) return flight;
|
|
29045
|
+
const f = flight;
|
|
29046
|
+
const world = frameMul(elementFrame, frameFromPlacement({
|
|
29047
|
+
origin: f["origin"] ?? [
|
|
29048
|
+
0,
|
|
29049
|
+
0,
|
|
29050
|
+
0
|
|
29051
|
+
],
|
|
29052
|
+
axisX: f["axisX"] ?? DEFAULT_AXIS_X,
|
|
29053
|
+
axisZ: f["axisZ"] ?? DEFAULT_AXIS_Z
|
|
29054
|
+
}));
|
|
29055
|
+
const placed = decomposeFrame(frameMul(toSpatial, world));
|
|
29056
|
+
return {
|
|
29057
|
+
...f,
|
|
29058
|
+
origin: placed.origin,
|
|
29059
|
+
axisX: placed.axisX,
|
|
29060
|
+
axisZ: placed.axisZ
|
|
29061
|
+
};
|
|
29062
|
+
})
|
|
29063
|
+
};
|
|
29064
|
+
}
|
|
29065
|
+
const placed = decomposeFrame(frameMul(toSpatial, elementBodyFrame(el, cumulativeFrame, occurrenceTransformDepth)));
|
|
29066
|
+
return {
|
|
29067
|
+
...flatInput,
|
|
29068
|
+
origin: placed.origin,
|
|
29069
|
+
axisX: placed.axisX,
|
|
29070
|
+
axisZ: placed.axisZ
|
|
29071
|
+
};
|
|
29072
|
+
}
|
|
29073
|
+
/** Moves an owned world-baked body into its spatial container's local frame.
|
|
29074
|
+
* A pure translation keeps the fast `translate` path (identical to the prior
|
|
29075
|
+
* behaviour); a rotated container applies the inverse frame via `applyMatrix`. */
|
|
29076
|
+
function localizeBodyToFrame(el, body, spatialFrame, errorCode, frameName) {
|
|
29077
|
+
if (isPureTranslation(spatialFrame)) return localizeOwnedSolid(el, body, frameOrigin(spatialFrame), errorCode, frameName);
|
|
29078
|
+
const inverse = decomposeFrame(frameInverse(spatialFrame));
|
|
29079
|
+
try {
|
|
29080
|
+
const localized = applyMatrix(body, placementToMatrix(inverse));
|
|
29081
|
+
if (!localized.ok) {
|
|
29082
|
+
body[Symbol.dispose]();
|
|
29083
|
+
return err(specError(errorCode, `familiesToBim: '${el.keyPath}' could not move its body into the ${frameName} frame`, localized.error));
|
|
29084
|
+
}
|
|
29085
|
+
body[Symbol.dispose]();
|
|
29086
|
+
return ok(localized.value);
|
|
29087
|
+
} catch (cause) {
|
|
29088
|
+
body[Symbol.dispose]();
|
|
29089
|
+
return err(specError(errorCode, `familiesToBim: '${el.keyPath}' could not move its body into the ${frameName} frame`, cause));
|
|
29090
|
+
}
|
|
29091
|
+
}
|
|
28578
29092
|
function civilSpatialInput(el, localTranslation) {
|
|
28579
29093
|
const semantics = el.semantics;
|
|
28580
29094
|
const composition = semantics !== void 0 && "composition" in semantics ? CIVIL_COMPOSITION[semantics.composition] : void 0;
|
|
@@ -28587,22 +29101,36 @@ function civilSpatialInput(el, localTranslation) {
|
|
|
28587
29101
|
...composition !== void 0 ? { compositionType: composition } : {}
|
|
28588
29102
|
};
|
|
28589
29103
|
}
|
|
29104
|
+
/** Relativizes a civil node's world frame against its parent civil frame into
|
|
29105
|
+
* the origin/axisX/axisZ the spatial specs consume. */
|
|
29106
|
+
function civilSpatialFrameInput(el, nodeFrame, parentFrame) {
|
|
29107
|
+
const semantics = el.semantics;
|
|
29108
|
+
const composition = semantics !== void 0 && "composition" in semantics ? CIVIL_COMPOSITION[semantics.composition] : void 0;
|
|
29109
|
+
const local = decomposeFrame(frameMul(frameInverse(parentFrame), nodeFrame));
|
|
29110
|
+
return {
|
|
29111
|
+
name: semanticName(el),
|
|
29112
|
+
origin: local.origin,
|
|
29113
|
+
axisX: local.axisX,
|
|
29114
|
+
axisZ: local.axisZ,
|
|
29115
|
+
...composition !== void 0 ? { compositionType: composition } : {}
|
|
29116
|
+
};
|
|
29117
|
+
}
|
|
28590
29118
|
function civilParentAccepts(kind, parent) {
|
|
28591
29119
|
if (kind === "site") return parent === "project";
|
|
28592
29120
|
if (kind === "bridge") return parent === "site";
|
|
28593
29121
|
return parent === "bridge" || parent === "bridge-part";
|
|
28594
29122
|
}
|
|
28595
|
-
function addCivilSpatialOccurrence(model, el, kind,
|
|
29123
|
+
function addCivilSpatialOccurrence(model, el, kind, input) {
|
|
28596
29124
|
if (kind === "site") {
|
|
28597
29125
|
if (el.semantics?.role !== "transport-site") return unsupportedCivilRole(el, "Site");
|
|
28598
|
-
const parsed = parseSiteSpec(
|
|
29126
|
+
const parsed = parseSiteSpec(input);
|
|
28599
29127
|
return parsed.ok ? model.addSite(parsed.value, { stableKey: el.keyPath }) : parsed;
|
|
28600
29128
|
}
|
|
28601
29129
|
if (kind === "bridge") {
|
|
28602
29130
|
const predefinedType = lookup(BRIDGE_ROLE, el.semantics?.role ?? "");
|
|
28603
29131
|
if (predefinedType === void 0) return unsupportedCivilRole(el, "Bridge");
|
|
28604
29132
|
const parsed = parseBridgeSpec({
|
|
28605
|
-
...
|
|
29133
|
+
...input,
|
|
28606
29134
|
predefinedType
|
|
28607
29135
|
});
|
|
28608
29136
|
return parsed.ok ? model.addBridge(parsed.value, { stableKey: el.keyPath }) : parsed;
|
|
@@ -28611,7 +29139,7 @@ function addCivilSpatialOccurrence(model, el, kind, localTranslation) {
|
|
|
28611
29139
|
const predefinedType = lookup(BRIDGE_PART_ROLE, el.semantics?.role ?? "");
|
|
28612
29140
|
if (predefinedType === void 0) return unsupportedCivilRole(el, "Bridge Part");
|
|
28613
29141
|
const parsed = parseBridgePartSpec({
|
|
28614
|
-
...
|
|
29142
|
+
...input,
|
|
28615
29143
|
predefinedType,
|
|
28616
29144
|
usageType: subdivision !== void 0 ? CIVIL_USAGE[subdivision] : "NOTDEFINED"
|
|
28617
29145
|
});
|
|
@@ -28638,7 +29166,7 @@ function relativeSpecInput(input, projectedSpatialTranslation) {
|
|
|
28638
29166
|
origin: relativeOrigin(input["origin"])
|
|
28639
29167
|
};
|
|
28640
29168
|
}
|
|
28641
|
-
function addEarthworksFillElement(model, el, evaluator,
|
|
29169
|
+
function addEarthworksFillElement(model, el, evaluator, spatialFrame) {
|
|
28642
29170
|
if (el.semantics?.kind !== "product") return err(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: '${el.keyPath}' is not authored as a civil Product`));
|
|
28643
29171
|
const predefinedType = lookup(EARTHWORKS_FILL_ROLE, el.semantics.role);
|
|
28644
29172
|
if (predefinedType === void 0) return unsupportedCivilRole(el, "Earthworks Fill");
|
|
@@ -28649,7 +29177,7 @@ function addEarthworksFillElement(model, el, evaluator, projectedSpatialTranslat
|
|
|
28649
29177
|
routeName: "Earthworks Fill"
|
|
28650
29178
|
});
|
|
28651
29179
|
if (!body.ok) return body;
|
|
28652
|
-
const localized =
|
|
29180
|
+
const localized = localizeBodyToFrame(el, body.value, spatialFrame, "FAMILIES_EARTHWORKS_LOCALIZE_FAILED", "Bridge Part");
|
|
28653
29181
|
if (!localized.ok) return localized;
|
|
28654
29182
|
const specProps = collectSpecProps(el);
|
|
28655
29183
|
const authoredMaterial = el.props["materialName"];
|
|
@@ -28699,10 +29227,15 @@ function familiesToBim(root, options) {
|
|
|
28699
29227
|
const walk = (el, state) => {
|
|
28700
29228
|
const rotatedHere = state.rotated || hasRotateOp(el);
|
|
28701
29229
|
const cumulativeTranslationHere = addTranslation(state.cumulativeTranslation, authoredTranslation(el));
|
|
29230
|
+
const occurrenceTransformDepthHere = state.occurrenceTransformDepth + el.localTransforms.length;
|
|
29231
|
+
const cumulativeFrameHere = frameMul(state.cumulativeFrame, frameFromOps(el.localTransforms));
|
|
28702
29232
|
let proxiedHere = false;
|
|
29233
|
+
let nextRotated = rotatedHere;
|
|
28703
29234
|
let nextSpatialStructureId = state.spatialStructureId;
|
|
28704
29235
|
let nextCivilParent = state.civilParent;
|
|
28705
29236
|
let nextProjectedSpatialTranslation = state.projectedSpatialTranslation;
|
|
29237
|
+
let nextCumulativeFrame = cumulativeFrameHere;
|
|
29238
|
+
let nextSpatialFrame = state.spatialFrame;
|
|
28706
29239
|
const archetype = archetypeFor(el);
|
|
28707
29240
|
const effectiveArchetype = el.semantics?.kind === "product" ? civilProductArchetype(el) : archetype;
|
|
28708
29241
|
const route = specRoute(effectiveArchetype);
|
|
@@ -28711,15 +29244,19 @@ function familiesToBim(root, options) {
|
|
|
28711
29244
|
if (!usesAuthoredCivilHierarchy || !civilParentAccepts(civilKind, state.civilParent) || state.spatialStructureId === null) return err(specError("FAMILIES_INVALID_CIVIL_HIERARCHY", `familiesToBim: civil '${civilKind}' at '${el.keyPath}' cannot occur under '${state.civilParent}'`));
|
|
28712
29245
|
const keyed = requireKeyed(el);
|
|
28713
29246
|
if (!keyed.ok) return keyed;
|
|
28714
|
-
if (rotatedHere || hasRotatedAxes(el)) return err(specError("FAMILIES_UNSUPPORTED_TRANSFORM", `familiesToBim: civil spatial element '${el.keyPath}' carries a rotated frame — descendants are relativized by translation only, so bake the orientation into child geometry`));
|
|
28715
29247
|
if (el.geometry.kind !== "Empty") return err(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: civil spatial element '${el.keyPath}' carries its own geometry — Site/Bridge/Bridge Part export no body, so author it as a child Product (e.g. Earthworks Fill)`));
|
|
28716
|
-
const
|
|
28717
|
-
const
|
|
29248
|
+
const nodeFrame = civilNodeFrame(el, cumulativeFrameHere);
|
|
29249
|
+
const rotatedFrame = rotatedHere || hasRotatedAxes(el);
|
|
29250
|
+
const input = rotatedFrame ? civilSpatialFrameInput(el, nodeFrame, state.spatialFrame) : civilSpatialInput(el, subtractTranslation(cumulativeTranslationHere, state.projectedSpatialTranslation));
|
|
29251
|
+
const added = addCivilSpatialOccurrence(model, el, civilKind, input);
|
|
28718
29252
|
if (!added.ok) return added;
|
|
28719
29253
|
model.aggregate(state.spatialStructureId, added.value);
|
|
28720
29254
|
idByKeyPath.set(el.keyPath, added.value);
|
|
28721
29255
|
nextSpatialStructureId = added.value;
|
|
28722
29256
|
nextCivilParent = civilKind;
|
|
29257
|
+
nextRotated = rotatedFrame;
|
|
29258
|
+
nextCumulativeFrame = nodeFrame;
|
|
29259
|
+
nextSpatialFrame = nodeFrame;
|
|
28723
29260
|
nextProjectedSpatialTranslation = addTranslation(cumulativeTranslationHere, authoredSpecOrigin(el));
|
|
28724
29261
|
} else if (isCivilSpatialIntent(el)) return err(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: unsupported civil '${el.semantics?.kind ?? "unknown"}' category '${el.semantics?.category ?? ""}' at '${el.keyPath}'`));
|
|
28725
29262
|
else if (archetype === "storey") {
|
|
@@ -28740,21 +29277,22 @@ function familiesToBim(root, options) {
|
|
|
28740
29277
|
if (bodyEvaluator === void 0) return err(specError("FAMILIES_EARTHWORKS_EVALUATOR_REQUIRED", `familiesToBim: Earthworks Fill '${el.keyPath}' needs bodyEvaluator to materialize its exact Product Body`));
|
|
28741
29278
|
const keyed = requireKeyed(el);
|
|
28742
29279
|
if (!keyed.ok) return keyed;
|
|
28743
|
-
const added = addEarthworksFillElement(model, el, bodyEvaluator, state.
|
|
29280
|
+
const added = addEarthworksFillElement(model, el, bodyEvaluator, state.spatialFrame);
|
|
28744
29281
|
if (!added.ok) return added;
|
|
28745
29282
|
model.placeIn(added.value, nextSpatialStructureId);
|
|
28746
29283
|
idByKeyPath.set(el.keyPath, added.value);
|
|
28747
29284
|
} else if (route !== void 0) {
|
|
28748
29285
|
const keyed = requireKeyed(el);
|
|
28749
29286
|
if (!keyed.ok) return keyed;
|
|
28750
|
-
if (rotatedHere) return err(specError("FAMILIES_UNSUPPORTED_TRANSFORM", `familiesToBim: '${el.keyPath}' carries a rotated placement — the spec path folds only translations into IfcLocalPlacement; orient walls via axisX instead of tRotate`));
|
|
28751
29287
|
const voids = el.props["voids"];
|
|
28752
29288
|
if (Array.isArray(voids)) {
|
|
28753
29289
|
const openings = el.children.filter((c) => c.type === "Opening").length;
|
|
28754
29290
|
if (voids.length > openings) return err(specError("FAMILIES_ANONYMOUS_VOID", `familiesToBim: '${el.keyPath}' has ${voids.length - openings} anonymous void(s) the IFC body cannot carry — use a fill-role family (Door/Window) for each void`));
|
|
28755
29291
|
}
|
|
28756
29292
|
const routedInput = el.semantics?.kind === "product" ? civilProductSpecInput(el) : ("input" in route ? route.input : specInput)(el);
|
|
28757
|
-
const
|
|
29293
|
+
const rotatedBase = Array.isArray(routedInput["flights"]) ? specInput(el) : routedInput;
|
|
29294
|
+
const placedInput = rotatedHere ? rotatedRoutedInput(rotatedBase, el, cumulativeFrameHere, occurrenceTransformDepthHere, state.spatialFrame) : usesAuthoredCivilHierarchy ? relativeSpecInput(routedInput, state.projectedSpatialTranslation) : routedInput;
|
|
29295
|
+
const parsed = route.parse(placedInput);
|
|
28758
29296
|
if (!parsed.ok) return parsed;
|
|
28759
29297
|
const added = route.add(model, parsed.value, el.keyPath);
|
|
28760
29298
|
if (!added.ok) return added;
|
|
@@ -28771,7 +29309,7 @@ function familiesToBim(root, options) {
|
|
|
28771
29309
|
const keyed = requireKeyed(el);
|
|
28772
29310
|
if (!keyed.ok) return keyed;
|
|
28773
29311
|
if (nextSpatialStructureId === null || usesAuthoredCivilHierarchy && state.civilParent !== "bridge-part") return err(specError(usesAuthoredCivilHierarchy ? "FAMILIES_INVALID_CIVIL_HIERARCHY" : "FAMILIES_NO_STOREY", usesAuthoredCivilHierarchy ? `familiesToBim: physical product '${el.keyPath}' needs a Bridge Part ancestor` : `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment; a container family needs archetype: 'storey' to be recognised under any name`));
|
|
28774
|
-
const added = addProxyElement(model, el, options.proxyEvaluator, state.
|
|
29312
|
+
const added = addProxyElement(model, el, options.proxyEvaluator, state.spatialFrame);
|
|
28775
29313
|
if (!added.ok) return added;
|
|
28776
29314
|
model.placeIn(added.value, nextSpatialStructureId);
|
|
28777
29315
|
idByKeyPath.set(el.keyPath, added.value);
|
|
@@ -28787,9 +29325,12 @@ function familiesToBim(root, options) {
|
|
|
28787
29325
|
const r = walk(child, {
|
|
28788
29326
|
spatialStructureId: nextSpatialStructureId,
|
|
28789
29327
|
civilParent: nextCivilParent,
|
|
28790
|
-
rotated:
|
|
29328
|
+
rotated: nextRotated,
|
|
28791
29329
|
cumulativeTranslation: cumulativeTranslationHere,
|
|
28792
|
-
projectedSpatialTranslation: nextProjectedSpatialTranslation
|
|
29330
|
+
projectedSpatialTranslation: nextProjectedSpatialTranslation,
|
|
29331
|
+
occurrenceTransformDepth: occurrenceTransformDepthHere,
|
|
29332
|
+
cumulativeFrame: nextCumulativeFrame,
|
|
29333
|
+
spatialFrame: nextSpatialFrame
|
|
28793
29334
|
});
|
|
28794
29335
|
if (!r.ok) return r;
|
|
28795
29336
|
}
|
|
@@ -28800,7 +29341,10 @@ function familiesToBim(root, options) {
|
|
|
28800
29341
|
civilParent: "project",
|
|
28801
29342
|
rotated: false,
|
|
28802
29343
|
cumulativeTranslation: ZERO_TRANSLATION,
|
|
28803
|
-
projectedSpatialTranslation: ZERO_TRANSLATION
|
|
29344
|
+
projectedSpatialTranslation: ZERO_TRANSLATION,
|
|
29345
|
+
occurrenceTransformDepth: 0,
|
|
29346
|
+
cumulativeFrame: IDENTITY_FRAME,
|
|
29347
|
+
spatialFrame: IDENTITY_FRAME
|
|
28804
29348
|
});
|
|
28805
29349
|
if (!walked.ok) {
|
|
28806
29350
|
model[Symbol.dispose]();
|