Resources Numerical accuracy Calculation policy

Erection plan: picks, crane loads and times

Works out what the crane lifts, each pick's weight, radius, share of the load chart, hook height and time.

How the precision is kept

64-bit arithmetic on the model's part weights and the load chart the person enters. The chart is read as a step table, never interpolated: a radius between two rows gets the capacity of the farther row. Weights are gross (holes and copes not deducted), with a percentage added for rigging. The file says the result is not a lift plan.

Open item. Add each part's section offset when its center is taken for the center of gravity, so a beam hung from its top line is not weighed at the top of steel.

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.

erection.js DetailCraft 0.8.1 64-bit 10 calculations Reviewed

Tests tools/detailcraft-test/test-erection.mjs

Load chart cleaned

64-bit

The chart rows kept: sorted by radius, with a capacity that never rises as the radius grows.

Rows outside 0 to 500 m or 0 to 5,000,000 kg are dropped. A row with more capacity than the row before it is lowered to that row's capacity; values are otherwise kept as typed.

  • erection.js
  • lines 42 to 52
  • DetailCraft 0.8.1
/** A chart row list cleaned: numbers only, sorted by radius, a capacity that does not rise with radius. */
export function cleanChart(rows) {
  const out = [];
  for (const r of Array.isArray(rows) ? rows : []) {
    const radius = Number(r?.radius), kg = Number(r?.kg);
    if (Number.isFinite(radius) && Number.isFinite(kg) && radius > 0 && radius <= 500 && kg >= 0 && kg <= 5e6) out.push({ radius, kg });
  }
  out.sort((a, b) => a.radius - b.radius);
  for (let i = 1; i < out.length; i++) if (out[i].kg > out[i - 1].kg) out[i] = { radius: out[i].radius, kg: out[i - 1].kg };
  return out.slice(0, 200);
}

Capacity at a radius

Exact

The chart capacity that applies at a given radius.

capacity = kg of the first row with radius ≥ r; 0 beyond the last row

A lookup, not an interpolation: the value returned is always one the person typed, or zero. The comparison allows 1e-9 m, absolute.

  • erection.js
  • lines 54 to 59
  • DetailCraft 0.8.1
export function capacityAt(chart, radiusM) {
  if (!chart?.length || !(radiusM >= 0)) return 0;
  if (radiusM > chart[chart.length - 1].radius + 1e-9) return 0;
  for (const row of chart) if (radiusM <= row.radius + 1e-9) return row.kg;   // the first row at or beyond the radius
  return 0;
}

Weight and center of gravity of a pick

64-bit

The weight of an assembly and the point its weight acts at.

W = Σ w_i; cg = Σ w_i·(a_i + b_i) ÷ 2 ÷ W

A running sum of part weights and a weighted mean of each part's line midpoint. The midpoint is of the part's frame line from its cut start to its end; the section's offset from that line is not added. An assembly of zero weight divides by 1.

  • erection.js
  • lines 104 to 106
  • DetailCraft 0.8.1
const weight = parts.reduce((t, q) => t + q.weight, 0);
// the centre of gravity of the assembly: weight-weighted centres of its parts (bolts and welds add to the rigging percentage, not here)
const cg = [0, 1, 2].map((i) => parts.reduce((t, q, k) => t + q.weight * (bx[k].a[i] + bx[k].b[i]) / 2, 0) / (weight || 1));

Storey a pick belongs to

64-bit

Which storey a pick is erected with, for the order of the sequence.

Levels are counted against the pick's lowest point (columns) or its center of gravity (others) with an absolute 50 mm allowance.

  • erection.js
  • lines 141 to 144
  • DetailCraft 0.8.1
const tierOf = (k) => {
  if (k.type === 'column') return Math.max(0, levels.filter((z) => z <= k.lo[2] + tol).length - 1);
  return Math.max(0, levels.filter((z) => z < k.cg[2] - tol).length - 1);
};

Load, radius and share of the chart

64-bit

The load on the hook, the working radius, the chart capacity there and the share of it used.

load = W·(1 + rigging% ÷ 100) + deduction; radius = max(r_pick, r_set); share = load ÷ capacity

Radii are plan distances from Math.hypot divided by 1000. The share is one division, set to infinity when the capacity is zero. The hook height needed is the pick's top plus headroom plus clearance.

  • erection.js
  • lines 175 to 184
  • DetailCraft 0.8.1
const load0 = k.weight * (1 + setup.riggingPct / 100);
const checks = cranes.map((c) => {
  const ld = laydownFor(c);
  const rPick = dist2(c.at, ld) / 1000, rSet = dist2(c.at, setAt) / 1000;
  const radius = Math.max(rPick, rSet);
  const cap = capacityAt(c.chart, radius);
  const load = load0 + c.deductKg;
  const hookNeed = k.hi[2] + setup.headroom + setup.clearance;
  return { crane: c, rPick, rSet, radius, cap, load, util: cap > 0 ? load / cap : Infinity, hookNeed, hookOk: !c.hookHeight || hookNeed <= c.hookHeight, reach: radius <= c.chart[c.chart.length - 1].radius + 1e-9, ld };
});

Time of a pick

64-bit

How long a pick takes and when it starts and ends.

travel = (plan distance + top height) ÷ 1000; minutes = hook + travel ÷ speed + set + release; start = max(crane free, supports done)

