Piping isometric drawing and its bill of materials
Draws pipe runs as a 30 degree isometric with the length of every leg, elevations and a bill of materials, and writes the drawing as SVG or DXF.
How the precision is kept
Leg lengths and the pipe total are 64-bit sums of Math.hypot over the run's 3D points. What a person reads is rounded when it is turned into text: whole millimeters, or the nearest 1/8 inch. The drawing itself is not to scale; its paper coordinates are written to 4 decimals in DXF and 2 decimals in SVG.
Open item. Guard the elbow angle against a zero-length leg before the division.
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 position on the drawing of a 3D point, before fitting to the sheet.
u = (x − y) × cos 30°; v = z + (x + y) × 0.5
cos 30° is computed once as Math.cos(Math.PI / 6) and sin 30° is the exact 0.5. The projection is two 64-bit expressions.
addons/plant/lib/iso.js
line 23
BimCraft 0.8.1
export const project = ([x, y, z]) => [(x - y) * C30, z + (x + y) * S30];
Length text
64-bit
Turns a length in millimeters into the text on the drawing: whole millimeters, or feet and inches to 1/8 inch.
mm: round(L); inches: round(L ÷ 25.4 × 8) eighths
The value is rounded to a whole millimeter, or to the nearest 1/8 inch using the exact 25.4. This is the only rounding of a leg length or elevation, and it happens when the text is made, not before.
addons/plant/lib/iso.js
lines 26 to 33
BimCraft 0.8.1
export function fmtLen(mm, units = 'mm') {
if (units !== 'in') return String(Math.round(mm));
let eighths = Math.round((mm / 25.4) * 8); // to 1/8 in
const ft = Math.floor(eighths / 96); eighths -= ft * 96;
const inch = Math.floor(eighths / 8), fr = eighths - inch * 8;
const frac = fr ? ' ' + (fr % 4 === 0 ? fr / 4 + '/2' : fr % 2 === 0 ? fr / 2 + '/4' : fr + '/8') : '';
return (ft ? ft + "'-" : '') + inch + frac + '"';
}
Point at a distance along a run
64-bit
The 3D point and direction at a given distance along a pipe run, used to place valves and spool balloons.
Leg lengths are added in order and the leg is chosen with an absolute slack of 1e-9 mm. The position inside the leg is a linear interpolation; a zero-length leg uses t = 0 and a divisor of 1.
addons/plant/lib/iso.js
lines 35 to 44
BimCraft 0.8.1
function pointAt(pts, s) {
let run = 0;
for (let i = 1; i < pts.length; i++) {
const a = pts[i - 1], b = pts[i], l = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
if (s <= run + l + 1e-9) { const t = l ? (s - run) / l : 0; return { p: [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t], dir: [(b[0] - a[0]) / (l || 1), (b[1] - a[1]) / (l || 1), (b[2] - a[2]) / (l || 1)] }; }
run += l;
}
const a = pts[pts.length - 2], b = pts[pts.length - 1], l = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]) || 1;
return { p: b.slice(), dir: [(b[0] - a[0]) / l, (b[1] - a[1]) / l, (b[2] - a[2]) / l] };
}
Bill of materials quantities
64-bit
Pipe length per size and spec, the count of elbows by angle, valves and welds.
Pipe length is the 64-bit sum of leg lengths, rounded only when printed. The cosine is clamped to [−1, 1] before acos and the angle is rounded to a whole degree, so elbows within half a degree of each other are counted together. The division by the two leg lengths is not guarded: a zero-length leg gives an angle that is not a number.
The single scale and offset that place the projected runs inside the drawing area of the sheet.
scale = min(availW ÷ bw, availH ÷ bh)
The projected extent is floored at 1 so the division cannot be by zero. The scale only places the picture; the dimension texts are taken from the 3D points, not from the scaled drawing.
The dimension line beside each leg and the number on it.
real = √(Δx² + Δy² + Δz²) of the leg's 3D ends
The number shown is the 3D length of the leg from Math.hypot, formatted by the length text function. Legs shorter on paper than 2.5 symbol units get no dimension.
addons/plant/lib/iso.js
lines 111 to 121
BimCraft 0.8.1
// dimensions: the length of every leg, a line offset from it with ticks, the number along it
for (let i = 1; i < pts.length; i++) {
const a = pts[i - 1], b = pts[i], l = Math.hypot(b[0] - a[0], b[1] - a[1]);
if (l < unit * 2.5) continue;
const u = [(b[0] - a[0]) / l, (b[1] - a[1]) / l], n = [-u[1], u[0]], off = unit * 1.1;
const a2 = [a[0] + n[0] * off, a[1] + n[1] * off], b2 = [b[0] + n[0] * off, b[1] + n[1] * off];
out.push(L(a, a2, 'dim'), L(b, b2, 'dim'), L(a2, b2, 'dim'));
const real = Math.hypot(r.points[i][0] - r.points[i - 1][0], r.points[i][1] - r.points[i - 1][1], r.points[i][2] - r.points[i - 1][2]);
const rot = readable(Math.atan2(u[1], u[0]));
out.push(T((a2[0] + b2[0]) / 2 + Math.sin(rot) * -textH * 0.7, (a2[1] + b2[1]) / 2 + Math.cos(rot) * textH * 0.7, textH, fmtLen(real, units), { rot, role: 'dim' }));
}
Coordinates in the DXF file
64-bit
Formats paper coordinates, text heights and text angles for the DXF output.
num(v) = v rounded to 4 decimals
Each value goes through toFixed(4) and back to a number, so the DXF holds paper millimeters to 0.0001 mm and text angles to 0.0001 degree. A value that is not finite is written as 0.
addons/plant/lib/iso.js
line 182
BimCraft 0.8.1
const num = (v) => (Number.isFinite(v) ? +v.toFixed(4) : 0);
Coordinates in the SVG file
64-bit
Formats paper coordinates for the SVG output and flips Y to run downward.
f(n) = n to 2 decimals; Y = paper height − y
Every coordinate is written with toFixed(2), so the SVG holds paper millimeters to 0.01 mm.
addons/plant/lib/iso.js
line 166
BimCraft 0.8.1
const f = (n) => n.toFixed(2), Y = (y) => f(paper.h - y);
In detail
Method
Isometric projection u = (x − y) cos 30°, v = z + (x + y) × 0.5; one uniform scale to fit the sheet; elbow angle from the dot product of adjacent legs.
Number types
64-bit numbers; text for labels and files.
Tolerances
1e-9 mm when finding a position along a run; 1e-6 mm when deciding whether a run changes level; elbow angles rounded to whole degrees.
Limits
The pipe length in the bill of materials is the center-line length through the corner points, with no deduction for elbows or valves. A leg of zero length makes the elbow angle not a number.
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.