Resources Numerical accuracy Calculation policy

Import of a parametric export file

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.js PartCraft 0.8.1 32-bit in part 11 calculations Reviewed

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.

  • swimport.js
  • lines 417 to 430
  • PartCraft 0.8.1
  const n = V.norm(f.plane.n), x = V.norm(V.sub(f.plane.x, V.mul(n, V.dot(f.plane.x, n))));
  swFrame = { o: f.plane.o, x, y: V.cross(n, x), n };
  const b = baseOf(f.plane.o, n);
  if (b) { ref = { kind: 'base', name: b }; frame = V.BASE_PLANES[b]; }
  else {
    const face = part.features.length ? planarFaceAt(f.plane.o, n) : null;
    if (face) { ref = refForName(face.name); frame = V.frameFor(face.n, face.o); }
    else { ref = { kind: 'fixed', o: [...f.plane.o], n }; frame = V.frameFor(n, f.plane.o); }
  }
}
const flipped = V.dot(swFrame.n, frame.n) < 0;
const ax = Math.abs(V.dot(swFrame.x, frame.x));
const axes = ax > 1 - 1e-9 ? 'same' : Math.abs(V.dot(swFrame.x, frame.y)) > 1 - 1e-9 ? 'swapped' : 'turned';
const to = (u, v) => V.toPlane(frame, V.toWorld(swFrame, u, v));

Distance from a point to a segment

64-bit

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.

| |n_face · n| − 1 | < 1e-9 and |(o − o_face) · n_face| < 1e-7 × max(1, |o|)

Both tests use the exact face records in 64-bit. The distance tolerance scales with how far the plane's origin is from the model origin.

  • swimport.js
  • lines 493 to 500
  • PartCraft 0.8.1
/** A named flat face of the part so far lying in the plane (o, n) (same side first). */
function planarFaceAt(o, n) {
  const rg = regen();
  if (!rg.faces) return null;
  const scale = Math.max(1, V.len(o));
  const hits = rg.faces.filter((f) => f.type === 'plane' && f.name && Math.abs(Math.abs(V.dot(f.n, n)) - 1) < 1e-9 && Math.abs(V.dot(V.sub(o, f.o), f.n)) < 1e-7 * scale);
  return hits.find((f) => V.dot(f.n, n) > 0) || hits[0] || null;
}

Base plane and world axis tests

64-bit

Decides whether a plane is one of the three base planes.

| |n̂ · n_base| − 1 | < 1e-9 and |o · n_base| < 1e-9 × max(1, |o|)

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.

  • swimport.js
  • line 540
  • PartCraft 0.8.1
vars.push({ name: nm, expr: formatNumber(tgt.value) });

Equation result against the stored value

64-bit

Warns when an imported equation would give a different number from the value the file holds.

|v − held| > 1e-6 × max(1, |held|)

A relative test with a floor of 1e-6; differences below it are not reported. The stored numbers are kept either way.

  • swimport.js
  • line 559
  • PartCraft 0.8.1
if (Number.isFinite(v) && Math.abs(v - cur) > 1e-6 * Math.max(1, Math.abs(cur))) warn(t('xch.swimp.warn.equation_value', { name: tg.label, v: formatNumber(v), held: formatNumber(cur) }));

Volume check

64-bit

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.

  • swimport.js
  • lines 568 to 569
  • PartCraft 0.8.1
const vol = final.stats?.volume;
if (Number.isFinite(vol)) report.volume = { partcraft: vol, solidworks: j.check?.volume ?? null, same: j.check ? Math.abs(vol - j.check.volume) <= 1e-4 * Math.max(1, j.check.volume) : null };

In detail

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

Every policy that takes this approach