Skip to content
Merged
101 changes: 82 additions & 19 deletions bins/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3329,7 +3329,10 @@ <h2 id="drawersTitle">Your drawers</h2>
* one question is two too many, and the third is always the one nobody updates.
*
* Returns the operations for one site: `cut` — ONE closed cutter solid to subtract — and
* `add`, closed shells to stand in the cavity it leaves.
* `add`, closed shells to stand in the cavity it leaves. A housing put in from above also
* says where it stands, for mountLimits to keep the mounting pockets out of it (#75):
* `footprint`, the outline everything it cuts or builds stays inside, and `bottom`, the
* underside of the floor it builds under the key, the lowest it reaches.
*
* The top-inserted housings used to return a third thing: convex prisms for
* clipConvexPrismTop, which peeled the material above a z-plane off whatever it was
Expand All @@ -3351,16 +3354,21 @@ <h2 id="drawersTitle">Your drawers</h2>
function keySiteOps(kind, shape, prm, clr, edge, e, s, H) {
if (kind === 'snaptop') {
const p = snapTopPrm(clr);
const { env } = snapTopParts(edge, e, s, p, H);
const { env, boxes } = snapTopParts(edge, e, s, p, H);
const g2 = (edge === '+x' || edge === '+y') ? -1 : 1;
const ss2 = s + 0.0017;
const rect = (edge === '+x' || edge === '-x')
? [[e + g2*env[0], ss2 + env[2]], [e + g2*env[1], ss2 + env[2]],
[e + g2*env[1], ss2 + env[3]], [e + g2*env[0], ss2 + env[3]]]
: [[ss2 + env[2], e + g2*env[0]], [ss2 + env[3], e + g2*env[0]],
[ss2 + env[3], e + g2*env[1]], [ss2 + env[2], e + g2*env[1]]];
// the slot's rectangle out to the walls round it, open to the seam as the cut is
const t = Math.max(...boxes.map((b) => b[3])), d = Math.max(...boxes.map((b) => b[1]));
const footprint = [[env[0], -t], [d, -t], [d, t], [env[0], t]].map(([u, v]) =>
edge === '+x' || edge === '-x' ? [e + g2*u, ss2 + v] : [ss2 + v, e + g2*u]);
return { cut: extrudePoly(rect, env[4], H + 0.5),
add: snapTopPocket(edge, e, s, p, H) };
add: snapTopPocket(edge, e, s, p, H),
footprint, bottom: Math.min(...boxes.map((b) => b[4])) };
}
if (kind === 'cup') {
const zf = Math.max(1.2, H - 2.25), zTop = H - 0.85;
Expand All @@ -3370,7 +3378,8 @@ <h2 id="drawersTitle">Your drawers</h2>
decomposition, so the cavity is parallel to the wall it houses everywhere instead
of squaring off across the dogbone's taper. */
return { cut: extrudePoly(keyHalf(shape, edge, e, s, prm, clr + 0.55), zf - 0.55, H + 0.5),
add: topPocketCup(shape, edge, e, s, prm, clr, zf, zTop) };
add: topPocketCup(shape, edge, e, s, prm, clr, zf, zTop),
footprint: keyHalf(shape, edge, e, s, prm, clr + 0.6), bottom: zf - 0.6 };
}
/* Bottom-inserted recess. The depth is prm.depth capped clear of the plate top, which
is what every configuration was already cutting: 2.3 for the H-clip, 2.0 for both
Expand Down Expand Up @@ -4108,10 +4117,25 @@ <h2 id="drawersTitle">Your drawers</h2>
notch a dovetail or puzzle tab fits into (tabNotch). Each starts under the plate, as
a magnet pocket from beneath, a counterbore and a shank do, so any of those that
reaches one in plan has broken into it. A magnet from above meets it unless the
pocket's floor stands at least MOUNT_SKIN over the cut's ceiling. The housings a key
is dropped into from above (keySiteOps' 'cup' and 'snaptop') start 1.3 mm or more
over the socket floor: a pocket from above stops under them, and a shank, which
stays on the socket floor, is in the socket's open air up there.
pocket's floor stands at least MOUNT_SKIN over the cut's ceiling.

The housing a key is put into from above (keySiteOps' 'cup' and 'snaptop') is a
cavity cut and a cup built in it: a floor 0.6 mm thick under the key, its underside
1.4 mm over the socket floor (1.3 for a snap clip), and walls from there up, at a
corner where four cells meet. Over a solid floor nothing reaches it: a pocket from
above stops under it, and a shank, which stays on the socket floor, is in the
socket's open air up there. A corner boss stands on the bed, though, with its
pocket's mouth at the bed or at the boss's top (bossHeight, at most 2.6 mm), and the
housing is made after the pockets are cut, so it stood in any it reached (#75): a
wall bowtie at 36.13 mm left 1.3 mm³ of it in the four 6 mm magnet pockets beside
the seam, in the top 0.6 mm of each, and 1.6 mm³ in 9.3 mm ones at 39.46 mm, and a
7.9 mm magnet 1.45 mm deep, level with the cavity's floor, left 13 edges open a
piece, all with Download on. So it counts as a cut that stands from its underside
up ('housing'), as wide as all it cuts or builds: a pocket from beneath clears it by
stopping MOUNT_LEVEL under that, and a pocket from above and a shank, which go in
through it, meet any they reach. Keys in the wall reach a boss's pocket up to
40 mm, the H-clip up to 38.5 and a snap clip in the wall up to 35; nothing over a
solid floor, and nothing else.

Measured off the solids buildPiece cuts, at the sites it cuts pockets at: whole cells
only, so a housing beside a half cell or a margin is measured from the whole cell on
Expand Down Expand Up @@ -4188,8 +4212,11 @@ <h2 id="drawersTitle">Your drawers</h2>
const shapeOf = (edge) => {
if (shapes[edge]) return shapes[edge];
const at = { edge, e: 0, s: 0 };
const solid = plan.kind === 'recess'
? keySiteOps(plan.kind, plan.shape, plan.prm, plan.clr, edge, 0, 0, H).cut
// a housing a key is put into from above, from its underside up through the plate
const housing = plan.kind === 'cup' || plan.kind === 'snaptop'
? keySiteOps(plan.kind, plan.shape, plan.prm, plan.clr, edge, 0, 0, H) : null;
const solid = housing ? extrudePoly(housing.footprint, housing.bottom, H + 0.5)
: plan.kind === 'recess' ? keySiteOps(plan.kind, plan.shape, plan.prm, plan.clr, edge, 0, 0, H).cut
: tabNotch(cfg, at, pad);
const segs = [], bb = [Infinity, Infinity, -Infinity, -Infinity];
let ceiling = -Infinity;
Expand All @@ -4203,12 +4230,13 @@ <h2 id="drawersTitle">Your drawers</h2>
const a = p.verts[0], b = p.verts.find((v) => Math.hypot(v[0] - a[0], v[1] - a[1]) > 1e-9);
if (b) segs.push([a, b]);
}
const part = plan.kind === 'recess' ? keyHalf(plan.shape, edge, 0, 0, plan.prm, 0)
const part = plan.kind === 'recess' || housing ? keyHalf(plan.shape, edge, 0, 0, plan.prm, 0)
: cfg.connector === 'puzzle' ? puzzleShape(OPP[edge], 0, 0, cfg.puzzle, 0, false)
: tabFootprint(OPP[edge], 0, 0, cfg.tab.wr, cfg.tab.wt, cfg.tab.dp, 0.8);
// the ceiling to the micron: a notch's is 2.2 + 0.2, 2.4000000000000004 as summed
const roof = Math.round(ceiling * 1e6) / 1e6;
return (shapes[edge] = { segs, bb, ceiling: roof, part: ring(part) });
const bottom = housing ? Math.round(housing.bottom * 1e6) / 1e6 : undefined;
return (shapes[edge] = { segs, bb, ceiling: roof, bottom, part: ring(part) });
};
// how far a point is from the footprint, less than none inside it
const from = (segs, x, y) => {
Expand All @@ -4223,11 +4251,12 @@ <h2 id="drawersTitle">Your drawers</h2>
};
for (const piece of layout.pieces) {
const conn = pieceConnectors(cfg, layout, piece);
// the cuts buildPiece makes in this piece: notches for tabs, recesses for keys
const cuts = [...conn.notches, ...conn.pnotches, ...(plan.kind !== 'recess' ? []
: conn.keyed.filter((bo) => !(plan.junction && offJunction(bo, cfg.pitch, piece))))];
// the cuts buildPiece makes in this piece: notches for tabs, recesses for keys, and
// the housings of keys put in from above
const cuts = [...conn.notches, ...conn.pnotches,
...conn.keyed.filter((bo) => !(plan.junction && offJunction(bo, cfg.pitch, piece)))];
for (const bo of cuts) {
const { segs, bb, ceiling, part } = shapeOf(bo.edge);
const { segs, bb, ceiling, bottom, part } = shapeOf(bo.edge);
const across = bo.edge === '+x' || bo.edge === '-x';
const ox = across ? bo.e : bo.s, oy = across ? bo.s : bo.e;
// the whole cells with a site in reach of it (a half cell has none; see buildPiece)
Expand All @@ -4247,7 +4276,7 @@ <h2 id="drawersTitle">Your drawers</h2>
const bore = (n) => room > 0 ? boreReach(n, segs, x, y) : room;
sites.push({ room, body: from(part, x, y), bore: { 12: bore(12), 14: bore(14) } });
}
if (sites.length) jointCuts.push({ top: ceiling, sites });
if (sites.length) jointCuts.push({ top: ceiling, bottom, sites });
}
}
}
Expand Down Expand Up @@ -4292,13 +4321,20 @@ <h2 id="drawersTitle">Your drawers</h2>
for (const c of jointCuts) {
/* a pocket from above clears a cut whose ceiling it leaves a layer of floor over; in
a corner boss its floor is the pocket's depth under the boss's top */
if (above && !(c.top + MOUNT_SKIN > (bosses ? bossHeight(cfg) : pad) - cfg.magnetH + 1e-6)) continue;
if (above && c.bottom === undefined &&
!(c.top + MOUNT_SKIN > (bosses ? bossHeight(cfg) : pad) - cfg.magnetH + 1e-6)) continue;
/* a housing a key is put into from above stands from its underside (`bottom`) up: a
pocket from beneath clears it by stopping MOUNT_LEVEL under that, to the micron as
level is below. One from above takes its magnet in through it, and a shank goes up
through it, so each meets one it reaches. */
if (c.bottom !== undefined && !above &&
Math.round((c.bottom - ceiling) * 1e6) / 1e6 >= MOUNT_LEVEL) continue;
/* Level within MOUNT_LEVEL, measured to the micron, so a pocket that far above the
notch's ceiling and one that far below it are taken alike: 2.35 and 2.45 mm deep
against a notch 2.4 mm high, which both build clean. Unrounded, 2.45 counted as
level, and with the ceiling alone rounded 2.35 would. */
const level = Math.round(Math.abs(ceiling - c.top) * 1e6) / 1e6 < MOUNT_LEVEL;
const stop = above ? 'floor' : !notch ? 'cut' : level ? 'level' : 'part';
const stop = c.bottom !== undefined ? 'housing' : above ? 'floor' : !notch ? 'cut' : level ? 'level' : 'part';
for (const s of c.sites) {
const short = fits(s.bore[sides] - seam);
const size = stop === 'part' ? 2 * s.body : short;
Expand Down Expand Up @@ -5362,6 +5398,33 @@ <h2 id="drawersTitle">Your drawers</h2>
!others.some((d) => touchesBuilt(again, c.box, d))) { shells[c.i] = again; c.alt = alt; break; }
}
}
/* And if it touches still, the joint is cut again with its cutters moved a NUDGE
along one diagonal and then the other, as cutAgain moves them, and the first
that is closed, has nothing turned over and shares no edge is kept.
TOUCH_TRIES only reorder the cut, and a split that comes from a plane of the
cell's own can land in the same place every time. Puzzle tabs at a 0.35 mm fit
clearance, four pieces meeting, left one edge used four times in two of them at
every pitch from 31.6 to 52.2 mm (#72): the plane of a socket wall's facet at a
cell's corner, carried across the cell by the BSP, crossed the notch lobe's face
beside its pole 0.3 microns from where the side of the region across the
junction crosses it, and the two shells shared the edge there. The lobe moves
with the clearance and the facet with the rim cutoff and the tolerance, so it is
one clearance for each: 0.349 and 0.35 at the defaults, 0.608 and 0.609 tight,
0.843 and 0.844 with a 1 mm rim cutoff, and 0.102, 0.103 and 0.279 to 0.281
with 0.1. Moved 1.7 microns, the face is crossed somewhere else. A cell that
touches nothing, or that the tries above clear, is built as before. One with
mounting pockets that still touched here was sometimes cleared later, by settle
cutting its pockets again; this clears it first, so such a plate comes out with
other bytes than before, and as closed. */
if (touching.length && beside.some((c) => touchesBuilt(region, own, c)))
for (const [dx, dy] of [[NUDGE, NUDGE], [-NUDGE, NUDGE]]) {
const alt = (solid, cut) => csgSubtract(solid, movePolys(cut, dx, dy));
let again;
try { again = cutCell(alt); } catch (e) { continue; }
if (again && !worse(again, region) && !beside.some((c) => touchesBuilt(again, own, c))) {
region = again; cell.alt = alt; break;
}
}
cell.joint = cutCell;
jointCells.push(cell);
}
Expand Down
37 changes: 34 additions & 3 deletions docs/ENGINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -825,8 +825,29 @@ currently installed):
3 mm thick, is taken: its pocket is 0.04 to 0.09 mm into the notch and the magnet
0.24 mm clear of the tab. 2.4 mm thick it is level, built 96 bad edges, and is refused.
- **A pocket from above** meets any of them unless its floor stands a layer over the
cut's ceiling. The housings a key is dropped into from above start 1.3 mm over the
socket floor and nothing reaches them.
cut's ceiling.
- **The housing a key is put into from above is cut, then built** (`keySiteOps`' cup
and snap slot): a cavity is cut and a cup stood in it, a floor 0.6 mm thick under the
key, its underside 1.4 mm over the socket floor (1.3 for a snap clip), and walls from
there up, at a corner where four cells meet. Over a solid floor nothing reaches it. A
corner boss's pockets do, and the housing is made after them, so it stood in them
with Download on (#75): a wall bowtie at 36.13 mm left 1.3 mm³ in the four 6 mm
magnet pockets beside the seam, in the top 0.6 mm of each, and a 7.9 mm magnet there
1.45 mm deep, level with the cavity's floor, left 13 edges open a piece. `mountLimits`
counts it as a cut from its underside up (`housing`), as wide as all it cuts or
builds: a pocket from beneath clears it by stopping `MOUNT_LEVEL` under that, and a
pocket from above or a shank, which go in through it, meets any it reaches. Of 17,820
designs (every joint, both floors, magnets from both sides and screws, three layouts,
54 pitches from 30 to 52.2 mm) that changes the limits of 1,280, every one with
corner bosses and a key put in from above: wall keys up to 40 mm, the H-clip up to
38.5 and a snap clip in the wall up to 35. Of the 1,771 caps it lowers, 1,573 stop a
tenth or two short of the first size that meets the housing, and no size taken meets
one. Where an H-clip's or a snap clip's housing reaches a pocket, the room it takes is
inside the socket rim's corner, which stands in that pocket already with no joint at
all (a boss's pocket may run under the rim, #83), so the pocket has no less room with
the joint than without it. The plate can still come out open there: an H-clip from
above at 34 mm took a 4 mm magnet from above with 4 edges open where the cavity meets
the pocket's wall, with Download on. So the cap is held off every housing alike.
- **A pocket in a corner boss counts the same way**, since the joint is cut from the
boss too (#70), and from above its floor is measured from the boss's top: wall keys
and the dovetail reach a boss's pocket up to about 39 mm, the H-clip up to about 34.5,
Expand Down Expand Up @@ -879,7 +900,17 @@ currently installed):
`touchesBuilt` compares the two shells' edges inside the band, and a jointed cell that
shares one with a jointed cell built before it is cut again (`TOUCH_TRIES`: each cutter
on its own, the cutters reversed, the faces reversed, the cutters one at a time from the
last), or else the earlier cell is; the top-insert pass does the same. Mounting pockets
last), or else the earlier cell is; the top-insert pass does the same. Those only reorder
the cut, and a split that comes from a plane of the cell's own can land in the same
place every time, so a jointed cell that still shares an edge after both has its joint
cut again with the cutters moved a `NUDGE` along one diagonal and then the other (#72).
Puzzle tabs at a 0.35 mm fit clearance, four pieces meeting, left one edge used four
times in two of them at every pitch from 31.6 to 52.2 mm: the plane of a socket wall's
facet at a cell's corner crossed the notch lobe's face beside its pole 0.3 microns from
where the side of the region across the junction crosses it. The lobe moves with the
clearance and the facet with the rim cutoff and the tolerance, so it is one clearance
for each: 0.349 and 0.35 at the defaults, 0.608 and 0.609 tight, 0.843 and 0.844 with a
1 mm rim cutoff, and 0.102, 0.103 and 0.279 to 0.281 with 0.1. Mounting pockets
do it with no joint at all: a pocket's wall, carried across its cell as a plane, splits
the cell's side where it crosses it, and that side stands on the same plane as the
neighbour's, so a split that lands on a corner of the neighbour's region is that
Expand Down
Loading
Loading