Skip to content

pack.footprintPlacement ​

Seat footprints inside a boundary.

One call. Inset the boundary by its setbacks, generate candidate seats along the buildable edges (or on a grid), then place each footprint greedily, honouring a minimum separation between footprints and any keep-clear zones. Whatever will not fit comes back in unplaced with the reason it failed.

The module knows nothing about buildings. Residential towers, warehouse sheds, villas on a subdivided parcel, plant rooms in a yard, kiosks in a concourse: the same call with different values. Towers are one caller.

Pure module: no host, no DOM, no clock, no RNG. All lengths are in input.unit and the output echoes it; the module's own tolerances are defined in millimetres and expressed in that unit. Plan coordinates are x/z; Y is up and never appears. Deterministic: the same input always returns the same output, and seed only chooses which of several equally valid arrangements you get.

ts
// plugin worker
import { footprintPlacement } from "@snaptrude/plugin-toolkit/pack-footprint-placement";

// agent run_worker
modules.pack.footprintPlacement.footprintPlacement(...)

Every input carries unit ("mm" | "cm" | "m" | "ft" | "in"). All lengths in that call — input and output — are in it; angles are degrees, areas are that unit squared.

When to use it ​

Use it forNot for
Seating rectangles or plate outlines in a boundary: towers, sheds, villas, plant rooms, kiosksTrue irregular nesting; a plate outline is seated by its bounding rectangle
Setbacks, keep-clear zones, a minimum separation and edge or grid alignmentA guaranteed optimum — the search is greedy and budgeted
Capacity questions: strategy: "maximiseCount" then read placed.lengthAnything in the third dimension: height planes, shadow, solar

Inputs and outputs ​

The shapes every function below speaks. Each appears once; a field's note says what changing it does to the result.

FootprintPlacementInput ​

  • unit: LengthUnit — The unit every length in this input is in; the output echoes it.
  • boundary: Polygon — The site: outer ring plus any courtyards, in unit, any winding.
  • setback?: number | number[] — Inward setback: one number for every edge, or one per boundary-ring edge (setback[i] applies to the edge leaving boundary.ring[i], whichever way the ring winds — a clockwise survey is honoured as written). The buildable ring is the boundary inset by this. Holes are grown outwards by the largest setback. An edge SHORTER than the setbacks eating it from both ends — a chamfer on a surveyed parcel — drops out of the buildable ring instead of failing it. A setback that collapses, inverts or pinches the site yields an empty buildable ring and no placements.
  • footprints: FootprintSpec[]
  • fixed?: FixedFootprint[] — Footprints a human has already placed and the module must not move. Each one is emitted in placed first, verbatim, with fixed: true, and counts against its spec's count. It is an obstacle for everything else — minSeparation is kept from it and nothing overlaps it — but it is never itself tested against the boundary, the setbacks or the keep-clear zones; a violation is reported as a note instead.
  • keepClear?: Polygon[] — Zones no footprint may overlap: tree canopies, easements, existing buildings.
  • minSeparation?: number — Clear distance kept between any two placed footprints, in unit. Default 0. The dominant capacity lever. 0 lets footprints abut.
  • alignTo?: AlignTo — What candidate seats line up with. Default "boundaryEdge". "boundaryEdge" takes its orientations from the buildable ring's own edge directions, so a rotated or angled site packs square to its frontage. "grid" seats on an axis-aligned grid lattice. "none" tries axis alignment only, at a step derived from the footprint size.
  • grid?: number — Lattice pitch for alignTo: "grid", in unit. Required in that mode.
  • anchor?: PlacementAnchor — Which end of the buildable area fills first. Default "minCorner". Pure preference — it orders the candidate seats, it never changes which seats are legal. "edge" fills nearest anchorEdgeIndex first, which is how a frontage preference is expressed.
  • anchorEdgeIndex?: number — Buildable-ring edge index that anchor: "edge" fills first. Default 0.
  • strategy?: PlacementStrategy — How seats are chosen. Default "maximiseCount". "maximiseCount" places the largest footprints first, first fit — the most placed in practice. "asSpecified" keeps the caller's order so placement k pairs with footprint k. "spreadEven" picks, for each footprint, the legal seat with the most room around it.
  • seed?: number — Chooses between equally valid arrangements. Rotates the candidate scan order by a fixed amount derived from the seed, so a different seed gives a different arrangement and the same seed always gives the same one. Default 0 = the canonical order.
  • budget?: FootprintPlacementBudget

FootprintSpec ​

One footprint to place, count times. Give a size or a plate outline.

  • id: string — Stable identity. Every placement and every failure carries it.
  • width?: number — Extent along the footprint's own x axis. Required unless ring is given.
  • depth?: number — Extent along the footprint's own z axis. Required unless ring is given.
  • ring?: PlanPoint[] — The real plate outline in local coordinates, any winding, first point not repeated. Recentred on its own bounding box, so absolute position is ignored. Containment is tested against this outline, so an L or a cross fits pockets a rectangle could not. Separation is still measured between bounding rectangles, which is conservative.
  • count?: number — How many of this footprint to place. Default 1.
  • rotationDeg?: number | "auto" — Fixed rotation, counter-clockwise degrees, or "auto" to let the module choose. Default "auto". A number pins the orientation and the footprint is only ever tried at that angle. "auto" tries the angles alignTo implies and keeps the first that fits.

