Resources Numerical accuracy Calculation policy

Values changed when a DWG is written

The few places where the DWG writer changes a number on its way into the file: lineweights, ellipse axes, ray directions, the drawing extents and the defaults of a light.

How the precision is kept

Coordinates, radii and angles are passed to the encoder as the 64-bit numbers the model holds. A lineweight is replaced by the nearest of the 24 values the format can store. The extents written to the header are a box of defining points, not of the drawn shapes. One default angle of a light is a constant written to 10 digits.

Open item. Write the default falloff angle of a light as π ÷ 4 + 5π ÷ 180 computed in 64-bit, instead of adding the 10-digit constant 0.0872664626.

Calculations under this policy

5 calculations in 2 source files. 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.

dwg/write.js CadCraft 0.8.2 64-bit 4 calculations Reviewed

Tests tools/dwg-test/writer-check.mjs, tools/dwg-test/writer-roundtrip.mjs

Lineweight to the file's lineweight index

Exact

Picks the stored lineweight that is nearest to the one the layer or object has.

index = the i with the smallest |table[i] − lineweight|

The table holds 24 whole numbers of hundredths of a millimeter. The comparison is strict, so a value exactly halfway between two entries takes the smaller one. A lineweight that is not in the table is changed to the nearest entry in the file.

  • dwg/write.js
  • lines 40 to 47
  • CadCraft 0.8.2
export function lwIndex(lw) {
  if (lw === undefined || lw === null || lw === -1) return 29;
  if (lw === -2) return 30;
  if (lw === -3 || lw < 0) return 31;
  let best = 0;
  for (let i = 1; i < LW_TABLE.length; i++) if (Math.abs(LW_TABLE[i] - lw) < Math.abs(LW_TABLE[best] - lw)) best = i;
  return best;
}

Ellipse with a ratio above 1

64-bit

Rewrites an ellipse whose minor axis is longer than its major axis so the file holds a ratio of 1 or less.

major′ = (−m_y, m_x) × ratio; ratio′ = 1 ÷ ratio; start and end − π ÷ 2

The new axis is the old one turned a quarter turn and multiplied by the ratio, and the ratio is replaced by its reciprocal. Both parameters lose π ÷ 2, a rounded 64-bit constant. An ellipse with a ratio of 1 or less is written unchanged.

  • dwg/write.js
  • lines 385 to 390
  • CadCraft 0.8.2
case 'ELLIPSE': {
  if (!okP(e.center) || !okP(e.majorAxis) || !(e.ratio > 0)) return null;
  let { majorAxis: maj, ratio, startParam: s = 0, endParam: en = 2 * Math.PI } = e;
  if (ratio > 1) { maj = { x: -maj.y * ratio, y: maj.x * ratio, z: num(maj.z) * ratio }; ratio = 1 / ratio; s -= Math.PI / 2; en -= Math.PI / 2; }
  return [{ ...c('ELLIPSE'), center: P3(e.center), majorAxis: P3(maj), ratio, startParam: s, endParam: en, extrusion: e.extrusion }];
}

Ray and construction line direction

64-bit

Writes the direction of a ray or construction line as a unit vector.

d′ = d ÷ |d|

The length is one Math.hypot call and each component is divided by it. A direction of zero length leaves the object out of the file.

  • dwg/write.js
  • lines 395 to 400
  • CadCraft 0.8.2
case 'RAY': case 'XLINE': {
  if (!okP(e.start) || !okP(e.direction)) return null;
  const d = P3(e.direction), n = Math.hypot(d.x, d.y, d.z);
  if (!(n > 0)) return null;
  return [{ ...c(type), start: P3(e.start), direction: { x: d.x / n, y: d.y / n, z: d.z / n } }];   // a unit vector, as DXF 11 is
}

Drawing extents

64-bit

The lower left and upper right corner of model space written to the file header.

min and max of x, y, z over the defining points

Minimum and maximum only, so nothing is rounded. The box is not tight: an arc counts as its whole circle, an ellipse as a circle of its major radius in x and y, text and block references as their insertion point, and a hatch by its polyline loops only. When the model's header already has valid extents they are written as they are.

  • dwg/write.js
  • lines 642 to 662
  • CadCraft 0.8.2
