Stores where a plan symbol sits and how it is turned, and moves symbols fixed to a wall when the wall moves.
How the precision is kept
A symbol's point is stored in file units rounded to 6 decimals and its angle in degrees rounded to 4 decimals. A symbol fixed to a wall keeps a distance along the wall and a side, and its point is recomputed on the wall's face in 64-bit millimeters.
Calculations under this policy
3 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.
X, Y = round(mm ÷ unit, 6); Angle = round(radians × 180 ÷ π, 4)
The point is rounded to 6 decimals of the file unit. The angle is converted to degrees and rounded to 4 decimals, and is converted back to radians when read.
The point on a wall's face at a distance along the wall, and the direction the face looks.
at = a + s·u + side × (t ÷ 2)·n; face angle = atan2(side·n_y, side·nₓ)
The wall's direction is normalized with Math.hypot. One expression per coordinate; the wall is treated as straight.
plan-symbols.js
lines 87 to 91
BimCraft 0.8.1
/** The spot on a wall face `side` of the wall at distance `s` along it (the same maths as the Electrical devices, res-elec.js). */
function onWall(wl, s, side) {
const d = [wl.b[0] - wl.a[0], wl.b[1] - wl.a[1]], L = Math.hypot(d[0], d[1]) || 1, u = [d[0] / L, d[1] / L], n = [-u[1], u[0]];
return { at: [wl.a[0] + u[0] * s + n[0] * side * wl.t / 2, wl.a[1] + u[1] * s + n[1] * side * wl.t / 2], base: Math.atan2(n[1] * side, n[0] * side), L };
}
Symbols following a wall
64-bit
Moves a symbol to where its stored side and distance are on the wall now.
turn = atan2(sin Δ, cos Δ), Δ = new face angle − stored face angle
The stored distance is clamped to the wall's length. A symbol that moved less than 0.5 mm and turned less than 1e-6 rad is left as stored, so differences below those limits are not written.
plan-symbols.js
lines 92 to 109
BimCraft 0.8.1
/** Symbols fixed to a wall follow it: put again where the stored side and offset are now. A wall that is gone leaves the symbol where it was. Returns how many moved. */
export function syncPlanSymbols(w, storey) {
const list = readPlanSymbols(w).filter((d) => d.storey === storey && d.wall);
if (!list.length) return 0;
const walls = readWalls(w, storey);
let n = 0;
for (const d of list) {
const wl = walls.find((x) => x.guid === d.wall);
if (!wl) continue;
const g = onWall(wl, Math.min(Math.max(0, d.offset), Math.hypot(wl.b[0] - wl.a[0], wl.b[1] - wl.a[1])), d.side);
let turn = g.base - d.base;
turn = Math.atan2(Math.sin(turn), Math.cos(turn));
if (Math.hypot(g.at[0] - d.at[0], g.at[1] - d.at[1]) < 0.5 && Math.abs(turn) < 1e-6) continue;
w.upsertPset(d.id, PLAN_SYMBOL_PSET, [['X', 'IFCLENGTHMEASURE', round(w.u(g.at[0]))], ['Y', 'IFCLENGTHMEASURE', round(w.u(g.at[1]))], ['Angle', 'IFCREAL', round(((d.angle + turn) * 180) / Math.PI, 4)], ['Base', 'IFCREAL', round((g.base * 180) / Math.PI, 4)]]);
n++;
}
return n;
}
In detail
Method
Point on a wall face = start + along × unit direction + side × half thickness × normal; turn = new face angle − stored face angle, wrapped with atan2(sin, cos).
Number types
64-bit numbers; stored angles are decimal degrees with 4 decimals.
Tolerances
A symbol is rewritten when it moved 0.5 mm or more or turned 1e-6 rad or more.
Limits
An angle read back from the file is on a 0.0001° grid.
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.