Estimates what a part costs to machine or print from its volume, bounding box, material price and shop rates.
How the precision is kept
A first-order formula on 64-bit numbers: stock mass times price, removal volume over a removal rate, time per hole, handling and a shared setup. The file's header calls the default rates placeholders, not a quote. Amounts are kept unrounded while they are added and are rounded to two decimals only when returned.
Calculations under this policy
5 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 factor 1e-9 converts mm³ × kg/m³ to kg. The removal volume is a difference of the stock and part volumes; the part's volume comes from the build's statistics. Amounts in the rows are unrounded here; the values under the key basis are rounded copies for display.
printed volume = volume × fill; filament = printed volume × density × 1e-9 × price per kg; hours = printed volume ÷ volume per hour
Walls plus infill are taken as a fixed share of the solid volume (0.35 by default), which the notes returned with the estimate say. The arithmetic is three products and a quotient.
The cost of each part at the given quantity, the share of each row, and the rounded figures returned.
total = unit + setup ÷ quantity; share = row ÷ total; returned amounts = round to 0.01
The total and the shares are computed from unrounded amounts. Then each row, the unit cost, the setup and the total are rounded to two decimals independently, so the rounded rows need not add up to the rounded total. The part's mass is returned rounded to 1e-5 kg.
cost.js
lines 113 to 120
PartCraft 0.8.1
const total = unit + job / qty;
const shares = {};
for (const r of rows) shares[r.id] = total > 0 ? r.amount / total : 0;
shares.setup = total > 0 ? (job / qty) / total : 0;
rows.push({ id: 'setup', what: 'Setup, shared', amount: job / qty, basis: { job, qty } });
for (const r of rows) r.amount = r2(r.amount);
notes.push('The rates are placeholders for a typical small shop, not a quote: set your own (they are saved with the part).');
return { process, qty, currency: rates.currency, material: matId, partKg: Math.round(partKg * 1e5) / 1e5, volume_mm3: r2(vol), rows, unit: r2(unit), job: r2(job), total: r2(total), perPartAtQty: r2(total), shares, drivers, notes, rates };
Rounding to two decimals
64-bit
Rounds an amount or a listed quantity to hundredths.
r2(v) = round(v × 100) ÷ 100
The product v × 100 is itself rounded in binary before Math.round sees it, so a value that is a half cent in decimal can land on either side. The result is the 64-bit number nearest the two-decimal value.
cost.js
line 31
PartCraft 0.8.1
const r2 = (v) => Math.round(v * 100) / 100;
Number of holes
Exact
Counts the holes the part has, for the time per hole.
hole features + circles in cut sketches, each × (pattern count − 1) more for patterns
Integer counting only. A pattern whose seed is another pattern adds nothing, so holes multiplied by nested patterns are undercounted.
cost.js
lines 38 to 58
PartCraft 0.8.1
/** Holes the part has: hole features (a pattern of them counts each copy) and circles in the sketches of cut extrusions. */
export function holeCount(part) {
const byId = new Map(part.features.map((f) => [f.id, f]));
let n = 0;
const holes = new Map();
for (const f of part.features) {
if (f.suppressed) continue;
if (f.type === 'hole') holes.set(f.id, 1);
else if (f.type === 'extrude' && f.op === 'cut') {
const sk = byId.get(f.sketch)?.sketch;
if (sk) holes.set(f.id, sk.entities.filter((e) => e.type === 'circle' && !e.construction).length);
}
}
const patterned = new Set();
for (const f of part.features) {
if (f.suppressed || (f.type !== 'lpattern' && f.type !== 'cpattern')) continue;
for (const id of f.seeds || []) if (holes.has(id)) { n += holes.get(id) * (Math.max(1, f.count) - 1); }
}
for (const v of holes.values()) n += v;
return n;
}
In detail
Method
CNC: stock = bounding box + allowance on every face, bought by mass; cutting time = (stock volume − part volume) ÷ removal rate × finishing factor. Printed: filament = volume × fill share by mass; time = printed volume ÷ volume rate. Total per part = unit cost + setup ÷ quantity.
Number types
64-bit numbers; money as decimal 64-bit values, not integer cents.
Tolerances
Returned amounts rounded to 0.01; part mass to 0.00001 kg; listed volumes and sizes to 0.01.
Limits
Each row and the total are rounded separately, so the rounded rows can differ from the rounded total by a cent or more. Rounding uses Math.round(v × 100) ÷ 100 on binary values, so an amount exactly on a half cent can round either way.
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.