FixedFootprint ​

A footprint a human has already placed. The module does not move it.

  • id: string — Instance identity, the caller's own. Must not collide with <footprintId>#<n>.
  • footprintId: string — The spec in footprints this is an instance of. Must exist.
  • ring: PlanPoint[] — The seated outline in world plan coordinates, emitted verbatim.
  • rotationDeg?: number — Counter-clockwise degrees, for reporting and for the separation rectangle. Default 0.

FootprintPlacementBudget ​

Deterministic search caps. Nothing here is a clock.

  • maxTests?: number — Hard cap on candidate seat tests across the whole call. Default 200000. On exhaustion the run stops and everything still to place is reported "budget-exhausted". Same input, same cut-off point.
  • maxOrientations?: number — Cap on auto orientations tried per footprint. Default 12.
  • maxCandidatesPerOrientation?: number — Cap on candidate seats per footprint per orientation. Default 4000.

FootprintPlacementOutput ​

  • contractVersion: typeof FOOTPRINT_PLACEMENT_CONTRACT_VERSION
  • unit: LengthUnit — The unit every length in this output is in — input.unit.
  • placed: PlacedFootprint[]
  • unplaced: UnplacedFootprint[]
  • buildableRing: PlanPoint[] — The boundary after setbacks, wound counter-clockwise. [] when it collapsed.
  • buildableHoles: PlanPoint[][] — Courtyards after their setback, wound clockwise.
  • buildableArea: number
  • coverageRatio: number — Placed footprint area over buildable area. 0 when there is no buildable area.
  • notes: string[] — Plain statements about what happened: collapsed setbacks, counts, budget.

PlacedFootprint ​

  • id: string — Instance identity, <footprintId>#<n>, n from 1.
  • footprintId: string
  • ring: PlanPoint[] — The seated outline in world plan coordinates, wound as the plate was.
  • centre: PlanPoint — Centre of the seated bounding rectangle.
  • rotationDeg: number
  • area: number — Area of ring, in unit².
  • fixed: boolean — True when the caller pinned this instance through fixed.

UnplacedFootprint ​

  • id: string
  • footprintId: string
  • reason: UnplacedReason

Functions ​

footprintPlacement(input) ​

Place footprints inside a boundary, honouring setbacks, separation, keep-clear zones, alignment and orientation.

Effect: the buildable ring is the boundary inset by setback; everything else works inside it. Candidate seats come from the buildable edges (or grid), are ordered by anchor, and are taken greedily in the order strategy implies. Nothing is ever seated outside the buildable ring, inside a keep-clear zone, or closer than minSeparation to another footprint; when that leaves no seat, the footprint is reported in unplaced.

Examples ​

Three towers on a site with per-edge setbacks (both)

ts
const towers: FootprintSpec[] = [0, 1, 2].map((i) => ({
  id: `T${i}`,
  width: 28_000,
  depth: 22_000,
  rotationDeg: "auto"
}));
return footprintPlacement({
  unit: "mm",
  boundary: site,
  setback: [9000, 6000, 6000, 6000],
  footprints: towers,
  minSeparation: 16_000,
  alignTo: "boundaryEdge",
  strategy: "spreadEven"
});

Seat against one preferred (road-facing) edge (both)

ts
return footprintPlacement({
  unit: "mm",
  boundary: site,
  setback: 6000,
  footprints,
  minSeparation: 16_000,
  alignTo: "boundaryEdge",
  anchor: "edge",
  anchorEdgeIndex: roadEdgeIndex
});

Capacity first: how many of one unit fit at all (agent)

ts
const res = footprintPlacement({
  unit: "mm",
  boundary: site,
  setback: 6000,
  keepClear,
  footprints: [{ id: "unit", ...unit, count: 40 }],
  minSeparation: 6000,
  strategy: "maximiseCount"
});
return res.placed.length;

Non-residential: warehouses on a grid, wide service gaps (both)

ts
return footprintPlacement({
  unit: "mm",
  boundary: plot,
  setback: 12_000,
  footprints: [{ id: "shed", width: 80_000, depth: 40_000, count: 6 }],
  minSeparation: 20_000,
  alignTo: "grid",
  grid: 5000,
  strategy: "maximiseCount"
});

Keep the tower the client already placed and fill round it (both)

ts
const input = {
  unit: "mm" as const,
  boundary: site,
  setback: 6000,
  // count 4 includes the pinned instances, so this asks for what is missing
  footprints: [{ id: "tower", width: 28_000, depth: 22_000, count: 4 }] as FootprintSpec[],
  fixed: pinned,
  minSeparation: 16_000,
  strategy: "maximiseCount" as const
};
// The pinned towers come back with `fixed: true`; the notes say what did not fit.
return footprintPlacement(input);