Resources Numerical accuracy Calculation policy

Window and door pieces, glass and hardware

Turns an opening, a layout of panes and a profile system into the profile pieces to cut with their angles, the glass sizes and the hardware and gasket quantities.

How the precision is kept

All sizes are millimeters in 64-bit numbers, built from short sums and differences of the opening and the profile system's face widths, overlaps and gaps. Stored piece lengths, cell positions and glass sizes are rounded to 0.001 mm. Hardware quantities are rounded to 0.001 after every addition.

Calculations under this policy

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

addons/windows/lib/model.js CadCraft 0.8.2 64-bit 7 calculations Reviewed

Tests None found by the review

Column widths and row heights

64-bit

Shares the inner opening between columns or rows: typed sizes keep theirs, the others split what is left equally.

each free size = (avail − Σ typed) ÷ free count

If the typed sizes exceed the space by more than 1e-9 mm, or leave less than 1 mm for the free ones, every size becomes an equal share and the result is flagged. If all sizes are typed and miss the space by more than 1e-6 mm they are scaled by avail ÷ Σ typed.

  • addons/windows/lib/model.js
  • lines 70 to 78
  • CadCraft 0.8.2
/** Share `avail` between sizes: given ones keep theirs, the others split what is left. null sizes → equal. Returns { sizes, ok }. */
function split(given, avail) {
  const fixed = given.reduce((a, g) => a + (g || 0), 0), free = given.filter((g) => !g).length;
  if (fixed > avail + 1e-9 || (free && avail - fixed < 1)) { const e = avail / given.length; return { sizes: given.map(() => e), ok: false }; }
  const each = free ? (avail - fixed) / free : 0;
  const sizes = given.map((g) => g || each);
  if (!free && Math.abs(fixed - avail) > 1e-6) { const k = avail / fixed; return { sizes: sizes.map((s) => s * k), ok: false }; }
  return { sizes, ok: true };
}

Inner opening

64-bit

The opening inside the frame, and the space left for the columns after the mullions.

inner w = W − 2f; inner h = H − f − tf; columns share inner w − (n − 1) × m

Subtractions of the frame and mullion face widths from the opening; for a door the bottom is the threshold's face instead of the frame's.

  • addons/windows/lib/model.js
  • lines 86 to 89
  • CadCraft 0.8.2
const inner = { w: spec.w - 2 * f, h: spec.h - f - tf };
const cols = parseLayout(spec.layout);
const nc = cols.length;
const cw = split(cols.map((c) => c.w), inner.w - (nc - 1) * m);

Cut length of a profile piece

64-bit

The length a piece is sawn to, with the welding allowance, and the merging of equal pieces into one row.

cut = outer + (mitred ends) × weld ÷ 2

The outer length is rounded to 0.001 mm first, then the allowance is added and the sum rounded again. Pieces are merged when role, rounded cut length, angles and note are equal. Clipped beads get no allowance.

  • addons/windows/lib/model.js
  • lines 97 to 108
  • CadCraft 0.8.2
const addPiece = (role, profile, name, length, cuts, extra = {}) => {
  const outer = r1(length);
  const welds = sys.weld && !extra.clip ? cuts.filter((a) => Math.abs(a - 90) > 1e-9).length : 0;
  const cut = r1(outer + welds * sys.weld / 2);
  const key = role + '|' + cut + '|' + cuts.join(',') + '|' + (extra.note || '');
  const n = extra.n || 1;
  const hit = pieces.find((p) => p._key === key);
  if (hit) { hit.qty += n; return hit; }
  const p = { _key: key, id: spec.no + '-' + String(++seq).padStart(2, '0'), role, name, profile: profile.code, group: extra.group || groupOf(profile.code), length: cut, outer, cuts, depth: profile.w, thick: profile.depth, qty: n, ...(extra.note ? { note: extra.note } : {}) };
  pieces.push(p);
  return p;
};

Steel reinforcement length

64-bit

The length of the steel inside a PVC profile.

steel = max(10, length − 2 × setback)

Frame and sash profiles get steel only when longer than the system's limit; mullions and transoms always. The section is written as 28 × 22 mm in the code for every system.

  • addons/windows/lib/model.js
  • lines 109 to 114
  • CadCraft 0.8.2
