Resources Numerical accuracy Calculation policy

Quantities handed to TakeoffCraft

Lays the model's walls, openings, roofs, foundations, cabinets and devices as marks on scaled plan sheets inside a PDF, so TakeoffCraft measures every quantity from the marks.

How the precision is kept

No quantity is sent as a number: each one is a mark on a sheet plus the sheet's scale. The mark coordinates are rounded to 0.01 PDF point, so what TakeoffCraft measures is limited by that rounding times the sheet scale. Wall heights are rounded to 0.01 foot and roof pitches to 0.01 degree and then to 4 decimals of rise per 12 before they are sent.

Open item. Write the mark coordinates unrounded, or with at least 6 decimals of a point, so the measured quantity is not limited to 0.01 point times the sheet scale.

Calculations under this policy

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

res-takeoff.js BimCraft 0.8.1 64-bit 8 calculations Reviewed

Tests tools/bimcraft-test/res-test.mjs

Sheet scale as feet per point

Exact

The number of feet in the building that one PDF point stands for on a sheet drawn at 1:ratio.

ft per point = ratio × (25.4 ÷ 72) ÷ 304.8 = ratio ÷ 864

The factor reduces to the whole number 864, so the only rounding is the one division of a whole-number ratio.

  • res-takeoff.js
  • line 31
  • BimCraft 0.8.1
export const ftPerUnit = (ratio) => ratio / 864;   // one point = 25.4 / 72 mm of paper = ratio × that in the building; 304.8 mm a foot

Mark coordinates on the sheet

64-bit

Maps a plan point to the sheet and writes it as the PDF-point coordinates of a takeoff mark.

mark = round(sheet mm × 72 ÷ 25.4 × 100) ÷ 100

Each coordinate is rounded to 0.01 point before it is written. 0.01 point is about 0.0035 mm of paper, which is about 0.17 mm of building on a sheet at 1:48 and more at smaller scales. Every length and area TakeoffCraft measures from the marks carries this rounding.

  • res-takeoff.js
  • line 73
  • BimCraft 0.8.1
const P = (q) => { const s = sh.place.P(q); return [r2(s[0] * PT), r2(s[1] * PT)]; };

Roof pitch as rise per 12

64-bit

Converts a pitch in degrees to the rise per 12 that TakeoffCraft uses for sloped areas and lengths.

rise per 12 = round(12 × tan θ × 10000) ÷ 10000

The rise per 12 is rounded to 4 decimals. The pitch it starts from was already rounded to 2 decimals of a degree for roof faces.

  • res-takeoff.js
  • line 61
  • BimCraft 0.8.1
...(d.type !== 'count' && d.pitch > 0 ? { pitch: Math.round(12 * Math.tan(d.pitch * Math.PI / 180) * 10000) / 10000 } : {}),   // TakeoffCraft: rise per 12

Walls as runs with a height

64-bit

Sends each wall as a run along its center line, grouped by wall type and height.

height (ft) = round(height ÷ 304.8 × 100) ÷ 100

The wall height is rounded to 0.01 ft, which is 3.048 mm, and that rounded height is what the wall area is worked out from in TakeoffCraft. The run is the wall's two end points, unrounded until they become marks.

  • res-takeoff.js
  • lines 132 to 135
  • BimCraft 0.8.1
const hFt = Math.round(w.height / FT * 100) / 100;
const ext = /^ext-2x6/.test(w.typeKey) ? 'wall.ext.2x6' : /^int-2x4/.test(w.typeKey) ? 'wall.int.2x4.' + nearest(hFt, [8, 9, 10, 12]) : null;
const k = def('wall:' + w.typeKey + ':' + hFt, { name: t('res.to.walls', { type: w.typeName || w.typeKey, h: d.fmtLen(w.height) }), type: 'linear', heightFt: hFt, pack: ext });
level(w.storey).runs.push({ item: k, pts: [w.a, w.b] });

Door and window positions

64-bit

Places a count mark at the middle of each door and window along its wall.

at = a + (b − a) ÷ L × (offset + width ÷ 2)

One 64-bit interpolation along the wall; a zero wall length is replaced by 1. The position only places the count mark.

  • res-takeoff.js
  • lines 142 to 143
  • BimCraft 0.8.1
const L = Math.hypot(w.b[0] - w.a[0], w.b[1] - w.a[1]) || 1, s = o.offset + o.width / 2;
const at = [w.a[0] + (w.b[0] - w.a[0]) / L * s, w.a[1] + (w.b[1] - w.a[1]) / L * s];

Roof faces and roof lines

64-bit

Sends each roof face as a plan area with its pitch, and the ridges, hips, valleys, eaves and rakes as runs.

face pitch = round(pitch × 100) ÷ 100; line pitch = max over the faces

Each face's pitch is rounded to 0.01 degree. Hips, valleys and rakes are all given the largest pitch among the roof's faces, so on a roof with faces of different pitches their sloped lengths are worked out from the steepest one.

  • res-takeoff.js
  • lines 154 to 163
  • BimCraft 0.8.1
