Resources Numerical accuracy Calculation policy

Design-it sizing and hand checks

Sizes a shelf bracket or a round shaft from a load and a material, and reports the stresses, deflection, bolt loads, volume and mass.

How the precision is kept

The arithmetic is 64-bit closed-form beam and shaft formulas. The accuracy of the result is set by the method: textbook hand formulas, the factor 0.577 in place of 1 ÷ √3, material table values, and sizes rounded up to stock. Pass or fail is decided on unrounded values, but every number returned in the result is rounded to 2 decimals (3 for two of them).

Open item. Use 1 ÷ √3 and the full pound-force torque factors, and return the unrounded values next to the rounded ones.

Calculations under this policy

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

design-it.js PartCraft 0.8.1 64-bit 9 calculations Reviewed

Tests tools/partcraft-test/test-cost.mjs

Unit factors

64-bit

The factors that turn the given force, length and torque units into newtons, millimeters and newton millimeters.

kN, lbf (4.4482216152605 N), kgf (9.80665 N), inch (25.4 mm) and foot (304.8 mm) are the exact defined values. The torque factors 112.98482902 and 1355.8179483 are shortened: the products 4.4482216152605 × 25.4 and × 304.8 are 112.9848290276167 and 1355.8179483314004, so a pound-force torque is right to about 10 digits.

  • design-it.js
  • lines 31 to 33
  • PartCraft 0.8.1
export const FORCE_UNITS = { N: 1, kN: 1000, lbf: 4.4482216152605, lb: 4.4482216152605, kgf: 9.80665 };
export const LENGTH_UNITS = { mm: 1, cm: 10, m: 1000, in: 25.4, inch: 25.4, ft: 304.8 };
export const TORQUE_UNITS = { 'N*m': 1000, 'N*mm': 1, 'lbf*in': 112.98482902, 'lbf*ft': 1355.8179483 };   // → N·mm

Arguments to working units

64-bit

Converts the given force, lengths and torque to N, mm and N·mm.

value × unit factor

One 64-bit multiplication per value by the factor from the unit tables.

  • design-it.js
  • lines 276 to 292
  • PartCraft 0.8.1
/** Assistant arguments → the spec estimate() takes (all lengths mm, forces N, moments N·mm). */
export function specFromArgs(a) {
  const lu = a.length_unit || 'mm';
  const L = (v) => (v === undefined || v === null ? undefined : lengthMm(v, lu));
  const tq = (v) => (v === undefined || v === null ? undefined : v * (TORQUE_UNITS[a.torque_unit || 'N*m']));
  if (!a.generator) throw new DesignError('invalid_argument', 'Say which generator.', ['Call generators_find with the need in words.']);
  if (!a.material) throw new DesignError('invalid_argument', 'Say the material.', ['Use "aluminium", "steel", "stainless", "titanium", "ABS", "PLA" or "nylon".']);
  const spec = { generator: a.generator, material: a.material, process: a.process, safety: a.safety };
  if (a.generator === 'bracket') {
    if (a.force === undefined || a.arm === undefined) throw new DesignError('invalid_argument', 'A bracket needs a force and an arm.', ['Give force (with force_unit) and arm (with length_unit).']);
    Object.assign(spec, { force_N: forceN(a.force, a.force_unit || 'N'), arm_mm: L(a.arm), width_mm: L(a.width), max_deflection_mm: L(a.max_deflection), thickness_mm: L(a.thickness) });
  } else if (a.generator === 'shaft') {
    if (a.length === undefined) throw new DesignError('invalid_argument', 'A shaft needs a length.', ['Give length (with length_unit).']);
    Object.assign(spec, { length_mm: L(a.length), torque_Nmm: tq(a.torque), moment_Nmm: tq(a.moment) });
  }
  return spec;
}

Rounding to stock sizes and to reported decimals

64-bit

Rounds a thickness up to a stock plate or a 0.5 mm step, and rounds reported values.

