P&ID placement, zones and values written to the drawing
Places P&ID symbols on process lines and connection points, cuts lines around inline symbols, draws hazard zones to scale, and writes imported stream values and relief sizes into the drawing.
How the precision is kept
Placement geometry is 64-bit: unit directions from Math.hypot, cut points at a half-length either side of the symbol, zone circles scaled by drawing units per meter. Values written into the drawing are rounded first: stream table cells to fixed decimals and the relief area to a whole square millimeter. The calculators themselves are in other files.
Open item. Write the relief area to the symbol unrounded and round it only where it is shown.
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.
Finds the connection point, line end or point on a line within reach of a click.
nearest within tol; a point on a line is ranked with distance + 0.1 × tol
The returned point is the connection's or end's own coordinates, or the projection on the line from nearestOnPath. Distances are Math.hypot in drawing units.
addons/pid/main.js
lines 98 to 109
CadCraft 0.8.2
/** The connection point, line end or line within reach of p: { x, y, kind, ... } | null. Snaps a new line's start. */
function snapTo(model, p, tol) {
let best = null;
const take = (cand, d) => { if (d <= tol && (!best || d < best.d)) best = { ...cand, d }; };
for (const s of model.symbols) for (const c of s.conns) take({ x: c.x, y: c.y, kind: 'conn', sym: s, conn: c }, Math.hypot(p.x - c.x, p.y - c.y));
for (const l of model.lines) {
l.ends.forEach((e, i) => take({ x: e.x, y: e.y, kind: 'end', line: l, end: i }, Math.hypot(p.x - e.x, p.y - e.y)));
const n = nearestOnPath(l.pts, p);
if (n) take({ x: n.x, y: n.y, kind: 'on', line: l, seg: n.seg }, n.dist + tol * 0.1); // a click on a line stays on it unless a nozzle or an end is closer
}
return best;
}
A line cut around an inline symbol
64-bit
Cuts a process line either side of a symbol placed on it, so the symbol's ends meet the line's.
u = (b − a) ÷ |b − a|; cut points = at ± u × half
The unit direction is the segment divided by its Math.hypot length, with no guard against a zero-length segment in this function. The line is left whole when the symbol is nearer than half its length to either corner. The longer piece, by summed length, keeps the line number.
addons/pid/main.js
lines 140 to 157
CadCraft 0.8.2
/** Put an inline symbol on a process line: the line is cut either side of it, so the symbol's ends meet the line's. */
function splitLineAround(doc, hit, at, half) {
const L = hit.l, d = L.data;
const [ax, ay] = d.pts[hit.seg], [bx, by] = d.pts[hit.seg + 1];
const len = Math.hypot(bx - ax, by - ay), ux = (bx - ax) / len, uy = (by - ay) / len;
const su = Math.hypot(at.x - ax, at.y - ay);
if (su < half || len - su < half) return false; // too near a corner: the symbol goes on, the line is left whole
const before = d.pts.slice(0, hit.seg + 1).concat([[at.x - ux * half, at.y - uy * half]]);
const after = [[at.x + ux * half, at.y + uy * half]].concat(d.pts.slice(hit.seg + 1));
const plen = (pts) => pts.slice(1).reduce((n, p, i) => n + Math.hypot(p[0] - pts[i][0], p[1] - pts[i][1]), 0);
const aKeeps = plen(before) >= plen(after); // the longer piece keeps writing the number
const A = { ...structuredClone(d), pts: before, arrow: d.arrow === 'start' || d.arrow === 'both' ? 'start' : 'none', lbl: d.lbl && aKeeps };
const B = { ...structuredClone(d), pts: after, arrow: d.arrow === 'end' || d.arrow === 'both' ? 'end' : 'none', lbl: d.lbl && !aKeeps };
const eid = Number(L.uid);
doc.update(eid, api.objects.update(doc.get(eid), cleanLine(A)));
doc.add(api.objects.create('pidline', cleanLine(B), { layer: LAYER_OF[d.style] }));
return true;
}
A symbol's connection landed on a picked point
64-bit
Moves a new symbol so its nearest connection sits exactly on the connection or line end that was clicked.
The offset is recomputed with Math.cos and Math.sin on each call. The symbol's insertion point is the picked point minus the offset, so the connection lands on the target to within a rounding of that subtraction.
The drawing units per millimeter used to place an imported P&ID.
k = 1 ÷ (millimeters per drawing unit)
One reciprocal of the drawing's unit factor, passed to the importer.
addons/pid/main.js
lines 408 to 409
CadCraft 0.8.2
const k = 1 / perUnit(doc, 'mm'); // drawing units in a millimetre
const r = fromDexpi(d, { symL: sizes(doc).sym, perMm: k, fmt: settings.lineFormat });
Imported stream table values
64-bit
Converts stream temperature, pressure and flows to display units and writes them into a table in the drawing.
°C = K − 273.15 (1 decimal); bar = Pa ÷ 1e5 (3); kg/h = kg/s × 3600 (1); kmol/h = mol/s × 3.6 (3); fractions 3 and 4 decimals
The factors are exact. Each value is rounded with toFixed and stored in the table as text, so the drawing holds the rounded figures. 3.6 has no exact binary form, which matters far less than the rounding.
Hands the symbols' positions and the meters per drawing unit to the hazardous-area calculator.
toMetres = (millimeters per drawing unit) ÷ 1000
Positions are passed in drawing units with one factor; the division by 1000 is exact up to one rounding.
addons/pid/main.js
lines 472 to 474
CadCraft 0.8.2
// PIDSOURCES (0.8 CH.17): the release sources of the drawing itself. Grade by what each symbol is, the substance by its service (the list is the person's), then the area calculator
// classifies them; PIDZONE draws the zones. Items carry the drawing's coordinates in metres.
const drawingItems = (doc) => { const model = readModel(doc), m = perUnit(doc, 'mm') / 1000; return { toMetres: m, items: model.symbols.map((sy) => ({ tag: sy.tag, sym: sy.sym, x: sy.x, y: sy.y, svc: (sy.attrs && sy.attrs.svc) || '', attrs: sy.attrs || {} })) }; };
Consequence zones drawn to scale
64-bit
Draws each zone of the consequence calculator as a circle or polygon at the picked point.
k = 1000 ÷ (millimeters per drawing unit); center = at + (cx, cy) × k; radius = r × k
Zone sizes in meters are multiplied by k, the drawing units per meter; nothing is rounded.
Line cut at ± half the symbol length along the segment's unit direction; a symbol's nearest connection moved onto the picked connection; zones placed at at + (x, y) × k.
Number types
64-bit numbers; table cells and attributes are text.
Tolerances
A click reaches a connection, end or line within 0.4 × the symbol size; a line candidate is ranked with an extra 0.1 × tol so ends and connections win.
Limits
Stream table cells and the relief area attribute hold rounded text, so later reads of the drawing see the rounded figures.
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.