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