Resources Numerical accuracy Calculation policy

Sketch editor drawing

Draws the sketch, its dimensions and their labels on the canvas over the 3-D view.

How the precision is kept

This code maps sketch coordinates to canvas pixels and lays out dimension lines, arrows and label text. It reads the sketch and does not change its geometry. Dimension labels show the stored value rounded to 3 decimals.

Calculations under this policy

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

sketchview.js PartCraft 0.8.1 32-bit, display only 2 calculations Reviewed

Tests bin/test-partcraft.mjs

Sketch coordinates to canvas pixels

32-bit, display only

Maps a sketch point to its place on the canvas.

x = w ÷ 2 + (u − c_x) × s; y = h ÷ 2 − (v − c_y) × s

64-bit arithmetic whose result is a pixel position; it is used to draw and to place the input box.

  • sketchview.js
  • line 91
  • PartCraft 0.8.1
toScreen([u, v]) { const { w, h } = this.size(); return [w / 2 + (u - this.view.cx) * this.view.s, h / 2 - (v - this.view.cy) * this.view.s]; }

Dimension lines and label

32-bit, display only

The lines, arcs, arrowheads and text of a dimension as drawn.

label text = fmt(value, 3)

The label shows the dimension's value rounded to 3 decimals; the stored value is not changed here. Default label positions use the rounded constant 0.7071 for a 45° direction, which only places the label.

  • sketchview.js
  • lines 1410 to 1507
  • PartCraft 0.8.1
