Resources Numerical accuracy Calculation policy

Anchor points of manual annotations

Turns a point picked on a part in the Illustrate workspace into the anchor that labels, arrows and measurements of a manual are attached to.

How the precision is kept

The picked point is moved into the part's own axes with the inverse of the instance's matrix and stored with each coordinate rounded to six decimals. The face normal is the normalized cross product of the picked triangle's edges. The measurement values themselves are computed in illustrate/annotate.js from these anchors.

Calculations under this policy

1 calculation 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.

ws-illustrate.js PartCraft 0.8.1 64-bit 1 calculation Reviewed

Tests tools/partcraft-test/test-illustrate.mjs

Picked point as an anchor

64-bit

The anchor position in the part's own axes and the face normal at the pick.

p = M⁻¹ p_pick, each coordinate rounded to 6 decimals; n = (b − a) × (c − a) ÷ length

The stored anchor passes through Number(x.toFixed(6)), so it is rounded to 1e-6 in model units before it is saved in the manual. The normal divides by the cross product's length only when that length is above 0.

  • ws-illustrate.js
  • lines 466 to 481
  • PartCraft 0.8.1
/** A picked point as an anchor (in the part's own axes) and the picked face's normal there. */
function anchorAt(h) {
  const m = S.doc.m, inst = m.instances.find((i) => i.id === h.inst);
  const M = MN.worldOf(m, shownScene()).get(h.inst), inv = invert(M);
  const p = applyM(inv, h.point);
  let n = [0, 1, 0];
  const mesh = S.doc.meshes.get(inst.part);
  if (mesh && Number.isInteger(h.face) && h.face * 3 + 2 < mesh.indices.length) {
    const P = mesh.positions, I = mesh.indices, k = h.face * 3;
    const a = [P[I[k] * 3], P[I[k] * 3 + 1], P[I[k] * 3 + 2]], b = [P[I[k + 1] * 3], P[I[k + 1] * 3 + 1], P[I[k + 1] * 3 + 2]], c = [P[I[k + 2] * 3], P[I[k + 2] * 3 + 1], P[I[k + 2] * 3 + 2]];
    const u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]], v = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
    const nn = [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]], l = Math.hypot(...nn);
    if (l > 0) n = nn.map((x) => x / l);
  }
  return { anchor: { inst: h.inst, p: p.map((x) => Number(x.toFixed(6))) }, normal: n };
}

In detail

Method
p_local = M⁻¹ p_world with illustrate/m4.js; normal = (b − a) × (c − a) normalized.
Number types
64-bit numbers; the stored anchor is rounded to 1e-6. The scene mesh's vertex array is read as handed in.
Tolerances
Anchor coordinates rounded to six decimals (absolute). A triangle with zero area keeps the default normal (0, 1, 0).
Limits
A measurement between two anchors is taken between points rounded to 1e-6. The pick point itself comes from the scene's pick, outside this file.

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