Resources Numerical accuracy Calculation policy

Add-on display bodies: stored coordinates and placement

Checks the boxes and meshes an add-on places in the view, stores them in the part file, and places them in the world.

How the precision is kept

Every coordinate, offset, turn angle and opacity is rounded to 3 decimals before it is stored in the part file. Placement is a turn about the Y axis and a move, in 64-bit. The file's own comment says these are display bodies that take no part in the solid, its mass or its exports.

Open item. Store the coordinates as given, or state the 0.001 mm grid in the add-on API.

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.

addons/addon-bodies.js PartCraft 0.8.1 64-bit 4 calculations Reviewed

Tests None found by the review

Rounding to 3 decimals

64-bit

Rounds a number to a multiple of 0.001 before it is stored.

round(v × 1000) ÷ 1000

Math.round of v × 1000, then ÷ 1000: the value kept is the 64-bit number nearest a multiple of 0.001. Math.round takes halves toward +∞.

  • addons/addon-bodies.js
  • line 23
  • PartCraft 0.8.1
const r3 = (v) => Math.round(v * 1000) / 1000;

Body coordinates as stored

64-bit

The box corners or mesh positions, offset and turn of one body as they go into the part file.

A box's corners are ordered with min and max, then each number is rounded by r3 before it is stored; mesh positions, the offset, the turn angle and the opacity are rounded the same way. A box with no size on an axis after rounding is refused.

  • addons/addon-bodies.js
  • lines 26 to 44
  • PartCraft 0.8.1
export function cleanBody(raw) {
  if (!raw || typeof raw !== 'object') return null;
  const out = { name: text(raw.name, LIMITS.name), color: COLOR.test(raw.color) ? raw.color.toLowerCase() : '#c9c2b4' };
  if (typeof raw.opacity === 'number' && raw.opacity >= 0.05 && raw.opacity < 1) out.opacity = r3(raw.opacity);
  if (Array.isArray(raw.box)) {
    if (raw.box.length !== 6 || !raw.box.every(fin)) return null;
    const b = raw.box.map(Number);
    out.box = [Math.min(b[0], b[3]), Math.min(b[1], b[4]), Math.min(b[2], b[5]), Math.max(b[0], b[3]), Math.max(b[1], b[4]), Math.max(b[2], b[5])].map(r3);
    if (out.box[3] - out.box[0] <= 0 || out.box[4] - out.box[1] <= 0 || out.box[5] - out.box[2] <= 0) return null;
  } else if (raw.mesh && typeof raw.mesh === 'object') {
    const P = raw.mesh.positions, I = raw.mesh.indices;
    if (!Array.isArray(P) || !Array.isArray(I) || P.length < 9 || P.length % 3 || I.length < 3 || I.length % 3 || I.length / 3 > LIMITS.triangles || P.length > 3 * LIMITS.triangles) return null;
    if (!P.every(fin) || !I.every((i) => Number.isInteger(i) && i >= 0 && i < P.length / 3)) return null;
    out.mesh = { positions: P.map((v) => r3(Number(v))), indices: I.slice() };
  } else return null;
  if (Array.isArray(raw.at)) { if (raw.at.length !== 3 || !raw.at.every(fin)) return null; out.at = raw.at.map((v) => r3(Number(v))); }
  if (raw.rotY !== undefined) { if (!fin(raw.rotY)) return null; out.rotY = r3(Number(raw.rotY)); }
  return out;
}

Body placed in the world

64-bit

Turns a body's points about the Y axis and moves them by its offset.

x′ = x cos a + z sin a + t_x; y′ = y + t_y; z′ = −x sin a + z cos a + t_z; a = rotY × π ÷ 180

The angle is converted to radians in 64-bit and its cosine and sine are taken once per body. Nothing is rounded here.

  • addons/addon-bodies.js
  • lines 112 to 115
  • PartCraft 0.8.1
function placed(b, pts) {
  const a = ((b.rotY || 0) * Math.PI) / 180, c = Math.cos(a), s = Math.sin(a), at = b.at || [0, 0, 0];
  return pts.map(([x, y, z]) => [x * c + z * s + at[0], y + at[1], -x * s + z * c + at[2]]);   // a right-handed turn about +Y
}

Box of all add-on bodies

64-bit

The least and largest coordinates of every placed add-on body.

Min and max by comparison over the placed corner points; no arithmetic beyond placed.

  • addons/addon-bodies.js
  • lines 117 to 121
  • PartCraft 0.8.1
export function boundsOf(part) {
  let min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
  for (const groups of Object.values(part.addonBodies || {})) for (const g of Object.values(groups)) for (const b of g.bodies) for (const p of bodyPoints(b)) for (let i = 0; i < 3; i++) { if (p[i] < min[i]) min[i] = p[i]; if (p[i] > max[i]) max[i] = p[i]; }
  return Number.isFinite(min[0]) ? { min, max } : null;
}

In detail

Method
cleanBody validates and rounds. placed applies x′ = x cos a + z sin a, z′ = −x sin a + z cos a, then adds the offset.
Number types
64-bit numbers in plain arrays; Float32Array only for the drawn geometry (groupObject).
Tolerances
Coordinates beyond ±1e7 are refused. Stored values are rounded to 0.001.
Limits
A coordinate an add-on gives with more than 3 decimals is not kept as given.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach