Resources Numerical accuracy Calculation policy

CabinetCraft parts, fronts and face frames

Works out the cut-list parts of a cabinet job: face-frame stiles and rails, overlay doors and drawer fronts, drawer boxes, blind panels, fillers and end panels.

How the precision is kept

Sizes are 64-bit numbers in the job's unit, millimeters or inches. Every part length and width and every front position is rounded to 6 decimals when it is stored. Default sizes are given in millimeters; for an inch job they are divided by 25.4 and rounded to the nearest 1/16 inch.

Open item. Keep part sizes unrounded in the job and round only where a list or file is written.

Calculations under this policy

11 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/build.js CadCraft 0.8.2 64-bit 11 calculations Reviewed

Tests lib/cabinetcraft/test/t-build.mjs

Face-frame default sizes

64-bit

The stile and rail width, overlay and door gap a job uses when it sets none.

stile = rail = 38 mm; overlay = 12 mm; gap = 3 mm; inch job: round(mm ÷ 25.4 × 16) ÷ 16

conv (core.js) leaves millimeter values as they are and, for an inch job, divides by 25.4 and rounds to the nearest 1/16 inch. Sizes set in the job replace the defaults unchanged.

  • addons/cabinetcraft/lib/build.js
  • lines 30 to 36
  • CadCraft 0.8.2
export function frameRules(project, say = (k) => k) {
  const u = project.unit;
  const st = frameFromPicks(u, project.picks, { say, custom: project.custom });
  const d = { stile: conv(38, u), rail: conv(38, u), overlay: conv(12, u), gap: conv(3, u) };
  const o = project.ff || {};
  return { stile: o.stile ?? d.stile, rail: o.rail ?? d.rail, overlay: o.overlay ?? d.overlay, gap: o.gap ?? d.gap, t: o.t ?? st.t, mat: st.mat };
}

Carcass depth behind a face frame

64-bit

The depth the carcass is built with when a face frame takes part of the cabinet's depth.

d = max(1, d − frame thickness)

One 64-bit subtraction. The result is floored at 1 unit of the job (1 mm or 1 in), an absolute floor.

  • addons/cabinetcraft/lib/build.js
  • lines 38 to 42
  • CadCraft 0.8.2
const modelCab = (cell, construction, F) => {
  const c = { ...cell.cab, no: cell.no };
  if (construction === 'faceframe') c.d = Math.max(1, c.d - F.t);
  return c;
};

Face-frame openings and overlay fronts

64-bit

The rails, the openings between them and the doors or drawer fronts that cover each opening.

opening h = (box h − (2 + mids) × rail) ÷ (mids + 1); front h = opening h + lap below + lap above; pair door w = (opening w + 2 × overlay − gap) ÷ 2

The running height adds unrounded opening heights and rails; the openings' y, width and height and every front's position and size are rounded to 6 decimals. Front sizes are computed from the opening's already rounded y, width and height and rounded again. Two stacked doors are used when box height less the reveal exceeds the maximum door height.

  • addons/cabinetcraft/lib/build.js
  • lines 44 to 80
  • CadCraft 0.8.2
/** The frame, its openings and the overlay fronts of a face-frame cabinet (front view: x from the left side, y from the bottom incl. the plinth). */
export function frameLayout(cab, R, F, blind = 0, hinge = 'left') {
  const { kick, boxH, iw } = boxOf(cab, R);
  const blindLeft = blind > 0 && hinge === 'right';
  const x0 = F.stile + (blindLeft ? blind : 0), openW = cab.w - 2 * F.stile - blind;
  const rv = R.reveal;
  const n = cab.type === 'drawers' ? (cab.fronts ? cab.fronts.length : cab.drawers) : (cab.doors === 0 ? 0 : (boxH - rv > R.maxDoorH ? 2 : 1));
  const rails = [];                                  // y of each rail from the plinth top, bottom up
  const stack = cab.type === 'drawers' ? n : n;      // openings stacked
  const mids = Math.max(0, (cab.type === 'drawers' ? n : n) - 1);
  const openH = (boxH - (2 + mids) * F.rail) / Math.max(1, mids + 1);
  rails.push({ y: kick, h: F.rail, kind: 'rail' });
  const openings = [];
  let y = kick + F.rail;
  for (let i = 0; i < Math.max(1, stack); i++) {
    openings.push({ x: x0, y: r6(y), w: r6(openW), h: r6(openH) });
    y += openH;
    if (i < Math.max(1, stack) - 1) { rails.push({ y: r6(y), h: F.rail, kind: 'mid' }); y += F.rail; }
  }
  rails.push({ y: r6(kick + boxH - F.rail), h: F.rail, kind: 'rail' });
  const fronts = [];
  if (n > 0) {
    const edge = F.overlay, inner = (F.rail - F.gap) / 2;
    const pair = cab.type !== 'drawers' && (cab.doors === 2 || (cab.doors === 'auto' && openW + 2 * edge > R.maxDoorW));
    openings.forEach((o, i) => {
      const bottom = i === 0 ? edge : inner, top = i === openings.length - 1 ? edge : inner;
      const fy = o.y - bottom, fh = o.h + bottom + top;
      if (cab.type === 'drawers') fronts.push({ kind: 'drawer', x: r6(o.x - edge), y: r6(fy), w: r6(o.w + 2 * edge), h: r6(fh), opening: o });
      else if (pair) {
        const dw = (o.w + 2 * edge - F.gap) / 2;
        fronts.push({ kind: 'door', x: r6(o.x - edge), y: r6(fy), w: r6(dw), h: r6(fh), hinge: 'left', opening: o });
        fronts.push({ kind: 'door', x: r6(o.x - edge + dw + F.gap), y: r6(fy), w: r6(dw), h: r6(fh), hinge: 'right', opening: o });
      } else fronts.push({ kind: 'door', x: r6(o.x - edge), y: r6(fy), w: r6(o.w + 2 * edge), h: r6(fh), hinge, opening: o });
    });
  }
  return { stile: F.stile, rails, openings, fronts, boxH, kick, iw };
}