const steelFor = (role, len, always = false) => {
  if (!sys.steel || sys.material !== 'pvc') return;
  if (!always && len <= sys.steel.over) return;
  const p = { code: sys.steel.code, w: 28, depth: 22 };
  addPiece('steel', p, 'part.steel', Math.max(10, len - 2 * sys.steel.setback), [90, 90], { group: sys.steel.code });
};

Sash and glass sizes

64-bit

The size of an opening sash and of the glass in a sash or in the frame.

sash = cell + 2 (overlap − gap); glass in a sash = sash − 2 × face + 2 (seat − glass gap); glass in the frame = cell + 2 (frame seat − glass gap)

Computed from the unrounded cell size; the sash and glass sizes are rounded to 0.001 mm only where they are stored. The heavy-sash warning compares the sash width with 1400 mm and its area with 2.2e6 mm².

  • addons/windows/lib/model.js
  • lines 145 to 161
  • CadCraft 0.8.2
if (OPENS.has(cell.op)) {
  const grow = 2 * (sys.sashOverlap - sys.sashGap);
  const sw = colW + grow, sh = ch + grow;
  c.sash = { w: r1(sw), h: r1(sh), x: r1(x - grow / 2), y: r1(y - grow / 2) };
  addPiece('sash-top', P.sash, 'part.sash-top', sw, [45, 45]);
  addPiece('sash-bottom', P.sash, 'part.sash-bottom', sw, [45, 45]);
  addPiece('sash-side', P.sash, 'part.sash-side', sh, [45, 45], { n: 2 });
  for (const [role, len, n] of [['sash-top', sw, 1], ['sash-bottom', sw, 1], ['sash-side', sh, 2]]) for (let i = 0; i < n; i++) steelFor(role, len);
  gw = (sw - 2 * P.sash.w) + 2 * (P.sash.seat - sys.glassGap);
  gh = (sh - 2 * P.sash.w) + 2 * (P.sash.seat - sys.glassGap);
  if (sw > 1400 || sw * sh > 2.2e6) problems.push({ code: 'sash-heavy', vars: { n: ci + 1 + '.' + (ri + 1) } });
  if ((cell.op === 'TL' || cell.op === 'TR' || cell.op === 'T') && sw > 1400) problems.push({ code: 'tilt-wide', vars: { n: ci + 1 + '.' + (ri + 1) } });
} else {
  gw = colW + 2 * (P.frame.seat - sys.glassGap);
  gh = ch + 2 * (P.frame.seat - sys.glassGap);
}
c.glass = { w: r1(gw), h: r1(gh) };

Glass rows and area

64-bit

One row per glass size with its quantity and the area of one pane.

area = round(gw × gh) ÷ 10⁶ m²

The area is rounded to a whole square millimeter before it is divided into square meters, from the unrounded width and height. Panes are merged by their sizes rounded to 0.001 mm; the row's area stays that of one pane.

  • addons/windows/lib/model.js
  • lines 166 to 169
  • CadCraft 0.8.2
const gk = r1(gw) + 'x' + r1(gh);
const g = glass.find((q) => q._key === gk);
if (g) g.qty++; else glass.push({ _key: gk, id: spec.no + '-G' + String(glass.length + 1).padStart(2, '0'), w: r1(gw), h: r1(gh), qty: 1, build: spec.glass, area: Math.round(gw * gh) / 1e6 });
gMax = Math.max(gMax, gw * gh);

Hardware and gasket quantities

64-bit

Handles, hinges, locking points, strikers, packers, connectors and the meters of each gasket for a set of windows.

locks = max(2, ⌈perimeter ÷ lock pitch⌉); sash gasket = 2 (w + h) ÷ 1000 m; glass gasket = 4 (gw + gh) ÷ 1000 m

The running total of each item is rounded to 0.001 after every addition (a millimeter of gasket), so the rounding can add up over many panes. Perimeters use the sash and glass sizes already rounded to 0.001 mm. No waste allowance is added.

  • addons/windows/lib/model.js
  • lines 198 to 223
  • CadCraft 0.8.2
