Resources Numerical accuracy Calculation policy

Factory layout checks

Checks equipment placed on a floor for overlaps, blocked access strips, narrow aisles, items outside the room and floor load, and measures the path between items.

How the precision is kept

Each item is a turned rectangle whose corners are computed in 64-bit millimeters. Overlap is a separating-axis test with an absolute 1 mm allowance, the gap is the least corner-to-edge distance, and the floor load divides the weight by the shoelace area. Reported gaps and loads are rounded to whole numbers for the message; the comparisons use the unrounded values.

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.

addons/plant/lib/layout.js BimCraft 0.8.1 64-bit 8 calculations Reviewed

Tests None found by the review

Corners of an item

64-bit

The four corners in plan of an item of given width, depth, position and turn.

corner = (x₀ + x cos a − y sin a, y₀ + x sin a + y cos a), a = rot × π ÷ 180

Half width and half depth are rotated about the item's center in 64-bit. For turns of 90 degrees the cosine is about 6e-17 rather than 0, which the 1 mm overlap allowance absorbs.

  • addons/plant/lib/layout.js
  • lines 72 to 75
  • BimCraft 0.8.1
export function corners(it) {
  const a = (it.rot * Math.PI) / 180, c = Math.cos(a), s = Math.sin(a), hx = it.w / 2, hy = it.d / 2;
  return [[-hx, -hy], [hx, -hy], [hx, hy], [-hx, hy]].map(([x, y]) => [it.x + x * c - y * s, it.y + x * s + y * c]);
}

Access strips

64-bit

The strips beside each side of an item that must stay free, as turned rectangles.

Each strip is built in the item's own axes from the half sizes and the clearance, then rotated and moved the same way as the corners. Sides with a clearance of 0 give no strip.

  • addons/plant/lib/layout.js
  • lines 76 to 87
  • BimCraft 0.8.1
/** The strips beside each side that must stay free: front = +Y, back = −Y, left = −X, right = +X (before turning). */
export function accessZones(it) {
  const a = (it.rot * Math.PI) / 180, c = Math.cos(a), s = Math.sin(a), hx = it.w / 2, hy = it.d / 2;
  const T = ([x, y]) => [it.x + x * c - y * s, it.y + x * s + y * c];
  const strip = (x0, y0, x1, y1) => [[x0, y0], [x1, y0], [x1, y1], [x0, y1]].map(T);
  return [
    { side: 'front', clear: it.clear.front, corners: strip(-hx, hy, hx, hy + it.clear.front) },
    { side: 'back', clear: it.clear.back, corners: strip(-hx, -hy - it.clear.back, hx, -hy) },
    { side: 'left', clear: it.clear.left, corners: strip(-hx - it.clear.left, -hy, -hx, hy) },
    { side: 'right', clear: it.clear.right, corners: strip(hx, -hy, hx + it.clear.right, hy) },
  ].filter((z) => z.clear > 0);
}

Overlap of two convex polygons

64-bit

Whether two items, or an item and an access strip or obstacle, overlap by more than a sliver.

separated if maxA ≤ minB + tol or maxB ≤ minA + tol on any edge normal; tol = 1

Each edge normal is normalized, so the projections are distances in millimeters and the allowance of 1 is an absolute 1 mm. Polygons that touch or overlap by 1 mm or less are reported as not overlapping.

  • addons/plant/lib/layout.js
  • lines 92 to 100
  • BimCraft 0.8.1
/** Do two convex polygons overlap by more than a sliver (touching edges do not count)? */
export function overlaps(A, B, tol = 1) {
  for (const P of [A, B]) for (const [p, q] of edges(P)) {
    const ax = [-(q[1] - p[1]), q[0] - p[0]], l = Math.hypot(...ax) || 1, n = [ax[0] / l, ax[1] / l];
    const [a0, a1] = project(A, n), [b0, b1] = project(B, n);
    if (a1 <= b0 + tol || b1 <= a0 + tol) return false;
  }
  return true;
}

Distance from a point to a segment

64-bit

The shortest distance from a corner of one item to an edge of another.

t = clamp(((p − a) · (b − a)) ÷ |b − a|², 0, 1); d = |p − (a + t (b − a))|

The parameter is clamped to the segment and a zero-length edge uses t = 0, so there is no division by zero. The distance is a Math.hypot of two differences.

  • addons/plant/lib/layout.js
  • line 101
  • BimCraft 0.8.1
