Appearance
geom.polygonBoolean
Plan-polygon booleans and offsetting: set operations on regions with holes, stroking a centreline into a band, and the per-edge mitre inset a setback is. Primitives (area, winding, containment, ring cleaning, self-intersection) come from geom/polygon.ts; this file only adds what needs an arrangement.
Pure modules may not take a dependency, so the polygon clipper the corridor band needs lives here. It is an arrangement clipper, not a sweep line:
- snap every input vertex to a 1e-6 mm grid (scaled to the caller's unit), 2. split every edge at every vertex and proper crossing that lies on it, 3. drop coincident duplicates so a shared edge is one segment, 4. classify each surviving segment by sampling the winding number of both operands a hair to its left and to its right, 5. keep the segments whose two sides disagree, directed so the result lies on the left, and trace rings by always taking the next edge clockwise.
That is O(E·P) rather than O(E log E), and it is exact about coincident and collinear edges — which is what a band built from abutting rectangles is made of. It is deliberately not a general-purpose robust clipper: see the "Limitations" note at the bottom of this file.
Units: every length is in whatever unit the caller's coordinates are in; the algorithms compute natively on the caller's numbers. The module's own grid, tolerances and probe steps are defined in millimetres and scaled with constMm to the optional trailing unit parameter (default "mm") of every function that uses one. Pass the unit your coordinates are in.
Pure module: no host, no DOM, no clock, no RNG. Plan coordinates are x/z, Y is up and never appears here.
ts
// plugin worker
import { polygonSetArea, normalizePolygon, pointInPolygons, interiorPointOfRing, polylineSelfIntersects, booleanOp } from "@snaptrude/plugin-toolkit/geom-polygon-boolean";
// agent run_worker
modules.geom.polygonBoolean.polygonSetArea(...)
modules.geom.polygonBoolean.normalizePolygon(...)
modules.geom.polygonBoolean.pointInPolygons(...)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 |
|---|---|
| union, intersection, difference and xor over plan regions with holes | Primitives — area, winding, containment, cleaning — see geom.polygon |
| Stroking a centreline into a band with mitre / round / bevel joins and flat / square / round caps | Huge or badly conditioned inputs; the clipper is an O(E·P) arrangement, not a robust sweep line |
Per-edge mitre setbacks (insetRing) that survive edges shorter than the setback, with a named reason when the inset fails | Curves; every input is a polygon |
Inputs and outputs
The shapes every function below speaks. Each appears once; a field's note says what changing it does to the result.
BandBuildOptions
width:number— Total band thickness, in the caller's unit. Must be > 0.unit?:LengthUnit— Unit the centreline andwidthare in. Default"mm". Tolerances are defined in mm and scaled to it.align?:BandAlign—center(default) splits the width either side;left/rightputs it all on one side.capStyle?:CapStyle— Free ends of an open centreline. Defaultflat.joinStyle?:JoinStyle— Interior corners. Defaultmitre.mitreLimitRatio?:number— Mitre apex distance allowed, as a multiple of the width. Beyond it the join bevels. Default 4.roundSegments?:number— Segments per round cap or round join. Default 8.
InsetRingResult
ring:PlanPoint[]— The inset ring, or[]when the inset failed.failure:InsetFailure | null
Functions
polygonSetArea(polys)
Unsigned area of a set of polygons. Overlaps are counted twice.
- Parameters:
polys:Polygon[]
- Returns:
number
normalizePolygon(poly, unit)
Outer ring counter-clockwise, holes clockwise, every ring cleaned. Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.
- Parameters:
poly:Polygonunit?:LengthUnit— Default"mm".
- Returns:
Polygon
pointInPolygons(polys, p)
Non-zero winding test against a normalised polygon set (outer rings CCW, holes CW). A point on a boundary is undefined — callers sample off it.
- Parameters:
polys:Polygon[]p:PlanPoint
- Returns:
boolean
interiorPointOfRing(ring, unit)
A representative point strictly inside the ring (any winding). The probe step is defined in mm and scaled to unit; pass the unit your coordinates are in.
- Parameters:
ring:Ringunit?:LengthUnit— Default"mm".
- Returns:
PlanPoint
polylineSelfIntersects(ptsIn, unit)
True when two non-adjacent segments of the open polyline cross or overlap. Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.
- Parameters:
ptsIn:PlanPoint[]unit?:LengthUnit— Default"mm".
- Returns:
boolean
booleanOp(aIn, bIn, op, unit)
a op b, both as polygon sets with holes. Result is normalised: outer rings counter-clockwise, holes clockwise, vertices canonically ordered.
The snap grid and tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.
- Parameters:
aIn:Polygon[]bIn:Polygon[]op:BooleanOpNameunit?:LengthUnit— Default"mm".
- Returns:
Polygon[] - Throws:
PolygonBooleanError— when a traced ring will not close.
unionPolygons(polys, unit)
Union of a set of polygons. Overlaps are merged, touching edges dissolve. Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.
- Parameters:
polys:Polygon[]unit?:LengthUnit— Default"mm".
- Returns:
Polygon[]
intersectPolygons(a, b, unit)
a ∩ b. Tolerances are defined in mm and scaled to unit.
- Parameters:
a:Polygon[]b:Polygon[]unit?:LengthUnit— Default"mm".
- Returns:
Polygon[]
subtractPolygons(a, b, unit)
a − b. Holes in a survive; holes in b protect what they enclose. Tolerances are defined in mm and scaled to unit.
- Parameters:
a:Polygon[]b:Polygon[]unit?:LengthUnit— Default"mm".
- Returns:
Polygon[]
bandFromPolyline(ptsIn, opts)
Offset a plan centreline into a band: one rectangle per segment, a patch at every corner, a cap at every free end, all unioned so overlaps are counted once. A closed centreline (first point repeated last) gets no caps and a corner patch at the seam, which is how a ring corridor keeps its hole.
Tolerances are defined in mm and scaled to opts.unit; pass the unit your coordinates are in.
- Parameters:
ptsIn:PlanPoint[]opts:BandBuildOptions
- Returns:
BandBuildResult - Throws:
BandBuildError— on a zero width or fewer than two distinct points.
insetRing(ring, distances, unit)
Offset each edge of a ring inwards by its own distance, with sharp mitres. distances[i] applies to the edge leaving vertex i; a negative distance grows the ring outwards (used to set a courtyard back).
Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.
- Parameters:
ring:PlanPoint[]distances:number[]unit?:LengthUnit— Default"mm".
- Returns:
InsetRingResult
Effect: where two edges of DIFFERENT distance meet at a near-straight vertex the mitre is replaced by a perpendicular step, so each edge keeps its own setback right up to the corner instead of the deeper one swallowing the run. An edge SHORTER than the setbacks eating it from both ends disappears — its offset segment runs backwards — and is dropped from the ring rather than failing it, which is what lets a chamfered or surveyed boundary take a setback deeper than its shortest edge. What is left is peeled apart at its self-crossings and the one surviving lobe returned; a ring the setback pinches into SEVERAL lobes returns ring: [] with failure: "pinched", and one it turns inside out or eats entirely returns "inverted" / "collapsed". Callers must check failure.
Examples
What is left of a plate once the corridor strip is taken out (both)
ts
const band = bandFromPolyline(centreline, { width, capStyle: "square" });
return subtractPolygons([plate], band.polygons);A per-edge setback, with the failure named when the ring collapses (both)
ts
const res = insetRing(siteRing, setbacks);
// An over-large inward offset otherwise starts growing again, so the reason
// must be read rather than the ring trusted.
return res.failure === null ? res.ring : [];Errors
| Condition | Result |
|---|---|
booleanOp() — a traced ring will not close. | throws PolygonBooleanError |
bandFromPolyline() — a zero width or fewer than two distinct points. | throws BandBuildError |
Related
geom.polygon— the primitives this file builds onpack.corridorBand— the main caller of the band and carve path