let minX = Infinity, minY = Infinity, minZ = Infinity, maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
const g = (p, r = 0) => { if (!p || !fin(p.x, p.y)) return; const z = num(p.z); minX = Math.min(minX, p.x - r); maxX = Math.max(maxX, p.x + r); minY = Math.min(minY, p.y - r); maxY = Math.max(maxY, p.y + r); minZ = Math.min(minZ, z); maxZ = Math.max(maxZ, z); };
const he = hdrIn.EXTMIN, hx = hdrIn.EXTMAX;
if (he && hx && fin(he.x, he.y, hx.x, hx.y) && he.x <= hx.x && he.y <= hx.y && Math.abs(he.x) < 1e19) { g(he); g(hx); }
else for (const list of MS.ents) {
  const r = list[0];
  switch (r.t) {
    case 'LINE': g(r.start); g(r.end); break;
    case 'CIRCLE': case 'ARC': g(r.center, r.radius); break;
    case 'ELLIPSE': g(r.center, Math.hypot(r.majorAxis.x, r.majorAxis.y)); break;
    case 'LWPOLYLINE': for (const p of r.vertices) g({ x: p.x, y: p.y, z: r.elevation }); break;
    case 'POLYLINE_2D': case 'POLYLINE_3D': case 'POLYLINE_PFACE': case 'POLYLINE_MESH': for (const v of list.slice(1)) if (v.point) g(v.point); break;
    case 'POINT': g(r.position); break;
    case 'TEXT': case 'MTEXT': case 'INSERT': case 'MINSERT': g(r.insert); break;
    case 'SOLID': case 'TRACE': case '3DFACE': for (const p of r.points) g(p); break;
    case 'HATCH': for (const l of r.loops) { if (l.polyline) for (const p of l.polyline.vertices) g(p); } break;
    case 'SPLINE': for (const p of r.control.length ? r.control : r.fit) g(p); break;
    default: if (r.defPoint) g(r.defPoint);
  }
}
if (Number.isFinite(minX)) ext = { min: { x: minX, y: minY, z: minZ }, max: { x: maxX, y: maxY, z: maxZ } };
dwg/solid-write.js CadCraft 0.8.2 64-bit 1 calculation Reviewed

Tests None found by the review

Default angles of a light

64-bit

The hotspot and falloff angles written for a light that has none of its own.

hotspot = π ÷ 4; falloff = π ÷ 4 + 0.0872664626

The falloff default adds 0.0872664626, which is 5 degrees in radians written to 10 digits; the 64-bit value of 5π ÷ 180 is 0.08726646259971647. The other fields of a light and of a solid are written as the model holds them.

  • dwg/solid-write.js
  • lines 117 to 128
  • CadCraft 0.8.2
function encodeLight(rec, ctx) {
  const w = new ObjWriter(ctx.version);
  begin(w, ctx.classNum('LIGHT'), rec);
  entityCommon(w, rec);
  w.BL(rec.lightVersion ?? 1); w.TV(rec.name ?? ''); w.BS(rec.lightType ?? 2); w.B(rec.status ? 1 : 0);
  w.CMC(rec.lightColor || { index: 7 }); w.B(rec.plotGlyph ? 1 : 0); w.BD(num(rec.intensity, 1));
  w.P3(rec.position); w.P3(rec.target);
  w.BS(rec.attenuationType | 0); w.B(rec.useAttenuationLimits ? 1 : 0); w.BD(num(rec.attenuationStart)); w.BD(num(rec.attenuationEnd, 10));
  w.BD(num(rec.hotspot, Math.PI / 4)); w.BD(num(rec.falloff, Math.PI / 4 + 0.0872664626)); w.B(rec.castShadows ? 1 : 0);
  w.BS(rec.shadowType | 0); w.BS(rec.shadowMapSize ?? 256); w.RC(rec.shadowMapSoftness ?? 1);
  return finish(w);
}

In detail

Method
Nearest table entry for lineweights; an axis swap for an ellipse whose ratio is above 1; a unit vector for ray and construction line directions; minimum and maximum of defining points for the extents.
Number types
64-bit numbers. Lineweights are whole hundredths of a millimeter.
Tolerances
The header's own extents are used only when they are finite, ordered and below 1e19 in size.
Limits
A lineweight that is not in the format's table is written as the nearest one. The computed extents take an arc as its whole circle, an ellipse as a circle of its major radius, and text and block references as their insertion point only.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach