Resources Numerical accuracy Calculation policy

Piping in an IFC file as PCF components

Reads pipe segments, fittings and valves from an IFC file and gives their end points, bores, bend angles and center points in millimeters.

How the precision is kept

End points come from the port placements or from the two ends of the extruded body, through the element's world matrix, and are multiplied by the file's length unit times 1000 to give millimeters. All of it is 64-bit. The turn angle of a bend is rounded to 0.01 degree before it is kept.

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.

ifc/pipes.js BimCraft 0.8.1 64-bit 4 calculations Reviewed

Tests bin/test-ifc43.mjs

Position and direction of a port

64-bit

Where a port is, in millimeters, and the way it faces.

p = M · (0, 0, 0) × mm; z = normalize(M · (0, 0, 1) direction)

The port's world matrix gives the point, which is multiplied by the file's length unit × 1000. The direction is the matrix's Z column, normalized.

  • ifc/pipes.js
  • line 66
  • BimCraft 0.8.1
const portPoint = (pid) => { const m = g.placement(ref(g.a(pid, 'ObjectPlacement'))); return { p: apply(m, 0, 0, 0).map((v) => v * mm), z: norm(applyDir(m, 0, 0, 1)) }; };

Ends and bore from the extruded body

64-bit

The two ends of a pipe segment and its bore from its extruded solid.

a = T · 0; b = T · (direction × depth); bore = 2 × radius × mm

T is the element's world matrix times the solid's position. The far end is the extrusion direction times the depth, transformed; the bore is twice the circle profile's radius, or 0 for any other profile.

  • ifc/pipes.js
  • lines 67 to 81
  • BimCraft 0.8.1
const bodyOf = (id) => {   // { a, b, bore } from the extruded body, or null
  const pds = ref(g.a(id, 'Representation'));
  if (pds === null) return null;
  const M = g.placement(ref(g.a(id, 'ObjectPlacement')));
  for (const sr of refs(g.a(pds, 'Representations'))) {
    for (const item of refs(g.a(sr, 'Items'))) {
      if (file.type(item) !== 'IFCEXTRUDEDAREASOLID') continue;
      const pos = g.axis2(ref(g.a(item, 'Position')));
      const T = mul(M, pos);
      const d = g.dir(ref(g.a(item, 'ExtrudedDirection')), [0, 0, 1]);
      const depth = g.num(g.a(item, 'Depth'), 0);
      const prof = ref(g.a(item, 'SweptArea'));
      const t = prof !== null ? file.type(prof) : null;
      const r = t === 'IFCCIRCLEPROFILEDEF' || t === 'IFCCIRCLEHOLLOWPROFILEDEF' ? g.num(g.a(prof, 'Radius'), 0) : 0;
      return { a: apply(T, 0, 0, 0).map((v) => v * mm), b: apply(T, d[0] * depth, d[1] * depth, d[2] * depth).map((v) => v * mm), bore: 2 * r * mm };

Turn angle of a fitting

64-bit

The angle a two-port fitting turns the pipe through.

turn = 180 − acos(p · q) × 180 ÷ π, rounded to 2 decimals

The dot product of the two outward unit directions is clamped to [−1, 1] before acos. For a nearly straight fitting the dot product is near −1, where acos loses digits. The result is rounded to 0.01 degree with toFixed(2), and that rounded value is what the component keeps.

  • ifc/pipes.js
  • lines 120 to 121
  • BimCraft 0.8.1
const between = (Math.acos(Math.max(-1, Math.min(1, dot(p.d, q.d)))) * 180) / Math.PI;   // 180° apart = straight
const turn = +(180 - between).toFixed(2);   // the angle the pipe turns through

Center point of a bend

64-bit

The corner where the axes of a bend's two ports meet.

s = (b e − c d) ÷ (a c − b²), t = (a e − b d) ÷ (a c − b²); center = midpoint of the two closest points

The closest points of the two axis lines are solved in closed form. The denominator a c − b² is the squared sine of the angle between the unit directions, so it is small for a slight bend and the corner then has few good digits. The solve is skipped, and the midpoint of the ports used, when the cross product is shorter than 1e-6.

  • ifc/pipes.js
  • lines 123 to 129
  • BimCraft 0.8.1
const n = cross(p.d, q.d);
let centre = mid;
if (len(n) > 1e-6) {   // lines p.p + s·p.d and q.p + t·q.d meet at the corner
  const w = sub(p.p, q.p), a = dot(p.d, p.d), b = dot(p.d, q.d), c = dot(q.d, q.d), d = dot(p.d, w), e = dot(q.d, w), den = a * c - b * b;
  const s = (b * e - c * d) / den, t = (a * e - b * d) / den;
  centre = scale(add(add(p.p, scale(p.d, s)), add(q.p, scale(q.d, t))), 0.5);
}

In detail

Method
World matrix × local point; turn angle from the dot product of the two outward port directions; bend center as the midpoint of the closest points of the two port axes.
Number types
64-bit numbers.
Tolerances
A segment or valve shorter than 0.01 mm is left out. Port axes are treated as parallel when their cross product is shorter than 1e-6. A two-port fitting is a bend when it turns more than 1 degree, and an elbow when within 1 degree of 90.
Limits
Only the first extruded solid of an element is used for its ends and bore. A fitting without ports is left out.

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