r2(v) = round(100 v) ÷ 100; r3(v) = round(1000 v) ÷ 1000

A required thickness is rounded up, with 1e-9 mm allowed so a value that is a stock size within rounding is not pushed to the next one. r2 and r3 round the numbers that are returned in the result to 2 and 3 decimals.

  • design-it.js
  • lines 107 to 113
  • PartCraft 0.8.1
const PROCESS = {
  machined: { tmin: 3, round: (t) => STOCK_T.find((s) => s >= t - 1e-9) ?? Math.ceil(t) },
  printed: { tmin: 2.5, round: (t) => Math.ceil(t * 2 - 1e-9) / 2 },
  any: { tmin: 1, round: (t) => Math.ceil(t * 2 - 1e-9) / 2 },
};
const r2 = (v) => Math.round(v * 100) / 100;
const r3 = (v) => Math.round(v * 1000) / 1000;

Bolt positions in the flange

64-bit

The heights of the two bolts and the flange height.

y_b = t + 1.5 × hole; spacing = clamp(round(a ÷ 10) × 5, 30, 100); H = y_t + 1.5 × hole

The two bolt heights and the flange height are rounded to 0.01 mm before they are used in the checks and in the built part. The spacing is rounded to a 5 mm step by design.

  • design-it.js
  • lines 123 to 130
  • PartCraft 0.8.1
function boltLayout(dims) {
  const { t, a } = dims;
  const bolt = dims.bolt;
  const yb = r2(t + 1.5 * bolt.hole);
  const s = Math.min(100, Math.max(30, Math.round(a / 2 / 5) * 5));
  const yt = r2(yb + s);
  return { yb, yt, H: r2(yt + 1.5 * bolt.hole), spacing: s };
}

Bracket checks

64-bit

Bending and shear stress, tip deflection and bolt loads of the bracket, each with its limit and safety factor.

σ = 6 F a ÷ (b t²); τ = 1.5 F ÷ (b t); δ = F a³ ÷ (3 E I), I = b t³ ÷ 12; T = M y₂ ÷ (y₁² + y₂²); (T ÷ T_all)² + (V ÷ V_all)² ≤ 1

Each check is one 64-bit expression and is passed or failed on the unrounded value with 1e-9 absolute allowance. The shear limits use 0.577 for 1 ÷ √3 (0.57735…), which makes them about 0.06 percent lower than the exact factor. The values, limits and safety factors in the result are rounded to 2 decimals, and the overall safety factor is the smallest of the already rounded ones.

  • design-it.js
  • lines 132 to 152
  • PartCraft 0.8.1
