Resources Numerical accuracy Calculation policy

Paper sizes, drawing scales and viewport aim

Turns a scale 1:n and the drawing's units into a viewport scale, picks the standard scale at which a view fits, and sizes the space a new sheet leaves for views.

How the precision is kept

All values are 64-bit numbers. Unit factors are millimeters per unit, written as their defined values (25.4, 304.8, 914.4, 1609344). Sheet borders and margins are designed in millimeters and converted with the reciprocal 1 ÷ (millimeters per unit), which is a rounded number for inch paper. Standard scales are matched with relative tolerances; the viewport scale that is stored is never rounded.

Calculations under this policy

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

sheets.js CadCraft 0.8.2 64-bit 10 calculations Reviewed

Tests bin/test-autosheet.mjs, bin/test-cadcraft.mjs

Viewport scale from 1:n

64-bit

Paper units per model unit for a drawing scale 1:n.

s = mm per model unit ÷ mm per paper unit ÷ n

Two 64-bit divisions, nothing rounded. The unit table holds millimeters per unit as the defined values (inch 25.4, foot 304.8, yard 914.4, mile 1609344); a drawing with no known unit uses the paper's unit.

  • sheets.js
  • lines 49 to 50
  • CadCraft 0.8.2
/** Paper units per model unit for a scale 1:n. */
export const sOf = (doc, paperUnit, n) => modelMm(doc, paperUnit) / UNIT_MM[paperUnit] / n;

Scale 1:n from a viewport scale

64-bit

The n of 1:n that a viewport's stored scale stands for.

n = mm per model unit ÷ mm per paper unit ÷ s

The same two 64-bit divisions with s in place of n. No guard for s = 0 in this function.

  • sheets.js
  • lines 51 to 52
  • CadCraft 0.8.2
/** The n of 1:n for a viewport scale s. */
export const nOf = (doc, paperUnit, s) => modelMm(doc, paperUnit) / UNIT_MM[paperUnit] / s;

List of standard and custom scales

64-bit

The scales offered for a drawing: the standard ones and the drawing's own that the standard list lacks.

custom n = drawing ÷ paper; kept when |n − listed n| ≥ 1e-6 × n for every listed scale

A custom scale's n is one 64-bit division of the two numbers in the file's scale list. It is dropped as a duplicate when within 1e-6 × n of a listed scale, a relative tolerance. Enlargements are held as n = 1/10, 1/5 and 1/2.

  • sheets.js
  • lines 63 to 73
  • CadCraft 0.8.2
/** Standard scales for this drawing: architectural and engineering ones for imperial drawings. */
export function scaleList(doc) {
  const metric = METRIC.map((n) => ({ n, label: ratioLabel(n) }));
  const std = isImperial(doc) ? [...ARCH.map(([label, n]) => ({ n, label })).sort((a, b) => a.n - b.n), ...metric] : metric;
  // the drawing's own scales (its file's list, SCALELISTEDIT: scales.js) that the standard list lacks, after it
  const own = (Array.isArray(doc.meta.scales) ? doc.meta.scales : []).map((x) => ({ n: x.drawing / x.paper, label: x.name, custom: true }))
    .filter((x) => x.n > 0 && Number.isFinite(x.n)).sort((a, b) => a.n - b.n);
  const extra = [];
  for (const x of own) if (![...std, ...extra].some((y) => Math.abs(y.n - x.n) < 1e-6 * x.n)) extra.push(x);
  return extra.length ? [...std, ...extra] : std;
}

Name of a viewport scale

64-bit

The text a viewport scale reads as: a standard name when it is one, else 1:n.

A listed scale is recognized when its n is within 1e-6 × n of the viewport's, a relative tolerance. The result is text only; the viewport's stored scale is not changed.

  • sheets.js
  • lines 75 to 80
  • CadCraft 0.8.2
/** How a viewport scale reads: a standard name when it is one, else 1:n. */
export function scaleLabel(doc, paperUnit, s) {
  const n = nOf(doc, paperUnit, s);
  const std = scaleList(doc).find((x) => Math.abs(x.n - n) < 1e-6 * n);
  return std ? std.label : ratioLabel(n);
}

Ratio text for a scale that is not listed

64-bit

Writes 1:n or n:1 for a scale with no standard name.

n ≥ 1: "1:n"; n < 1: "(1 ÷ n):1"

toPrecision(6) keeps 6 significant digits of n in the text. This text is what the title block's SCALE field and a viewport caption show for a scale that is not in the list.

  • sheets.js
  • lines 60 to 61
  • CadCraft 0.8.2
const ratioLabel = (n) => (n >= 1 ? '1:' + fmtN(n) : fmtN(1 / n) + ':1');
const fmtN = (n) => Number(n.toPrecision(6)).toString();

Largest standard scale that fits

64-bit

Picks the largest listed scale at which the window still fits the viewport.

largest s with s ≤ sFit × (1 + 1e-9)

Each listed scale is converted to s and compared with the fitting scale with a relative allowance of 1e-9. When no listed scale fits, the fitting scale itself is returned unrounded.

  • sheets.js
  • lines 82 to 90
  • CadCraft 0.8.2
