Sheet scale: presets, calibration and drawing units
Sets how many real feet one unit on the sheet stands for: from a named scale, from two picked points and a typed distance, or from a DWG or DXF drawing's own units and viewport.
How the precision is kept
The scale is one 64-bit number per sheet, feet per page unit, and every length and area on the sheet is multiplied by it (or by its square). Preset scales are whole-number ratios. A calibrated scale is as good as the two picks. A drawing's scale comes from its INSUNITS code and, on a layout, from its biggest viewport.
Open item. Give INSUNITS codes 21 to 24 their size in feet (the US survey foot is 1200 ÷ 3937 ÷ 0.3048 feet); the table holds meter values there, so a drawing in US survey feet gets a scale 3.28 times too small.
Calculations under this policy
10 calculations in 4 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.
The scale number for a preset such as 1/4 inch = 1 foot or 1:100.
ftPerUnit = (userUnit ÷ 72) × ratio ÷ 12
ratio is a whole number (real inches per paper inch) and 72 and 12 are exact. The expression is evaluated as (userUnit ÷ 72) × ratio ÷ 12, three operations each rounded once, where ratio ÷ 864 would round once.
units.js
lines 284 to 289
TakeoffCraft 0.8.1
/** Real feet per PDF user-space unit for a preset (a unit is 1/72 in × userUnit on paper). */
export function presetFtPerUnit(key, userUnit = 1) {
const p = PRESETS.get(key);
if (!p) throw new Error('Unknown scale ' + key); // i18n-ignore: a programming error
return (userUnit / 72) * p.ratio / 12;
}
Scale from two points and a typed distance
64-bit
The scale that makes the distance between two picked points equal a typed real distance.
ftPerUnit = realFt ÷ |b − a|
One Math.hypot and one division. Points 0.5 PDF units apart or closer are refused, an absolute limit. The result is stored unrounded; its accuracy is that of the two picks relative to their distance.
units.js
lines 291 to 297
TakeoffCraft 0.8.1
/** Two picked points (user space) and the real distance between them → ftPerUnit. */
export function calibrate(a, b, realFt) {
const d = Math.hypot(b.x - a.x, b.y - a.y);
if (!(d > 0.5)) return { ok: false, error: t('cal.err.near') };
if (!(realFt > 0) || !Number.isFinite(realFt)) return { ok: false, error: t('len.err.positive') };
return { ok: true, ftPerUnit: realFt / d, units: d };
}
Named scale a number matches
64-bit
Finds the preset scale closest to a scale number, if any is close enough.
err = |ftPerUnit − s| ÷ s ≤ 0.005
The relative difference |s − preset| ÷ preset must be at most 0.005; the closest preset wins. 0.5 % is wide enough to join scales that are not the same.
units.js
lines 299 to 308
TakeoffCraft 0.8.1
/** The preset a scale matches within tol (relative), or null. */
export function matchPreset(ftPerUnit, userUnit = 1, tol = 0.005) {
let best = null;
for (const p of PRESETS.values()) {
const s = presetFtPerUnit(p.key, userUnit);
const err = Math.abs(ftPerUnit - s) / s;
if (err <= tol && (!best || err < best.err)) best = { ...p, err };
}
return best;
}
Codes 1 to 10 and 14 to 16 are exact expressions (1 ÷ 12, 0.001 ÷ 0.3048, 5280, 3 and so on). Codes 21, 23 and 24 (US survey foot, yard and mile) hold 1200 ÷ 3937 = 0.3048006…, 3600 ÷ 3937 and 5280 × 1200 ÷ 3937, which are those units in meters; in feet they are those values ÷ 0.3048 (1.000002 for the survey foot). Code 22 (US survey inch) holds 100 ÷ 3937 ÷ 12 = 0.0021167, which is neither its size in meters (0.0254000508) nor in feet (0.0833335). Codes with no entry give no scale.
Reads the INSUNITS code and looks up feet per drawing unit.
The code must be a whole number; the factor must be finite and above zero, otherwise feetPerUnit is null and the sheet has no scale. The factor is used exactly as the table holds it.
cadcount.js
lines 44 to 49
TakeoffCraft 0.8.1
/** The drawing's length unit: `{ code, name, feetPerUnit }`; feetPerUnit is null for "unitless" and units that are not lengths we know. */
export function drawingUnits(model) {
const code = Number(model?.header?.INSUNITS);
const ft = Number.isInteger(code) ? FT[code] : undefined;
return { code: Number.isInteger(code) ? code : 0, name: UNIT_NAMES[code] || '', feetPerUnit: ft && Number.isFinite(ft) && ft > 0 ? ft : null };
}
The matrix that places model space on the paper through a layout's viewport.
k = h ÷ viewHeight; M = [k·c, k·s, −k·s, k·c, cx − k(c·vx − s·vy), cy − k(s·vx + c·vy)]
k = viewport height ÷ view height; the twist's cosine and sine come from Math.cos and Math.sin, so a quarter-turn twist leaves a cosine of about 6e-17 in place of 0. The translation subtracts k times the rotated view center from the viewport center.
cadset.js
lines 59 to 63
TakeoffCraft 0.8.1
/** A viewport's model → paper transform: about the view centre, scaled to the viewport, twisted, moved to its centre (as dwg-view.js). */
export function vpMatrix(vp) {
const k = vp.h / vp.viewHeight, c = Math.cos(vp.twist || 0), sn = Math.sin(vp.twist || 0);
return [k * c, k * sn, -k * sn, k * c, vp.cx - k * (c * vp.vx - sn * vp.vy), vp.cy - k * (sn * vp.vx + c * vp.vy)];
}
Sheet size and the drawing-to-page transform
64-bit
Puts a layout on a page in points, or fits model space to a page about 2000 units on its longest side.
A layout uses 72 points per inch or 72 ÷ 25.4 per millimeter, both exact definitions. A model sheet uses k = 2000 ÷ extent and the offset 24 − k·minX. Page x is k·x plus that offset, so when the drawing sits far from its origin two large numbers are subtracted and the small page coordinate keeps only the digits left after the cancellation.
model: f = ft ÷ k; layout: f = (1 ÷ ptU) ÷ s × ft, s = h ÷ viewHeight
Model sheet: feet per drawing unit ÷ k. Layout: (1 ÷ ptU) ÷ s × feet per drawing unit, with s = viewport height ÷ view height of the viewport with the largest w × h. Three operations, each rounded once. Other viewports are compared with s at a 1 % relative tolerance only to set a flag.
cadset.js
lines 191 to 210
TakeoffCraft 0.8.1
/**
* The scale a sheet starts with, or null (and a reason in `sheet.note`). → { method: 'preset'|'drawing', ftPerUnit, preset?, label }
* `ftPerUnit` is real feet per page unit. A scale the app can name (1/4" = 1'-0", 1:100…) becomes that preset.
*/
function sheetScale(sheet, units) {
const ft = units.feetPerUnit;
if (!ft) { sheet.note = units.code === 0 ? 'nounits' : 'badunits'; return null; }
if (sheet.kind === 'model') {
const f = ft / sheet.k;
return { method: 'drawing', ftPerUnit: f, preset: null, label: t('cad.scale.units', { units: unitLabel(units) }) };
}
if (!sheet.viewports.length) { sheet.note = 'noviewport'; return null; }
// the biggest viewport sets the sheet's scale; a sheet with viewports at other scales says so
const biggest = [...sheet.viewports].sort((a, c) => c.w * c.h - a.w * a.h)[0];
const s = biggest.h / biggest.viewHeight; // paper units per model unit
if (!fin(s) || s <= 0) { sheet.note = 'noviewport'; return null; }
const f = (1 / sheet.ptU) / s * ft; // feet per point
sheet.mixed = sheet.viewports.some((v) => Math.abs(v.h / v.viewHeight / s - 1) > 0.01);
return { method: 'drawing', ftPerUnit: f, preset: null, label: '', viewportScale: s };
}
Gives each sheet of an opened drawing the scale the drawing itself states, when the sheet has none yet.
A model sheet keeps the drawing's number. A layout is passed through matchPreset: when its viewport scale is within 0.5 % of a named scale, the preset's value is stored in place of the drawing's own, so a viewport at 1:99.6 becomes 1:100.
takeoffcraft.js
lines 326 to 344
TakeoffCraft 0.8.1
/**
* TD.2: a drawing's sheets start with the scale the drawing itself gives (INSUNITS, a layout's viewport scale), for the sheets that have
* none yet — one undo step. A sheet with no usable units stays "No scale"; Calibrate and the scale menu override at any time.
*/
function applyDrawingScales(set) {
const map = {};
for (const sh of set.cad.sheets) {
const key = sheetKey(set.hash, sh.index);
if (!sh.scale || model.scaleOf(key)) continue;
const sc = sh.scale;
if (sh.kind === 'model') map[key] = U.scaleRecord({ method: 'drawing', ftPerUnit: sc.ftPerUnit, label: t('cad.scale.units', { units: cadMod.unitLabel(set.cad.units) }) });
else {
const pm = U.matchPreset(sc.ftPerUnit);
map[key] = pm ? U.scaleRecord({ method: 'preset', preset: pm.key, ftPerUnit: U.presetFtPerUnit(pm.key), label: pm.label })
: U.scaleRecord({ method: 'drawing', ftPerUnit: sc.ftPerUnit, label: t('cad.scale.viewport', { scale: U.describeScale(sc.ftPerUnit) }) });
}
}
if (Object.keys(map).length) model.setScales(map, t('cad.scale.undo', { file: set.name }));
}
Calibration stored on the sheet
64-bit
Reads the typed distance, computes the scale and stores it with the two points and the text.
The scale is stored as computed (ftPerUnit), with the two picked points, the parsed feet and the text as typed. The label beside it is made by describeScale, which rounds for display; the stored ftPerUnit is not rounded.
Preset: (userUnit ÷ 72) × ratio ÷ 12. Calibration: typed feet ÷ picked distance. Drawing: feet per drawing unit from a table by INSUNITS; a model sheet divides it by the sheet's fit factor, a layout uses paper units per model unit of its biggest viewport.
Number types
64-bit numbers.
Tolerances
Calibration refuses two points 0.5 PDF units apart or less (absolute). A scale matches a named preset within 0.5 % (relative). A layout is flagged as mixed when another viewport differs from the biggest by more than 1 %. A model sheet's extent is floored at 1e-9 drawing units.
Limits
The INSUNITS table's entries for US survey units (codes 21 to 24) hold meter values, not feet. A layout whose viewport scale is within 0.5 % of a preset is given the preset's value in place of its own. A model sheet maps x to k·x + (24 − k·minX): for a drawing far from its origin the two terms nearly cancel, so page coordinates carry the rounding of the large product.
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.