Resources Numerical accuracy Calculation policy

Manual annotations: measured lengths, angles and radii

Works out the value a measurement annotation shows in a manual: a distance, an angle or a radius through three points.

How the precision is kept

Anchor points are stored as 64-bit numbers in each part's own coordinates and placed with the part's matrix. The value is one closed-form 64-bit expression; the text shown is that value with 0 to 4 decimals. The anchors lie on the manual's part meshes, which are stored in 32-bit (see the manual meshes policy).

Calculations under this policy

3 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.

illustrate/annotate.js PartCraft 0.8.1 64-bit 3 calculations Reviewed

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

Anchor point as assembled

64-bit

Where an annotation's anchor sits when the parts are assembled.

The anchor is placed with the instance's own matrix (apply in m4.js), not the exploded one, so an explode does not change a measured size.

  • illustrate/annotate.js
  • line 99
  • PartCraft 0.8.1
const restPoint = (m, an) => { const i = m.instances.find((x) => x.id === an.inst); return i ? apply(i.matrix, an.p) : null; };

Circle through three points

64-bit

The center and radius of the circle through three points.

n = ab × ac; c = a + ((n × ab)|ac|² + (ac × n)|ab|²) ÷ (2|n|²); r = |c − a|

All vectors are differences from the first point, so the result does not depend on where the part sits. For nearly collinear points |n|² is small and the center far away; below the scaled threshold the function returns nothing.

  • illustrate/annotate.js
  • lines 101 to 108
  • PartCraft 0.8.1
/** A circle through three points: { c, r } (null when they are in a line). */
export function circleThrough(a, b, c) {
  const ab = sub(b, a), ac = sub(c, a), n = cross(ab, ac), nn = dot(n, n);
  if (nn < 1e-18 * Math.max(dot(ab, ab), dot(ac, ac)) ** 2) return null;
  const k = add(mul(cross(n, ab), dot(ac, ac)), mul(cross(ac, n), dot(ab, ab)));
  const c0 = add(a, mul(k, 1 / (2 * nn)));
  return { c: c0, r: len(sub(c0, a)), n: norm(n) };
}

Measured value

64-bit

The length, angle or radius an annotation shows.

length = |P₁ − P₀|; angle = acos(û·ŵ) × 180 ÷ π; radius from the circle through three points

The length is Math.hypot of a difference of two placed points. For an angle the two directions are made unit and their dot product is clamped to [−1, 1] before acos. The returned value is not rounded; the text uses toFixed with the annotation's decimals.

  • illustrate/annotate.js
  • lines 109 to 122
  • PartCraft 0.8.1
/** The value a measurement shows, from the parts where they are assembled (an explode does not change a size). */
export function measureValue(m, a) {
  const P = a.measure.pts.map((an) => restPoint(m, an));
  if (P.some((p) => !p)) return { value: null, text: '' };
  const d = a.measure.decimals;
  if (a.measure.kind === 'linear') { const v = len(sub(P[1], P[0])); return { value: v, text: t('illustrate.measure.mm', { v: fmtN(v, d) }) }; }
  if (a.measure.kind === 'angle') {
    const u = norm(sub(P[0], P[1])), w = norm(sub(P[2], P[1]));
    const v = Math.acos(Math.max(-1, Math.min(1, dot(u, w)))) * 180 / Math.PI;
    return { value: v, text: t('illustrate.measure.deg', { v: fmtN(v, d) }) };
  }
  const c = circleThrough(...P);
  return c ? { value: c.r, text: t('illustrate.measure.radius', { v: fmtN(c.r, d) }) } : { value: null, text: '' };
}

In detail

Method
Distance by Math.hypot; angle by acos of the dot product of two unit vectors; radius of the circle through three points from cross products.
Number types
64-bit numbers.
Tolerances
Three points count as in a line when |n|² < 1e-18 × max(|ab|², |ac|²)², a test scaled to the points' spacing. The cosine is clamped to [−1, 1] before acos.
Limits
acos loses digits for angles near 0° and 180°.

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