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.jsBimCraft 0.8.1 32-bit in part9 calculationsReviewed
TestsNone 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.
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.
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.