#!/usr/bin/env node /* test-fit.cjs — assert the dimensions that decide whether the parts actually WORK. * * The mesh checks prove the file is valid geometry; the renders let a human sanity-check the * shape. Neither says the breadboard fits. These do, by measuring the finished solids the * same way a caliper would: take the boxes, and for a given Z height, ask what clear span is * left between them along a scan line. If a wall moved, or a clearance got typo'd, or a bay * silently inherited the wrong dimension, this fails — and it fails BEFORE filament is spent. */ 'use strict'; const assert = require('assert'); const { benchPlate, fitGauge, P } = require('./models.cjs'); let pass = 0; const ok = (name, fn) => { try { fn(); console.log(` ok ${name}`); pass++; } catch (e) { console.log(` FAIL ${name}\n ${e.message}`); process.exitCode = 1; } }; /* Largest clear gap along one axis between the solids that block a scan line, within the window [from,to]. Returns the gap and its bounds — what a part dropped in there can occupy. * * CLAMP THE BLOCKERS TO THE WINDOW. The first version did not, and it silently measured * geometry 70 mm outside the range it was asked about: every gauge channel came back as * 26.20 mm regardless of its real width, because the widest gap ANYWHERE on that scan line * won. A measuring instrument that ignores its own bounds reports confident nonsense, and it * would have failed a correct model — the exact way a bad test costs more than no test. */ function span(blocked, from, to) { const segs = blocked .map(([s, e]) => [Math.max(s, from), Math.min(e, to)]) .filter(([s, e]) => e > s) .sort((a, b) => a[0] - b[0]); let best = 0, at = null, cursor = from; for (const [s, e] of segs) { if (s > cursor && s - cursor > best) { best = s - cursor; at = [cursor, s]; } cursor = Math.max(cursor, e); } if (to > cursor && to - cursor > best) { best = to - cursor; at = [cursor, to]; } return { width: best, at }; } const clearSpanX = (boxes, y, z, from, to) => span(boxes .filter((b) => y > b[1] + 1e-9 && y < b[4] - 1e-9 && z > b[2] + 1e-9 && z < b[5] - 1e-9) .map((b) => [b[0], b[3]]), from, to); const clearSpanY = (boxes, x, z, from, to) => span(boxes .filter((b) => x > b[0] + 1e-9 && x < b[3] - 1e-9 && z > b[2] + 1e-9 && z < b[5] - 1e-9) .map((b) => [b[1], b[4]]), from, to); console.log('\nbench plate'); { const { m, meta } = benchPlate(); const boxes = m.boxes; const zMid = P.baseT + P.wallH / 2; // halfway up the walls const { W, D } = meta; ok('the breadboard bay is wide enough for a BB400, and not sloppy', () => { const span = clearSpanX(boxes, D / 2, zMid, 0, W); // the widest clear span across the middle is the breadboard bay assert(span.width >= P.bbL, `bay ${span.width.toFixed(2)} < breadboard ${P.bbL}`); assert(span.width <= P.bbL + P.bbClear + 0.01, `bay ${span.width.toFixed(2)} is sloppier than ${P.bbL + P.bbClear}`); }); ok('the breadboard bay is DEEP enough, and not sloppy', () => { const span = clearSpanY(boxes, P.margin + P.wallT + 5, zMid, 0, D); assert(span.width >= P.bbW, `depth ${span.width.toFixed(2)} < breadboard ${P.bbW}`); assert(span.width <= P.bbW + P.bbClear + 0.01, `depth ${span.width.toFixed(2)} is sloppier than ${P.bbW + P.bbClear}`); }); ok('the devkit bay holds the board width with the intended clearance', () => { const span = clearSpanX(boxes, D / 2, zMid, W - P.margin - P.wallT - 40, W); assert(Math.abs(span.width - (P.boardW + P.boardClear)) < 0.01, `devkit bay ${span.width.toFixed(2)}, expected ${(P.boardW + P.boardClear).toFixed(2)}`); }); ok('the devkit bay is OPEN end to end, so an unverified board LENGTH cannot bind', () => { // scan down the middle of the devkit bay: nothing may block it anywhere in Y const xMid = W - P.margin - P.wallT - (P.boardW + P.boardClear) / 2; const span = clearSpanY(boxes, xMid, zMid, 0, D); assert(Math.abs(span.width - D) < 0.01, `devkit bay is obstructed: clear span ${span.width.toFixed(2)} of ${D.toFixed(2)} mm`); }); ok('the rear wall really is notched for cables', () => { const yRear = D - P.margin - P.wallT / 2; const blocked = boxes.filter((b) => yRear > b[1] && yRear < b[4] && zMid > b[2] && zMid < b[5]); assert(blocked.length >= 3, `rear wall is ${blocked.length} segment(s); notches would make 3+`); }); ok('nothing overhangs: every solid sits on the base or on another solid', () => { for (const b of boxes) { if (b[2] < 1e-9) continue; // on the plate assert(Math.abs(b[2] - P.baseT) < 1e-9, `a solid starts at z=${b[2]}, not on the base`); } }); } console.log('\nfit gauge'); { const { m, meta } = fitGauge(); const boxes = m.boxes; const z = 2.4 + 3.0 / 2; ok('every channel measures its stated width', () => { // walk each channel by finding the pair of walls that bracket it for (const w of meta.widths) { const found = meta.rows.find((r) => Math.abs(r.w - w) < 1e-9); const span = clearSpanY(boxes, found.chanX0 + 1, z, found.y - 1, found.y + 2 * 2.0 + w + 1); assert(Math.abs(span.width - w) < 0.01, `channel ${w}: clear span ${span.width.toFixed(2)}`); } }); ok('the ribs never intrude into a channel', () => { for (const r of meta.rows) { for (const b of boxes) { const isRib = (b[3] - b[0]) < 2.0 && b[2] > 2.0; if (!isRib) continue; const inChannelY = b[1] < r.y + 2.0 + r.w && b[4] > r.y + 2.0; const inChannelX = b[0] < r.chanX0 + 16 && b[3] > r.chanX0; assert(!(inChannelY && inChannelX), `a rib overlaps channel ${r.w}`); } } }); ok('channel widths bracket the nominal 25.4 mm guess in both directions', () => { assert(Math.min(...meta.widths) < 25.4, 'no channel narrower than the guess'); assert(Math.max(...meta.widths) > 25.4, 'no channel wider than the guess'); }); ok('the PUBLISHED band is exactly the array the STL on the site was built from', () => { // The default must be immovable: html/electronics/esp32-bench-parts/esp32-fit-gauge.stl is // served from this array, so a default that drifts makes the published solid disagree // with the generator and with the dimensions both pages state. assert.deepStrictEqual(meta.widths, [24.6, 25.0, 25.4, 25.8, 26.2, 26.6]); }); } /* A RE-CENTRED GAUGE. The default band brackets a DevKitC-1-sized board and nothing else, so the method is only reusable if the ladder can be moved onto a width the reader supplies. These assertions are the ones that decide whether that variant is usable at all: does it still straddle the target in BOTH directions, is it still six channels, and is the solid still a printable mesh? A variant that brackets nothing is a ruler with no marks on it. */ console.log('\nfit gauge, re-centred on a supplied width'); { const target = 31.7; // an arbitrary caller estimate, not a board fact const { m, meta } = fitGauge({ centre: target }); const boxes = m.boxes; ok('a supplied centre is bracketed in both directions', () => { assert(meta.widths.length === 6, `${meta.widths.length} channels, not six`); assert(Math.min(...meta.widths) < target, `no channel narrower than ${target}`); assert(Math.max(...meta.widths) > target, `no channel wider than ${target}`); // Nothing lands ON the estimate: half-step offsets mean every channel gives a verdict. for (const w of meta.widths) assert(Math.abs(w - target) > 1e-9, `channel ${w} sits on the estimate`); }); ok('the re-centred ladder keeps the 0.4 mm default step, evenly spaced', () => { const sorted = meta.widths.slice().sort((a, b) => a - b); for (let i = 1; i < sorted.length; i++) { assert(Math.abs(sorted[i] - sorted[i - 1] - 0.4) < 1e-9, `step ${sorted[i - 1]} -> ${sorted[i]} is not 0.4`); } // 2 dp, so no 31.099999999999998 reaches the mesh. for (const w of meta.widths) assert(Math.abs(w * 100 - Math.round(w * 100)) < 1e-9, `${w} is not 2 dp`); }); ok('every re-centred channel measures its stated width, and no rib intrudes', () => { const z = meta.baseT + meta.wallH / 2; for (const r of meta.rows) { const s = clearSpanY(boxes, r.chanX0 + 1, z, r.y - 1, r.y + 2 * r.wallT + r.w + 1); assert(Math.abs(s.width - r.w) < 0.01, `channel ${r.w}: clear span ${s.width.toFixed(2)}`); for (const b of boxes) { const isRib = (b[3] - b[0]) < 2.0 && b[2] > 2.0; if (!isRib) continue; const inY = b[1] < r.y + r.wallT + r.w && b[4] > r.y + r.wallT; const inX = b[0] < r.chanX0 + r.chanLen && b[3] > r.chanX0; assert(!(inY && inX), `a rib overlaps re-centred channel ${r.w}`); } } }); ok('the re-centred solid is still a valid, non-interpenetrating mesh', () => { const v = m.check(); assert(v.ok, `mesh invalid: ${(v.problems || []).join('; ')}`); assert(v.triangles > 0 && v.solids === 34, `${v.solids} solids, expected the same 34 as the default`); }); ok('a re-centred gauge does not disturb the default one', () => { // Same process, called again after the variant: the published band must be unchanged. assert.deepStrictEqual(fitGauge().meta.widths, [24.6, 25.0, 25.4, 25.8, 26.2, 26.6]); }); } console.log(`\n${pass} assertion(s) passed${process.exitCode ? ', SOME FAILED' : ''}`);