Resources Numerical accuracy Calculation policy

Cabinet plan, elevation, section and 3D boxes

Builds the drawing geometry of a cabinet job: each cabinet in plan, front view and section, the dimensioned shop drawings, and the boxes of the 3D view.

How the precision is kept

The cabinet geometry in draw.js and solid.js is products, sums and differences of the cabinet sizes and rule values in 64-bit numbers, with no rounding. drawings.js places cells in the room through toWorld (footprint.js), which rounds to 6 decimals, and writes SVG coordinates rounded to 6 decimals. Dimension text is rounded for reading: 1 decimal in millimeters and 3 in inches (fmtLen, core.js); the single-cabinet plan label uses 2 decimals.

Open item. Show inch dimensions with 4 decimals or as fractions, so sizes in sixteenths of an inch are not rounded in the labels.

Calculations under this policy

13 calculations in 3 source files. 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/draw.js CadCraft 0.8.2 64-bit 5 calculations Reviewed

Tests None found by the review

One cabinet in plan

64-bit

The cabinet seen from above in its own frame, with its fronts, number and width label.

W = w × k; D = d × k; label = round(w × 100) ÷ 100

Sizes are multiplied by k, drawing units per rule unit. The width label is rounded to 2 decimals, so 23.0625 is written 23.06. Text sizes use k ÷ 25.4 for inch rules.

  • addons/cabinetcraft/lib/draw.js
  • lines 25 to 39
  • CadCraft 0.8.2
export function planEnts(cab, R, k = 1) {
  const W = cab.w * k, D = cab.d * k, F = R.frontT * k;
  const out = [rect(0, 0, W, D)];
  // fronts across the cabinet's face (doors or drawers: the same strip seen from above), with the reveals
  for (const f of frontsOf(cab, R).filter((g, i, a) => a.findIndex((h) => h.x === g.x) === i)) out.push(rect(f.x * k, D, f.w * k, F));
  if (cab.type === 'wall') out.push(ln(0, 0, W, D), ln(0, D, W, 0));
  if (cab.type === 'tall') out.push(ln(0, 0, W, D));
  // a wall cabinet hangs over a base one in plan: its words sit near the wall, a base cabinet's near the front, so they don't meet
  const mmK = k * (R.unit === 'in' ? 1 / 25.4 : 1);   // drawing units per mm
  const h = Math.min(D * 0.16, W * 0.12, 60 * mmK), wallish = cab.type === 'wall';
  const ty = wallish ? D * 0.5 : Math.max(D * 0.62, Math.min(D - h * 0.8, 400 * mmK));
  out.push({ type: 'text', x: W / 2, y: ty, h, rot: 0, s: cab.no, ha: 'center', va: 'middle' });
  out.push({ type: 'text', x: W / 2, y: ty - h * 1.5, h: h * 0.7, rot: 0, s: fmt(cab.w), ha: 'center', va: 'middle' });
  return out;
}

Front view of a run

64-bit

Each cabinet's box, plinth and fronts along the run, x along the wall and y up from the floor.

X = x × k; front at (X + f.x × k, base + f.y × k)

Every coordinate is a rule value times k plus an offset; nothing is rounded. The default text height is 8 % of the narrowest cabinet.

  • addons/cabinetcraft/lib/draw.js
  • lines 41 to 69
  • CadCraft 0.8.2
export function elevEnts(items, k = 1, { textH = null } = {}) {
  const out = [];
  let th = textH;
  if (!th) { const ws = items.map((i) => i.cab.w); th = (ws.length ? Math.min(...ws) : 600) * 0.08; }
  for (const { cab, R, x } of items) {
    const X = x * k, base = cab.elev * k, { kick, boxH } = boxOf(cab, R);
    const W = cab.w * k;
    // the box (behind the fronts) and the plinth, set back (drawn as its top line and its face)
    out.push(rect(X, base + kick * k, W, boxH * k));
    if (kick > 0) {
      if (R.kick === 'notch') out.push(ln(X, base, X, base + kick * k), ln(X + W, base, X + W, base + kick * k), ln(X, base, X + W, base));
      else out.push(rect(X, base, W, kick * k));
    }
    for (const f of frontsOf(cab, R)) {
      const fx = X + f.x * k, fy = base + f.y * k, fw = f.w * k, fh = f.h * k;
      out.push(rect(fx, fy, fw, fh));
      if (f.kind === 'door') {
        // the swing lines meet at the hinge side's middle
        const hx = f.hinge === 'right' ? fx + fw : fx, ox = f.hinge === 'right' ? fx : fx + fw;
        out.push(ln(ox, fy, hx, fy + fh / 2), ln(hx, fy + fh / 2, ox, fy + fh));
      } else {
        const pw = Math.min(fw * 0.3, 160 * k * (R.unit === 'in' ? 1 / 25.4 : 1)), py = fy + fh * 0.7;
        out.push(ln(fx + (fw - pw) / 2, py, fx + (fw + pw) / 2, py));
      }
    }
    out.push({ type: 'text', x: X + W / 2, y: base - th * 1.6, h: th, rot: 0, s: cab.no, ha: 'center', va: 'middle' });
  }
  return out;
}

