Resources Numerical accuracy Calculation policy

Foundation parts and quantities

Sizes footings from a default table, builds the slab, stem wall and footing shapes under the building outline, steps them to the ground, and writes their lengths, widths and volumes.

How the precision is kept

Shapes are offsets of the building outline computed in 64-bit millimeters. Footing sizes come from a small table in whole inches that the file labels as defaults for a designer to check. Where the ground is known, footings step down in whole 8-inch steps, with the ground sampled every 600 mm along each wall. Quantities are stored through the writer's rounding.

Open item. Compute the Length of a stepped footing or stem wall along its center line, as for the unstepped ring, instead of from half the perimeter of each piece.

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-foundation.js BimCraft 0.8.1 64-bit 8 calculations Reviewed

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

Default footing size

Exact

The footing's width and depth from the foundation kind, the number of stories and the wall material.

width = table inches (+ 4 under masonry or concrete) × 25.4; depth = 10 in from 24 in wide, else 8 in

Whole-inch table values times 25.4. The story count is rounded and clamped to 1 … 3. The file's comment calls these labelled defaults for 1500 psf soil that a designer or engineer checks.

  • res-foundation.js
  • lines 40 to 49
  • BimCraft 0.8.1
/**
 * Footing width and depth (mm) — labelled defaults after the usual light-frame table for 1500 psf soil (a slab or stem-wall
 * slab 12" / 15" / 23" wide for 1 / 2 / 3 stories; with a crawlspace or basement 18" / 22" / 27"); 4" wider under masonry or
 * concrete walls; 8" deep, 10" from 24" wide. A designer or engineer checks them for the soil and loads.
 */
export function footingSize({ kind = 'stemslab', stories = 1, heavy = false } = {}) {
  const n = Math.max(1, Math.min(3, Math.round(stories)));
  const base = (kind === 'crawl' || kind === 'basement' ? [18, 22, 27] : [12, 15, 23])[n - 1] + (heavy ? 4 : 0);
  return { width: base * IN, depth: (base >= 24 ? 10 : 8) * IN };
}

Outline offset inward

64-bit

A polygon moved inward by a distance, with mitred corners.

corner = intersection of the two neighboring edges, each moved by d along its left normal

Each corner is the intersection of two offset lines, solved with cross products of unit directions. Edges whose cross product is under 1e-9 in size are treated as parallel and the corner is the offset point of the second edge. There is no limit on the mitre length at a sharp corner.

  • res-foundation.js
  • lines 73 to 83
  • BimCraft 0.8.1
export function insetPoly(P, d) {
  const n = P.length, E = P.map((a, i) => { const b = P[(i + 1) % n], L = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1, u = [(b[0] - a[0]) / L, (b[1] - a[1]) / L]; return { a, u, n: [-u[1], u[0]] }; });
  return E.map((e, i) => {
    const f = E[(i + n - 1) % n];
    const p0 = [f.a[0] + f.n[0] * d, f.a[1] + f.n[1] * d], p1 = [e.a[0] + e.n[0] * d, e.a[1] + e.n[1] * d];
    const c = cross(f.u, e.u);
    if (Math.abs(c) < 1e-9) return p1;
    const t = cross(sub(p1, p0), e.u) / c;
    return [p0[0] + f.u[0] * t, p0[1] + f.u[1] * t];
  });
}

Parts of a foundation

64-bit

The slab, edge beam, stem wall and footing outlines with their bottom and top heights.

footing ring between offsets (t_stem − w_foot) ÷ 2 and (t_stem + w_foot) ÷ 2; footing top = −bottom + depth

Rings are pairs of inward offsets of the building outline, so the footing is centered under the stem wall. A part is left out when its inner outline has 10000 mm² or less of area or its height is not positive.

  • res-foundation.js
  • lines 135 to 162
  • BimCraft 0.8.1
/** The parts of a foundation under `outline` (anticlockwise outer faces), heights from the floor. With `grade` (a function
 *  of plan x, y → the ground's height from the floor), stem walls and crawlspace footings step down to follow it. */
