Turns a cabinet's width, height and depth and the shop's construction rules into the parts to cut: sides, bottom, top or rails, back, shelves, doors, drawer fronts and drawer boxes.
How the precision is kept
Each part size is a short sum or difference of the cabinet's sizes and rule values in 64-bit numbers. The length and width of every part are rounded to six decimals of the job unit when the part is recorded, which removes binary noise such as 563.9999999 without changing a size a shop can measure. Parts of equal rounded size are merged into one row with a quantity.
Calculations under this policy
6 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.
The 32 mm system's drilling and hardware distances for the inch rule set.
value = round(mm × (1 ÷ 25.4) × 10⁴) ÷ 10⁴
The inch values are rounded to 0.0001 in: the 37 mm system line is stored as 1.4567 in, where the exact value is 1.45669… in. For millimeter jobs the factor is 1 and the values are unchanged.
addons/cabinetcraft/lib/model.js
lines 42 to 50
CadCraft 0.8.2
/** The 32 mm system's numbers (step 5): drilling and hardware. mm are the source; inches are the same distances (k = 1 / 25.4). */
const hw = (k) => {
const r = (v) => Math.round(v * k * 1e4) / 1e4;
return {
sysLine: r(37), pitch: r(32), sysStart: r(64), pinD: r(5), pinDepth: r(12), pinsPerShelf: 4,
cupD: r(35), cupDepth: r(12.5), cupEdge: r(5), hingeInset: r(100), hingeSteps: [r(1000), r(1600), r(2200)],
plateD: r(5), plateDepth: r(12), slideHole: r(5), legsWide: r(900), pulls: true,
};
};
Box sizes
64-bit
The box height without the plinth, the inside width and the depth the back takes.
boxH = h − kick; iw = w − 2t; backDepth = backSetback + backT
Three subtractions and one sum of the cabinet's sizes and rule values; nothing is rounded here.
The size and position of every door and drawer front in the cabinet's front view.
one door: (w − reveal) × (boxH − reveal); a pair: each (w − 2·reveal) ÷ 2 wide; n drawer fronts: each (boxH − n·reveal) ÷ n high
Widths, heights and positions are rounded to six decimals of the job unit as they are stored. Drawer fronts are stacked from the top by subtracting each height and reveal in turn, so the position of the lowest front carries the rounding of the ones above it, far below the six decimals kept.
addons/cabinetcraft/lib/model.js
lines 126 to 151
CadCraft 0.8.2
/** The fronts in the cabinet's front view: x from its left side, y from its bottom (floor for base/tall, its own bottom for wall). */
export function frontsOf(cab, R) {
const { kick, boxH } = boxOf(cab, R);
const rv = R.reveal, out = [];
if (cab.type === 'drawers') {
const n = cab.fronts ? cab.fronts.length : cab.drawers;
const free = boxH - n * rv;
const hs = cab.fronts ? cab.fronts : Array.from({ length: n }, () => free / n);
let y = kick + boxH - rv / 2; // top down
for (const fh of hs) { out.push({ kind: 'drawer', x: rv / 2, y: r6(y - fh), w: r6(cab.w - rv), h: r6(fh) }); y -= fh + rv; }
return out;
}
if (cab.doors === 0) return out;
const pair = cab.doors === 2 || (cab.doors === 'auto' && cab.w - rv > R.maxDoorW);
const rows = boxH - rv > R.maxDoorH ? 2 : 1;
const dh = rows === 2 ? (boxH - 2 * rv) / 2 : boxH - rv;
for (let r = 0; r < rows; r++) {
const y = kick + rv / 2 + r * (dh + rv);
if (pair) {
const dw = (cab.w - 2 * rv) / 2;
out.push({ kind: 'door', x: rv / 2, y: r6(y), w: r6(dw), h: r6(dh), hinge: 'left' });
out.push({ kind: 'door', x: r6(rv / 2 + dw + rv), y: r6(y), w: r6(dw), h: r6(dh), hinge: 'right' });
} else out.push({ kind: 'door', x: rv / 2, y: r6(y), w: r6(cab.w - rv), h: r6(dh), hinge: cab.hinge });
}
return out;
}
Longest drawer slide that fits
64-bit
Picks the drawer slide length from the rule's list for the cabinet's inside depth.
room = panel depth − backDepth − drawerBackClear; the longest listed L with L ≤ room + 1e-9
A table choice with an absolute tolerance of 1e-9 job units, so a slide exactly as long as the room is accepted despite rounding in the subtraction.
addons/cabinetcraft/lib/model.js
lines 153 to 160
CadCraft 0.8.2
/** The longest slide that fits the inside depth, or 0. */
export function slideFor(cab, R) {
const { backDepth, panelD } = boxOf(cab, R);
const room = panelD - backDepth - R.drawerBackClear;
let best = 0;
for (const L of R.slideLengths) if (L <= room + 1e-9) best = L;
return best;
}
Part list of one cabinet
64-bit
Every part of a cabinet with its length, width, thickness, material, quantity and banded edges.
Length and width are rounded to 1e-6 job units when a part is added; thickness is the rule value as given. Doors and drawer fronts of equal rounded size share one row; drawer boxes are grouped by their rounded height. A part whose length or width is not above zero is left out without a message here (problems reports the cause).
addons/cabinetcraft/lib/model.js
lines 162 to 229
CadCraft 0.8.2
export function cabinetParts(cabIn, R = RULES.mm) {
const cab = cleanCabinet(cabIn, R);
if (!cab) return [];
const { t, backT, groove } = R;
const { kick, boxH, iw, backDepth, panelD } = boxOf(cab, R);
const out = [];
let seq = 0;
const add = (role, name, length, width, thickness, material, extra = {}) => {
if (!(length > 0 && width > 0)) return;
seq++;
out.push({ id: cab.no + '-' + String(seq).padStart(2, '0'), name, role, group: cab.no, length: r6(length), width: r6(width), thickness, material, grain: 'length', qty: 1, band: {}, ...extra });
};
const L1 = (b) => ({ l1: b });
const all = (b) => ({ l1: b, l2: b, w1: b, w2: b });
// sides: a notched toe kick runs the sides to the floor with the kick cut out of the front bottom corner
const notch = kick > 0 && R.kick === 'notch';
const sideL = notch ? cab.h : boxH;
const side = { qty: 2, band: L1(R.bandCarcass) };
if (notch) {
// x along the length (up the side, from the floor), y across the width (from the back edge to the front edge, L1 at y = width)
// the outline is at the CUT size (lib/cutlist nests outlines as given): the front edge's banding comes off the width
const a = R.kickSetback, W = r6(panelD - R.bandCarcass.thickness);
side.outline = [[{ x: 0, y: 0 }, { x: sideL, y: 0 }, { x: sideL, y: W }, { x: kick, y: W }, { x: kick, y: W - a }, { x: 0, y: W - a }]];
}
add('side', 'part.side', sideL, panelD, t, R.carcass, side);
add('bottom', 'part.bottom', iw, panelD, t, R.carcass, { band: L1(R.bandCarcass) });
if (hasTop(cab)) add('top', 'part.top', iw, panelD, t, R.carcass, { band: L1(R.bandCarcass) });
else {
add('rail', 'part.rail-front', iw, R.railDepth, t, R.carcass, { band: L1(R.bandCarcass) });
add('rail', 'part.rail-back', iw, R.railDepth, t, R.carcass);
}
const ends = hasTop(cab) ? 2 : 1; // panels the back meets top and bottom (rails don't take a groove)
if (R.back === 'groove') add('back', 'part.back', iw + 2 * groove, boxH - ends * t + ends * groove, backT, R.backMat, { grain: 'none' });
else if (R.back === 'between') add('back', 'part.back', iw, boxH - ends * t, backT, R.backMat, { grain: 'none' });
else add('back', 'part.back', cab.w, boxH, backT, R.backMat, { grain: 'none' });
const fronts = frontsOf(cab, R);
const insideD = panelD - backDepth;
if (cab.type !== 'drawers') {
if (fronts.length && cab.type === 'tall' && fronts.some((f) => f.y > kick + boxH / 2 - 1e-9)) // two rows of doors: a fixed shelf between
add('shelf-fixed', 'part.shelf-fixed', iw, insideD, t, R.carcass, { band: L1(R.bandCarcass) });
if (cab.shelves) add('shelf', 'part.shelf', iw - 2 * R.shelfClear, insideD - R.shelfFront, t, R.carcass, { qty: cab.shelves, band: L1(R.bandCarcass) });
// doors: the same size merge into one part with a quantity (grain up the door)
const sizes = new Map();
for (const f of fronts) { const k = f.h + 'x' + f.w; sizes.set(k, { f, n: (sizes.get(k)?.n || 0) + 1 }); }
for (const { f, n } of sizes.values()) add('door', 'part.door', f.h, f.w, R.frontT, R.frontMat, { qty: n, band: all(R.bandFront) });
} else {
const sizes = new Map();
for (const f of fronts) { const k = f.h + 'x' + f.w; sizes.set(k, { f, n: (sizes.get(k)?.n || 0) + 1 }); }
// drawer fronts: grain across (along the width), as most shops run it
for (const { f, n } of sizes.values()) add('drawer-front', 'part.drawer-front', f.w, f.h, R.frontT, R.frontMat, { qty: n, band: all(R.bandFront) });
const L = slideFor(cab, R), bw = iw - 2 * R.slideGap, dt = R.drawerT, g = R.drawerGroove;
if (L > 0) {
const boxes = new Map();
for (const f of fronts) { const bh = r6(f.h - R.drawerHClear); if (bh >= R.drawerMinH) boxes.set(bh, (boxes.get(bh) || 0) + 1); }
for (const [bh, n] of boxes) {
add('drawer-side', 'part.drawer-side', L, bh, dt, R.drawerMat, { qty: 2 * n, band: L1(R.bandDrawer), meta: { slide: L } });
add('drawer-end', 'part.drawer-end', bw - 2 * dt, bh, dt, R.drawerMat, { qty: 2 * n, band: L1(R.bandDrawer) });
add('drawer-bottom', 'part.drawer-bottom', bw - 2 * dt + 2 * g, L - 2 * dt + 2 * g, R.drawerBottomT, R.drawerBottomMat, { qty: n, grain: 'none' });
}
}
}
if (kick > 0 && R.plinth) {
if (notch) add('plinth', 'part.kick-board', iw, kick, t, R.carcass);
else add('plinth', 'part.plinth', cab.w, kick, t, R.carcass);
}
return out;
}
Size checks on a cabinet
64-bit
Reports a cabinet that is too narrow, too low or too shallow for its parts, or whose drawers do not fit.
The first three comparisons are exact, with no tolerance; the sum of typed drawer fronts has an absolute tolerance of 1e-9. The sum and the limit shown in the message are rounded to six decimals.
Frameless construction: inside width = w − 2t, box height = h − plinth, door and drawer front sizes from an even reveal, drawer box from the longest slide that fits. The file's header comment lists each formula.
Number types
64-bit numbers. Millimeter rules are whole or one-decimal values; the inch rule set is written in inches, except the drilling distances, which are converted from millimeters.
Tolerances
Sizes rounded to 1e-6 job units when stored. A slide fits when its length is at most the room plus 1e-9. Typed drawer fronts are reported as too tall when their sum exceeds the box height by more than 1e-9. All absolute, in job units.
Limits
The inch drilling distances are the millimeter values times 1 ÷ 25.4, rounded to four decimals of an inch.
Approach: 64-bit, direct
Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.