Resources Numerical accuracy Calculation policy

Typed points and door or window positions

Turns a typed length, step, polar entry or distance into the next wall point, and places a door or window along a wall from the pointer or a typed distance.

How the precision is kept

Typed values are parsed to 64-bit millimeters and added to the last point. A polar entry uses the cosine and sine of the typed angle in degrees times π ÷ 180. A door or window placed with the pointer is rounded to a 10 mm or half-inch step along the wall unless it snaps to the wall's middle.

Calculations under this policy

2 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-ui.js BimCraft 0.8.1 64-bit 2 calculations Reviewed

Tests None found by the review

Next point from a typed entry

64-bit

The plan point for a typed absolute point, relative step, length at an angle, or length along the pointer's direction; and a door or window offset from a typed distance.

relative: q = last + (dx, dy); polar: q = last + len × (cos θ, sin θ), θ = degrees × π ÷ 180; along the pointer: q = last + len × (to − last) ÷ |to − last|

Each case is one 64-bit expression on the parsed millimeters. The polar case evaluates Math.cos and Math.sin of the angle in radians, so right angles do not give an exact 0. A typed door distance is measured from the nearer end of the wall face and is refused when the opening would have to move more than 0.5 mm to fit.

  • model-ui.js
  • lines 1048 to 1086
  • BimCraft 0.8.1
function applyTyped(text) {
  if (S.tool === 'select') return false;
  if (extTool(S.tool)) return !!extTool(S.tool).typed?.(S.tool, text);
  if (S.tool === 'door' || S.tool === 'window') {
    // a distance from the nearer end of the wall face the preview shows
    const d = parseLen(text, S.units), h = S.hover, o = S.open[S.tool];
    if (!h || !Number.isFinite(d) || d < 0) { host.status(t(h ? 'model.err.length' : 'model.typed.wall'), true); return false; }
    const [e0, e1] = h.ends, nearStart = h.s < (e0 + e1) / 2;
    const offset = nearStart ? e0 + d : e1 - d - o.width, fit = fitOffset(h.wall, o.width, offset);
    if (fit === null || Math.abs(fit - offset) > 0.5) { host.status(t('model.err.nofit'), true); return false; }
    placeHere({ ...h, offset: fit, ok: true }, S.tool);
    return true;
  }
  // 0.6 round 2: a curved wall's arc height (to the side the pointer is on), once its end is set
  if (S.tool === 'wall' && S.wall.shape === 'arc' && S.chain && S.arcEnd) {
    const h = parseLen(text, S.units), a = S.chain.points[S.chain.points.length - 1], b = S.arcEnd;
    if (!Number.isFinite(h)) { host.status(t('model.err.length'), true); return false; }
    const p = S.last?.snap?.p, side = p ? Math.sign((b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0])) || 1 : 1;
    arcPoint(b, bulgeFromHeight(a, b, side * Math.abs(h)));
    return true;
  }
  const v = parseTyped(text, S.units);
  if (!v) { host.status(t('model.err.typed'), true); return false; }
  const pts = S.chain?.points;
  const last = pts ? pts[pts.length - 1] : null;
  let q = null;
  if (v.kind === 'abs') q = [v.x, v.y];
  else if (!last) { host.status(t('model.typed.first'), true); return false; }
  else if (v.kind === 'rel') q = [last[0] + v.dx, last[1] + v.dy];
  else if (v.kind === 'polar') q = [last[0] + v.len * Math.cos(v.angle * Math.PI / 180), last[1] + v.len * Math.sin(v.angle * Math.PI / 180)];
  else {
    // a length along the direction the pointer shows (its snapped direction), else along X
    const to = S.last?.snap?.p || [last[0] + 1, last[1]];
    const d = Math.hypot(to[0] - last[0], to[1] - last[1]) || 1;
    q = [last[0] + (to[0] - last[0]) / d * v.len, last[1] + (to[1] - last[1]) / d * v.len];
  }
  if (S.tool === 'wall') wallPoint(q);
  return true;
}

Door or window position from the pointer

64-bit

The wall under the pointer and the offset of an opening centered on it.

s = (p − a)·u; offset = s − width ÷ 2, then (L − width) ÷ 2 near the middle, else round(offset ÷ step) × step

The pointer is projected on the wall's unit direction with a dot product. The offset is rounded to a 10 mm or 12.7 mm step measured from the wall's start, so a clicked opening lands on that grid; within the snap distance of the wall's middle it is centered exactly instead.

  • model-ui.js
  • lines 780 to 804
  • BimCraft 0.8.1
/**
 * The wall under (or beside) plan point p and where an opening `width` wide would go, centred on p: → { wall, offset,
 * face, ok, ends: [x0, x1] (the face's ends along the wall, for the distances), s (along), side } | null.
 * Doors swing to the side the pointer is on; windows sit on the wall's outside.
 */
function hoverOpening(p, kind, width) {
  const tol = pv.pxToMm(16);
  let best = null;
  for (const w of [...wallsHere(), ...throughHere()]) {   // 0.7 BIM-MLWALL: walls running through this level take doors and windows too
    const L = w.len, u = [(w.b[0] - w.a[0]) / L, (w.b[1] - w.a[1]) / L], n = [-u[1], u[0]];
    const rx = p[0] - w.a[0], ry = p[1] - w.a[1], q = w.bulge ? wallGeom(w).param(p) : null;   // a curved wall: along its arc
    const s = q ? q.s : rx * u[0] + ry * u[1], d = q ? q.off : rx * n[0] + ry * n[1];
    if (s < -tol || s > L + tol) continue;
    const score = Math.max(0, Math.abs(d) - w.t / 2);
    if (score <= tol && (!best || score < best.score)) best = { w, s, d, score };
  }
  if (!best) return null;
  const w = best.w, step = round();
  let offset = best.s - width / 2;
  if (Math.abs(best.s - w.len / 2) <= tol) offset = (w.len - width) / 2;   // the middle of the wall snaps
  else offset = Math.round(offset / step) * step;
  const fit = fitOffset(w, width, offset);
  const face = kind === 'door' ? (best.d >= 0 ? 'left' : 'right') : w.extSide;
  return { wall: w, offset: fit ?? offset, face, ok: fit !== null, ends: faceEnds(w, face), s: best.s };
}

In detail

Method
Vector addition; polar to Cartesian; projection of the pointer on the wall's direction; rounding of the offset to a step.
Number types
64-bit numbers in plan millimeters.
Tolerances
A typed door distance is refused when the fitted offset differs from the asked one by more than 0.5 mm. The wall-middle snap and the wall pick use 16 screen pixels converted to millimeters. Offset step 10 mm (metric) or 12.7 mm.
Limits
A polar entry at 90° or 270° gives an x step that is a tiny non-zero number, because cos(90 × π ÷ 180) is not exactly 0 in 64-bit.

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