Resources Numerical accuracy Calculation policy

Element quantities for carbon and cost

Reads each element's volume, area and length and splits the volume between its materials, as the embodied carbon and cost calculations use them.

How the precision is kept

Quantities stated in the file are used as 64-bit numbers exactly as written. Where an element has no stated volume, the volume of its triangle mesh is summed in 64-bit. Volumes are split between materials by 64-bit ratios.

Open item. Sum the mesh volume from a point on the body, such as its first corner, instead of the coordinate origin.

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.

carbon-calc.js BimCraft 0.8.1 64-bit 5 calculations Reviewed

Tests tools/bimcraft-test/eg-carbon-test.mjs, tools/bimcraft-test/lifecycle-test.mjs

Which stated quantity is used

64-bit

Picks an element's volume, area or length from the quantities the file states.

Returns the first named quantity that is finite and above zero, in the listed order (NetVolume before GrossVolume, and so on). The number is the file's own, as qtoOf read it; no unit conversion is applied to it.

  • carbon-calc.js
  • line 48
  • BimCraft 0.8.1
const first = (q, names) => { for (const n of names) if (Number.isFinite(q[n]) && q[n] > 0) return q[n]; return undefined; };

Volume of a mesh

64-bit

The volume of an element from its triangles, used when the file states none.

V = |Σ a·(b × c)| ÷ 6 × (unit ÷ 1000)³

Signed tetrahedra from the coordinate origin, summed in order in 64-bit. For a body far from the origin each term is large and the terms cancel, which costs digits; the sum is not taken from a point on the body. The unit factor is cubed by two multiplications.

  • carbon-calc.js
  • lines 50 to 59
  • BimCraft 0.8.1
/** Volume of a closed triangle mesh in m³ (signed tetrahedra); `positions` in file units. */
export function meshVolume(positions, indices, unitMm) {
  let v = 0;
  for (let i = 0; i < indices.length; i += 3) {
    const a = indices[i] * 3, b = indices[i + 1] * 3, c = indices[i + 2] * 3, P = positions;
    v += P[a] * (P[b + 1] * P[c + 2] - P[b + 2] * P[c + 1]) - P[a + 1] * (P[b] * P[c + 2] - P[b + 2] * P[c]) + P[a + 2] * (P[b] * P[c + 1] - P[b + 1] * P[c]);
  }
  const k = unitMm / 1000;
  return Math.abs(v) / 6 * k * k * k;
}

Share of the volume per material

64-bit

How an element's volume is divided between the materials it is made of.

layer share = tᵢ ÷ Σt; constituent share = fᵢ ÷ Σf; otherwise 1 ÷ n

Shares are 64-bit ratios. Layers with the same material name are added together. When the thicknesses or the fractions sum to zero the volume is split equally. The shares are not forced to add up to exactly 1.

  • carbon-calc.js
  • lines 61 to 87
  • BimCraft 0.8.1
