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