Resources Numerical accuracy Calculation policy

IFC export

Writes the steel parts, holes, copes, mitre cuts, bolts, footings, bars and grids of the model as an IFC4 file.

How the precision is kept

Geometry is taken from the built model in 64-bit, but every real number in the file is written with six decimals: coordinates and sizes in millimeters, weights in kilograms, and also the components of direction vectors. Values smaller than 1e-9 are written as zero, and a value that is not finite is written as zero as well.

Open item. Write direction vectors, and preferably all reals, with full 64-bit precision instead of six decimals.

Calculations under this policy

10 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.js DetailCraft 0.8.1 64-bit 10 calculations Reviewed

Tests bin/test-detailcraft.mjs

A real number in the file

64-bit

How every length, coordinate, direction component and weight is written.

text = toFixed(6), trailing zeros dropped; |v| < 1e-9 → 0

The value is rounded to six decimals with toFixed before it is written, whatever it measures. For a coordinate in millimeters that is a millionth of a millimeter; for a component of a unit direction it is six digits. A value that is not finite is written as 0. with no warning.

  • ifc.js
  • lines 49 to 56
  • DetailCraft 0.8.1
/** A STEP real: always with a decimal point. */
export function R(v) {
  if (!Number.isFinite(v)) return '0.';
  let s = Number((Math.abs(v) < 1e-9 ? 0 : v).toFixed(6)).toString();
  if (s.includes('e')) { const [m, e] = s.split('e'); s = (m.includes('.') ? m : m + '.') + 'E' + e; }
  else if (!s.includes('.')) s += '.';
  return s;
}

Storey of an element

64-bit

Which storey an element is put in: the highest level at or below its lowest point.

level z ≤ lowest point + 0.5 mm

An absolute 0.5 mm allowance on the height comparison.

  • ifc.js
  • line 113
  • DetailCraft 0.8.1
const storeyFor = (z) => { let s = storeys[0]; for (const t of storeys) if (t.lv.z <= z + 0.5) s = t; return s; };

Placement of a part

64-bit

The origin and the two axes each part is placed with, relative to its storey.

location = (p_x, p_y, p_z − level z); Axis = part x; RefDirection = part u

One subtraction for the height, then the point and both unit axes go through the six-decimal writer. The same direction text is written once and shared.

  • ifc.js
  • lines 115 to 119
  • DetailCraft 0.8.1
/** Local placement relative to a storey: origin p (world), axis z, ref x. */
const place = (st, p, axis, ref) => {
  const loc = w.pt([p[0], p[1], p[2] - st.lv.z]);
  return w.add('IFCLOCALPLACEMENT(' + st.lp + ',' + w.add('IFCAXIS2PLACEMENT3D(' + loc + ',' + w.dir(axis) + ',' + w.dir(ref) + ')') + ')');
};

Profile dimensions

64-bit

The section dimensions written for each profile type.

The profile's stored dimensions are written as they are, to six decimals. A round tube's radius is its diameter divided by 2.

  • ifc.js
  • lines 127 to 138
  • DetailCraft 0.8.1
const profileDef = (p, voff) => {
  const pos = pos2(0, voff), nm = S(p.name);
  switch (p.kind) {
    case 'I': return w.add('IFCISHAPEPROFILEDEF(.AREA.,' + nm + ',' + pos + ',' + [p.bf, p.d, p.tw, p.tf].map(R).join(',') + ',' + (p.r > 0 ? R(p.r) : '$') + ',$,$)');
    case 'C': return w.add('IFCUSHAPEPROFILEDEF(.AREA.,' + nm + ',' + pos + ',' + [p.d, p.bf, p.tw, p.tf].map(R).join(',') + ',$,$,$)');
    case 'L': return w.add('IFCLSHAPEPROFILEDEF(.AREA.,' + nm + ',' + pos + ',' + [p.b, p.b, p.t].map(R).join(',') + ',$,$,$)');
    case 'RHS': return w.add('IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,' + nm + ',' + pos + ',' + [p.b, p.h, p.t, p.ri, p.ro].map(R).join(',') + ')');
    case 'CHS': return w.add('IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,' + nm + ',' + pos + ',' + R(p.D / 2) + ',' + R(p.t) + ')');
    case 'PL': return w.add('IFCRECTANGLEPROFILEDEF(.AREA.,' + nm + ',' + pos + ',' + R(p.b) + ',' + R(p.t) + ')');
    default: throw new Error(t('ifcout.err.profile', { kind: p.kind }));
  }
};

