Looks up the footing size, stud spacing and height, header span, joist and rafter span and wall insulation a regulation pack asks for, and compares the model with them.
How the precision is kept
Limits come from printed tables carried by the pack, in inches and feet, converted with the exact factors 25.4 and 304.8. A table gives a value per row and column, so the limit is the table's, taken at the next listed column or interpolated linearly between building widths. The insulation R-value uses a conversion constant written with 7 digits.
Open item. Write the R-value conversion as 5.678263337 so the constant is not the limit of the result.
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 footing size the pack's table gives for the number of stories, the foundation type, the snow load and the soil pressure.
width = inches × 25.4; thickness = inches × 25.4
The story count is rounded and held between 1 and 3, the load row is the first one at or above the snow load, and the column is the first soil pressure at or above 1500 psf. The cell text 'width x thickness' is parsed and multiplied by the exact 25.4; a thickness above 24 inches is treated as a misprint and not used.
addons/regs/lib/check.js
lines 305 to 321
BimCraft 0.8.1
/** The table's width × thickness (mm) for this footing, or null. */
export function footingFromTable(profile, { stories = 1, foundation = 'slab-on-grade', heavy = false, snowPsf = null, soilPsf = 1500 } = {}) {
const tables = Object.values(profile?.tables || {}).filter((t) => t.kind === 'footing');
const t = tables.find((x) => (heavy ? /concrete|masonry/i : /light frame$/i).test(String(x.data?.construction || ''))) || tables[0];
if (!t || !Array.isArray(t.data?.rows)) return null;
const loads = [...new Set(t.data.rows.map((r) => Number(r.load)))].sort((a, b) => a - b);
// the "20" row is 20 psf roof live or 25 psf ground snow; a larger snow load takes the next row up
const want = snowPsf === null ? loads[0] : (loads.find((l) => (l === 20 ? 25 : l) >= snowPsf) ?? null);
if (want === null) return null;
const n = Math.max(1, Math.min(3, Math.round(stories)));
const row = t.data.rows.find((r) => Number(r.load) === want && Number(r.stories) === n && r.foundation === foundation);
const col = (t.data.columns_psf || []).findIndex((c) => Number(c) >= soilPsf);
const cell = row && col >= 0 ? String(row.cells[col] || '') : '';
const m = /^(\d+(?:\.\d+)?)\s*x\s*(\d+(?:\.\d+)?)$/.exec(cell);
if (!m || +m[2] > 24) return null; // a printed typo (e.g. "20x60") is not used
return { width: +m[1] * IN_MM, thick: +m[2] * IN_MM, table: t, load: want };
}
Footing depth below grade
64-bit
The depth of the footing's underside below grade compared with the rule and the frost depth.
required = max(rule, frost depth); found = max(0, depth below the floor − 6 × 25.4)
Grade is taken as 6 inches (exactly 152.4 mm) below the floor. Both sides are plain 64-bit maxima and one subtraction.
Turns a span written as feet-inches in a table into millimeters.
mm = (feet × 12 + inches) × 25.4
Only the whole feet and whole inches at the start of the cell are read; the factors 12 and 25.4 are exact.
addons/regs/lib/check.js
line 364
BimCraft 0.8.1
const ftIn = (s) => { const m = /^(\d+)-(\d+)/.exec(String(s || '')); return m ? (+m[1] * 12 + +m[2]) * IN_MM : null; };
Header span from the table
64-bit
The allowed span of a header for its size, what it carries, the snow load and the building width.
span = a + (b − a) × (w − lo) ÷ (hi − lo), w = building width ÷ 304.8
The building width is converted to feet with the exact 304.8 and the span is interpolated linearly between the two nearest listed widths; when they coincide the cell is used as is. A width more than 1e-6 ft beyond the widest column is reported as beyond the table and not judged.
addons/regs/lib/check.js
lines 365 to 383
BimCraft 0.8.1
export function headerSpanFromTable(profile, { size, floorsAbove = 0, snowPsf = null, widthMm }) {
const t = Object.values(profile?.tables || {}).find((x) => x.kind === 'header' && /R602\.7\(1\)|exterior/i.test(x.id + ' ' + (x.label || '')))
|| Object.values(profile?.tables || {}).find((x) => x.kind === 'header');
if (!t || !Array.isArray(t.data?.rows)) return null;
const supporting = floorsAbove <= 0 ? /^roof and ceiling$/i : floorsAbove === 1 ? /one center-bearing floor/i : /two center-bearing floors/i;
const r = t.data.rows.find((x) => supporting.test(String(x.supporting || '')) && String(x.size) === size);
if (!r) return null;
const cols = Object.keys(r.cells || {}).map((k) => [parseFloat(k), k]).sort((a, b) => a[0] - b[0]);
const s = snowPsf === null ? cols[0] : cols.find(([v]) => v >= Math.max(30, snowPsf));
if (!s) return null;
const byW = r.cells[s[1]], ws = Object.keys(byW).map(Number).sort((a, b) => a - b);
const wFt = widthMm / FT_MM;
if (wFt > ws[ws.length - 1] + 1e-6) return { beyond: true, table: t };
const lo = [...ws].reverse().find((x) => x <= wFt) ?? ws[0], hi = ws.find((x) => x >= wFt) ?? ws[ws.length - 1];
const a = ftIn(byW[String(lo)]), b = ftIn(byW[String(hi)]);
if (a === null || b === null) return null;
const span = hi === lo ? a : a + (b - a) * (wFt - lo) / (hi - lo);
return { span, table: t, snowCol: s[0] };
}
Span of a header in the model
64-bit
The span of a framed header compared with the table.
span = max(0, length − 3 × 25.4)
The header's stored length less 3 inches of bearing (exactly 76.2 mm), in one 64-bit expression.
The allowed span of a joist or rafter size at its spacing.
column = first listed spacing ≥ spacing ÷ 25.4 − 0.05; span = least span of the listed species
The spacing in millimeters is divided by the exact 25.4 and matched to the next listed spacing with a slack of 0.05 inch. The limit is the minimum over the table's rows for that size, each parsed from feet-inches text.
addons/regs/lib/check.js
lines 439 to 452
BimCraft 0.8.1
// Floor joists: the pack's joist table (kind 'joist', data.rows = [{ species, grade, size, spans: { '16': '15-7', … } }], the 40 psf
// living-area table, the stricter of the two). BimCraft does not record the species, so the span asked for is the LOWEST of the
// table's species for that size and spacing (between listed spacings the next wider one). Joists framed as TJI (engineered) are the
// maker's tables, not the code's, and are left out. A joist's span = its length (the framing tool runs joists wall to wall).
export function spanFromTable(profile, kind, { size, spacingMm }) {
const t = Object.values(profile?.tables || {}).find((x) => x.kind === kind);
const rows = (t?.data?.rows || []).filter((r) => r.size === size);
if (!rows.length) return null;
const sps = Object.keys(rows[0].spans || {}).map(Number).sort((a, b) => a - b);
const sIn = spacingMm / IN_MM, col = sps.find((x) => x >= sIn - 0.05);
if (col === undefined) return null;
const spans = rows.map((r) => ftIn(r.spans[String(col)])).filter((v) => v !== null);
return spans.length ? { span: Math.min(...spans), table: t, spacing: col } : null;
}
Horizontal span of a rafter
64-bit
The span of the longest rafter or ceiling joist measured on the horizontal, compared with the table.
span = sloped length × cos(pitch × π ÷ 180)
The longest piece's stored length is multiplied by the cosine of the roof pitch in degrees, with π ÷ 180 from Math.PI. Ceiling joists use their length unchanged. A rafter under a ground snow load above 20 psf is listed but not judged.
The nominal R-value of a wall type's cavity and continuous insulation layers, in imperial units.
R = thickness (mm) ÷ 1000 ÷ λ × 5.678263
Each insulation layer's thickness is divided by its conductivity and multiplied by 5.678263, a conversion constant written with 7 digits, so the result agrees with the full conversion to about 7 digits. Layers are summed in the order they are listed.
addons/regs/lib/check.js
lines 532 to 545
BimCraft 0.8.1
/** A wall type's insulation: { cavity, cont } in R-IP, or null when no layer is insulation (or has no known conductivity). */
export function insulationOf(type, lambdaOf) {
if (!type?.layers) return null;
let cavity = 0, cont = 0, any = false;
for (const l of type.layers) {
if (!(l.function === 'insulation' || IS_INS.test(String(l.mat || '')))) continue;
const lam = lambdaOf(String(l.mat || ''));
if (!(lam > 0) || !(l.t > 0)) continue;
any = true;
const r = l.t / 1000 / lam * SI_TO_IP_R;
if (CONT_INS.test(String(l.mat || ''))) cont += r; else cavity += r;
}
return any ? { cavity, cont } : null;
}
Wall insulation against the table's options
64-bit
Whether a wall type's insulation meets any of the cavity and continuous options of its climate zone.
met if cavity + 0.05 ≥ option cavity and continuous + 0.05 ≥ option continuous, for any option
The comparison allows 0.05 in R-value on each part, an absolute slack. The found value shown is cavity plus continuous, and the required value shown is the smallest total among the options.
addons/regs/lib/check.js
lines 569 to 574
BimCraft 0.8.1
const need = opts.length ? Math.min(...opts.map((o) => o.cavity + o.cont)) : 0;
const rule = { ...rw, value: need, clause };
if (!opts.length || !ins) { out.push(note(rule, where, a.ref)); continue; }
const met = opts.some((o) => ins.cavity + 0.05 >= o.cavity && ins.cont + 0.05 >= o.cont);
const rowx = row('wall', where, rule, ins.cavity + ins.cont, a.ref);
rowx.ok = met; // the options are cavity AND continuous pairs: met by any one
In detail
Method
Table lookup by stories, snow load, soil pressure, size and spacing; linear interpolation of header spans between table widths; R = thickness ÷ conductivity.
Number types
64-bit numbers; table cells are text parsed to numbers.
Tolerances
Joist and rafter spacing is matched to a table column with a slack of 0.05 inch; a building width may exceed the widest table column by 1e-6 ft; an insulation option is met with a slack of 0.05 in R-value.
Limits
A span cell is read as whole feet and whole inches. Spans between listed spacings use the next wider spacing, and the lowest span of the table's species is used because the model does not record the species.
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.