Resources Numerical accuracy Calculation policy

Analysis results drawn on the model

The deflected shape, force diagrams and colored plate contours shown on the 3D view.

How the precision is kept

These functions build line and triangle coordinates and colors for the 3D view from the solver's 64-bit results. The drawing is scaled to a share of the model's size and has no effect on any result. The value given with a label is the solver's own number.

Calculations under this policy

3 calculations in 2 source files. 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.

frame/frame-draw.js CadCraft 0.8.2 32-bit, display only 2 calculations Reviewed

Tests tools/frame/test-plates-app.mjs

Deflected shape

32-bit, display only

The members' deflected shape, enlarged so it can be seen.

drawn point = point + scale × displacement; scale = 0.015 × model size ÷ largest displacement

The scale is chosen for the picture. The largest displacement is returned unscaled beside the geometry.

  • frame/frame-draw.js
  • lines 39 to 54
  • CadCraft 0.8.2
if (mode === 'deflected') {
  let umax = 0;
  for (const d of diags) for (let k = 0; k < d.x.length; k++) umax = Math.max(umax, Math.hypot(d.dx[k], d.dy[k], d.dz[k]));
  const plated = !!prep.shell?.pl?.length;
  if (plated) for (let k = 0; k < prep.P.length; k++) umax = Math.max(umax, Math.hypot(R.u[6 * k], R.u[6 * k + 1], R.u[6 * k + 2]));
  const s = scale > 0 ? scale : umax > 0 ? 0.015 * size / umax : 1;
  prep.mem.forEach((e, k) => {
    const d = diags[k], o = prep.P[e.i], { ex, ey, ez } = e.ax;
    for (let q = 0; q < d.x.length - 1; q++) for (const r of [q, q + 1]) {
      const u = [d.dx[r], d.dy[r], d.dz[r]], b = at(o, ex, d.x[r]);
      out.lines.push(...[0, 1, 2].map((a) => b[a] + s * (ex[a] * u[0] + ey[a] * u[1] + ez[a] * u[2])));
      out.lineColors.push(...DEFL);
    }
  });
  if (plated) { const e = plateEdgesDeflected(sol, key, s, DEFL); out.lines.push(...e.lines); out.lineColors.push(...e.lineColors); }
  out.max = umax; out.scale = s;

Force diagram beside a member

32-bit, display only

A filled diagram of one internal force drawn square to each member, with its largest value labeled.

height = value × 0.08 × model size ÷ largest value

Heights and colors are for the picture. The label carries the unrounded station value with the largest magnitude; a member whose largest value is under 2 % of the model's largest gets no label.

  • frame/frame-draw.js
  • lines 57 to 81
  • CadCraft 0.8.2
let vmax = 0;
for (const d of diags) for (const v of d[mode]) vmax = Math.max(vmax, Math.abs(v));
const s = scale > 0 ? scale : vmax > 0 ? 0.08 * size / vmax : 0;
prep.mem.forEach((e, k) => {
  const d = diags[k], o = prep.P[e.i], { ex, ey, ez } = e.ax;
  // the side a positive value is drawn on
  const dir = mode === 'My' ? ez.map((v) => -v) : mode === 'Mz' ? ey.map((v) => -v) : mode === 'Vy' ? ey : ez;
  const tip = (r) => { const b = at(o, ex, d.x[r]), h = d[mode][r] * s; return [b[0] + dir[0] * h, b[1] + dir[1] * h, b[2] + dir[2] * h]; };
  const col = (r) => RAMP(vmax > 0 ? Math.abs(d[mode][r]) / vmax : 0);
  for (let q = 0; q < d.x.length - 1; q++) {
    const b0 = at(o, ex, d.x[q]), b1 = at(o, ex, d.x[q + 1]), t0 = tip(q), t1 = tip(q + 1), c0 = col(q), c1 = col(q + 1);
    out.tris.push(...b0, ...t0, ...b1, ...t0, ...t1, ...b1);
    out.triColors.push(...c0, ...c0, ...c1, ...c0, ...c1, ...c1);
    out.lines.push(...t0, ...t1);
    out.lineColors.push(...c0, ...c1);
  }
  // the ends of the diagram, closing it on the member
  for (const r of [0, d.x.length - 1]) { out.lines.push(...at(o, ex, d.x[r]), ...tip(r)); out.lineColors.push(...col(r), ...col(r)); }
  let best = 0;
  d[mode].forEach((v, r) => { if (Math.abs(v) > Math.abs(d[mode][best])) best = r; });
  if (vmax > 0 && Math.abs(d[mode][best]) >= 0.02 * vmax) {
    const p = tip(best), push = 0.012 * size * Math.sign(d[mode][best] || 1);
    out.labels.push({ p: [p[0] + dir[0] * push, p[1] + dir[1] * push, p[2] + dir[2] * push], value: d[mode][best], unit: mode === 'N' || mode === 'Vy' || mode === 'Vz' ? 'kN' : 'kNm' });
  }
});
frame/plate-draw.js CadCraft 0.8.2 32-bit, display only 1 calculation Reviewed

Tests tools/frame/test-plates-app.mjs

Plate contours

32-bit, display only

Each plate as two triangles colored by its corner values, with the largest values labeled.

color = ramp(|value| ÷ largest |value|); kN·m/m = N·mm/mm ÷ 1000

Corner values are the smoothed node values. Label text is written with 2 decimals, or with none when the value is 100 or more; labels below 5 % of the largest value are left out.

  • frame/plate-draw.js
  • lines 19 to 49
  • CadCraft 0.8.2
export function plateGeometry(sol, key, mode, RAMP) {
  const out = { max: 0, unit: mode === 'plateW' ? 'mm' : 'kNm/m', tris: [], triColors: [], lines: [], lineColors: [], labels: [] };
  const R = sol?.results?.[key], pl = sol?.prep?.shell?.pl;
  if (!R || !pl?.length || !PLATE_MODES.includes(mode)) return out;
  const prep = sol.prep, P = prep.P;
  const disp = (nd) => Math.hypot(R.u[6 * nd], R.u[6 * nd + 1], R.u[6 * nd + 2]);
  const node = mode === 'plateW' ? null : plateNodeResults(prep, R);
  const corner = (k, c) => {
    const nd = pl[k].nodes[c];
    if (mode === 'plateW') return disp(nd);
    const e = node.get(nd)?.find((q) => q.axes === qkey(pl[k].el.ax));
    return e ? e.values[COMP[mode]] / 1e3 : 0;                          // N·mm/mm → kN·m/m
  };
  const vals = pl.map((_, k) => [0, 1, 2, 3].map((c) => corner(k, c)));
  for (const v of vals) for (const x of v) out.max = Math.max(out.max, Math.abs(x));
  const col = (v) => RAMP(out.max > 0 ? Math.abs(v) / out.max : 0);
  pl.forEach((p, k) => {
    const q = p.nodes.map((nd) => P[nd]);
    for (const tri of [[0, 1, 2], [0, 2, 3]]) for (const c of tri) { out.tris.push(...q[c]); out.triColors.push(...col(vals[k][c])); }
    for (let c = 0; c < 4; c++) { out.lines.push(...q[c], ...q[(c + 1) % 4]); out.lineColors.push(0.25, 0.25, 0.25, 0.25, 0.25, 0.25); }
  });
  // the largest value of each plate set, positive and negative, at the plate it is in
  const groups = new Map();
  pl.forEach((p, k) => { const g = p.el.ax.ez.map((v) => Math.round(v * 1e3)).join(',') + '|' + (prep.model.plates[k].from?.ref || prep.model.plates[k].id.split('.p')[0]); if (!groups.has(g)) groups.set(g, []); groups.get(g).push(k); });
  for (const list of groups.values()) {
    let hi = null, lo = null;
    for (const k of list) for (let c = 0; c < 4; c++) { const v = vals[k][c]; if (!hi || v > hi.v) hi = { v, k, c }; if (!lo || v < lo.v) lo = { v, k, c }; }
    for (const b of mode === 'plateW' ? [hi] : [hi, lo]) if (b && Math.abs(b.v) >= 0.05 * out.max && out.max > 0) out.labels.push({ p: [...P[pl[b.k].nodes[b.c]]], value: b.v, unit: out.unit, text: (Math.abs(b.v) >= 100 ? b.v.toFixed(0) : b.v.toFixed(2)) + (mode === 'plateW' ? ' mm' : '') });
  }
  return out;
}

In detail

Method
Deflections drawn so the largest is 1.5 % of the model's size, diagrams so the largest value is 8 % of it; colors by a four-stop ramp.
Number types
64-bit numbers in plain arrays, handed to the graphics library by the app.
Limits
Shapes are drawn through 17 stations per member by default. Plate label text shows 2 decimals, or none from 100 up.

Approach: Display only

Computed to draw the screen, often in 32-bit on the graphics processor. No number a person reads, saves or exports comes from it.

Every policy that takes this approach