/** The checks of a bracket as dimensioned (dims: t, b, a (arm = shelf length), bolt, H, ys). Pure hand formulas. */
export function checkBracket(dims, load, mat, safety) {
  const { t, b, a } = dims, F = load.force_N, M = F * a;
  const sigma = 6 * M / (b * t * t), tau = 1.5 * F / (b * t);
  const I = b * t ** 3 / 12, delta = F * a ** 3 / (3 * mat.E * I);
  const allow = mat.yield / safety;
  const rows = [
    { id: 'shelf-bending', what: 'Bending stress at the wall face (σ = 6 F a / (b t²))', value: r2(sigma), limit: r2(allow), unit: 'MPa', sf: r2(mat.yield / sigma), ok: sigma <= allow + 1e-9 },
    { id: 'shelf-shear', what: 'Shear stress in the shelf (τ = 1.5 F / (b t), limit 0.577 Sy / SF)', value: r2(tau), limit: r2(0.577 * allow), unit: 'MPa', sf: r2(0.577 * mat.yield / tau), ok: tau <= 0.577 * allow + 1e-9 },
  ];
  const dl = load.max_deflection_mm;
  rows.push({ id: 'tip-deflection', what: 'Tip deflection of the shelf (δ = F a³ / (3 E I); the flange\'s own rotation is not included)', value: r2(delta), limit: dl ? r2(dl) : null, unit: 'mm', sf: dl ? r2(dl / delta) : null, ok: dl ? delta <= dl + 1e-9 : true });
  // bolts: rotation about the flange's bottom edge; force proportional to height above it
  const ys = [dims.ys[0], dims.ys[1]], sum2 = ys[0] ** 2 + ys[1] ** 2;
  const Ttop = M * ys[1] / sum2, V = F / 2;
  const Tall = dims.bolt.as * BOLT_PROOF / safety, Vall = 0.577 * dims.bolt.as * BOLT_PROOF / safety;
  const comb = (Ttop / Tall) ** 2 + (V / Vall) ** 2;
  rows.push({ id: 'bolt-tension', what: 'Tension in the top bolt (T = M y / Σy²; ' + dims.bolt.size + ' class 8.8, Tall = As Sp / SF)', value: r2(Ttop), limit: r2(Tall), unit: 'N', sf: r2(Tall * safety / Ttop), ok: Ttop <= Tall + 1e-9 });
  rows.push({ id: 'bolt-combined', what: 'Tension and shear together ((T/Tall)² + (V/Vall)² ≤ 1)', value: r3(comb), limit: 1, unit: '', sf: r2(1 / Math.sqrt(comb || 1e-9)), ok: comb <= 1 + 1e-9 });
  return { rows, ok: rows.every((r) => r.ok), safetyFactor: r2(Math.min(...rows.filter((r) => r.sf !== null && r.id !== 'tip-deflection' && r.id !== 'bolt-combined').map((r) => r.sf))), bolt: { top_N: r2(Ttop), shear_N: r2(V) } };
}

Bracket sizing, volume and mass

64-bit

The thickness for strength and for the deflection limit, the bolt size, and the bracket's volume and mass.

t = √(6 M ÷ (b σ_allow)); t = ∛(4 F a³ ÷ (E b δ)); V = b (L t + (H − t) t) − 2 (π ÷ 4) hole² t; mass = V × density × 1e-6

Thicknesses come from Math.sqrt and Math.cbrt in 64-bit and are then rounded up to stock. The volume is the L-shaped section times the width minus two bolt holes through one thickness; volume, mass, force, arm and moment are rounded to 2 decimals in the returned result. The steps text prints values rounded to 2 decimals.

  • design-it.js
  • lines 154 to 202
  • PartCraft 0.8.1
