Resources Numerical accuracy Calculation policy

Installation loads and cable lengths

Adds up the load on each electrical circuit of a building plan, works out its current, and measures each circuit's cable along the drawn routes.

How the precision is kept

Loads are attribute text parsed to 64-bit numbers and summed per circuit; the current is one division. Cable routes are a graph of the drawn route segments; lengths are Math.hypot of 64-bit coordinates, summed over the edges on the shortest paths, then multiplied by the meters per drawing unit. Nothing is rounded in this file.

Calculations under this policy

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

addons/electrical/lib/install.js CadCraft 0.8.2 64-bit 10 calculations Reviewed

Tests None found by the review

Meters per drawing unit

Exact

The factor that turns a drawing length into meters.

inch 0.0254; foot 0.3048; yard 0.9144; mm 0.001; cm 0.01; m 1

0.0254, 0.3048 and 0.9144 are the exact definitions. Lengths are multiplied by the factor; 0.001 and 0.01 have no exact binary form, so a millimeter drawing is multiplied by the nearest 64-bit number to 0.001 instead of being divided by 1000.

  • addons/electrical/lib/install.js
  • lines 24 to 25
  • CadCraft 0.8.2
/** Metres per drawing unit (doc.meta.units, the DXF $INSUNITS names; unitless = millimetres, as CadCraft's 3D reads it). */
export const UNIT_M = { '': 0.001, inches: 0.0254, feet: 0.3048, millimeters: 0.001, centimeters: 0.01, meters: 1, yards: 0.9144 };

Number read from an attribute

64-bit

Turns attribute text such as a load, voltage or breaker rating into a number.

The first comma is replaced by a point and parseFloat reads the leading number, ignoring any text after it. A value with both a thousands point and a decimal comma, such as 1.234,5, is read as 1.234.

  • addons/electrical/lib/install.js
  • line 120
  • CadCraft 0.8.2
const num = (v) => { const n = parseFloat(String(v ?? '').replace(',', '.')); return Number.isFinite(n) ? n : NaN; };

Load of a library item

64-bit

The watts of a placed library item from its kW, amps and volts, or horsepower attributes.

W = kW × 1000; or A × V (× √3 for three phase); or hp × 746

√3 is Math.sqrt(3). The amps × volts form applies no power factor. 746 W per horsepower is the electrical horsepower; the mechanical horsepower is 745.6999 W.

  • addons/electrical/lib/install.js
  • lines 122 to 131
  • CadCraft 0.8.2
/** Library items placed by CadCraft (CC-ELEC-* attributes) as loads: watts or null. */
export function libraryLoad(attrs) {
  const kw = num(attrs['CC-ELEC-KW']);
  if (kw > 0) return kw * 1000;
  const amps = num(attrs['CC-ELEC-AMPS']), volts = num(attrs['CC-ELEC-VOLTS']);
  if (amps > 0 && volts > 0) { const ph = num(attrs['CC-ELEC-PHASE']); return amps * volts * (ph === 3 ? Math.sqrt(3) : 1); }
  const hp = num(attrs['CC-ELEC-HP']);
  if (hp > 0) return hp * 746;
  return null;
}

Load per circuit

64-bit

The total watts on each circuit and the circuit's current.

watts = Σ load of the devices on the circuit

A plain sum in the order the loads were found, with no demand factor. A circuit with no definition takes its board's voltage; otherwise 230 V is the default.

  • addons/electrical/lib/install.js
  • lines 172 to 176
  • CadCraft 0.8.2