Frame of a run from two wall points

64-bit

The origin, angle and length of a run picked by two points on a wall and a point in the room.

len = √(Δx² + Δy²); left = (room − a) × u; ang = atan2(u_y, uₓ)

The length is one Math.hypot and the side is the sign of one cross product. The frame is returned as an angle, so the direction goes through atan2 here and back through cos and sin in along().

  • addons/cabinetcraft/lib/draw.js
  • lines 71 to 78
  • CadCraft 0.8.2
export function runPlace(a, b, room) {
  const dx = b.x - a.x, dy = b.y - a.y, len = Math.hypot(dx, dy);
  if (!(len > 0)) return null;
  const ux = dx / len, uy = dy / len;
  const left = (room.x - a.x) * -uy + (room.y - a.y) * ux;   // + = the room is on the left of a → b
  if (left >= 0) return { x: a.x, y: a.y, ang: Math.atan2(uy, ux), len };
  return { x: b.x, y: b.y, ang: Math.atan2(-uy, -ux), len };
}

Point in a run's frame

64-bit

The distance along a run and out from the wall of a point.

s = Δx cos a + Δy sin a; off = −Δx sin a + Δy cos a

A rotation by the run's angle with Math.cos and Math.sin of the stored angle; nothing is rounded.

  • addons/cabinetcraft/lib/draw.js
  • lines 80 to 83
  • CadCraft 0.8.2
export function along(run, p) {
  const c = Math.cos(run.ang), s = Math.sin(run.ang), dx = p.x - run.x, dy = p.y - run.y;
  return { s: dx * c + dy * s, off: -dx * s + dy * c };
}

Cabinet in section

64-bit

The side panel, bottom, top or rails, back, shelves, plinth, fronts and drawer boxes of a cabinet cut through its middle.

shelf i of n: y = bottom + (top − bottom) × (i + 1) ÷ (n + 1) − t ÷ 2

Rectangles are built from differences of the rule values; a rectangle's height is Y(y₁) − Y(y₀) after scaling, not the scaled difference. Adjustable shelves are spread evenly; a fixed shelf sits at the middle. Nothing is rounded.

  • addons/cabinetcraft/lib/draw.js
  • lines 85 to 130
  • CadCraft 0.8.2