/** Lines, arcs, arrowheads and the label for a dimension, all in sketch coordinates. */
export function dimGeometry(sk, c) {
  if (c.off) { const a = dimAnchor(sk, c); if (a) c = { ...c, at: [a[0] + c.off[0], a[1] + c.off[1]] }; }
  const P = (id) => S.pointXY(sk, id);
  const E = (id) => S.byId(sk, id);
  const m = S.matchSig(sk, c.type, c.refs);
  if (!m) return null;
  const sig = m.sig, r = c.refs;
  const scale = Math.max(1, S.extent(sk));
  const off = scale * 0.08;
  const out = { lines: [], arcs: [], arrows: [], text: '', at: null };
  const v = fmt(c.value, 3);
  if (c.type === 'radius' || c.type === 'diameter') {
    const e = E(r[0]), ce = P(e.c), rad = S.radiusOf(sk, e);
    let dir = c.at ? [c.at[0] - ce[0], c.at[1] - ce[1]] : [0.7071, 0.7071];
    if (e.type === 'arc' && !c.at) { const { a0, sweep } = S.arcAngles(sk, e); dir = [Math.cos(a0 + sweep / 2), Math.sin(a0 + sweep / 2)]; }
    const dl = Math.hypot(...dir) || 1; dir = [dir[0] / dl, dir[1] / dl];
    const edge = [ce[0] + dir[0] * rad, ce[1] + dir[1] * rad];
    const at = c.at || [ce[0] + dir[0] * (rad + off), ce[1] + dir[1] * (rad + off)];
    if (c.type === 'radius') { out.lines.push([ce, edge], [edge, at]); out.arrows.push([edge, dir]); out.text = 'R' + v; }
    else { const back = [ce[0] - dir[0] * rad, ce[1] - dir[1] * rad]; out.lines.push([back, edge], [edge, at]); out.arrows.push([edge, dir], [back, [-dir[0], -dir[1]]]); out.text = '⌀' + v; }
    out.at = at;
    return out;
  }
  if (c.type === 'angle') {
    const l1 = E(r[0]), l2 = E(r[1]);
    const a1 = P(l1.p1), b1 = P(l1.p2), a2 = P(l2.p1), b2 = P(l2.p2);
    const d1 = [b1[0] - a1[0], b1[1] - a1[1]], d2 = [b2[0] - a2[0], b2[1] - a2[1]];
    const den = d1[0] * d2[1] - d1[1] * d2[0];
    if (Math.abs(den) < 1e-12) return null;
    const t = ((a2[0] - a1[0]) * d2[1] - (a2[1] - a1[1]) * d2[0]) / den;
    const X = [a1[0] + d1[0] * t, a1[1] + d1[1] * t];
    const far = (a, b) => (Math.hypot(a[0] - X[0], a[1] - X[1]) > Math.hypot(b[0] - X[0], b[1] - X[1]) ? a : b);
    const f1 = far(a1, b1), f2 = far(a2, b2);
    const ang1 = Math.atan2(f1[1] - X[1], f1[0] - X[0]), ang2 = Math.atan2(f2[1] - X[1], f2[0] - X[0]);
    let s = ang1, e = ang2, d = e - s;
    while (d > Math.PI) d -= 2 * Math.PI; while (d < -Math.PI) d += 2 * Math.PI;
    if (d < 0) { s = ang2; d = -d; }
    const rad = c.at ? Math.hypot(c.at[0] - X[0], c.at[1] - X[1]) : Math.min(Math.hypot(f1[0] - X[0], f1[1] - X[1]), Math.hypot(f2[0] - X[0], f2[1] - X[1])) * 0.6 || off;
    out.arcs.push({ c: X, r: rad, a0: s, a1: s + d });
    const mid = s + d / 2;
    out.at = c.at || [X[0] + Math.cos(mid) * rad, X[1] + Math.sin(mid) * rad];
    out.text = v + '°';
    out.arrows.push([[X[0] + Math.cos(s) * rad, X[1] + Math.sin(s) * rad], [Math.sin(s), -Math.cos(s)]], [[X[0] + Math.cos(s + d) * rad, X[1] + Math.sin(s + d) * rad], [-Math.sin(s + d), Math.cos(s + d)]]);
    return out;
  }
  if (c.type === 'offset' && sig === 'RR') {
    const a = E(r[0]), b = E(r[1]), ce = P(a.c), ra = S.radiusOf(sk, a), rb = S.radiusOf(sk, b);
    let dir = c.at ? [c.at[0] - ce[0], c.at[1] - ce[1]] : [0.7071, 0.7071];
    if (b.type === 'arc' && !c.at) { const { a0, sweep } = S.arcAngles(sk, b); dir = [Math.cos(a0 + sweep / 2), Math.sin(a0 + sweep / 2)]; }
    const dl = Math.hypot(...dir) || 1; dir = [dir[0] / dl, dir[1] / dl];
    const pa = [ce[0] + dir[0] * ra, ce[1] + dir[1] * ra], pb = [ce[0] + dir[0] * rb, ce[1] + dir[1] * rb];
    out.lines.push([pa, pb]);
    out.arrows.push([pb, [pb[0] - pa[0], pb[1] - pa[1]]], [pa, [pa[0] - pb[0], pa[1] - pb[1]]]);
    out.at = c.at || [(pa[0] + pb[0]) / 2 + dir[0] * off * 0.3, (pa[1] + pb[1]) / 2 + dir[1] * off * 0.3];
    out.text = v;
    return out;
  }
  if ((c.type === 'distance' || c.type === 'offset') && (sig === 'PL' || sig === 'LL')) {
    const p = sig === 'PL' ? P(r[0]) : P(E(r[1]).p1);
    const L = E(sig === 'PL' ? r[1] : r[0]);
    const a = P(L.p1), b = P(L.p2);
    const dx = b[0] - a[0], dy = b[1] - a[1], l2 = dx * dx + dy * dy || 1e-300;
    const t = ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / l2;
    const q = [a[0] + t * dx, a[1] + t * dy];
    out.lines.push([p, q]);
    if (t < 0 || t > 1) out.lines.push([t < 0 ? a : b, q]);
    const u = [q[0] - p[0], q[1] - p[1]];
    out.arrows.push([q, u], [p, [-u[0], -u[1]]]);
    out.at = c.at || [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
    if (c.at) out.lines.push([[(p[0] + q[0]) / 2, (p[1] + q[1]) / 2], c.at]);
    out.text = v;
    return out;
  }
  // point-to-point / line length: aligned, horizontal or vertical
  const [a, b] = sig === 'L' ? [P(E(r[0]).p1), P(E(r[0]).p2)] : [P(r[0]), P(r[1])];
  if (c.type === 'hdistance' || c.type === 'vdistance') {
    const k = c.type === 'hdistance' ? 1 : 0;   // the coordinate the dimension line sits at
    const lvl = c.at ? c.at[k] : Math.max(a[k], b[k]) + off;
    const A = k === 1 ? [a[0], lvl] : [lvl, a[1]], B = k === 1 ? [b[0], lvl] : [lvl, b[1]];
    out.lines.push([a, A], [b, B], [A, B]);
    const u = [B[0] - A[0], B[1] - A[1]];
    out.arrows.push([A, [-u[0], -u[1]]], [B, u]);
    out.at = c.at || [(A[0] + B[0]) / 2, (A[1] + B[1]) / 2];
    out.text = v;
    return out;
  }
  const d = [b[0] - a[0], b[1] - a[1]], l = Math.hypot(...d) || 1;
  const n = [-d[1] / l, d[0] / l];
  const mid = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
  const o = c.at ? (c.at[0] - a[0]) * n[0] + (c.at[1] - a[1]) * n[1] : off;
  const A = [a[0] + n[0] * o, a[1] + n[1] * o], B = [b[0] + n[0] * o, b[1] + n[1] * o];
  out.lines.push([a, A], [b, B], [A, B]);
  out.arrows.push([A, [-d[0], -d[1]]], [B, d]);
  out.at = c.at || [mid[0] + n[0] * o, mid[1] + n[1] * o];
  out.text = v;
  return out;
}

In detail

Method
screen = (w ÷ 2 + (u − c_x) × s, h ÷ 2 − (v − c_y) × s); dimension lines and arcs placed from the measured geometry.
Number types
64-bit numbers handed to the 2-D canvas.

Approach: Display only

Computed to draw the screen, often in 32-bit on the graphics processor. No number a person reads, saves or exports comes from it.

Every policy that takes this approach