Resources Numerical accuracy Calculation policy

Measurements and typed values in the main window

The measured distances and areas, located points, typed lengths and quantity figures that BimCraft's main window shows or writes back to the file.

How the precision is kept

This file mostly passes numbers between modules. Typed lengths are parsed to millimeters and converted in 64-bit; a length written back to the file is rounded to nine decimals of the file's unit. Measured distances, areas and located points start from points picked on the drawn model, whose mesh positions ifc-model.js keeps in a Float32Array.

Open item. Take measured points and face areas from the model's 64-bit geometry instead of the viewer's 32-bit mesh positions.

Calculations under this policy

9 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.

bimcraft.js BimCraft 0.8.1 32-bit in part 9 calculations Reviewed

Tests None found by the review

Distance and area measured in 3D

32-bit in part

The length between two picked points with its X, Y and Z parts, or the area of a picked face.

distance = |b − a|; area = the picked face's area

The two points and the face area come from viewer.measureAt (viewer3d.js), which reads the drawn mesh; ifc-model.js keeps those positions in a Float32Array (finishBatch). The length is then taken in 64-bit by measure.js distance, so the reading carries the 32-bit positions' digits, about 7. The face center, used to place the label, divides each coordinate by the corner count before adding.

  • bimcraft.js
  • lines 3672 to 3680
  • BimCraft 0.8.1