/** The cabinet in section from the side: x out from the wall, y up from the floor (rule units × k). */
export function sideEnts(cab, R, k = 1) {
  const { kick, boxH, backDepth, panelD } = boxOf(cab, R);
  const out = [];
  const t = R.t, px0 = R.back === 'applied' ? R.backT : 0, px1 = cab.d, top = kick + boxH;
  const X = (v) => v * k, Y = (v) => (cab.elev + v) * k;
  const R_ = (x0, y0, x1, y1) => rect(X(x0), Y(y0), X(x1 - x0), Y(y1) - Y(y0));
  const notch = kick > 0 && R.kick === 'notch';
  // the side panel (the cut face): a notched kick runs to the floor with a corner cut out
  if (notch) out.push({ type: 'poly', closed: true, pts: [[px0, 0], [px1 - R.kickSetback, 0], [px1 - R.kickSetback, kick], [px1, kick], [px1, top], [px0, top]].map(([x, y]) => ({ x: X(x), y: Y(y) })) });
  else out.push(R_(px0, kick, px1, top));
  // bottom, and the top or the two rails (the panels between the sides, seen end-on at the middle)
  out.push(R_(px0, kick, px1, kick + t));
  const hasTop = cab.type === 'wall' || cab.type === 'tall';
  if (hasTop) out.push(R_(px0, top - t, px1, top));
  else out.push(R_(px1 - R.railDepth, top - t, px1, top), R_(px0, top - t, px0 + R.railDepth, top));
  // the back
  if (R.back === 'applied') out.push(R_(0, kick, R.backT, top));
  else if (R.back === 'groove') out.push(R_(R.backSetback, kick + t - R.groove, R.backSetback + R.backT, hasTop ? top - t + R.groove : top - t));
  else out.push(R_(R.backSetback, kick + t, R.backSetback + R.backT, hasTop ? top - t : top - t));
  // the shelves, spread evenly between the bottom and the top (or the rails)
  const inside = cabinetParts(cab, R).filter((p) => p.role === 'shelf' || p.role === 'shelf-fixed');
  const innerBottom = kick + t, innerTop = top - t;
  const sx0 = px0 + backDepth + (R.back === 'applied' ? 0 : 0), sx1 = px1 - R.shelfFront;
  for (const p of inside) {
    const n = p.qty;
    for (let i = 0; i < n; i++) {
      const y = p.role === 'shelf-fixed' ? (innerBottom + innerTop) / 2 - t / 2 : innerBottom + (innerTop - innerBottom) * (i + 1) / (n + 1) - t / 2;
      out.push(R_(sx0, y, p.role === 'shelf-fixed' ? px1 : sx1, y + t));
    }
  }
  // the plinth, set back
  if (kick > 0 && R.plinth) { const x = notch ? px1 - R.kickSetback : px1 - R.kickSetback - t; out.push(R_(x, 0, x + t, kick)); }
  // the fronts, standing in front of the box; drawers also show their box and slide
  const L = cab.type === 'drawers' ? slideFor(cab, R) : 0;
  for (const f of frontsOf(cab, R)) {
    out.push(R_(px1, f.y, px1 + R.frontT, f.y + f.h));
    if (f.kind === 'drawer' && L > 0) {
      const bh = f.h - R.drawerHClear, y0 = f.y + (f.h - bh) / 2;
      if (bh > 0) out.push(R_(px1 - L, y0, px1, y0 + bh));
    }
  }
  const th = Math.max(6, cab.d * 0.06);
  out.push({ type: 'text', x: X(cab.d / 2), y: Y(0) - th * 1.6 * k, h: th * k, rot: 0, s: cab.no, ha: 'center', va: 'middle' });
  return out;
}
addons/cabinetcraft/lib/drawings.js CadCraft 0.8.2 64-bit 6 calculations Reviewed

Tests None found by the review

Drawing extent and SVG coordinates

64-bit

The size of a drawing and the coordinates written into its SVG.

W = x₁ − x₀; H = y₁ − y₀; each coordinate written as round(v × 1e6) ÷ 1e6

The extent is the minimum and maximum over all entity points; a text's extent is estimated as 0.35 × height per character. Every coordinate in the SVG is rounded to 6 decimals. The returned w and h are not rounded.

  • addons/cabinetcraft/lib/drawings.js
  • lines 37 to 40
  • CadCraft 0.8.2
const W = x1 - x0, H = y1 - y0, p = Math.max(W, H) * pad;
const vx = x0 - p, vy = -(y1 + p), vw = W + 2 * p, vh = H + 2 * p;
const sw = Math.max(vw, vh) / 900;
const pts = (a) => a.map((q) => r6(q.x) + ',' + r6(-q.y)).join(' ');

Cabinets placed on the room plan

64-bit

The rectangle of each cabinet, filler and end panel in room coordinates.

corner = toWorld(frame, s + x, o)

Each corner goes through toWorld in footprint.js: the wall's 6-decimal direction times the distance, rounded to 6 decimals.

  • addons/cabinetcraft/lib/drawings.js
  • lines 82 to 83
  • CadCraft 0.8.2
const cq = (L, c, x0, x1, o0, o1, o = {}) => { const f = cellFrame(L, c); return quad(f.frame, f.s + x0, f.s + x1, o0, o1, o); };
const cp = (L, c, list) => { const f = cellFrame(L, c); return list.map(([s, o]) => toWorld(f.frame, f.s + s, o)); };

Room dimensions on the plan

64-bit

The overall width and depth dimensions of the room.

label = fmtLen(W, unit)

The dimension lines use the room's stored width and depth. The text comes from fmtLen in core.js: toFixed(1) in millimeters and toFixed(3) in inches, with trailing zeros removed.

  • addons/cabinetcraft/lib/drawings.js
  • lines 151 to 153
  • CadCraft 0.8.2
const unitLabel = u === 'in' ? 'in' : 'mm';
ents.push(...dimLine(0, 0, W, 0, fmtLen(W, u) + ' ' + unitLabel, th, -th * 4));
ents.push(...dimLine(0, 0, 0, D, fmtLen(D, u) + ' ' + unitLabel, th, -th * 4, true));

