Resources Numerical accuracy Calculation policy

Surface features built with the kernel

Makes freeform surface bodies and turns them into solids: patches, lofts, network surfaces, offsets, thickened and stitched surfaces.

How the precision is kept

The geometry is computed by the OpenCascade kernel in double precision from 64-bit numbers handed to it. Each operation is approximate to a tolerance this file passes in: the filling tolerances of a patch, the loft's approximation tolerance, the stitching tolerance, and the sewing tolerance of a network surface.

Calculations under this policy

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

surface.js PartCraft 0.8.1 64-bit 8 calculations Reviewed

Tests tools/partcraft-test/test-form.mjs, tools/partcraft-test/test-gordon.mjs

Patch through boundary edges

64-bit

A surface that fills a closed loop of edges, optionally tangent to the faces next to it.

MakeFilling(3, 15, 2, false, 1e-5, 1e-4, 0.01, 0.1, 8, 9)

The surface is an approximation computed by OpenCascade to the tolerances passed here: 1e-4 in 3-D and 0.01 on the angle. The largest gap to the boundary (G0Error) and, with supports, the largest tangent error (G1Error) are read back and returned with the shape.

  • surface.js
  • lines 209 to 223
  • PartCraft 0.8.1
/** A filling surface through closed boundary edges (and tangent to `supports[i]` faces where given). */
export function fillShape(oc, edges, { supports = null, T: T0 = null } = {}) {
  const { T, done } = arenaOf(T0);
  const mk = T(new oc.BRepOffsetAPI_MakeFilling(3, 15, 2, false, 1e-5, 1e-4, 0.01, 0.1, 8, 9));
  edges.forEach((e, i) => {
    const E = T(oc.TopoDS.Edge(e));
    const sup = supports && supports[i];
    if (sup) mk.Add(E, T(oc.TopoDS.Face(sup)), oc.GeomAbs_Shape.GeomAbs_G1, true);
    else mk.Add(E, oc.GeomAbs_Shape.GeomAbs_C0, true);
  });
  try { mk.Build(prog(oc, T)); } catch { sfail(tr('model.surface.the_patch_could_not_be_made')); }
  if (!mk.IsDone()) sfail(tr('model.surface.the_patch_could_not_be_made'));
  const g0 = mk.G0Error(), g1 = supports ? mk.G1Error() : 0;
  return { shape: done(T(mk.Shape())), g0, g1 };
}

Loft through wires

64-bit

A surface or solid through a series of curves in order.

ThruSections(solid, ruled, 1e-6)

OpenCascade builds the loft with an approximation tolerance of 1e-6 passed here. At least two wires are required.

  • surface.js
  • lines 225 to 235
  • PartCraft 0.8.1
/** A lofted surface (or solid) through wires in order. */
export function loftShape(oc, wires, { solid = false, ruled = false, T: T0 = null } = {}) {
  const { T, done } = arenaOf(T0);
  if (wires.length < 2) sfail(tr('model.surface.a_loft_needs_at_least_two_curves'));
  const mk = T(new oc.BRepOffsetAPI_ThruSections(solid, ruled, 1e-6));
  for (const w of wires) mk.AddWire(T(oc.TopoDS.Wire(w)));
  mk.CheckCompatibility(true);
  try { mk.Build(prog(oc, T)); } catch { sfail(tr('model.surface.the_loft_could_not_be_made_through')); }
  if (!mk.IsDone()) sfail(tr('model.surface.the_loft_could_not_be_made_through'));
  return { shape: done(T(mk.Shape())), mk };
}

Thickening a surface

64-bit

Turns a surface body into a solid of a given thickness on one side or half on each side.

both sides: offset the sheet by −t ÷ 2, then thicken by t; in: thicken by −t

The halving of the thickness is exact; the offset and the thick solid are OpenCascade's simple offset algorithm. The result is reversed when its volume comes out negative.

  • surface.js
  • lines 247 to 261
  • PartCraft 0.8.1
