Turns the millimeter values the building tools work in into the coordinates, directions, extrusion depths and quantities stored in the IFC file.
How the precision is kept
The tools compute in 64-bit millimeters. On the way into the file every length is divided by the file's length unit and rounded to 6 decimals of that unit, and every direction component is rounded to 12 decimals. Areas and volumes are stored as square and cubic meters rounded to 6 decimals. The rounding, not the arithmetic, sets how many digits a stored value keeps.
Open item. Store coordinates, depths and quantities without the 6-decimal rounding, or round to a fixed number of significant digits instead of a fixed number of decimals.
Calculations under this policy
8 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.
Rounds a number to a given count of decimals before it is written to the file.
round(v, d) = Math.round(v × 10ᵈ) ÷ 10ᵈ, d = 6 by default
The value is multiplied by 10^d, rounded to a whole number and divided again, so the result is the nearest 64-bit number to a d-decimal value; negative zero is returned as 0. Every length, offset and seed point the building tools store passes through it with d = 6, so the stored value keeps 6 decimals of the file unit rather than all its 64-bit digits.
The origin and X direction of an element's placement from a point in millimeters and an angle in degrees.
location = round(mm ÷ unit, 6); X direction = (round(cos θ, 12), round(sin θ, 12), 0), θ = degrees × π ÷ 180
The three coordinates are divided by the file unit and rounded to 6 decimals. The angle arrives in degrees and is converted back to radians, then its cosine and sine are rounded to 12 decimals, so a stored direction is within 5e-13 of a unit vector component.
A placement with its own Z and X directions, used for sloped parts.
The origin is rounded to 6 decimals of the file unit and each component of the two direction vectors to 12 decimals. The function does not normalize the vectors or check that they are perpendicular; it stores what the caller passes.
model-write.js
lines 130 to 134
BimCraft 0.8.1
/** An IfcAxis2Placement3D at o (mm) with its Z along zDir and X along xDir (unit vectors). */
axisFrame(o, zDir, xDir) {
const r = (v) => round(v, 12);
return this.make('IFCAXIS2PLACEMENT3D', this.H(this.point3(round(this.u(o[0])), round(this.u(o[1])), round(this.u(o[2])))), this.H(this.dir(r(zDir[0]), r(zDir[1]), r(zDir[2]))), this.H(this.dir(r(xDir[0]), r(xDir[1]), r(xDir[2]))));
}
Quantity values
64-bit
Converts a length, area or volume in millimeter units to the value stored in a quantity set.
length = round(mm ÷ unit, 6); area = round(mm² ÷ 10⁶, 6); volume = round(mm³ ÷ 10⁹, 6)
Lengths use the file's length unit. Areas are divided by 1e6 and volumes by 1e9, which gives m² and m³ whatever the file's length unit is, and both are rounded to 6 decimals: 1 mm² for an area and 1000 mm³ for a volume. Counts are stored unchanged.
Converts a placement matrix from file units to millimeters.
translation × unit in mm; rotation part unchanged
Only the three translation entries are multiplied by the unit factor; one 64-bit multiplication each.
model-write.js
lines 398 to 403
BimCraft 0.8.1
/** A world matrix in file units → mm (translation scaled). */
export function scaleT(M, unitMm) {
const o = M.slice();
o[12] *= unitMm; o[13] *= unitMm; o[14] *= unitMm;
return o;
}
Placement relative to a parent
64-bit
Where an element sits in the axes of its level or site.
M = P⁻¹ × C
Both world matrices are read from the IFC library, scaled to millimeters, and combined with the general 4×4 inverse and product in ifc-util.js. For a model far from the origin the translation of the result is a difference of two large coordinates.
model-write.js
lines 91 to 95
BimCraft 0.8.1
/** A placement's matrix relative to another one's (mm): where `child` sits in `parent`'s axes. */
relMatrix(parentLp, childLp) {
const P = scaleT(this.api.GetWorldTransformMatrix(this.mid, parentLp), this.unitMm), C = scaleT(this.api.GetWorldTransformMatrix(this.mid, childLp), this.unitMm);
return mul4(inv4(P), C);
}
In detail
Method
round(v, d) = Math.round(v × 10^d) ÷ 10^d, applied to each coordinate, depth and quantity as it is written; placements relative to a parent come from the product of one inverse world matrix and one world matrix.
Number types
64-bit numbers; matrices are plain arrays of 16 numbers.
Tolerances
No comparisons. Rounding steps: 6 decimals of the file's length unit for lengths, 12 decimals for direction components, 1e-6 m² for areas, 1e-6 m³ for volumes.
Limits
In a file whose length unit is the meter a stored coordinate is on a 0.001 mm grid; in a millimeter file on a 1e-6 mm grid. An area is stored to 1 mm² and a volume to 1000 mm³. The area and volume conversion divides by 1e6 and 1e9 and does not use the file's length unit.
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.