Splits a ramp's rise into sloped flights with landings between them, and works out each flight's slope, length and volume.
How the precision is kept
The number of flights, their run and the slope are closed-form 64-bit expressions from the rise and planning values (760 mm most rise per flight, 1525 mm landings, 1 in 12). Positions along the ramp and heights are running sums. A flight's written volume multiplies its sloped length by the thickness, while its body has that thickness measured vertically, so the written volume is larger than the body's.
Open item. Write a flight's volume as plan length × thickness × width, which is the volume of the body that is built.
Calculations under this policy
5 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.
Where each flight and landing starts and ends along the ramp, and at what height.
n = max(1, ⌈rise ÷ maxRise − 1e-9⌉); flight run = rise ÷ n × slope; steep when slope < 12 − 1e-9
Positions along the ramp are a running sum of flight runs and landing lengths, and heights a running sum of rise ÷ n, so the top height can differ from the rise by rounding in the last digit. The inch defaults are whole inches times 25.4, which is exact.
model-ramps.js
lines 36 to 56
BimCraft 0.8.1
export function rampLayout(spec, rise) {
const s = { ...RAMP_DEFAULTS, ...spec };
const n = Math.max(1, Math.ceil(rise / s.maxRise - 1e-9));
const landings = n - 1;
let flightRun, slope;
if (s.mode === 'length') {
flightRun = (s.length - landings * s.landing) / n;
if (!(flightRun > 0)) return { error: 'model.err.ramp.short' };
slope = flightRun / (rise / n);
} else { slope = s.slope; flightRun = rise / n * slope; }
const pieces = [];
let sPos = 0, z = 0;
for (let i = 0; i < n; i++) {
pieces.push({ kind: 'flight', s0: sPos, s1: sPos + flightRun, z0: z, z1: z + rise / n });
sPos += flightRun; z += rise / n;
if (i < n - 1) { pieces.push({ kind: 'landing', s0: sPos, s1: sPos + s.landing, z0: z, z1: z }); sPos += s.landing; }
}
const a = (s.angle || 0) * Math.PI / 180, u = [Math.cos(a), Math.sin(a)], l = [-u[1], u[0]], hw = s.width / 2, st = s.start || [0, 0];
const P = (along, across) => [st[0] + u[0] * along + l[0] * across, st[1] + u[1] * along + l[1] * across];
return { pieces, run: sPos, slope, steep: slope < 12 - 1e-9, outline: [P(0, -hw), P(sPos, -hw), P(sPos, hw), P(0, hw)], arrow: [P(0, 0), P(sPos, 0)], P, u, l };
}
Rise of a ramp
64-bit
The height a ramp climbs: to its top level, else the rise typed.
rise = top elevation − own elevation
One subtraction of two level elevations; the typed rise is used when the top level is missing or not higher.
model-ramps.js
lines 80 to 86
BimCraft 0.8.1
function riseOf(w, r) {
if (r.topLevel) {
const L = readLevels(w), me = L.find((l) => l.id === r.storey), top = L.find((l) => l.guid === r.topLevel);
if (me && top && top.elevation > me.elevation) return top.elevation - me.elevation;
}
return r.rise > 0 ? r.rise : 0;
}
Flight slope, length and volume
64-bit
The slope angle, sloped length and volume written on each flight.
slope = atan(Δz ÷ len); length = √(len² + Δz²); volume written = length × width × t
The body's side profile has its top and bottom edges t apart vertically, so its volume is plan length × t × width. The quantity written multiplies the sloped length by t instead, which is larger by √(1 + (Δz ÷ len)²): about 0.35 % at 1 in 12. The slope angle is rounded to 9 decimals of a radian.
The start point, sizes, slope and the rise and run used, kept in the ramp's property set.
required slope = atan(1 ÷ slope)
Lengths are rounded to 6 decimals of the file's unit, the slope ratio and the angle in degrees to 6 decimals, and the required slope atan(1 ÷ slope) to 9 decimals of a radian.
Rebuilds ramps whose rise follows a level that moved.
rebuild when |rise − rise used| > 0.5 mm
A ramp is rebuilt only when the rise its levels give differs from the rise it was built with by more than 0.5 mm, an absolute tolerance.
model-ramps.js
lines 174 to 181
BimCraft 0.8.1
/** Ramps whose rise follows a level that moved: made again. */
export function syncRamps(w, storey) {
let n = 0, rs = [];
try { rs = readRamps(w); } catch (e) { return 0; }
for (const r of rs) if (r.topLevel && Math.abs(riseOf(w, r) - r.riseUsed) > 0.5) { writeRamp(w, r); n++; }
void storey;
return n;
}
In detail
Method
n = ⌈rise ÷ max rise⌉ flights; run per flight = rise ÷ n × slope, or (length − landings) ÷ n when set by length; flight body = a side profile extruded across the width.
Number types
64-bit numbers in millimeters; slopes written as angles in radians.
Tolerances
1e-9 slack when counting flights and when flagging a slope steeper than 1 in 12. A ramp is rebuilt when its rise is more than 0.5 mm from the rise its levels give. Width under 300 mm and rise under 10 mm are refused.
Checked by
tools/bimcraft-test/draw06-test.mjs calls rampLayout and placeRamp in Node.
Limits
The maximum rise per flight and the landing length are described in the file as planning values. The slope angles are written in radians without converting to the file's angle unit.
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.