Resources Numerical accuracy Calculation policy

IFC length unit and placement matrices

Finds how many millimeters one length unit of an IFC file is, and multiplies, inverts and applies the 4 × 4 placement matrices used when reading and placing elements.

How the precision is kept

The unit factor is a table value for SI prefixes or the file's own conversion factor times the base unit, in 64-bit. Matrices are plain arrays of 64-bit numbers; the inverse is the general cofactor formula divided by the determinant, refused only when the determinant is exactly zero.

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.

ifc-util.js BimCraft 0.8.1 64-bit 6 calculations Reviewed

Tests bin/test-bimcraft.mjs

Millimeters per unit of one IFC unit

64-bit

The size in millimeters of an SI or conversion-based length unit.

mm per unit = prefix table value, or conversion factor × mm of the base unit

For a conversion-based unit the file's factor is multiplied by the base unit's millimeters in 64-bit: 0.3048 × 1000 gives 304.8. The table values for micro and nano (1e-3, 1e-6) are not exact in binary. An unknown prefix is read as meters.

  • ifc-util.js
  • lines 83 to 102
  • BimCraft 0.8.1
export function unitMm(u) {
  if (!u) return 1000;
  if (u.Name && val(u.Name) === 'METRE' && !u.ConversionFactor) {
    const pre = val(u.Prefix);
    return pre ? (PREFIX[pre] ?? 1000) : 1000;
  }
  if (u.ConversionFactor) {
    const f = u.ConversionFactor;   // IfcMeasureWithUnit: value × its unit (usually SI metre)
    // web-ifc: {ValueComponent, UnitComponent}; cc-ifc: {name:'IFCMEASUREWITHUNIT', value:[factor, unit]}. The unit is a
    // {ref} when it was not inlined; it is the SI metre in every file we write, so a ref reads as metres.
    const arr = Array.isArray(f.value) ? f.value : null;
    const v = Number(arr ? val(arr[0]) : val(f.ValueComponent));
    const uc = arr ? arr[1] : f.UnitComponent;
    const base = uc && !uc.ref && typeof uc === 'object' && (uc.Name || uc.ConversionFactor) ? unitMm(uc) : 1000;
    if (Number.isFinite(v) && v > 0) return v * base;
    const n = String(val(u.Name) || '').toUpperCase();
    return n.includes('FOOT') ? 304.8 : n.includes('INCH') ? 25.4 : 1000;
  }
  return 1000;
}

Length unit of the file

64-bit

The millimeters per length unit of the open file, from its project units.

A lookup of the project's length unit handed to unitMm; nothing is computed here. A file with no length unit returns 1000, the meter.

  • ifc-util.js
  • lines 68 to 82
  • BimCraft 0.8.1
/** mm per length unit of the file (project units; SI metre with prefix, or a conversion-based unit). */
export function lengthUnitMm(api, mid) {
  const proj = ids(api, mid, 'IFCPROJECT')[0];
  let assign = null;
  if (proj) {
    const p = api.GetLine(mid, proj, false);
    if (p.UnitsInContext) assign = api.GetLine(mid, p.UnitsInContext.value, true);
  }
  if (!assign) { const a = ids(api, mid, 'IFCUNITASSIGNMENT')[0]; if (a) assign = api.GetLine(mid, a, true); }
  for (const u of assign?.Units || []) {
    if (!u || val(u.UnitType) !== 'LENGTHUNIT') continue;
    return unitMm(u);
  }
  return 1000;   // IFC's default length unit is the metre
}

Product of two 4 × 4 matrices

64-bit

Two placements combined into one.

(A·B)ᵣ꜀ = Σₖ Aᵣₖ × Bₖ꜀

Four products summed in order for each of the 16 entries, in 64-bit.

  • ifc-util.js
  • lines 104 to 112
  • BimCraft 0.8.1
export function mul4(a, b) {
  const o = new Array(16);
  for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) {
    let s = 0;
    for (let k = 0; k < 4; k++) s += a[k * 4 + r] * b[c * 4 + k];
    o[c * 4 + r] = s;
  }
  return o;
}

A point moved by a matrix

64-bit

A point in one frame as a point in another.

p′ = M · (x, y, z, 1)

Three products and the translation added in order, in 64-bit. When the translation is large, the sum is rounded at the size of the translation.

  • ifc-util.js
  • lines 113 to 115
  • BimCraft 0.8.1
export function apply4(m, x, y, z) {
  return [m[0] * x + m[4] * y + m[8] * z + m[12], m[1] * x + m[5] * y + m[9] * z + m[13], m[2] * x + m[6] * y + m[10] * z + m[14]];
}

Inverse of a 4 × 4 matrix

64-bit

The matrix that undoes a placement.

M⁻¹ = adj(M) ÷ det(M)

Each cofactor is a sum of six triple products, so entries of very different size (a rotation beside a translation of millions of units) lose digits in those sums. A determinant of exactly 0 returns null; there is no test for a nearly singular matrix.

  • ifc-util.js
  • lines 129 to 151
  • BimCraft 0.8.1
