Resources Numerical accuracy Calculation policy

Sheet metal base and edge flanges

Builds the cross-section of a sheet metal flange (bend arcs, leg, tip) and its bend reliefs from thickness, bend radius, angle and length.

How the precision is kept

The section's points are closed-form 64-bit expressions in the bend angle. Arcs are given to OpenCascade as three points (start, point at half the angle, end). Points that are numerically identical share one vertex because vertices are looked up by the text of their coordinates.

Calculations under this policy

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

brep-sheet.js PartCraft 0.8.1 64-bit 4 calculations Reviewed

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

Edge flange frame

64-bit

The axes a flange is built in: along the edge, out of the sheet's face, and out of its side.

e = unit(B − A); square when |n_big · n_side| ≤ 1e-6; P = (A + B) ÷ 2 − v × t ÷ 2 must lie on the side face

Both checks are distances or dot products in 64-bit. The squareness test is absolute on unit normals; the thickness test scales with the distance of the edge from the origin.

  • brep-sheet.js
  • lines 69 to 91
  • PartCraft 0.8.1
/** The edge flange's frame: A, B the edge's ends; e along it; v the big face's outward normal (flip: the other big
 *  face, A/B moved onto it); u the side face's outward normal. */
function flangeFrame(ctx, f, sheet) {
  if (!ctx.body) fail(t('sheet.err.no_body'));
  if (!f.edge) fail(t('sheet.err.pick_edge'));
  const key = [...f.edge.faces].sort().join('|');
  const cands = ctx.edgeTable().filter((x) => x.key === key);
  if (!cands.length) fail(t('sheet.err.edge_gone', { a: describeName(ctx.part, f.edge.faces[0]), b: describeName(ctx.part, f.edge.faces[1]) }));
  const er = nearest(cands, f.edge.at, (x) => x.at);
  if (er.type !== 'line') fail(t('sheet.err.straight'));
  const faces = ctx.faceTable().filter((x) => f.edge.faces.includes(x.name) && x.type === 'plane' && ctx.H.explore(x.face, 'EDGE').some((g) => g.IsSame(er.edge)));
  if (faces.length !== 2) fail(t('sheet.err.planar'));
  const e = V.norm(V.sub(er.b, er.a));
  // the big face: the larger one; the side face must be t wide across the edge
  const [big, side] = faces[0].area >= faces[1].area ? faces : [faces[1], faces[0]];
  if (Math.abs(V.dot(big.n, side.n)) > 1e-6) fail(t('sheet.err.square'));
  let v = big.n, u = side.n, A = er.a, B = er.b;
  if (f.flip) { v = V.mul(v, -1); A = V.add(A, V.mul(v, sheet.t)); B = V.add(B, V.mul(v, sheet.t)); }   // onto the other big face
  // the sheet is t thick here: the side face reaches t below the edge
  const P = V.add(V.mul(V.add(A, B), 0.5), V.mul(v, -sheet.t * 0.5)), sideOk = Math.abs(V.dot(V.sub(P, side.o), side.n)) < 1e-6 * Math.max(1, V.len(P));
  if (!sideOk) fail(t('sheet.err.thickness', { t: sheet.t }));
  return { A, B, e, u, v, len: V.len(V.sub(B, A)) };
}

Flange cross-section

64-bit

The corner points of the bend and the leg in the plane square to the edge.

C = (0, r); point(ρ, φ) = C + ρ (cos φ, sin φ); φ from −π ÷ 2 to −π ÷ 2 + θ; leg end = arc end + length × (−sin φ₁, cos φ₁)

Each point is one trigonometric expression of the angle, not a chain of rotations. For a 90° bend the end angle φ₁ is exactly 0 in 64-bit, so the leg direction is exact; for a 180° bend cos(π ÷ 2) evaluates to about 6e-17 instead of 0, and that stays in the coordinates passed on.

  • brep-sheet.js
  • lines 99 to 109
  • PartCraft 0.8.1
const r = sheet.r, th = sheet.t, ang = f.angle * Math.PI / 180;
// the section in (u, v) about the start of the flange's run; C = the bend centre
const S0 = V.add(A, V.mul(e, o0));
const W = (p) => V.add(S0, V.add(V.mul(u, p[0]), V.mul(v, p[1])));
const C = [0, r];
const at = (rad, phi) => [C[0] + rad * Math.cos(phi), C[1] + rad * Math.sin(phi)];
const p0 = -Math.PI / 2, p1 = p0 + ang, pm = p0 + ang / 2;
const tan = [-Math.sin(p1), Math.cos(p1)];
const Ie = at(r, p1), Oe = at(r + th, p1);
const It = [Ie[0] + tan[0] * f.length, Ie[1] + tan[1] * f.length], Ot = [Oe[0] + tan[0] * f.length, Oe[1] + tan[1] * f.length];
const P0 = [0, 0], Q0 = [0, -th];

Bend relief slot

64-bit

The box cut beside an offset flange end so the bend can form.

depth into the sheet = r + t; box length along u = (r + t) + 1e-3 × max(1, r + t); through 2 × t

The box starts r + t inside the sheet's edge and runs past the edge by 0.1 % (at least 0.001 mm), so the extra length is in air. Its height is twice the thickness centered on the sheet.

  • brep-sheet.js
  • lines 133 to 139
  • PartCraft 0.8.1
const deep = r + th, w = f.reliefWidth;
const slot = (s0, s1) => {
  if (!(s1 - s0 > 1e-6)) return;
  // gp_Ax2(o, N = u × e, X = u): Y = N × X = e; the box spans 2t along N, centred on the sheet whichever way N points
  const N = V.cross(u, e), up = V.dot(N, v) > 0;
  const o = V.add(A, V.add(V.mul(e, s0), V.add(V.mul(u, -deep), V.mul(v, up ? -th * 1.5 : th * 0.5))));
  const mb = T(new oc.BRepPrimAPI_MakeBox(T(new oc.gp_Ax2(H.pnt(o), H.dir(N), H.dir(u))), deep + 1e-3 * Math.max(1, deep), s1 - s0, th * 2));

Side of the first bend

64-bit

Which side of an open profile the sheet's thickness goes on.

turn = sign((p₁ − p₀) × (p₂ − p₁))

The sign of a 2D cross product of two segment vectors, computed as a difference of two products. For three nearly collinear points the sign can be decided by rounding; an exact zero is treated as no turn.

  • brep-sheet.js
  • lines 55 to 57
  • PartCraft 0.8.1
const turn = pts.length > 2 ? Math.sign((pts[1][0] - pts[0][0]) * (pts[2][1] - pts[1][1]) - (pts[1][1] - pts[0][1]) * (pts[2][0] - pts[1][0])) : 0;
let bp;
try { bp = bentProfile(pts, { t: f.t, r: f.r, side: dir * (turn || 1) }); } catch (e) { fail(e.message); }

In detail

Method
Bend center at (0, r) in the edge's frame; inner arc radius r, outer r + t, from −90° through the bend angle; leg along the tangent for the flange length; prism along the edge for the run less the offsets.
Number types
64-bit numbers; the angle is in degrees in the file and converted with Math.PI ÷ 180.
Tolerances
Faces square when |n₁ · n₂| ≤ 1e-6. Sheet thickness matches when the side face is within 1e-6 × max(1, |P|) of the expected point. Run and relief width must exceed 1e-6 mm. An inner arc is made only when r > 1e-9.
Limits
The bigger of the two faces at the edge is taken as the sheet's face by area.

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