Finds a pipe route between two nozzles that runs only along X, Y and Z, keeps clear of obstacles and has few bends, and reports its length.
How the precision is kept
The route is searched on a grid whose lines are 64-bit coordinates taken from the nozzles, the obstacle faces moved out by the clearance, the preferred levels and the midpoints between neighbors. Route points are grid coordinates, so the reported length is a sum of exact coordinate differences. The search state rounds the length of the current leg to a whole millimeter, and that rounding decides whether a bend is allowed.
Open item. Cap the stored run at the minimum leg itself (or at its ceiling), so a minimum leg with a fraction below one half does not block every later bend.
Calculations under this policy
6 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 straight legs of a route with their axis and length.
axis = first of x, y, z with |Δ| > 10⁻⁶; length = |Δ| on that axis
The length of a leg is the absolute difference of one coordinate, which is exact for a leg that runs along an axis. A leg that is not along an axis would be given the length of one component only.
addons/plant/lib/route.js
lines 26 to 34
BimCraft 0.8.1
export function legsOf(points) {
const out = [];
for (let i = 1; i < points.length; i++) {
const a = points[i - 1], b = points[i], d = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
const ax = Math.abs(d[0]) > EPS ? 0 : Math.abs(d[1]) > EPS ? 1 : 2;
out.push({ a, b, axis: 'xyz'[ax], len: Math.abs(d[ax]) });
}
return out;
}
Route against obstacles
64-bit
Whether the center line of a route comes within a clearance of any obstacle box.
Each leg is tested at 21 evenly spaced points against each box grown by the clearance, with a 1e-6 margin. An obstacle thinner than one twentieth of a leg can fall between the sample points.
addons/plant/lib/route.js
lines 37 to 44
BimCraft 0.8.1
/** Does a route's centre line come within `clear` of any obstacle's inside? (the router's own promise, tested). */
export function hitsObstacle(points, obstacles, clear = 0) {
for (const l of legsOf(points)) for (const o of obstacles) {
const g = grow(o, clear);
for (let k = 0; k <= 20; k++) { const t = k / 20; if (inside([l.a[0] + (l.b[0] - l.a[0]) * t, l.a[1] + (l.b[1] - l.a[1]) * t, l.a[2] + (l.b[2] - l.a[2]) * t], g)) return true; }
}
return false;
}
Grid lines of the search
64-bit
The X, Y and Z coordinates at which a route may turn.
lines = nozzle and lead coordinates, obstacle faces ± clearance, levels, two outer lines, and the midpoint of each neighboring pair
Coordinates are collected in a Set, so only bit-identical values merge; two faces that differ in the last digit give two lines and a midpoint between them. The outer lines are 2 × the minimum leg beyond the extremes.
addons/plant/lib/route.js
lines 73 to 83
BimCraft 0.8.1
const lines = [0, 1, 2].map((k) => {
const set = new Set([s[k], g[k], opt.from.p[k], opt.to.p[k]]);
for (const b of obst) { set.add(b.min[k]); set.add(b.max[k]); }
if (k === 2) for (const z of opt.levels || []) set.add(z);
const sorted = [...set].sort((a, b) => a - b);
const lo = sorted[0] - minLeg * 2, hi = sorted[sorted.length - 1] + minLeg * 2; // room to go round the outside
const all = new Set([...sorted, lo, hi]);
const s2 = [...all].sort((a, b) => a - b);
for (let i = 1; i < s2.length; i++) all.add((s2[i - 1] + s2[i]) / 2);
return [...all].sort((a, b) => a - b);
});
Search state and the minimum leg
64-bit
The key that identifies a search state: grid node, direction and the length of the current leg.
The run so far is rounded to a whole millimeter and capped at Math.round(minLeg), and the capped value is also what the next step reads back. A bend needs run ≥ minLeg − 1e-9, so when minLeg has a fraction below one half the capped run stays below it and every bend after the first is refused. Evaluating the function for this review with no obstacles gave a route for a minimum leg of 438 and 'no-route' for 438.2.
cost = g + step × (1 + climbCost if vertical, else 1) + (bendCost if the direction changes)
The step is the sum of absolute coordinate differences between two grid nodes. Costs are added in 64-bit along the path and compared with a slack of 1e-9. Defaults: climb factor 0.3, bend cost 4 × the outside diameter + 200.
The final corner points of the route with collinear points removed, its length, bends and climbs.
length = Σ leg lengths; bends = legs − 1
A point closer than 1e-6 mm to the previous one is dropped, and a middle point is removed when the cross product of the two legs is below 1e-6 in size and they point the same way. The cross product is in square millimeters, so that test is absolute and not scaled to the leg lengths.
A* over (grid node, direction, run so far) with a Manhattan-distance estimate; cost = length, plus a factor for climbs and a fixed cost per bend.
Number types
64-bit numbers; integer keys for the search states.
Tolerances
1e-6 mm (absolute) for inside-a-box tests, for the axis of a leg and for removing collinear points; 1e-9 for cost comparisons and for the minimum-leg test.
Limits
The grid has only the lines listed above, so a route is the best one on that grid. The minimum leg is capped in the search state at Math.round(minLeg): when the minimum leg is not a whole number and rounds down, the stored run never reaches it and no bend after the first is accepted. The default minimum leg is 2 × the outside diameter (at least 150), which is not a whole number for several pipe sizes.
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.