Works out the lengths, angles and positions handed to the solid kernel for extrudes, revolves, holes, patterns, shells and chamfers.
How the precision is kept
Each feature's numbers are single 64-bit expressions from the stored dimensions; the solid operations themselves are OpenCascade's. Angles are stored in degrees and converted with Math.PI ÷ 180. Pattern copies are placed at step × i from the seed, not by repeated moves. Through-all depths are lengthened past the body on purpose.
Open item. Replace the thread factor 1.082532 with 5 × √3 ÷ 8 and review the fixed 1e-3 mm tolerance passed to the shell offset.
Calculations under this policy
11 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.
Where an extrude starts and how far it runs for blind, mid-plane and through-all ends.
blind: z₀ = 0 or −h; mid: z₀ = −h ÷ 2; through: h = far side + 1 + 0.01 × body range
Blind and mid-plane depths use the stored depth unchanged. A through-all cut is lengthened by 1 mm plus 1 % of the body's extent so it clears the far side; the excess lies outside the body.
brep.js
lines 446 to 475
PartCraft 0.8.1
extrude(f, ctx) {
const { oc, T, H } = ctx;
const { sk, frame } = sketchOf(ctx, f.sketch);
const pr = profileCurves(sk);
if (pr.error) fail(pr.error);
if (!pr.loops.length) fail(tr('model.brep.no_profile_extrude'));
if (LIB.extrudeExtended(f)) return LIB.extrudeExt(f, ctx, pr, frame);
const sign = (f.op === 'cut' ? -1 : 1) * (f.reverse ? -1 : 1); // add, intersect and new bodies grow the same way
let h = f.depth, z0;
if (f.end === 'blind') z0 = sign > 0 ? 0 : -h;
else if (f.end === 'mid') z0 = -h / 2;
else {
if (f.op !== 'cut') fail(tr('model.brep.through_all_cuts'));
const rg = bodyRange(ctx, frame);
if (!rg) fail(tr('model.brep.nothing_to_cut'));
const margin = 1 + 0.01 * (rg[1] - rg[0]);
if (sign > 0) { if (rg[1] <= 1e-9) fail(tr('model.brep.nothing_to_cut_side')); z0 = 0; h = rg[1] + margin; }
else { if (rg[0] >= -1e-9) fail(tr('model.brep.nothing_to_cut_side')); h = -rg[0] + margin; z0 = -h; }
}
if (!(h > 0)) fail(tr('model.brep.depth_positive'));
const { faces, edgeOf } = profileFaces(oc, T, H, pr.loops, frame, z0);
const tools = [];
for (const face of faces) {
const mk = T(new oc.BRepPrimAPI_MakePrism(face, H.vec(V.mul(frame.n, h)), false, true));
if (!mk.IsDone()) fail(tr('model.brep.extrude_failed'));
const shape = T(mk.Shape());
tools.push({ shape, op: f.op, names: nameSweep(ctx, mk, f.id, edgeOf, true) });
}
return { tools };
},
Revolve axis checks
64-bit
Checks that the axis lies in the sketch plane and that the profile stays on one side of it.
side s = −u_y (p_x − a_x) + u_x (p_y − a_y); crosses when some s > 1e-7 × scale and some s < −1e-7 × scale
The side value is a 2D cross product with the unit axis direction. It is tested on the profile's polyline points, so a curve that bulges across the axis between two points is not seen. Tolerances scale with the largest distance from the axis start.
brep.js
lines 484 to 508
PartCraft 0.8.1
const sc = Math.max(1, V.len(V.sub(ax.o, frame.o)));
if (Math.abs(V.dot(ax.d, frame.n)) > 1e-9 || Math.abs(V.dot(V.sub(ax.o, frame.o), frame.n)) > 1e-7 * sc) fail(tr('model.brep.axis_in_plane'));
a2 = V.toPlane(frame, ax.o); b2 = V.toPlane(frame, V.add(ax.o, ax.d));
axis = { id: null, construction: true };
} else {
axis = f.axis ? byId(sk, f.axis) : sk.entities.find((e) => e.type === 'line' && e.construction);
if (!axis || axis.type !== 'line') fail(f.axis ? tr('model.brep.axis_line_gone') : tr('model.brep.draw_centerline'));
a2 = pointXY(sk, axis.p1); b2 = pointXY(sk, axis.p2);
}
const ul = Math.hypot(b2[0] - a2[0], b2[1] - a2[1]);
if (ul < 1e-12) fail(tr('model.brep.axis_no_length'));
const u = [(b2[0] - a2[0]) / ul, (b2[1] - a2[1]) / ul];
const skNoAxis = axis.construction ? sk : { ...sk, entities: sk.entities.map((e) => (e.id === axis.id ? { ...e, construction: true } : e)) };
let pr = profileCurves(skNoAxis);
if (pr.error && !axis.construction) pr = profileCurves(sk);
if (pr.error) fail(pr.error);
if (!pr.loops.length) fail(tr('model.brep.no_profile_revolve'));
const all = pr.loops.flatMap((l) => l.poly);
const scale = Math.max(1, ...all.map((p) => Math.hypot(p[0] - a2[0], p[1] - a2[1])));
let pos = 0, neg = 0;
for (const p of all) {
const s = -u[1] * (p[0] - a2[0]) + u[0] * (p[1] - a2[1]);
if (s > 1e-7 * scale) pos++; else if (s < -1e-7 * scale) neg++;
}
if (pos && neg) fail(tr('model.brep.profile_crosses_axis'));
Revolve angle
64-bit
The sweep angle given to the kernel.
full turn when |angle − 360| < 1e-9; else angle × π ÷ 180
A full turn is built as a closed revolution with no angle passed, so 360° is not subject to the rounding of 2π. Other angles are one multiplication and one division.
brep.js
lines 520 to 524
PartCraft 0.8.1
const full = Math.abs(f.angle - 360) < 1e-9;
const tools = [];
for (const face of faces) {
// Copy = true: with false, Generated() misses some faces of a full revolution in this OCCT build
const mk = full ? T(new oc.BRepPrimAPI_MakeRevol(face, ax1, true)) : T(new oc.BRepPrimAPI_MakeRevol(face, ax1, f.angle * Math.PI / 180, true));
Through-hole depth
64-bit
How deep a through hole is drilled.
depth = distance to the body's far side + 1 + 0.01 × body range
The body's extent along the hole comes from its bounding-box corners. The hole is made 1 mm plus 1 % longer than that, so the excess is outside the body.
One expression. The included angle in degrees is halved and converted in a single factor π ÷ 360. The tangent is not guarded against an angle of 0 or 180.
The major and minor radius recorded for a tapped hole's thread.
r₁ = D ÷ 2; r₀ = min(tap drill, D − 1.082532 × P) ÷ 2
The factor 1.082532 is a rounded constant; the exact value is 5√3 ÷ 8 = 1.0825317547…, a difference of about 2.5e-7 × pitch in the diameter. The thread is not modeled as geometry; these radii feed the drawing note and the interference check's thread zones.
brep.js
lines 589 to 590
PartCraft 0.8.1
// the thread is cosmetic: drawn as the major diameter over the tap drill, and noted (ISO 261 / ISO 2306)
if (thread) threads.push({ o: P0, d: din, r: thread[0] / 2, depth: f.threadDepth || (f.through ? depth - 1 : depth), r0: Math.min(thread[2], thread[0] - 1.082532 * thread[1]) / 2, r1: thread[0] / 2 });
Linear pattern positions
64-bit
Where each copy of a linear pattern sits.
copy i moved by direction × spacing × i
Each copy's translation is computed from the seed as spacing × i, so rounding does not accumulate along the pattern.
full circle: step = 360 ÷ count; otherwise step = angle ÷ (count − 1); copy i turned by step × i
Each copy is rotated from the seed by step × i degrees in one rotation, not by repeated turns. The step is in degrees and converted to radians inside the rotation function in vec.js.
offset = +t (outward) or −t; kernel tolerance 1e-3
The thickness is passed unchanged. The offset operation is called with a tolerance argument of 1e-3, a fixed value in the code that does not scale with the part.
The radius, distances or distance and angle given to the kernel for each picked edge.
two distances: (d, d₂); distance and angle: (d, angle × π ÷ 180); fillet: r, or (r, r₂) along the edge
Sizes are passed as stored. Only the chamfer angle is converted, with one multiplication and one division.
brep.js
lines 738 to 741
PartCraft 0.8.1
if (kind === 'chamfer' && f.mode === 'two') mk.Add(size, f.d2, E, faceOn(f.edges[i], e));
else if (kind === 'chamfer' && f.mode === 'angle') mk.AddDA(size, f.angle * Math.PI / 180, E, faceOn(f.edges[i], e));
else if (kind === 'fillet' && typeof f.r2 === 'number') { if (f.flip) mk.Add(f.r2, size, E); else mk.Add(size, f.r2, E); }
else mk.Add(size, E);
Sewing an imported surface model
64-bit
Joins the faces of a STEP file that has no solid into a closed shell.
sewing tolerance = 1e-6
Used only when the file contains no solid. Faces whose edges are farther apart than 1e-6 (model units) are not joined, and the import then reports that there is no solid.
brep.js
lines 928 to 929
PartCraft 0.8.1
// a closed shell written as a surface model: sew it and make it a solid
const sew = T(new oc.BRepBuilderAPI_Sewing(1e-6, true, true, true, false));
In detail
Method
Prism, revolution, Boolean, fillet, chamfer and offset operations of OpenCascade, with face names carried through its history.
Number types
64-bit numbers.
Tolerances
Full revolution or full circular pattern when |angle − 360| < 1e-9. External revolve axis in the sketch plane within 1e-9 (direction) and 1e-7 × scale (position). Profile on one side of the axis within 1e-7 × scale. Shell offset requested with tolerance 1e-3. STEP surface models sewn with tolerance 1e-6.
Limits
The cosmetic thread's minor diameter uses the rounded factor 1.082532 (5√3 ÷ 8 = 1.0825317547…). The shell's 1e-3 tolerance is an absolute value in millimeters whatever the part's size.
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.