Resources Numerical accuracy Calculation policy

Depth and angle set by dragging a handle

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.

handle-math.js PartCraft 0.8.1 64-bit 3 calculations Reviewed

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

Point on the handle axis nearest the pointer ray

64-bit

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;
}

Dragged extrude depth

64-bit

The depth stored on the Extrude after a drag.

depth = max(step, round((v₀ + scale × (t − t₀)) ÷ step) × step), step = max(0.01, 10^⌊log₁₀|v₀|⌋ ÷ 10)

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.

  • handle-math.js
  • lines 78 to 81
  • PartCraft 0.8.1
export function dragExtrude(h, v0, t0, t) {
  const step = niceStep(v0);
  return Math.max(step, roundTo(v0 + h.scale * (t - t0), step));
}

Dragged revolve angle

64-bit

The angle stored on the Revolve after a drag.

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.

Every policy that takes this approach