Mitre cut plane

64-bit

The plane that clips a part's extrusion at a mitre end.

normal = unit(−sg·ku, −sg·kv, sg); point = (0, voff, x0)

The normal is normalized in 64-bit and then written with six decimals per component, like every direction.

  • ifc.js
  • lines 154 to 156
  • DetailCraft 0.8.1
const sg = end === 'start' ? -1 : 1, nrm = norm([-sg * c.ku, -sg * c.kv, sg]);
const pl = w.add('IFCPLANE(' + w.add('IFCAXIS2PLACEMENT3D(' + w.pt([0, part.voff, c.x0]) + ',' + w.dir(nrm) + ',' + w.dir(anyPerp(nrm)) + ')') + ')');
solid = w.add('IFCBOOLEANCLIPPINGRESULT(.DIFFERENCE.,' + solid + ',' + w.add('IFCHALFSPACESOLID(' + pl + ',.F.)') + ')');

Hole openings

64-bit

The position, direction, depth and radius of each hole's opening.

depth = thickness + 2; start = center − axis·depth ÷ 2; radius = d ÷ 2

The opening is centered on the material and made 2 mm longer than the thickness, 1 mm out of each face. The hole's diameter and position are the stored values.

  • ifc.js
  • lines 169 to 174
  • DetailCraft 0.8.1
if (h.face === 'web') { c = [0, h.y + part.voff, h.x]; axis = [1, 0, 0]; depth = (p.kind === 'RHS' ? p.b : p.tw) + 2; }
else if (h.face === 'plate') { c = [h.y, part.voff, h.x]; axis = [0, 1, 0]; depth = p.t + 2; }
else { const sg = h.face === 'top' ? 1 : -1, t = p.kind === 'RHS' ? p.t : p.tf; c = [h.y, part.voff + sg * (p.depth / 2 - t / 2), h.x]; axis = [0, 1, 0]; depth = t + 2; }
const start = sub(c, mul(axis, depth / 2));
const olp = w.add('IFCLOCALPLACEMENT(' + lp + ',' + w.add('IFCAXIS2PLACEMENT3D(' + w.pt(start) + ',' + w.dir(axis) + ',' + w.dir(anyPerp(axis)) + ')') + ')');
const circ = w.add('IFCCIRCLEPROFILEDEF(.AREA.,$,' + pos2(0, 0) + ',' + R(h.d / 2) + ')');

Cope openings

64-bit

The box that cuts each cope out of a part.

center v = voff ± (depth ÷ 2 − cope depth ÷ 2 + 0.5); box = (width + 2) × (cope depth + 1) × (cope length + 1)

The box is 1 mm oversize on the sides where it leaves the part. Its inner faces work out to the cope's stored length and depth: the half millimeter in the center and the extra millimeter in the size cancel on the inside.

  • ifc.js
  • lines 181 to 185
  • DetailCraft 0.8.1
const vC = part.voff + sg * (p.depth / 2 - c.depth / 2 + 0.5);
const x0 = c.end === 'start' ? -1 : part.length - c.length;
const olp = w.add('IFCLOCALPLACEMENT(' + lp + ',' + w.add('IFCAXIS2PLACEMENT3D(' + w.pt([0, vC, x0]) + ',$,$)') + ')');
const rect = w.add('IFCRECTANGLEPROFILEDEF(.AREA.,$,' + pos2(0, 0) + ',' + R(p.width + 2) + ',' + R(c.depth + 1) + ')');
const op = w.add('IFCOPENINGELEMENT(' + S(seed('cope' + part.id + ':' + i)) + ",$,'Cope',$,$," + olp + ',' + shape([extrude(rect, c.length + 1)]) + ',$,.OPENING.)');

