Decides which objects are unchanged, moved, changed, added or removed between two versions of a drawing.
How the precision is kept
Every number of an object is rounded to a grid of 0.0001 drawing units before two objects are compared, so the comparison is a stated tolerance and not an exact test. What is left is paired by the distance between bounding-box centers, in 64-bit. The compared numbers are only used to decide; the drawing's own values are not changed.
Calculations under this policy
4 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.
The text a number is compared by: its value on a grid of 0.0001.
Math.round(v ÷ 0.0001) × 0.0001, written with at most 4 decimals
Values are equal when they round to the same grid step. Two values up to 0.0001 apart can be equal, and two values closer than that on either side of a half step differ. Digits after the fourth decimal in the product's text are cut off, which removes the binary noise of n × 0.0001.
Pairs each leftover object with the nearest object of the other version that is close enough.
d = |center_a − center_b|; candidate when d ≤ max(size_a, size_b) + 1e-9; nearest first
Candidates are looked up in a grid whose cell is the largest object size (at least 1), in the cell and its 8 neighbors. The 1e-9 slack is absolute. Each object is used in at most one pair.
drawing-compare.js
lines 131 to 151
CadCraft 0.8.2
const pair = (listA, listB, ok) => { // nearest first, on a grid of the larger sizes
if (!listA.length || !listB.length) return [listA, listB];
for (const x of listA) x.box ||= boxOf(x.e, ba);
for (const y of listB) y.box ||= boxOf(y.e, bb);
const cell = Math.max(1, ...listA.map((x) => x.box.d), ...listB.map((y) => y.box.d)) || 1;
const grid = new Map(), key = (cx, cy) => Math.floor(cx / cell) + ',' + Math.floor(cy / cell);
for (const x of listA) { const k = key(x.box.cx, x.box.cy); (grid.get(k) || grid.set(k, []).get(k)).push(x); }
const cand = [];
for (const y of listB) {
const gx = Math.floor(y.box.cx / cell), gy = Math.floor(y.box.cy / cell);
for (let i = -1; i <= 1; i++) for (let j = -1; j <= 1; j++) for (const x of grid.get((gx + i) + ',' + (gy + j)) || []) {
if (!ok(x, y)) continue;
const d = Math.hypot(x.box.cx - y.box.cx, x.box.cy - y.box.cy);
if (d <= Math.max(x.box.d, y.box.d) + 1e-9) cand.push([d, x, y]);
}
}
cand.sort((p, q) => p[0] - q[0]);
const usedA = new Set(), usedB = new Set();
for (const [, x, y] of cand) { if (usedA.has(x) || usedB.has(y)) continue; usedA.add(x); usedB.add(y); changed.push({ a: x.e, b: y.e, what: what(x.e, y.e) }); }
return [listA.filter((x) => !usedA.has(x)), listB.filter((y) => !usedB.has(y))];
};
Moved or reshaped
64-bit
Says whether a changed object only moved: every x shifted by one amount and every y by another.
|Δxᵢ − Δx₀| ≤ 1e-3 and |Δyᵢ − Δy₀| ≤ 1e-3 for every coordinate; other numbers equal on the 0.0001 grid
The shifts are compared with an absolute tolerance of 1e-3 drawing units, ten times the equality grid. Radii, heights and angles must be equal after rounding to the grid.
drawing-compare.js
lines 103 to 117
CadCraft 0.8.2
/** Same size and shape, only shifted: every number differs by one shift (x with x, y with y). */
function pureMove(a, b) {
const pts = (e) => { const out = []; const walk = (v) => { if (v && typeof v === 'object') for (const k of Object.keys(v)) { if (k === 'x' || k === 'x1' || k === 'x2' || k === 'cx') out.push(['x', v[k]]); else if (k === 'y' || k === 'y1' || k === 'y2' || k === 'cy') out.push(['y', v[k]]); else if (!k.startsWith('_') && !SKIP.has(k)) walk(v[k]); } }; walk(e); return out; };
const pa = pts(a), pb = pts(b);
if (!pa.length || pa.length !== pb.length) return false;
let dx = null, dy = null;
for (let i = 0; i < pa.length; i++) {
if (pa[i][0] !== pb[i][0] || !Number.isFinite(pa[i][1]) || !Number.isFinite(pb[i][1])) return false;
const d = pb[i][1] - pa[i][1];
if (pa[i][0] === 'x') { if (dx === null) dx = d; else if (Math.abs(d - dx) > 1e-3) return false; } else if (dy === null) dy = d; else if (Math.abs(d - dy) > 1e-3) return false;
}
// the numbers other than positions (radius, height, angles) must be equal
const strip = (e) => canon(e, true).replace(/(?:^|[,{])(?:x|y|x1|y1|x2|y2|cx|cy):[^,}]*/g, '');
return strip(a) === strip(b);
}
In detail
Method
Canonical text of each object with numbers on a 0.0001 grid, hashed; then nearest-first pairing on a grid of cells the size of the largest object.
Number types
64-bit numbers; signatures are 53-bit string hashes that are compared, never decoded.
Tolerances
0.0001 drawing units (absolute grid) for equality; a move is pure when every coordinate shift agrees within 1e-3 (absolute); two objects can pair when their centers are within the larger object's diagonal plus 1e-9.
Limits
The grid is absolute: it does not scale with the drawing's units or size. Two values less than 0.0001 apart can fall on different grid steps and count as changed.
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.