Foreshortened width on a curved wall

64-bit

The drawn width of a cabinet that is turned on a curved wall, in the elevation.

kₓ = max(0.05, (ext₁ − ext₀) ÷ w); drawn width = w × kₓ

The factor is the cell's projected extent on the chord divided by its width, never below 0.05. The dimension above the cabinet still shows the true width c.w through fmtLen.

  • addons/cabinetcraft/lib/drawings.js
  • lines 184 to 185
  • CadCraft 0.8.2
const kx = c.pf && c.ext ? Math.max(0.05, (c.ext[1] - c.ext[0]) / c.w) : 1;   // a cabinet turned on a curved wall is seen foreshortened
const cw = c.w * kx;

Overall run length and height in the elevation

64-bit

The total length of a run and the height to the top of its tallest cabinet.

length = b − a; top = max(elevation + height)

a and b are the smallest start and largest end of the rows, or of the cell extents on a curved wall. The labels are b − a and top through fmtLen: 1 decimal in millimeters, 3 in inches.

  • addons/cabinetcraft/lib/drawings.js
  • lines 219 to 222
  • CadCraft 0.8.2
const used = [L.rows.base, L.rows.upper].filter((r) => r.cells.length).map((r) => [r.start, r.end]);
const a = arcRun && exts.length ? Math.min(...exts.map((e) => e[0])) : Math.min(...used.map((x) => x[0])), b = arcRun && exts.length ? Math.max(...exts.map((e) => e[1])) : Math.max(...used.map((x) => x[1]));
ents.push(...dimLine(a, 0, b, 0, fmtLen(b - a, u) + (u === 'in' ? ' in' : ' mm'), th, -th * 4.5));
ents.push(...dimLine(a, 0, a, top, fmtLen(top, u), th, -th * 2, true));

Section of one cabinet with dimensions

64-bit

The section view of one cabinet with its face frame, fronts, overall depth and height.

depth shown = d + frame thickness + front thickness

The depth dimension is a sum of three stored sizes and the height is the cabinet's own. Fronts drawn by draw.js are filtered out with a 1e-6 comparison against the cabinet depth and redrawn at the face-frame position.

  • addons/cabinetcraft/lib/drawings.js
  • lines 228 to 246
  • CadCraft 0.8.2
/** One cabinet cut through its middle, as entities. */
export function sectionEnts(build, no) {
  const c = build.cells.find((x) => x.no === no && x.kind === 'cab');
  if (!c) return [];
  const { R, F, project } = build;
  const u = project.unit;
  const ff = c.construction === 'faceframe';
  const cab = c.cab;
  const mine = (e) => !(e.type === 'poly' && e.pts.every((p) => p.x >= cab.d - 1e-6) && e.pts.some((p) => p.x > cab.d + 1e-6));
  let ents = sideEnts(cab, R, 1).filter(mine);
  const frontX = cab.d + (ff ? F.t : 0);
  if (ff) { const kick = R.kick === 'none' || cab.type === 'wall' ? 0 : R.kickH; ents.push(rect(cab.d, cab.elev + kick, F.t, cab.h - kick, { fill: '#d9c9a6' })); }
  for (const f of c.fronts) if (f.kind !== 'blind') ents.push(rect(frontX, cab.elev + f.y, R.frontT, f.h));
  const th = Math.max(cab.d, cab.h) * 0.03;
  ents.push(...dimLine(0, cab.elev, frontX + R.frontT, cab.elev, fmtLen(frontX + R.frontT, u), th, -th * 2.4));
  ents.push(...dimLine(0, cab.elev, 0, cab.elev + cab.h, fmtLen(cab.h, u), th, -th * 2.4, true));
  if (ff) ents.push(txt(cab.d + F.t / 2, cab.elev + cab.h + th * 1.2, th * 0.8, T('draw.frame')));
  return ents;
}
addons/cabinetcraft/lib/solid.js CadCraft 0.8.2 64-bit 2 calculations Reviewed

Tests None found by the review

Cabinet panels as boxes

64-bit

The carcass panels, back, shelves, plinth and fronts of a cabinet as boxes in its own frame.

shelf i of n: z = bottom + (top − bottom) × (i + 1) ÷ (n + 1) − t ÷ 2

Each box corner is a sum or difference of the cabinet sizes and rule values; nothing is rounded. A box is dropped when any side is 1e-9 or thinner.

  • addons/cabinetcraft/lib/solid.js
  • lines 13 to 38
  • CadCraft 0.8.2
