Works out the new extrude depth or revolve angle when the handle of a selected feature is dragged.
How the precision is kept
The pointer ray and the handle axis are combined in closed form in 64-bit numbers. The value written to the feature is then rounded on purpose: a depth to a step of one tenth of its order of magnitude and to 6 decimals, an angle to a whole degree. A value typed in the feature's own field does not pass through this file.
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.
How far along the handle's axis the pointer is, read from the pointer's ray.
t = (b·e − c·d) ÷ (a·c − b²)
The denominator a·c − b² subtracts nearly equal numbers when the ray is close to parallel with the axis; the drag is refused when it is under 1e-9 of a·c, a test that scales with the two vectors.
handle-math.js
lines 64 to 69
PartCraft 0.8.1
export function axisParam(o, d, ro, rd) {
const w0 = sub(o, ro), a = dot(d, d), b = dot(d, rd), c = dot(rd, rd), dd = dot(d, w0), e = dot(rd, w0);
const den = a * c - b * b;
if (Math.abs(den) < 1e-9 * a * c) return null; // the ray runs along the axis: no way to read a drag
return (b * e - c * dd) / den;
}
The stored depth is rounded to a multiple of the step and then to 6 decimals with toFixed (roundTo). The step comes from the depth at the start of the drag, so a 25 mm depth moves in 1 mm steps and a 3 mm depth in 0.1 mm steps.
angle = atan2(q·w, q·u) in degrees, unwrapped toward the previous value, rounded to 1°, kept in 1…360
The pointer ray is intersected with the plane the handle turns in; the division by rd·d is skipped when its magnitude is under 1e-9. The stored angle is Math.round of the result, so a drag sets whole degrees only.
handle-math.js
lines 83 to 96
PartCraft 0.8.1
export function dragRevolve(h, v0, ro, rd, prev = v0) {
const { o, d } = h.axis;
const c = add(o, mul(d, dot(sub(h.start, o), d))); // the circle's centre on the axis
const den = dot(rd, d);
if (Math.abs(den) < 1e-9) return prev; // looking along the plane: keep what it was
const hit = add(ro, mul(rd, dot(sub(c, ro), d) / den));
const u = norm(sub(h.start, c)), w = cross(d, u), q = sub(hit, c);
let ang = Math.atan2(dot(q, w), dot(q, u)) * 180 / Math.PI;
ang = ((ang % 360) + 360) % 360;
// unwrap toward the previous value, so 350° → 10° reads as 370° (then clamped) rather than a jump back to 10°
const k = Math.round((prev - ang) / 360);
ang += 360 * k;
return Math.min(360, Math.max(1, Math.round(ang)));
}
In detail
Method
Closest point between the handle axis and the pointer ray; ray and plane intersection with atan2 for the angle.
Number types
64-bit numbers; plain arrays of three.
Tolerances
Ray parallel to the axis: |a·c − b²| < 1e-9 × a·c (relative). Ray parallel to the turning plane: |rd·d| < 1e-9 (the axis is a unit vector; the ray direction is not normalized here).
Limits
A dragged depth can only land on multiples of its step (0.01 at the smallest); a dragged angle only on whole degrees from 1 to 360.
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.