Door and blind panel of a blind corner

64-bit

The door over the open part of a blind corner cabinet and the panel over the blind part.

door w = (w − blind) − reveal; panel w = blind − reveal; h = box h − reveal

Subtractions of the reveal and half the reveal in 64-bit; each position and size is rounded to 6 decimals.

  • addons/cabinetcraft/lib/build.js
  • lines 82 to 91
  • CadCraft 0.8.2
/** A blind corner's fronts (frameless): one door over the open part, a blind panel over the blind part. */
function blindFronts(cab, R, blind, hinge) {
  const { kick, boxH } = boxOf(cab, R);
  const rv = R.reveal, openW = cab.w - blind;
  const blindLeft = hinge === 'right';
  const dx = (blindLeft ? blind : 0) + rv / 2;
  const out = [{ kind: 'door', x: r6(dx), y: r6(kick + rv / 2), w: r6(openW - rv), h: r6(boxH - rv), hinge }];
  out.push({ kind: 'blind', x: r6(blindLeft ? rv / 2 : openW + rv / 2), y: r6(kick + rv / 2), w: r6(blind - rv), h: r6(boxH - rv) });
  return out;
}

Part size stored in the cut list

64-bit

Adds one part with its length, width, thickness and material.

Length and width are rounded to 6 decimals of the job's unit when the part is stored; thickness is kept as given. A part whose length or width is not above 0 is left out.

  • addons/cabinetcraft/lib/build.js
  • lines 93 to 96
  • CadCraft 0.8.2
const addPart = (list, no, role, name, length, width, thickness, material, extra = {}) => {
  if (!(length > 0 && width > 0)) return;
  list.push({ role, name, group: no, length: r6(length), width: r6(width), thickness, material, grain: 'length', qty: 1, band: {}, ...extra });
};

Frame rail length and counts

64-bit

The length of the frame's rails and how many rails and mid rails a cabinet needs.

rail length = w − 2 × stile − blind

One 64-bit expression, rounded to 6 decimals by addPart. The quantities are counts of the layout's rail records.

  • addons/cabinetcraft/lib/build.js
  • lines 145 to 148
  • CadCraft 0.8.2
const railLen = cab.w - 2 * F.stile - blind;
const rails = frame.rails.filter((r) => r.kind === 'rail').length, mids = frame.rails.filter((r) => r.kind === 'mid').length;
addPart(L, cab.no, 'frame-rail', 'part.frame-rail', railLen, F.rail, F.t, F.mat, { qty: rails, ...fb, ...lin });
if (mids) addPart(L, cab.no, 'frame-mid-rail', 'part.frame-mid-rail', railLen, F.rail, F.t, F.mat, { qty: mids, ...fb, ...lin });

Doors and drawer fronts counted by size

Exact

Groups equal fronts into one cut-list line with a quantity.

Fronts are grouped by the text of their rounded height and width, so two fronts count as one size only when both rounded values are identical. The quantity is a count.

  • addons/cabinetcraft/lib/build.js
  • lines 150 to 153
  • CadCraft 0.8.2
