Works out the draft angle of each face corner against a pull direction, the areas that pass or need draft, and the curvature limit shown with the curvature colors.
How the precision is kept
The arithmetic is 64-bit, but the vertex positions come from the part's display mesh, which the kernel stores in a Float32Array. The areas and the curvature limit are therefore measured on 32-bit vertices of a faceted mesh, not on the exact faces. The draft readout in draft-ui.js shows each area as a percentage of the total with one decimal.
Open item. Measure the draft areas on the exact faces, or on a 64-bit copy of the mesh positions, instead of the 32-bit display mesh.
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.
analysis.jsPartCraft 0.8.1 32-bit in part3 calculationsReviewed
Tests tools/partcraft-test/test-analysis.mjs
Draft angle
64-bit
The angle between a surface normal and the plane square to the pull direction, in degrees.
angle = asin(clamp(n · pull, −1, 1)) × 180 ÷ π
The dot product is clamped to [−1, 1] before asin. Near ±90° asin is steep, so a small error in the dot product gives a larger error in the angle there; near 0° (a vertical wall, where draft matters) it is well conditioned.
The surface area with enough draft, with too little, and facing away from the pull.
area = |(B − A) × (C − A)| ÷ 2; each corner adds area ÷ 3 to its class
Vertex positions are read from the mesh's Float32Array; the cross product and the running sums are 64-bit and added in triangle order. The class limits have an absolute slack of 1e-9 degrees. The totals carry the precision of the 32-bit vertices.
analysis.js
lines 22 to 43
PartCraft 0.8.1
export function draftAnalysis(mesh, pull, minDeg, colors) {
const P = mesh.positions, I = mesh.indices, nt = I.length / 3, CN = mesh.cornerNormals && mesh.cornerNormals.length === nt * 9 ? mesh.cornerNormals : null;
const d = unit(pull), m = Math.abs(minDeg);
const out = new Float32Array(nt * 9);
const areas = { positive: 0, needs: 0, negative: 0, total: 0 };
for (let t = 0; t < nt; t++) {
const a = I[t * 3] * 3, b = I[t * 3 + 1] * 3, c = I[t * 3 + 2] * 3;
const u = [P[b] - P[a], P[b + 1] - P[a + 1], P[b + 2] - P[a + 2]], v = [P[c] - P[a], P[c + 1] - P[a + 1], P[c + 2] - P[a + 2]];
const cr = [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]];
const area = Math.hypot(cr[0], cr[1], cr[2]) / 2, tn = unit(cr);
areas.total += area;
for (let k = 0; k < 3; k++) {
const n = CN ? unit([CN[t * 9 + k * 3], CN[t * 9 + k * 3 + 1], CN[t * 9 + k * 3 + 2]]) : tn;
const ang = draftAngle(n, d);
const cls = ang >= m - 1e-9 ? 'positive' : ang <= -m + 1e-9 ? 'negative' : 'needs';
areas[cls] += area / 3;
const col = colors[cls];
out[t * 9 + k * 3] = col[0]; out[t * 9 + k * 3 + 1] = col[1]; out[t * 9 + k * 3 + 2] = col[2];
}
}
return { colors: out, areas };
}
Curvature color limit
32-bit in part
The curvature value (1/mm) at which the colors reach full blue or red, stated in the panel with its radius.
lim = 95th percentile of |mean curvature|; color weight = clamp(mean ÷ lim, −1, 1)
The mean curvature per vertex comes from surface.js, computed on the Float32Array mesh positions. The percentile is the sorted value at index floor(0.95 × n), with no interpolation; a zero limit is replaced by 1. The colors are written to a Float32Array for the screen.
analysis.js
lines 61 to 71
PartCraft 0.8.1
export function curvatureColors(mesh, { concave, flat, convex }) {
const { mean } = curvature(mesh);
const abs = Array.from(mean, Math.abs).sort((x, y) => x - y);
const lim = abs[Math.floor(abs.length * 0.95)] || 1;
const n = mesh.positions.length / 3, out = new Float32Array(n * 3);
for (let i = 0; i < n; i++) {
const t = Math.max(-1, Math.min(1, mean[i] / lim)), to = t < 0 ? concave : convex, w = Math.abs(t);
for (let k = 0; k < 3; k++) out[i * 3 + k] = flat[k] + (to[k] - flat[k]) * w;
}
return { colors: out, lim };
}
In detail
Method
Draft angle = asin(n · pull) in degrees per triangle corner, using the stored surface normal when the mesh has one, else the triangle normal. A triangle's area is split in thirds among its corners by class. Curvature colors are scaled to the 95th percentile of the absolute mean curvature from surface.js.
Number types
Positions and corner normals read from Float32Array; angles, areas and the percentile in 64-bit numbers; colors written to Float32Array for the screen.
Tolerances
A corner counts as positive when its angle is at least the minimum minus 1e-9 degrees, and negative when at most minus the minimum plus 1e-9 degrees (absolute).
Limits
Areas are facet areas of the mesh. A triangle whose corners fall in different classes is split in thirds, not along the true boundary.
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.