Resources Numerical accuracy Calculation policy

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.

res-elec.js BimCraft 0.8.1 64-bit 7 calculations Reviewed

Tests tools/bimcraft-test/res-test.mjs

Device spot on the nearest wall face

64-bit

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.

placement angle = angle × 180 ÷ π − 90; X, Y = round(mm ÷ unit, 6); Angle = round(degrees, 4)

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.

  • res-elec.js
  • lines 280 to 286
  • BimCraft 0.8.1
// ------------------------------------------------------------------------------------------------------------ schedules
export function deviceSchedule(devs, levels, { fmtLen = (mm) => String(Math.round(mm)), t = tr } = {}) {
  const byGuid = new Map(devs.map((d) => [d.guid, d]));
  const rows = devs.map((d) => [d.tag, t('res.el.kind.' + d.kind), levels.find((l) => l.id === d.storey)?.name || '', d.circuit, d.load ? String(Math.round(d.load)) : '', fmtLen(d.height), d.controls.map((g) => byGuid.get(g)?.tag).filter(Boolean).join(', ')]);
  const watts = devs.reduce((s, d) => s + (d.load || 0), 0);
  return { head: [t('res.el.sch.tag'), t('res.el.sch.device'), t('res.el.sch.level'), t('res.el.sch.circuit'), t('res.el.sch.load'), t('res.el.sch.height'), t('res.el.sch.controls')], rows, total: [t('model.sch.total'), String(devs.length), '', '', String(Math.round(watts)), '', ''] };
}

In detail

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

Every policy that takes this approach