Resources Numerical accuracy Calculation policy

Bodies: volume, move and scale

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.

brep-bodies.js PartCraft 0.8.1 64-bit 4 calculations Reviewed

Tests None found by the review

Volume and box of each body

64-bit

The volume and bounding box the body list shows.

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.

Every policy that takes this approach