export function estimateBracket(spec) {
  const mat = materialFor(spec.material);
  const F = fin(spec.force_N, 'The force'), a = fin(spec.arm_mm, 'The arm');
  const safety = safetyOf(mat, spec.safety);
  const pr = PROCESS[spec.process || 'machined'];
  if (!pr) throw new DesignError('invalid_argument', 'Unknown process "' + spec.process + '".', ['Use machined, printed or any.']);
  const M = F * a;
  const b = spec.width_mm ? fin(spec.width_mm, 'The width') : Math.max(20, Math.ceil(0.15 * a / 5) * 5);
  const steps = [];
  steps.push('Moment at the wall: M = F·a = ' + r2(F) + ' N × ' + r2(a) + ' mm = ' + r2(M) + ' N·mm.');
  steps.push('Allowable stress: Sy / SF = ' + mat.yield + ' / ' + safety + ' = ' + r2(mat.yield / safety) + ' MPa (' + MAT_LABEL[mat.id] + ').');
  const tStrength = Math.sqrt(6 * M / (b * mat.yield / safety));
  steps.push('Thickness for strength: t = √(6 M / (b σ_allow)) = √(6 × ' + r2(M) + ' / (' + b + ' × ' + r2(mat.yield / safety) + ')) = ' + r2(tStrength) + ' mm at width b = ' + b + ' mm.');
  let t = tStrength;
  const dl = spec.max_deflection_mm;
  if (dl) {
    const tStiff = Math.cbrt(4 * F * a ** 3 / (mat.E * b * fin(dl, 'max_deflection')));
    steps.push('Thickness for the tip deflection limit ' + dl + ' mm: t = ∛(4 F a³ / (E b δ)) = ' + r2(tStiff) + ' mm.');
    t = Math.max(t, tStiff);
  }
  t = Math.max(t, pr.tmin);
  const tr = spec.thickness_mm ? fin(spec.thickness_mm, 'The thickness') : pr.round(t);
  if (spec.thickness_mm) steps.push('Thickness given: t = ' + tr + ' mm (checked, not sized).');
  else steps.push('Rounded up for the ' + (spec.process || 'machined') + ' process (' + (spec.process === 'printed' ? '0.5 mm steps, 2.5 mm minimum' : spec.process === 'any' ? '0.5 mm steps' : 'stock thicknesses, 3 mm minimum') + '): t = ' + tr + ' mm.');
  // the smallest bolt that carries the load
  const dims = { t: tr, b, a, L: a };
  let chosen = null;
  for (const bolt of BOLTS) {
    if (bolt.d < 5) continue;   // M5 is the smallest a bracket this size is given
    const lay = boltLayout({ t: tr, a, bolt });
    const trial = { ...dims, bolt, H: lay.H, ys: [lay.yb, lay.yt], spacing: lay.spacing };
    const c = checkBracket(trial, { force_N: F, max_deflection_mm: dl }, mat, safety);
    if (c.rows.filter((r) => r.id.startsWith('bolt')).every((r) => r.ok)) { chosen = { trial, c }; break; }
  }
  if (!chosen) throw new DesignError('failed', 'No bolt up to M12 carries this load with two bolts.', ['Lower the force or the arm.', 'Ask for a wider mounting (three or more bolts is not built).']);
  const { trial, c } = chosen;
  steps.push('Two ' + trial.bolt.size + ' class 8.8 bolts ' + trial.spacing + ' mm apart in the flange (top bolt tension ' + c.bolt.top_N + ' N, shear ' + c.bolt.shear_N + ' N each).');
  const volume = trial.b * (trial.L * trial.t + (trial.H - trial.t) * trial.t) - 2 * Math.PI / 4 * trial.bolt.hole ** 2 * trial.t;
  const massG = volume * mat.density * 1e-6;   // mm³ × kg/m³ → g  (1 mm³ = 1e-9 m³; kg → g ×1e3)
  return {
    generator: 'bracket', process: spec.process || 'machined',
    material: { id: mat.id, name: MAT_LABEL[mat.id], E_MPa: mat.E, yield_MPa: mat.yield, density_kg_m3: mat.density },
    load: { force_N: r2(F), arm_mm: r2(a), moment_Nmm: r2(M), safety },
    dims: { thickness_mm: trial.t, width_mm: trial.b, shelf_length_mm: trial.L, flange_height_mm: trial.H, bolt: trial.bolt.size, bolt_hole_mm: trial.bolt.hole, bolt_heights_mm: trial.ys, bolt_spacing_mm: trial.spacing },
    checks: c.rows, ok: c.ok, safetyFactor: c.safetyFactor, volume_mm3: r2(volume), massG: r2(massG), steps,
    notes: NOT_CHECKED_BRACKET.concat(PLASTIC.has(mat.id) ? ['Plastic: the yield figure is a short-term value; creep under a steady load is not checked.'] : []),
    _t: trial,
  };
}

Shaft checks

64-bit

Von Mises stress, torsional shear and twist of a round shaft.

σ' = 32 ÷ (π d³) × √(M² + ¾T²); τ = 16 T ÷ (π d³); θ = T L ÷ (G J), G = E ÷ (2(1 + ν)), J = π d⁴ ÷ 32

64-bit expressions with Math.PI; the square root is of a sum of squares, so nothing cancels. The shear limit uses 0.577 for 1 ÷ √3. Reported stress is rounded to 2 decimals and twist to 3 decimals of a degree.

  • design-it.js
  • lines 210 to 222
  • PartCraft 0.8.1