for (const f of P.faces) { const p = Math.round(f.pitch * 100) / 100; lv.areas.push({ item: def('roof:' + p, { name: t('res.to.roof', { pitch: p.toFixed(1) }), type: 'area', pitch: p, pack: 'roof.shingles' }), pts: f.poly }); }
const pitch = Math.max(...P.faces.map((f) => f.pitch));
for (const l of P.lines) {
  const kind = l.kind === 'ridge' ? 'ridge' : l.kind === 'hip' ? 'hip' : 'valley';
  const k = def('roofline:' + kind + (kind === 'ridge' ? '' : ':' + pitch.toFixed(2)), { name: t('res.to.roofline.' + kind), type: 'linear', pack: kind === 'valley' ? 'roof.valley' : 'roof.ridge', ...(kind === 'ridge' ? {} : { slope: 'hip', pitch }) });
  lv.runs.push({ item: k, pts: [[l.a[0], l.a[1]], [l.b[0], l.b[1]]] });
}
for (const fa of P.fascia) {
  const k = fa.kind === 'eave' ? def('roofline:eave', { name: t('res.to.roofline.eave'), type: 'linear', pack: 'roof.eave' }) : def('roofline:rake:' + pitch.toFixed(2), { name: t('res.to.roofline.rake'), type: 'linear', slope: 'rake', pitch });
  lv.runs.push({ item: k, pts: [[fa.a[0], fa.a[1]], [fa.b[0], fa.b[1]]] });

Footings, stem walls and slabs

64-bit

Sends slabs as areas with their thickness, and footings and stem walls as runs along the middle of their outline with a width and height.

mid = (outer + inner) ÷ 2; width (in) = |gap| ÷ 25.4; height (ft) = (z₁ − z₀) ÷ 304.8

The run is the average of matching outer and inner outline points. A slab's thickness in feet is sent unrounded; a stem wall's height is rounded to 0.01 ft. Widths and thicknesses choose the nearest trade pack size.

  • res-takeoff.js
  • lines 172 to 183
  • BimCraft 0.8.1
  const tIn = (p.z1 - p.z0) / IN;
  lv.areas.push({ item: def('slab:' + Math.round(tIn * 100) / 100, { name: t('res.to.slab', { t: d.fmtLen(p.z1 - p.z0) }), type: 'area', heightFt: (p.z1 - p.z0) / FT, pack: 'slab.' + nearest(tIn, [4, 5, 6]) }), pts: p.outer });
  continue;
}
if (!p.inner || p.inner.length !== p.outer.length) continue;
const mid = p.outer.map((q, i) => [(q[0] + p.inner[i][0]) / 2, (q[1] + p.inner[i][1]) / 2]);
const wIn = Math.abs(polyPerimeterGap(p.outer, p.inner)) / IN, hFt = (p.z1 - p.z0) / FT;
let k;
if (p.role === 'footing') k = def('footing:' + Math.round(wIn) + ':' + Math.round((p.z1 - p.z0) / IN), { name: t('res.to.footing', { w: d.fmtLen(wIn * IN), d: d.fmtLen(p.z1 - p.z0) }), type: 'linear', pack: 'footing.' + nearest(wIn, [16, 20, 24]) });
else if (p.role === 'stem') k = def('stem:' + Math.round(wIn) + ':' + Math.round(hFt * 100), { name: t('res.to.stem', { w: d.fmtLen(wIn * IN), h: d.fmtLen(p.z1 - p.z0) }), type: 'linear', heightFt: Math.round(hFt * 100) / 100, pack: 'stem.' + nearest(wIn, [8, 10]) + '.' + nearest(hFt, [4, 8]) });
else k = def('edge:' + Math.round(wIn), { name: t('res.to.edge', { w: d.fmtLen(wIn * IN) }), type: 'linear', heightFt: Math.round(hFt * 100) / 100 });
lv.runs.push({ item: k, pts: ring(mid) });

Width of a footing ring

64-bit

The distance between a foundation's outer and inner outlines.

width = cross(b − a, q − a) ÷ |b − a|

The distance of the first inner point from the line through the first outer edge. Only that one edge is measured; a zero edge length is replaced by 1.

  • res-takeoff.js
  • lines 196 to 201
  • BimCraft 0.8.1
/** The width of a ring: the distance between its outer and inner outlines (both parallel insets of one outline). */
function polyPerimeterGap(outer, inner) {
  const a = outer[0], b = outer[1], q = inner[0];
  const L = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1;
  return ((b[0] - a[0]) * (q[1] - a[1]) - (b[1] - a[1]) * (q[0] - a[0])) / L;
}

In detail

Method
Walls as runs along their center lines with a height, roofs as plan areas with a pitch, ridges, hips, valleys, eaves and rakes as runs, foundations as runs along the middle of the footing and as slab areas, openings and devices as counts. Sheet scale as feet per PDF point = ratio ÷ 864.
Number types
64-bit numbers. One point is 25.4 ÷ 72 mm and one foot is 304.8 mm, so ratio ÷ 864 is exact.
Tolerances
Mark coordinates: 0.01 point. Wall and stem wall heights: 0.01 ft. Pitch: 0.01 degree, then 0.0001 of rise per 12.
Limits
Hips, valleys and rakes are all given the steepest pitch of the roof's faces. A foundation's width is measured at the first edge of its outline only.

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