const group = (list) => { const m = new Map(); for (const f of list) { const k = f.h + 'x' + f.w; m.set(k, { f, n: (m.get(k)?.n || 0) + 1 }); } return [...m.values()]; };
for (const { f, n } of group(doors)) addPart(L, cab.no, 'door', 'part.door', f.h, f.w, R.frontT, R.frontMat, { qty: n, band: { l1: R.bandFront, l2: R.bandFront, w1: R.bandFront, w2: R.bandFront } });
const dr = fronts.filter((f) => f.kind === 'drawer');
for (const { f, n } of group(dr)) addPart(L, cab.no, 'drawer-front', 'part.drawer-front', f.w, f.h, R.frontT, R.frontMat, { qty: n, band: { l1: R.bandFront, l2: R.bandFront, w1: R.bandFront, w2: R.bandFront } });

Drawer boxes of a face-frame cabinet

64-bit

The sides, ends and bottom of each drawer box behind a face frame.

box h = min(front h − clearance, opening h − 20 mm); end = bw − 2t; bottom = (bw − 2t + 2g) × (slide − 2t + 2g); bw = opening w − 2 × slide gap

The box height is rounded to 6 decimals and used as the key that counts equal boxes. The 20 mm allowance goes through conv, so an inch job uses 13/16 in (20.6375 mm). Boxes lower than the minimum height are left out.

  • addons/cabinetcraft/lib/build.js
  • lines 156 to 163
  • CadCraft 0.8.2
const bw = frame.openings[0].w - 2 * R.slideGap, dt = R.drawerT, g = R.drawerGroove;
const boxes = new Map();
for (const f of dr) { const bh = r6(Math.min(f.h - R.drawerHClear, f.opening.h - conv(20, R.unit))); if (bh >= R.drawerMinH) boxes.set(bh, (boxes.get(bh) || 0) + 1); }
for (const [bh, n] of boxes) {
  addPart(L, cab.no, 'drawer-side', 'part.drawer-side', slide, bh, dt, R.drawerMat, { qty: 2 * n, band: { l1: R.bandDrawer }, meta: { slide } });
  addPart(L, cab.no, 'drawer-end', 'part.drawer-end', bw - 2 * dt, bh, dt, R.drawerMat, { qty: 2 * n, band: { l1: R.bandDrawer } });
  addPart(L, cab.no, 'drawer-bottom', 'part.drawer-bottom', bw - 2 * dt + 2 * g, slide - 2 * dt + 2 * g, R.drawerBottomT, R.drawerBottomMat, { qty: n, grain: 'none' });
}

Dowels of a corner cabinet

64-bit

The number of dowels a corner cabinet's drilling needs.

dowels = dowel holes ÷ 2

A count of holes divided by 2 with no rounding: an odd number of dowel holes would give a quantity ending in .5.

  • addons/cabinetcraft/lib/build.js
  • lines 115 to 116
  • CadCraft 0.8.2
const dow = drill.reduce((q, pl) => q + pl.holes.filter((h) => h.kind === 'dowel').length, 0) / 2;
if (dow > 0) cb.hardware = [...cb.hardware, { key: 'dowel', item: 'hw.dowel', spec: '', qty: dow }];

Curved cabinet outline in the cell's own axes

64-bit

The outline points of a made-to-order curved cabinet, measured from the cell's origin along its two axes.

(x′, y′) = ((q − o) · u, (q − o) · n)

Two dot products of 64-bit differences with the axes u and n of the cell's frame (cell.pf), each rounded to 6 decimals. Without a frame the points are kept as they are.

  • addons/cabinetcraft/lib/build.js
  • line 122
  • CadCraft 0.8.2
const loc = (q) => (f ? [r6((q[0] - f.o.x) * f.u.x + (q[1] - f.o.y) * f.u.y), r6((q[0] - f.o.x) * f.n.x + (q[1] - f.o.y) * f.n.y)] : q);

Closing panel of an angled corner

64-bit

The height of the panel that closes a corner which is not a right angle.

height = cabinet h − plinth height

One subtraction rounded to 6 decimals. The plinth height is taken off only for a cabinet that is not a wall cabinet and when the rules have a plinth.

  • addons/cabinetcraft/lib/build.js
  • lines 211 to 212
  • CadCraft 0.8.2
const kick = host && host.cab.type !== 'wall' && R.kick !== 'none' ? R.kickH : 0;
const height = host ? r6(host.cab.h - kick) : 0;

In detail

Method
Face frame: opening height = (box height − (2 + mid rails) × rail) ÷ openings. Fronts lap the opening by the overlay on outer edges and by (rail − gap) ÷ 2 where two fronts meet on a rail. A pair of doors splits the width less the gap.
Number types
64-bit numbers, stored rounded to 6 decimals (core.js r6: Math.round(v × 1e6) ÷ 1e6).
Limits
Stored sizes carry 6 decimals of the job's unit. Fronts are computed from openings already rounded to 6 decimals. Inch defaults are multiples of 1/16 inch, so they are not the millimeter defaults converted exactly.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach