Turns the part into the machine's work frame for toolpaths: the stock block, the work origin, exact outlines of flat faces, hole positions and sizes, and the height of the part's top at any point.
How the precision is kept
Frame changes are exact: the tool axis is a world axis, so rotation only swaps and negates coordinates, and the origin is one subtraction. Outlines of flat faces are rebuilt from the exact edge table (64-bit end points; circles through three edge points), with the mesh used only to find which edges bound a face. Heights of curved surfaces, however, are interpolated on the display mesh, whose vertices were read from a Float32Array; the code's own tolerance comment names this.
Open item. Use the edge table's exact circle center and radius instead of the three-point circumcircle, and take heights of curved surfaces from the exact faces or a 64-bit mesh.
Calculations under this policy
12 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.
cam.jsPartCraft 0.8.1 32-bit in part12 calculationsReviewed
Tests tools/partcraft-test/test-cam.mjs
Stock block
64-bit
The size and position of the stock around the part.
offset mode: min = box min − (side, side, bottom), max = box max + (side, side, top); cylinder: r = ½ × box diagonal in XY + side
Additions and subtractions of the model's exact bounding box and the stored offsets. The cylinder radius uses Math.hypot of the box's two XY sides.
Moves points from model coordinates to the work frame (G54) and back.
work = (p · x, p · y, p · z) − origin
The frame axes hold only 0, 1 and −1, so the dot products just pick and negate coordinates with no rounding. The origin subtraction is one 64-bit operation per coordinate.
Copies the part's mesh into work coordinates and sets how close a mesh point must be to an exact edge to be taken as that edge.
tol = 1e-4 + 2e-6 × largest model dimension
The vertices come from the kernel's Float32Array and are stored in a Float64Array after the frame change, so each still carries 32-bit precision. The code comment on the tolerance reads 'float32 mesh vertices vs exact edges'.
The heights at which a vertical line through (x, y) crosses the part's surface.
weights l₁, l₂, l₃ from the triangle's XY corners; z = l₁ z₁ + l₂ z₂ + l₃ z₃
Barycentric interpolation in 64-bit on vertices of 32-bit precision. Triangles seen nearly edge-on (|den| < 1e-14, an absolute test in mm²) are skipped, and a weight down to −1e-12 still counts as inside. The top of the part returned by heightAt is the largest of these values.
cam.js
lines 276 to 293
PartCraft 0.8.1
/** z values where the vertical line at (x, y) meets the mesh (triangles seen edge-on are skipped). */
function hits(x, y) {
const i = Math.floor((x - bx0) / bw), j = Math.floor((y - by0) / bh);
if (i < 0 || j < 0 || i >= binN || j >= binN) return [];
const out = [];
for (const t of bins[j * binN + i]) {
const a = I[t * 3] * 3, b = I[t * 3 + 1] * 3, c = I[t * 3 + 2] * 3;
const x1 = pos[a], y1 = pos[a + 1], x2 = pos[b], y2 = pos[b + 1], x3 = pos[c], y3 = pos[c + 1];
const den = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
if (Math.abs(den) < 1e-14) continue;
const l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / den;
const l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / den;
const l3 = 1 - l1 - l2;
if (l1 < -1e-12 || l2 < -1e-12 || l3 < -1e-12) continue;
out.push({ z: l1 * pos[a + 2] + l2 * pos[b + 2] + l3 * pos[c + 2], up: den > 0 });
}
return out;
}
Top facet and its slope
32-bit in part
The height and slope of the highest mesh triangle under a point, used by 3D finishing.
The slope is that of a flat facet of the mesh, not of the true surface, and its inputs are 32-bit vertices. The divisor is twice the facet's projected area, the same magnitude as the value den that the 1e-14 test guards.
Whether there is part material just under a height at (x, y), used to tell a through hole from a blind one.
first surface below z faces up
The query point is nudged by fixed amounts of 1.37e-7 and 0.91e-7 mm so the line does not pass exactly through a mesh vertex or edge. The answer is a yes or no from the mesh.
cam.js
lines 326 to 332
PartCraft 0.8.1
/** Is there part material just below z at (x, y)? (ray parity, nudged off mesh vertices) */
function materialBelow(x, y, z) {
const hs = hits(x + 1.37e-7, y + 0.91e-7).filter((h) => h.z < z).sort((a, b) => b.z - a.z);
if (!hs.length) return false;
// the first surface below: facing up means we are above material
return hs[0].up;
}
Circles of round edges
64-bit
The center and radius of each circular edge lying in a plane square to the tool.
full circle: c = (a + mid) ÷ 2, r = |a − mid| ÷ 2; arc: circle through a, mid, b
The three points are exact 64-bit points of the edge. The center and radius are recomputed from them rather than read from the edge table, which also carries the kernel's own circle (the field named circ) that this file does not use.
cam.js
lines 337 to 344
PartCraft 0.8.1
const circles = [];
for (const e of edges) {
if (e.type !== 'circle' || Math.abs(e.a[2] - e.b[2]) > tol || Math.abs(e.a[2] - e.at[2]) > tol) continue;
let c, r;
if (Math.hypot(e.a[0] - e.b[0], e.a[1] - e.b[1]) < tol) { c = [(e.a[0] + e.at[0]) / 2, (e.a[1] + e.at[1]) / 2]; r = Math.hypot(e.a[0] - e.at[0], e.a[1] - e.at[1]) / 2; }
else { const cc = circum(e.a, e.at, e.b); if (!cc) continue; c = cc.c; r = cc.r; }
circles.push({ c, r, z: e.a[2], a: e.a, b: e.b, at: e.at });
}
Circle through three points
64-bit
The center and radius of the circle through three 2D points.
The textbook formula on absolute coordinates. The terms |a|², |b|², |c|² grow with the square of the distance from the origin and nearly cancel, so a small circle far from the work origin loses digits. Three points closer to a line than |d| < 1e-14 (absolute) give no circle.
Angles come from atan2 about the center. A sweep below 1e-12 is taken as a full turn. The arc keeps the given end points as its stored ends.
cam.js
lines 425 to 432
PartCraft 0.8.1
/** Arc about c from s to e passing through m (all 2D). */
function arcThrough(c, r, s, m, e) {
const a0 = ang(c, s), am = ang(c, m), a1 = ang(c, e);
const ccw = mod(am - a0, O.TAU) < mod(a1 - a0, O.TAU) || O.dist(s, e) < 1e-12;
let sw = ccw ? mod(a1 - a0, O.TAU) : -mod(a0 - a1, O.TAU);
if (Math.abs(sw) < 1e-12) sw = ccw ? O.TAU : -O.TAU;
return O.arc(c, r, a0, sw, [s[0], s[1]], [e[0], e[1]]);
}
Exact outline of a flat face
64-bit
Turns a face's mesh boundary into lines and arcs whose ends are the exact edge ends.
mesh corner within tol of an edge end → that end; mesh run within tol of an edge circle → arc on that circle
Corners are replaced by the exact 64-bit edge ends and curved runs by arcs on the edge circles, so the 32-bit mesh points do not reach the outline in those cases. A run that fits neither a line nor a known circle is kept as the mesh polyline, which does carry the mesh's precision and chord tolerance.
cam.js
lines 434 to 471
PartCraft 0.8.1
/** Turn a mesh boundary loop (2D points) into exact prims: corners snapped to edge ends, arcs onto edge circles. */
function snapLoop(pts, zEnds, zCirc, tol) {
const n = pts.length;
const corner = pts.map((p) => { let best = null, bd = tol; for (const q of zEnds) { const d = Math.hypot(p[0] - q[0], p[1] - q[1]); if (d < bd) { bd = d; best = [q[0], q[1]]; } } return best; });
const ci = [];
for (let i = 0; i < n; i++) if (corner[i]) ci.push(i);
const onC = (p, c) => Math.abs(Math.hypot(p[0] - c.c[0], p[1] - c.c[1]) - c.r) < tol;
if (!ci.length) {
// no exact vertex (should not happen with OCCT edges): keep the polyline
return pts.map((p, i) => O.line(p, pts[(i + 1) % n]));
}
const out = [];
for (let k = 0; k < ci.length; k++) {
const i = ci[k], j = ci[(k + 1) % ci.length];
const span = j > i ? j - i : j + n - i;
const mids = [];
for (let s = 1; s < span; s++) mids.push(pts[(i + s) % n]);
const s = corner[i], e = corner[j];
let cand = null;
if (mids.length) cand = zCirc.find((c) => onC(s, c) && onC(e, c) && mids.every((p) => onC(p, c)));
else cand = zCirc.find((c) => O.dist(s, e) > tol && ((O.dist(c.a, s) < tol && O.dist(c.b, e) < tol) || (O.dist(c.a, e) < tol && O.dist(c.b, s) < tol)) && Math.abs(crossDist(c.at, s, e)) > tol);
if (cand) {
const m = mids.length ? mids[Math.floor(mids.length / 2)] : [cand.at[0], cand.at[1]];
out.push(arcThrough(cand.c, cand.r, s, m, e));
} else if (mids.length && !mids.every((p) => Math.abs(crossDist(p, s, e)) < tol)) {
// a free-form edge: the mesh polyline (chord tolerance = the display mesh's)
let prev = s;
for (const p of mids) { out.push(O.line(prev, p)); prev = p; }
out.push(O.line(prev, e));
} else if (O.dist(s, e) > 1e-12) out.push(O.line(s, e));
}
// join exactly
for (let k = 0; k < out.length; k++) {
const p = out[k], q = out[(k + 1) % out.length];
if (p.e !== q.s) out[(k + 1) % out.length] = q.k === 'L' ? O.line(p.e, q.e) : O.arc(q.c, q.r, q.a0, q.sw, p.e, q === p ? p.e : q.e);
}
return out;
}
Hole position, size and depth
64-bit
The center, radius, top and bottom of a round face along the tool axis, and whether it is a hole and goes through.
c, r from the exact cylinder; top, bottom = mesh z range snapped to an edge end within tol
The center and radius are the kernel's exact values. Top and bottom start as the mesh's extreme heights and are replaced by an exact edge end when one lies within tol; if none does, the 32-bit mesh value is kept.
cam.js
lines 361 to 380
PartCraft 0.8.1
/** A cylinder face along the tool axis → {c, r, top, bottom, hole (material outside), full, through} or null. */
function cylinderInfo(i) {
const f = faces[i];
if (!f || f.type !== 'cylinder' || Math.abs(Math.abs(f.axis.d[2]) - 1) > 1e-9) return null;
let zmin = Infinity, zmax = -Infinity, hole = 0, ang = new Set();
const c = [f.axis.o[0], f.axis.o[1]];
for (let t = 0; t < nt; t++) {
if (mesh.triFace[t] !== i) continue;
const vs = [I[t * 3], I[t * 3 + 1], I[t * 3 + 2]].map((v) => [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]]);
for (const p of vs) { zmin = Math.min(zmin, p[2]); zmax = Math.max(zmax, p[2]); ang.add(Math.floor(((Math.atan2(p[1] - c[1], p[0] - c[0]) + Math.PI) / O.TAU) * 36) % 36); }
const u = [vs[1][0] - vs[0][0], vs[1][1] - vs[0][1], vs[1][2] - vs[0][2]], w = [vs[2][0] - vs[0][0], vs[2][1] - vs[0][1], vs[2][2] - vs[0][2]];
const n = [u[1] * w[2] - u[2] * w[1], u[2] * w[0] - u[0] * w[2], u[0] * w[1] - u[1] * w[0]];
const m = [(vs[0][0] + vs[1][0] + vs[2][0]) / 3 - c[0], (vs[0][1] + vs[1][1] + vs[2][1]) / 3 - c[1]];
hole += n[0] * m[0] + n[1] * m[1] < 0 ? 1 : -1;
}
const snapZ = (z) => { let best = z, bd = tol; for (const p of ends) { const d = Math.abs(p[2] - z); if (d < bd) { bd = d; best = p[2]; } } return best; };
const top = snapZ(zmax), bottom = snapZ(zmin);
const isHole = hole > 0;
return { c, r: f.r, top, bottom, hole: isHole, full: ang.size >= 34, through: isHole ? !materialBelow(c[0], c[1], bottom - 1e-3) : false };
}
Chaining picked edges into a loop
64-bit
Orders picked edges into one closed outline and makes each start exactly on the previous end.
ends join when closer than tol; then next.start = previous.end
After chaining, each piece's start point is replaced by the previous piece's end point object. For an arc this leaves its center and radius as they were, so the stored start can sit off the circle by the small gap that was closed.
cam.js
lines 477 to 498
PartCraft 0.8.1
/** Order and orient prims into one closed loop (prims may come in any direction). */
function chainOpen(prims, tol) {
if (prims.length === 1) return O.dist(prims[0].s, prims[0].e) < tol ? [prims[0]] : null;
const left = prims.slice(1), loop = [prims[0]];
while (left.length) {
const end = loop[loop.length - 1].e;
let k = left.findIndex((p) => O.dist(p.s, end) < tol), rev = false;
if (k < 0) { k = left.findIndex((p) => O.dist(p.e, end) < tol); rev = true; }
if (k < 0) {
if (loop.length === 1) { loop[0] = O.reversePrim(loop[0]); continue; }
return null;
}
const p = left.splice(k, 1)[0];
loop.push(rev ? O.reversePrim(p) : p);
}
if (O.dist(loop[loop.length - 1].e, loop[0].s) > tol) return null;
for (let k = 0; k < loop.length; k++) {
const p = loop[k], q = loop[(k + 1) % loop.length];
if (p.e !== q.s) loop[(k + 1) % loop.length] = q.k === 'L' ? O.line(p.e, q.e) : O.arc(q.c, q.r, q.a0, q.sw, p.e, q.e);
}
return loop;
}
In detail
Method
Axis-aligned change of frame; stock from the model box plus offsets, or a fixed size; height by barycentric interpolation on the mesh triangle under a point; mesh boundary loops snapped to exact edge ends and circles; circle through three points by the circumcenter formula.
Number types
64-bit numbers. Mesh vertices are copied from a Float32Array into a Float64Array after the frame change, so they hold 32-bit values in 64-bit storage.
Tolerances
Snap tolerance tol = 1e-4 + 2e-6 × largest model dimension (mm). Triangles with twice-projected-area below 1e-14 are skipped. A point counts as inside a triangle down to −1e-12 in barycentric weights. Faces count as square to the tool when |n_z| is within 1e-9 of 1. A cylinder counts as full when its mesh covers at least 34 of 36 ten-degree sectors.
Limits
Heights carry the display mesh's chord tolerance and 32-bit vertices. Free-form edges of a flat face stay as the mesh polyline. The circumcenter formula uses squared absolute coordinates, which lose digits for a small circle far from the work origin.
Approach: 32-bit step, flagged
One step stores or exchanges 32-bit values that reach a number a person reads. It is listed here with its open item, and the step is either moved to 64-bit or the app will warn where the number is read.