if (meas.mode === 'area') {
  if (!h.face || !h.face.tris.length) { status(t('measure.nothing')); return; }
  const T = h.face.tris, c = [0, 0, 0];
  for (let i = 0; i < T.length; i += 3) for (let k = 0; k < 3; k++) c[k] += T[i + k] / (T.length / 3);
  const it = { kind: 'area', tris: T, center: c, value: h.face.area }; it.text = measurementText(it); meas.items.push(it);
} else if (!meas.a) { meas.a = h.point; status(t('measure.second')); }
else {
  const a = meas.a, b = h.point, d = ifcDelta(a, b);
  const it = { kind: 'distance', a, b, value: distance(a, b), delta: d }; it.text = measurementText(it); meas.items.push(it);

Map position of a picked point

32-bit in part

The map grid coordinates, height, latitude and longitude of a point picked on the model.

picked point → file coordinates → map grid (IfcMapConversion) → latitude and longitude (proj4)

The picked point comes from the same 32-bit mesh positions. From there the chain is 64-bit: viewerToFile and fileToMap (ifc-geo.js), a division by the map unit's size in meters, and the proj4 transformer. The result depends on the datum shift the chosen systems carry.

  • bimcraft.js
  • lines 3751 to 3772
  • BimCraft 0.8.1
const p = viewerToFile(h.point, viewer.modelMatrix(ent.mi), C, g.fileUnitM);
const rows = [];
let lonlat = null, sysName = '', note = '', gridRow = '';
if (g.kind === 'map') {
  const [E, N, H] = fileToMap(g, p);
  crsLib ||= await import('./crs.js');
  if (!crsLib.catalogLoaded()) await crsLib.loadCatalog();
  pickLib ||= await import('./crs-pick.js');
  // 0.8 D8.9: the system the person chose for the model (saved in it) wins; else the EPSG code the file names
  const ms = pickLib.modelSystem(g.crs);
  const crsLabel = (ms && ms.source === 'model' ? ms.crs.name : g.crs.name) + (!ms && g.crs.description && g.crs.description !== g.crs.name ? ' (' + g.crs.description + ')' : '');
  rows.push(t('geo.grid.u', { e: gu(E), n: gu(N), crs: crsLabel }) + ownNote(ms ? ms.crs.unit : (Math.abs((g.mapUnitM || 1) - 0.3048) < 1e-9 ? 'ft' : Math.abs((g.mapUnitM || 1) - 1200 / 3937) < 1e-9 ? 'ftUS' : 'm')));
  rows.push(t('geo.height.u', { h: gu(H), datum: g.crs.verticalDatum || t('geo.datum.unstated') }));
  const entry = ms ? ms.crs : null;
  if (!entry || entry.unit === 'deg') note = t(g.crs.code ? 'geo.crs.unknown' : 'geo.crs.nocode', { crs: g.crs.name });
  else {
    const per = crsLib.METRES_PER[entry.unit] || 1;
    const own = settings.geoCrs === 'file', pick = own ? null : pickLib.systemFor(settings.geoCrs);
    const targetGrid = pick && pick.unit !== 'deg';   // a projected system asked for: lat/long in WGS 84 and the grid of that system besides
    const target = own ? datumOf(entry.proj4) : targetGrid || settings.geoCrs === '4326' ? null : pick;
    const shiftTo = ms.entry?.shiftAccuracy;
    lonlat = crsLib.transformer(entry, target)(E / per, N / per);

Latitude and longitude as text

64-bit

How a located point's latitude and longitude are written in the readout and the label.

round(v × 10ⁿ) ÷ 10ⁿ, then n decimals

Display rounding: 8 decimals of a degree in the text (about 1 mm on the ground) and 6 in the label. The unrounded pair is kept beside the text for copying.

  • bimcraft.js
  • line 3741
  • BimCraft 0.8.1
const fx = (v, n) => (Math.round(v * 10 ** n) / 10 ** n).toFixed(n);

Lengths shown in Properties

64-bit

A length property or quantity shown in the person's display unit.

shown = file value × file unit in mm

The file's own number is kept as it was read in raw; the shown text is that number times the file's unit in millimeters, formatted by model-units.js. Nothing is written back here.

  • bimcraft.js
  • lines 2044 to 2054
  • BimCraft 0.8.1
/** GH.3-A: a length in Properties (a quantity, or a property typed as a length) is shown in the display unit; r.raw keeps the file's own number. */
function lengthRows(p) {
  if (!p?.unitMm) return p;
  const u = getDisplayUnits();
  for (const s of p.sets) for (const r of s.rows) {
    const n = Number(r.value);
    if (!r.len || r.raw !== undefined || r.value === '' || !Number.isFinite(n)) continue;
    r.raw = r.value; r.value = lenMark(n * p.unitMm, u);
  }
  return p;
}

A typed length written to the file

64-bit

Converts a length typed in Properties from the display unit to the file's own unit.

file value = round(mm ÷ file unit × 10⁹) ÷ 10⁹

The typed length is parsed to millimeters, divided by the file's unit and rounded to nine decimals of that unit before it is written. In a file in meters that is 0.000001 mm.

  • bimcraft.js
  • lines 2055 to 2061
  • BimCraft 0.8.1
/** What a person typed into a length row, back in the file's own unit (the editor writes the file's number). */
function lengthToFile(r, p, text) {
  if (r.raw === undefined) return text;
  const mm = parseLen(String(text).trim(), getDisplayUnits());
  if (!Number.isFinite(mm)) throw new Error(t('ifcedit.err.number'));
  return String(Math.round(mm / p.unitMm * 1e9) / 1e9);
}

Numbers in the quantity tables

64-bit

How a total or a quantity is written in the on-screen quantity tables.

Display rounding only: a number that is not whole is shown with at most four decimals (toFixed(4), trailing zeros dropped). The spaces CSV in this file (qtoCsv) passes the areas and volumes on without this rounding.

  • bimcraft.js
  • line 1259
  • BimCraft 0.8.1
const fmtNum = (n) => (Number.isInteger(n) ? String(n) : String(Number(n.toFixed(4))));

Height of a ceiling-mounted item

64-bit

How far above its level a library item is placed when it hangs from the ceiling.

z = max(0, top − item height)

Model millimeters in 64-bit. Without a ceiling over the point the top is the next level up (one more than 1 mm above this level), else 2438.4 mm, which is 8 ft exactly.

  • bimcraft.js
  • lines 4542 to 4550
  • BimCraft 0.8.1
/** 0.6 DC.117: how far above its storey an item sits: 0 on the floor; at the ceiling, its top at the underside of the ceiling
 *  over the point (model-ui.js ceilingZ), else at the level's height (up to the next storey). */
function mountZ(item, at) {
  if (pb.mount?.value !== 'ceiling' || !at) return 0;
  const st = Number(pb.storey.value), b = localBox(item);
  let top = modelUi?.ceilingZ?.(st, at.x, at.y) ?? null;
  if (top === null) { const e = storeyElev(), next = [...storeyElevs.values()].filter((v) => v > e + 1).sort((a, c) => a - c)[0]; top = next !== undefined ? next - e : 2438.4; }
  return Math.max(0, top - b.z1);
}

Clash tolerance as typed

64-bit

Reads the tolerance for the clash check, in meters.

A bare number is read as meters with Number(). A value with a unit mark goes through parseLen to millimeters and is divided by 1000. A negative or non-numeric value is refused.

  • bimcraft.js
  • lines 1642 to 1644
  • BimCraft 0.8.1
const tolText = String(clashTol).trim().replace(',', '.');   // GH.3-A: a bare number is metres (the label says so); a unit mark (2 cm, 1/2") is read in that unit
const tol = /^-?[\d.]+$/.test(tolText) ? Number(tolText) : parseLen(tolText, getDisplayUnits()) / 1000;
if (!Number.isFinite(tol) || tol < 0) { status(t('clash.bad.tolerance'), true); return; }

Offset of an added model

64-bit

The X, Y and Z move typed for a model added beside the first one.

offset in m = typed length in mm ÷ 1000

Each offset is parsed in the display unit to millimeters and divided by 1000; the model's placement keeps it in meters, unrounded.

  • bimcraft.js
  • lines 2292 to 2294
  • BimCraft 0.8.1
const v = [...off.querySelectorAll('input')].map((i) => parseLen(String(i.value).trim(), getDisplayUnits()) / 1000);
if (!v.every(Number.isFinite)) { status(t('fed.offset.bad.u'), true); return; }
m.offset = v;

In detail

Method
Distances and areas from measure.js on points picked in the 3D view; a located point goes through the file's map conversion (ifc-geo.js) and proj4 to latitude and longitude.
Number types
64-bit numbers in this file; the picked points come from 32-bit mesh positions.
Tolerances
A length written to the file is rounded to 1e-9 of the file's unit. A clash tolerance below zero is refused. A level counts as above another when it is more than 1 mm higher.
Limits
Readings taken in the 3D view carry the digits of the 32-bit mesh, about 7. Latitude and longitude are shown to 8 decimals.

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