Picks the smallest joist depth whose listed clear span reaches the span asked for, from a table the person loads.
How the precision is kept
The code holds no span numbers of its own. The table the person loads sets the accuracy: lengths are converted to millimeters in 64-bit with the exact factors 25.4 and 304.8, and the pick is a comparison against the listed spans with a stated tolerance.
Calculations under this policy
6 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.
A listed span, as a number or as feet-inches text, in millimeters.
mm = ft × 304.8 + (in + n ÷ d) × 25.4
Feet times 304.8 plus inches times 25.4, both exact factors, with a fraction added to the inches before converting. Metric spans multiply meters by 1000. A zero or negative span gives NaN, which the loader refuses.
tji-table.js
lines 34 to 43
BimCraft 0.8.1
/** "15-6" = 15 ft 6 in; "15-6 1/2" too; a plain number is feet (imperial) or metres (metric). → mm, or NaN. */
function spanMm(v, metric) {
if (metric) { const n = num(v); return n > 0 ? n * 1000 : NaN; }
if (typeof v === 'number') return v > 0 ? v * FT : NaN;
const s = String(v ?? '').trim();
const m = /^(\d+)\s*-\s*(\d+(?:\.\d+)?)(?:\s+(\d+)\/(\d+))?$/.exec(s);
if (m) return +m[1] * FT + (+m[2] + (m[3] ? +m[3] / +m[4] : 0)) * IN;
const n = Number(s);
return n > 0 ? n * FT : NaN;
}
Table units to millimeters
64-bit
Depth, spacing and bearing of the loaded table in millimeters.
Imperial depths, spacings and bearings are multiplied by 25.4 (exact) as read; metric ones by 1. Each number is converted once; nothing is rounded.
tji-table.js
lines 94 to 103
BimCraft 0.8.1
const series = String(r?.series ?? '').trim(), depth = num(r?.depth) * u;
if (!series || !(depth > 0)) { errors.push(bad('tji.err.row', { series: series || '?' })); continue; }
const spans = new Map();
for (const [cid, bySp] of Object.entries(r.spans || {})) {
if (!cases.some((c) => c.id === cid)) { errors.push(bad('tji.err.case', { id: cid })); continue; }
const list = [];
for (const [sp, v] of Object.entries(bySp || {})) {
const spacing = num(sp) * u, max = spanMm(v, metric);
if (!(spacing > 0) || !(max > 0)) { errors.push(bad('tji.err.span', { series, case: cid, spacing: sp })); continue; }
list.push({ spacing, max });
Spacing match
64-bit
Whether a requested on-center spacing is served by a listed column.
0.98 × listed ≤ want ≤ 1.005 × listed
Relative band: 2% closer or 0.5% wider than the listed spacing, so 400 mm serves a listed 16 in (406.4 mm) and 600 mm serves 24 in (609.6 mm).
tji-table.js
lines 118 to 119
BimCraft 0.8.1
/** A listed spacing column serves a requested spacing equal to it or up to 2% CLOSER (400 mm for 16 in, 600 mm for 24 in): never a wider one. */
const spacingFits = (listed, want) => want <= listed * 1.005 && want >= listed * 0.98;
Smallest depth that reaches the span
64-bit
The joist size for a span, spacing and load case.
first candidate with max + 10⁻⁶ ≥ span
Sorts candidates by depth then listed span and takes the first with max + 1e-6 >= span; the 1e-6 mm is absolute. It refuses a spacing or a span the table does not list.
tji-table.js
lines 120 to 138
BimCraft 0.8.1
/**
* The smallest depth (then the shortest-reaching series at that depth) whose listed maximum clear span, for this case and this on-centre
* spacing, reaches `span` (mm). Refuses, with a plain message, a case or spacing the table does not list and a span beyond every row.
*/
export function pickTji(table, { span, spacing, caseId } = {}) {
if (!table) return bad('tji.err.none');
const cs = table.cases.find((c) => c.id === caseId) || table.cases[0];
const near = (a) => spacingFits(a, spacing);
const listed = [...new Set(table.rows.flatMap((r) => (r.spans.get(cs.id) || []).map((x) => x.spacing)))].sort((a, b) => a - b);
if (!listed.some(near)) return bad('tji.err.spacing', { spacing: depthWord(spacing, table.metric), list: listed.map((x) => depthWord(x, table.metric)).join(', '), table: table.id });
const cands = [];
for (const r of table.rows) { const hit = (r.spans.get(cs.id) || []).find((x) => near(x.spacing)); if (hit) cands.push({ r, max: hit.max }); }
cands.sort((a, b) => a.r.depth - b.r.depth || a.max - b.max);
const top = cands.reduce((m, c) => Math.max(m, c.max), 0);
const pick = cands.find((c) => c.max + 1e-6 >= span);
if (!pick) return bad('tji.err.beyond', { span: (span / FT).toFixed(1) + ' ft / ' + (span / 1000).toFixed(2) + ' m', max: (top / FT).toFixed(1) + ' ft / ' + (top / 1000).toFixed(2) + ' m', table: table.id });
const label = pick.r.label || (pick.r.series + ' ' + depthWord(pick.r.depth, table.metric));
return { ok: true, series: pick.r.series, depth: pick.r.depth, size: label, max: pick.max, spacing, caseId: cs.id };
}
Listed maximum span for a size
64-bit
The listed span of a named framing size at a spacing and case.
Looks up the table row by its label and returns the listed maximum in millimeters unchanged, using the same spacing band as the pick.
tji-table.js
lines 140 to 147
BimCraft 0.8.1
/** The listed maximum clear span (mm) of the row a framing named by its size label, at this spacing and case: for the regs add-on's check. null = not in the table. */
export function tjiMaxSpan(table, size, spacing, caseId) {
if (!table) return null;
const cs = table.cases.find((c) => c.id === caseId) || table.cases[0];
const r = table.rows.find((x) => (x.label || (x.series + ' ' + depthWord(x.depth, table.metric))) === size);
const hit = r && (r.spans.get(cs.id) || []).find((x) => spacingFits(x.spacing, spacing));
return hit ? { span: hit.max, case: cs } : null;
}
Depth written on the drawing
64-bit
The depth text used in the size label.
in = round(mm ÷ 25.4 × 1000) ÷ 1000
Inches are the millimeter depth divided by 25.4, rounded to 3 decimals with Math.round(x × 1000) ÷ 1000; metric depths are rounded to a whole millimeter. The rounded text goes into the size label.
Convert the table to millimeters, find the spacing column within a relative band, and take the first depth whose listed span, plus 1e-6 mm, is at least the span asked for.
Number types
64-bit numbers throughout; no typed arrays.
Tolerances
spacingFits accepts a requested spacing between 0.98 and 1.005 times a listed spacing (relative); a listed span passes when max + 1e-6 mm >= span (absolute, millimeters); the error message formats spans with toFixed(1) feet and toFixed(2) meters (text only).
Limits
No interpolation between listed spacings or spans: a span is either under a listed maximum or refused. The label written on a drawing uses a depth rounded to 3 decimals in inches.
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.