Finds where a beam end meets a column, where two members meet end to end in line or at an angle, and where a brace lands on another member.
How the precision is kept
64-bit vector arithmetic on the members' stored end points. Every decision is a comparison against a fixed tolerance: 1 mm for distances and fixed cosine limits for directions. The tolerances are absolute and do not scale with the size of the model.
Calculations under this policy
5 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 shortest distance from a point to the segment between a member's two ends.
k = clamp((P − a)·(b − a) ÷ |b − a|², 0, 1); d = |P − (a + k·(b − a))|
The division is guarded for a zero-length segment. The foot point is rebuilt from a + k·(b − a) before the final subtraction.
connections.js
lines 121 to 126
DetailCraft 0.8.1
/** Distance from point P to the segment a–b. */
function segDist(P, a, b) {
const ab = sub(b, a), L2 = dot(ab, ab);
const k = L2 > 0 ? Math.max(0, Math.min(1, dot(sub(P, a), ab) / L2)) : 0;
return len(sub(P, add(a, mul(ab, k))));
}
Is a member vertical
64-bit
Whether a member stands vertical, the condition for a column to take a connection.
|Δx| < 1 mm and |Δy| < 1 mm and |Δz| ≥ 1 mm
Absolute 1 mm limits on the coordinate differences, whatever the member's length, so the lean allowed is under 1 mm in each plan direction over the full height.
Pairs of member ends that meet in line, with the gap between them.
along = d·d̂_a; off = |d − d̂_a·along|; in line when |d̂_a·d̂_b| ≥ 0.9999, off ≤ 1 mm, |d| ≤ 50 mm
Directions are normalized end differences. The offset from the line is found by subtracting the along part from the gap vector. The gap returned is |along|, unrounded.
connections.js
lines 160 to 182
DetailCraft 0.8.1
/**
* Where two members meet end to end in line: [{ a, aEnd, b, bEnd, gap }] — a's end within 50 mm of b's end, the two
* parallel and on one line (within 1 mm), both I-shapes of one profile is not required here (buildSplice checks).
*/
export function findSplices(state, ids = null) {
const ms = state.members.filter((m) => !ids || ids.includes(m.id));
const out = [];
for (let i = 0; i < ms.length; i++) for (let j = i + 1; j < ms.length; j++) {
const A = ms[i], B = ms[j];
const da = norm(sub(A.end, A.start)), db = norm(sub(B.end, B.start));
if (Math.abs(dot(da, db)) < 0.9999) continue;
for (const aEnd of ['start', 'end']) for (const bEnd of ['start', 'end']) {
const P = A[aEnd], Q = B[bEnd], d = sub(Q, P), along = dot(d, da), off = len(sub(d, mul(da, along)));
if (off > TOL || len(d) > 50) continue;
// b must lie beyond a's end, not overlap it
const out_ = aEnd === 'end' ? 1 : -1;
const bOther = B[bEnd === 'start' ? 'end' : 'start'];
if (dot(sub(bOther, P), da) * out_ <= 0) continue;
out.push({ a: A.id, aEnd, b: B.id, bEnd, gap: Math.abs(along) });
}
}
return out;
}
Beam end at a column
64-bit
Which beam ends land on a column, and whether they meet its flange, its web or come in skewed.
flange when |away·v̂| > 0.9999; web when |away·û| > 0.9999; else skew
The beam end must be within 1 mm of the column line in plan and within its height plus 1 mm. The beam's plan direction is normalized; a beam with no plan length is skipped.
connections.js
lines 186 to 208
DetailCraft 0.8.1
/** Where a beam end meets a column: [{ beam, end, column, face: 'flange'|'web'|'skew', away }]. */
export function findJoints(state) {
const cols = state.members.filter((m) => m.type === 'column' && isVertical(m));
const out = [];
for (const b of state.members) {
if (b.type !== 'beam') continue;
for (const end of ['start', 'end']) {
const N = b[end], O = end === 'start' ? b.end : b.start;
for (const c of cols) {
const z0 = Math.min(c.start[2], c.end[2]), z1 = Math.max(c.start[2], c.end[2]);
if (Math.abs(N[0] - c.start[0]) > TOL || Math.abs(N[1] - c.start[1]) > TOL || N[2] < z0 - TOL || N[2] > z1 + TOL) continue;
const away = norm([O[0] - N[0], O[1] - N[1], 0]);
if (len(away) < 0.5) continue;
const fc = frame(c.start, c.end, c.rotation);
const dv = dot(away, fc.v), du = dot(away, fc.u);
const face = Math.abs(dv) > 0.9999 ? 'flange' : Math.abs(du) > 0.9999 ? 'web' : 'skew';
out.push({ beam: b.id, end, column: c.id, face, away });
break;
}
}
}
return out;
}
Members meeting at an angle
64-bit
Pairs of I-shaped member ends that share a point and are not in line, with the angle between them.
angle = acos(clamp(ê_A·ê_B, −1, 1)) × 180 ÷ π
The dot product is clamped to [−1, 1] before Math.acos. Near 0 and 180 degrees the arc cosine is steep, so the angle there carries fewer digits than the dot product; joints closer than cos = −0.9998 to straight are left out.
connections.js
lines 692 to 710
DetailCraft 0.8.1
/**
* Where two I-shaped members meet end to end at an angle: [{ a, aEnd, b, bEnd, angle }] — a's end within 1 mm of b's end (their
* reference lines share the node), not in line, among `ids` (all members if null). The angle is between the two members (90 = square).
*/
export function findMitres(state, ids = null) {
const ms = state.members.filter((m) => m.type !== 'plate' && (!ids || ids.includes(m.id)) && getProfile(m.profile, state.profiles)?.kind === 'I');
const out = [];
for (let i = 0; i < ms.length; i++) for (let j = i + 1; j < ms.length; j++) {
const A = ms[i], B = ms[j];
for (const aEnd of ['start', 'end']) for (const bEnd of ['start', 'end']) {
if (len(sub(A[aEnd], B[bEnd])) > TOL) continue;
const eA = norm(sub(A[otherEnd(aEnd)], A[aEnd])), eB = norm(sub(B[otherEnd(bEnd)], B[bEnd]));
const c = dot(eA, eB);
if (c < -0.9998) continue; // in line: a splice, not a mitre
out.push({ a: A.id, aEnd, b: B.id, bEnd, angle: Math.acos(Math.max(-1, Math.min(1, c))) * 180 / Math.PI });
}
}
return out;
}
In detail
Method
Point-to-segment distance, dot products of unit directions, differences of coordinates.
Number types
64-bit numbers in plain arrays.
Tolerances
TOL = 1 mm for all distance tests. In line: |cos| ≥ 0.9999 (about 0.81 degrees). Flange or web face: |cos| > 0.9999. Splice gap up to 50 mm. Mitre: not in line when cos > −0.9998 (more than about 1.15 degrees from straight).
Limits
A column is found only if it is vertical within 1 mm in plan over its whole length.
Approach: 64-bit, direct
Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.