Appearance
pack.corridorBand
Reserve a strip along a centreline and carve it out of the regions you are about to pack.
One call. Give it centrelines (plan polylines, or edge chains off a boundary ring), a width, and the pieces you were going to pack. Get back the band as polygon rings, the pieces with the band removed, and the area the band took.
It does not pack, score, or decide where circulation should go. It answers "where does the strip go, and what is left" — the same question whether the strip is a corridor, a road reserve, a service easement or a setback.
Order of work: centreline → per-segment rectangles + corner patches + end caps → union → clip to the boundary → subtract from each piece → drop fragments under the sliver floor → recompute areas.
Pure module: no host, no DOM, no clock, no RNG. All lengths are in input.unit (areas in its square); the module computes natively on the caller's numbers and only its own tolerances, defined in millimetres, are scaled to that unit. Plan coordinates are x/z; Y is up and never appears here.
ts
// plugin worker
import { corridorBand, bandOnly, ringAreaOf } from "@snaptrude/plugin-toolkit/pack-corridor-band";
// agent run_worker
modules.pack.corridorBand.corridorBand(...)
modules.pack.corridorBand.bandOnly(...)
modules.pack.corridorBand.ringAreaOf(...)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 |
|---|---|
| Reserving a strip before packing: a corridor, a road reserve, a service easement, a setback | Finding where circulation should go; the centreline is an input |
Asking what width a plate can afford, with probeMaxWidth | Grading corridor quality — this returns geometry and areas, not a score |
| Clipping a finished set of pieces and reporting the area each one lost | A general polygon offset: it strokes a chain, not a ring |
Inputs and outputs
The shapes every function below speaks. Each appears once; a field's note says what changing it does to the result.
CorridorBandInput
unit:LengthUnit— The length unit every length in this input is in and every length in the output is written in. Areas are in its square. Validated first.paths?:CorridorPath[]— Centrelines given as coordinates.edges?:CorridorEdgeChain[]— Centrelines given as boundary edge indices. Requiresboundary. Resolved into paths before anything else happens, so everything downstream behaves identically.width?:number— Band thickness for every path that does not override it. Must be > 0 wherever a path has no own width.align?:BandAlign— Default side for every path. Defaultcenter.capStyle?:CapStyle— Default free-end treatment. Defaultflat.squareextends each free end by half a width;roundcaps it with a polygonised half disc. A closed centreline has no free ends and ignores this.joinStyle?:JoinStyle— Default corner treatment. Defaultmitre.mitrefills the outer corner to the apex (bevelling pastmitreLimitRatio),bevelcuts it straight across,roundarcs it.mitreLimitRatio?:number— Mitre apex distance allowed, as a multiple of the width. Default 4.roundSegments?:number— Segments per round cap or round join. Default 8.extendToBoundary?:number— Push every free end of every open centreline out along its own direction by this much before building the band. With aboundarygiven, the overshoot is clipped away, so a centreline that stops short of the boundary still reaches it. Default 0.boundary?:Polygon— Clip the band to this polygon. The band never leaves it. A centreline on a boundary edge withcenteralign therefore loses its outer half — the carved width iswidth / 2. Holes in the boundary are subtracted from the band too.pieces?:CorridorPiece[]— The regions to carve. Omit or pass[]to get the band only.minPieceArea?:number— Remainder fragments under this area are dropped. Default 0 keeps every fragment. A piece whose every fragment is dropped lands indroppedPieceswith reasonbelow-min-areaand its area indroppedArea.probeMaxWidth?:boolean— Run the max-width probe. Default true when pieces are given.falsereturnsmaxWidth: nulland saves the bisection.
CorridorPath
One centreline. Repeat the first point at the end for a closed loop.
id:string— Caller's handle. Comes back in error messages and notes.pts:PlanPoint[]— The centreline in plan. Two points minimum; three for a closed loop.width?:number— Band thickness for this path. Overrides the input-levelwidth. Must be > 0.align?:BandAlign— Which side of the centreline the strip sits on.center(default) splits the width either side of the line;left/rightputs all of it on one side, which is how a setback strip stays inside its boundary edge.capStyle?:CapStyle— Overrides the input-levelcapStyle.joinStyle?:JoinStyle— Overrides the input-leveljoinStyle.
CorridorEdgeChain
A centreline named by edges of the boundary ring instead of by coordinates. Edge i runs from boundary.ring[i] to boundary.ring[i + 1].
id:stringboundaryEdgeIndices:number[]— Indices into the boundary ring's edges. Order does not matter; runs of consecutive indices (wrapping at the seam) become one centreline each.width?:numberalign?:BandAligncapStyle?:CapStylejoinStyle?:JoinStyle
CorridorPiece
A region about to be packed. Holes are kept.
id:stringring:Ringholes?:Ring[]
CorridorBandOutput
contractVersion:typeof CORRIDOR_BAND_CONTRACT_VERSIONunit:LengthUnit— Echo ofinput.unit: every length below is in it, every area in its square.band:CorridorBandBandpieces:CarvedPiece[]— One entry per surviving input piece, in input order.droppedPieces:{ id: string; reason: DroppedPieceReason }[]— Pieces the band ate. Non-empty means the width is too aggressive.carvedArea:number— Band ∩ pieces. This is what the packer's denominator loses.remainingArea:number— Sum of the surviving piece areas.droppedArea:number— Area lost to the sliver floor, plus the area of every dropped piece.maxWidth:number | null— Largest width, applied to every path in proportion, at which no piece is dropped.nullwhen there are no pieces or the probe was turned off. Capped at twice the requested width (a note says so when it hits the cap).notes:string[]— Diagnostics: clipping, bevelled joins, fragmenting pieces, a capped probe.
CorridorBandBand
rings:Ring[]— Every ring of the band: outer rings counter-clockwise, holes clockwise. A ring corridor's band has a hole, and that hole is what keeps the region it encircles intact.polygons:Polygon[]— The same geometry grouped, so a consumer never has to guess which hole belongs to which ring.area:number— Band area after the boundary clip.
CarvedPiece
id:stringrings:Ring[]— Outer ring of every surviving fragment, counter-clockwise.holes:Ring[]— Every surviving hole, clockwise.fragments:Polygon[]— The fragments grouped: each one ring with its own holes. Feed these to a packer.area:number— Area after the carve.areaBefore:number— Area before the carve.
Functions
corridorBand(input)
Build the band along the given centrelines and carve it out of the pieces.
- Parameters:
input:CorridorBandInput
- Returns:
CorridorBandOutput - Throws:
CorridorBandError— on a missing or non-positive width, a self-intersecting centreline or piece ring, an edge chain with no boundary, a duplicate id, or a band that consumes every piece.
Effect on output: band is the strip after the boundary clip; pieces are the inputs minus the band, holes preserved, one entry per fragment in fragments; carvedArea is band ∩ pieces, which is what a packer's denominator loses. Area is conserved: remainingArea + carvedArea + droppedArea == Σ areaBefore.
bandOnly(input)
The band on its own, with no pieces. Sugar for corridorBand when the caller only wants the strip — a drawn overlay, or an input to another carve.
- Parameters:
input:Omit<CorridorBandInput, "pieces">
- Returns:
CorridorBandBand
ringAreaOf(ring)
Area of the ring, for callers checking conservation without importing the geometry helpers. Signed: positive counter-clockwise.
- Parameters:
ring:Ring
- Returns:
number
Examples
Reserve a 1.8 m spine and hand the remainder to a packer (both)
ts
return corridorBand({
unit: "mm",
paths: [{ id: "spine", pts: seam }],
width: 1800,
boundary: plate,
pieces: zones,
minPieceArea: 4_000_000
});Ask what width the plate can afford before committing (agent)
ts
const probe = corridorBand({
unit: "mm",
paths: [{ id: "spine", pts: seam }],
width: 1800,
boundary: plate,
pieces: zones,
probeMaxWidth: true
});
// The engine's own clamp, backed off a hair so the carve is not marginal.
return Math.min(2400, (probe.maxWidth ?? 1800) * 0.98);A primary spine plus secondary ribs (both)
ts
const spine = corridorBand({
unit: "mm",
paths: [{ id: "spine", pts: mainAxis }],
width: 2400,
boundary: plate,
pieces: zones
});
const remainder: CorridorPiece[] = spine.pieces.flatMap((p) =>
p.fragments.map((f, i) => ({
id: `${p.id}#${i}`,
ring: f.ring,
holes: f.holes
}))
);
return corridorBand({
unit: "mm",
paths: ribs.map((pts, i) => ({ id: `rib${i}`, pts })),
width: 1500,
boundary: plate,
pieces: remainder
});Not a corridor at all — a road reserve across a site (both)
ts
return corridorBand({
unit: "mm",
paths: [{ id: "road", pts: centreline, capStyle: "square" }],
width: 7200,
boundary: site,
pieces: [{ id: "site", ring: site.ring, holes: site.holes }]
});Errors
| Condition | Result |
|---|---|
corridorBand() — a missing or non-positive width, a self-intersecting centreline or piece ring, an edge chain with no boundary, a duplicate id, or a band that consumes every piece. | throws CorridorBandError |
| a 6° corner would throw the mitre apex miles out; mitreLimitRatio 2 bevels it and says so in the notes | pinned by mitre-limit-bevels-a-sharp-corner |
| a zero width throws instead of returning an empty band | pinned by zero-width-error |
| a centreline that crosses itself throws: the joint patches there would be wrong | pinned by self-intersecting-path-error |
| a band that eats every piece throws rather than silently returning the input uncarved | pinned by all-pieces-consumed-error |
| edge indices with no boundary to resolve against throw | pinned by edges-without-boundary-error |
Related
pack.corridorRuns— fill the bands this module leaves behindpack.footprintPlacement— seat footprints in the remainder piecesgeom.polygonBoolean— the clipper the carve is built on