Electrical devices: positions, loads and circuit amps
Places sockets, switches and lights on wall faces or ceilings, keeps them on their wall when it moves, and totals the load and current of each circuit.
How the precision is kept
A device on a wall is stored as a distance along the wall and a side; its point is recomputed on the wall face in 64-bit millimeters and stored rounded to 6 decimals, its angle to 4 decimals of a degree. Circuit current is the summed watts divided by a nominal voltage chosen from the device kind and the unit system, shown to one decimal.
Open item. Divide a three-phase circuit's watts by √3 × volts, or label the amps column as watts ÷ volts.
Calculations under this policy
7 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.
The wall face nearest the pointer, the distance along it and the direction the device faces.
s = clamp((p − a)·u, 0, L); distance to face = ||(p − a)·n| − t ÷ 2|; angle = atan2(side·n_y, side·nₓ)
Dot products with the wall's unit direction and normal. The along distance is clamped to the wall; the pointer may be up to one wall thickness past either end. The nearest face within the reach wins.
res-elec.js
lines 111 to 126
BimCraft 0.8.1
/** The nearest wall face within `reach` of p: the device's spot on it and the way it faces (into the room). */
export function wallSpot(walls, p, reach = 600) {
let best = null;
for (const w of walls) {
const d = [w.b[0] - w.a[0], w.b[1] - w.a[1]], L = Math.hypot(...d);
if (L < 1) continue;
const u = [d[0] / L, d[1] / L], n = [-u[1], u[0]];
const s = Math.max(0, Math.min(L, (p[0] - w.a[0]) * u[0] + (p[1] - w.a[1]) * u[1]));
const off = (p[0] - w.a[0]) * n[0] + (p[1] - w.a[1]) * n[1];
const side = off >= 0 ? 1 : -1, dist = Math.abs(Math.abs(off) - w.t / 2);
const along = (p[0] - w.a[0]) * u[0] + (p[1] - w.a[1]) * u[1];
if (along < -w.t || along > L + w.t || dist > reach) continue;
if (!best || dist < best.dist) best = { wall: w.guid, side, offset: s, dist, at: spotOn(w, s, side), angle: Math.atan2(n[1] * side, n[0] * side) };
}
return best;
}
Point on a wall face
64-bit
The plan point on a wall's face at a distance along the wall.
at = a + s·u + side × (t ÷ 2)·n
One expression per coordinate from the wall's ends in 64-bit millimeters; the wall is treated as straight.
res-elec.js
lines 127 to 130
BimCraft 0.8.1
function spotOn(w, s, side) {
const d = [w.b[0] - w.a[0], w.b[1] - w.a[1]], L = Math.hypot(...d) || 1, u = [d[0] / L, d[1] / L], n = [-u[1], u[0]];
return [w.a[0] + u[0] * s + n[0] * side * w.t / 2, w.a[1] + u[1] * s + n[1] * side * w.t / 2];
}
Stored device position and body
64-bit
The device's placement, box and the X, Y and Angle kept for it.
The stored offset is clamped to the wall's length before the point is computed. The angle is kept in degrees rounded to 4 decimals and converted back to radians when read. The body is a box of fixed size centered on the mounting height.
res-elec.js
lines 172 to 189
BimCraft 0.8.1
// ------------------------------------------------------------------------------------------------------------ writing
function writeDevice(w, d, walls) {
const D = DEVICES[d.kind];
let at = d.at, ang = d.angle;
const wall = d.wall ? walls.find((x) => x.guid === d.wall) : null;
if (wall) { const L = Math.hypot(wall.b[0] - wall.a[0], wall.b[1] - wall.a[1]); const s = Math.min(Math.max(0, d.offset), L); at = spotOn(wall, s, d.side); const n = [-(wall.b[1] - wall.a[1]) / L, (wall.b[0] - wall.a[0]) / L]; ang = Math.atan2(n[1] * d.side, n[0] * d.side); }
// local axes: x along the wall (the device's right), y out into the room; the placement turns +x to (sin, −cos)·…
const deg = (ang * 180) / Math.PI - 90;
const l = w.api.GetLine(w.mid, d.id, false);
w.movePlacement(l.ObjectPlacement.value, at[0], at[1], 0, deg);
const b = D.body;
const solid = D.mount === 'wall' ? w.box(-b.w / 2, 0, d.height - b.h / 2, b.w / 2, b.d, d.height + b.h / 2) : w.box(-b.w / 2, -b.d / 2, d.height - b.h, b.w / 2, b.d / 2, d.height);
const color = isBoard(d.kind) ? [0.55, 0.57, 0.6] : isLight(d.kind) ? [0.98, 0.95, 0.75] : [0.95, 0.95, 0.93];
w.styleItem(solid, w.surfaceStyle(tr(isBoard(d.kind) ? 'res.style.panel' : 'res.style.device'), color, 0));
w.setShape(d.id, [w.rep('body', 'Body', 'SweptSolid', [solid])]);
w.upsertPset(d.id, ELEC_PSET, [['X', 'IFCLENGTHMEASURE', round(w.u(at[0]))], ['Y', 'IFCLENGTHMEASURE', round(w.u(at[1]))], ['Angle', 'IFCREAL', round((ang * 180) / Math.PI, 4)]]);
return { at, angle: ang };
}
Ceiling height of a level
64-bit
The height ceiling-mounted devices are placed at.
middle value of the sorted wall heights; else max(2000, level height − 300)
The wall heights are sorted and the element at index floor(n ÷ 2) is taken, which for an even count is the upper of the two middle values. Without walls the level's height less 300 mm is used, at least 2000 mm.
res-elec.js
lines 164 to 170
BimCraft 0.8.1
/** The ceiling of a level: its walls' usual height, else the level's height less 300 (a floor's depth). */
function ceilingOf(w, storey) {
const ws = readWalls(w, storey);
if (ws.length) { const hs = ws.map((x) => x.height).sort((a, b) => a - b); return hs[Math.floor(hs.length / 2)]; }
const lv = readLevels(w).find((l) => l.id === storey);
return Math.max(2000, (lv?.height || 2700) - 300);
}
Nominal voltage of a device
Exact
The voltage used for a circuit's current, from the device kind and the unit system.
metric: 230 V, three-phase socket 400 V; feet and inches: 120 V, cooker, heater, charger and three-phase socket 240 V
Fixed nominal values chosen by kind and unit system; nothing is measured or typed.
res-elec.js
lines 77 to 78
BimCraft 0.8.1
/** Volts for the circuit schedule's amps: 120 / 240 in a feet-and-inches model, 230 / 400 in a metric one. */
export const voltsOf = (kind, units) => (kind === 'socket.3ph' ? (isMetric(units) ? 400 : 240) : /^(cooker|heater|evcharger)$/.test(kind) ? (isMetric(units) ? 230 : 240) : (isMetric(units) ? 230 : 120));
Circuit schedule
64-bit
The device count, load, current and voltage of each circuit.
watts = Σ load; volts = highest nominal voltage on the circuit; amps = watts ÷ volts, to 1 decimal
Loads are summed in order and the amps are one division, shown with toFixed(1); watts are shown rounded to whole numbers. The same formula is used for the three-phase socket at 400 V or 240 V, so its current is not divided by √3. Volts are never 0 because every kind has a nominal value.
res-elec.js
lines 287 to 301
BimCraft 0.8.1
export function circuitSchedule(devs, { units = 'ftin', t = tr } = {}) {
const map = new Map();
for (const d of devs) {
if (isBoard(d.kind)) continue;
const c = d.circuit || '';
if (!map.has(c)) map.set(c, { circuit: c, n: 0, watts: 0, volts: 0, kinds: new Map() });
const r = map.get(c);
r.n++; r.watts += d.load || 0; r.volts = Math.max(r.volts, voltsOf(d.kind, units));
r.kinds.set(d.kind, (r.kinds.get(d.kind) || 0) + 1);
}
const list = [...map.values()].sort((a, b) => (a.circuit === '') - (b.circuit === '') || cmpTag(a.circuit, b.circuit));
const rows = list.map((r) => [r.circuit || t('res.el.nocircuit'), String(r.n), [...r.kinds].map(([k, n]) => n + ' × ' + t('res.el.kind.' + k)).join(', '), String(Math.round(r.watts)), (r.watts / r.volts).toFixed(1), String(r.volts)]); // i18n-ignore: a count
const watts = list.reduce((s, r) => s + r.watts, 0);
return { head: [t('res.el.sch.circuit'), t('res.el.sch.count'), t('res.el.sch.devices'), t('res.el.sch.load'), t('res.el.sch.amps'), t('res.el.sch.volts')], rows, total: [t('model.sch.total'), String(list.reduce((s, r) => s + r.n, 0)), '', String(Math.round(watts)), '', ''] };
}
Device schedule total
64-bit
The rows of the device schedule and the total load.
total watts = round(Σ load)
Loads are summed unrounded and the total is rounded to a whole watt for display; each row's load is rounded the same way, so the rows as shown need not add up to the total as shown.
Projection of the pointer on each wall, nearest face within reach; point = start + along × direction ± half thickness × normal; amps = watts ÷ volts.
Number types
64-bit numbers; loads stored to 3 decimals of a watt.
Tolerances
Wall reach 600 mm by default. A device is rewritten when its point moved 0.5 mm or more. Walls shorter than 1 mm are ignored.
Limits
Voltages are nominal: 120 or 240 V in a feet-and-inches model, 230 or 400 V in a metric one. The three-phase socket's current is also watts ÷ volts, with no √3 and no power factor. The ceiling height used for ceiling devices is the middle value of the level's wall heights.
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.