Resources Numerical accuracy Calculation policy

Building data for flood, wind and earthquake checks

Takes room floor heights, opening sills, storey heights, areas, widths and masses from the model for the hazard checks.

How the precision is kept

Elevations and sills are millimeters in the model and are divided by 1000 in 64-bit. A storey's mass is its footprint area times the mass per square meter the person gives (500 kg/m² by default) and uses the unrounded area. The area and the width across the wind kept in the storey record are rounded to two decimals.

Calculations under this policy

3 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.

hazard-model.js BimCraft 0.8.1 64-bit 3 calculations Reviewed

Tests tools/bimcraft-test/hazard-test.mjs

Room floors and opening sills for flooding

64-bit

Each room's floor height above the lowest level and each opening's sill height, in meters.

floor = (elevation − lowest elevation) ÷ 1000; sill = max(0, sill) ÷ 1000

One subtraction and one division by 1000 per value, in 64-bit. A negative sill is set to 0; a stair joins its two rooms with a sill of 0.

  • hazard-model.js
  • lines 18 to 27
  • BimCraft 0.8.1
export function floodScene(ed) {
  const { w, levels, rooms, doors, stairs, notes } = modelLinks(ed);
  if (!levels.length) return { rooms: [], openings: [], notes };
  const datum = levels[0].elevation, elev = new Map(levels.map((l) => [l.guid, (l.elevation - datum) / 1000]));
  const out = rooms.map((r) => ({ id: r.id, name: r.name, level: r.level, levelName: r.levelName, floor: elev.get(r.level), area_m2: r.area_m2, poly: r.poly }));
  const at = attachDoors(rooms, doors), byId = new Map(doors.map((d) => [d.id, d]));
  const openings = at.map((o) => { const d = byId.get(o.id); return { id: o.id, a: o.rooms[0], b: o.rooms[1], sill: Math.max(0, (d.sill || 0)) / 1000, kind: d.kind }; });   // a door's or window's sill is measured from its own level's floor
  for (const s of stairs) openings.push({ id: s.id, a: s.a.room, b: s.b.room, sill: 0, kind: 'stair' });
  return { rooms: out, openings, datum_mm: datum, notes };
}

Storey height, area, width and mass

64-bit

The storey data the wind and earthquake checks use, from the model's levels and outlines.

area = Σ |outline area| ÷ 1e6; h = (next elevation − elevation) ÷ 1000; z = (elevation − datum) ÷ 1000 + h ÷ 2; mass = area × mass per m²; T1 = 0.1 × storeys when not given

The mass uses the unrounded area. The storey record keeps the area rounded to 0.01 m² and the width as Math.round(ext ÷ 10) ÷ 100, that is 0.01 m.

  • hazard-model.js
  • lines 29 to 48
  • BimCraft 0.8.1
export function hazardBuilding(ed, { massPerM2 = 500, T1 = null, dir = 'x' } = {}) {
  const { w, levels, rooms, notes } = modelLinks(ed);
  if (!levels.length) return { storeys: [], notes };
  const datum = levels[0].elevation, storeys = [];
  levels.forEach((lv, i) => {
    const loops = storeyLoops(w, lv.id), outs = (loops.outlines || []).filter((o) => o.poly && o.poly.length > 2);
    const mine = rooms.filter((r) => r.level === lv.guid);
    const area = outs.length ? outs.reduce((s, o) => s + Math.abs(o.area) / 1e6, 0) : mine.reduce((s, r) => s + r.area_m2, 0);
    const pts = (outs.length ? outs.flatMap((o) => o.poly) : mine.flatMap((r) => r.poly));
    if (!pts.length) return;
    const xs = pts.map((p) => p[0]), ys = pts.map((p) => p[1]);
    const ext = dir === 'x' ? Math.max(...ys) - Math.min(...ys) : Math.max(...xs) - Math.min(...xs);
    const next = levels[i + 1], h = (next ? next.elevation - lv.elevation : lv.height) / 1000;
    storeys.push({ name: lv.name, guid: lv.guid, z: (lv.elevation - datum) / 1000 + h / 2, h, area_m2: Math.round(area * 100) / 100, width_m: Math.round(ext / 10) / 100, mass: area * massPerM2 });
  });
  if (!storeys.length) return { storeys: [], notes };
  const m = storeys.map((s) => s.mass), T = T1 > 0 ? T1 : 0.1 * storeys.length;   // the rule of thumb of a tenth of a second a storey, until the person gives the period
  const k = uniformFor(m, T), building = shearBuilding({ m, k: m.map(() => k) });
  return { storeys, m, k, T1: T, assumedT1: !(T1 > 0), building, notes, height: storeys.reduce((s, x) => s + x.h, 0) };
}

