Resources Numerical accuracy Calculation policy

Constructability checks

Warns about bolts a socket cannot reach, bolts too close for a socket, fillet welds outside size limits, welds a torch cannot reach, and tubes that hold water.

How the precision is kept

64-bit arithmetic on sample points. A socket or a torch is modeled as a cylinder and tested at a grid of points against the parts' true sections, so the result depends on the grid. The tool sizes are typical ratios stated in the file (nut 1.96 × d across corners, 4 mm socket wall, 60 mm socket length, 10 mm torch radius) and can be changed.

Calculations under this policy

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

constructability.js DetailCraft 0.8.1 64-bit 6 calculations Reviewed

Tests tools/detailcraft-test/test-constructability.mjs, tools/detailcraft-test/test-constructability-3.mjs

Two directions across an axis

64-bit

Two unit directions square to a bolt or torch axis, used to lay out the sample grid.

u = unit(a × h); v = a × u

The helper axis h is X unless the axis is within about 26 degrees of X (|a_x| ≥ 0.9), then Y, so the cross product is never near zero.

  • constructability.js
  • lines 42 to 47
  • DetailCraft 0.8.1
/** Two vectors across an axis. */
function perp(a) {
  const h = Math.abs(a[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
  const u = unit(cross(a, h));
  return [u, cross(a, u)];
}

Socket access to a bolt

64-bit

Whether a socket over a bolt head or nut runs into another part, and how much free length there is.

R_s = 1.96·d ÷ 2 + wall; grid step = max(2.5, R_s ÷ 8); stations at 0, ¼, ½, ¾ and 1 × socket length

The socket's disc is sampled on a square grid, clipped to the circle by gx² + gy² ≤ R_s². The grid loop adds the step repeatedly from −R_s, so the last row can stop just short of +R_s. The free length reported is the first of the five stations with a hit, not the exact distance.

  • constructability.js
  • lines 76 to 93
  • DetailCraft 0.8.1
const corners = o.cornersRatio * d, Rs = corners / 2 + o.socketWall;
const nutT = o.nutRatio * d;
const sides = [
  { side: 'head', from: add(b.head, mul(ax, -2)), dir: mul(ax, -1) },
  { side: 'nut', from: add(b.head, mul(ax, b.grip + nutT + 2)), dir: ax },
];
if (on.has('access')) for (const sd of sides) {
  const [u, v] = perp(sd.dir);
  let first = null;
  const step = Math.max(2.5, Rs / 8), pts = [[0, 0]];   // a grid over the socket's disc, fine enough to meet a 6 mm web
  for (let gx = -Rs; gx <= Rs; gx += step) for (let gy = -Rs; gy <= Rs; gy += step) if ((gx || gy) && gx * gx + gy * gy <= Rs * Rs) pts.push([gx, gy]);
  for (let k = 0; k <= 4 && !first; k++) {
    const s = k === 0 ? 0 : (o.socketLength * k) / 4;
    for (const [gx, gy] of (k === 0 ? [[0, 0]] : pts)) {
      const p = add(add(sd.from, mul(sd.dir, s)), add(mul(u, gx), mul(v, gy)));
      for (const q of S) {
        if (!inBox(q, p)) continue;
        if (depthIn(q, p) > 0.5) { first = { q, s, r: Math.hypot(gx, gy) }; break; }

Room between neighboring bolts

64-bit

Whether the socket over one bolt touches the head of the next.

lat = |dv − A·(dv·A)|; need = 1.96·d_max ÷ 2 + wall + 1.96·d_min ÷ 2

The sideways distance is the head-to-head vector with its part along the bolt axis taken off. It is compared with the needed distance less 1e-6 mm. The values in the message are rounded to 0.1 mm.

  • constructability.js
  • lines 116 to 122
  • DetailCraft 0.8.1
  const dv = sub(c.head, a.head), along = dot(dv, A), lat = Math.hypot(...sub(dv, mul(A, along)));
  const need = Math.max(a.d, c.d) * o.cornersRatio / 2 + o.socketWall + Math.min(a.d, c.d) * o.cornersRatio / 2;
  const headH = o.headRatio * Math.max(a.d, c.d);
  if (sameSide && Math.abs(along) <= headH + 1 && lat < need - 1e-6 && (!worst || lat < worst.lat)) worst = { lat, need, d: Math.max(a.d, c.d) };
  if (!sameSide && Math.abs(along - b2grip(a)) <= headH + 1 && lat < need - 1e-6 && (!worst || lat < worst.lat)) worst = { lat, need, d: Math.max(a.d, c.d) };
}
if (worst) push('bolts', 'warning', { conn, spacing: r1(worst.lat), need: r1(worst.need), d: worst.d }, [{ kind: 'conn', id: conn }], conn);

Fillet weld size limits

64-bit

The smallest and largest fillet weld allowed for the thinner of the two parts joined.

min = 3, 5, 6 or 8 mm for thickness up to 6.35, 12.7, 19.05 mm or more; max = t when t < 6.35, else t − 1.6

The thickness steps 6.35, 12.7 and 19.05 mm are exact conversions of 1/4, 1/2 and 3/4 inch. The 1.6 mm taken off for the maximum stands for 1/16 inch, which is 1.5875 mm. Comparisons allow 1e-6 mm.

  • constructability.js
  • lines 133 to 136
  • DetailCraft 0.8.1
const thin = Math.min(...ts);
const min = thin <= 6.35 ? 3 : thin <= 12.7 ? 5 : thin <= 19.05 ? 6 : 8;
const max = thin < 6.35 ? thin : thin - 1.6;
const which = w.size < min - 1e-6 ? 'min' : w.size > max + 1e-6 ? 'max' : null;

Torch access along an all-round weld

64-bit

The share of an all-round fillet weld's line where a torch on the 45 degree bisector is blocked.

start s0 = r·√2 + 1; direction = unit(outward + away); tested every 5 mm for the reach

Uses Math.SQRT2. Each outline edge is sampled at round(length ÷ 20) points, between 1 and 40, so on an edge longer than 800 mm the points are more than 20 mm apart. The torch is tested at its axis and eight points on its rim.

  • constructability.js
  • lines 155 to 185
  • DetailCraft 0.8.1
const s0 = o.torchRadius * Math.SQRT2 + 1;   // where the nozzle's side first clears both faces of the fillet
let blocked = 0, total = 0, free = Infinity, by = null;
for (const loop of A.loops) {
  for (let i = 0, j = loop.length - 1; i < loop.length; j = i++) {
    const a = loop[j], b = loop[i], e = [b[0] - a[0], b[1] - a[1]], len = Math.hypot(e[0], e[1]);
    if (len < 1e-6) continue;
    const steps = Math.max(1, Math.min(40, Math.round(len / 20)));
    let nrm = [e[1] / len, -e[0] / len];
    // the outward side is the one that leaves the member's section
    const mid = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2], probe = world(mid[0] + nrm[0] * 0.8, mid[1] + nrm[1] * 0.8);
    const inside = (pt) => depthIn(A, add(pt, mul(away, 0.01))) > 0 || depthIn(A, pt) > 0;
    if (inside(probe)) nrm = [-nrm[0], -nrm[1]];
    const outW = add(mul(A.frame.u, nrm[0]), mul(A.frame.v, nrm[1]));
    const dir = unit(add(outW, away));   // 45° between the member's face and the plate's face
    const [pu, pv] = perp(dir);
    for (let k = 0; k < steps; k++) {
      const f = (k + 0.5) / steps, p0 = world(a[0] + e[0] * f, a[1] + e[1] * f);
      total++;
      let hit = null;
      const rings = [[0, 0], [1, 0], [-1, 0], [0, 1], [0, -1], [0.7, 0.7], [-0.7, 0.7], [0.7, -0.7], [-0.7, -0.7]];
      for (let s = s0; s <= s0 + o.torchReach && !hit; s += 5) {
        for (const [gx, gy] of rings) {
          const pt = add(add(p0, mul(dir, s)), add(mul(pu, gx * o.torchRadius), mul(pv, gy * o.torchRadius)));
          for (const q of S) {
            if (q.id === B.id || !inBox(q, pt)) continue;
            if (depthIn(q, pt) > 0.5) { hit = { q, s }; break; }
          }
          if (hit) break;
        }
      }
      if (hit) { blocked++; if (hit.s < free) { free = hit.s; by = hit.q; } }

Slope of a hollow section

64-bit

The slope of a tube, used to decide whether an open upper end lets rain in.

slope = asin(|Δz| ÷ length) × 180 ÷ π

The argument of Math.asin is not clamped to 1. For a vertical tube it is |Δz| ÷ hypot(0, 0, Δz); if that ratio ever rounded above 1 the slope would be not-a-number and the open-end note would not be given. Members shorter than 1 mm are left out.

  • constructability.js
  • lines 207 to 210
  • DetailCraft 0.8.1
const dz = m.end[2] - m.start[2], len = Math.hypot(m.end[0] - m.start[0], m.end[1] - m.start[1], dz);
if (len < 1) continue;
const upper = dz >= 0 ? 'end' : 'start', lower = upper === 'end' ? 'start' : 'end';
const slope = Math.asin(Math.abs(dz) / len) * 180 / Math.PI;

In detail

Method
Sampled cylinders against the clash check's point-in-part test; fillet weld limits from AISC 360-16 J2.2b and Table J2.4 as cited in the file.
Number types
64-bit numbers.
Tolerances
A sample counts as blocked when it is more than 0.5 mm inside a part. Box pre-test margin 2 mm. Weld and spacing comparisons use 1e-6 mm. Parallel bolts: |cos| ≥ 0.999.
Limits
Socket grid step max(2.5 mm, radius ÷ 8) at four stations along the socket, plus the axis point at the bearing face; torch tested at nine points every 5 mm, every 20 mm or so along the weld line. Only all-round fillet welds are located.

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