Times accumulate by addition from pick to pick with no rounding in the plan. The speed is limited to at least 0.5 m per minute when the setup is cleaned, so the division is safe.

  • erection.js
  • lines 188 to 192
  • DetailCraft 0.8.1
const travel = (Math.hypot(pick.ld[0] - setAt[0], pick.ld[1] - setAt[1], 0) + Math.max(0, k.hi[2] - 0)) / 1000;   // a straight lift and carry, metres
const minutes = c.hookMin + travel / c.speed + c.setMin + c.releaseMin;
const ready = Math.max(0, ...[...deps.get(k.id)].map((d) => finish.get(d) ?? 0));
const start = Math.max(free.get(c.id), ready);
const end = start + minutes;

Status of a pick

64-bit

Whether a pick is fine, over the chart, above the chosen limit, out of reach or too high for the hook.

over when share > 1; high when share > limit

Strict comparisons on the unrounded share with no tolerance, so a share of exactly the limit passes and one a rounding error above it does not.

  • erection.js
  • line 194
  • DetailCraft 0.8.1
const status = !pick.reach || pick.cap <= 0 ? 'out' : pick.util > 1 ? 'over' : pick.util > setup.limit ? 'high' : !pick.hookOk ? 'hook' : 'ok';

Plan totals

64-bit

The total weight, the total time, the heaviest pick and the pick that uses most of the chart.

total kg = Σ W; minutes = max end time

A running sum and plain maximums over the picks, unrounded.

  • erection.js
  • lines 210 to 218
  • DetailCraft 0.8.1
function summaryOf(list, nCranes) {
  const heavy = list.reduce((a, p) => (!a || p.weight > a.weight ? p : a), null);
  const tight = list.reduce((a, p) => (!a || p.util > a.util ? p : a), null);
  return {
    picks: list.length, cranes: nCranes, minutes: list.reduce((t, p) => Math.max(t, p.end), 0), totalKg: list.reduce((t, p) => t + p.weight, 0),
    heaviest: heavy ? { id: heavy.id, mark: heavy.mark, kg: heavy.weight } : null, tightest: tight && Number.isFinite(tight.util) ? { id: tight.id, mark: tight.mark, util: tight.util } : null,
    over: list.filter((p) => p.status === 'over' || p.status === 'out' || p.status === 'hook').length, high: list.filter((p) => p.status === 'high').length,
  };
}

Figures in the plan's warnings

64-bit

The numbers quoted when a pick is over the chart, over the limit or too high.

Rounded for the message only: weights and capacity to whole kilograms, radius to 0.1 m, share to a whole percent, hook height to 0.01 m. The status was decided before on the unrounded values.

  • erection.js
  • line 231
  • DetailCraft 0.8.1
const vars = { mark: labelOf(p.id), kg: Math.round(p.weight), load: Math.round(p.load), r: r1(p.radius), cap: Math.round(p.capacity), pct: Math.round(p.util * 100), limit: Math.round(pl.setup.limit * 100), hook: Math.round(p.hookNeed / 10) / 100 };

Data handed to the movie

64-bit

The picks, boxes and times saved into the erection movie page.

Rounded on the way out: coordinates and sizes to whole millimeters with Math.round, weights to whole kilograms, radius to 0.1 m, times to 0.01 minute. The movie page shows these rounded values.

  • erection.js
  • lines 245 to 261
  • DetailCraft 0.8.1
/** What the movie player draws: the phases' picks in order with their routes, the boxes, the cranes (plain JSON, mm, z up). */
export function movieData(pl, { title = '' } = {}) {
  const members = [];
  for (const p of pl.picks) {
    for (const b of p.boxes || []) members.push({ pick: p.n, type: b.type, a: b.a.map(Math.round), b: b.b.map(Math.round), u: b.u, v: b.v, w: Math.round(b.w), d: Math.round(b.d), voff: Math.round(b.voff) });
  }
  return {
    format: 'cadcraft-erection', version: 1, title,
    cranes: pl.cranes.map((c) => ({ id: c.id, name: c.name, at: c.at, hookHeight: c.hookHeight, chart: c.chart })),
    picks: pl.picks.map((p) => ({
      n: p.n, mark: p.mark, type: p.type, profile: p.profile, phase: p.phase, kg: Math.round(p.weight), crane: p.crane, from: p.pickAt.map(Math.round), to: p.setAt.map(Math.round),
      top: Math.round(p.top), radius: r1(p.radius), cap: Math.round(p.capacity), pct: Number.isFinite(p.util) ? Math.round(p.util * 100) : null, status: p.status,
      t0: Math.round(p.start * 100) / 100, t1: Math.round(p.end * 100) / 100, h: r2(p.hookMin), tr: r2(p.travelMin), cg: p.cg.map(Math.round),
    })),
    members, minutes: pl.summary.minutes,
  };
}

In detail

Method
Sum of part weights per assembly; plan distance from the crane; table lookup; time = fixed minutes + travel ÷ speed.
Number types
64-bit numbers. The movie data is rounded (millimeters to whole numbers, times to 0.01 minute).
Tolerances
Chart lookup 1e-9 m on the radius. Storey grouping 50 mm. Splice order 1 mm.
Limits
The radius is the larger of the pick-up and the set radius, measured in plan to the assembly's center of gravity. Travel is a straight carry plus the height of the pick's top.

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