/** Thicken a surface into a solid: out (along its normal), in, or both (half each way). */
export function thickenShape(oc, sheet, t, { side = 'out', T: T0 = null } = {}) {
  const { T, done } = arenaOf(T0);
  if (!(t > 0)) sfail(tr('model.surface.the_thickness_must_be_more_than_zero'));
  let base = sheet;
  if (side === 'both') base = offsetShape(oc, sheet, -t / 2, { T }).shape;
  const mk = T(new oc.BRepOffsetAPI_MakeThickSolid());
  try { mk.MakeThickSolidBySimple(base, side === 'in' ? -t : t); mk.Build(prog(oc, T)); } catch { sfail(tr('model.surface.the_surface_could_not_be_thickened')); }
  if (!mk.IsDone()) sfail(tr('model.surface.the_surface_could_not_be_thickened'));
  let s = T(mk.Shape());
  const solids = explore(oc, T, s, 'SOLID');
  if (!solids.length) sfail(tr('model.surface.thickening_made_no_solid'));
  if (volumeOf(oc, T, s) < 0) s = T(s.Reversed());
  return { shape: done(s) };
}

Offsetting a surface

64-bit

A new surface at a distance along the normals of an existing one.

MakeOffsetShape.PerformBySimple(sheet, d)

The distance is passed unchanged as a 64-bit number. The offset surface is computed by OpenCascade; this file adds no tolerance of its own.

  • surface.js
  • lines 263 to 270
  • PartCraft 0.8.1
/** Offset a surface along its normals. */
export function offsetShape(oc, sheet, d, { T: T0 = null } = {}) {
  const { T, done } = arenaOf(T0);
  const mk = T(new oc.BRepOffsetAPI_MakeOffsetShape());
  try { mk.PerformBySimple(sheet, d); } catch { sfail(tr('model.surface.the_surface_could_not_be_offset')); }
  if (!mk.IsDone()) sfail(tr('model.surface.the_surface_could_not_be_offset'));
  return { shape: done(T(mk.Shape())), mk };
}

Stitching surfaces

64-bit

Sews surface bodies together along their edges, into a solid when they close.

edges closer than tol are joined; tol = 0.01 by default

The stitching tolerance is a model length, 0.01 by default, and edges within it are merged by OpenCascade's sewing (in form-brep.js sewFaces). Geometry up to that far apart is treated as one.

  • surface.js
  • lines 272 to 279
  • PartCraft 0.8.1
/** Sew shapes; closed → { shape: solid, closed: true }. */
export function stitchShapes(oc, shapes, { tol = 0.01, T: T0 = null } = {}) {
  const { T, done } = arenaOf(T0);
  const faces = shapes.flatMap((s) => explore(oc, T, s, 'FACE'));
  if (!faces.length) sfail(tr('model.surface.there_are_no_surfaces_to_stitch'));
  const r = sewFaces(oc, T, faces, tol);
  return { shape: done(r.shape), closed: r.closed, free: r.free, sew: r.sew, faces };
}

Sketch curves as kernel edges

64-bit

Turns the lines, arcs, circles and splines of a sketch into the kernel's edges for a loft.

arc: start, middle and end points c + r × (cos, sin) at a₀, a₀ + sweep ÷ 2, a₀ + sweep, then an arc through the three points

A circle is passed by its center, axis and radius. An arc is passed as three points computed with cos and sin, and the kernel finds the circle through them; for an arc of very small sweep the three points are nearly in line and the circle is poorly determined by them. Ends join when closer than 1e-5 × max(1, extent).

  • surface.js
  • lines 466 to 487
  • PartCraft 0.8.1
