Resources Numerical accuracy Calculation policy

Ramps: flights, landings and quantities

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.

model-ramps.js BimCraft 0.8.1 64-bit 5 calculations Reviewed

Tests tools/bimcraft-test/draw06-test.mjs, tools/bimcraft-test/modify-test.mjs

Flights and landings of a ramp

64-bit

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.

  • model-ramps.js
  • lines 108 to 112
  • BimCraft 0.8.1
const len = pc.s1 - pc.s0, t = r.thickness;
const prof = w.profile([[0, 0], [len, pc.z1 - pc.z0], [len, pc.z1 - pc.z0 - t], [0, -t]]);
w.setShape(id, [w.rep('body', 'Body', 'SweptSolid', [w.extrudeAt(prof, w.axisFrame([o[0], o[1], pc.z0], zDir, xDir), r.width)])]);
w.upsertPset(id, 'Pset_RampFlightCommon', [['ClearWidth', 'IFCPOSITIVELENGTHMEASURE', round(w.u(r.width))], ['Slope', 'IFCPLANEANGLEMEASURE', round(Math.atan((pc.z1 - pc.z0) / len), 9)]]);
w.upsertQto(id, 'Qto_RampFlightBaseQuantities', [['Length', 'length', Math.hypot(len, pc.z1 - pc.z0)], ['Width', 'length', r.width], ['GrossVolume', 'volume', Math.hypot(len, pc.z1 - pc.z0) * r.width * t]]);

Values saved with a ramp

64-bit

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.

  • model-ramps.js
  • lines 129 to 133
  • BimCraft 0.8.1
w.upsertPset(r.id, 'Pset_RampCommon', [['RequiredSlope', 'IFCPLANEANGLEMEASURE', round(Math.atan(1 / L.slope), 9)], ['IsExternal', 'IFCBOOLEAN', false]]);
const Lm = (k, v) => [k, 'IFCLENGTHMEASURE', round(w.u(v || 0))];
w.upsertPset(r.id, RAMP_PSET, [Lm('StartX', r.start[0]), Lm('StartY', r.start[1]), ['Angle', 'IFCREAL', round(r.angle || 0, 6)], Lm('Width', r.width), Lm('Rise', r.rise),
  ['TopLevel', 'IFCLABEL', r.topLevel || ''], ['Mode', 'IFCLABEL', r.mode], ['Slope', 'IFCREAL', round(L.slope, 6)], Lm('Length', r.mode === 'length' ? r.length : L.run),
  Lm('Thickness', r.thickness), Lm('MaxRise', r.maxRise), Lm('Landing', r.landing), Lm('RiseUsed', rise), Lm('RunUsed', L.run), ['Steep', 'IFCBOOLEAN', !!L.steep]]);

Ramps following a level

64-bit

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.

Every policy that takes this approach