export function hardwareList(models) {
  const rows = new Map();
  const add = (key, item, spec, qty) => { if (!(qty > 0)) return; const k = key + '|' + spec; const r = rows.get(k) || { key, item, spec, qty: 0 }; r.qty = Math.round((r.qty + qty) * 1000) / 1000; rows.set(k, r); };
  for (const mod of models) {
    const sys = mod.system, hw = sys.hardware;
    for (const col of mod.cols) for (const c of col.cells) {
      if (c.sash) {
        const per = 2 * (c.sash.w + c.sash.h);
        add('handle', 'hw.handle', '', 1);
        const locks = Math.max(2, Math.ceil(per / hw.lockPitch));
        if (c.op === 'TL' || c.op === 'TR') add('tt-set', 'hw.tt-set', c.op === 'TL' ? 'L' : 'R', 1);
        else if (c.op === 'T') add('tilt-set', 'hw.tilt-set', '', 1);
        else add('hinge', 'hw.hinge', '', c.sash.h > hw.hingeOver ? 3 : 2);
        add('lock', 'hw.lock', '', locks);
        add('striker', 'hw.striker', '', locks);
        add('gasket-sash', 'hw.gasket-sash', 'm', per / 1000);
      }
      add('packer', 'hw.packer', '', hw.packers);
      add('gasket-glass', 'hw.gasket-glass', 'm', 2 * 2 * (c.glass.w + c.glass.h) / 1000);
    }
    add('gasket-frame', 'hw.gasket-frame', 'm', 2 * (mod.inner.w + mod.inner.h) / 1000);
    add('connector', 'hw.connector', '', 2 * (mod.cols.length - 1) + 2 * mod.cols.reduce((a, c) => a + c.cells.length - 1, 0));
  }
  const ORDER = ['handle', 'tt-set', 'tilt-set', 'hinge', 'lock', 'striker', 'connector', 'packer', 'gasket-frame', 'gasket-sash', 'gasket-glass'];
  return [...rows.values()].sort((a, b) => ORDER.indexOf(a.key) - ORDER.indexOf(b.key) || a.spec.localeCompare(b.spec));
}
addons/windows/main.js CadCraft 0.8.2 64-bit 2 calculations Reviewed

Tests None found by the review

Millimeters per drawing unit

64-bit

The factor between the drawing's units and the builder's millimeters, and its reciprocal stored with each window.

mm per drawing unit = per × 25.4 (inch rules) or per × 1; kmm = 1 ÷ that

25.4 is exact. The stored kmm is a reciprocal, 1 ÷ (per × 25.4) in an inch drawing, which a binary number cannot hold exactly; it is the factor the elevation drawing multiplies every millimeter size by.

  • addons/windows/main.js
  • line 64
  • CadCraft 0.8.2
const unitFacts = (doc) => { const u = ruleUnitOf(doc), per = perUnit(doc, u), mmPerRule = u === 'in' ? MM_IN : 1; return { u, mmPerDrawing: per * mmPerRule, kmm: 1 / (per * mmPerRule) }; };

Typed opening size

64-bit

A size typed as width x height in drawing units, as millimeters.

w = typed w × mm per drawing unit

The text is read as plain decimals (no fractions, no unit marks) and multiplied by the factor; nothing is rounded.

  • addons/windows/main.js
  • lines 65 to 68
  • CadCraft 0.8.2
const parseSize = (s, mmPerDrawing) => {
  const m = /^\s*(\d+(?:\.\d+)?)\s*[x×*]\s*(\d+(?:\.\d+)?)\s*$/i.exec(String(s ?? ''));
  return m ? { w: Number(m[1]) * mmPerDrawing, h: Number(m[2]) * mmPerDrawing } : null;
};

In detail

Method
The file's header comment gives each formula: inner opening, columns and rows shared after the mullions, sash = cell + 2 (overlap − gap), glass from the sight size plus the seats, beads from the glass, half the weld allowance on every mitred end of a welded frame.
Number types
64-bit numbers.
Tolerances
Lengths rounded to 0.001 mm. An end counts as mitred when its angle differs from 90° by more than 1e-9. Typed column or row sizes are refused when they exceed the space by more than 1e-9 mm, and rescaled when they miss it by more than 1e-6 mm.
Limits
The steel reinforcement section is fixed at 28 × 22 mm in the code. The heavy-sash warning uses fixed limits of 1400 mm width and 2.2 m².

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