const tol = 1e-6 * Math.max(1, extent(sk));
const W = (p) => V.toWorld(frame, p[0], p[1]);
const segs = [];
for (const e of sk.entities) {
  if (e.construction) continue;
  if (e.type === 'line') segs.push({ e, a: pointXY(sk, e.p1), b: pointXY(sk, e.p2) });
  else if (e.type === 'arc') { const { c, r, a0, sweep } = arcAngles(sk, e); segs.push({ e, a: [c[0] + r * Math.cos(a0), c[1] + r * Math.sin(a0)], b: [c[0] + r * Math.cos(a0 + sweep), c[1] + r * Math.sin(a0 + sweep)], mid: [c[0] + r * Math.cos(a0 + sweep / 2), c[1] + r * Math.sin(a0 + sweep / 2)] }); }
  else if (e.type === 'circle') segs.push({ e, closed: true, c: pointXY(sk, e.c), r: e.r });
  else if (e.type === 'spline') { const bs = splineCurve(sk, e); const pts = SP.tessellate(bs, 4); segs.push({ e, bs, closed: !!e.closed, a: pts[0], b: pts[pts.length - 1] }); }
}
if (!segs.length) fail(tr('model.surface.a_sketch_it_lofts_through_has_no'));
const closedOne = segs.filter((s) => s.closed);
if (closedOne.length && segs.length > 1) fail(tr('model.surface.a_sketch_it_lofts_through'));
const mw = T(new oc.BRepBuilderAPI_MakeWire());
const near = (p, q) => Math.hypot(p[0] - q[0], p[1] - q[1]) <= tol * 10;
const edgeOf = (s, rev) => {
  if (s.c) return T(new oc.BRepBuilderAPI_MakeEdge(T(new oc.gp_Circ(T(new oc.gp_Ax2(H.pnt(W(s.c)), H.dir(frame.n), H.dir(frame.x))), s.r)))).Edge();
  if (s.bs) return T(new oc.BRepBuilderAPI_MakeEdge(splineGeom(oc, T, rev ? SP.reverse(s.bs) : s.bs, W))).Edge();
  const [a, b] = rev ? [s.b, s.a] : [s.a, s.b];
  if (s.mid) return T(new oc.BRepBuilderAPI_MakeEdge(T(T(new oc.GC_MakeArcOfCircle(H.pnt(W(a)), H.pnt(W(s.mid)), H.pnt(W(b)))).Value()))).Edge();
  return T(new oc.BRepBuilderAPI_MakeEdge(H.pnt(W(a)), H.pnt(W(b)))).Edge();
};

Network surface as a kernel face

64-bit

Hands the surface fitted through a network of curves to the kernel and sets the tolerance its seams are sewn with.

face tolerance 1e-9; sewing tolerance = max(1e-7, 1e-7 × size)

The surface itself, and how closely it follows the curves, come from gordon in gordon.js with the feature's tolerance (0.01 by default). Here the poles and knots are passed to OpenCascade unchanged and the sewing tolerance is scaled to the network's size.

  • surface.js
  • lines 674 to 679
  • PartCraft 0.8.1
const g = GD.gordon(curvesOf(f.profiles), curvesOf(f.guides), { tol: f.tol, tangent });
const rep = g.report;
const mf = T(new oc.BRepBuilderAPI_MakeFace(bsplineSurface(oc, T, g.surface), 1e-9));
if (!mf.IsDone()) fail(tr('model.surface.the_network_surface_could_not_be_made'));
const face = T(mf.Face());
const sewTol = Math.max(1e-7, 1e-7 * rep.size);

Area of a surface body

64-bit

The area of each surface body left after a rebuild.

area = Σ over the faces of OpenCascade's surface area

The area is summed from OpenCascade's surface properties of the exact faces, not from the display triangles made in the same function.

  • surface.js
  • lines 408 to 409
  • PartCraft 0.8.1
let area = 0;
for (const f of explore(oc, T, sh.shape, 'FACE')) { const g = T(new oc.GProp_GProps()); oc.BRepGProp.SurfaceProperties(f, g, false, false); area += g.Mass(); }

In detail

Method
BRepOffsetAPI_MakeFilling, BRepOffsetAPI_ThruSections, BRepOffsetAPI_MakeThickSolid (MakeThickSolidBySimple), BRepOffsetAPI_MakeOffsetShape (PerformBySimple), BRepBuilderAPI_Sewing; the network surface itself is fitted in gordon.js and handed over as a B-spline surface.
Number types
64-bit numbers passed to OpenCascade; OpenCascade works in double precision.
Tolerances
Patch: degree 3, 15 points per edge, 2 iterations, tolerances 1e-5 (2-D), 1e-4 (3-D), 0.01 (angle) and 0.1 (curvature), maximum degree 8, 9 segments. Loft: 1e-6. Stitch: 0.01 by default (1e-7 to 10 from a file). Network surface face: 1e-9; its sewing tolerance max(1e-7, 1e-7 × size). Sketch ends join within 1e-5 × max(1, extent). A curve is closed when its ends are within 1e-9 × max(1, |start|).
Limits
A patch meets its boundary only to the filling tolerance; the gap and tangent errors OpenCascade measured are returned and reported. Stitching moves edges up to the stitching tolerance apart onto each other.

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