/** The largest standard scale whose s is at most sFit (so the drawing still fits). */
export function bestScale(doc, paperUnit, sFit) {
  let best = null;
  for (const x of scaleList(doc)) {
    const s = sOf(doc, paperUnit, x.n);
    if (s <= sFit * (1 + 1e-9) && (!best || s > best.s)) best = { ...x, s };
  }
  return best || { n: nOf(doc, paperUnit, sFit), label: ratioLabel(nOf(doc, paperUnit, sFit)), s: sFit };
}

Viewport center and scale for a model window

64-bit

The model point a viewport looks at and the scale at which a model window fits it.

sFit = min(w ÷ Δx, h ÷ Δy); center = ((minX + maxX) ÷ 2, (minY + maxY) ÷ 2)

The window's width and height are floored at 1e-9 drawing units before the divisions, an absolute floor that guards a zero-size window. The center is the mean of the window's limits.

  • sheets.js
  • lines 103 to 109
  • CadCraft 0.8.2
/** Aim a viewport of w × h paper units at a model window (bounds): centre and a standard scale. */
export function aimViewport(doc, paperUnit, w, h, win, { exact = false } = {}) {
  if (!validBounds(win)) return { cx: 0, cy: 0, s: sOf(doc, paperUnit, 1) };
  const bw = Math.max(win.maxX - win.minX, 1e-9), bh = Math.max(win.maxY - win.minY, 1e-9);
  const sFit = Math.min(w / bw, h / bh);
  return { cx: (win.minX + win.maxX) / 2, cy: (win.minY + win.maxY) / 2, s: exact ? sFit : bestScale(doc, paperUnit, sFit).s };
}

Standard paper for a sheet size

64-bit

Finds the standard paper a sheet of w × h is, either way round.

|standard side − side| ≤ 0.005 × side, both sides in mm

Both sides are converted to millimeters by one multiplication and compared with each standard size within 0.5 % of the side, a relative tolerance.

  • sheets.js
  • lines 32 to 37
  • CadCraft 0.8.2
/** The standard paper a sheet of w × h (in its unit) is, either way round, within 0.5 %; else null. */
export function matchPaper(w, h, unit) {
  const k = UNIT_MM[unit] || 1;
  const [a, b] = [Math.max(w, h) * k, Math.min(w, h) * k];
  return PAPERS.find((p) => { const pk = UNIT_MM[p.unit]; return Math.abs(p.w * pk - a) <= a * 0.005 && Math.abs(p.h * pk - b) <= b * 0.005; }) || null;
}

Space for viewports on a sheet

64-bit

The rectangle inside the border and above the title block, in paper units.

x0 = sheet x0 + 15 mm × k; y0 = sheet y0 + 47 mm × k; x1 = sheet x0 + (W − 15 mm) × k; y1 = sheet y0 + (H − 15 mm) × k; k = 1 ÷ mm per paper unit

The sheet's width and height are converted to millimeters by dividing by k = 1 ÷ (mm per unit), and the corners are converted back by multiplying by k. For inch paper k = 1 ÷ 25.4 is a rounded number, so each far corner goes through a division and a multiplication by a rounded factor.

  • sheets.js
  • lines 122 to 126
  • CadCraft 0.8.2
/** The space a sheet leaves for viewports (inside the border, above the title block), in paper units: { x0, y0, x1, y1 }. Matches sheetContents. */
export function viewZone(paper) {
  const k = 1 / UNIT_MM[paper.unit], W = paper.w / k, H = paper.h / k, m = 10, th = 32;
  return { x0: paper.x0 + (m + 5) * k, y0: paper.y0 + (m + th + 5) * k, x1: paper.x0 + (W - m - 5) * k, y1: paper.y0 + (H - m - 5) * k };
}

Viewport of a new sheet

64-bit

The size, paper position, model center and scale of the viewport a new sheet starts with.

The viewport's size and paper center use the same millimeter margins and factor k as the border. Its model center and scale come from aimViewport over the extents of the visible model, and are stored unrounded.

  • sheets.js
  • lines 154 to 158
  • CadCraft 0.8.2
const vx0 = m + 5, vx1 = W - m - 5, vy0 = m + th + 5, vy1 = H - m - 5;
const vw = (vx1 - vx0) * k, vh = (vy1 - vy0) * k;
const aim = aimViewport(doc, paper.unit, vw, vh, modelExtents(doc));
const vc = at((vx0 + vx1) / 2, (vy0 + vy1) / 2);
out.push({ type: 'viewport', layer: 'Viewports', color: null, x: vc.x, y: vc.y, w: vw, h: vh, cx: aim.cx, cy: aim.cy, s: aim.s });

In detail

Method
s = (model millimeters per unit) ÷ (paper millimeters per unit) ÷ n for a scale 1:n. The chosen scale is the largest listed scale whose s does not exceed the scale at which the window just fits.
Number types
64-bit numbers.
Tolerances
Listed scale match: 1e-6 × n (relative). Scale fits: s ≤ sFit × (1 + 1e-9) (relative). Standard paper match: 0.5 % of each side (relative). Model window sides are floored at 1e-9 drawing units (absolute).
Limits
The text of a scale that is not in the list is written with 6 significant digits. A drawing without a known unit is taken to be in the paper's unit.

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