export function boxesOf(cabIn, R) {
  const cab = cleanCabinet(cabIn, R);
  if (!cab) return [];
  const { kick, boxH, backDepth } = boxOf(cab, R);
  const t = R.t, px0 = R.back === 'applied' ? R.backT : 0, px1 = cab.d, top = kick + boxH, W = cab.w;
  const hasTop = cab.type === 'wall' || cab.type === 'tall';
  const out = [];
  const box = (role, x0, y0, z0, x1, y1, z1) => { if (x1 > x0 + 1e-9 && y1 > y0 + 1e-9 && z1 > z0 + 1e-9) out.push({ role, x0, y0, z0, x1, y1, z1 }); };
  const notch = kick > 0 && R.kick === 'notch';
  box('side', 0, px0, notch ? 0 : kick, t, px1, top);
  box('side', W - t, px0, notch ? 0 : kick, W, px1, top);
  box('bottom', t, px0, kick, W - t, px1, kick + t);
  if (hasTop) box('top', t, px0, top - t, W - t, px1, top);
  else { box('rail', t, px1 - R.railDepth, top - t, W - t, px1, top); box('rail', t, px0, top - t, W - t, px0 + R.railDepth, top); }
  if (R.back === 'applied') box('back', 0, 0, kick, W, R.backT, top);
  else box('back', t, R.backSetback, kick + t, W - t, R.backSetback + R.backT, hasTop ? top - t : top - t);
  const shelves = cabinetParts(cab, R).filter((p) => p.role === 'shelf' || p.role === 'shelf-fixed');
  const innerBottom = kick + t, innerTop = top - t;
  for (const p of shelves) for (let i = 0; i < p.qty; i++) {
    const z = p.role === 'shelf-fixed' ? (innerBottom + innerTop) / 2 - t / 2 : innerBottom + (innerTop - innerBottom) * (i + 1) / (p.qty + 1) - t / 2;
    box(p.role, t + (p.role === 'shelf' ? R.shelfClear : 0), px0 + backDepth, z, W - t - (p.role === 'shelf' ? R.shelfClear : 0), p.role === 'shelf' ? px1 - R.shelfFront : px1, z + t);
  }
  if (kick > 0 && R.plinth) box('plinth', notch ? t : 0, px1 - R.kickSetback - t, 0, notch ? W - t : W, px1 - R.kickSetback, kick);
  for (const f of frontsOf(cab, R)) box(f.kind === 'door' ? 'door' : 'drawer-front', f.x, px1, f.y, f.x + f.w, px1 + R.frontT, f.y + f.h);
  return out;
}

Boxes placed in the drawing

64-bit

The faces of each box in drawing coordinates, for the 3D tab.

X = x + lx cos r − ly sin r; Y = y + lx sin r + ly cos r; Z = (z + elev) × k

A scale by k and the mirror factors, then one rotation with Math.cos and Math.sin of the entity's angle, computed once. Nothing is rounded. A mirrored placement reverses each face's point order.

  • addons/cabinetcraft/lib/solid.js
  • lines 56 to 66
  • CadCraft 0.8.2
export function solidPolys(cab, R, place) {
  const { x = 0, y = 0, rot = 0, k = 1, sx = 1, sy = 1 } = place || {};
  const c = Math.cos(rot), s = Math.sin(rot), flip = sx * sy < 0, elev = (cleanCabinet(cab, R)?.elev) || 0;
  const P = ([px, py, pz]) => {
    const lx = px * k * sx, ly = py * k * sy;
    return [x + lx * c - ly * s, y + lx * s + ly * c, (pz + elev) * k];
  };
  const polys = [];
  for (const b of boxesOf(cab, R)) for (const q of quads(b)) { const poly = q.map(P); polys.push(flip ? poly.reverse() : poly); }
  return polys;
}

In detail

Method
Rectangles and lines from the cabinet's width, depth, height, panel thickness and the construction rules; shelves spread evenly between the inside bottom and top; a plane rotation and scale for placing boxes in the drawing.
Number types
64-bit numbers; entities are plain objects with x and y fields.
Tolerances
A box thinner than 1e-9 in any direction is dropped (solid.js). The section view filters fronts with 1e-6.
Limits
Inch dimension text has 3 decimals, so a size ending in a sixteenth (0.0625) is shown rounded. The 3D sheet (isoSvg) is a picture and is not listed.

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