Measures how thick the model is behind each triangle and lists the areas thinner than the printer's limit.
How the precision is kept
One ray per triangle runs from just inside its middle along the inward normal to the first face it meets from behind; the ray math is 64-bit. The measured thickness is stored in a Float32Array, and the thinnest value and the comparison with the limit are both taken from that array. On a mesh with more triangles than maxRays only every k-th triangle is measured and the others copy a neighbor's value.
Open item. Store the thickness in a Float64Array, or take the minimum and the comparison with the limit in 64-bit before the value is stored.
Calculations under this policy
4 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.
print-walls.jsModelCraft 0.8.1 32-bit in part4 calculationsReviewed
Tests tools/modelcraft-test/print07.mjs
Thickness behind a triangle
32-bit in part
The distance from a triangle's middle, straight in, to the far side of the wall.
origin = centroid − n·eps; thickness = distance to the first face met from behind + eps
The centroid, the ray and d + eps are 64-bit, but the result is written into a Float32Array, which keeps about 7 significant digits. A ray that first meets a front face, or nothing within 2 × diagonal + 1, leaves Infinity.
print-walls.js
lines 27 to 42
ModelCraft 0.8.1
const values = new Float32Array(nt).fill(Infinity), measured = new Uint8Array(nt);
const step = Math.max(1, Math.ceil(nt / Math.max(1, maxRays)));
const eps = Math.max(1e-5, 1e-7 * Math.max(...T.size, 1));
const hit = { tri: -1, back: 0 };
const far = 2 * Math.hypot(...T.size) + 1;
let rays = 0;
for (let t = 0; t < nt; t += step) {
if (!A[t]) continue;
const a = I[t * 3] * 3, b = I[t * 3 + 1] * 3, c = I[t * 3 + 2] * 3;
const nx = N[t * 3], ny = N[t * 3 + 1], nz = N[t * 3 + 2];
const ox = (P[a] + P[b] + P[c]) / 3 - nx * eps, oy = (P[a + 1] + P[b + 1] + P[c + 1]) / 3 - ny * eps, oz = (P[a + 2] + P[b + 2] + P[c + 2]) / 3 - nz * eps;
const d = rayNearest(T, ox, oy, oz, -nx, -ny, -nz, 0, far, t, hit);
rays++;
measured[t] = 1;
if (d >= 0 && hit.back) values[t] = d + eps;
}
Values for unmeasured triangles
32-bit in part
On a decimated check, gives each unmeasured triangle the value of the nearest measured one across shared edges.
The value is copied, not interpolated, breadth-first from the measured triangles. An unmeasured triangle can therefore show a thickness that was measured some triangles away.
print-walls.js
lines 44 to 57
ModelCraft 0.8.1
if (step > 1) {
const nv = P.length / 3, edges = new Map(), nbr = Array.from({ length: nt }, () => []);
for (let t = 0; t < nt; t++) for (let k = 0; k < 3; k++) {
const u = I[t * 3 + k], v = I[t * 3 + (k + 1) % 3], key = u < v ? u * nv + v : v * nv + u;
const o = edges.get(key);
if (o === undefined) edges.set(key, t); else { nbr[t].push(o); nbr[o].push(t); }
}
const queue = [], seen = new Uint8Array(nt);
for (let t = 0; t < nt; t++) if (measured[t]) { queue.push(t); seen[t] = 1; }
for (let q = 0; q < queue.length; q++) {
const t = queue[q];
for (const o of nbr[t]) if (!seen[o]) { seen[o] = 1; values[o] = values[t]; queue.push(o); }
}
}
Thinnest wall and thin area
32-bit in part
The smallest thickness found and the total area of triangles under the limit.
thinnest = min(values); thin area = Σ area of triangles with value < min
Both the minimum and the comparison with the limit read the 32-bit array. Between 0.5 and 1 mm a 32-bit number has a spacing of about 6 × 10⁻⁸ mm, so a wall within that distance of the limit can be counted on either side. The areas summed are 64-bit.
print-walls.js
lines 58 to 63
ModelCraft 0.8.1
let thinnest = Infinity, thinArea = 0;
const thin = [];
for (let t = 0; t < nt; t++) {
if (values[t] < thinnest) thinnest = values[t];
if (values[t] < min) { thin.push(t); thinArea += A[t]; }
}
Thin regions
32-bit in part
Thin triangles joined across shared edges into regions, each with its area, its thinnest value and where that is.
region area = Σ triangle areas; at = centroid of the thinnest triangle
The region's thinnest value is read from the 32-bit array. Its area, its box and the point reported are 64-bit. At most 50 regions are returned, thinnest first.
print-walls.js
lines 67 to 97
ModelCraft 0.8.1
/** Thin triangles joined by shared edges → regions, thinnest first (≤ 50). */
function regionsOf(T, thin, values) {
const { P, I, A } = T, nv = P.length / 3, isThin = new Set(thin), byEdge = new Map();
for (const t of thin) for (let k = 0; k < 3; k++) {
const u = I[t * 3 + k], v = I[t * 3 + (k + 1) % 3], key = u < v ? u * nv + v : v * nv + u;
const l = byEdge.get(key);
if (l) l.push(t); else byEdge.set(key, [t]);
}
const seen = new Set(), out = [];
for (const s of thin) {
if (seen.has(s)) continue;
seen.add(s);
const tris = [s], lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
let area = 0, best = s;
for (let q = 0; q < tris.length; q++) {
const t = tris[q];
area += A[t];
if (values[t] < values[best]) best = t;
for (let k = 0; k < 3; k++) {
const v = I[t * 3 + k];
for (let j = 0; j < 3; j++) { const x = P[v * 3 + j]; if (x < lo[j]) lo[j] = x; if (x > hi[j]) hi[j] = x; }
const u = I[t * 3 + (k + 1) % 3], key = v < u ? v * nv + u : u * nv + v;
for (const o of byEdge.get(key) || []) if (isThin.has(o) && !seen.has(o)) { seen.add(o); tris.push(o); }
}
}
const a = I[best * 3] * 3, b = I[best * 3 + 1] * 3, c = I[best * 3 + 2] * 3;
out.push({ tris, area, thinnest: values[best], at: [(P[a] + P[b] + P[c]) / 3, (P[a + 1] + P[b + 1] + P[c + 1]) / 3, (P[a + 2] + P[b + 2] + P[c + 2]) / 3], lo, hi });
}
out.sort((x, y) => x.thinnest - y.thinnest || y.area - x.area);
return out.slice(0, 50);
}
In detail
Method
Ray casting on the mesh (print-geom.js); thin triangles joined into regions across shared edges.
Number types
Float32Array for the thickness per triangle; Float64Array for positions, normals and areas; millimeters.
Tolerances
The ray starts eps = max(1e-5, 1e-7 × largest size) mm inside the face and eps is added back to the distance. The default limit is 0.8 mm.
Limits
Thickness is measured at one point per triangle, along its normal only. With more triangles than maxRays (20 000 by default) the values of unmeasured triangles are copies.
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.