Writes a part's dimensions, positions and parameters as text in a .pcscript file and reads them back.
How the precision is kept
By default every number is written with 12 significant digits and sketch positions with 9 decimals, so a part written to a script and read back can differ from the original beyond those digits. With the exact option numbers are written with String(), which reads back to the same 64-bit number. The reader evaluates literals and expressions in 64-bit and solves the sketches again.
Open item. Write numbers with all their digits by default, as the exact option already does, so a script reads back to the same 64-bit values.
Calculations under this policy
3 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.
How a number and a position are turned into text for the script.
number → 12 significant digits; position → 9 decimals, then 12 significant digits
Without the exact option a number is written with toPrecision(12), and a position below 10¹² is first rounded with toFixed(9). The code comment says positions carry the sketch solver's residue and the reader solves them back onto the relations. With exact, String(v) is written and −0 becomes 0.
script.js
lines 583 to 587
PartCraft 0.8.1
const num = (v) => (exact ? String(Object.is(v, -0) ? 0 : v) : formatNumber(v));
// positions (sketch points, picked points) carry the solver's residue (30.0000000000558): written to 9 decimals of
// a millimetre, which the reader solves back onto the relations
const pos = (v) => (exact ? num(v) : formatNumber(Math.abs(v) < 1e12 ? Number(v.toFixed(9)) : v));
const vecS = (a) => a.map(pos).join(', ');
Sketch geometry and dimensions
64-bit
Writes a sketch's points, circles, relations and dimension values.
Point coordinates and circle radii use the 9-decimal position format; dimension values use the 12-digit number format. Sides and fix values are compared with a copy of the sketch rounded the same way, so the file states them only when the reader would measure something else. The fix comparison uses 1e-9 × max(1, |x|).
script.js
lines 686 to 725
PartCraft 0.8.1
function writeSketch(f) {
out.push(` on ${planeS(f.plane)}`); // i18n-ignore: pcscript file text
const sk = f.sketch;
// sides and fix values are compared with what the reader will measure: the geometry as written
const skW = JSON.parse(JSON.stringify(sk));
for (const e of skW.entities) { if (e.type === 'point') { e.x = Number(pos(e.x)); e.y = Number(pos(e.y)); } if (e.type === 'circle') e.r = Number(pos(e.r)); }
const labelled = new Set(sk.constraints.filter((c) => c.type === 'offsetlink').map((c) => c.of));
for (const e of sk.entities) {
const mods = [];
if (e.construction) mods.push('construction');
let body;
if (e.type === 'point') body = `point ${e.id} ${pos(e.x)}, ${pos(e.y)}`; // i18n-ignore: pcscript file text
else if (e.type === 'line') body = `line ${e.id} ${e.p1} ${e.p2}`; // i18n-ignore: pcscript file text
else if (e.type === 'circle') body = `circle ${e.id} ${e.c} ${pos(e.r)}`; // i18n-ignore: pcscript file text
else if (e.type === 'arc') body = `arc ${e.id} ${e.c} ${e.s} ${e.e}`; // i18n-ignore: pcscript file text
else if (e.type === 'spline') {
body = `spline ${e.id} ${e.kind} ${e.pts.join(' ')}`; // i18n-ignore: pcscript file text
if (e.closed) mods.unshift('closed');
for (const k of ['a0', 'a1']) if (Number.isFinite(e[k])) mods.push(`${k} ${num(e[k])}`);
if (e.knots) mods.push(`knots ${e.knots.map(num).join(', ')}`); // i18n-ignore: pcscript file text
} else body = `# unknown geometry ${e.type}`;
out.push(` ${body}${mods.length ? ` [${mods.join('; ')}]` : ''}`);
}
for (const c of sk.constraints) {
const def = S.CONSTRAINTS[c.type];
let body = `${labelled.has(c.id) ? c.id + ': ' : ''}${c.type} ${c.refs.join(' ')}`;
const key = f.id + '.' + c.id;
if (def?.dim) body += links.has(key) ? ` = ${links.get(key)}` : ` ${num(c.value)}`;
const mods = [];
let meas = {};
try { meas = S.measure(skW, c.type, c.refs); } catch { /* unknown: everything is written */ }
if (c.sign !== meas.sign) mods.push(c.sign === undefined ? 'side none' : `side ${c.sign}`); // i18n-ignore: pcscript file text
if (c.kind !== meas.kind) mods.push(c.kind === undefined ? 'kind none' : c.kind); // i18n-ignore: pcscript file text
if (c.type === 'fix' && Array.isArray(c.value) && !(Array.isArray(meas.value) && meas.value.length === c.value.length && meas.value.every((x, i) => Math.abs(x - c.value[i]) <= 1e-9 * Math.max(1, Math.abs(x))))) mods.push(`value ${c.value.map(pos).join(', ')}`); // i18n-ignore: pcscript file text
if (c.at) mods.push(`at ${vecS(c.at)}`); // i18n-ignore: pcscript file text
if (c.off) mods.push(`label ${vecS(c.off)}`); // i18n-ignore: pcscript file text
if (c.type === 'offsetlink') mods.push(`of ${c.of}`); // i18n-ignore: pcscript file text
out.push(` ${body}${mods.length ? ` [${mods.join('; ')}]` : ''}`);
}
}
A feature value read from a script
64-bit
Reads a setting such as a depth, given as a number or as an expression.
A literal is stored as parsed. An expression is evaluated in 64-bit, rounded with Math.round for whole-number settings, checked against the setting's limits, and kept together with its expression as an equation.
script.js
lines 340 to 353
PartCraft 0.8.1
function numberProp(feat, k, v, line, spec) {
let expr = v.trim(), linked = false;
if (expr.startsWith('=')) { expr = expr.slice(1).trim(); linked = true; }
if (!expr) throw new Error(t('xch.script.needs_value', { key: k }));
const lit = linked ? null : literal(expr);
if (lit !== null) { feat[k] = lit; return; }
if (expr === 'none' && !linked) { feat[k] = null; return; }
let val = calc(expr, k);
if (spec.integer) val = Math.round(val);
const vp = valueProblem({ ...spec, label: `${feat.name} ${spec.label}` }, val);
if (vp) throw new Error(t('xch.script.expr_error', { expr, error: vp }));
feat[k] = val;
links.push({ target: feat.id + '.' + k, expr, line });
}
In detail
Method
Numbers pass through formatNumber in expr.js, which uses toPrecision(12). Positions are first rounded with toFixed(9).
Number types
64-bit numbers; decimal text in the file.
Tolerances
A fix relation's stored value is written out only when it differs from the value measured on the written geometry by more than 1e-9 × max(1, |value|).
Limits
12 significant digits by default, fewer than the 15 to 17 a 64-bit number carries; the file's header comment says the same. The call in params-panel.js does not pass the exact option.
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.