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.js
CHANGED
|
@@ -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
|
|
@@ -14476,17 +14496,17 @@ function readAxis2Placement3D(reader, ref, scale) {
|
|
|
14476
14496
|
refDir[2] - dotXZ * zN[2]
|
|
14477
14497
|
];
|
|
14478
14498
|
const xRawLenSq = xRaw[0] * xRaw[0] + xRaw[1] * xRaw[1] + xRaw[2] * xRaw[2];
|
|
14479
|
-
const orthoZ = Math.abs(zN[0]) < .9 ? cross(zN, [
|
|
14499
|
+
const orthoZ = Math.abs(zN[0]) < .9 ? cross$1(zN, [
|
|
14480
14500
|
1,
|
|
14481
14501
|
0,
|
|
14482
14502
|
0
|
|
14483
|
-
]) : cross(zN, [
|
|
14503
|
+
]) : cross$1(zN, [
|
|
14484
14504
|
0,
|
|
14485
14505
|
1,
|
|
14486
14506
|
0
|
|
14487
14507
|
]);
|
|
14488
14508
|
const xN = xRawLenSq < 1e-12 ? normalize(orthoZ) : normalize(xRaw);
|
|
14489
|
-
const yN = cross(zN, xN);
|
|
14509
|
+
const yN = cross$1(zN, xN);
|
|
14490
14510
|
return {
|
|
14491
14511
|
linear: [
|
|
14492
14512
|
xN[0],
|
|
@@ -14646,7 +14666,7 @@ function normalize(v) {
|
|
|
14646
14666
|
v[2] / len
|
|
14647
14667
|
];
|
|
14648
14668
|
}
|
|
14649
|
-
function cross(a, b) {
|
|
14669
|
+
function cross$1(a, b) {
|
|
14650
14670
|
return [
|
|
14651
14671
|
a[1] * b[2] - a[2] * b[1],
|
|
14652
14672
|
a[2] * b[0] - a[0] * b[2],
|
|
@@ -27908,6 +27928,217 @@ function assignNum(target, key, value) {
|
|
|
27908
27928
|
if (value !== void 0) target[key] = Number(value);
|
|
27909
27929
|
}
|
|
27910
27930
|
//#endregion
|
|
27931
|
+
//#region src/placementFrame.ts
|
|
27932
|
+
var IDENTITY_FRAME = [
|
|
27933
|
+
1,
|
|
27934
|
+
0,
|
|
27935
|
+
0,
|
|
27936
|
+
0,
|
|
27937
|
+
0,
|
|
27938
|
+
1,
|
|
27939
|
+
0,
|
|
27940
|
+
0,
|
|
27941
|
+
0,
|
|
27942
|
+
0,
|
|
27943
|
+
1,
|
|
27944
|
+
0,
|
|
27945
|
+
0,
|
|
27946
|
+
0,
|
|
27947
|
+
0,
|
|
27948
|
+
1
|
|
27949
|
+
];
|
|
27950
|
+
var DEFAULT_AXIS = [
|
|
27951
|
+
0,
|
|
27952
|
+
0,
|
|
27953
|
+
1
|
|
27954
|
+
];
|
|
27955
|
+
var ORIGIN = [
|
|
27956
|
+
0,
|
|
27957
|
+
0,
|
|
27958
|
+
0
|
|
27959
|
+
];
|
|
27960
|
+
/** Column-major 4x4 multiply: `a . b` (apply b first, then a). */
|
|
27961
|
+
function frameMul(a, b) {
|
|
27962
|
+
const out = new Array(16).fill(0);
|
|
27963
|
+
for (let col = 0; col < 4; col++) for (let row = 0; row < 4; row++) {
|
|
27964
|
+
let sum = 0;
|
|
27965
|
+
for (let k = 0; k < 4; k++) sum += (a[k * 4 + row] ?? 0) * (b[col * 4 + k] ?? 0);
|
|
27966
|
+
out[col * 4 + row] = sum;
|
|
27967
|
+
}
|
|
27968
|
+
return out;
|
|
27969
|
+
}
|
|
27970
|
+
function translationFrame(v) {
|
|
27971
|
+
return [
|
|
27972
|
+
1,
|
|
27973
|
+
0,
|
|
27974
|
+
0,
|
|
27975
|
+
0,
|
|
27976
|
+
0,
|
|
27977
|
+
1,
|
|
27978
|
+
0,
|
|
27979
|
+
0,
|
|
27980
|
+
0,
|
|
27981
|
+
0,
|
|
27982
|
+
1,
|
|
27983
|
+
0,
|
|
27984
|
+
v[0],
|
|
27985
|
+
v[1],
|
|
27986
|
+
v[2],
|
|
27987
|
+
1
|
|
27988
|
+
];
|
|
27989
|
+
}
|
|
27990
|
+
function normalizeAxis(axis) {
|
|
27991
|
+
const len = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
|
|
27992
|
+
if (len < 1e-12) return DEFAULT_AXIS;
|
|
27993
|
+
return [
|
|
27994
|
+
axis[0] / len,
|
|
27995
|
+
axis[1] / len,
|
|
27996
|
+
axis[2] / len
|
|
27997
|
+
];
|
|
27998
|
+
}
|
|
27999
|
+
function cross(a, b) {
|
|
28000
|
+
return [
|
|
28001
|
+
a[1] * b[2] - a[2] * b[1],
|
|
28002
|
+
a[2] * b[0] - a[0] * b[2],
|
|
28003
|
+
a[0] * b[1] - a[1] * b[0]
|
|
28004
|
+
];
|
|
28005
|
+
}
|
|
28006
|
+
/**
|
|
28007
|
+
* Builds a frame from an authored `(origin, axisX, axisZ)` placement, using the
|
|
28008
|
+
* same IFC orthonormalization as `placementToMatrix`/`readAxis2Placement3D`:
|
|
28009
|
+
* z = normalize(axisZ); x = normalize(axisX projected perpendicular to z);
|
|
28010
|
+
* y = z x. Lets a civil node authored with explicit axis props enter the same
|
|
28011
|
+
* frame pipeline as a `tRotate` chain.
|
|
28012
|
+
*/
|
|
28013
|
+
function frameFromPlacement(f) {
|
|
28014
|
+
const z = normalizeAxis(f.axisZ);
|
|
28015
|
+
const dot = z[0] * f.axisX[0] + z[1] * f.axisX[1] + z[2] * f.axisX[2];
|
|
28016
|
+
const x = normalizeAxis([
|
|
28017
|
+
f.axisX[0] - dot * z[0],
|
|
28018
|
+
f.axisX[1] - dot * z[1],
|
|
28019
|
+
f.axisX[2] - dot * z[2]
|
|
28020
|
+
]);
|
|
28021
|
+
const y = cross(z, x);
|
|
28022
|
+
return [
|
|
28023
|
+
x[0],
|
|
28024
|
+
x[1],
|
|
28025
|
+
x[2],
|
|
28026
|
+
0,
|
|
28027
|
+
y[0],
|
|
28028
|
+
y[1],
|
|
28029
|
+
y[2],
|
|
28030
|
+
0,
|
|
28031
|
+
z[0],
|
|
28032
|
+
z[1],
|
|
28033
|
+
z[2],
|
|
28034
|
+
0,
|
|
28035
|
+
f.origin[0],
|
|
28036
|
+
f.origin[1],
|
|
28037
|
+
f.origin[2],
|
|
28038
|
+
1
|
|
28039
|
+
];
|
|
28040
|
+
}
|
|
28041
|
+
/**
|
|
28042
|
+
* Rotation by `angleDeg` about `axis` (default +Z) through pivot `at` (default
|
|
28043
|
+
* origin), matching `csg.rotate` (Rodrigues; right-handed about the axis). With
|
|
28044
|
+
* a pivot the motion is `T(at) . R . T(-at)`, i.e. `p -> at + R(p - at)`.
|
|
28045
|
+
*/
|
|
28046
|
+
function rotationFrame(angleDeg, axis = DEFAULT_AXIS, at = ORIGIN) {
|
|
28047
|
+
const [x, y, z] = normalizeAxis(axis);
|
|
28048
|
+
const t = angleDeg * Math.PI / 180;
|
|
28049
|
+
const c = Math.cos(t);
|
|
28050
|
+
const s = Math.sin(t);
|
|
28051
|
+
const C = 1 - c;
|
|
28052
|
+
const r00 = c + x * x * C;
|
|
28053
|
+
const r01 = x * y * C - z * s;
|
|
28054
|
+
const r02 = x * z * C + y * s;
|
|
28055
|
+
const r10 = y * x * C + z * s;
|
|
28056
|
+
const r11 = c + y * y * C;
|
|
28057
|
+
const r12 = y * z * C - x * s;
|
|
28058
|
+
const r20 = z * x * C - y * s;
|
|
28059
|
+
const r21 = z * y * C + x * s;
|
|
28060
|
+
const r22 = c + z * z * C;
|
|
28061
|
+
const rat0 = r00 * at[0] + r01 * at[1] + r02 * at[2];
|
|
28062
|
+
const rat1 = r10 * at[0] + r11 * at[1] + r12 * at[2];
|
|
28063
|
+
const rat2 = r20 * at[0] + r21 * at[1] + r22 * at[2];
|
|
28064
|
+
return [
|
|
28065
|
+
r00,
|
|
28066
|
+
r10,
|
|
28067
|
+
r20,
|
|
28068
|
+
0,
|
|
28069
|
+
r01,
|
|
28070
|
+
r11,
|
|
28071
|
+
r21,
|
|
28072
|
+
0,
|
|
28073
|
+
r02,
|
|
28074
|
+
r12,
|
|
28075
|
+
r22,
|
|
28076
|
+
0,
|
|
28077
|
+
at[0] - rat0,
|
|
28078
|
+
at[1] - rat1,
|
|
28079
|
+
at[2] - rat2,
|
|
28080
|
+
1
|
|
28081
|
+
];
|
|
28082
|
+
}
|
|
28083
|
+
function opFrame(op) {
|
|
28084
|
+
if (op.op === "translate") return translationFrame(op.v);
|
|
28085
|
+
return rotationFrame(op.angleDeg, op.axis ?? DEFAULT_AXIS, op.at ?? ORIGIN);
|
|
28086
|
+
}
|
|
28087
|
+
/**
|
|
28088
|
+
* Composes an authored `TransformOp` chain into one frame, matching families'
|
|
28089
|
+
* `applyOps`: `ops[0]` is innermost (applied first). For `[A, B]` the composed
|
|
28090
|
+
* motion is `B . A`, so a point transforms as `B(A(p))`.
|
|
28091
|
+
*/
|
|
28092
|
+
function frameFromOps(ops) {
|
|
28093
|
+
let m = IDENTITY_FRAME;
|
|
28094
|
+
for (const op of ops) m = frameMul(opFrame(op), m);
|
|
28095
|
+
return m;
|
|
28096
|
+
}
|
|
28097
|
+
/** Rigid inverse of `[R | t]`: `[R^T | -R^T t]`. */
|
|
28098
|
+
function frameInverse(f) {
|
|
28099
|
+
const t0 = f[12] ?? 0;
|
|
28100
|
+
const t1 = f[13] ?? 0;
|
|
28101
|
+
const t2 = f[14] ?? 0;
|
|
28102
|
+
const inv0 = -(f[0] * t0 + f[1] * t1 + f[2] * t2);
|
|
28103
|
+
const inv1 = -(f[4] * t0 + f[5] * t1 + f[6] * t2);
|
|
28104
|
+
const inv2 = -(f[8] * t0 + f[9] * t1 + f[10] * t2);
|
|
28105
|
+
return [
|
|
28106
|
+
f[0],
|
|
28107
|
+
f[4],
|
|
28108
|
+
f[8],
|
|
28109
|
+
0,
|
|
28110
|
+
f[1],
|
|
28111
|
+
f[5],
|
|
28112
|
+
f[9],
|
|
28113
|
+
0,
|
|
28114
|
+
f[2],
|
|
28115
|
+
f[6],
|
|
28116
|
+
f[10],
|
|
28117
|
+
0,
|
|
28118
|
+
inv0,
|
|
28119
|
+
inv1,
|
|
28120
|
+
inv2,
|
|
28121
|
+
1
|
|
28122
|
+
];
|
|
28123
|
+
}
|
|
28124
|
+
/** Decomposes a frame into origin (mm) + IFC axes (axisX, axisZ). */
|
|
28125
|
+
function decomposeFrame(f) {
|
|
28126
|
+
return decomposePlacement(f);
|
|
28127
|
+
}
|
|
28128
|
+
/** Translation column of a frame. */
|
|
28129
|
+
function frameOrigin(f) {
|
|
28130
|
+
return [
|
|
28131
|
+
f[12] ?? 0,
|
|
28132
|
+
f[13] ?? 0,
|
|
28133
|
+
f[14] ?? 0
|
|
28134
|
+
];
|
|
28135
|
+
}
|
|
28136
|
+
/** True when the rotation part is the identity (within tolerance): a pure
|
|
28137
|
+
* translation, so the existing translation-only placement path suffices. */
|
|
28138
|
+
function isPureTranslation(f, eps = 1e-9) {
|
|
28139
|
+
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;
|
|
28140
|
+
}
|
|
28141
|
+
//#endregion
|
|
27911
28142
|
//#region src/familiesAdapter.ts
|
|
27912
28143
|
/**
|
|
27913
28144
|
* brepjs-families -> BimModel adapter. Consumes a resolved element tree and
|
|
@@ -28129,6 +28360,36 @@ function centreOrigin(base, shift) {
|
|
|
28129
28360
|
origin[2] + shift[2]
|
|
28130
28361
|
];
|
|
28131
28362
|
}
|
|
28363
|
+
function synthesizedProfile(el) {
|
|
28364
|
+
if (el.semantics?.kind !== "product" || el.props["profile"] !== void 0) return void 0;
|
|
28365
|
+
const length = semanticDimension(el, "length");
|
|
28366
|
+
const width = semanticDimension(el, "width");
|
|
28367
|
+
const height = semanticDimension(el, "height");
|
|
28368
|
+
if (el.semantics.category === "beam" && width !== void 0 && height !== void 0) return {
|
|
28369
|
+
profile: {
|
|
28370
|
+
kind: "RECTANGULAR",
|
|
28371
|
+
width,
|
|
28372
|
+
height
|
|
28373
|
+
},
|
|
28374
|
+
shift: [
|
|
28375
|
+
0,
|
|
28376
|
+
width / 2,
|
|
28377
|
+
height / 2
|
|
28378
|
+
]
|
|
28379
|
+
};
|
|
28380
|
+
if (el.semantics.category === "column" && length !== void 0 && width !== void 0) return {
|
|
28381
|
+
profile: {
|
|
28382
|
+
kind: "RECTANGULAR",
|
|
28383
|
+
width: length,
|
|
28384
|
+
height: width
|
|
28385
|
+
},
|
|
28386
|
+
shift: [
|
|
28387
|
+
length / 2,
|
|
28388
|
+
width / 2,
|
|
28389
|
+
0
|
|
28390
|
+
]
|
|
28391
|
+
};
|
|
28392
|
+
}
|
|
28132
28393
|
/**
|
|
28133
28394
|
* Adapts target-independent civil envelope dimensions onto existing typed BIM
|
|
28134
28395
|
* spec inputs. Reference infrastructure Families intentionally author domain
|
|
@@ -28143,18 +28404,7 @@ function civilProductSpecInput(el) {
|
|
|
28143
28404
|
const height = semanticDimension(el, "height");
|
|
28144
28405
|
switch (el.semantics.category) {
|
|
28145
28406
|
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;
|
|
28407
|
+
const synthesized = synthesizedProfile(el);
|
|
28158
28408
|
return {
|
|
28159
28409
|
...base,
|
|
28160
28410
|
...synthesized !== void 0 ? {
|
|
@@ -28166,18 +28416,7 @@ function civilProductSpecInput(el) {
|
|
|
28166
28416
|
};
|
|
28167
28417
|
}
|
|
28168
28418
|
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;
|
|
28419
|
+
const synthesized = synthesizedProfile(el);
|
|
28181
28420
|
return {
|
|
28182
28421
|
...base,
|
|
28183
28422
|
...synthesized !== void 0 ? {
|
|
@@ -28244,12 +28483,12 @@ function peelTranslates(node) {
|
|
|
28244
28483
|
moved
|
|
28245
28484
|
};
|
|
28246
28485
|
}
|
|
28247
|
-
/** True when the element's rendered local transform carries a rotation
|
|
28248
|
-
*
|
|
28249
|
-
*
|
|
28250
|
-
*
|
|
28251
|
-
*
|
|
28252
|
-
*
|
|
28486
|
+
/** True when the element's rendered local transform carries a rotation, which
|
|
28487
|
+
* switches the walk to the rigid-frame placement path (the composed rotation
|
|
28488
|
+
* folds into origin + axisX + axisZ). Only authored `transform` rotations
|
|
28489
|
+
* count: a rotation a family render bakes into its own body geometry (e.g. a
|
|
28490
|
+
* circular beam oriented along axisX) stays in the body, which the spec
|
|
28491
|
+
* rebuilds parametrically from props. */
|
|
28253
28492
|
function hasRotateOp(el) {
|
|
28254
28493
|
return el.localTransforms.some((op) => op.op === "rotate");
|
|
28255
28494
|
}
|
|
@@ -28364,7 +28603,7 @@ function materializeOwnedSolid(el, evaluator, errors) {
|
|
|
28364
28603
|
/** Materialize an unrouted element's IR and add it as a proxy. The body is
|
|
28365
28604
|
* authoritative for a proxy (no parametric spec to diverge from), so baked
|
|
28366
28605
|
* transforms — rotations included — are fine here. */
|
|
28367
|
-
function addProxyElement(model, el, evaluator,
|
|
28606
|
+
function addProxyElement(model, el, evaluator, spatialFrame) {
|
|
28368
28607
|
const body = materializeOwnedSolid(el, evaluator, {
|
|
28369
28608
|
evalCode: "FAMILIES_PROXY_EVAL_FAILED",
|
|
28370
28609
|
notSolidCode: "FAMILIES_PROXY_NOT_SOLID",
|
|
@@ -28372,7 +28611,7 @@ function addProxyElement(model, el, evaluator, projectedSpatialTranslation) {
|
|
|
28372
28611
|
routeName: "proxy"
|
|
28373
28612
|
});
|
|
28374
28613
|
if (!body.ok) return body;
|
|
28375
|
-
const localized =
|
|
28614
|
+
const localized = localizeBodyToFrame(el, body.value, spatialFrame, "FAMILIES_PROXY_LOCALIZE_FAILED", "spatial parent");
|
|
28376
28615
|
if (!localized.ok) return localized;
|
|
28377
28616
|
const nameAttr = el.attributes["name"];
|
|
28378
28617
|
const materialProp = el.props["materialName"];
|
|
@@ -28535,8 +28774,9 @@ var DEFAULT_AXIS_Z = [
|
|
|
28535
28774
|
function axisEquals(value, axis) {
|
|
28536
28775
|
return Array.isArray(value) && value.length === 3 && value.every((component, i) => typeof component === "number" && component === axis[i]);
|
|
28537
28776
|
}
|
|
28538
|
-
/**
|
|
28539
|
-
*
|
|
28777
|
+
/** True when a civil node authors non-default `axisX`/`axisZ` props: like a
|
|
28778
|
+
* tRotate, this puts the node (and its subtree) on the rigid-frame placement
|
|
28779
|
+
* path so descendants relativize against the rotated parent frame. */
|
|
28540
28780
|
function hasRotatedAxes(el) {
|
|
28541
28781
|
const axisX = el.props["axisX"];
|
|
28542
28782
|
const axisZ = el.props["axisZ"];
|
|
@@ -28575,6 +28815,119 @@ function authoredTranslation(el) {
|
|
|
28575
28815
|
}
|
|
28576
28816
|
return total;
|
|
28577
28817
|
}
|
|
28818
|
+
function authoredAxisX(el) {
|
|
28819
|
+
const v = el.props["axisX"];
|
|
28820
|
+
return Array.isArray(v) && v.length === 3 ? v : DEFAULT_AXIS_X;
|
|
28821
|
+
}
|
|
28822
|
+
function authoredAxisZ(el) {
|
|
28823
|
+
const v = el.props["axisZ"];
|
|
28824
|
+
return Array.isArray(v) && v.length === 3 ? v : DEFAULT_AXIS_Z;
|
|
28825
|
+
}
|
|
28826
|
+
/** The placement shift that centres a synthesized beam/column cross-section on
|
|
28827
|
+
* its axis, in the element's own (body) frame. Zero for every other route. */
|
|
28828
|
+
function specLocalShift(el) {
|
|
28829
|
+
return synthesizedProfile(el)?.shift ?? [
|
|
28830
|
+
0,
|
|
28831
|
+
0,
|
|
28832
|
+
0
|
|
28833
|
+
];
|
|
28834
|
+
}
|
|
28835
|
+
/** World frame of an element's own body: the walk's cumulative frame (all
|
|
28836
|
+
* 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) {
|
|
28840
|
+
return frameMul(frameMul(cumulativeFrame, frameFromPlacement({
|
|
28841
|
+
origin: authoredSpecOrigin(el),
|
|
28842
|
+
axisX: authoredAxisX(el),
|
|
28843
|
+
axisZ: authoredAxisZ(el)
|
|
28844
|
+
})), translationFrame(specLocalShift(el)));
|
|
28845
|
+
}
|
|
28846
|
+
/** World frame of a civil spatial node: cumulative transforms composed with any
|
|
28847
|
+
* authored `origin`/`axisX`/`axisZ` props (identity when default). */
|
|
28848
|
+
function civilNodeFrame(el, cumulativeFrame) {
|
|
28849
|
+
return frameMul(cumulativeFrame, frameFromPlacement({
|
|
28850
|
+
origin: authoredSpecOrigin(el),
|
|
28851
|
+
axisX: authoredAxisX(el),
|
|
28852
|
+
axisZ: authoredAxisZ(el)
|
|
28853
|
+
}));
|
|
28854
|
+
}
|
|
28855
|
+
/** Places a routed element's spec input through the composed placement frame:
|
|
28856
|
+
* the flat spec input keeps its dimensions/profile/psets, but `origin`/`axisX`/
|
|
28857
|
+
* `axisZ` are recomputed from the element's world frame relative to its spatial
|
|
28858
|
+
* container. Stair/ramp flights, which carry their own per-flight frame, are
|
|
28859
|
+
* each re-placed the same way. */
|
|
28860
|
+
function rotatedRoutedInput(flatInput, el, cumulativeFrame, spatialFrame) {
|
|
28861
|
+
const toSpatial = frameInverse(spatialFrame);
|
|
28862
|
+
if (Array.isArray(flatInput["flights"])) {
|
|
28863
|
+
const elementFrame = elementBodyFrame(el, cumulativeFrame);
|
|
28864
|
+
const flights = flatInput["flights"];
|
|
28865
|
+
return {
|
|
28866
|
+
...flatInput,
|
|
28867
|
+
origin: [
|
|
28868
|
+
0,
|
|
28869
|
+
0,
|
|
28870
|
+
0
|
|
28871
|
+
],
|
|
28872
|
+
axisX: [
|
|
28873
|
+
1,
|
|
28874
|
+
0,
|
|
28875
|
+
0
|
|
28876
|
+
],
|
|
28877
|
+
axisZ: [
|
|
28878
|
+
0,
|
|
28879
|
+
0,
|
|
28880
|
+
1
|
|
28881
|
+
],
|
|
28882
|
+
flights: flights.map((flight) => {
|
|
28883
|
+
if (typeof flight !== "object" || flight === null) return flight;
|
|
28884
|
+
const f = flight;
|
|
28885
|
+
const world = frameMul(elementFrame, frameFromPlacement({
|
|
28886
|
+
origin: f["origin"] ?? [
|
|
28887
|
+
0,
|
|
28888
|
+
0,
|
|
28889
|
+
0
|
|
28890
|
+
],
|
|
28891
|
+
axisX: f["axisX"] ?? DEFAULT_AXIS_X,
|
|
28892
|
+
axisZ: f["axisZ"] ?? DEFAULT_AXIS_Z
|
|
28893
|
+
}));
|
|
28894
|
+
const placed = decomposeFrame(frameMul(toSpatial, world));
|
|
28895
|
+
return {
|
|
28896
|
+
...f,
|
|
28897
|
+
origin: placed.origin,
|
|
28898
|
+
axisX: placed.axisX,
|
|
28899
|
+
axisZ: placed.axisZ
|
|
28900
|
+
};
|
|
28901
|
+
})
|
|
28902
|
+
};
|
|
28903
|
+
}
|
|
28904
|
+
const placed = decomposeFrame(frameMul(toSpatial, elementBodyFrame(el, cumulativeFrame)));
|
|
28905
|
+
return {
|
|
28906
|
+
...flatInput,
|
|
28907
|
+
origin: placed.origin,
|
|
28908
|
+
axisX: placed.axisX,
|
|
28909
|
+
axisZ: placed.axisZ
|
|
28910
|
+
};
|
|
28911
|
+
}
|
|
28912
|
+
/** Moves an owned world-baked body into its spatial container's local frame.
|
|
28913
|
+
* A pure translation keeps the fast `translate` path (identical to the prior
|
|
28914
|
+
* behaviour); a rotated container applies the inverse frame via `applyMatrix`. */
|
|
28915
|
+
function localizeBodyToFrame(el, body, spatialFrame, errorCode, frameName) {
|
|
28916
|
+
if (isPureTranslation(spatialFrame)) return localizeOwnedSolid(el, body, frameOrigin(spatialFrame), errorCode, frameName);
|
|
28917
|
+
const inverse = decomposeFrame(frameInverse(spatialFrame));
|
|
28918
|
+
try {
|
|
28919
|
+
const localized = applyMatrix(body, placementToMatrix(inverse));
|
|
28920
|
+
if (!localized.ok) {
|
|
28921
|
+
body[Symbol.dispose]();
|
|
28922
|
+
return err(specError(errorCode, `familiesToBim: '${el.keyPath}' could not move its body into the ${frameName} frame`, localized.error));
|
|
28923
|
+
}
|
|
28924
|
+
body[Symbol.dispose]();
|
|
28925
|
+
return ok(localized.value);
|
|
28926
|
+
} catch (cause) {
|
|
28927
|
+
body[Symbol.dispose]();
|
|
28928
|
+
return err(specError(errorCode, `familiesToBim: '${el.keyPath}' could not move its body into the ${frameName} frame`, cause));
|
|
28929
|
+
}
|
|
28930
|
+
}
|
|
28578
28931
|
function civilSpatialInput(el, localTranslation) {
|
|
28579
28932
|
const semantics = el.semantics;
|
|
28580
28933
|
const composition = semantics !== void 0 && "composition" in semantics ? CIVIL_COMPOSITION[semantics.composition] : void 0;
|
|
@@ -28587,22 +28940,36 @@ function civilSpatialInput(el, localTranslation) {
|
|
|
28587
28940
|
...composition !== void 0 ? { compositionType: composition } : {}
|
|
28588
28941
|
};
|
|
28589
28942
|
}
|
|
28943
|
+
/** Relativizes a civil node's world frame against its parent civil frame into
|
|
28944
|
+
* the origin/axisX/axisZ the spatial specs consume. */
|
|
28945
|
+
function civilSpatialFrameInput(el, nodeFrame, parentFrame) {
|
|
28946
|
+
const semantics = el.semantics;
|
|
28947
|
+
const composition = semantics !== void 0 && "composition" in semantics ? CIVIL_COMPOSITION[semantics.composition] : void 0;
|
|
28948
|
+
const local = decomposeFrame(frameMul(frameInverse(parentFrame), nodeFrame));
|
|
28949
|
+
return {
|
|
28950
|
+
name: semanticName(el),
|
|
28951
|
+
origin: local.origin,
|
|
28952
|
+
axisX: local.axisX,
|
|
28953
|
+
axisZ: local.axisZ,
|
|
28954
|
+
...composition !== void 0 ? { compositionType: composition } : {}
|
|
28955
|
+
};
|
|
28956
|
+
}
|
|
28590
28957
|
function civilParentAccepts(kind, parent) {
|
|
28591
28958
|
if (kind === "site") return parent === "project";
|
|
28592
28959
|
if (kind === "bridge") return parent === "site";
|
|
28593
28960
|
return parent === "bridge" || parent === "bridge-part";
|
|
28594
28961
|
}
|
|
28595
|
-
function addCivilSpatialOccurrence(model, el, kind,
|
|
28962
|
+
function addCivilSpatialOccurrence(model, el, kind, input) {
|
|
28596
28963
|
if (kind === "site") {
|
|
28597
28964
|
if (el.semantics?.role !== "transport-site") return unsupportedCivilRole(el, "Site");
|
|
28598
|
-
const parsed = parseSiteSpec(
|
|
28965
|
+
const parsed = parseSiteSpec(input);
|
|
28599
28966
|
return parsed.ok ? model.addSite(parsed.value, { stableKey: el.keyPath }) : parsed;
|
|
28600
28967
|
}
|
|
28601
28968
|
if (kind === "bridge") {
|
|
28602
28969
|
const predefinedType = lookup(BRIDGE_ROLE, el.semantics?.role ?? "");
|
|
28603
28970
|
if (predefinedType === void 0) return unsupportedCivilRole(el, "Bridge");
|
|
28604
28971
|
const parsed = parseBridgeSpec({
|
|
28605
|
-
...
|
|
28972
|
+
...input,
|
|
28606
28973
|
predefinedType
|
|
28607
28974
|
});
|
|
28608
28975
|
return parsed.ok ? model.addBridge(parsed.value, { stableKey: el.keyPath }) : parsed;
|
|
@@ -28611,7 +28978,7 @@ function addCivilSpatialOccurrence(model, el, kind, localTranslation) {
|
|
|
28611
28978
|
const predefinedType = lookup(BRIDGE_PART_ROLE, el.semantics?.role ?? "");
|
|
28612
28979
|
if (predefinedType === void 0) return unsupportedCivilRole(el, "Bridge Part");
|
|
28613
28980
|
const parsed = parseBridgePartSpec({
|
|
28614
|
-
...
|
|
28981
|
+
...input,
|
|
28615
28982
|
predefinedType,
|
|
28616
28983
|
usageType: subdivision !== void 0 ? CIVIL_USAGE[subdivision] : "NOTDEFINED"
|
|
28617
28984
|
});
|
|
@@ -28638,7 +29005,7 @@ function relativeSpecInput(input, projectedSpatialTranslation) {
|
|
|
28638
29005
|
origin: relativeOrigin(input["origin"])
|
|
28639
29006
|
};
|
|
28640
29007
|
}
|
|
28641
|
-
function addEarthworksFillElement(model, el, evaluator,
|
|
29008
|
+
function addEarthworksFillElement(model, el, evaluator, spatialFrame) {
|
|
28642
29009
|
if (el.semantics?.kind !== "product") return err(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: '${el.keyPath}' is not authored as a civil Product`));
|
|
28643
29010
|
const predefinedType = lookup(EARTHWORKS_FILL_ROLE, el.semantics.role);
|
|
28644
29011
|
if (predefinedType === void 0) return unsupportedCivilRole(el, "Earthworks Fill");
|
|
@@ -28649,7 +29016,7 @@ function addEarthworksFillElement(model, el, evaluator, projectedSpatialTranslat
|
|
|
28649
29016
|
routeName: "Earthworks Fill"
|
|
28650
29017
|
});
|
|
28651
29018
|
if (!body.ok) return body;
|
|
28652
|
-
const localized =
|
|
29019
|
+
const localized = localizeBodyToFrame(el, body.value, spatialFrame, "FAMILIES_EARTHWORKS_LOCALIZE_FAILED", "Bridge Part");
|
|
28653
29020
|
if (!localized.ok) return localized;
|
|
28654
29021
|
const specProps = collectSpecProps(el);
|
|
28655
29022
|
const authoredMaterial = el.props["materialName"];
|
|
@@ -28699,10 +29066,14 @@ function familiesToBim(root, options) {
|
|
|
28699
29066
|
const walk = (el, state) => {
|
|
28700
29067
|
const rotatedHere = state.rotated || hasRotateOp(el);
|
|
28701
29068
|
const cumulativeTranslationHere = addTranslation(state.cumulativeTranslation, authoredTranslation(el));
|
|
29069
|
+
const cumulativeFrameHere = frameMul(state.cumulativeFrame, frameFromOps(el.localTransforms));
|
|
28702
29070
|
let proxiedHere = false;
|
|
29071
|
+
let nextRotated = rotatedHere;
|
|
28703
29072
|
let nextSpatialStructureId = state.spatialStructureId;
|
|
28704
29073
|
let nextCivilParent = state.civilParent;
|
|
28705
29074
|
let nextProjectedSpatialTranslation = state.projectedSpatialTranslation;
|
|
29075
|
+
let nextCumulativeFrame = cumulativeFrameHere;
|
|
29076
|
+
let nextSpatialFrame = state.spatialFrame;
|
|
28706
29077
|
const archetype = archetypeFor(el);
|
|
28707
29078
|
const effectiveArchetype = el.semantics?.kind === "product" ? civilProductArchetype(el) : archetype;
|
|
28708
29079
|
const route = specRoute(effectiveArchetype);
|
|
@@ -28711,15 +29082,19 @@ function familiesToBim(root, options) {
|
|
|
28711
29082
|
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
29083
|
const keyed = requireKeyed(el);
|
|
28713
29084
|
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
29085
|
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
|
|
29086
|
+
const nodeFrame = civilNodeFrame(el, cumulativeFrameHere);
|
|
29087
|
+
const rotatedFrame = rotatedHere || hasRotatedAxes(el);
|
|
29088
|
+
const input = rotatedFrame ? civilSpatialFrameInput(el, nodeFrame, state.spatialFrame) : civilSpatialInput(el, subtractTranslation(cumulativeTranslationHere, state.projectedSpatialTranslation));
|
|
29089
|
+
const added = addCivilSpatialOccurrence(model, el, civilKind, input);
|
|
28718
29090
|
if (!added.ok) return added;
|
|
28719
29091
|
model.aggregate(state.spatialStructureId, added.value);
|
|
28720
29092
|
idByKeyPath.set(el.keyPath, added.value);
|
|
28721
29093
|
nextSpatialStructureId = added.value;
|
|
28722
29094
|
nextCivilParent = civilKind;
|
|
29095
|
+
nextRotated = rotatedFrame;
|
|
29096
|
+
nextCumulativeFrame = nodeFrame;
|
|
29097
|
+
nextSpatialFrame = nodeFrame;
|
|
28723
29098
|
nextProjectedSpatialTranslation = addTranslation(cumulativeTranslationHere, authoredSpecOrigin(el));
|
|
28724
29099
|
} 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
29100
|
else if (archetype === "storey") {
|
|
@@ -28740,21 +29115,22 @@ function familiesToBim(root, options) {
|
|
|
28740
29115
|
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
29116
|
const keyed = requireKeyed(el);
|
|
28742
29117
|
if (!keyed.ok) return keyed;
|
|
28743
|
-
const added = addEarthworksFillElement(model, el, bodyEvaluator, state.
|
|
29118
|
+
const added = addEarthworksFillElement(model, el, bodyEvaluator, state.spatialFrame);
|
|
28744
29119
|
if (!added.ok) return added;
|
|
28745
29120
|
model.placeIn(added.value, nextSpatialStructureId);
|
|
28746
29121
|
idByKeyPath.set(el.keyPath, added.value);
|
|
28747
29122
|
} else if (route !== void 0) {
|
|
28748
29123
|
const keyed = requireKeyed(el);
|
|
28749
29124
|
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
29125
|
const voids = el.props["voids"];
|
|
28752
29126
|
if (Array.isArray(voids)) {
|
|
28753
29127
|
const openings = el.children.filter((c) => c.type === "Opening").length;
|
|
28754
29128
|
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
29129
|
}
|
|
28756
29130
|
const routedInput = el.semantics?.kind === "product" ? civilProductSpecInput(el) : ("input" in route ? route.input : specInput)(el);
|
|
28757
|
-
const
|
|
29131
|
+
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;
|
|
29133
|
+
const parsed = route.parse(placedInput);
|
|
28758
29134
|
if (!parsed.ok) return parsed;
|
|
28759
29135
|
const added = route.add(model, parsed.value, el.keyPath);
|
|
28760
29136
|
if (!added.ok) return added;
|
|
@@ -28771,7 +29147,7 @@ function familiesToBim(root, options) {
|
|
|
28771
29147
|
const keyed = requireKeyed(el);
|
|
28772
29148
|
if (!keyed.ok) return keyed;
|
|
28773
29149
|
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.
|
|
29150
|
+
const added = addProxyElement(model, el, options.proxyEvaluator, state.spatialFrame);
|
|
28775
29151
|
if (!added.ok) return added;
|
|
28776
29152
|
model.placeIn(added.value, nextSpatialStructureId);
|
|
28777
29153
|
idByKeyPath.set(el.keyPath, added.value);
|
|
@@ -28787,9 +29163,11 @@ function familiesToBim(root, options) {
|
|
|
28787
29163
|
const r = walk(child, {
|
|
28788
29164
|
spatialStructureId: nextSpatialStructureId,
|
|
28789
29165
|
civilParent: nextCivilParent,
|
|
28790
|
-
rotated:
|
|
29166
|
+
rotated: nextRotated,
|
|
28791
29167
|
cumulativeTranslation: cumulativeTranslationHere,
|
|
28792
|
-
projectedSpatialTranslation: nextProjectedSpatialTranslation
|
|
29168
|
+
projectedSpatialTranslation: nextProjectedSpatialTranslation,
|
|
29169
|
+
cumulativeFrame: nextCumulativeFrame,
|
|
29170
|
+
spatialFrame: nextSpatialFrame
|
|
28793
29171
|
});
|
|
28794
29172
|
if (!r.ok) return r;
|
|
28795
29173
|
}
|
|
@@ -28800,7 +29178,9 @@ function familiesToBim(root, options) {
|
|
|
28800
29178
|
civilParent: "project",
|
|
28801
29179
|
rotated: false,
|
|
28802
29180
|
cumulativeTranslation: ZERO_TRANSLATION,
|
|
28803
|
-
projectedSpatialTranslation: ZERO_TRANSLATION
|
|
29181
|
+
projectedSpatialTranslation: ZERO_TRANSLATION,
|
|
29182
|
+
cumulativeFrame: IDENTITY_FRAME,
|
|
29183
|
+
spatialFrame: IDENTITY_FRAME
|
|
28804
29184
|
});
|
|
28805
29185
|
if (!walked.ok) {
|
|
28806
29186
|
model[Symbol.dispose]();
|