const segDist = (p, a, b) => { const dx = b[0] - a[0], dy = b[1] - a[1], L2 = dx * dx + dy * dy, t = L2 ? Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / L2)) : 0; return Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy)); };

Gap between two items

64-bit

The least distance between two items that do not overlap, compared with the minimum aisle width.

gap = min over corners and edges of the point-to-segment distance

Every corner of each polygon is measured against every edge of the other and the minimum is kept. The warning compares the unrounded gap with the limit; the message shows the gap rounded to a whole millimeter.

  • addons/plant/lib/layout.js
  • lines 102 to 108
  • BimCraft 0.8.1
/** The least distance between two convex polygons that do not overlap. */
export function gap(A, B) {
  let best = Infinity;
  for (const p of A) for (const [a, b] of edges(B)) best = Math.min(best, segDist(p, a, b));
  for (const p of B) for (const [a, b] of edges(A)) best = Math.min(best, segDist(p, a, b));
  return best;
}

Floor load of an item

64-bit

The weight of an item divided by its footprint area, compared with the floor load limit.

load = weight ÷ (area ÷ 10⁶) in kg per m²; area = |Σ (xᵢ yᵢ₊₁ − xᵢ₊₁ yᵢ)| ÷ 2

The area is the shoelace sum over the turned corners in absolute coordinates, so for an item far from the origin it subtracts large products that are nearly equal. The comparison uses the unrounded load; the message shows it rounded to a whole number.

  • addons/plant/lib/layout.js
  • lines 121 to 124
  • BimCraft 0.8.1
if (it.weight > 0 && floorLoad > 0) {
  const load = it.weight / (area(P) / 1e6);
  if (load > floorLoad) out.push({ code: 'floor-load', level: 'warning', items: [it.id], vars: { name: it.tag || it.name || it.id, load: Math.round(load), limit: floorLoad }, at: [it.x, it.y] });
}

Footprint of Plant equipment

64-bit

The width, depth, height and center of a piece of Plant equipment for the layout, from its bounding box.

Width, depth and height are differences of the bounding box limits, and the box center is rotated about the placement point by the item's turn. A round vessel therefore has a square footprint the size of its diameter.

  • addons/plant/lib/layout.js
  • lines 47 to 54
  • BimCraft 0.8.1
/** Equipment from equip.js (unplaced): its box in plan is the footprint, turned about the place's point like the equipment is. */
export function itemFromEquipment(eq, place = {}) {
  if (!eq?.bbox) return null;
  const w = eq.bbox.max[0] - eq.bbox.min[0], d = eq.bbox.max[1] - eq.bbox.min[1];
  const cx = (eq.bbox.max[0] + eq.bbox.min[0]) / 2, cy = (eq.bbox.max[1] + eq.bbox.min[1]) / 2;
  const a = ((place.rot || 0) * Math.PI) / 180, c = Math.cos(a), s = Math.sin(a);
  return layoutItem({ id: place.id ?? '', name: place.name ?? eq.type, tag: place.tag, w, d, h: eq.bbox.max[2] - eq.bbox.min[2], weight: place.weight, x: (place.x || 0) + cx * c - cy * s, y: (place.y || 0) + cx * s + cy * c, rot: place.rot || 0, clear: place.clear });
}

Flow path length

64-bit

The length of the path through a sequence of items, measured center to center along X then Y.

L = Σ (|Δx| + |Δy|)

A sum of absolute coordinate differences in order, in 64-bit. An id in the sequence that is not in the layout is skipped without a message.

  • addons/plant/lib/layout.js
  • lines 147 to 152
  • BimCraft 0.8.1
export function flowLength(items, sequence) {
  const by = new Map((items || []).map(layoutItem).filter(Boolean).map((i) => [i.id, i]));
  let n = 0;
  for (let k = 1; k < sequence.length; k++) { const a = by.get(sequence[k - 1]), b = by.get(sequence[k]); if (a && b) n += Math.abs(a.x - b.x) + Math.abs(a.y - b.y); }
  return n;
}

In detail

Method
Separating axis test for convex polygons; point-to-segment distance; shoelace area.
Number types
64-bit numbers.
Tolerances
Overlap must exceed 1 mm (absolute) on every axis. The room boundary test allows 1e-6 mm (absolute). Neither scales with the size of the layout.
Limits
Items are rectangles in plan; equipment from the Plant add-on uses its bounding box as the footprint. The flow path is measured along X then Y between centers, not around obstacles.

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