for (const l of loads) if (l.circuit) { const c = get(l.circuit); c.loads.push(l); c.watts += l.watts; }
for (const c of circuits.values()) {
  c.board = boards.find((b) => b.tag && b.tag === c.boardTag) || (boards.length === 1 ? boards[0] : null);
  if (!c.def && c.board) c.voltage = c.board.voltage;
  c.amps = currentOf(c);

Circuit current

64-bit

The current a circuit draws.

I = W ÷ (V × pf); three phase: I = W ÷ (√3 × V × pf)

One 64-bit expression with Math.sqrt(3). A voltage or power factor of 0 falls back to 230 and 1, so the division is guarded. The power factor was clamped to 0.1 to 1 when the circuit was read.

  • addons/electrical/lib/install.js
  • lines 189 to 194
  • CadCraft 0.8.2
/** Current drawn by a circuit (A): single phase W / (V·pf); three phase ("3P", "L1L2L3") W / (√3·V·pf). */
export function currentOf(c) {
  const three = /^(3P|L1L2L3|3)$/i.test(c.phase);
  const v = c.voltage || 230;
  return c.watts / ((three ? Math.sqrt(3) : 1) * v * (c.pf || 1));
}

RCD trip current from text

64-bit

Reads the milliamps a circuit's RCD text names.

The first number followed by mA is read with parseFloat after a decimal comma is turned into a point. A value written in amps is not read, as the comment says.

  • addons/electrical/lib/install.js
  • lines 181 to 185
  • CadCraft 0.8.2
/** The trip current (mA) a circuit's RCD text names ("30 mA", "RCD1 0.03 A" is not read: write mA), or null. */
export function rcdMilliamps(text) {
  const m = /(\d+(?:[.,]\d+)?)\s*mA\b/i.exec(String(text || ''));
  return m ? parseFloat(m[1].replace(',', '.')) : null;
}

Route network

64-bit

Builds the network of cable routes: nodes, and edges with their lengths, split where one route's end lands on another.

split point = a + (b − a) × u; edge length = √(Δx² + Δy²)

Points within 1e-6 of an existing node become that node, found by a linear search, so the first node within the tolerance wins. Split points are interpolated along the segment, and each edge length is Math.hypot of its two end points.

  • addons/electrical/lib/install.js
  • lines 223 to 248
  • CadCraft 0.8.2
/** The route network of one space: nodes and edges, routes split where an end lands on another route. */
export function routeGraph(routes, { tol = 1e-6 } = {}) {
  const nodes = [], edges = [];
  const nodeAt = (p) => {
    for (const n of nodes) if (Math.hypot(n.x - p.x, n.y - p.y) <= tol) return n.i;
    nodes.push({ i: nodes.length, x: p.x, y: p.y, adj: [] });
    return nodes.length - 1;
  };
  // segments, with the extra points that land on them (other routes' ends)
  const segs = [];
  for (const r of routes) for (let i = 0; i + 1 < r.pts.length; i++) segs.push({ a: r.pts[i], b: r.pts[i + 1], cuts: [] });
  const ends = routes.flatMap((r) => [r.pts[0], r.pts[r.pts.length - 1]]);
  for (const s of segs) for (const p of ends) { const q = segDist2(p, s.a, s.b); if (q.d2 <= Math.max(tol, 1e-9) ** 2 && q.u > 1e-9 && q.u < 1 - 1e-9) s.cuts.push(q.u); }
  for (const s of segs) {
    const us = [0, ...[...new Set(s.cuts)].sort((x, y) => x - y), 1];
    for (let k = 0; k + 1 < us.length; k++) {
      const p = { x: s.a.x + (s.b.x - s.a.x) * us[k], y: s.a.y + (s.b.y - s.a.y) * us[k] }, q = { x: s.a.x + (s.b.x - s.a.x) * us[k + 1], y: s.a.y + (s.b.y - s.a.y) * us[k + 1] };
      const i = nodeAt(p), j = nodeAt(q), len = Math.hypot(q.x - p.x, q.y - p.y);
      if (i === j || len <= 0) continue;
      const e = { i, j, len, k: edges.length };
      edges.push(e);
      nodes[i].adj.push(e); nodes[j].adj.push(e);
    }
  }
  return { nodes, edges };
}

Device joined to the nearest route

64-bit

Connects a board or device to the nearest point of a route within reach, and counts the drop.

drop = √d²; split lengths = len × u and len × (1 − u)

The two halves of a split edge are the old length times u and 1 − u, not lengths recomputed from coordinates; their sum can differ from the old length by a rounding. Within 1e-9 of an end the existing node is used.

  • addons/electrical/lib/install.js
  • lines 250 to 282
  • CadCraft 0.8.2
/** Attach a point to the graph: the nearest point on an edge within reach → a new node (the edge split). → node index or -1. */
export function attach(g, p, reach) {
  let best = null;
  for (const e of g.edges) {
    if (e.dead) continue;
    const a = g.nodes[e.i], b = g.nodes[e.j];
    const q = segDist2(p, a, b);
    if (q.d2 <= reach * reach && (!best || q.d2 < best.q.d2)) best = { e, q };
  }
  if (!best) return -1;
  const { e, q } = best;
  const drop = Math.sqrt(q.d2);
  let mid;
  if (q.u <= 1e-9) mid = e.i;
  else if (q.u >= 1 - 1e-9) mid = e.j;
  else {
    mid = g.nodes.length;
    g.nodes.push({ i: mid, x: q.q.x, y: q.q.y, adj: [] });
    e.dead = true;
    const a = { i: e.i, j: mid, len: e.len * q.u, k: g.edges.length }, b = { i: mid, j: e.j, len: e.len * (1 - q.u), k: g.edges.length + 1 };
    g.edges.push(a, b);
    g.nodes[e.i].adj = g.nodes[e.i].adj.filter((x) => x !== e).concat(a);
    g.nodes[e.j].adj = g.nodes[e.j].adj.filter((x) => x !== e).concat(b);
    g.nodes[mid].adj.push(a, b);
  }
  // the drop from the point to the route
  const n = g.nodes.length;
  g.nodes.push({ i: n, x: p.x, y: p.y, adj: [] });
  const d = { i: n, j: mid, len: drop, k: g.edges.length, drop: true };
  g.edges.push(d);
  g.nodes[n].adj.push(d); g.nodes[mid].adj.push(d);
  return n;
}

Shortest routes from the board

64-bit

The shortest distance from the board to every node of the route network.

Dijkstra: dist[v] = min(dist[v], dist[u] + len)

Distances are running 64-bit sums of edge lengths compared with a plain less-than; two routes of equal length are decided by rounding and node order.

  • addons/electrical/lib/install.js
  • lines 284 to 300
  • CadCraft 0.8.2
/** Shortest paths from one node (Dijkstra). → {dist[], via[] (edge index)}. */
export function shortest(g, from) {
  const dist = new Array(g.nodes.length).fill(Infinity), via = new Array(g.nodes.length).fill(-1), done = new Array(g.nodes.length).fill(false);
  dist[from] = 0;
  for (;;) {
    let u = -1;
    for (let i = 0; i < dist.length; i++) if (!done[i] && dist[i] < Infinity && (u < 0 || dist[i] < dist[u])) u = i;
    if (u < 0) break;
    done[u] = true;
    for (const e of g.nodes[u].adj) {
      if (e.dead) continue;
      const v = e.i === u ? e.j : e.i;
      if (dist[u] + e.len < dist[v]) { dist[v] = dist[u] + e.len; via[v] = e.k; }
    }
  }
  return { dist, via };
}

Cable length of a circuit

64-bit

The meters of cable a circuit needs.

meters = Σ used edges × meters per unit + devices reached × 0.5 + 1

Edges shared by several devices are counted once through a Set. The sum is in drawing units and is multiplied by the unit factor once. The allowances of 0.5 m per device and 1 m at the board are defaults the caller can change. The result is not rounded.

  • addons/electrical/lib/install.js
  • lines 302 to 332
  • CadCraft 0.8.2
/**
 * Cable length of every circuit, in metres. opts: reach (drawing units; default 0.5 m), atDevice / atBoard (metres added
 * at each device and at the board; defaults 0.5 and 1). → Map circuit name → {metres, routed (bool), unreached [load],
 * reason ('no-board' | 'no-route' | '')}.
 */
export function cableLengths(doc, model, inst, { reach, atDevice = 0.5, atBoard = 1 } = {}) {
  const mpu = mPerUnit(doc.meta.units);
  reach = reach ?? 0.5 / mpu;
  const out = new Map();
  for (const c of inst.circuits.values()) {
    if (!c.loads.length) { out.set(c.name, { metres: 0, routed: false, unreached: [], reason: '' }); continue; }
    if (!c.board) { out.set(c.name, { metres: 0, routed: false, unreached: c.loads, reason: 'no-board' }); continue; }
    const sp = c.board.space;
    const g = routeGraph(model.routes.filter((r) => r.space.key === sp.key));   // by key: inst may come from another scan of the drawing
    const start = attach(g, c.board.at, reach);
    if (start < 0) { out.set(c.name, { metres: 0, routed: false, unreached: c.loads, reason: 'no-route' }); continue; }
    const targets = c.loads.map((l) => ({ l, n: l.space.key === sp.key ? attach(g, l.at, reach) : -1 }));
    const { dist, via } = shortest(g, start);
    const used = new Set(), unreached = [];
    for (const { l, n } of targets) {
      if (n < 0 || dist[n] === Infinity) { unreached.push(l); continue; }
      for (let v = n; v !== start;) { const e = g.edges[via[v]]; used.add(e.k); v = e.i === v ? e.j : e.i; }
    }
    let len = 0;
    for (const k of used) len += g.edges[k].len;
    const reached = targets.length - unreached.length;
    const metres = reached ? len * mpu + reached * atDevice + atBoard : 0;
    out.set(c.name, { metres, routed: reached > 0, unreached, reason: unreached.length ? 'no-route' : '' });
  }
  return out;
}

In detail

Method
I = W ÷ (V × pf), or W ÷ (√3 × V × pf) for three phase. Cable length = length of the union of the shortest routes from the board to each device (Dijkstra), × meters per unit, + an allowance per device and at the board.
Number types
64-bit numbers; attribute values read with parseFloat.
Tolerances
Route points closer than 1e-6 drawing units are one node; a route end splits another route when it lies within 1e-6 of it and 1e-9 < u < 1 − 1e-9 along it. A board or device joins a route within reach, 0.5 m by default. All absolute.
Limits
Routes are measured in the plan's x and y only. The circuit load is a plain sum of the planning values. Horsepower is converted with 746 W.

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.

Every policy that takes this approach