Resources Numerical accuracy Calculation policy

Cabinet parts from a box and construction rules

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.

addons/cabinetcraft/lib/model.js CadCraft 0.8.2 64-bit 6 calculations Reviewed

Tests None found by the review

Drilling distances in inches

64-bit

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.

  • addons/cabinetcraft/lib/model.js
  • lines 117 to 124
  • CadCraft 0.8.2
export function boxOf(cab, R) {
  const kick = hasKick(cab, R) ? R.kickH : 0;
  const boxH = cab.h - kick;
  const iw = cab.w - 2 * R.t;
  const backDepth = R.back === 'applied' ? 0 : R.backSetback + R.backT;   // inside depth lost to the back (applied: outside the panels)
  const panelD = R.back === 'applied' ? cab.d - R.backT : cab.d;
  return { kick, boxH, iw, backDepth, panelD };
}

Door and drawer front sizes

64-bit

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.

back in grooves: (iw + 2·groove) × (boxH − n·t + n·groove); shelf: (iw − 2·shelfClear) × (inside depth − shelfFront); drawer end: iw − 2·slideGap − 2·drawerT

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.

iw ≤ 0; boxH ≤ 2t; inside depth ≤ shelfFront; Σ fronts + n·reveal > boxH + 1e-9

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.

  • addons/cabinetcraft/lib/model.js
  • lines 231 to 246
  • CadCraft 0.8.2
export function problems(cabIn, R = RULES.mm) {
  const cab = cleanCabinet(cabIn, R);
  if (!cab) return [{ code: 'no-size', vars: {} }];
  const out = [];
  const { boxH, iw, backDepth, panelD } = boxOf(cab, R);
  if (iw <= 0) out.push({ code: 'too-narrow', vars: { min: 2 * R.t } });
  if (boxH <= 2 * R.t) out.push({ code: 'too-low', vars: { min: 2 * R.t + (cab.type === 'wall' ? 0 : R.kickH) } });
  if (panelD - backDepth <= R.shelfFront) out.push({ code: 'too-shallow', vars: { min: backDepth + R.shelfFront } });
  if (cab.type === 'drawers') {
    if (!slideFor(cab, R)) out.push({ code: 'no-slide', vars: { min: R.slideLengths[0] + backDepth + R.drawerBackClear } });
    const fr = frontsOf(cab, R);
    if (fr.some((f) => f.h - R.drawerHClear < R.drawerMinH)) out.push({ code: 'drawer-low', vars: { min: R.drawerMinH + R.drawerHClear } });
    if (cab.fronts) { const sum = cab.fronts.reduce((a, b) => a + b, 0) + cab.fronts.length * R.reveal; if (sum > boxH + 1e-9) out.push({ code: 'fronts-tall', vars: { sum: r6(sum), max: r6(boxH) } }); }
  }
  return out;
}

In detail

Method
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.

Every policy that takes this approach