Skip to content

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 forNot for
Reserving a strip before packing: a corridor, a road reserve, a service easement, a setbackFinding where circulation should go; the centreline is an input
Asking what width a plate can afford, with probeMaxWidthGrading corridor quality — this returns geometry and areas, not a score
Clipping a finished set of pieces and reporting the area each one lostA 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. Requires boundary. 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. Default center.
  • capStyle?: CapStyle — Default free-end treatment. Default flat. square extends each free end by half a width; round caps it with a polygonised half disc. A closed centreline has no free ends and ignores this.
  • joinStyle?: JoinStyle — Default corner treatment. Default mitre. mitre fills the outer corner to the apex (bevelling past mitreLimitRatio), bevel cuts it straight across, round arcs 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 a boundary given, 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 with center align therefore loses its outer half — the carved width is width / 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 in droppedPieces with reason below-min-area and its area in droppedArea.
  • probeMaxWidth?: boolean — Run the max-width probe. Default true when pieces are given. false returns maxWidth: null and 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-level width. 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/right puts all of it on one side, which is how a setback strip stays inside its boundary edge.
  • capStyle?: CapStyle — Overrides the input-level capStyle.
  • joinStyle?: JoinStyle — Overrides the input-level joinStyle.

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: string
  • boundaryEdgeIndices: 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?: number
  • align?: BandAlign
  • capStyle?: CapStyle
  • joinStyle?: JoinStyle

CorridorPiece ​

A region about to be packed. Holes are kept.

  • id: string
  • ring: Ring
  • holes?: Ring[]

CorridorBandOutput ​

  • contractVersion: typeof CORRIDOR_BAND_CONTRACT_VERSION
  • unit: LengthUnit — Echo of input.unit: every length below is in it, every area in its square.
  • band: CorridorBandBand
  • pieces: 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. null when 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: string
  • rings: 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:
  • 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.


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 ​

ConditionResult
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 notespinned by mitre-limit-bevels-a-sharp-corner
a zero width throws instead of returning an empty bandpinned by zero-width-error
a centreline that crosses itself throws: the joint patches there would be wrongpinned by self-intersecting-path-error
a band that eats every piece throws rather than silently returning the input uncarvedpinned by all-pieces-consumed-error
edge indices with no boundary to resolve against throwpinned by edges-without-boundary-error