Bolts and anchor rods

64-bit

The placement, diameter and length written for each bolt and rod.

Head position, axis, diameter and length are the built values, written to six decimals. The second axis of the placement is any direction square to the bolt.

  • ifc.js
  • lines 205 to 209
  • DetailCraft 0.8.1
const st = storeyFor(Math.min(b.head[2], b.head[2] + b.axis[2] * b.length));
const lp = place(st, b.head, b.axis, anyPerp(b.axis));
const circ = w.add('IFCCIRCLEPROFILEDEF(.AREA.,$,' + pos2(0, 0) + ',' + R(b.d / 2) + ')');
const label = (b.kind === 'ANCHORBOLT' ? 'Anchor rod ' : 'Bolt ') + b.d + ' x ' + b.length + ' ' + b.grade;   // i18n-ignore (IFC element name, a data format)
const id = w.add('IFCMECHANICALFASTENER(' + S(seed('bolt' + (b.conn || b.loose) + ':' + i)) + ',$,' + S(label) + ',$,' + S(b.grade) + ',' + lp + ',' + shape([extrude(circ, b.length)]) + ',$,' + R(b.d) + ',' + R(b.length) + ',' + E(b.kind) + ')');

Reinforcing bar center lines

64-bit

The center line of each placed bar as lines and true arcs, with the bar's radius.

point = origin + s·ŝ + t·t̂; arc through start, middle and end; radius = d_b ÷ 2

Bends are written as three-point arcs, not as short straight pieces. The bar length written beside the shape is the schedule's cut length, not a length measured from this curve.

  • ifc.js
  • lines 229 to 240
  • DetailCraft 0.8.1
const Wp = ([s, t]) => add(add(set.origin, mul(set.sDir, s)), mul(set.tDir, t));
const pts = [Wp(segs[0].a)];
const idx = [];
for (const sg of segs) {
  const i0 = pts.length;
  if (sg.k === 'L') { pts.push(Wp(sg.b)); idx.push('IFCLINEINDEX((' + i0 + ',' + (i0 + 1) + '))'); }
  else { pts.push(Wp(sg.m), Wp(sg.b)); idx.push('IFCARCINDEX((' + i0 + ',' + (i0 + 1) + ',' + (i0 + 2) + '))'); }
}
const rel = pts.map((p) => [p[0], p[1], p[2] - st.lv.z]);
const list = w.add('IFCCARTESIANPOINTLIST3D((' + rel.map((p) => '(' + p.map(R).join(',') + ')').join(',') + '),$)');
const curve = w.add('IFCINDEXEDPOLYCURVE(' + list + ',(' + idx.join(',') + '),.F.)');
const disk = w.add('IFCSWEPTDISKSOLID(' + curve + ',' + R(b.db / 2) + ',$,$,$)');

Length and weight properties

64-bit

The cut length and weight written in each part's property set.

The built part's length and weight are written through the six-decimal writer; whole numbers such as the phase go through Math.round.

  • ifc.js
  • line 268
  • DetailCraft 0.8.1
const ps = props.map(([n, t, v]) => w.add('IFCPROPERTYSINGLEVALUE(' + S(n) + ',$,' + t + '(' + (t === 'IFCINTEGER' ? String(Math.round(v)) : typeof v === 'number' ? R(v) : S(v)) + '),$)'));

In detail

Method
Each part is an extruded parametric profile placed by its frame; holes and copes are openings; mitre ends are half-space clippings; bars are swept disks along lines and true arcs.
Number types
64-bit numbers, written as decimal text with at most six decimals.
Tolerances
Storey assignment: 0.5 mm above a level still counts as on it. The file declares a geometric precision of 1.E-05.
Limits
A unit direction rounded to six decimals per component can point off by up to about one millionth of a radian, which moves the far end of a 10 m part by up to about 0.01 mm. Openings are made 1 mm oversize where they leave the part so that they cut cleanly.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach