Lays out grid lines by spacing and angle, finds where they cross for snapping, and decides which lines an elevation sees end on.
How the precision is kept
Line positions are running 64-bit sums of the spacings, turned by one cosine and sine. Crossings come from the two-line parametric solve with a relative test for parallel lines. Points are moved between the story's plan, the building's axes and the grid's own axes with 64-bit 4 × 4 matrices; the shared model writer rounds the stored points to 6 decimals of the file's unit.
Calculations under this policy
6 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.
Each line's position is the one before plus a spacing, a running sum, so rounding can build up over many lines of a spacing that is not exact in binary. The angle in degrees is multiplied by π ÷ 180 for one cosine and sine.
model-grids.js
lines 61 to 74
BimCraft 0.8.1
export function gridLayout({ origin = [0, 0], angle = 0, u = {}, v = {}, extend = 1500 } = {}) {
const at = (sp, n) => { const out = [0]; for (let i = 1; i < n; i++) out.push(out[i - 1] + (Array.isArray(sp) ? Number(sp[i - 1] ?? sp[sp.length - 1]) : Number(sp))); return out; };
const nu = Math.max(0, Math.floor(u.count || 0)), nv = Math.max(0, Math.floor(v.count || 0));
const xs = at(u.spacing ?? 6000, nu), ys = at(v.spacing ?? 6000, nv);
const xMax = xs.length ? xs[xs.length - 1] : 0, yMax = ys.length ? ys[ys.length - 1] : 0;
const c = Math.cos(angle * Math.PI / 180), s = Math.sin(angle * Math.PI / 180);
const P = (x, y) => [origin[0] + x * c - y * s, origin[1] + x * s + y * c];
const out = [];
let tg = u.first || '1';
for (const x of xs) { out.push({ tag: tg, a: P(x, -extend), b: P(x, yMax + extend), fam: 'u' }); tg = nextTag(tg); }
tg = v.first || 'A';
for (const y of ys) { out.push({ tag: tg, a: P(-extend, y), b: P(xMax + extend, y), fam: 'v' }); tg = nextTag(tg); }
return out;
}
Where grid lines cross
64-bit
The crossing points of every pair of grid lines, for snapping.
den = r × s; t = (q − p) × s ÷ den; point = p + t r
The parallel test is scaled by both lines' lengths, so it is a limit on the sine of the angle between them (1e-6). The parameters are accepted within 1e-6 beyond the segment's ends; the point is computed from the first line.
model-grids.js
lines 123 to 134
BimCraft 0.8.1
export function gridSnap(axes) {
const lines = axes.map((x) => ({ a: x.a, b: x.b })), points = [];
for (let i = 0; i < axes.length; i++) for (let j = i + 1; j < axes.length; j++) {
const p = axes[i].a, r = [axes[i].b[0] - p[0], axes[i].b[1] - p[1]], q = axes[j].a, s = [axes[j].b[0] - q[0], axes[j].b[1] - q[1]];
const den = r[0] * s[1] - r[1] * s[0];
if (Math.abs(den) < 1e-6 * Math.hypot(...r) * Math.hypot(...s)) continue;
const t = ((q[0] - p[0]) * s[1] - (q[1] - p[1]) * s[0]) / den, u = ((q[0] - p[0]) * r[1] - (q[1] - p[1]) * r[0]) / den;
if (t < -1e-6 || t > 1 + 1e-6 || u < -1e-6 || u > 1 + 1e-6) continue;
points.push([p[0] + r[0] * t, p[1] + r[1] * t]);
}
return { lines, points };
}
Grid lines an elevation shows
64-bit
The lines that run within 30° of a view's direction, and where they sit across the view.
kept when |e · d| ÷ |e| ≥ 0.866; x = a · right
0.866 stands for cos 30°, a rounded figure that sets the limit at about 30.003°. The view direction is divided by its length without a check for zero.
model-grids.js
lines 136 to 144
BimCraft 0.8.1
export function gridsInView(axes, dir) {
const l = Math.hypot(dir[0], dir[1]), d = [dir[0] / l, dir[1] / l], right = [d[1], -d[0]], out = [];
for (const x of axes) {
const e = [x.b[0] - x.a[0], x.b[1] - x.a[1]], m = Math.hypot(...e);
if (!m || Math.abs(e[0] * d[0] + e[1] * d[1]) / m < 0.866) continue;
out.push({ tag: x.tag, x: x.a[0] * right[0] + x.a[1] * right[1] });
}
return out.sort((p, q) => p.x - q.x);
}
Is the grid rectangular
64-bit
A yes or no that decides the grid's type in the file.
parallel when |d₁ × d₂| < 1e-6; square when |u · v| < 1e-6
Unit directions are compared with an absolute 1e-6 on their cross product (parallel) and dot product (square).
A grid line's two ends in building millimeters, from its polyline and the grid's placement.
world = M × (x × unit, y × unit, 0)
Coordinates are multiplied by the millimeters per file unit and taken through the grid's world matrix in 64-bit. A line stored against its sense is turned round.
Crossing of p + t r and q + u s: t = (q − p) × s ÷ (r × s).
Number types
64-bit numbers in millimeters.
Tolerances
Lines are parallel when |r × s| < 1e-6 × |r| × |s|. A crossing counts when t and u are within 1e-6 of [0, 1]. A grid is rectangular when unit directions are parallel or square within 1e-6. A line under 100 mm is refused. A line is seen end on when the cosine to the view direction is at least 0.866.
Checked by
tools/bimcraft-test/draw06-test.mjs calls gridLayout, gridSnap, gridsInView, readGrids and placeGrid in Node.
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.