/** The materials a material definition names, with the share of the volume each takes: [{ name, share }]. */
function materialsOf(w, def, depth = 0) {
  const l = def ? get(w, def) : null;
  if (!l || depth > 3) return [];
  const type = w.api.GetNameFromTypeCode(l.type).toUpperCase();
  if (type === 'IFCMATERIAL') { const n = val(l.Name); return n ? [{ name: String(n), share: 1 }] : []; }
  if (type === 'IFCMATERIALLIST') return (l.Materials || []).map((h) => ({ name: val(get(w, hid(h))?.Name), share: 0 })).filter((m) => m.name).map((m, _, a) => ({ ...m, share: 1 / a.length }));
  if (type === 'IFCMATERIALLAYERSETUSAGE') return materialsOf(w, hid(l.ForLayerSet), depth + 1);
  if (type === 'IFCMATERIALLAYERSET') {
    const layers = (l.MaterialLayers || []).map((h) => get(w, hid(h))).filter(Boolean).map((y) => ({ name: val(get(w, hid(y.Material))?.Name), t: Number(val(y.LayerThickness)) || 0 })).filter((y) => y.name);
    const sum = layers.reduce((s, y) => s + y.t, 0);
    const byName = new Map();
    layers.forEach((y) => byName.set(y.name, (byName.get(y.name) || 0) + (sum > 0 ? y.t / sum : 1 / layers.length)));
    return [...byName].map(([name, share]) => ({ name: String(name), share }));
  }
  if (type === 'IFCMATERIALCONSTITUENTSET') {
    const c = (l.MaterialConstituents || []).map((h) => get(w, hid(h))).filter(Boolean).map((y) => ({ name: val(get(w, hid(y.Material))?.Name), f: Number(val(y.Fraction)) })).filter((y) => y.name);
    const fs = c.reduce((s, y) => s + (y.f > 0 ? y.f : 0), 0);
    return c.map((y) => ({ name: String(y.name), share: fs > 0 ? (y.f > 0 ? y.f / fs : 0) : 1 / c.length }));
  }
  if (type === 'IFCMATERIALPROFILESETUSAGE') return materialsOf(w, hid(l.ForProfileSet), depth + 1);
  if (type === 'IFCMATERIALPROFILESET') {
    const p = (l.MaterialProfiles || []).map((h) => val(get(w, hid(get(w, hid(h))?.Material))?.Name)).filter(Boolean);
    return p.map((n) => ({ name: String(n), share: 1 / p.length }));
  }
  return [];
}

Floor area of the building

64-bit

The floor area the carbon total is divided by when the person has not typed one.

area = Σ space floor areas, else Σ floor slab areas

A plain 64-bit sum of the stated space areas in file order. Only when that sum is zero are the floor and base slabs' stated areas summed instead.

  • carbon-calc.js
  • lines 89 to 100
  • BimCraft 0.8.1
export function floorAreaOf(w) {
  let sum = 0;
  for (const id of tryIds(w.api, w.mid, 'IFCSPACE')) { const a = first(qtoOf(w, id), ['NetFloorArea', 'GrossFloorArea']); if (a) sum += a; }
  if (sum > 0) return sum;
  for (const id of tryIds(w.api, w.mid, 'IFCSLAB', true)) {
    const l = get(w, id);
    if (!l || !/^(FLOOR|BASESLAB)$/.test(String(val(l.PredefinedType) || ''))) continue;
    const a = first(qtoOf(w, id), ['GrossArea', 'NetArea', 'GrossFootprintArea', 'Area']);
    if (a) sum += a;
  }
  return sum;
}

Quantities per material part

64-bit

The volume, area and length handed on for each material of an element.

part volume = element volume × share

The stated volume wins; the mesh volume is used only when the file gives none. Only the volume is multiplied by the share: the element's whole area and length go to every material part.

  • carbon-calc.js
  • lines 119 to 126
  • BimCraft 0.8.1
let volume = first(q, VOL), qsource = 'qto';
if (volume === undefined && geometry) {
  try { const t = productTriangles(api, mid, id, w.unitMm); if (t.indices.length) { volume = meshVolume(t.positions, t.indices, w.unitMm); qsource = 'geometry'; } } catch { /* no shape */ }
}
if (!Number.isFinite(volume)) { volume = undefined; qsource = 'none'; }
const area = first(q, AREA), length = first(q, LEN);
const props = (() => { try { return w.readPset(id, PSET_ELEMENT) || {}; } catch { return {}; } })();
const parts = (mats.length ? mats : [{ name: '', share: 1 }]).map((m) => ({ material: m.name, volume: volume === undefined ? undefined : volume * m.share, area, length, count: 1 }));

In detail

Method
Stated quantities from the element's quantity sets, first match in a fixed order of names; otherwise the mesh volume by signed tetrahedra. Layered materials share the volume by layer thickness, constituents by their fractions, lists and profiles equally.
Number types
64-bit numbers; mesh positions in the plain array ifc-util.js productTriangles builds.
Limits
A stated quantity is taken as the file gives it, with no unit conversion. The mesh volume is only a volume when the mesh is closed and wound one way; this is not checked. Area and length are not split between materials.

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