Resources Numerical accuracy Calculation policy

Room, floor and ceiling quantities

Writes the shape, areas, perimeter and volume of rooms, floor slabs and ceilings found from the drawn walls.

How the precision is kept

Areas and perimeters are computed in 64-bit square millimeters and millimeters from the room or building loop, then stored through the writer's rounding (6 decimals of the file unit for lengths, 1e-6 m² and 1e-6 m³ for areas and volumes). A shape is rewritten only when a short hash of its inputs, rounded to 0.01 mm, changes.

Open item. Use atan(6 ÷ 12) in degrees at full precision as the default roof pitch instead of 26.565.

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.

model-slabs.js BimCraft 0.8.1 64-bit 6 calculations Reviewed

Tests tools/bimcraft-test/res-test.mjs, tools/bimcraft-test/mlwall-test.mjs

Room quantities

64-bit

Height, perimeters, floor areas and volume of a room.

h = elevation above − elevation; gross area = |shoelace(outline)|; volume = net area × h

The net area and net perimeter come from the loop found by model-rooms.js; the gross area and gross perimeter are computed here from the loop's outer outline. Volume is one product of net area and height. The seed point is stored rounded to 6 decimals of the file unit.

  • model-slabs.js
  • lines 128 to 136
  • BimCraft 0.8.1
function writeSpace(w, id, loop, lv) {
  const h = lv.above ? lv.above.elevation - lv.level.elevation : lv.level.height;
  const key = shapeKey(loop.poly, loop.holes, h);
  w.setShape(id, [w.rep('body', 'Body', 'SweptSolid', [w.extrude(w.profileVoids(loop.poly, loop.holes), 0, h)])]);
  const a = loop.area;
  w.upsertQto(id, 'Qto_SpaceBaseQuantities', [['Height', 'length', h], ['NetPerimeter', 'length', loop.perimeter], ['GrossPerimeter', 'length', perim(loop.poly)], ['NetFloorArea', 'area', a], ['GrossFloorArea', 'area', Math.abs(loopArea(loop.poly))], ['NetVolume', 'volume', a * h]]);
  const sd = labelPoint(loop.poly);
  w.upsertPset(id, ROOM_PSET, [['SeedX', 'IFCLENGTHMEASURE', round(w.u(sd[0]))], ['SeedY', 'IFCLENGTHMEASURE', round(w.u(sd[1]))], ['ShapeKey', 'IFCIDENTIFIER', key]]);
}

Floor slab quantities

64-bit

Thickness, perimeter, areas and volume of a floor slab.

net area = |A(outline)| − Σ |A(hole)|; volume = net area × thickness; solid from offset − thickness, depth = thickness

Hole areas are subtracted from the outline area in 64-bit mm². The perimeter is the outer outline only. Nothing is written when the change key matches the stored one.

  • model-slabs.js
  • lines 151 to 161
  • BimCraft 0.8.1
function writeFloor(w, f, loop) {
  const poly = loop.poly, holes = loop.holes || [];
  const key = shapeKey(poly, holes, f.thickness, f.offset);
  if (f.key === key) return false;
  w.setShape(f.id, [w.rep('body', 'Body', 'SweptSolid', [w.extrude(w.profileVoids(poly, holes), f.offset - f.thickness, f.thickness)])]);
  const a = Math.abs(loopArea(poly)) - holes.reduce((s, h) => s + Math.abs(loopArea(h)), 0);
  w.upsertQto(f.id, 'Qto_SlabBaseQuantities', [['Width', 'length', f.thickness], ['Perimeter', 'length', perim(poly)], ['GrossArea', 'area', Math.abs(loopArea(poly))], ['NetArea', 'area', a], ['NetVolume', 'volume', a * f.thickness]]);
  const sd = labelPoint(poly);
  w.upsertPset(f.id, FLOOR_PSET, [['SeedX', 'IFCLENGTHMEASURE', round(w.u(sd[0]))], ['SeedY', 'IFCLENGTHMEASURE', round(w.u(sd[1]))], ['ShapeKey', 'IFCIDENTIFIER', key]]);
  return true;
}

Ceiling quantities

64-bit

