Appearance
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 for | Not for |
|---|---|
| Seating rectangles or plate outlines in a boundary: towers, sheds, villas, plant rooms, kiosks | True irregular nesting; a plate outline is seated by its bounding rectangle |
| Setbacks, keep-clear zones, a minimum separation and edge or grid alignment | A guaranteed optimum — the search is greedy and budgeted |
Capacity questions: strategy: "maximiseCount" then read placed.length | Anything 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, inunit, any winding.setback?:number | number[]— Inward setback: one number for every edge, or one per boundary-ring edge (setback[i]applies to the edge leavingboundary.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 inplacedfirst, verbatim, withfixed: true, and counts against its spec'scount. It is an obstacle for everything else —minSeparationis 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, inunit. Default 0. The dominant capacity lever.0lets 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-alignedgridlattice."none"tries axis alignment only, at a step derived from the footprint size.grid?:number— Lattice pitch foralignTo: "grid", inunit. 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 nearestanchorEdgeIndexfirst, which is how a frontage preference is expressed.anchorEdgeIndex?:number— Buildable-ring edge index thatanchor: "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 unlessringis given.depth?:number— Extent along the footprint's own z axis. Required unlessringis 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 anglesalignToimplies 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 infootprintsthis 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_VERSIONunit: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:numbercoverageRatio:number— Placed footprint area over buildable area.0when there is no buildable area.notes:string[]— Plain statements about what happened: collapsed setbacks, counts, budget.
PlacedFootprint
id:string— Instance identity,<footprintId>#<n>,nfrom 1.footprintId:stringring:PlanPoint[]— The seated outline in world plan coordinates, wound as the plate was.centre:PlanPoint— Centre of the seated bounding rectangle.rotationDeg:numberarea:number— Area ofring, inunit².fixed:boolean— True when the caller pinned this instance throughfixed.
UnplacedFootprint
id:stringfootprintId:stringreason:UnplacedReason
Functions
footprintPlacement(input)
Place footprints inside a boundary, honouring setbacks, separation, keep-clear zones, alignment and orientation.
- Parameters:
input:FootprintPlacementInput
- Returns:
FootprintPlacementOutput
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);Related
pack.corridorBand— take the road reserve out firstpack.rooms— subdivide a single footprint once it is seatedgeom.polygonBoolean— the mitre inset the setback uses