Writes one NC file per part mark for the shop's machines: the header, the outer contours and the holes.
How the precision is kept
The part's 64-bit lengths, positions and section sizes are written as text with two decimals, and the skew cut angles with five. Values under 0.005 are written as 0.00. Contour points are plain sums and differences of the part's stored sizes; mitre contours take their end points from the cut plane.
Calculations under this policy
8 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 length, section sizes, weight and paint surface written in the header.
plates: weight = t × steel density ÷ 1000 (kg/m²); others: paint surface = perimeter ÷ 1000
Sizes are the part's stored profile values. A plate's weight line is its thickness in millimeters times the steel density divided by 1000; its paint surface is the constant 2.
The outline of the web face, notched for copes or ending on the mitre cut.
cope corners at (length, depth), (L − length, depth), (L − length, h − depth) …
Corner points are the cope's stored length and depth and differences from L and h. Nothing is computed beyond one subtraction per coordinate.
nc1.js
lines 66 to 79
DetailCraft 0.8.1
/**
* The web's outline with copes notched, y up from the side at v = −h/2 (counter-clockwise). x0(y) / x1(y): where the start / end
* face is at height y (square ends: 0 and L; a mitre: its cut face at the web's middle).
*/
function webOutline(L, h, copes, x0 = () => 0, x1 = () => L) {
const cs = (end, side) => copes.find((c) => c.end === end && c.side === side);
const pts = [];
const bs = cs('start', 'bottom'), be = cs('end', 'bottom'), te = cs('end', 'top'), ts = cs('start', 'top');
if (bs) pts.push([0, bs.depth], [bs.length, bs.depth], [bs.length, 0]); else pts.push([x0(0), 0]);
if (be) pts.push([L - be.length, 0], [L - be.length, be.depth], [L, be.depth]); else pts.push([x1(0), 0]);
if (te) pts.push([L, h - te.depth], [L - te.length, h - te.depth], [L - te.length, h]); else pts.push([x1(h), h]);
if (ts) pts.push([ts.length, h], [ts.length, h - ts.depth], [0, h - ts.depth]); else pts.push([x0(h), h]);
return pts;
}
Flange contour after copes
64-bit
Where a flange starts and ends once the copes on its side are cut.
x0 = cope length when cope depth ≥ t, else 0
A cope shortens the flange only when its depth is at least the flange thickness; the comparison has no tolerance.
nc1.js
lines 80 to 84
DetailCraft 0.8.1
/** [x0, x1] of a flange (or wall) of thickness t after the copes on its side. */
function flangeSpan(L, copes, side, t) {
const a = copes.find((c) => c.end === 'start' && c.side === side), b = copes.find((c) => c.end === 'end' && c.side === side);
return [a && a.depth >= t ? a.length : 0, b && b.depth >= t ? L - b.length : L];
}
Contours at a mitre end
64-bit
The end points of the web and flange contours where a plane cut crosses them.
x = x0 + ku·u + kv·v at the web's middle plane and at each flange's outside face
Each end point is one evaluation of the cut plane. The web's is taken at u = 0 and each flange's at its outside face, as the file header describes.
nc1.js
lines 134 to 139
DetailCraft 0.8.1
ak('v', webOutline(L, h, copes, (y) => endX(part, 'start', 0, y - h / 2), (y) => endX(part, 'end', 0, y - h / 2)));
// each flange's outside face: y = w/2 − u from the behind edge (the copes' notches are square, as before)
for (const [face, side, v] of [['o', 'top', h / 2], ['u', 'bottom', -h / 2]]) {
const [x0, x1] = flangeSpan(L, copes, side, tf), sq0 = x0 > 0, sq1 = x1 < L;
const at = (end, y) => endX(part, end, w / 2 - y, v);
ak(face, [[sq0 ? x0 : at('start', 0), 0], [sq1 ? x1 : at('end', 0), 0], [sq1 ? x1 : at('end', w), w], [sq0 ? x0 : at('start', w), w]]);
Hole positions on each face
64-bit
The x and y of each hole in the file's own face coordinates.
web: y = offset + d ÷ 2; flange of an I: y = bf ÷ 2 − offset; plate: y = offset + b ÷ 2
One addition or subtraction turns the stored offset from the section's center into the distance from the face's reference edge.
The header values and block rows read from an NC file.
Each field is turned into a number with Number, so what comes back is the two-decimal text's value, not the model's.
nc1.js
lines 171 to 198
DetailCraft 0.8.1
/** A strict reader for what writeNc1 writes (and the common subset others write): { header, blocks }. */
export function readNc1(text) {
const lines = text.split(/\r?\n/).filter((l) => l.trim() !== '' && !l.startsWith('**'));
if (lines[0] !== 'ST') throw new Error(t('nc.read.start'));
const header = [];
let i = 1;
while (i < lines.length && !/^[A-Z]{2}$/.test(lines[i])) header.push(lines[i++].trim());
if (header.length < 20) throw new Error(t('nc.read.fields', { n: header.length, min: 20 }));
const h = {
order: header[0], drawing: header[1], phase: header[2], piece: header[3], steel: header[4], quantity: Number(header[5]),
profile: header[6], code: header[7], length: Number(header[8]), height: Number(header[9]), flangeWidth: Number(header[10]),
flangeThickness: Number(header[11]), webThickness: Number(header[12]), radius: Number(header[13]), kgPerM: Number(header[14]), paintPerM: Number(header[15]),
};
const blocks = [];
let cur = null;
for (; i < lines.length; i++) {
const l = lines[i];
if (/^[A-Z]{2}$/.test(l)) { if (l === 'EN') { blocks.push({ type: 'EN', rows: [] }); cur = null; continue; } cur = { type: l, rows: [] }; blocks.push(cur); continue; }
if (!cur) throw new Error(t('nc.read.outside', { line: l }));
const m = l.match(/^\s+([vohu])\s+(.*)$/);
if (!m) throw new Error(t('nc.read.bad-line', { block: cur.type, line: l }));
const nums = m[2].trim().split(/\s+/).map((s) => Number(s.replace(/[a-z]$/, '')));
if (nums.some((n) => !Number.isFinite(n))) throw new Error(t('nc.read.nan', { block: cur.type, line: l }));
cur.rows.push({ face: m[1], nums });
}
if (!blocks.length || blocks[blocks.length - 1].type !== 'EN') throw new Error(t('nc.read.no-en'));
return { header: h, blocks };
}
In detail
Method
DSTV standard description, 7th edition, as cited in the file: ST header, AK contours, BO holes.
Number types
64-bit numbers written with toFixed(2), angles with toFixed(5).
Tolerances
|v| < 0.005 written as 0; angles under 0.000005 degrees written as 0.
Limits
0.01 mm is the resolution of everything in the file. Copes are notched square. Bevels, markings and saw cuts are not written, as the file header says.
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.