Rebuilds a part feature by feature from a cadcraft-sw-export file: sketches moved into the part's planes, edges and faces found from their positions, parameters and equations carried over, and the volume compared with the one in the file.
How the precision is kept
Coordinates, dimension values and feature sizes are taken from the file as 64-bit numbers in millimeters, with no scaling. Sketch points go through two frame changes in 64-bit and each sketch is then solved, so it meets its dimensions within the solver's tolerance. The edge or face a feature refers to is chosen by distance to the build's display mesh, whose positions and edge segments are Float32Array.
Open item. Measure the distance from each stored position to the kernel's exact edges and faces instead of to the 32-bit display mesh.
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.
swimport.jsPartCraft 0.8.1 32-bit in part11 calculationsReviewed
Tests bin/test-partcraft-sw.mjs
Sketch plane from the file
64-bit
Reads a sketch plane's origin, normal and x direction and checks that x lies in the plane.
|x̂ · n̂| ≤ 1e-6
The check is on unit vectors, so it is an angle test: x may be up to about 1e-6 rad out of the plane and still be accepted. The vectors are kept as read.
swimport.js
lines 138 to 146
PartCraft 0.8.1
function readPlaneSpec(p, owner, mirror = false) {
if (!p || typeof p !== 'object') bad(of(owner, 'xch.swimp.what.plane'));
if (typeof p.base === 'string') { if (!['front', 'top', 'right'].includes(p.base)) bad(of(owner, 'xch.swimp.what.plane')); return { base: p.base }; }
const o = vec3(p.o, of(owner, 'xch.swimp.what.plane')), n = dir3(p.n, of(owner, 'xch.swimp.what.plane'));
if (mirror) return { o, n };
const x = dir3(p.x, of(owner, 'xch.swimp.what.plane_x'));
if (Math.abs(V.dot(V.norm(x), V.norm(n))) > 1e-6) bad(of(owner, 'xch.swimp.what.plane_x_off'));
return { o, x, n };
}
Sketch points into the part's plane
64-bit
Builds the file's sketch frame and maps each sketch point into the frame of the plane the sketch is put on.
x = unit(x − n·(x · n)); y = n × x; (u, v) → world = o + u·x + v·y → plane = ((w − o′) · x′, (w − o′) · y′)
The x direction is made square to the normal by removing its part along the normal once, then normalized. On a base plane the axes hold only 0 and ±1, so the mapping adds no rounding; on any other plane each point is two 64-bit frame changes and can differ from the file's value in the last bits.
The distance from a position to a straight piece of an edge.
t = clamp((p − a) · (b − a) ÷ |b − a|², 0, 1); d = |p − (a + t·(b − a))|
64-bit arithmetic on the values it is given; a zero-length segment divides by 1e-300 in place of zero.
swimport.js
lines 184 to 187
PartCraft 0.8.1
function pointSegDist(p, a, b) {
const ab = V.sub(b, a), t = Math.max(0, Math.min(1, V.dot(V.sub(p, a), ab) / (V.dot(ab, ab) || 1e-300)));
return V.len(V.sub(p, V.add(a, V.mul(ab, t))));
}
Nearest edge to a stored position
32-bit in part
Finds which edge of the part built so far a fillet or chamfer in the file refers to, and how far the stored position is from it.
minimum over the edge polyline segments of the point-to-segment distance
The segments are read from the build's edgeSegs, a Float32Array, so each distance carries about 7 significant digits of the coordinates. The largest such distance is shown in a warning when it exceeds max(1e-3, the fillet or chamfer size); otherwise only the choice of edge is kept.
swimport.js
lines 188 to 200
PartCraft 0.8.1
/** The index of the regen edge nearest a point (OCCT's edge polylines), and that distance. */
function nearestEdge(rg, p) {
const S6 = rg.mesh?.edgeSegs, E = rg.mesh?.segEdge;
let best = -1, bd = Infinity;
if (S6 && E) {
for (let k = 0; k < E.length; k++) {
if (E[k] < 0) continue;
const d = pointSegDist(p, [S6[6 * k], S6[6 * k + 1], S6[6 * k + 2]], [S6[6 * k + 3], S6[6 * k + 4], S6[6 * k + 5]]);
if (d < bd) { bd = d; best = E[k]; }
}
} else rg.edges.forEach((e, i) => { const d = Math.min(V.len(V.sub(e.at, p)), V.len(V.sub(e.a, p)), V.len(V.sub(e.b, p))); if (d < bd) { bd = d; best = i; } });
return { index: best, dist: bd };
}
Distance from a point to a triangle
64-bit
The distance from a position to one display triangle of a face.
inside the triangle: |(p − a) · n̂|; else the least distance to its three sides
64-bit arithmetic on the values it is given. The inside test allows a slack of 1e-12 relative to the triangle's size, and a triangle with no area falls back to its sides.
swimport.js
lines 201 to 209
PartCraft 0.8.1
function pointTriDist(p, a, b, c) {
const n = V.cross(V.sub(b, a), V.sub(c, a)), nl = V.len(n);
if (nl > 1e-300) {
const u = V.mul(n, 1 / nl), d = V.dot(V.sub(p, a), u), q = V.sub(p, V.mul(u, d));
const inside = [[a, b], [b, c], [c, a]].every(([x, y]) => V.dot(V.cross(V.sub(y, x), V.sub(q, x)), n) >= -1e-12 * nl * V.len(V.sub(y, x)));
if (inside) return Math.abs(d);
}
return Math.min(pointSegDist(p, a, b), pointSegDist(p, b, c), pointSegDist(p, c, a));
}
Nearest face to a stored position
32-bit in part
Finds which face of the part built so far a feature in the file refers to.
minimum over the display triangles of the point-to-triangle distance
The triangle corners are read from the build's display mesh, whose positions are a Float32Array, and a curved face is represented by flat triangles, so the distance is to the tessellation, not to the exact face. The result is used to choose the face.
swimport.js
lines 210 to 217
PartCraft 0.8.1
/** The index of the regen face nearest a point (its triangles), and that distance. */
function nearestFace(rg, p) {
const M = rg.mesh, Pp = M.positions, I = M.indices, TF = M.triFace;
let best = -1, bd = Infinity;
const at = (i) => [Pp[i * 3], Pp[i * 3 + 1], Pp[i * 3 + 2]];
for (let t = 0; t < TF.length; t++) { const d = pointTriDist(p, at(I[3 * t]), at(I[3 * t + 1]), at(I[3 * t + 2])); if (d < bd) { bd = d; best = TF[t]; } }
return { index: best, dist: bd };
}
Flat face lying in a sketch's plane
64-bit
Looks for a named flat face of the part in the plane a sketch was drawn on, so the sketch can follow that face.
A dot product compared with 1 to within 1e-9 accepts normals up to about 4.5e-5 rad off the axis; the plane is then treated as the base plane exactly.
swimport.js
lines 585 to 590
PartCraft 0.8.1
/** The base plane (o, n) is, if any: through the origin, normal along X, Y or Z. */
function baseOf(o, n) {
const u = V.norm(n);
for (const [name, f] of Object.entries(V.BASE_PLANES)) if (Math.abs(Math.abs(V.dot(u, f.n)) - 1) < 1e-9 && Math.abs(V.dot(o, f.n)) < 1e-9 * Math.max(1, V.len(o))) return name;
return null;
}
Dimension turned into a parameter
64-bit
Gives a dimension that other equations use a parameter of its own, holding its value.
expression text = formatNumber(value) (12 significant digits, expr.js)
The value is written as text by formatNumber, which rounds to 12 significant digits, and the parameter then drives the dimension. A value with more than 12 significant digits is therefore changed from the 13th on.
Compares the volume of the rebuilt part with the volume recorded in the file.
same when |V − V_file| ≤ 1e-4 × max(1, V_file)
The rebuilt volume comes from the build's statistics. Volumes that agree to about 4 significant digits are reported as the same; the report line shows both rounded to 3 decimals.
Frame change sketch → world → plane; nearest edge by point-to-segment distance over the edge polylines; nearest face by point-to-triangle distance; volume compared with the file's.
Number types
64-bit numbers for the file's values; Float32Array mesh positions and edge segments (made in brep.js) read for the nearest-edge and nearest-face distances.
Tolerances
Plane x direction square to the normal within 1e-6. Base plane: normal within 1e-9 and offset within 1e-9 × max(1, |o|). Flat face in a plane: normal within 1e-9, distance within 1e-7 × max(1, |o|). Sketch axes the same or swapped within 1e-9. Volume the same within 1e-4 × max(1, volume). Equation result differs when over 1e-6 × max(1, |value|). An edge counts as far when over max(1e-3, the fillet or chamfer size).
Limits
Only millimeter files are accepted. A dimension that other equations use becomes a parameter written with 12 significant digits. The pick distances carry the 32-bit rounding of the display mesh.
Approach: 32-bit step, flagged
One step stores or exchanges 32-bit values that reach a number a person reads. It is listed here with its open item, and the step is either moved to 64-bit or the app will warn where the number is read.