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