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