Resources Numerical accuracy Calculation policy

STL and 3MF files for the printer

Writes the checked mesh as a binary STL or a 3MF file.

How the precision is kept

The mesh is 64-bit in millimeters. Binary STL stores every normal and corner as a 32-bit number, as the format defines. 3MF is text and each coordinate is written with 10 significant digits.

Open item. Write 3MF coordinates with 17 significant digits instead of 10, and say in the export that binary STL holds 32-bit numbers by its format.

Calculations under this policy

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

print3d.js ModelCraft 0.8.1 32-bit in part 3 calculations Reviewed

Tests bin/test-modelcraft.mjs, tools/modelcraft-test/print3d.mjs

Binary STL

32-bit in part

Writes each triangle's unit normal and three corners.

normal = n ÷ |n| from the winding; every number written with setFloat32

Normals and corners are converted to 32-bit as they are written, at their absolute coordinates. A 32-bit number between 128 and 256 has a spacing of about 1.5 × 10⁻⁵, so a coordinate of that size in millimeters is written to the nearest 15 nm or so. A triangle without area gets the normal [0, 0, 0] (its length is replaced by 1).

  • print3d.js
  • lines 337 to 352
  • ModelCraft 0.8.1
export function stlBinary(mesh, header = 'binary STL, millimeters') {
  const P = mesh.positions, I = mesh.indices, nt = I.length / 3;
  const out = new Uint8Array(84 + nt * 50), dv = new DataView(out.buffer);
  const h = String(header).slice(0, 79);
  for (let i = 0; i < h.length; i++) out[i] = h.charCodeAt(i) & 0x7f;
  dv.setUint32(80, nt, true);
  let o = 84;
  for (let t = 0; t < nt; t++) {
    const a = I[t * 3], b = I[t * 3 + 1], c = I[t * 3 + 2];
    const n = triNormal(P, a, b, c), l = Math.hypot(n[0], n[1], n[2]) || 1;
    for (const v of [n[0] / l, n[1] / l, n[2] / l]) { dv.setFloat32(o, v, true); o += 4; }
    for (const v of [a, b, c]) for (let k = 0; k < 3; k++) { dv.setFloat32(o, P[v * 3 + k], true); o += 4; }
    o += 2;
  }
  return out;
}

A coordinate as 3MF text

64-bit

Formats one coordinate for the 3MF file.

0 if |v| < 10⁻¹²; else v rounded to 10 significant digits

The exported value is rounded with toPrecision(10). A 64-bit number needs up to 17 significant digits to be read back unchanged, so the file does not hold the mesh's coordinates exactly.

  • print3d.js
  • line 394
  • ModelCraft 0.8.1
const num = (v) => { const r = Math.abs(v) < 1e-12 ? 0 : v; return Number(r.toPrecision(10)).toString(); };

3MF vertex list

64-bit

Writes every corner of the mesh as a vertex element.

Each of x, y and z passes through num, so every vertex is written with 10 significant digits. The unit attribute of the model element is millimeter.

  • print3d.js
  • line 410
  • ModelCraft 0.8.1
for (let i = 0; i < nv; i++) out.push(`     <vertex x="${num(P[i * 3])}" y="${num(P[i * 3 + 1])}" z="${num(P[i * 3 + 2])}"/>\n`);

In detail

Method
Binary STL records of 50 bytes per triangle; 3MF core XML in a stored zip.
Number types
Float64Array in; 32-bit floats in STL; decimal text with 10 significant digits in 3MF.
Tolerances
In 3MF a coordinate with |v| < 1e-12 is written as 0.
Limits
STL coordinates carry about 7 significant digits. 3MF coordinates carry 10.

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