export function checkShaft(d, L, load, mat, safety) {
  const T = load.torque_Nmm || 0, M = load.moment_Nmm || 0;
  const sigma = 32 / (Math.PI * d ** 3) * Math.sqrt(M ** 2 + 0.75 * T ** 2);
  const allow = mat.yield / safety;
  const rows = [{ id: 'von-mises', what: 'Von Mises stress at the surface (32/(π d³)·√(M² + ¾T²))', value: r2(sigma), limit: r2(allow), unit: 'MPa', sf: r2(mat.yield / sigma), ok: sigma <= allow + 1e-9 }];
  if (T) {
    const tau = 16 * T / (Math.PI * d ** 3);
    rows.push({ id: 'torsion-shear', what: 'Torsional shear (16 T / (π d³), limit 0.577 Sy / SF)', value: r2(tau), limit: r2(0.577 * allow), unit: 'MPa', sf: r2(0.577 * mat.yield / tau), ok: tau <= 0.577 * allow + 1e-9 });
    const theta = T * L / (mat.E / (2 * (1 + mat.nu)) * Math.PI * d ** 4 / 32) * 180 / Math.PI;
    rows.push({ id: 'twist', what: 'Twist over the length (θ = T L / (G J))', value: r3(theta), limit: null, unit: 'degrees', sf: null, ok: true });
  }
  return { rows, ok: rows.every((r) => r.ok), safetyFactor: r2(Math.min(...rows.filter((r) => r.sf !== null).map((r) => r.sf))) };
}

Shaft sizing, volume and mass

64-bit

The required diameter, the standard size above it, and the shaft's volume and mass.

d = ∛(32 SF √(M² + ¾T²) ÷ (π S_y)); V = (π ÷ 4) d² L

The required diameter is one 64-bit expression; the first standard diameter at or above it (within 1e-9 mm) is taken, or the next whole millimeter above 100 mm. Volume and mass are rounded to 2 decimals in the result.

  • design-it.js
  • lines 224 to 245
  • PartCraft 0.8.1
export function estimateShaft(spec) {
  const mat = materialFor(spec.material);
  const T = spec.torque_Nmm || 0, M = spec.moment_Nmm || 0;
  if (!(T > 0 || M > 0)) throw new DesignError('invalid_argument', 'A shaft needs a torque or a bending moment.', ['Give torque (with torque_unit) and/or moment.']);
  const L = fin(spec.length_mm, 'The length');
  const safety = safetyOf(mat, spec.safety);
  const dReq = Math.cbrt(32 * safety * Math.sqrt(M ** 2 + 0.75 * T ** 2) / (Math.PI * mat.yield));
  const d = SHAFT_D.find((s) => s >= dReq - 1e-9) ?? Math.ceil(dReq);
  const c = checkShaft(d, L, { torque_Nmm: T, moment_Nmm: M }, mat, safety);
  const steps = [
    'Equivalent moment √(M² + ¾T²) = ' + r2(Math.sqrt(M ** 2 + 0.75 * T ** 2)) + ' N·mm; allowable ' + r2(mat.yield / safety) + ' MPa (' + MAT_LABEL[mat.id] + ', SF ' + safety + ').',
    'Diameter d = ∛(32 SF √(M² + ¾T²) / (π Sy)) = ' + r2(dReq) + ' mm, rounded up to the standard size ' + d + ' mm.',
  ];
  const volume = Math.PI / 4 * d * d * L;
  return {
    generator: 'shaft', process: spec.process || 'machined',
    material: { id: mat.id, name: MAT_LABEL[mat.id], E_MPa: mat.E, yield_MPa: mat.yield, density_kg_m3: mat.density },
    load: { torque_Nmm: r2(T), moment_Nmm: r2(M), safety },
    dims: { diameter_mm: d, length_mm: L }, checks: c.rows, ok: c.ok, safetyFactor: c.safetyFactor, volume_mm3: r2(volume), massG: r2(volume * mat.density * 1e-6), steps,
    notes: ['Not checked: keyways, shoulders and fillets (stress concentration), fatigue, critical speed, bearing loads.'],
  };
}

