brepjs-bim 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brepjs-bim.cjs +438 -58
- package/dist/brepjs-bim.js +438 -58
- package/dist/placementFrame.d.ts +36 -0
- package/package.json +1 -1
package/dist/brepjs-bim.cjs
CHANGED
|
@@ -7205,7 +7205,7 @@ function placementToMatrix(f) {
|
|
|
7205
7205
|
f.axisX[2] - dot * z[2]
|
|
7206
7206
|
];
|
|
7207
7207
|
const x = lengthSq(projX) < 1e-12 ? normalize$1(orthogonal$2(z)) : normalize$1(projX);
|
|
7208
|
-
const y = cross$
|
|
7208
|
+
const y = cross$2(z, x);
|
|
7209
7209
|
return {
|
|
7210
7210
|
linear: [
|
|
7211
7211
|
x[0],
|
|
@@ -7225,7 +7225,27 @@ function placementToMatrix(f) {
|
|
|
7225
7225
|
]
|
|
7226
7226
|
};
|
|
7227
7227
|
}
|
|
7228
|
-
|
|
7228
|
+
/** Decomposes a column-major matrix into origin (mm) + IFC axes (Z, X). */
|
|
7229
|
+
function decomposePlacement(m) {
|
|
7230
|
+
return {
|
|
7231
|
+
axisX: normalize$1([
|
|
7232
|
+
m[0],
|
|
7233
|
+
m[1],
|
|
7234
|
+
m[2]
|
|
7235
|
+
]),
|
|
7236
|
+
axisZ: normalize$1([
|
|
7237
|
+
m[8],
|
|
7238
|
+
m[9],
|
|
7239
|
+
m[10]
|
|
7240
|
+
]),
|
|
7241
|
+
origin: [
|
|
7242
|
+
m[12],
|
|
7243
|
+
m[13],
|
|
7244
|
+
m[14]
|
|
7245
|
+
]
|
|
7246
|
+
};
|
|
7247
|
+
}
|
|
7248
|
+
function cross$2(a, b) {
|
|
7229
7249
|
return [
|
|
7230
7250
|
a[1] * b[2] - a[2] * b[1],
|
|
7231
7251
|
a[2] * b[0] - a[0] * b[2],
|
|
@@ -7249,11 +7269,11 @@ function normalize$1(v) {
|
|
|
7249
7269
|
];
|
|
7250
7270
|
}
|
|
7251
7271
|
function orthogonal$2(v) {
|
|
7252
|
-
return Math.abs(v[0]) < .9 ? cross$
|
|
7272
|
+
return Math.abs(v[0]) < .9 ? cross$2(v, [
|
|
7253
7273
|
1,
|
|
7254
7274
|
0,
|
|
7255
7275
|
0
|
|
7256
|
-
]) : cross$
|
|
7276
|
+
]) : cross$2(v, [
|
|
7257
7277
|
0,
|
|
7258
7278
|
1,
|
|
7259
7279
|
0
|
|
@@ -14499,17 +14519,17 @@ function readAxis2Placement3D(reader, ref, scale) {
|
|
|
14499
14519
|
refDir[2] - dotXZ * zN[2]
|
|
14500
14520
|
];
|
|
14501
14521
|
const xRawLenSq = xRaw[0] * xRaw[0] + xRaw[1] * xRaw[1] + xRaw[2] * xRaw[2];
|
|
14502
|
-
const orthoZ = Math.abs(zN[0]) < .9 ? cross(zN, [
|
|
14522
|
+
const orthoZ = Math.abs(zN[0]) < .9 ? cross$1(zN, [
|
|
14503
14523
|
1,
|
|
14504
14524
|
0,
|
|
14505
14525
|
0
|
|
14506
|
-
]) : cross(zN, [
|
|
14526
|
+
]) : cross$1(zN, [
|
|
14507
14527
|
0,
|
|
14508
14528
|
1,
|
|
14509
14529
|
0
|
|
14510
14530
|
]);
|
|
14511
14531
|
const xN = xRawLenSq < 1e-12 ? normalize(orthoZ) : normalize(xRaw);
|
|
14512
|
-
const yN = cross(zN, xN);
|
|
14532
|
+
const yN = cross$1(zN, xN);
|
|
14513
14533
|
return {
|
|
14514
14534
|
linear: [
|
|
14515
14535
|
xN[0],
|
|
@@ -14669,7 +14689,7 @@ function normalize(v) {
|
|
|
14669
14689
|
v[2] / len
|
|
14670
14690
|
];
|
|
14671
14691
|
}
|
|
14672
|
-
function cross(a, b) {
|
|
14692
|
+
function cross$1(a, b) {
|
|
14673
14693
|
return [
|
|
14674
14694
|
a[1] * b[2] - a[2] * b[1],
|
|
14675
14695
|
a[2] * b[0] - a[0] * b[2],
|
|
@@ -27931,6 +27951,217 @@ function assignNum(target, key, value) {
|
|
|
27931
27951
|
if (value !== void 0) target[key] = Number(value);
|
|
27932
27952
|
}
|
|
27933
27953
|
//#endregion
|
|
27954
|
+
//#region src/placementFrame.ts
|
|
27955
|
+
var IDENTITY_FRAME = [
|
|
27956
|
+
1,
|
|
27957
|
+
0,
|
|
27958
|
+
0,
|
|
27959
|
+
0,
|
|
27960
|
+
0,
|
|
27961
|
+
1,
|
|
27962
|
+
0,
|
|
27963
|
+
0,
|
|
27964
|
+
0,
|
|
27965
|
+
0,
|
|
27966
|
+
1,
|
|
27967
|
+
0,
|
|
27968
|
+
0,
|
|
27969
|
+
0,
|
|
27970
|
+
0,
|
|
27971
|
+
1
|
|
27972
|
+
];
|
|
27973
|
+
var DEFAULT_AXIS = [
|
|
27974
|
+
0,
|
|
27975
|
+
0,
|
|
27976
|
+
1
|
|
27977
|
+
];
|
|
27978
|
+
var ORIGIN = [
|
|
27979
|
+
0,
|
|
27980
|
+
0,
|
|
27981
|
+
0
|
|
27982
|
+
];
|
|
27983
|
+
/** Column-major 4x4 multiply: `a . b` (apply b first, then a). */
|
|
27984
|
+
function frameMul(a, b) {
|
|
27985
|
+
const out = new Array(16).fill(0);
|
|
27986
|
+
for (let col = 0; col < 4; col++) for (let row = 0; row < 4; row++) {
|
|
27987
|
+
let sum = 0;
|
|
27988
|
+
for (let k = 0; k < 4; k++) sum += (a[k * 4 + row] ?? 0) * (b[col * 4 + k] ?? 0);
|
|
27989
|
+
out[col * 4 + row] = sum;
|
|
27990
|
+
}
|
|
27991
|
+
return out;
|
|
27992
|
+
}
|
|
27993
|
+
function translationFrame(v) {
|
|
27994
|
+
return [
|
|
27995
|
+
1,
|
|
27996
|
+
0,
|
|
27997
|
+
0,
|
|
27998
|
+
0,
|
|
27999
|
+
0,
|
|
28000
|
+
1,
|
|
28001
|
+
0,
|
|
28002
|
+
0,
|
|
28003
|
+
0,
|
|
28004
|
+
0,
|
|
28005
|
+
1,
|
|
28006
|
+
0,
|
|
28007
|
+
v[0],
|
|
28008
|
+
v[1],
|
|
28009
|
+
v[2],
|
|
28010
|
+
1
|
|
28011
|
+
];
|
|
28012
|
+
}
|
|
28013
|
+
function normalizeAxis(axis) {
|
|
28014
|
+
const len = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
|
|
28015
|
+
if (len < 1e-12) return DEFAULT_AXIS;
|
|
28016
|
+
return [
|
|
28017
|
+
axis[0] / len,
|
|
28018
|
+
axis[1] / len,
|
|
28019
|
+
axis[2] / len
|
|
28020
|
+
];
|
|
28021
|
+
}
|
|
28022
|
+
function cross(a, b) {
|
|
28023
|
+
return [
|
|
28024
|
+
a[1] * b[2] - a[2] * b[1],
|
|
28025
|
+
a[2] * b[0] - a[0] * b[2],
|
|
28026
|
+
a[0] * b[1] - a[1] * b[0]
|
|
28027
|
+
];
|
|
28028
|
+
}
|
|
28029
|
+
/**
|
|
28030
|
+
* Builds a frame from an authored `(origin, axisX, axisZ)` placement, using the
|
|
28031
|
+
* same IFC orthonormalization as `placementToMatrix`/`readAxis2Placement3D`:
|
|
28032
|
+
* z = normalize(axisZ); x = normalize(axisX projected perpendicular to z);
|
|
28033
|
+
* y = z x. Lets a civil node authored with explicit axis props enter the same
|
|
28034
|
+
* frame pipeline as a `tRotate` chain.
|
|
28035
|
+
*/
|
|
28036
|
+
function frameFromPlacement(f) {
|
|
28037
|
+
const z = normalizeAxis(f.axisZ);
|
|
28038
|
+
const dot = z[0] * f.axisX[0] + z[1] * f.axisX[1] + z[2] * f.axisX[2];
|
|
28039
|
+
const x = normalizeAxis([
|
|
28040
|
+
f.axisX[0] - dot * z[0],
|
|
28041
|
+
f.axisX[1] - dot * z[1],
|
|
28042
|
+
f.axisX[2] - dot * z[2]
|
|
28043
|
+
]);
|
|
28044
|
+
const y = cross(z, x);
|
|
28045
|
+
return [
|
|
28046
|
+
x[0],
|
|
28047
|
+
x[1],
|
|
28048
|
+
x[2],
|
|
28049
|
+
0,
|
|
28050
|
+
y[0],
|
|
28051
|
+
y[1],
|
|
28052
|
+
y[2],
|
|
28053
|
+
0,
|
|
28054
|
+
z[0],
|
|
28055
|
+
z[1],
|
|
28056
|
+
z[2],
|
|
28057
|
+
0,
|
|
28058
|
+
f.origin[0],
|
|
28059
|
+
f.origin[1],
|
|
28060
|
+
f.origin[2],
|
|
28061
|
+
1
|
|
28062
|
+
];
|
|
28063
|
+
}
|
|
28064
|
+
/**
|
|
28065
|
+
* Rotation by `angleDeg` about `axis` (default +Z) through pivot `at` (default
|
|
28066
|
+
* origin), matching `csg.rotate` (Rodrigues; right-handed about the axis). With
|
|
28067
|
+
* a pivot the motion is `T(at) . R . T(-at)`, i.e. `p -> at + R(p - at)`.
|
|
28068
|
+
*/
|
|
28069
|
+
function rotationFrame(angleDeg, axis = DEFAULT_AXIS, at = ORIGIN) {
|
|
28070
|
+
const [x, y, z] = normalizeAxis(axis);
|
|
28071
|
+
const t = angleDeg * Math.PI / 180;
|
|
28072
|
+
const c = Math.cos(t);
|
|
28073
|
+
const s = Math.sin(t);
|
|
28074
|
+
const C = 1 - c;
|
|
28075
|
+
const r00 = c + x * x * C;
|
|
28076
|
+
const r01 = x * y * C - z * s;
|
|
28077
|
+
const r02 = x * z * C + y * s;
|
|
28078
|
+
const r10 = y * x * C + z * s;
|
|
28079
|
+
const r11 = c + y * y * C;
|
|
28080
|
+
const r12 = y * z * C - x * s;
|
|
28081
|
+
const r20 = z * x * C - y * s;
|
|
28082
|
+
const r21 = z * y * C + x * s;
|
|
28083
|
+
const r22 = c + z * z * C;
|
|
28084
|
+
const rat0 = r00 * at[0] + r01 * at[1] + r02 * at[2];
|
|
28085
|
+
const rat1 = r10 * at[0] + r11 * at[1] + r12 * at[2];
|
|
28086
|
+
const rat2 = r20 * at[0] + r21 * at[1] + r22 * at[2];
|
|
28087
|
+
return [
|
|
28088
|
+
r00,
|
|
28089
|
+
r10,
|
|
28090
|
+
r20,
|
|
28091
|
+
0,
|
|
28092
|
+
r01,
|
|
28093
|
+
r11,
|
|
28094
|
+
r21,
|
|
28095
|
+
0,
|
|
28096
|
+
r02,
|
|
28097
|
+
r12,
|
|
28098
|
+
r22,
|
|
28099
|
+
0,
|
|
28100
|
+
at[0] - rat0,
|
|
28101
|
+
at[1] - rat1,
|
|
28102
|
+
at[2] - rat2,
|
|
28103
|
+
1
|
|
28104
|
+
];
|
|
28105
|
+
}
|
|
28106
|
+
function opFrame(op) {
|
|
28107
|
+
if (op.op === "translate") return translationFrame(op.v);
|
|
28108
|
+
return rotationFrame(op.angleDeg, op.axis ?? DEFAULT_AXIS, op.at ?? ORIGIN);
|
|
28109
|
+
}
|
|
28110
|
+
/**
|
|
28111
|
+
* Composes an authored `TransformOp` chain into one frame, matching families'
|
|
28112
|
+
* `applyOps`: `ops[0]` is innermost (applied first). For `[A, B]` the composed
|
|
28113
|
+
* motion is `B . A`, so a point transforms as `B(A(p))`.
|
|
28114
|
+
*/
|
|
28115
|
+
function frameFromOps(ops) {
|
|
28116
|
+
let m = IDENTITY_FRAME;
|
|
28117
|
+
for (const op of ops) m = frameMul(opFrame(op), m);
|
|
28118
|
+
return m;
|
|
28119
|
+
}
|
|
28120
|
+
/** Rigid inverse of `[R | t]`: `[R^T | -R^T t]`. */
|
|
28121
|
+
function frameInverse(f) {
|
|
28122
|
+
const t0 = f[12] ?? 0;
|
|
28123
|
+
const t1 = f[13] ?? 0;
|
|
28124
|
+
const t2 = f[14] ?? 0;
|
|
28125
|
+
const inv0 = -(f[0] * t0 + f[1] * t1 + f[2] * t2);
|
|
28126
|
+
const inv1 = -(f[4] * t0 + f[5] * t1 + f[6] * t2);
|
|
28127
|
+
const inv2 = -(f[8] * t0 + f[9] * t1 + f[10] * t2);
|
|
28128
|
+
return [
|
|
28129
|
+
f[0],
|
|
28130
|
+
f[4],
|
|
28131
|
+
f[8],
|
|
28132
|
+
0,
|
|
28133
|
+
f[1],
|
|
28134
|
+
f[5],
|
|
28135
|
+
f[9],
|
|
28136
|
+
0,
|
|
28137
|
+
f[2],
|
|
28138
|
+
f[6],
|
|
28139
|
+
f[10],
|
|
28140
|
+
0,
|
|
28141
|
+
inv0,
|
|
28142
|
+
inv1,
|
|
28143
|
+
inv2,
|
|
28144
|
+
1
|
|
28145
|
+
];
|
|
28146
|
+
}
|
|
28147
|
+
/** Decomposes a frame into origin (mm) + IFC axes (axisX, axisZ). */
|
|
28148
|
+
function decomposeFrame(f) {
|
|
28149
|
+
return decomposePlacement(f);
|
|
28150
|
+
}
|
|
28151
|
+
/** Translation column of a frame. */
|
|
28152
|
+
function frameOrigin(f) {
|
|
28153
|
+
return [
|
|
28154
|
+
f[12] ?? 0,
|
|
28155
|
+
f[13] ?? 0,
|
|
28156
|
+
f[14] ?? 0
|
|
28157
|
+
];
|
|
28158
|
+
}
|
|
28159
|
+
/** True when the rotation part is the identity (within tolerance): a pure
|
|
28160
|
+
* translation, so the existing translation-only placement path suffices. */
|
|
28161
|
+
function isPureTranslation(f, eps = 1e-9) {
|
|
28162
|
+
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;
|
|
28163
|
+
}
|
|
28164
|
+
//#endregion
|
|
27934
28165
|
//#region src/familiesAdapter.ts
|
|
27935
28166
|
/**
|
|
27936
28167
|
* brepjs-families -> BimModel adapter. Consumes a resolved element tree and
|
|
@@ -28152,6 +28383,36 @@ function centreOrigin(base, shift) {
|
|
|
28152
28383
|
origin[2] + shift[2]
|
|
28153
28384
|
];
|
|
28154
28385
|
}
|
|
28386
|
+
function synthesizedProfile(el) {
|
|
28387
|
+
if (el.semantics?.kind !== "product" || el.props["profile"] !== void 0) return void 0;
|
|
28388
|
+
const length = semanticDimension(el, "length");
|
|
28389
|
+
const width = semanticDimension(el, "width");
|
|
28390
|
+
const height = semanticDimension(el, "height");
|
|
28391
|
+
if (el.semantics.category === "beam" && width !== void 0 && height !== void 0) return {
|
|
28392
|
+
profile: {
|
|
28393
|
+
kind: "RECTANGULAR",
|
|
28394
|
+
width,
|
|
28395
|
+
height
|
|
28396
|
+
},
|
|
28397
|
+
shift: [
|
|
28398
|
+
0,
|
|
28399
|
+
width / 2,
|
|
28400
|
+
height / 2
|
|
28401
|
+
]
|
|
28402
|
+
};
|
|
28403
|
+
if (el.semantics.category === "column" && length !== void 0 && width !== void 0) return {
|
|
28404
|
+
profile: {
|
|
28405
|
+
kind: "RECTANGULAR",
|
|
28406
|
+
width: length,
|
|
28407
|
+
height: width
|
|
28408
|
+
},
|
|
28409
|
+
shift: [
|
|
28410
|
+
length / 2,
|
|
28411
|
+
width / 2,
|
|
28412
|
+
0
|
|
28413
|
+
]
|
|
28414
|
+
};
|
|
28415
|
+
}
|
|
28155
28416
|
/**
|
|
28156
28417
|
* Adapts target-independent civil envelope dimensions onto existing typed BIM
|
|
28157
28418
|
* spec inputs. Reference infrastructure Families intentionally author domain
|
|
@@ -28166,18 +28427,7 @@ function civilProductSpecInput(el) {
|
|
|
28166
28427
|
const height = semanticDimension(el, "height");
|
|
28167
28428
|
switch (el.semantics.category) {
|
|
28168
28429
|
case "beam": {
|
|
28169
|
-
const synthesized =
|
|
28170
|
-
profile: {
|
|
28171
|
-
kind: "RECTANGULAR",
|
|
28172
|
-
width,
|
|
28173
|
-
height
|
|
28174
|
-
},
|
|
28175
|
-
shift: [
|
|
28176
|
-
0,
|
|
28177
|
-
width / 2,
|
|
28178
|
-
height / 2
|
|
28179
|
-
]
|
|
28180
|
-
} : void 0;
|
|
28430
|
+
const synthesized = synthesizedProfile(el);
|
|
28181
28431
|
return {
|
|
28182
28432
|
...base,
|
|
28183
28433
|
...synthesized !== void 0 ? {
|
|
@@ -28189,18 +28439,7 @@ function civilProductSpecInput(el) {
|
|
|
28189
28439
|
};
|
|
28190
28440
|
}
|
|
28191
28441
|
case "column": {
|
|
28192
|
-
const synthesized =
|
|
28193
|
-
profile: {
|
|
28194
|
-
kind: "RECTANGULAR",
|
|
28195
|
-
width: length,
|
|
28196
|
-
height: width
|
|
28197
|
-
},
|
|
28198
|
-
shift: [
|
|
28199
|
-
length / 2,
|
|
28200
|
-
width / 2,
|
|
28201
|
-
0
|
|
28202
|
-
]
|
|
28203
|
-
} : void 0;
|
|
28442
|
+
const synthesized = synthesizedProfile(el);
|
|
28204
28443
|
return {
|
|
28205
28444
|
...base,
|
|
28206
28445
|
...synthesized !== void 0 ? {
|
|
@@ -28267,12 +28506,12 @@ function peelTranslates(node) {
|
|
|
28267
28506
|
moved
|
|
28268
28507
|
};
|
|
28269
28508
|
}
|
|
28270
|
-
/** True when the element's rendered local transform carries a rotation
|
|
28271
|
-
*
|
|
28272
|
-
*
|
|
28273
|
-
*
|
|
28274
|
-
*
|
|
28275
|
-
*
|
|
28509
|
+
/** True when the element's rendered local transform carries a rotation, which
|
|
28510
|
+
* switches the walk to the rigid-frame placement path (the composed rotation
|
|
28511
|
+
* folds into origin + axisX + axisZ). Only authored `transform` rotations
|
|
28512
|
+
* count: a rotation a family render bakes into its own body geometry (e.g. a
|
|
28513
|
+
* circular beam oriented along axisX) stays in the body, which the spec
|
|
28514
|
+
* rebuilds parametrically from props. */
|
|
28276
28515
|
function hasRotateOp(el) {
|
|
28277
28516
|
return el.localTransforms.some((op) => op.op === "rotate");
|
|
28278
28517
|
}
|
|
@@ -28387,7 +28626,7 @@ function materializeOwnedSolid(el, evaluator, errors) {
|
|
|
28387
28626
|
/** Materialize an unrouted element's IR and add it as a proxy. The body is
|
|
28388
28627
|
* authoritative for a proxy (no parametric spec to diverge from), so baked
|
|
28389
28628
|
* transforms — rotations included — are fine here. */
|
|
28390
|
-
function addProxyElement(model, el, evaluator,
|
|
28629
|
+
function addProxyElement(model, el, evaluator, spatialFrame) {
|
|
28391
28630
|
const body = materializeOwnedSolid(el, evaluator, {
|
|
28392
28631
|
evalCode: "FAMILIES_PROXY_EVAL_FAILED",
|
|
28393
28632
|
notSolidCode: "FAMILIES_PROXY_NOT_SOLID",
|
|
@@ -28395,7 +28634,7 @@ function addProxyElement(model, el, evaluator, projectedSpatialTranslation) {
|
|
|
28395
28634
|
routeName: "proxy"
|
|
28396
28635
|
});
|
|
28397
28636
|
if (!body.ok) return body;
|
|
28398
|
-
const localized =
|
|
28637
|
+
const localized = localizeBodyToFrame(el, body.value, spatialFrame, "FAMILIES_PROXY_LOCALIZE_FAILED", "spatial parent");
|
|
28399
28638
|
if (!localized.ok) return localized;
|
|
28400
28639
|
const nameAttr = el.attributes["name"];
|
|
28401
28640
|
const materialProp = el.props["materialName"];
|
|
@@ -28558,8 +28797,9 @@ var DEFAULT_AXIS_Z = [
|
|
|
28558
28797
|
function axisEquals(value, axis) {
|
|
28559
28798
|
return Array.isArray(value) && value.length === 3 && value.every((component, i) => typeof component === "number" && component === axis[i]);
|
|
28560
28799
|
}
|
|
28561
|
-
/**
|
|
28562
|
-
*
|
|
28800
|
+
/** True when a civil node authors non-default `axisX`/`axisZ` props: like a
|
|
28801
|
+
* tRotate, this puts the node (and its subtree) on the rigid-frame placement
|
|
28802
|
+
* path so descendants relativize against the rotated parent frame. */
|
|
28563
28803
|
function hasRotatedAxes(el) {
|
|
28564
28804
|
const axisX = el.props["axisX"];
|
|
28565
28805
|
const axisZ = el.props["axisZ"];
|
|
@@ -28598,6 +28838,119 @@ function authoredTranslation(el) {
|
|
|
28598
28838
|
}
|
|
28599
28839
|
return total;
|
|
28600
28840
|
}
|
|
28841
|
+
function authoredAxisX(el) {
|
|
28842
|
+
const v = el.props["axisX"];
|
|
28843
|
+
return Array.isArray(v) && v.length === 3 ? v : DEFAULT_AXIS_X;
|
|
28844
|
+
}
|
|
28845
|
+
function authoredAxisZ(el) {
|
|
28846
|
+
const v = el.props["axisZ"];
|
|
28847
|
+
return Array.isArray(v) && v.length === 3 ? v : DEFAULT_AXIS_Z;
|
|
28848
|
+
}
|
|
28849
|
+
/** The placement shift that centres a synthesized beam/column cross-section on
|
|
28850
|
+
* its axis, in the element's own (body) frame. Zero for every other route. */
|
|
28851
|
+
function specLocalShift(el) {
|
|
28852
|
+
return synthesizedProfile(el)?.shift ?? [
|
|
28853
|
+
0,
|
|
28854
|
+
0,
|
|
28855
|
+
0
|
|
28856
|
+
];
|
|
28857
|
+
}
|
|
28858
|
+
/** World frame of an element's own body: the walk's cumulative frame (all
|
|
28859
|
+
* 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) {
|
|
28863
|
+
return frameMul(frameMul(cumulativeFrame, frameFromPlacement({
|
|
28864
|
+
origin: authoredSpecOrigin(el),
|
|
28865
|
+
axisX: authoredAxisX(el),
|
|
28866
|
+
axisZ: authoredAxisZ(el)
|
|
28867
|
+
})), translationFrame(specLocalShift(el)));
|
|
28868
|
+
}
|
|
28869
|
+
/** World frame of a civil spatial node: cumulative transforms composed with any
|
|
28870
|
+
* authored `origin`/`axisX`/`axisZ` props (identity when default). */
|
|
28871
|
+
function civilNodeFrame(el, cumulativeFrame) {
|
|
28872
|
+
return frameMul(cumulativeFrame, frameFromPlacement({
|
|
28873
|
+
origin: authoredSpecOrigin(el),
|
|
28874
|
+
axisX: authoredAxisX(el),
|
|
28875
|
+
axisZ: authoredAxisZ(el)
|
|
28876
|
+
}));
|
|
28877
|
+
}
|
|
28878
|
+
/** Places a routed element's spec input through the composed placement frame:
|
|
28879
|
+
* the flat spec input keeps its dimensions/profile/psets, but `origin`/`axisX`/
|
|
28880
|
+
* `axisZ` are recomputed from the element's world frame relative to its spatial
|
|
28881
|
+
* container. Stair/ramp flights, which carry their own per-flight frame, are
|
|
28882
|
+
* each re-placed the same way. */
|
|
28883
|
+
function rotatedRoutedInput(flatInput, el, cumulativeFrame, spatialFrame) {
|
|
28884
|
+
const toSpatial = frameInverse(spatialFrame);
|
|
28885
|
+
if (Array.isArray(flatInput["flights"])) {
|
|
28886
|
+
const elementFrame = elementBodyFrame(el, cumulativeFrame);
|
|
28887
|
+
const flights = flatInput["flights"];
|
|
28888
|
+
return {
|
|
28889
|
+
...flatInput,
|
|
28890
|
+
origin: [
|
|
28891
|
+
0,
|
|
28892
|
+
0,
|
|
28893
|
+
0
|
|
28894
|
+
],
|
|
28895
|
+
axisX: [
|
|
28896
|
+
1,
|
|
28897
|
+
0,
|
|
28898
|
+
0
|
|
28899
|
+
],
|
|
28900
|
+
axisZ: [
|
|
28901
|
+
0,
|
|
28902
|
+
0,
|
|
28903
|
+
1
|
|
28904
|
+
],
|
|
28905
|
+
flights: flights.map((flight) => {
|
|
28906
|
+
if (typeof flight !== "object" || flight === null) return flight;
|
|
28907
|
+
const f = flight;
|
|
28908
|
+
const world = frameMul(elementFrame, frameFromPlacement({
|
|
28909
|
+
origin: f["origin"] ?? [
|
|
28910
|
+
0,
|
|
28911
|
+
0,
|
|
28912
|
+
0
|
|
28913
|
+
],
|
|
28914
|
+
axisX: f["axisX"] ?? DEFAULT_AXIS_X,
|
|
28915
|
+
axisZ: f["axisZ"] ?? DEFAULT_AXIS_Z
|
|
28916
|
+
}));
|
|
28917
|
+
const placed = decomposeFrame(frameMul(toSpatial, world));
|
|
28918
|
+
return {
|
|
28919
|
+
...f,
|
|
28920
|
+
origin: placed.origin,
|
|
28921
|
+
axisX: placed.axisX,
|
|
28922
|
+
axisZ: placed.axisZ
|
|
28923
|
+
};
|
|
28924
|
+
})
|
|
28925
|
+
};
|
|
28926
|
+
}
|
|
28927
|
+
const placed = decomposeFrame(frameMul(toSpatial, elementBodyFrame(el, cumulativeFrame)));
|
|
28928
|
+
return {
|
|
28929
|
+
...flatInput,
|
|
28930
|
+
origin: placed.origin,
|
|
28931
|
+
axisX: placed.axisX,
|
|
28932
|
+
axisZ: placed.axisZ
|
|
28933
|
+
};
|
|
28934
|
+
}
|
|
28935
|
+
/** Moves an owned world-baked body into its spatial container's local frame.
|
|
28936
|
+
* A pure translation keeps the fast `translate` path (identical to the prior
|
|
28937
|
+
* behaviour); a rotated container applies the inverse frame via `applyMatrix`. */
|
|
28938
|
+
function localizeBodyToFrame(el, body, spatialFrame, errorCode, frameName) {
|
|
28939
|
+
if (isPureTranslation(spatialFrame)) return localizeOwnedSolid(el, body, frameOrigin(spatialFrame), errorCode, frameName);
|
|
28940
|
+
const inverse = decomposeFrame(frameInverse(spatialFrame));
|
|
28941
|
+
try {
|
|
28942
|
+
const localized = (0, brepjs.applyMatrix)(body, placementToMatrix(inverse));
|
|
28943
|
+
if (!localized.ok) {
|
|
28944
|
+
body[Symbol.dispose]();
|
|
28945
|
+
return (0, brepjs.err)(specError(errorCode, `familiesToBim: '${el.keyPath}' could not move its body into the ${frameName} frame`, localized.error));
|
|
28946
|
+
}
|
|
28947
|
+
body[Symbol.dispose]();
|
|
28948
|
+
return (0, brepjs.ok)(localized.value);
|
|
28949
|
+
} catch (cause) {
|
|
28950
|
+
body[Symbol.dispose]();
|
|
28951
|
+
return (0, brepjs.err)(specError(errorCode, `familiesToBim: '${el.keyPath}' could not move its body into the ${frameName} frame`, cause));
|
|
28952
|
+
}
|
|
28953
|
+
}
|
|
28601
28954
|
function civilSpatialInput(el, localTranslation) {
|
|
28602
28955
|
const semantics = el.semantics;
|
|
28603
28956
|
const composition = semantics !== void 0 && "composition" in semantics ? CIVIL_COMPOSITION[semantics.composition] : void 0;
|
|
@@ -28610,22 +28963,36 @@ function civilSpatialInput(el, localTranslation) {
|
|
|
28610
28963
|
...composition !== void 0 ? { compositionType: composition } : {}
|
|
28611
28964
|
};
|
|
28612
28965
|
}
|
|
28966
|
+
/** Relativizes a civil node's world frame against its parent civil frame into
|
|
28967
|
+
* the origin/axisX/axisZ the spatial specs consume. */
|
|
28968
|
+
function civilSpatialFrameInput(el, nodeFrame, parentFrame) {
|
|
28969
|
+
const semantics = el.semantics;
|
|
28970
|
+
const composition = semantics !== void 0 && "composition" in semantics ? CIVIL_COMPOSITION[semantics.composition] : void 0;
|
|
28971
|
+
const local = decomposeFrame(frameMul(frameInverse(parentFrame), nodeFrame));
|
|
28972
|
+
return {
|
|
28973
|
+
name: semanticName(el),
|
|
28974
|
+
origin: local.origin,
|
|
28975
|
+
axisX: local.axisX,
|
|
28976
|
+
axisZ: local.axisZ,
|
|
28977
|
+
...composition !== void 0 ? { compositionType: composition } : {}
|
|
28978
|
+
};
|
|
28979
|
+
}
|
|
28613
28980
|
function civilParentAccepts(kind, parent) {
|
|
28614
28981
|
if (kind === "site") return parent === "project";
|
|
28615
28982
|
if (kind === "bridge") return parent === "site";
|
|
28616
28983
|
return parent === "bridge" || parent === "bridge-part";
|
|
28617
28984
|
}
|
|
28618
|
-
function addCivilSpatialOccurrence(model, el, kind,
|
|
28985
|
+
function addCivilSpatialOccurrence(model, el, kind, input) {
|
|
28619
28986
|
if (kind === "site") {
|
|
28620
28987
|
if (el.semantics?.role !== "transport-site") return unsupportedCivilRole(el, "Site");
|
|
28621
|
-
const parsed = parseSiteSpec(
|
|
28988
|
+
const parsed = parseSiteSpec(input);
|
|
28622
28989
|
return parsed.ok ? model.addSite(parsed.value, { stableKey: el.keyPath }) : parsed;
|
|
28623
28990
|
}
|
|
28624
28991
|
if (kind === "bridge") {
|
|
28625
28992
|
const predefinedType = lookup(BRIDGE_ROLE, el.semantics?.role ?? "");
|
|
28626
28993
|
if (predefinedType === void 0) return unsupportedCivilRole(el, "Bridge");
|
|
28627
28994
|
const parsed = parseBridgeSpec({
|
|
28628
|
-
...
|
|
28995
|
+
...input,
|
|
28629
28996
|
predefinedType
|
|
28630
28997
|
});
|
|
28631
28998
|
return parsed.ok ? model.addBridge(parsed.value, { stableKey: el.keyPath }) : parsed;
|
|
@@ -28634,7 +29001,7 @@ function addCivilSpatialOccurrence(model, el, kind, localTranslation) {
|
|
|
28634
29001
|
const predefinedType = lookup(BRIDGE_PART_ROLE, el.semantics?.role ?? "");
|
|
28635
29002
|
if (predefinedType === void 0) return unsupportedCivilRole(el, "Bridge Part");
|
|
28636
29003
|
const parsed = parseBridgePartSpec({
|
|
28637
|
-
...
|
|
29004
|
+
...input,
|
|
28638
29005
|
predefinedType,
|
|
28639
29006
|
usageType: subdivision !== void 0 ? CIVIL_USAGE[subdivision] : "NOTDEFINED"
|
|
28640
29007
|
});
|
|
@@ -28661,7 +29028,7 @@ function relativeSpecInput(input, projectedSpatialTranslation) {
|
|
|
28661
29028
|
origin: relativeOrigin(input["origin"])
|
|
28662
29029
|
};
|
|
28663
29030
|
}
|
|
28664
|
-
function addEarthworksFillElement(model, el, evaluator,
|
|
29031
|
+
function addEarthworksFillElement(model, el, evaluator, spatialFrame) {
|
|
28665
29032
|
if (el.semantics?.kind !== "product") return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: '${el.keyPath}' is not authored as a civil Product`));
|
|
28666
29033
|
const predefinedType = lookup(EARTHWORKS_FILL_ROLE, el.semantics.role);
|
|
28667
29034
|
if (predefinedType === void 0) return unsupportedCivilRole(el, "Earthworks Fill");
|
|
@@ -28672,7 +29039,7 @@ function addEarthworksFillElement(model, el, evaluator, projectedSpatialTranslat
|
|
|
28672
29039
|
routeName: "Earthworks Fill"
|
|
28673
29040
|
});
|
|
28674
29041
|
if (!body.ok) return body;
|
|
28675
|
-
const localized =
|
|
29042
|
+
const localized = localizeBodyToFrame(el, body.value, spatialFrame, "FAMILIES_EARTHWORKS_LOCALIZE_FAILED", "Bridge Part");
|
|
28676
29043
|
if (!localized.ok) return localized;
|
|
28677
29044
|
const specProps = collectSpecProps(el);
|
|
28678
29045
|
const authoredMaterial = el.props["materialName"];
|
|
@@ -28722,10 +29089,14 @@ function familiesToBim(root, options) {
|
|
|
28722
29089
|
const walk = (el, state) => {
|
|
28723
29090
|
const rotatedHere = state.rotated || hasRotateOp(el);
|
|
28724
29091
|
const cumulativeTranslationHere = addTranslation(state.cumulativeTranslation, authoredTranslation(el));
|
|
29092
|
+
const cumulativeFrameHere = frameMul(state.cumulativeFrame, frameFromOps(el.localTransforms));
|
|
28725
29093
|
let proxiedHere = false;
|
|
29094
|
+
let nextRotated = rotatedHere;
|
|
28726
29095
|
let nextSpatialStructureId = state.spatialStructureId;
|
|
28727
29096
|
let nextCivilParent = state.civilParent;
|
|
28728
29097
|
let nextProjectedSpatialTranslation = state.projectedSpatialTranslation;
|
|
29098
|
+
let nextCumulativeFrame = cumulativeFrameHere;
|
|
29099
|
+
let nextSpatialFrame = state.spatialFrame;
|
|
28729
29100
|
const archetype = archetypeFor(el);
|
|
28730
29101
|
const effectiveArchetype = el.semantics?.kind === "product" ? civilProductArchetype(el) : archetype;
|
|
28731
29102
|
const route = specRoute(effectiveArchetype);
|
|
@@ -28734,15 +29105,19 @@ function familiesToBim(root, options) {
|
|
|
28734
29105
|
if (!usesAuthoredCivilHierarchy || !civilParentAccepts(civilKind, state.civilParent) || state.spatialStructureId === null) return (0, brepjs.err)(specError("FAMILIES_INVALID_CIVIL_HIERARCHY", `familiesToBim: civil '${civilKind}' at '${el.keyPath}' cannot occur under '${state.civilParent}'`));
|
|
28735
29106
|
const keyed = requireKeyed(el);
|
|
28736
29107
|
if (!keyed.ok) return keyed;
|
|
28737
|
-
if (rotatedHere || hasRotatedAxes(el)) return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_TRANSFORM", `familiesToBim: civil spatial element '${el.keyPath}' carries a rotated frame — descendants are relativized by translation only, so bake the orientation into child geometry`));
|
|
28738
29108
|
if (el.geometry.kind !== "Empty") return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: civil spatial element '${el.keyPath}' carries its own geometry — Site/Bridge/Bridge Part export no body, so author it as a child Product (e.g. Earthworks Fill)`));
|
|
28739
|
-
const
|
|
28740
|
-
const
|
|
29109
|
+
const nodeFrame = civilNodeFrame(el, cumulativeFrameHere);
|
|
29110
|
+
const rotatedFrame = rotatedHere || hasRotatedAxes(el);
|
|
29111
|
+
const input = rotatedFrame ? civilSpatialFrameInput(el, nodeFrame, state.spatialFrame) : civilSpatialInput(el, subtractTranslation(cumulativeTranslationHere, state.projectedSpatialTranslation));
|
|
29112
|
+
const added = addCivilSpatialOccurrence(model, el, civilKind, input);
|
|
28741
29113
|
if (!added.ok) return added;
|
|
28742
29114
|
model.aggregate(state.spatialStructureId, added.value);
|
|
28743
29115
|
idByKeyPath.set(el.keyPath, added.value);
|
|
28744
29116
|
nextSpatialStructureId = added.value;
|
|
28745
29117
|
nextCivilParent = civilKind;
|
|
29118
|
+
nextRotated = rotatedFrame;
|
|
29119
|
+
nextCumulativeFrame = nodeFrame;
|
|
29120
|
+
nextSpatialFrame = nodeFrame;
|
|
28746
29121
|
nextProjectedSpatialTranslation = addTranslation(cumulativeTranslationHere, authoredSpecOrigin(el));
|
|
28747
29122
|
} else if (isCivilSpatialIntent(el)) return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: unsupported civil '${el.semantics?.kind ?? "unknown"}' category '${el.semantics?.category ?? ""}' at '${el.keyPath}'`));
|
|
28748
29123
|
else if (archetype === "storey") {
|
|
@@ -28763,21 +29138,22 @@ function familiesToBim(root, options) {
|
|
|
28763
29138
|
if (bodyEvaluator === void 0) return (0, brepjs.err)(specError("FAMILIES_EARTHWORKS_EVALUATOR_REQUIRED", `familiesToBim: Earthworks Fill '${el.keyPath}' needs bodyEvaluator to materialize its exact Product Body`));
|
|
28764
29139
|
const keyed = requireKeyed(el);
|
|
28765
29140
|
if (!keyed.ok) return keyed;
|
|
28766
|
-
const added = addEarthworksFillElement(model, el, bodyEvaluator, state.
|
|
29141
|
+
const added = addEarthworksFillElement(model, el, bodyEvaluator, state.spatialFrame);
|
|
28767
29142
|
if (!added.ok) return added;
|
|
28768
29143
|
model.placeIn(added.value, nextSpatialStructureId);
|
|
28769
29144
|
idByKeyPath.set(el.keyPath, added.value);
|
|
28770
29145
|
} else if (route !== void 0) {
|
|
28771
29146
|
const keyed = requireKeyed(el);
|
|
28772
29147
|
if (!keyed.ok) return keyed;
|
|
28773
|
-
if (rotatedHere) return (0, brepjs.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`));
|
|
28774
29148
|
const voids = el.props["voids"];
|
|
28775
29149
|
if (Array.isArray(voids)) {
|
|
28776
29150
|
const openings = el.children.filter((c) => c.type === "Opening").length;
|
|
28777
29151
|
if (voids.length > openings) return (0, brepjs.err)(specError("FAMILIES_ANONYMOUS_VOID", `familiesToBim: '${el.keyPath}' has ${voids.length - openings} anonymous void(s) the IFC body cannot carry — use a fill-role family (Door/Window) for each void`));
|
|
28778
29152
|
}
|
|
28779
29153
|
const routedInput = el.semantics?.kind === "product" ? civilProductSpecInput(el) : ("input" in route ? route.input : specInput)(el);
|
|
28780
|
-
const
|
|
29154
|
+
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;
|
|
29156
|
+
const parsed = route.parse(placedInput);
|
|
28781
29157
|
if (!parsed.ok) return parsed;
|
|
28782
29158
|
const added = route.add(model, parsed.value, el.keyPath);
|
|
28783
29159
|
if (!added.ok) return added;
|
|
@@ -28794,7 +29170,7 @@ function familiesToBim(root, options) {
|
|
|
28794
29170
|
const keyed = requireKeyed(el);
|
|
28795
29171
|
if (!keyed.ok) return keyed;
|
|
28796
29172
|
if (nextSpatialStructureId === null || usesAuthoredCivilHierarchy && state.civilParent !== "bridge-part") return (0, brepjs.err)(specError(usesAuthoredCivilHierarchy ? "FAMILIES_INVALID_CIVIL_HIERARCHY" : "FAMILIES_NO_STOREY", usesAuthoredCivilHierarchy ? `familiesToBim: physical product '${el.keyPath}' needs a Bridge Part ancestor` : `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment; a container family needs archetype: 'storey' to be recognised under any name`));
|
|
28797
|
-
const added = addProxyElement(model, el, options.proxyEvaluator, state.
|
|
29173
|
+
const added = addProxyElement(model, el, options.proxyEvaluator, state.spatialFrame);
|
|
28798
29174
|
if (!added.ok) return added;
|
|
28799
29175
|
model.placeIn(added.value, nextSpatialStructureId);
|
|
28800
29176
|
idByKeyPath.set(el.keyPath, added.value);
|
|
@@ -28810,9 +29186,11 @@ function familiesToBim(root, options) {
|
|
|
28810
29186
|
const r = walk(child, {
|
|
28811
29187
|
spatialStructureId: nextSpatialStructureId,
|
|
28812
29188
|
civilParent: nextCivilParent,
|
|
28813
|
-
rotated:
|
|
29189
|
+
rotated: nextRotated,
|
|
28814
29190
|
cumulativeTranslation: cumulativeTranslationHere,
|
|
28815
|
-
projectedSpatialTranslation: nextProjectedSpatialTranslation
|
|
29191
|
+
projectedSpatialTranslation: nextProjectedSpatialTranslation,
|
|
29192
|
+
cumulativeFrame: nextCumulativeFrame,
|
|
29193
|
+
spatialFrame: nextSpatialFrame
|
|
28816
29194
|
});
|
|
28817
29195
|
if (!r.ok) return r;
|
|
28818
29196
|
}
|
|
@@ -28823,7 +29201,9 @@ function familiesToBim(root, options) {
|
|
|
28823
29201
|
civilParent: "project",
|
|
28824
29202
|
rotated: false,
|
|
28825
29203
|
cumulativeTranslation: ZERO_TRANSLATION,
|
|
28826
|
-
projectedSpatialTranslation: ZERO_TRANSLATION
|
|
29204
|
+
projectedSpatialTranslation: ZERO_TRANSLATION,
|
|
29205
|
+
cumulativeFrame: IDENTITY_FRAME,
|
|
29206
|
+
spatialFrame: IDENTITY_FRAME
|
|
28827
29207
|
});
|
|
28828
29208
|
if (!walked.ok) {
|
|
28829
29209
|
model[Symbol.dispose]();
|