Resources Numerical accuracy Calculation policy

IGES files of NURBS curves and surfaces

Writes B-spline curves and surfaces to an IGES file in millimeters and reads them back, converting the file's unit.

How the precision is kept

Numbers are written with JavaScript's shortest text that reads back to the same 64-bit value, with D as the exponent letter, and parsed back with the same rule. Nothing is rounded on the way out. On reading, control points are multiplied by a factor chosen from the file's unit flag; knots and weights are not scaled.

Open item. Check the unit table against the IGES 5.3 unit flags (4 feet, 5 miles, 8 mils at 0.0254 mm; 3 means the unit is named in the next parameter): the entries at 3, 4, 5 and 8 do not match that order.

Calculations under this policy

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

nurbs/iges.js ModelCraft 0.8.1 64-bit 5 calculations Reviewed

Tests tools/nurbs/test.mjs

A number as IGES text

64-bit

Writes one real number into the file.

whole numbers below 10¹⁵: digits and a point; otherwise String(x) with E → D

String(x) gives the shortest decimal that reads back to the same 64-bit number, up to 17 significant digits, so the file loses nothing. A value that is not finite stops the export.

  • nurbs/iges.js
  • lines 22 to 26
  • ModelCraft 0.8.1
const num = (x) => {
  if (!Number.isFinite(x)) fail('nurbs.err.iges_number');
  if (Number.isInteger(x) && Math.abs(x) < 1e15) return x + '.';
  return String(x).toUpperCase().replace('E', 'D');
};

Global section

64-bit

States the file's number sizes, unit, resolution and largest coordinate.

unit flag 2 (MM); scale 1; resolution 10⁻⁶; largest coordinate = max |x| over all control points

The section declares 15 significant digits for double precision, while num() writes as many digits as the value needs, up to 17. The largest coordinate starts at 1 and is the maximum over control points only.

  • nurbs/iges.js
  • lines 53 to 58
  • ModelCraft 0.8.1
let maxc = 1;
for (const it of d.items) for (const q of it.kind === 'curve' ? it.curve.cvs : it.surface.cvs.flat()) for (const x of q) maxc = Math.max(maxc, Math.abs(x));
const global = [
  '1H,', '1H;', holl('CadCraft'), holl(name + '.igs'), holl('CadCraft cc-nurbs'), holl('cc-nurbs 0.8'), '32', '38', '6', '308', '15', holl(name),
  '1.', '2', '2HMM', '1', '1.', holl(when), '1.D-6', num(maxc), holl(author), holl(org), '11', '0', holl(when),
];

A number from IGES text

64-bit

Reads one real number from the file.

The first D or d is replaced by E and the text is converted by the language's own parser, which rounds correctly to 64-bit. An empty field reads as 0; text that is not a finite number stops the import.

  • nurbs/iges.js
  • lines 113 to 118
  • ModelCraft 0.8.1
const fnum = (s) => {
  if (typeof s !== 'string' || s === '') return 0;
  const v = +s.replace(/[Dd]/, 'E');
  if (!Number.isFinite(v)) fail('nurbs.err.iges_number');
  return v;
};

Unit factors

64-bit

Millimeters per file unit, by the file's unit flag.

1: 25.4; 2: 1; 3: 25.4 × 12; 4: 1609344; 5: 10⁶; 6: 1000; 7: 10⁶; 8: 10⁻³; 9: 10⁻³; 10: 10; 11: 25.4 × 10⁻⁶

25.4, 304.8 and 1609344 are exact; 1e-3 and 25.4e-6 are rounded binary values. The table gives feet's factor (304.8) at flag 3, the mile's (1609344) at flag 4, the kilometer's (1e6) at both 5 and 7, and the same 0.001 at 8 and 9. A flag that is not in the table reads as millimeters.

  • nurbs/iges.js
  • line 120
  • ModelCraft 0.8.1
const UNITS = { 1: 25.4, 2: 1, 3: 25.4 * 12, 4: 1609344, 5: 1e6, 6: 1000, 7: 1e6, 8: 1e-3, 9: 1e-3, 10: 10, 11: 25.4e-6 };

Control points in millimeters

64-bit

Scales a curve's control points by the file's unit as it is read.

scale = UNITS[flag] (1 when unknown); p′ = p × scale

The flag is the rounded value of Global parameter 14, taken as 2 when the field is not text. The second factor in the scale expression is 1 in both branches, so the file's model space scale is not used. Each coordinate is multiplied once; knots and weights are left as read.

  • nurbs/iges.js
  • lines 135 to 152
  • ModelCraft 0.8.1
const unitFlag = Math.round(fnum(typeof G[13] === 'string' ? G[13] : '2'));
const scale = (UNITS[unitFlag] ?? 1) * (typeof G[12] === 'string' && G[12] ? 1 : 1);
let doc = D.newDoc();
for (let k = 0; k + 1 < sec.D.length; k += 2) {
  const a = sec.D[k], b = sec.D[k + 1];
  const type = +a.slice(0, 8), ptr = +a.slice(8, 16), cnt = +b.slice(24, 32), label = b.slice(56, 64).trim();
  if (type !== 126 && type !== 128) continue;
  if (!(ptr >= 1) || !(cnt >= 1) || ptr - 1 + cnt > sec.P.length) fail('nurbs.err.iges_pointer');
  const body = sec.P.slice(ptr - 1, ptr - 1 + cnt).map((l) => l.slice(0, 64)).join('');
  const P = freeParams(body, pd, rd).map((x) => (typeof x === 'string' ? x : '0'));
  if (+P[0] !== type) fail('nurbs.err.iges_entity');
  let at = 1;
  const take = (n) => { if (n < 0 || at + n > P.length || n > 5e6) fail('nurbs.err.iges_entity'); const o = P.slice(at, at + n).map(fnum); at += n; return o; };
  if (type === 126) {
    const [K, M, , , poly] = take(6);
    if (!Number.isInteger(K) || !Number.isInteger(M) || K < 1 || M < 1) fail('nurbs.err.iges_entity');
    const knots = take(K + M + 2), w = take(K + 1), xyz = take(3 * (K + 1));
    const cvs = []; for (let i = 0; i <= K; i++) cvs.push([xyz[3 * i] * scale, xyz[3 * i + 1] * scale, xyz[3 * i + 2] * scale]);

In detail

Method
Entities 126 and 128 written from the IGES 5.3 specification, which the file's header cites. Unit factor from a table keyed by the Global section's unit flag.
Number types
64-bit numbers; text in the file.
Tolerances
The Global section declares a minimum resolution of 1.D-6 and the largest coordinate in the file.
Limits
The Global section's model space scale (parameter 13) is not applied when reading. Only entities 126 and 128 are read. The unit table holds 304.8 at flag 3, 1609344 at flag 4, 1e6 at flag 5 and 0.001 at flag 8.

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.

Every policy that takes this approach