Lists each body with its volume and box, and moves, copies or scales bodies.
How the precision is kept
Volumes come from the solid kernel's own volume properties on the exact shape and are added per body in 64-bit. A move is a 4 × 4 matrix built in 64-bit and applied by the kernel's transform. A uniform scale is the kernel's scale about a point; a non-uniform one is an affine map done by brep-gtrsf.js, which reports when it had to approximate.
Calculations under this policy
4 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
volume = Σ volume of the body's solids; box = min and max over them
Each solid's volume is the kernel's Mass() from VolumeProperties on the exact shape, not on a mesh. The solids of one body are added in the order the kernel lists them.
brep-bodies.js
lines 69 to 85
PartCraft 0.8.1
lib.bodyList = (ctx) => {
if (!ctx.body) return { bodies: [], faceBody: [] };
const { oc, T, H } = ctx;
const by = new Map(), faceId = new SMap(H.hash);
for (const so of H.explore(ctx.body, 'SOLID')) {
const id = bodyOf(ctx, so) || '?';
const gp = T(new oc.GProp_GProps());
oc.BRepGProp.VolumeProperties(so, gp, false, false, false);
const bb = ctx.bbox(so);
const b = by.get(id) || { id, solids: 0, volume: 0, bbox: { min: [Infinity, Infinity, Infinity], max: [-Infinity, -Infinity, -Infinity] } };
b.solids++; b.volume += gp.Mass();
for (let k = 0; k < 3; k++) { b.bbox.min[k] = Math.min(b.bbox.min[k], bb.min[k]); b.bbox.max[k] = Math.max(b.bbox.max[k], bb.max[k]); }
by.set(id, b);
for (const fc of H.unique(so, 'FACE')) faceId.set(fc, id);
}
return { bodies: [...by.values()], faceBody: ctx.faceTable().map((r) => faceId.get(r.face) || null) };
};
Move matrix
64-bit
The matrix a Move body feature applies: by distances, point to point, about an axis, or plane to plane.
points: T(to − from); align: rotate n_a onto ∓n_b about c, then t = n_b ((o_b − c) · n_b)
Built from 64-bit vectors by vec.js and bodies.js. In the align case the rotation uses normalized normals, while the translation uses n_b as the plane resolver returns it, without normalizing it again. The helper axis is chosen by comparing |nₓ| with 0.9.
brep-bodies.js
lines 186 to 205
PartCraft 0.8.1
/** The column-major 4×4 a Move body applies. */
function moveMatrix(f, ctx) {
if (f.mode === 'translate') return V.translation([f.dx, f.dy, f.dz]);
if (f.mode === 'points') {
if (!f.from || !f.to) fail(tr('model.brep_bodies.move.pick_points'));
return V.translation(V.sub(lib.pointOf(f.to, ctx), lib.pointOf(f.from, ctx)));
}
if (f.mode === 'rotate') { const a = axisOf(f, ctx); return V.rotationAbout(a.o, a.d, f.angle); }
if (f.mode === 'align') {
if (f.planes.length < 2) fail(tr('model.brep_bodies.move.pick_faces'));
const R = resolvers(ctx);
const a = R.plane(f.planes[0]), b = R.plane(f.planes[1]);
const want = f.flip ? b.n : V.mul(b.n, -1); // faces meet face to face (opposite normals) unless flipped
const Rm = BD.minimalRotation(V.norm(a.n), V.norm(want), V.norm(V.cross(a.n, Math.abs(a.n[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0])));
const c = a.center || a.o;
const t = V.mul(b.n, V.dot(V.sub(b.o, c), b.n));
return BD.rigid(Rm, c, t);
}
return fail(tr('model.brep_bodies.move.unknown'));
}
Shape under a move or a uniform scale
64-bit
Applies the matrix or the scale to a solid in the kernel.
A rigid move is applied by the kernel without copying the geometry (the shape is relocated). A scale is set on a gp_Trsf about the given center and applied with a copy.
brep-bodies.js
lines 110 to 127
PartCraft 0.8.1
/** shape → its image under column-major M4 (rigid: located; with a scale: copied), names carried (+ suffix). */
function transformed(ctx, shape, M4, suffix = '', { scale = null } = {}) {
const { oc, T, H } = ctx;
let tr;
if (scale) {
const t = T(new oc.gp_Trsf());
t.SetScale(H.pnt(scale.c), scale.s);
tr = T(new oc.BRepBuilderAPI_Transform(shape, t, true, false));
} else tr = T(new oc.BRepBuilderAPI_Transform(shape, H.trsf(M4), false, false));
const out = T(tr.Shape());
const names = new SMap(H.hash);
for (const fc of H.unique(shape, 'FACE')) {
const n = ctx.names.get(fc);
const m = T(tr.ModifiedShape(fc));
if (n && !m.IsNull()) names.set(m, n + suffix);
}
return { shape: out, names };
}
Scale center and affine map
64-bit
Chooses the center of a scale and builds the map for unequal factors.
c = picked point, or (box min + box max) × 0.5; A = diag(sₓ, s_y, s_z); t = c (1 − s)
The three factors are compared pairwise with 1e-15, an absolute number, to decide between the kernel's uniform scale and the affine map. The translation is c × (1 − s) per axis; for a factor between 0.5 and 2 the subtraction from 1 is exact in binary, so the product is the only rounding.
brep-bodies.js
lines 232 to 241
PartCraft 0.8.1
const c = f.point ? lib.pointOf(f.point, ctx) : V.mul(V.add(bb.min, bb.max), 0.5);
const parts = others(ctx, used).map((so) => ({ shape: so, names: ctx.names, id: bodyOf(ctx, so) }));
let note;
const s = [f.uniform ? f.s : f.sx, f.uniform ? f.s : f.sy, f.uniform ? f.s : f.sz];
if (!s.every((v) => v > 0)) fail(tr('model.brep_bodies.scale.factor_positive'));
if (f.uniform || (Math.abs(s[0] - s[1]) < 1e-15 && Math.abs(s[1] - s[2]) < 1e-15)) {
for (const g of groups) for (const so of g.solids) parts.push({ ...transformed(ctx, so, null, '', { scale: { c, s: s[0] } }), id: g.id });
} else {
// non-uniform: an affine map of the BRep (this build has no BRepBuilderAPI_GTransform; brep-gtrsf.js)
const A = { m: [[s[0], 0, 0], [0, s[1], 0], [0, 0, s[2]]], t: [c[0] * (1 - s[0]), c[1] * (1 - s[1]), c[2] * (1 - s[2])] };
In detail
Method
OpenCascade BRepGProp volume properties, BRepBuilderAPI_Transform and gp_Trsf scale, as named in the code.
Number types
64-bit numbers; kernel shapes.
Tolerances
Three scale factors count as uniform when each pair differs by less than 1e-15 (absolute). The move matrix must be finite in all sixteen entries.
Limits
A non-uniform scale can replace surfaces by approximations; the feature's note states how many and the deviation. The numbers in that note are shown to 8 significant digits.
Approach: 64-bit, direct
Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.