export function inv4(m) {
  const a = m, o = new Array(16);
  o[0] = a[5] * a[10] * a[15] - a[5] * a[11] * a[14] - a[9] * a[6] * a[15] + a[9] * a[7] * a[14] + a[13] * a[6] * a[11] - a[13] * a[7] * a[10];
  o[4] = -a[4] * a[10] * a[15] + a[4] * a[11] * a[14] + a[8] * a[6] * a[15] - a[8] * a[7] * a[14] - a[12] * a[6] * a[11] + a[12] * a[7] * a[10];
  o[8] = a[4] * a[9] * a[15] - a[4] * a[11] * a[13] - a[8] * a[5] * a[15] + a[8] * a[7] * a[13] + a[12] * a[5] * a[11] - a[12] * a[7] * a[9];
  o[12] = -a[4] * a[9] * a[14] + a[4] * a[10] * a[13] + a[8] * a[5] * a[14] - a[8] * a[6] * a[13] - a[12] * a[5] * a[10] + a[12] * a[6] * a[9];
  o[1] = -a[1] * a[10] * a[15] + a[1] * a[11] * a[14] + a[9] * a[2] * a[15] - a[9] * a[3] * a[14] - a[13] * a[2] * a[11] + a[13] * a[3] * a[10];
  o[5] = a[0] * a[10] * a[15] - a[0] * a[11] * a[14] - a[8] * a[2] * a[15] + a[8] * a[3] * a[14] + a[12] * a[2] * a[11] - a[12] * a[3] * a[10];
  o[9] = -a[0] * a[9] * a[15] + a[0] * a[11] * a[13] + a[8] * a[1] * a[15] - a[8] * a[3] * a[13] - a[12] * a[1] * a[11] + a[12] * a[3] * a[9];
  o[13] = a[0] * a[9] * a[14] - a[0] * a[10] * a[13] - a[8] * a[1] * a[14] + a[8] * a[2] * a[13] + a[12] * a[1] * a[10] - a[12] * a[2] * a[9];
  o[2] = a[1] * a[6] * a[15] - a[1] * a[7] * a[14] - a[5] * a[2] * a[15] + a[5] * a[3] * a[14] + a[13] * a[2] * a[7] - a[13] * a[3] * a[6];
  o[6] = -a[0] * a[6] * a[15] + a[0] * a[7] * a[14] + a[4] * a[2] * a[15] - a[4] * a[3] * a[14] - a[12] * a[2] * a[7] + a[12] * a[3] * a[6];
  o[10] = a[0] * a[5] * a[15] - a[0] * a[7] * a[13] - a[4] * a[1] * a[15] + a[4] * a[3] * a[13] + a[12] * a[1] * a[7] - a[12] * a[3] * a[5];
  o[14] = -a[0] * a[5] * a[14] + a[0] * a[6] * a[13] + a[4] * a[1] * a[14] - a[4] * a[2] * a[13] - a[12] * a[1] * a[6] + a[12] * a[2] * a[5];
  o[3] = -a[1] * a[6] * a[11] + a[1] * a[7] * a[10] + a[5] * a[2] * a[11] - a[5] * a[3] * a[10] - a[9] * a[2] * a[7] + a[9] * a[3] * a[6];
  o[7] = a[0] * a[6] * a[11] - a[0] * a[7] * a[10] - a[4] * a[2] * a[11] + a[4] * a[3] * a[10] + a[8] * a[2] * a[7] - a[8] * a[3] * a[6];
  o[11] = -a[0] * a[5] * a[11] + a[0] * a[7] * a[9] + a[4] * a[1] * a[11] - a[4] * a[3] * a[9] - a[8] * a[1] * a[7] + a[8] * a[3] * a[5];
  o[15] = a[0] * a[5] * a[10] - a[0] * a[6] * a[9] - a[4] * a[1] * a[10] + a[4] * a[2] * a[9] + a[8] * a[1] * a[6] - a[8] * a[2] * a[5];
  const det = a[0] * o[0] + a[1] * o[4] + a[2] * o[8] + a[3] * o[12];
  if (!det) return null;
  for (let i = 0; i < 16; i++) o[i] /= det;
  return o;
}

Where a federated model goes

64-bit

The matrix that puts a second model in the first model's coordinates, moved by an offset.

M = T(offset) · C_ref · C⁻¹

Two matrix products and one inverse in 64-bit. The offset in IFC axes (Z up) is entered as (x, z, −y) in the viewer's axes. A C that cannot be inverted is not guarded against here.

  • ifc-util.js
  • lines 117 to 127
  • BimCraft 0.8.1
/**
 * Federation (0.4 B16): where model k goes in the scene. Each model is loaded moved to the origin (web-ifc
 * COORDINATE_TO_ORIGIN, its coordination matrix C); undoing C and applying the first model's (refC) puts it in the first
 * model's coordinates, then `offset` [x, y, z] (metres, IFC axes: Z up) moves it — web-ifc's viewer space is Y up, so
 * IFC (x, y, z) → viewer (x, z, −y). Column-major 4 × 4.
 */
export function placementMatrix(refC, C, offset = [0, 0, 0]) {
  const [dx, dy, dz] = offset;
  const T = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, dx, dz, -dy, 1];
  return mul4(T, mul4(refC || C, inv4(C)));
}

In detail

Method
SI unit with prefix: a table (milli 1, centi 10, deci 100, none 1000, kilo 1e6 and so on). Conversion-based unit: its factor × the millimeters of its base unit; when the factor is missing, 304.8 for a name containing FOOT and 25.4 for INCH. Matrix inverse by cofactors.
Number types
64-bit numbers in plain arrays (column-major).
Tolerances
None. The inverse tests the determinant against exactly 0.
Limits
A nearly singular matrix is inverted without warning. A file with no length unit is read as meters.

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