Works out the risers, treads, pitch and the stringer and tread parts of a straight steel stair from its rise, going and width.
How the precision is kept
Closed-form 64-bit arithmetic: the rise is divided into equal risers, and the stringers are placed on a line parallel to the nosing line. The tread plate's width reaches the part through a profile name written with one decimal. The riser and going limits are the whole-millimeter figures 102, 178 and 279 named in the file for 4, 7 and 11 inches.
Calculations under this policy
4 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 number of risers, the riser height, the run and the pitch.
n = max(2, ⌈rise ÷ max riser − 1e-9⌉); r = rise ÷ n; run = (n − 1)·going; pitch = atan2(r, going)
The 1e-9 stops a rise that is an exact multiple of the largest riser from gaining a step. The riser is one division, so n risers add back to the rise within rounding.
stair.js
lines 54 to 56
DetailCraft 0.8.1
const n = Math.max(2, Math.ceil(s.rise / s.maxRiser - 1e-9));
const r = s.rise / n, g = s.going, treads = n - 1, run = treads * g;
const pitch = Math.atan2(r, g) / R;
Stringer axis
64-bit
The line the stringers run on, and where it meets the lower and upper floor.
k = r ÷ g; drop = (depth ÷ 2 − 50) ÷ cos(atan k); z(a) = k·(a + g) − drop; a0 = drop ÷ k − g, a1 = (rise + drop) ÷ k − g
The cosine of the slope is taken as Math.cos(Math.atan(k)), two library calls where 1 ÷ √(1 + k²) would be one square root. The divisions by k are safe because the riser and the going are both positive.
stair.js
lines 67 to 71
DetailCraft 0.8.1
const k = r / g, cosA = Math.cos(Math.atan(k)), drop = (sd / 2 - 50) / cosA;
if (sd / 2 - 50 < s.tread.t + 10) problems.push({ code: 'shallow', text: 'stair.prob.shallow', fills: { name: s.stringer } });
const axisZ = (along) => k * (along + g) - drop;
const a0 = drop / k - g, a1 = (s.rise + drop) / k - g; // where the axis meets the lower and the upper floor
const half = s.width / 2 + sw / 2;
Tread plates
64-bit
The size and position of each tread.
depth = going + nosing; middle = (i − 1)·going − nosing + depth ÷ 2; top = i·r
Each tread's height is i times the riser, not a running sum. The tread's width is written into its profile name as Math.round(depth × 10) ÷ 10, so the plate is built to 0.1 mm while its position uses the unrounded depth.
stair.js
lines 79 to 84
DetailCraft 0.8.1
const depth = g + s.tread.nosing;
for (let i = 1; i <= treads; i++) {
// tread i: front (nosing) edge at along (i − 1)·g − nosing, back edge at i·g; top at i·r
const mid = (i - 1) * g - s.tread.nosing + depth / 2;
parts.push({ key: 'tread' + i, type: 'plate', role: t('stair.role.tread'), profileName: 'PL' + s.tread.t + 'x' + Math.round(depth * 10) / 10, material: s.material,
start: P(mid, s.width / 2, i * r - s.tread.t / 2), end: P(mid, -s.width / 2, i * r - s.tread.t / 2), rotation: 0, position: 'middle', attachTo: 'stringerL' });
Riser, going and comfort checks
64-bit
Warnings when the riser, the going or twice the riser plus the going is outside its range.
The riser and going comparisons allow 1e-6 mm; the comfort rule has no tolerance. The limits are whole millimeters, so a riser of 177.9 mm passes although 7 inches is 177.8 mm.
n = rise ÷ largest riser, rounded up; equal risers; stringer axis offset square to the slope.
Number types
64-bit numbers; tread width in the profile name with one decimal.
Tolerances
Riser count: Math.ceil(rise ÷ max riser − 1e-9). Riser and going checks: 1e-6 mm.
Limits
4, 7 and 11 inches are 101.6, 177.8 and 279.4 mm; the checks use 102, 178 and 279. The stringers are square-ended; their cuts against the floors are not modeled, as the file header says.
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.