Resources Numerical accuracy Calculation policy

Trim, extend and corner walls

Finds where two wall center lines meet and the new ends of a wall trimmed or extended to another.

How the precision is kept

The meeting point is a closed-form line intersection in 64-bit plan millimeters. The parallel test is scaled to the lengths of both walls. Decisions about which part of a wall stays, and whether a door or window would be cut, use fixed margins of half a millimeter.

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.

model-trim.js BimCraft 0.8.1 64-bit 5 calculations Reviewed

Tests tools/bimcraft-test/draw06-test.mjs, tools/bimcraft-test/comforts-test.mjs

Where two center lines meet

64-bit

The meeting point of line a→b and line c→d, with its position along each.

den = r × s; t = ((c − a) × s) ÷ den; u = ((c − a) × r) ÷ den; p = a + t·r

The lines count as parallel when |den| < 1e-3 × |r| × |s|, a test on the sine of the angle between them, so it does not depend on the walls' lengths. The point is computed from the first line only.

  • model-trim.js
  • lines 34 to 39
  • BimCraft 0.8.1
export function axisMeet(a, b, c, d) {
  const r = sub(b, a), s = sub(d, c), den = cross(r, s);
  if (Math.abs(den) < 1e-3 * Math.hypot(...r) * Math.hypot(...s)) return null;   // within ~0.06°: parallel (walls drawn by hand)
  const ca = sub(c, a), t = cross(ca, s) / den, u = cross(ca, r) / den;
  return { t, u, p: [a[0] + r[0] * t, a[1] + r[1] * t] };
}

New ends of a trimmed wall

64-bit

Which part of the wall stays and where its moved end goes.

inside when 0.5 ÷ L < t < 1 − 0.5 ÷ L; click side by t_c = (click − a)·(b − a) ÷ L²

A meeting point within half a millimeter of an end counts as that end. The result is refused when the wall would be shorter than 50 mm.

  • model-trim.js
  • lines 41 to 52
  • BimCraft 0.8.1
/** The wall's new ends for a meeting point at t (along a→b), keeping the side of `click` when the point is inside the wall. */
function toPoint(wall, t, p, click) {
  const L = len(wall.a, wall.b), eps = 0.5 / L;   // half a millimetre
  let out;
  if (t > eps && t < 1 - eps) {
    const tc = dot(sub(click, wall.a), sub(wall.b, wall.a)) / (L * L);
    out = tc < t ? { a: wall.a, b: p } : { a: p, b: wall.b };
  } else if (t <= eps) out = { a: p, b: wall.b };
  else out = { a: wall.a, b: p };
  if (len(out.a, out.b) < MIN) return err('model.err.trim.short');
  return out;
}

Trim or extend to a wall

64-bit

A wall's new ends when it is trimmed or extended to another wall.

allowed when −slack ≤ u ≤ 1 + slack, slack = (t_edge ÷ 2) ÷ L_edge

The meeting point must lie on the edge wall, with half that wall's thickness allowed past each end. The slack is expressed as a fraction of the edge wall's length.

  • model-trim.js
  • lines 54 to 62
  • BimCraft 0.8.1
export function trimPlan(wall, edge, click) {
  if (wall.guid && wall.guid === edge.guid) return err('model.err.trim.same');
  if (wall.bulge || edge.bulge) return err('comforts.err.curvedtrim');   // 0.6 round 2: curved walls are not trimmed yet
  const m = axisMeet(wall.a, wall.b, edge.a, edge.b);
  if (!m) return err('model.err.trim.parallel');
  const Le = len(edge.a, edge.b), slack = (edge.t || 0) / 2 / Le;
  if (m.u < -slack || m.u > 1 + slack) return err('model.err.trim.miss');
  return toPoint(wall, m.t, m.p, click);
}

Corner two walls

64-bit

Both walls' new ends at the point their center lines meet.

One call to axisMeet gives the point and both parameters; each wall is then cut with toPoint. Both walls receive the same point, computed from the first wall's line.

  • model-trim.js
  • lines 64 to 74
  • BimCraft 0.8.1
export function cornerPlan(w1, click1, w2, click2) {
  if (w1.guid && w1.guid === w2.guid) return err('model.err.trim.same');
  if (w1.bulge || w2.bulge) return err('comforts.err.curvedtrim');   // 0.6 round 2: curved walls are not trimmed yet
  const m = axisMeet(w1.a, w1.b, w2.a, w2.b);
  if (!m) return err('model.err.trim.parallel');
  const r1 = toPoint(w1, m.t, m.p, click1);
  if (r1.error) return r1;
  const r2 = toPoint(w2, m.u, m.p, click2);
  if (r2.error) return r2;
  return [r1, r2];
}

Whether a trim cuts through an opening

64-bit

Checks each door and window on the wall against the wall's new extent.

s₀ = offset − shift, s₁ = s₀ + width; cut when the opening is not wholly outside and s₀ < −0.5 or s₁ > L + 0.5

The shift is the dot product of the start point's move with the wall's unit direction. All comparisons use a fixed 0.5 mm margin.

  • model-trim.js
  • lines 76 to 84
  • BimCraft 0.8.1
export function cutsOpening(wall, plan, openings) {
  const u = [(wall.b[0] - wall.a[0]) / len(wall.a, wall.b), (wall.b[1] - wall.a[1]) / len(wall.a, wall.b)];
  const shift = dot(sub(plan.a, wall.a), u), L = len(plan.a, plan.b);
  for (const o of openings.filter((x) => x.wallGuid === wall.guid)) {
    const s0 = o.offset - shift, s1 = s0 + o.width;
    if (!(s1 <= 0.5 || s0 >= L - 0.5) && (s0 < -0.5 || s1 > L + 0.5)) return 'model.err.trim.cuts';
  }
  return null;
}

In detail

Method
Parametric intersection of two lines by cross products: t = (c − a) × s ÷ (r × s), u = (c − a) × r ÷ (r × s).
Number types
64-bit numbers in plan millimeters.
Tolerances
Parallel when |r × s| < 1e-3 × |r| × |s| (about 0.06°). Inside-the-wall test and opening tests use 0.5 mm. No wall is trimmed shorter than 50 mm. The meeting point may lie up to half the edge wall's thickness past its ends.
Limits
Curved walls are refused. Walls closer to parallel than about 0.06° are refused, so near that limit the meeting point is far away and its position is sensitive to the walls' directions.

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.

Every policy that takes this approach