Resources Numerical accuracy Calculation policy

Library items from IFC types

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.js BimCraft 0.8.1 32-bit in part 4 calculations Reviewed

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.

Every policy that takes this approach