Thickness and areas of a ceiling.

net area = |A(outline)| − Σ |A(hole)|

Same area arithmetic as the floor slab. The solid starts at the ceiling height and is as deep as its thickness. Nothing is written when the change key matches the stored one.

  • model-slabs.js
  • lines 162 to 172
  • BimCraft 0.8.1
function writeCeiling(w, c, loop) {
  const poly = loop.poly, holes = loop.holes || [];
  const key = shapeKey(poly, holes, c.height, c.thickness);
  if (c.key === key) return false;
  w.setShape(c.id, [w.rep('body', 'Body', 'SweptSolid', [w.extrude(w.profileVoids(poly, holes), c.height, c.thickness)])]);
  const a = Math.abs(loopArea(poly)) - holes.reduce((s, h) => s + Math.abs(loopArea(h)), 0);
  w.upsertQto(c.id, 'Qto_CoveringBaseQuantities', [['Width', 'length', c.thickness], ['GrossArea', 'area', Math.abs(loopArea(poly))], ['NetArea', 'area', a]]);
  const sd = labelPoint(poly);
  w.upsertPset(c.id, CEILING_PSET, [['SeedX', 'IFCLENGTHMEASURE', round(w.u(sd[0]))], ['SeedY', 'IFCLENGTHMEASURE', round(w.u(sd[1]))], ['ShapeKey', 'IFCIDENTIFIER', key]]);
  return true;
}

Change key of a shape

64-bit

A short key of what a shape was made from, used to decide whether to write it again.

numbers rounded to 0.01 mm → text → 32-bit FNV-1a hash + text length

Every number is rounded with Math.round(v × 100) ÷ 100 before the text is hashed with Math.imul in 32 bits. The key only decides whether shape and quantities are rewritten; it is not a stored measurement. Inputs that differ by less than the rounding step, or that collide in the 32-bit hash, give the same key.

  • model-slabs.js
  • lines 117 to 123
  • BimCraft 0.8.1
/** A short hash of what a shape is made from (rounded to 0.01 mm). */
function shapeKey(...parts) {
  const s = JSON.stringify(parts, (k, v) => (typeof v === 'number' ? Math.round(v * 100) / 100 : v));
  let h = 2166136261;
  for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); }
  return (h >>> 0).toString(36) + s.length.toString(36);
}

Height a roof sits at

64-bit

The top of the tallest wall under a roof.

top = max(base offset + height) over the walls, else the level's height

A maximum of 64-bit sums starting from 0, so walls whose tops are all at or below 0 fall back to the level's height.

  • model-slabs.js
  • line 250
  • BimCraft 0.8.1
const top = L.walls.reduce((m, x) => Math.max(m, x.baseOffset + x.height), 0) || wallsLv.height;

Default roof sizes

64-bit

The pitch, overhang and thickness a new roof gets when none are given.

pitch = 26.565°; overhang = 12 × 25.4 mm; thickness = 8 × 25.4 mm

The overhang and thickness are exact products of 25.4. The pitch is the literal 26.565; atan(6 ÷ 12) is 26.56505…°, so the default is about 0.00005° short of a 6 in 12 slope. PitchDegrees is stored without rounding.

  • model-slabs.js
  • line 216
  • BimCraft 0.8.1
export function addRoof(w, { storey, seed, pitch = 26.565, overhang = 12 * IN, gables = [], thickness = 8 * IN, guid, edges = '', rake = null, plate = 0, fascia = ROOF_DEFAULTS.fascia, soffit = true } = {}) {

In detail

Method
Shoelace area of the loop minus the areas of its holes; volume = net area × height or thickness; room height = elevation of the level above − elevation of the level, else the level's own height.
Number types
64-bit numbers; the change key is a 32-bit hash. Default sizes in inches are multiplied by 25.4, which is exact.
Tolerances
Change detection rounds every input number to 0.01 mm before hashing.
Limits
A change smaller than the 0.01 mm rounding step may leave the stored shape and quantities unchanged. Two different shapes that hash to the same 32-bit key would be treated as unchanged. The default roof pitch is the literal 26.565°, while atan(6 ÷ 12) is 26.56505…°.

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