export function foundationGeometry(outline, p, { grade = null } = {}) {
  const parts = [];
  if (grade && (p.kind === 'stemslab' || p.kind === 'crawl')) {
    parts.push(...steppedRings(outline, p, grade));
    if (p.kind === 'stemslab') { const poly = insetPoly(outline, p.stemT); if (area(poly) > 1e4) parts.push({ role: 'slab', outer: poly, inner: null, z0: -p.slabT, z1: 0 }); }
    return parts;
  }
  const ring = (role, dOut, dIn, z0, z1) => { const outer = insetPoly(outline, dOut), inner = insetPoly(outline, dIn); if (area(inner) > 1e4 && z1 > z0) parts.push({ role, outer, inner, z0, z1 }); };
  const slab = (d, z0, z1) => { const poly = insetPoly(outline, d); if (area(poly) > 1e4) parts.push({ role: 'slab', outer: poly, inner: null, z0, z1 }); };
  const footUnder = (top) => ring('footing', (p.stemT - p.footW) / 2, (p.stemT + p.footW) / 2, top - p.footD, top);
  if (p.kind === 'slab') {
    slab(0, -p.slabT, 0);
    ring('edge', 0, p.edgeW, -p.edgeD, -p.slabT);
  } else if (p.kind === 'stemslab' || p.kind === 'crawl') {
    const footTop = -p.bottom + p.footD;
    ring('stem', 0, p.stemT, footTop, p.stemTop);
    footUnder(footTop);
    if (p.kind === 'stemslab') slab(p.stemT, -p.slabT, 0);
  } else if (p.kind === 'basement') {
    const slabTop = p.stemTop - p.basementH, footTop = slabTop - p.slabT;
    ring('stem', 0, p.stemT, footTop, p.stemTop);
    footUnder(footTop);
    slab(p.stemT, slabTop - p.slabT, slabTop);
  }
  return parts;
}

Footings stepped to the ground

64-bit

Splits each wall's footing and stem into runs whose underside steps down with the ground.

underside = min(floor((ground − bottom) ÷ step) × step, −bottom), step = 8 × 25.4 mm; sampled every 600 mm

The ground is sampled every 600 mm along the wall and the underside is rounded down to a whole step with Math.floor. A change of step between two samples is placed at their midpoint. Where the ground function returns nothing, −200 mm is used.

  • res-foundation.js
  • lines 102 to 134
  • BimCraft 0.8.1
/**
 * Stem walls and footings stepped to the grade: each wall's strip of the rings split into runs where the footing's underside
 * (the grade along the outside of the wall − `bottom`) falls in another 8" step. grade(x, y) → z from the floor, or null.
 */
function steppedRings(outline, p, grade) {
  const parts = [];
  const n = outline.length;
  const sO = outline, sI = insetPoly(outline, p.stemT), fO = insetPoly(outline, (p.stemT - p.footW) / 2), fI = insetPoly(outline, (p.stemT + p.footW) / 2);
  for (let i = 0; i < n; i++) {
    const a = outline[i], b = outline[(i + 1) % n], L = Math.hypot(b[0] - a[0], b[1] - a[1]);
    if (L < 1) continue;
    const u = [(b[0] - a[0]) / L, (b[1] - a[1]) / L];
    const quad = (O, I) => [O[i], O[(i + 1) % n], I[(i + 1) % n], I[i]];
    const stemQ = quad(sO, sI), footQ = quad(fO, fI);
    // the needed underside along the wall, in steps; runs of the same step
    // the footing's underside `bottom` below the grade there (where the ground is missing: 8" under the floor), in whole steps
    const at = (s) => { const g = grade(a[0] + u[0] * s, a[1] + u[1] * s); return Math.floor(((g ?? -200) - p.bottom) / STEP) * STEP; };
    const ss = [];
    for (let s = 0; s <= L; s += 600) ss.push(s);
    if (ss[ss.length - 1] < L) ss.push(L);
    let r0 = 0, z0 = Math.min(at(0), -p.bottom);
    const runs = [];
    for (let k = 1; k < ss.length; k++) { const z = Math.min(at(ss[k]), -p.bottom); if (z !== z0) { const m = (ss[k - 1] + ss[k]) / 2; runs.push([r0, m, z0]); r0 = m; z0 = z; } }
    runs.push([r0, L, z0]);
    for (const [s0, s1, zb] of runs) {
      const lo = runs.length === 1 ? -1e9 : s0, hi = runs.length === 1 ? 1e9 : s1;
      const fq = band(footQ, a, u, s0 === 0 ? -1e9 : lo, s1 === L ? 1e9 : hi), sq = band(stemQ, a, u, s0 === 0 ? -1e9 : lo, s1 === L ? 1e9 : hi);
      if (fq.length >= 3) parts.push({ role: 'footing', outer: fq, inner: null, z0: zb, z1: zb + p.footD, step: true });
      if (sq.length >= 3 && p.stemTop > zb + p.footD) parts.push({ role: 'stem', outer: sq, inner: null, z0: zb + p.footD, z1: p.stemTop, step: true });
    }
  }
  return parts;
}

Cutting a strip between two cross lines

64-bit

Keeps the part of a wall's strip between two distances along the wall.

t = f_a ÷ (f_a − f_b); point = a + t × (b − a)

Signed distances to the cut line are compared with ±1e-9 mm. The crossing point divides by the difference of two distances of opposite sign, each more than 1e-9 mm in size, so the ratio stays between 0 and 1.

  • res-foundation.js
  • lines 93 to 101
  • BimCraft 0.8.1
/** Keep the part of a polygon between two lines square to u at s0 and s1 (measured from o). */
function band(poly, o, u, s0, s1) {
  const clip = (P, sgn, c) => {
    const out = [], f = (q) => sgn * ((q[0] - o[0]) * u[0] + (q[1] - o[1]) * u[1] - c);
    for (let i = 0; i < P.length; i++) { const a = P[i], b = P[(i + 1) % P.length], fa = f(a), fb = f(b); if (fa >= -1e-9) out.push(a); if ((fa < -1e-9 && fb > 1e-9) || (fa > 1e-9 && fb < -1e-9)) { const t = fa / (fa - fb); out.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]); } }
    return out;
  };
  return clip(clip(poly, 1, s0), -1, s1);
}

Ground height under the foundation

64-bit

The ground's height relative to the floor at a plan point.

world = M · (x, y); height = ground(world) − level elevation

The plan point is moved to world coordinates with the level's matrix and the level's own height is subtracted from the terrain's height there. The terrain lookup itself is in another file.

  • res-foundation.js
  • line 224
  • BimCraft 0.8.1
grade = (x, y) => { const q = apply(x, y), g = groundAt(w, q[0], q[1]); return g === null ? null : g - M[14]; };

Quantities of a ring or slab

64-bit

Area, length, width, height and volume of a slab, stem wall or footing.

a = |A_outer| − |A_inner|; length = (P_outer + P_inner) ÷ 2; width = a ÷ length; volume = a × h

The ring area subtracts two shoelace areas of similar size; for a thin ring round a large building that subtraction loses some digits. Width is derived as area ÷ length, so it is an average, exact only for a ring of constant width with square corners.

  • res-foundation.js
  • lines 259 to 262
  • BimCraft 0.8.1
const a = Math.abs(area(g.outer)) - (g.inner ? Math.abs(area(g.inner)) : 0), h = g.z1 - g.z0;
if (g.role === 'slab') w.upsertQto(id, 'Qto_SlabBaseQuantities', [['Width', 'length', h], ['Perimeter', 'length', perim(g.outer)], ['GrossArea', 'area', a], ['NetArea', 'area', a], ['GrossVolume', 'volume', a * h], ['NetVolume', 'volume', a * h]]);
else {
  const len = (perim(g.outer) + perim(g.inner)) / 2, width = a / len;

Quantities of a stepped footing or stem

64-bit

The length and volume of a footing or stem wall made of stepped pieces.

volume = Σ |A_piece| × height; length = Σ P_piece ÷ 2

Volume is summed piece by piece in 64-bit. Length is the sum of half of each piece's perimeter; for a strip piece L long and w wide that is L + w, so the stored Length is longer than the wall by about one strip width per piece.

  • res-foundation.js
  • lines 250 to 251
  • BimCraft 0.8.1
const vol = pieces.reduce((sum, q) => sum + Math.abs(area(q.outer)) * (q.z1 - q.z0), 0);
const len = pieces.reduce((sum, q) => sum + perim(q.outer) / 2, 0);

In detail

Method
Mitred inward offset of a polygon; ring area = outer area − inner area; ring length = mean of the two perimeters; width = area ÷ length; volume = area × height; convex clipping of each wall's strip between two cross lines.
Number types
64-bit numbers. Inch sizes times 25.4, which is exact.
Tolerances
Offset corners: edges count as parallel when |cross| of their unit directions < 1e-9. A part needs more than 10000 mm² of area. Clipping uses 1e-9 mm. Step size 203.2 mm; ground sample spacing 600 mm; walls under 1 mm are skipped. The change key rounds numbers to 0.01 mm.
Limits
A step is placed midway between two ground samples, so its position is known to about 300 mm. Where the ground has no value, 200 mm below the floor is assumed. The Length written for a stepped footing or stem wall is the sum of half the perimeter of each piece, which is each piece's length plus its width.

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