Resources Numerical accuracy Calculation policy

Variant pack: parameter table, sizes and library bodies

Writes every configuration of a part as files: the parameter table as CSV and JSON, each variant's volume and size, and a library item with a body per variant.

How the precision is kept

Values written to the pack are rounded on purpose: parameters to 12 significant digits, volume to 9, sizes to 4 decimals of a millimeter, and library body positions to 0.01 mm, or 0.1 mm when the item would exceed the size limit. The STEP, IFC and DWG files in the pack are written by other modules.

Calculations under this policy

6 calculations in 2 source files. 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.

variants.js PartCraft 0.8.1 64-bit 4 calculations Reviewed

Tests bin/test-configurator.mjs

Parameter values per variant

64-bit

The value each driving parameter ends up with in each variant.

value = parameter to 12 significant digits

Each value is the variant's evaluated parameter, rounded with toPrecision(12) before it is stored in the table. A value that is not finite falls back to the configuration row's own number, or 0.

  • variants.js
  • lines 82 to 100
  • PartCraft 0.8.1
/** The parameter table: a column for every driving parameter any chosen configuration sets (the part's order), and for each
 * variant the value it ends up with. `variants[i].built` is the applied copy (variantPart); without it the row's own values. */
export function variantTable(part, variants) {
  const set = new Set();
  for (const v of variants) for (const k of Object.keys(v.row.values)) set.add(k);
  const columns = (part.params?.vars || []).filter((x) => set.has(x.name)).map((x) => ({ key: x.name }));
  for (const k of set) if (!columns.some((c) => c.key === k)) columns.push({ key: k });
  const rows = variants.map((v) => {
    const vals = v.built?.params ? PR.evaluateVars(v.built.params).values : null;
    const values = {};
    for (const c of columns) {
      const x = vals ? vals.get(c.key) : v.row.values[c.key];
      values[c.key] = Number.isFinite(x) ? Number(x.toPrecision(12)) : (Number.isFinite(v.row.values[c.key]) ? v.row.values[c.key] : 0);
    }
    const offIds = new Set(v.row.off || []);
    return { id: v.id, name: v.name, values, off: part.features.filter((f) => offIds.has(f.id)).map((f) => f.name) };
  });
  return { columns, rows };
}

Numbers in variants.csv

64-bit

How a table value is written into the CSV file.

Each table value is rounded to 12 significant digits a second time when the CSV is written. The JSON file writes the table values as stored.

  • variants.js
  • line 103
  • PartCraft 0.8.1
const num = (v) => String(Number(Number(v).toPrecision(12)));

Size of a variant

64-bit

The width, depth and height of each variant written to variants.json.

size = max − min per axis, to 4 decimals

Minimum and maximum are taken over every mesh vertex with 64-bit comparisons. The three differences are rounded with toFixed(4) and listed as width (x), depth (z) and height (y).

  • variants.js
  • lines 134 to 138
  • PartCraft 0.8.1
const sizeOf = (mesh) => {
  const P = mesh.positions, min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
  for (let i = 0; i < P.length; i += 3) for (let k = 0; k < 3; k++) { min[k] = Math.min(min[k], P[i + k]); max[k] = Math.max(max[k], P[i + k]); }
  return [max[0] - min[0], max[2] - min[2], max[1] - min[1]].map((x) => Number(x.toFixed(4)));   // width (x), depth (z), height (y) of the Y-up model
};

Volume written per variant

64-bit

The volume_mm3 figure of each variant in variants.json.

volume to 9 significant digits

The built solid's volume is rounded with toPrecision(9) before it is written.

  • variants.js
  • line 186
  • PartCraft 0.8.1
stats[v.id] = { volume_mm3: regen.stats ? Number(Number(regen.stats.volume).toPrecision(9)) : null, size_mm: sizeOf(regen.mesh) };
variants-item.js PartCraft 0.8.1 64-bit 2 calculations Reviewed

Tests bin/test-configurator.mjs

Variant body in library coordinates

64-bit

Turns a variant's mesh to Z up, anchors it at the back middle bottom and rounds it for the library item.

(x, y, z) → (x, −z, y), shifted to the anchor, rounded to d decimals

The axis change and the shift are 64-bit. Every position is then rounded to d decimals, 2 by default or 1 when the item is too large; bounds are always rounded to 2. The saved library item therefore carries positions to 0.01 mm or 0.1 mm.

  • variants-item.js
  • lines 24 to 39
  • PartCraft 0.8.1
/** The variant's mesh in library coordinates (Z up, front −Y, anchored back-middle-bottom) and its bounds. */
export function libraryBody(mesh, decimals) {
  const P = mesh.positions, n = P.length / 3;
  const q = new Array(P.length);
  let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, minZ = Infinity, maxZ = -Infinity;
  for (let i = 0; i < n; i++) {
    const x = P[i * 3], y = -P[i * 3 + 2], z = P[i * 3 + 1];   // (x, y, z) → (x, −z, y): rotate +90° about X
    q[i * 3] = x; q[i * 3 + 1] = y; q[i * 3 + 2] = z;
    if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y; if (z < minZ) minZ = z; if (z > maxZ) maxZ = z;
  }
  const ox = -(minX + maxX) / 2, oy = -maxY, oz = -minZ;
  const positions = new Array(q.length);
  for (let i = 0; i < n; i++) { positions[i * 3] = r(q[i * 3] + ox, decimals); positions[i * 3 + 1] = r(q[i * 3 + 1] + oy, decimals); positions[i * 3 + 2] = r(q[i * 3 + 2] + oz, decimals); }
  const bounds = { x: r(minX + ox, 2), y: r(minY + oy, 2), z: 0, w: r(maxX - minX, 2), d: r(maxY - minY, 2), h: r(maxZ - minZ, 2) };
  return { positions, indices: Array.from(mesh.indices), bounds };
}

Rounding helper

64-bit

Rounds a value to a number of decimals for the library item.

r(v, d) = round(v × 10ᵈ) ÷ 10ᵈ

Math.round rounds halves toward +∞, and −0 is turned into 0. The product v × 10ᵈ is itself rounded in binary before Math.round, so a value within one rounding step of a half can round either way.

  • variants-item.js
  • line 22
  • PartCraft 0.8.1
const r = (v, d) => { const k = 10 ** d; const x = Math.round(v * k) / k; return x === 0 ? 0 : x; };

In detail

Method
Each configuration is applied to a copy of the part and built; the table, sizes and library bodies are taken from the built parts and rounded with toPrecision, toFixed or Math.round.
Number types
64-bit numbers; rounded decimal values in the CSV, JSON and library item.
Limits
Library bodies are rounded to 0.01 mm or 0.1 mm. A variant over 200,000 triangles is written as a box of its size. When the item is still over 7,600,000 characters at 0.1 mm, the largest bodies become boxes until it fits.

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