Prices a cabinet job from its sheets, edge banding, frame stock, hardware, countertops, labor and machine time, with markup and tax.
How the precision is kept
Quantities come from the cut list and the nest in 64-bit numbers. Money is held as 64-bit numbers, not integer cents; each row amount, some scaled rates, and the subtotal, markup, tax and total are rounded to 2 decimals by r2 (core.js: Math.round(v × 100) ÷ 100). The default rates are placeholders, as the file's header says, and fixed allowances are built in: 15 % on sheet area when no nest ran, 10 % on frame stock, and the banding waste rate.
Open item. Do not split a rate line on commas when a semicolon or tab separates its cells, so a rate typed with a decimal comma is not cut at the comma.
Calculations under this policy
10 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 amount is computed from the unrounded quantity and then rounded to 2 decimals. The quantity kept on the row is rounded to 3 decimals, so quantity × rate recomputed from the row can differ from the amount in the last cent.
The price of a sheet of the job's size from the price of the stock sheet.
rate = round(base × (w × h × k²) ÷ (2440 × 1220), 2 decimals), k = 25.4 for an inch job
The area ratio is exact products and one division. The scaled price is rounded to cents before it is multiplied by the number of sheets.
addons/cabinetcraft/lib/estimate.js
lines 131 to 136
CadCraft 0.8.2
const sheetRate = (name, sh) => {
const id = build.materialNames ? build.materialNames.get(name) : null; // the library id whose written name this is (a typed name has none)
const known = id && id in R.sheet;
const area = sh.w * sh.h * mmK * mmK;
return { rate: r2((known ? R.sheet[id] : R.sheetOther) * (area / SHEET_AREA)), path: known ? 'sheet.' + id : 'sheetOther' };
};
Number of sheets
64-bit
How many sheets of a material the job needs.
nested: sheets used; else n = max(1, ⌈cut area × 1.15 ÷ (w × h)⌉)
When a nest ran the count is its number of sheets. Otherwise the cut area is raised by a fixed 15 % and divided by the sheet area, then rounded up; the quotient is not snapped before Math.ceil, so a value a rounding error above a whole number counts one more sheet.
addons/cabinetcraft/lib/estimate.js
lines 139 to 145
CadCraft 0.8.2
const sheetsFor = (g) => {
const m = nestJob && nestJob.mats.find((x) => x.material === g.material && x.thickness === g.thickness);
if (m) return { n: m.result.sheets.length, sh: m.sheet, nested: true };
const sh = sheetOf(g.material, g.thickness);
const area = g.cutArea * (1 + 0.15), n = Math.max(1, Math.ceil(area / (sh.w * sh.h)));
return { n, sh, nested: false };
};
Edge banding length priced
64-bit
The meters of each edge banding, with waste, and its rate.
meters = length ÷ 1000 × k × (1 + waste), k = 25.4 for an inch job
The banding length from the cut list is already rounded to 6 decimals there. The conversion divides by 1000 and multiplies by 25.4 for inches, both exact factors, and the waste rate defaults to 0.1.
addons/cabinetcraft/lib/estimate.js
lines 151 to 156
CadCraft 0.8.2
for (const b of cl.banding) {
const id = (BANDS.find((x) => (build.bandNames && build.bandNames.get(x.id) === b.code)) || {}).id;
const perM = id && id in R.bandPerM ? R.bandPerM[id] : R.bandOther;
const path = id && id in R.bandPerM ? 'bandPerM.' + id : 'bandOther';
add('band:' + b.code + b.thickness, 'est.band', b.code + ' ' + b.thickness, (b.length / 1000 * mmK) * (1 + R.bandWaste), 'est.u.m', perM, path);
}
Frame stock priced
64-bit
The price per meter of face-frame stock scaled from a 38 × 19 mm board, and the meters needed.
rate = base × (width ÷ 38) × (thickness ÷ 19), in mm; meters = length ÷ 1000 × k × 1.1
The rate scales with the cross-section and is rounded to cents before it is multiplied by the length. A fixed 10 % is added to the length.
The per-meter rate scales with the width against 600 mm and is rounded to cents first. A corner countertop arrives with length = width = √area, so its amount is proportional to its area.
addons/cabinetcraft/lib/estimate.js
line 163
CadCraft 0.8.2
for (const c of build.counters) add('counter:' + c.no, 'est.counter', c.no, c.length / 1000 * mmK, 'est.u.m', r2(R.counterPerM * (c.width * mmK / 600)), 'counterPerM');
Labor and machine hours
64-bit
The labor hours from the count of each cabinet kind, and the machine hours from the number of sheets.
Each step is rounded to 2 decimals, so the total is the sum of three already rounded figures. The amounts are binary numbers near cents, not integers, and are added in row order. The cost per cabinet is the total divided by the count, rounded to 2 decimals.
Turns one cell of an imported rate table into a number.
A leading $ is removed and the first comma is replaced by a point before Number(). A value with two separators such as 1,234.50 becomes not-a-number and is listed as ignored. Only values from 0 to 1,000,000 are accepted.
addons/cabinetcraft/lib/estimate.js
lines 85 to 89
CadCraft 0.8.2
const take = (path, v) => {
if (!known.has(path)) { ignored.push({ key: path, reason: 'unknown' }); return; }
if (path === 'currency') { if (typeof v === 'string' && /^[A-Za-z]{3}$/.test(v.trim())) values[path] = v.trim().toUpperCase(); else ignored.push({ key: path, reason: 'bad' }); return; }
const n = typeof v === 'number' ? v : Number(String(v).trim().replace(/^\$/, '').replace(',', '.'));
if (ok(n)) values[path] = n; else ignored.push({ key: path, reason: 'bad' });
Rate table lines
64-bit
Splits each line of a text rate table into a path and a value.
A line is split on every comma, semicolon and tab, and only the first two cells are used. A value typed with a decimal comma is therefore cut at the comma: labour.perHour;45,5 is read as 45.
addons/cabinetcraft/lib/estimate.js
lines 98 to 104
CadCraft 0.8.2
for (const line of src.split(/\r?\n/).slice(0, 2000)) {
const l = line.trim();
if (!l || l.startsWith('#')) continue;
const cells = l.split(/[,;\t]/).map((c) => c.trim().replace(/^"|"$/g, ''));
if (cells.length < 2) continue;
if (/^(path|rate|key|name)$/i.test(cells[0]) && !known.has(cells[0])) continue;
take(cells[0], cells[1]);
In detail
Method
amount = quantity × rate per row; sheet price scaled by sheet area ÷ (2440 × 1220); banding and counters priced per meter; labor hours per cabinet kind; machine time per sheet; markup on the subtotal; tax on subtotal plus markup.
Number types
64-bit numbers. The stock sheet area 2440 × 1220 and the factor 25.4 are exact.
Limits
Math.round(v × 100) ÷ 100 works on the binary value, so an amount that is a half cent in decimal can round down (1.005 gives 1). Sheet and frame rates are rounded to cents before they are multiplied by the quantity.
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.