Storey data from the analysis frame

64-bit

The same storey data when the stiffness comes from the structural frame instead of a typed period.

z = (floor z − base) ÷ 1000 − h ÷ 2; width = extent of the nodes within 150 mm of the floor

Floor heights, masses and the period come from the frame model (hazard-frame.js). Nodes are assigned to a floor with an absolute 150 mm tolerance; area and width are rounded to two decimals.

  • hazard-model.js
  • lines 50 to 64
  • BimCraft 0.8.1
/**
 * 0.8 EG.47 (bim-sim_PART_3): the same `hazardBuilding` shape, but the stiffness comes from the Analysis tab's frame (hazard-frame.js) instead of one period the person
 * types: hazardFrameBuilding(frameModel, ed?, { addKgM2, dir }) → { storeys, building, T1, source: 'frame', ... } | { storeys: [], reason, notes }.
 * Storey names are the model's levels when their count matches the frame's floors; else "Floor n". Mass = the members' weight + addKgM2 × the floor's plan box.
 */
export function hazardFrameBuilding(frameModel, ed = null, { addKgM2 = 500, dir = 'x' } = {}) {
  const fb = frameBuilding(frameModel, { dir, addKgM2 });
  if (!fb.ok) return { storeys: [], reason: fb.reason, notes: fb.notes || [] };
  let names = null;
  if (ed) { try { const lv = modelLinks(ed).levels; if (lv.length - 1 === fb.floors.length || lv.length === fb.floors.length) names = lv.slice(lv.length - fb.floors.length).map((l) => l.name); } catch (e) { names = null; } }
  const ext = (nodes) => { const v = nodes.map((i) => frameModel.nodes[i]), a = dir === 'x' ? v.map((n) => n.y) : v.map((n) => n.x); return Math.max(...a) - Math.min(...a); };
  const storeys = fb.floors.map((f, i) => ({ name: names ? names[i] : 'Floor ' + (i + 1), z: (f.z - fb.base) / 1000 - f.h / 2, h: f.h, area_m2: Math.round(f.area_m2 * 100) / 100, width_m: Math.round(ext(frameModel.nodes.map((n, k) => k).filter((k) => Math.abs(frameModel.nodes[k].z - f.z) <= 150)) / 10) / 100, mass: f.mass }));
  const T1 = fb.modes[0].period;
  return { storeys, m: fb.m, k: fb.k, T1, assumedT1: false, building: fb, notes: fb.notes, height: storeys.reduce((s, x) => s + x.h, 0), source: 'frame' };
}

In detail

Method
z = height of the storey's middle above the lowest floor; mass = area × mass per m²; width = extent of the footprint across the wind direction. With no period given, T1 = 0.1 s × the number of storeys, and one stiffness for every storey is derived from it (uniformFor in hazard.js).
Number types
64-bit numbers.
Tolerances
Frame nodes within 150 mm of a floor's height count as on that floor (absolute).
Limits
The width is rounded to 0.01 m and the area to 0.01 m² in the record handed on. The default period is a rule of thumb and the result says it was assumed.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach