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