Turns an IFC type or element into a library item: its body mesh, its width, depth and height, its plan outline and its sizes from properties, in millimeters.
How the precision is kept
The body comes from productTriangles (ifc-util.js), whose vertices start as 32-bit values from the IFC engine. They are taken back into the element's own placement with a 64-bit inverse matrix, multiplied by the millimeters per file unit and rounded to four decimals of a millimeter before they are stored in the item. Width, depth and height are the box of those rounded points.
Open item. Build the body from the IFC engine's 64-bit mesh positions instead of the 32-bit vertex array that productTriangles reads.
Calculations under this policy
4 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.
libitem-ifc.jsBimCraft 0.8.1 32-bit in part4 calculationsReviewed
Tests bin/test-bimcraft.mjs
Body mesh in the item's own frame
32-bit in part
The item's 3D shape in millimeters, relative to its own placement.
p_item = round(W⁻¹ · p × mm, 4 decimals)
The positions come from productTriangles, so they carry the 24 significant bits of the engine's 32-bit vertex array. The inverse placement and the unit factor are 64-bit; each coordinate is then rounded with Math.round(v × 10000) ÷ 10000 and stored.
libitem-ifc.js
lines 88 to 99
BimCraft 0.8.1
const W = ol.ObjectPlacement ? api.GetWorldTransformMatrix(mid, ol.ObjectPlacement.value) : null;
const Wi = W ? inv4(W) : null;
const tri = productTriangles(api, mid, occ, mm);
// i18n-ignore: tools/library/convert.mjs prints this without a language file (log: For the owner)
if (tri.indices.length / 3 > LIMITS.triangles) notes.push(c.name + ': ' + tri.indices.length / 3 + ' triangles, over the limit; no body.');
else if (tri.indices.length) {
const P = tri.positions, out = [];
for (let i = 0; i < P.length; i += 3) {
const [x, y, z] = Wi ? apply4(Wi, P[i], P[i + 1], P[i + 2]) : [P[i], P[i + 1], P[i + 2]];
out.push(r4(x * mm), r4(y * mm), r4(z * mm));
}
it.body = { type: 'mesh', positions: out, indices: tri.indices };
Width, depth and height
32-bit in part
The item's overall size from its body.
width = max x − min x; depth = max y − min y; height = max z − min z
Differences of the stored, already rounded body coordinates, rounded again to four decimals of a millimeter.
libitem-ifc.js
lines 100 to 102
BimCraft 0.8.1
let b = [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];
for (let i = 0; i < out.length; i += 3) for (let k = 0; k < 3; k++) { b[k] = Math.min(b[k], out[i + k]); b[k + 3] = Math.max(b[k + 3], out[i + k]); }
it.params.width = r4(b[3] - b[0]); it.params.depth = r4(b[4] - b[1]); it.params.height = r4(b[5] - b[2]);
Plan outline from the type's footprint
64-bit
The item's 2D plan symbol, from the type's FootPrint or Plan curves.
(x, y) = round(coordinate × mm, 4 decimals)
Coordinates are read from the file with Number(), multiplied by the unit factor and rounded to four decimals of a millimeter. They do not pass through the 32-bit mesh.
libitem-ifc.js
lines 174 to 196
BimCraft 0.8.1
/** The type's FootPrint / Plan curves (IfcPolyline / IfcIndexedPolyCurve lines) in mm, or null. */
function footprint(api, mid, typeId, mm) {
const tl = api.GetLine(mid, typeId, true);
const out = [];
for (const rm of tl.RepresentationMaps || []) {
const rep = rm?.MappedRepresentation;
if (!rep || !/^(FootPrint|Plan)$/i.test(val(rep.RepresentationIdentifier) || '')) continue;
const curves = [];
for (const item of rep.Items || []) {
if (item?.Elements) curves.push(...item.Elements); else curves.push(item);
}
for (const cv of curves) {
let pts = null;
if (cv?.Points && Array.isArray(cv.Points)) pts = cv.Points.map((p) => (p.Coordinates || []).map((q) => Number(val(q))));
else if (cv?.Points?.CoordList && !cv.Segments) pts = cv.Points.CoordList.map((c) => c.map((q) => Number(val(q))));
if (!pts || pts.length < 2) continue;
const P = pts.map((p) => [r4(p[0] * mm), r4(p[1] * mm)]);
const closed = P.length > 2 && P[0][0] === P[P.length - 1][0] && P[0][1] === P[P.length - 1][1];
out.push({ type: 'poly', layer: 'EQUIP', color: null, pts: closed ? P.slice(0, -1) : P, closed });
}
}
return out.length ? out : null;
}
Sizes from properties
64-bit
Width, depth and height from the type's properties when the body gave none.
size = round(value × mm, 4 decimals) for a length measure
A size already set from the body is kept. Otherwise a positive property value is multiplied by the unit factor when its type name contains LENGTH or is empty, and rounded to four decimals.
libitem-ifc.js
lines 134 to 137
BimCraft 0.8.1
for (const [k, names] of [['width', ['Width', 'NominalWidth', 'NominalLength']], ['depth', ['Depth', 'NominalDepth']], ['height', ['Height', 'NominalHeight']]]) {
const p = get(...names);
if (p && typeof p.v === 'number' && p.v > 0 && !it.params[k]) it.params[k] = r4(/LENGTH|POSITIVELENGTH/.test(p.type) || p.type === '' ? p.v * mm : p.v);
}
In detail
Method
Body point = W⁻¹ · p × mm, rounded to 0.0001 mm. Size = max − min of the rounded points. Plan = the type's FootPrint or Plan polylines × mm, else the convex hull of the body points.
Number types
Positions from a 32-bit source, carried in 64-bit; stored as plain numbers rounded to four decimals.
Tolerances
A footprint polyline is closed when its first and last rounded points are equal.
Limits
The triangles are taken in the file's coordinates and brought back with the inverse placement, so for an element far from the origin the subtraction happens at that size. A property size is multiplied by the unit factor only when its IFC type is a length measure or is not given.
Approach: 32-bit step, flagged
One step stores or exchanges 32-bit values that reach a number a person reads. It is listed here with its open item, and the step is either moved to 64-bit or the app will warn where the number is read.