The built part

64-bit

Sketches and extrudes the sized bracket or shaft as part features.

density (g/cm³) = density (kg/m³) ÷ 1000

Sketch points, constraint values and depths are the estimate's dimensions passed on unchanged as 64-bit numbers. The bolt circle radius is hole ÷ 2 and the cut depth 3 t.

  • design-it.js
  • lines 296 to 330
  • PartCraft 0.8.1
/** The part of an estimate: sketches, extrudes and cuts, as the Features panel would make them. */
export function buildPart(est, name) {
  const mat = materialById(est.material.id);
  const part = newPart(name || (est.generator === 'bracket' ? 'Bracket' : 'Shaft'));
  if (est.generator === 'bracket') {
    const { t, b, L, H, bolt, ys } = est._t;
    const sk = S.newSketch();
    const pts = [[0, 0], [L, 0], [L, t], [t, t], [t, H], [0, H]];
    const lines = [];
    let prev = null;
    for (let i = 0; i < 6; i++) { const l = S.addLine(sk, prev || pts[i], pts[(i + 1) % 6]); lines.push(l); prev = l.p2; }
    S.addConstraint(sk, 'coincident', [lines[5].p2, lines[0].p1]);
    S.addConstraint(sk, 'coincident', ['O', lines[0].p1]);
    [0, 2, 4].forEach((i) => S.addConstraint(sk, 'horizontal', [lines[i].id]));
    [1, 3, 5].forEach((i) => S.addConstraint(sk, 'vertical', [lines[i].id]));
    S.addConstraint(sk, 'hdistance', ['O', lines[0].p2], L);
    S.addConstraint(sk, 'distance', [lines[1].id], t);
    S.addConstraint(sk, 'vdistance', ['O', lines[4].p1], H);
    S.addConstraint(sk, 'hdistance', ['O', lines[2].p2], t);
    const s1 = addFeature(part, 'sketch', { plane: { kind: 'base', name: 'front' }, sketch: sk });
    addFeature(part, 'extrude', { sketch: s1.id, depth: b });
    const hs = S.newSketch();
    for (const y of ys) S.addCircle(hs, [-b / 2, y], bolt.hole / 2);   // the right plane has sketch x = −Z
    const s2 = addFeature(part, 'sketch', { plane: { kind: 'base', name: 'right' }, sketch: hs });
    addFeature(part, 'extrude', { sketch: s2.id, op: 'cut', end: 'mid', depth: 3 * t });
  } else {
    const { diameter_mm: d, length_mm: L } = est.dims;
    const sk = S.newSketch();
    S.addCircle(sk, [0, 0], d / 2);
    const s1 = addFeature(part, 'sketch', { plane: { kind: 'base', name: 'top' }, sketch: sk });
    addFeature(part, 'extrude', { sketch: s1.id, depth: L });
  }
  part.material = { name: MAT_LABEL[mat.id], density: mat.density / 1000 };
  return part;
}

In detail

Method
The file header cites Timoshenko, Strength of Materials, and Shigley's bolt joint simplifications: σ = 6M ÷ (b t²), τ = 1.5F ÷ (b t), δ = 4F a³ ÷ (E b t³), bolt tension by rotation about the flange's bottom edge, shaft von Mises stress 32 ÷ (π d³) × √(M² + ¾T²).
Number types
64-bit numbers.
Tolerances
Pass or fail comparisons allow 1e-9 absolute. Rounding up to a stock size allows 1e-9.
Limits
Stress concentration, fatigue, creep, the wall and bolt preload are not checked; the result's own notes say so. 0.577 is used for 1 ÷ √3. The pound-force torque factors are shortened to 11 significant digits.

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