@faicad/sheetmetal 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/allowanceFns.d.ts +42 -0
  2. package/dist/allowanceFns.d.ts.map +1 -0
  3. package/dist/allowanceFns.js +63 -0
  4. package/dist/allowanceFns.js.map +1 -0
  5. package/dist/api.d.ts +312 -0
  6. package/dist/api.d.ts.map +1 -0
  7. package/dist/api.js +367 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/authorFns.d.ts +118 -0
  10. package/dist/authorFns.d.ts.map +1 -0
  11. package/dist/authorFns.js +562 -0
  12. package/dist/authorFns.js.map +1 -0
  13. package/dist/bendTableFns.d.ts +87 -0
  14. package/dist/bendTableFns.d.ts.map +1 -0
  15. package/dist/bendTableFns.js +345 -0
  16. package/dist/bendTableFns.js.map +1 -0
  17. package/dist/compat.d.ts +90 -0
  18. package/dist/compat.d.ts.map +1 -0
  19. package/dist/compat.js +99 -0
  20. package/dist/compat.js.map +1 -0
  21. package/dist/contourFlangeFns.d.ts +122 -0
  22. package/dist/contourFlangeFns.d.ts.map +1 -0
  23. package/dist/contourFlangeFns.js +403 -0
  24. package/dist/contourFlangeFns.js.map +1 -0
  25. package/dist/cutoutFns.d.ts +95 -0
  26. package/dist/cutoutFns.d.ts.map +1 -0
  27. package/dist/cutoutFns.js +300 -0
  28. package/dist/cutoutFns.js.map +1 -0
  29. package/dist/dxfFns.d.ts +48 -0
  30. package/dist/dxfFns.d.ts.map +1 -0
  31. package/dist/dxfFns.js +236 -0
  32. package/dist/dxfFns.js.map +1 -0
  33. package/dist/facade.d.ts +449 -0
  34. package/dist/facade.d.ts.map +1 -0
  35. package/dist/facade.js +535 -0
  36. package/dist/facade.js.map +1 -0
  37. package/dist/featureTreeFns.d.ts +76 -0
  38. package/dist/featureTreeFns.d.ts.map +1 -0
  39. package/dist/featureTreeFns.js +190 -0
  40. package/dist/featureTreeFns.js.map +1 -0
  41. package/dist/foldFns.d.ts +83 -0
  42. package/dist/foldFns.d.ts.map +1 -0
  43. package/dist/foldFns.js +744 -0
  44. package/dist/foldFns.js.map +1 -0
  45. package/dist/foreignUnfoldFns.d.ts +62 -0
  46. package/dist/foreignUnfoldFns.d.ts.map +1 -0
  47. package/dist/foreignUnfoldFns.js +854 -0
  48. package/dist/foreignUnfoldFns.js.map +1 -0
  49. package/dist/formFns.d.ts +58 -0
  50. package/dist/formFns.d.ts.map +1 -0
  51. package/dist/formFns.js +300 -0
  52. package/dist/formFns.js.map +1 -0
  53. package/dist/hemFns.d.ts +17 -0
  54. package/dist/hemFns.d.ts.map +1 -0
  55. package/dist/hemFns.js +212 -0
  56. package/dist/hemFns.js.map +1 -0
  57. package/dist/index.d.ts +43 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +32 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/internal.d.ts +27 -0
  62. package/dist/internal.d.ts.map +1 -0
  63. package/dist/internal.js +38 -0
  64. package/dist/internal.js.map +1 -0
  65. package/dist/jogFns.d.ts +18 -0
  66. package/dist/jogFns.d.ts.map +1 -0
  67. package/dist/jogFns.js +180 -0
  68. package/dist/jogFns.js.map +1 -0
  69. package/dist/loftedFlangeFns.d.ts +23 -0
  70. package/dist/loftedFlangeFns.d.ts.map +1 -0
  71. package/dist/loftedFlangeFns.js +306 -0
  72. package/dist/loftedFlangeFns.js.map +1 -0
  73. package/dist/materials.d.ts +16 -0
  74. package/dist/materials.d.ts.map +1 -0
  75. package/dist/materials.js +44 -0
  76. package/dist/materials.js.map +1 -0
  77. package/dist/miterFns.d.ts +31 -0
  78. package/dist/miterFns.d.ts.map +1 -0
  79. package/dist/miterFns.js +128 -0
  80. package/dist/miterFns.js.map +1 -0
  81. package/dist/nestFns.d.ts +116 -0
  82. package/dist/nestFns.d.ts.map +1 -0
  83. package/dist/nestFns.js +517 -0
  84. package/dist/nestFns.js.map +1 -0
  85. package/dist/polygonFns.d.ts +144 -0
  86. package/dist/polygonFns.d.ts.map +1 -0
  87. package/dist/polygonFns.js +324 -0
  88. package/dist/polygonFns.js.map +1 -0
  89. package/dist/reliefFns.d.ts +44 -0
  90. package/dist/reliefFns.d.ts.map +1 -0
  91. package/dist/reliefFns.js +367 -0
  92. package/dist/reliefFns.js.map +1 -0
  93. package/dist/reportFns.d.ts +33 -0
  94. package/dist/reportFns.d.ts.map +1 -0
  95. package/dist/reportFns.js +71 -0
  96. package/dist/reportFns.js.map +1 -0
  97. package/dist/tabFns.d.ts +43 -0
  98. package/dist/tabFns.d.ts.map +1 -0
  99. package/dist/tabFns.js +179 -0
  100. package/dist/tabFns.js.map +1 -0
  101. package/dist/types.d.ts +629 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/types.js +2 -0
  104. package/dist/types.js.map +1 -0
  105. package/dist/unfoldFns.d.ts +120 -0
  106. package/dist/unfoldFns.d.ts.map +1 -0
  107. package/dist/unfoldFns.js +766 -0
  108. package/dist/unfoldFns.js.map +1 -0
  109. package/dist/validateFns.d.ts +18 -0
  110. package/dist/validateFns.d.ts.map +1 -0
  111. package/dist/validateFns.js +196 -0
  112. package/dist/validateFns.js.map +1 -0
  113. package/package.json +45 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * 2D simple-polygon geometry for true-shape (no-fit-polygon) nesting: an ordered
3
+ * vertex loop plus the overlap predicate that is the correctness backbone of the
4
+ * packer. A false negative here would let two parts physically collide on the laser
5
+ * bed, so the predicate is exhaustive: two simple polygons overlap iff any of their
6
+ * edges cross OR one polygon contains a vertex of the other (the latter catches full
7
+ * containment, where no edges cross).
8
+ *
9
+ * Sheet-metal flat outlines are straight-edged polylines; an arc edge (rare in a
10
+ * developed pattern) is approximated by its two endpoints — the same pre-existing
11
+ * assumption the DXF/SVG writers and the bbox nester make.
12
+ */
13
+ import { type Result } from '@faicad/faijs/brepjs-compat';
14
+ import type { Wire } from '@faicad/faijs/brepjs-compat';
15
+ import type { FlatPattern } from './types.js';
16
+ /** A 2D point as a plain `[x, y]` coordinate tuple. */
17
+ export type Pt2 = [number, number];
18
+ /** A simple (non-self-intersecting) polygon as an ordered, non-closing vertex loop. */
19
+ export type Polygon = Pt2[];
20
+ /**
21
+ * Ordered vertex loop of a closed outline wire — one vertex per edge start point, the
22
+ * same 2D read the DXF writer (`outlinePoints`) and the bbox nester (`patternBbox`)
23
+ * use. The loop is NOT closed (the last vertex is not repeated); edges are taken as
24
+ * `v[i] → v[(i+1) % n]`.
25
+ *
26
+ * @param outline - the closed outline wire to read into a vertex loop.
27
+ * @returns a `Result<Polygon>` — `ok` with the ordered vertex loop, or `err` with a
28
+ * `validationError` when the outline is empty or has fewer than 3 vertices.
29
+ */
30
+ export declare function wireToPolygon(outline: Wire): Result<Polygon>;
31
+ /** A bend line in the developed plane: a segment plus its fold direction. */
32
+ export interface FlatPatternBendLine {
33
+ from: Pt2;
34
+ to: Pt2;
35
+ direction: 'up' | 'down';
36
+ }
37
+ /** A flat pattern reduced to serializable 2D polylines for a developed-view overlay. */
38
+ export interface FlatPatternPolylines {
39
+ /** Closed outer boundary as an ordered, non-closing vertex loop. */
40
+ outline: Polygon;
41
+ /** Interior cutout loops (holes / slots / polygon cutouts). */
42
+ holes: Polygon[];
43
+ /** Bend lines, each with its fold direction. */
44
+ bendLines: FlatPatternBendLine[];
45
+ /** Open form-feature cut paths in the blank (e.g. louver/lance three-sided cuts). */
46
+ formCuts: Polygon[];
47
+ /** Form-feature hinge lines (the uncut fold edge a louver/lance forms up along). */
48
+ formHinges: {
49
+ from: Pt2;
50
+ to: Pt2;
51
+ }[];
52
+ }
53
+ /**
54
+ * Reduce a {@link FlatPattern} to serializable 2D polylines (outline, holes, bend
55
+ * lines) for a developed-pattern overlay. Best-effort: a wire that fails to read
56
+ * is dropped rather than throwing, so a partial pattern still yields what it can.
57
+ * Arc edges are approximated by endpoints, the same assumption {@link wireToPolygon}
58
+ * and the DXF writer make.
59
+ *
60
+ * @param pattern - the flat pattern to reduce to 2D polylines.
61
+ * @returns a {@link FlatPatternPolylines} with the outline, holes, bend lines,
62
+ * form cuts, and form hinges as plain 2D vertex loops.
63
+ */
64
+ export declare function flatPatternToPolylines(pattern: FlatPattern): FlatPatternPolylines;
65
+ /**
66
+ * Signed area (CCW positive) — used to normalise orientation and reject degenerates.
67
+ *
68
+ * @param poly - the polygon to measure.
69
+ * @returns the signed area in square units: positive for counter-clockwise vertex
70
+ * order, negative for clockwise.
71
+ */
72
+ export declare function signedArea(poly: Polygon): number;
73
+ /**
74
+ * Axis-aligned bounds of a polygon.
75
+ *
76
+ * @param poly - the polygon to bound.
77
+ * @returns the axis-aligned bounding box as `[minX, minY, maxX, maxY]`.
78
+ */
79
+ export declare function polygonBounds(poly: Polygon): [number, number, number, number];
80
+ /**
81
+ * Rotate every vertex (CCW, degrees, about the origin), then translate by (dx, dy).
82
+ *
83
+ * @param poly - the polygon to transform.
84
+ * @param dx - the translation along X applied after rotation.
85
+ * @param dy - the translation along Y applied after rotation.
86
+ * @param rotationDeg - the counter-clockwise rotation angle in degrees.
87
+ * @returns a new `Polygon` with every vertex rotated then translated.
88
+ */
89
+ export declare function transformPolygon(poly: Polygon, dx: number, dy: number, rotationDeg: number): Polygon;
90
+ /**
91
+ * Ray-cast point-in-polygon: points strictly inside return true; behaviour for a
92
+ * point exactly on the boundary is undefined (the overlap predicate handles edge
93
+ * contact separately via segment-intersection).
94
+ *
95
+ * @param poly - the polygon to test against.
96
+ * @param x - the point's X coordinate.
97
+ * @param y - the point's Y coordinate.
98
+ * @returns `true` when the point is strictly inside the polygon.
99
+ */
100
+ export declare function pointInPolygon(poly: Polygon, x: number, y: number): boolean;
101
+ /**
102
+ * Proper-or-improper segment intersection (`p1p2` vs `p3p4`). Returns true when the
103
+ * segments cross OR touch (collinear overlap or a shared endpoint), so it is
104
+ * conservative for the overlap test — never a false negative.
105
+ *
106
+ * @param p1 - first endpoint of the first segment.
107
+ * @param p2 - second endpoint of the first segment.
108
+ * @param p3 - first endpoint of the second segment.
109
+ * @param p4 - second endpoint of the second segment.
110
+ * @returns `true` when the two segments cross or touch, `false` otherwise.
111
+ */
112
+ export declare function segmentsIntersect(p1: Pt2, p2: Pt2, p3: Pt2, p4: Pt2): boolean;
113
+ /**
114
+ * Do two simple polygons overlap? True iff any edge of `a` intersects any edge of
115
+ * `b`, or one polygon fully contains a vertex of the other. This pair of conditions
116
+ * is exhaustive for simple polygons: a disjoint pair has no crossing edges and no
117
+ * mutually-contained vertex; a touching-but-non-overlapping pair (shared edge/vertex,
118
+ * zero interior overlap) is caught by `segmentsIntersect` and so is reported as
119
+ * overlapping — callers wanting a clearance gap should inflate via {@link polygonsOverlapWithClearance}.
120
+ *
121
+ * Centroid containment is NOT sufficient on its own (a concave part's centroid can
122
+ * lie outside it), so a representative INTERIOR vertex of each polygon is tested for
123
+ * containment in the other — combined with the edge-crossing test this covers the
124
+ * nested-without-crossing case.
125
+ *
126
+ * @param a - the first polygon.
127
+ * @param b - the second polygon.
128
+ * @returns `true` when the polygons overlap (including edge touch), `false` when they
129
+ * are disjoint.
130
+ */
131
+ export declare function polygonsOverlap(a: Polygon, b: Polygon): boolean;
132
+ /**
133
+ * Overlap test honoring a clearance gap: the two polygons must stay at least
134
+ * `clearance` apart. Implemented by testing raw overlap and, when disjoint, the
135
+ * minimum edge-to-edge distance against the clearance. `clearance <= 0` is the plain
136
+ * {@link polygonsOverlap}.
137
+ *
138
+ * @param a - the first polygon.
139
+ * @param b - the second polygon.
140
+ * @param clearance - the minimum required gap between the polygons.
141
+ * @returns `true` when the polygons overlap or come closer than `clearance`.
142
+ */
143
+ export declare function polygonsOverlapWithClearance(a: Polygon, b: Polygon, clearance: number): boolean;
144
+ //# sourceMappingURL=polygonFns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polygonFns.d.ts","sourceRoot":"","sources":["../src/polygonFns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,KAAK,MAAM,EAQZ,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,uDAAuD;AACvD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEnC,uFAAuF;AACvF,MAAM,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;AAI5B;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAc5D;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,GAAG,CAAC;IACV,EAAE,EAAE,GAAG,CAAC;IACR,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;CAC1B;AAED,wFAAwF;AACxF,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,gDAAgD;IAChD,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,qFAAqF;IACrF,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,oFAAoF;IACpF,UAAU,EAAE;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,EAAE,EAAE,GAAG,CAAA;KAAE,EAAE,CAAC;CACtC;AA2BD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,oBAAoB,CAwBjF;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAShD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAY7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CASpG;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAW3E;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,OAAO,CAe7E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAsB/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAI/F"}
@@ -0,0 +1,324 @@
1
+ /**
2
+ * 2D simple-polygon geometry for true-shape (no-fit-polygon) nesting: an ordered
3
+ * vertex loop plus the overlap predicate that is the correctness backbone of the
4
+ * packer. A false negative here would let two parts physically collide on the laser
5
+ * bed, so the predicate is exhaustive: two simple polygons overlap iff any of their
6
+ * edges cross OR one polygon contains a vertex of the other (the latter catches full
7
+ * containment, where no edges cross).
8
+ *
9
+ * Sheet-metal flat outlines are straight-edged polylines; an arc edge (rare in a
10
+ * developed pattern) is approximated by its two endpoints — the same pre-existing
11
+ * assumption the DXF/SVG writers and the bbox nester make.
12
+ */
13
+ import { ok, err, isOk, validationError, getEdges, curveStartPoint, curveEndPoint, } from '@faicad/faijs/brepjs-compat';
14
+ const EPS = 1e-9;
15
+ /**
16
+ * Ordered vertex loop of a closed outline wire — one vertex per edge start point, the
17
+ * same 2D read the DXF writer (`outlinePoints`) and the bbox nester (`patternBbox`)
18
+ * use. The loop is NOT closed (the last vertex is not repeated); edges are taken as
19
+ * `v[i] → v[(i+1) % n]`.
20
+ *
21
+ * @param outline - the closed outline wire to read into a vertex loop.
22
+ * @returns a `Result<Polygon>` — `ok` with the ordered vertex loop, or `err` with a
23
+ * `validationError` when the outline is empty or has fewer than 3 vertices.
24
+ */
25
+ export function wireToPolygon(outline) {
26
+ const edges = getEdges(outline);
27
+ if (edges.length === 0) {
28
+ return err(validationError('EMPTY_OUTLINE', 'outline wire has no edges to read'));
29
+ }
30
+ const poly = [];
31
+ for (const edge of edges) {
32
+ const p = curveStartPoint(edge);
33
+ poly.push([p[0], p[1]]);
34
+ }
35
+ if (poly.length < 3) {
36
+ return err(validationError('EMPTY_OUTLINE', 'outline polygon has fewer than 3 vertices'));
37
+ }
38
+ return ok(poly);
39
+ }
40
+ /**
41
+ * Read a wire as an OPEN polyline: every edge start plus the final edge's end,
42
+ * so the last vertex of a non-closing path (a form cut) isn't dropped the way
43
+ * {@link wireToPolygon} drops it for closed loops.
44
+ *
45
+ * Arc edges are linearised to their endpoints — the same assumption
46
+ * {@link wireToPolygon} and the DXF writer make. Form cuts are straight-sided
47
+ * today (louver/lance slits), so this is exact; a future curved form-feature cut
48
+ * would render as straight chords in the overlay until arc tessellation is added.
49
+ */
50
+ function wireToOpenPolyline(wire) {
51
+ const edges = getEdges(wire);
52
+ if (edges.length === 0)
53
+ return [];
54
+ const pts = edges.map((edge) => {
55
+ const p = curveStartPoint(edge);
56
+ return [p[0], p[1]];
57
+ });
58
+ const lastEdge = edges[edges.length - 1];
59
+ if (lastEdge) {
60
+ const e = curveEndPoint(lastEdge);
61
+ pts.push([e[0], e[1]]);
62
+ }
63
+ return pts;
64
+ }
65
+ /**
66
+ * Reduce a {@link FlatPattern} to serializable 2D polylines (outline, holes, bend
67
+ * lines) for a developed-pattern overlay. Best-effort: a wire that fails to read
68
+ * is dropped rather than throwing, so a partial pattern still yields what it can.
69
+ * Arc edges are approximated by endpoints, the same assumption {@link wireToPolygon}
70
+ * and the DXF writer make.
71
+ *
72
+ * @param pattern - the flat pattern to reduce to 2D polylines.
73
+ * @returns a {@link FlatPatternPolylines} with the outline, holes, bend lines,
74
+ * form cuts, and form hinges as plain 2D vertex loops.
75
+ */
76
+ export function flatPatternToPolylines(pattern) {
77
+ const outlineResult = wireToPolygon(pattern.outline);
78
+ const outline = isOk(outlineResult) ? outlineResult.value : [];
79
+ const holes = [];
80
+ for (const hole of pattern.holes) {
81
+ const holeResult = wireToPolygon(hole);
82
+ if (isOk(holeResult))
83
+ holes.push(holeResult.value);
84
+ }
85
+ const bendLines = pattern.bendLines.map((bend) => {
86
+ const start = curveStartPoint(bend.line);
87
+ const end = curveEndPoint(bend.line);
88
+ return {
89
+ from: [start[0], start[1]],
90
+ to: [end[0], end[1]],
91
+ direction: bend.direction,
92
+ };
93
+ });
94
+ const formCuts = pattern.formCuts.map(wireToOpenPolyline).filter((p) => p.length >= 2);
95
+ const formHinges = pattern.formHinges.map((hinge) => {
96
+ const start = curveStartPoint(hinge);
97
+ const end = curveEndPoint(hinge);
98
+ return { from: [start[0], start[1]], to: [end[0], end[1]] };
99
+ });
100
+ return { outline, holes, bendLines, formCuts, formHinges };
101
+ }
102
+ /**
103
+ * Signed area (CCW positive) — used to normalise orientation and reject degenerates.
104
+ *
105
+ * @param poly - the polygon to measure.
106
+ * @returns the signed area in square units: positive for counter-clockwise vertex
107
+ * order, negative for clockwise.
108
+ */
109
+ export function signedArea(poly) {
110
+ let a = 0;
111
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i, i += 1) {
112
+ const pi = poly[i];
113
+ const pj = poly[j];
114
+ if (pi === undefined || pj === undefined)
115
+ continue;
116
+ a += pj[0] * pi[1] - pi[0] * pj[1];
117
+ }
118
+ return a / 2;
119
+ }
120
+ /**
121
+ * Axis-aligned bounds of a polygon.
122
+ *
123
+ * @param poly - the polygon to bound.
124
+ * @returns the axis-aligned bounding box as `[minX, minY, maxX, maxY]`.
125
+ */
126
+ export function polygonBounds(poly) {
127
+ let minX = Infinity;
128
+ let minY = Infinity;
129
+ let maxX = -Infinity;
130
+ let maxY = -Infinity;
131
+ for (const p of poly) {
132
+ if (p[0] < minX)
133
+ minX = p[0];
134
+ if (p[0] > maxX)
135
+ maxX = p[0];
136
+ if (p[1] < minY)
137
+ minY = p[1];
138
+ if (p[1] > maxY)
139
+ maxY = p[1];
140
+ }
141
+ return [minX, minY, maxX, maxY];
142
+ }
143
+ /**
144
+ * Rotate every vertex (CCW, degrees, about the origin), then translate by (dx, dy).
145
+ *
146
+ * @param poly - the polygon to transform.
147
+ * @param dx - the translation along X applied after rotation.
148
+ * @param dy - the translation along Y applied after rotation.
149
+ * @param rotationDeg - the counter-clockwise rotation angle in degrees.
150
+ * @returns a new `Polygon` with every vertex rotated then translated.
151
+ */
152
+ export function transformPolygon(poly, dx, dy, rotationDeg) {
153
+ const rad = (rotationDeg * Math.PI) / 180;
154
+ const c = Math.cos(rad);
155
+ const s = Math.sin(rad);
156
+ return poly.map(([x, y]) => {
157
+ const rx = x * c - y * s;
158
+ const ry = x * s + y * c;
159
+ return [rx + dx, ry + dy];
160
+ });
161
+ }
162
+ /**
163
+ * Ray-cast point-in-polygon: points strictly inside return true; behaviour for a
164
+ * point exactly on the boundary is undefined (the overlap predicate handles edge
165
+ * contact separately via segment-intersection).
166
+ *
167
+ * @param poly - the polygon to test against.
168
+ * @param x - the point's X coordinate.
169
+ * @param y - the point's Y coordinate.
170
+ * @returns `true` when the point is strictly inside the polygon.
171
+ */
172
+ export function pointInPolygon(poly, x, y) {
173
+ let inside = false;
174
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i, i += 1) {
175
+ const pi = poly[i];
176
+ const pj = poly[j];
177
+ if (pi === undefined || pj === undefined)
178
+ continue;
179
+ const intersect = pi[1] > y !== pj[1] > y && x < ((pj[0] - pi[0]) * (y - pi[1])) / (pj[1] - pi[1]) + pi[0];
180
+ if (intersect)
181
+ inside = !inside;
182
+ }
183
+ return inside;
184
+ }
185
+ function orient(ax, ay, bx, by, cx, cy) {
186
+ return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
187
+ }
188
+ function onSegment(ax, ay, bx, by, px, py) {
189
+ return (Math.min(ax, bx) - EPS <= px &&
190
+ px <= Math.max(ax, bx) + EPS &&
191
+ Math.min(ay, by) - EPS <= py &&
192
+ py <= Math.max(ay, by) + EPS);
193
+ }
194
+ /**
195
+ * Proper-or-improper segment intersection (`p1p2` vs `p3p4`). Returns true when the
196
+ * segments cross OR touch (collinear overlap or a shared endpoint), so it is
197
+ * conservative for the overlap test — never a false negative.
198
+ *
199
+ * @param p1 - first endpoint of the first segment.
200
+ * @param p2 - second endpoint of the first segment.
201
+ * @param p3 - first endpoint of the second segment.
202
+ * @param p4 - second endpoint of the second segment.
203
+ * @returns `true` when the two segments cross or touch, `false` otherwise.
204
+ */
205
+ export function segmentsIntersect(p1, p2, p3, p4) {
206
+ const d1 = orient(p3[0], p3[1], p4[0], p4[1], p1[0], p1[1]);
207
+ const d2 = orient(p3[0], p3[1], p4[0], p4[1], p2[0], p2[1]);
208
+ const d3 = orient(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);
209
+ const d4 = orient(p1[0], p1[1], p2[0], p2[1], p4[0], p4[1]);
210
+ if (((d1 > EPS && d2 < -EPS) || (d1 < -EPS && d2 > EPS)) && ((d3 > EPS && d4 < -EPS) || (d3 < -EPS && d4 > EPS))) {
211
+ return true;
212
+ }
213
+ if (Math.abs(d1) <= EPS && onSegment(p3[0], p3[1], p4[0], p4[1], p1[0], p1[1]))
214
+ return true;
215
+ if (Math.abs(d2) <= EPS && onSegment(p3[0], p3[1], p4[0], p4[1], p2[0], p2[1]))
216
+ return true;
217
+ if (Math.abs(d3) <= EPS && onSegment(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]))
218
+ return true;
219
+ if (Math.abs(d4) <= EPS && onSegment(p1[0], p1[1], p2[0], p2[1], p4[0], p4[1]))
220
+ return true;
221
+ return false;
222
+ }
223
+ /**
224
+ * Do two simple polygons overlap? True iff any edge of `a` intersects any edge of
225
+ * `b`, or one polygon fully contains a vertex of the other. This pair of conditions
226
+ * is exhaustive for simple polygons: a disjoint pair has no crossing edges and no
227
+ * mutually-contained vertex; a touching-but-non-overlapping pair (shared edge/vertex,
228
+ * zero interior overlap) is caught by `segmentsIntersect` and so is reported as
229
+ * overlapping — callers wanting a clearance gap should inflate via {@link polygonsOverlapWithClearance}.
230
+ *
231
+ * Centroid containment is NOT sufficient on its own (a concave part's centroid can
232
+ * lie outside it), so a representative INTERIOR vertex of each polygon is tested for
233
+ * containment in the other — combined with the edge-crossing test this covers the
234
+ * nested-without-crossing case.
235
+ *
236
+ * @param a - the first polygon.
237
+ * @param b - the second polygon.
238
+ * @returns `true` when the polygons overlap (including edge touch), `false` when they
239
+ * are disjoint.
240
+ */
241
+ export function polygonsOverlap(a, b) {
242
+ for (let i = 0; i < a.length; i += 1) {
243
+ const a1 = a[i];
244
+ const a2 = a[(i + 1) % a.length];
245
+ if (a1 === undefined || a2 === undefined)
246
+ continue;
247
+ for (let j = 0; j < b.length; j += 1) {
248
+ const b1 = b[j];
249
+ const b2 = b[(j + 1) % b.length];
250
+ if (b1 === undefined || b2 === undefined)
251
+ continue;
252
+ if (segmentsIntersect(a1, a2, b1, b2))
253
+ return true;
254
+ }
255
+ }
256
+ // No edges cross: the only remaining overlap is full containment. If any vertex of
257
+ // one lies strictly inside the other, the polygons overlap. (With no crossings,
258
+ // containment of a single vertex implies containment of the whole polygon.)
259
+ for (const v of a) {
260
+ if (pointInPolygon(b, v[0], v[1]))
261
+ return true;
262
+ }
263
+ for (const v of b) {
264
+ if (pointInPolygon(a, v[0], v[1]))
265
+ return true;
266
+ }
267
+ return false;
268
+ }
269
+ /**
270
+ * Overlap test honoring a clearance gap: the two polygons must stay at least
271
+ * `clearance` apart. Implemented by testing raw overlap and, when disjoint, the
272
+ * minimum edge-to-edge distance against the clearance. `clearance <= 0` is the plain
273
+ * {@link polygonsOverlap}.
274
+ *
275
+ * @param a - the first polygon.
276
+ * @param b - the second polygon.
277
+ * @param clearance - the minimum required gap between the polygons.
278
+ * @returns `true` when the polygons overlap or come closer than `clearance`.
279
+ */
280
+ export function polygonsOverlapWithClearance(a, b, clearance) {
281
+ if (polygonsOverlap(a, b))
282
+ return true;
283
+ if (clearance <= EPS)
284
+ return false;
285
+ return minEdgeDistance(a, b) < clearance - EPS;
286
+ }
287
+ /** Minimum distance between any edge of `a` and any edge of `b` (both assumed disjoint). */
288
+ function minEdgeDistance(a, b) {
289
+ let min = Infinity;
290
+ for (let i = 0; i < a.length; i += 1) {
291
+ const a1 = a[i];
292
+ const a2 = a[(i + 1) % a.length];
293
+ if (a1 === undefined || a2 === undefined)
294
+ continue;
295
+ for (let j = 0; j < b.length; j += 1) {
296
+ const b1 = b[j];
297
+ const b2 = b[(j + 1) % b.length];
298
+ if (b1 === undefined || b2 === undefined)
299
+ continue;
300
+ const d = segmentSegmentDistance(a1, a2, b1, b2);
301
+ if (d < min)
302
+ min = d;
303
+ }
304
+ }
305
+ return min;
306
+ }
307
+ function segmentSegmentDistance(p1, p2, p3, p4) {
308
+ if (segmentsIntersect(p1, p2, p3, p4))
309
+ return 0;
310
+ return Math.min(pointSegmentDistance(p1, p3, p4), pointSegmentDistance(p2, p3, p4), pointSegmentDistance(p3, p1, p2), pointSegmentDistance(p4, p1, p2));
311
+ }
312
+ function pointSegmentDistance(p, a, b) {
313
+ const vx = b[0] - a[0];
314
+ const vy = b[1] - a[1];
315
+ const wx = p[0] - a[0];
316
+ const wy = p[1] - a[1];
317
+ const len2 = vx * vx + vy * vy;
318
+ let t = len2 <= EPS ? 0 : (wx * vx + wy * vy) / len2;
319
+ t = Math.max(0, Math.min(1, t));
320
+ const dx = p[0] - (a[0] + t * vx);
321
+ const dy = p[1] - (a[1] + t * vy);
322
+ return Math.hypot(dx, dy);
323
+ }
324
+ //# sourceMappingURL=polygonFns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polygonFns.js","sourceRoot":"","sources":["../src/polygonFns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAEL,EAAE,EACF,GAAG,EACH,IAAI,EACJ,eAAe,EACf,QAAQ,EACR,eAAe,EACf,aAAa,GACd,MAAM,6BAA6B,CAAC;AAUrC,MAAM,GAAG,GAAG,IAAI,CAAC;AAEjB;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,OAAa;IACzC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,eAAe,CAAC,eAAe,EAAE,mCAAmC,CAAC,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,IAAI,GAAY,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,OAAO,GAAG,CAAC,eAAe,CAAC,eAAe,EAAE,2CAA2C,CAAC,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;AAClB,CAAC;AAuBD;;;;;;;;;GASG;AACH,SAAS,kBAAkB,CAAC,IAAU;IACpC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,GAAG,GAAU,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACpC,MAAM,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACzC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QAClC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAoB;IACzD,MAAM,aAAa,GAAG,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACjC,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC,UAAU,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACrD,CAAC;IACD,MAAM,SAAS,GAA0B,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACtE,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,OAAO;YACL,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1B,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;YACpB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IACvF,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAClD,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QACjC,OAAO,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAQ,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAQ,EAAE,CAAC;IAC5E,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;AAC7D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACpE,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACnB,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACnB,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;YAAE,SAAS;QACnD,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa;IACzC,IAAI,IAAI,GAAG,QAAQ,CAAC;IACpB,IAAI,IAAI,GAAG,QAAQ,CAAC;IACpB,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC;IACrB,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC;IACrB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;YAAE,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;YAAE,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;YAAE,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;YAAE,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAa,EAAE,EAAU,EAAE,EAAU,EAAE,WAAmB;IACzF,MAAM,GAAG,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;IAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;QACzB,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,CAAQ,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,IAAa,EAAE,CAAS,EAAE,CAAS;IAChE,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACpE,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACnB,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACnB,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;YAAE,SAAS;QACnD,MAAM,SAAS,GACb,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3F,IAAI,SAAS;YAAE,MAAM,GAAG,CAAC,MAAM,CAAC;IAClC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,MAAM,CAAC,EAAU,EAAE,EAAU,EAAE,EAAU,EAAE,EAAU,EAAE,EAAU,EAAE,EAAU;IACpF,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,SAAS,CAAC,EAAU,EAAE,EAAU,EAAE,EAAU,EAAE,EAAU,EAAE,EAAU,EAAE,EAAU;IACvF,OAAO,CACL,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,EAAE;QAC5B,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG;QAC5B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,EAAE;QAC5B,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,CAC7B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAO,EAAE,EAAO,EAAE,EAAO,EAAE,EAAO;IAClE,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAE5D,IAAI,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;QACjH,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5F,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5F,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5F,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5F,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAAC,CAAU,EAAE,CAAU;IACpD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAChB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;YAAE,SAAS;QACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAChB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;YACjC,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;gBAAE,SAAS;YACnD,IAAI,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;gBAAE,OAAO,IAAI,CAAC;QACrD,CAAC;IACH,CAAC;IACD,mFAAmF;IACnF,gFAAgF;IAChF,4EAA4E;IAC5E,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAClB,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IACjD,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAClB,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,4BAA4B,CAAC,CAAU,EAAE,CAAU,EAAE,SAAiB;IACpF,IAAI,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,SAAS,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,GAAG,GAAG,CAAC;AACjD,CAAC;AAED,4FAA4F;AAC5F,SAAS,eAAe,CAAC,CAAU,EAAE,CAAU;IAC7C,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAChB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;YAAE,SAAS;QACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAChB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;YACjC,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;gBAAE,SAAS;YACnD,MAAM,CAAC,GAAG,sBAAsB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;YACjD,IAAI,CAAC,GAAG,GAAG;gBAAE,GAAG,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,sBAAsB,CAAC,EAAO,EAAE,EAAO,EAAE,EAAO,EAAE,EAAO;IAChE,IAAI,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;QAAE,OAAO,CAAC,CAAC;IAChD,OAAO,IAAI,CAAC,GAAG,CACb,oBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAChC,oBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAChC,oBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAChC,oBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CACjC,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,CAAM,EAAE,CAAM,EAAE,CAAM;IAClD,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC/B,IAAI,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;IACrD,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAChC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAClC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["/**\n * 2D simple-polygon geometry for true-shape (no-fit-polygon) nesting: an ordered\n * vertex loop plus the overlap predicate that is the correctness backbone of the\n * packer. A false negative here would let two parts physically collide on the laser\n * bed, so the predicate is exhaustive: two simple polygons overlap iff any of their\n * edges cross OR one polygon contains a vertex of the other (the latter catches full\n * containment, where no edges cross).\n *\n * Sheet-metal flat outlines are straight-edged polylines; an arc edge (rare in a\n * developed pattern) is approximated by its two endpoints — the same pre-existing\n * assumption the DXF/SVG writers and the bbox nester make.\n */\n\nimport {\n type Result,\n ok,\n err,\n isOk,\n validationError,\n getEdges,\n curveStartPoint,\n curveEndPoint,\n} from '@faicad/faijs/brepjs-compat';\nimport type { Wire } from '@faicad/faijs/brepjs-compat';\nimport type { FlatPattern } from './types.js';\n\n/** A 2D point as a plain `[x, y]` coordinate tuple. */\nexport type Pt2 = [number, number];\n\n/** A simple (non-self-intersecting) polygon as an ordered, non-closing vertex loop. */\nexport type Polygon = Pt2[];\n\nconst EPS = 1e-9;\n\n/**\n * Ordered vertex loop of a closed outline wire — one vertex per edge start point, the\n * same 2D read the DXF writer (`outlinePoints`) and the bbox nester (`patternBbox`)\n * use. The loop is NOT closed (the last vertex is not repeated); edges are taken as\n * `v[i] → v[(i+1) % n]`.\n *\n * @param outline - the closed outline wire to read into a vertex loop.\n * @returns a `Result<Polygon>` — `ok` with the ordered vertex loop, or `err` with a\n * `validationError` when the outline is empty or has fewer than 3 vertices.\n */\nexport function wireToPolygon(outline: Wire): Result<Polygon> {\n const edges = getEdges(outline);\n if (edges.length === 0) {\n return err(validationError('EMPTY_OUTLINE', 'outline wire has no edges to read'));\n }\n const poly: Polygon = [];\n for (const edge of edges) {\n const p = curveStartPoint(edge);\n poly.push([p[0], p[1]]);\n }\n if (poly.length < 3) {\n return err(validationError('EMPTY_OUTLINE', 'outline polygon has fewer than 3 vertices'));\n }\n return ok(poly);\n}\n\n/** A bend line in the developed plane: a segment plus its fold direction. */\nexport interface FlatPatternBendLine {\n from: Pt2;\n to: Pt2;\n direction: 'up' | 'down';\n}\n\n/** A flat pattern reduced to serializable 2D polylines for a developed-view overlay. */\nexport interface FlatPatternPolylines {\n /** Closed outer boundary as an ordered, non-closing vertex loop. */\n outline: Polygon;\n /** Interior cutout loops (holes / slots / polygon cutouts). */\n holes: Polygon[];\n /** Bend lines, each with its fold direction. */\n bendLines: FlatPatternBendLine[];\n /** Open form-feature cut paths in the blank (e.g. louver/lance three-sided cuts). */\n formCuts: Polygon[];\n /** Form-feature hinge lines (the uncut fold edge a louver/lance forms up along). */\n formHinges: { from: Pt2; to: Pt2 }[];\n}\n\n/**\n * Read a wire as an OPEN polyline: every edge start plus the final edge's end,\n * so the last vertex of a non-closing path (a form cut) isn't dropped the way\n * {@link wireToPolygon} drops it for closed loops.\n *\n * Arc edges are linearised to their endpoints — the same assumption\n * {@link wireToPolygon} and the DXF writer make. Form cuts are straight-sided\n * today (louver/lance slits), so this is exact; a future curved form-feature cut\n * would render as straight chords in the overlay until arc tessellation is added.\n */\nfunction wireToOpenPolyline(wire: Wire): Pt2[] {\n const edges = getEdges(wire);\n if (edges.length === 0) return [];\n const pts: Pt2[] = edges.map((edge) => {\n const p = curveStartPoint(edge);\n return [p[0], p[1]];\n });\n const lastEdge = edges[edges.length - 1];\n if (lastEdge) {\n const e = curveEndPoint(lastEdge);\n pts.push([e[0], e[1]]);\n }\n return pts;\n}\n\n/**\n * Reduce a {@link FlatPattern} to serializable 2D polylines (outline, holes, bend\n * lines) for a developed-pattern overlay. Best-effort: a wire that fails to read\n * is dropped rather than throwing, so a partial pattern still yields what it can.\n * Arc edges are approximated by endpoints, the same assumption {@link wireToPolygon}\n * and the DXF writer make.\n *\n * @param pattern - the flat pattern to reduce to 2D polylines.\n * @returns a {@link FlatPatternPolylines} with the outline, holes, bend lines,\n * form cuts, and form hinges as plain 2D vertex loops.\n */\nexport function flatPatternToPolylines(pattern: FlatPattern): FlatPatternPolylines {\n const outlineResult = wireToPolygon(pattern.outline);\n const outline = isOk(outlineResult) ? outlineResult.value : [];\n const holes: Polygon[] = [];\n for (const hole of pattern.holes) {\n const holeResult = wireToPolygon(hole);\n if (isOk(holeResult)) holes.push(holeResult.value);\n }\n const bendLines: FlatPatternBendLine[] = pattern.bendLines.map((bend) => {\n const start = curveStartPoint(bend.line);\n const end = curveEndPoint(bend.line);\n return {\n from: [start[0], start[1]],\n to: [end[0], end[1]],\n direction: bend.direction,\n };\n });\n const formCuts = pattern.formCuts.map(wireToOpenPolyline).filter((p) => p.length >= 2);\n const formHinges = pattern.formHinges.map((hinge) => {\n const start = curveStartPoint(hinge);\n const end = curveEndPoint(hinge);\n return { from: [start[0], start[1]] as Pt2, to: [end[0], end[1]] as Pt2 };\n });\n return { outline, holes, bendLines, formCuts, formHinges };\n}\n\n/**\n * Signed area (CCW positive) — used to normalise orientation and reject degenerates.\n *\n * @param poly - the polygon to measure.\n * @returns the signed area in square units: positive for counter-clockwise vertex\n * order, negative for clockwise.\n */\nexport function signedArea(poly: Polygon): number {\n let a = 0;\n for (let i = 0, j = poly.length - 1; i < poly.length; j = i, i += 1) {\n const pi = poly[i];\n const pj = poly[j];\n if (pi === undefined || pj === undefined) continue;\n a += pj[0] * pi[1] - pi[0] * pj[1];\n }\n return a / 2;\n}\n\n/**\n * Axis-aligned bounds of a polygon.\n *\n * @param poly - the polygon to bound.\n * @returns the axis-aligned bounding box as `[minX, minY, maxX, maxY]`.\n */\nexport function polygonBounds(poly: Polygon): [number, number, number, number] {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const p of poly) {\n if (p[0] < minX) minX = p[0];\n if (p[0] > maxX) maxX = p[0];\n if (p[1] < minY) minY = p[1];\n if (p[1] > maxY) maxY = p[1];\n }\n return [minX, minY, maxX, maxY];\n}\n\n/**\n * Rotate every vertex (CCW, degrees, about the origin), then translate by (dx, dy).\n *\n * @param poly - the polygon to transform.\n * @param dx - the translation along X applied after rotation.\n * @param dy - the translation along Y applied after rotation.\n * @param rotationDeg - the counter-clockwise rotation angle in degrees.\n * @returns a new `Polygon` with every vertex rotated then translated.\n */\nexport function transformPolygon(poly: Polygon, dx: number, dy: number, rotationDeg: number): Polygon {\n const rad = (rotationDeg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n return poly.map(([x, y]) => {\n const rx = x * c - y * s;\n const ry = x * s + y * c;\n return [rx + dx, ry + dy] as Pt2;\n });\n}\n\n/**\n * Ray-cast point-in-polygon: points strictly inside return true; behaviour for a\n * point exactly on the boundary is undefined (the overlap predicate handles edge\n * contact separately via segment-intersection).\n *\n * @param poly - the polygon to test against.\n * @param x - the point's X coordinate.\n * @param y - the point's Y coordinate.\n * @returns `true` when the point is strictly inside the polygon.\n */\nexport function pointInPolygon(poly: Polygon, x: number, y: number): boolean {\n let inside = false;\n for (let i = 0, j = poly.length - 1; i < poly.length; j = i, i += 1) {\n const pi = poly[i];\n const pj = poly[j];\n if (pi === undefined || pj === undefined) continue;\n const intersect =\n pi[1] > y !== pj[1] > y && x < ((pj[0] - pi[0]) * (y - pi[1])) / (pj[1] - pi[1]) + pi[0];\n if (intersect) inside = !inside;\n }\n return inside;\n}\n\nfunction orient(ax: number, ay: number, bx: number, by: number, cx: number, cy: number): number {\n return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);\n}\n\nfunction onSegment(ax: number, ay: number, bx: number, by: number, px: number, py: number): boolean {\n return (\n Math.min(ax, bx) - EPS <= px &&\n px <= Math.max(ax, bx) + EPS &&\n Math.min(ay, by) - EPS <= py &&\n py <= Math.max(ay, by) + EPS\n );\n}\n\n/**\n * Proper-or-improper segment intersection (`p1p2` vs `p3p4`). Returns true when the\n * segments cross OR touch (collinear overlap or a shared endpoint), so it is\n * conservative for the overlap test — never a false negative.\n *\n * @param p1 - first endpoint of the first segment.\n * @param p2 - second endpoint of the first segment.\n * @param p3 - first endpoint of the second segment.\n * @param p4 - second endpoint of the second segment.\n * @returns `true` when the two segments cross or touch, `false` otherwise.\n */\nexport function segmentsIntersect(p1: Pt2, p2: Pt2, p3: Pt2, p4: Pt2): boolean {\n const d1 = orient(p3[0], p3[1], p4[0], p4[1], p1[0], p1[1]);\n const d2 = orient(p3[0], p3[1], p4[0], p4[1], p2[0], p2[1]);\n const d3 = orient(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);\n const d4 = orient(p1[0], p1[1], p2[0], p2[1], p4[0], p4[1]);\n\n if (((d1 > EPS && d2 < -EPS) || (d1 < -EPS && d2 > EPS)) && ((d3 > EPS && d4 < -EPS) || (d3 < -EPS && d4 > EPS))) {\n return true;\n }\n\n if (Math.abs(d1) <= EPS && onSegment(p3[0], p3[1], p4[0], p4[1], p1[0], p1[1])) return true;\n if (Math.abs(d2) <= EPS && onSegment(p3[0], p3[1], p4[0], p4[1], p2[0], p2[1])) return true;\n if (Math.abs(d3) <= EPS && onSegment(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1])) return true;\n if (Math.abs(d4) <= EPS && onSegment(p1[0], p1[1], p2[0], p2[1], p4[0], p4[1])) return true;\n return false;\n}\n\n/**\n * Do two simple polygons overlap? True iff any edge of `a` intersects any edge of\n * `b`, or one polygon fully contains a vertex of the other. This pair of conditions\n * is exhaustive for simple polygons: a disjoint pair has no crossing edges and no\n * mutually-contained vertex; a touching-but-non-overlapping pair (shared edge/vertex,\n * zero interior overlap) is caught by `segmentsIntersect` and so is reported as\n * overlapping — callers wanting a clearance gap should inflate via {@link polygonsOverlapWithClearance}.\n *\n * Centroid containment is NOT sufficient on its own (a concave part's centroid can\n * lie outside it), so a representative INTERIOR vertex of each polygon is tested for\n * containment in the other — combined with the edge-crossing test this covers the\n * nested-without-crossing case.\n *\n * @param a - the first polygon.\n * @param b - the second polygon.\n * @returns `true` when the polygons overlap (including edge touch), `false` when they\n * are disjoint.\n */\nexport function polygonsOverlap(a: Polygon, b: Polygon): boolean {\n for (let i = 0; i < a.length; i += 1) {\n const a1 = a[i];\n const a2 = a[(i + 1) % a.length];\n if (a1 === undefined || a2 === undefined) continue;\n for (let j = 0; j < b.length; j += 1) {\n const b1 = b[j];\n const b2 = b[(j + 1) % b.length];\n if (b1 === undefined || b2 === undefined) continue;\n if (segmentsIntersect(a1, a2, b1, b2)) return true;\n }\n }\n // No edges cross: the only remaining overlap is full containment. If any vertex of\n // one lies strictly inside the other, the polygons overlap. (With no crossings,\n // containment of a single vertex implies containment of the whole polygon.)\n for (const v of a) {\n if (pointInPolygon(b, v[0], v[1])) return true;\n }\n for (const v of b) {\n if (pointInPolygon(a, v[0], v[1])) return true;\n }\n return false;\n}\n\n/**\n * Overlap test honoring a clearance gap: the two polygons must stay at least\n * `clearance` apart. Implemented by testing raw overlap and, when disjoint, the\n * minimum edge-to-edge distance against the clearance. `clearance <= 0` is the plain\n * {@link polygonsOverlap}.\n *\n * @param a - the first polygon.\n * @param b - the second polygon.\n * @param clearance - the minimum required gap between the polygons.\n * @returns `true` when the polygons overlap or come closer than `clearance`.\n */\nexport function polygonsOverlapWithClearance(a: Polygon, b: Polygon, clearance: number): boolean {\n if (polygonsOverlap(a, b)) return true;\n if (clearance <= EPS) return false;\n return minEdgeDistance(a, b) < clearance - EPS;\n}\n\n/** Minimum distance between any edge of `a` and any edge of `b` (both assumed disjoint). */\nfunction minEdgeDistance(a: Polygon, b: Polygon): number {\n let min = Infinity;\n for (let i = 0; i < a.length; i += 1) {\n const a1 = a[i];\n const a2 = a[(i + 1) % a.length];\n if (a1 === undefined || a2 === undefined) continue;\n for (let j = 0; j < b.length; j += 1) {\n const b1 = b[j];\n const b2 = b[(j + 1) % b.length];\n if (b1 === undefined || b2 === undefined) continue;\n const d = segmentSegmentDistance(a1, a2, b1, b2);\n if (d < min) min = d;\n }\n }\n return min;\n}\n\nfunction segmentSegmentDistance(p1: Pt2, p2: Pt2, p3: Pt2, p4: Pt2): number {\n if (segmentsIntersect(p1, p2, p3, p4)) return 0;\n return Math.min(\n pointSegmentDistance(p1, p3, p4),\n pointSegmentDistance(p2, p3, p4),\n pointSegmentDistance(p3, p1, p2),\n pointSegmentDistance(p4, p1, p2)\n );\n}\n\nfunction pointSegmentDistance(p: Pt2, a: Pt2, b: Pt2): number {\n const vx = b[0] - a[0];\n const vy = b[1] - a[1];\n const wx = p[0] - a[0];\n const wy = p[1] - a[1];\n const len2 = vx * vx + vy * vy;\n let t = len2 <= EPS ? 0 : (wx * vx + wy * vy) / len2;\n t = Math.max(0, Math.min(1, t));\n const dx = p[0] - (a[0] + t * vx);\n const dy = p[1] - (a[1] + t * vy);\n return Math.hypot(dx, dy);\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import { type Result } from '@faicad/faijs/brepjs-compat';
2
+ import type { ReliefSpec, SheetMetalPart } from './types.js';
3
+ /**
4
+ * Add a bend relief to a flange: a small slot cut into the PARENT flat at each end
5
+ * of the bend line that does not reach the parent edge endpoint (a partial/offset
6
+ * flange). Without it the parent material tears at the corner where the bend
7
+ * terminates mid-edge. Each slot is `width` (≈ thickness) along the parent edge by
8
+ * `depth` (≈ developed length + clearance) into the parent flat, cut from the 3D
9
+ * solid and recorded so {@link unfold} replays the 2D notch.
10
+ *
11
+ * A relief is a recorded feature replayed by unfold — exactly the pattern
12
+ * {@link autoMiterCorner} establishes for corner miters.
13
+ *
14
+ * @param part - the sheet metal part to cut.
15
+ * @param flangeId - the flange (bend) ID to relieve.
16
+ * @param spec - the relief specification (default rectangular).
17
+ * @returns the updated part with the bend relief recorded, or an error.
18
+ */
19
+ export declare function addBendRelief(part: SheetMetalPart, flangeId: string, spec?: ReliefSpec): Result<SheetMetalPart>;
20
+ /**
21
+ * Add a bend relief to every partial-span bend (a flange that does not span its
22
+ * full parent edge). Full-span flanges are skipped — they have no mid-edge bend
23
+ * terminus to relieve. Convenience over calling {@link addBendRelief} per flange.
24
+ *
25
+ * @param part - the sheet metal part to process.
26
+ * @param spec - the relief specification (default rectangular).
27
+ * @returns the updated part with all needed bend reliefs, or an error.
28
+ */
29
+ export declare function autoBendReliefs(part: SheetMetalPart, spec?: ReliefSpec): Result<SheetMetalPart>;
30
+ /**
31
+ * Corner relief between two adjacent flanges: a notch cut at their shared corner so
32
+ * the two upright flanges clear each other once folded — the alternative to a 45°
33
+ * miter for the same corner {@link autoMiterCorner} handles. The notch is cut from
34
+ * the solid and recorded as a {@link ReliefFeature}; the corner is also recorded in
35
+ * `miters` (gap 0) so the collision check treats the interference as resolved.
36
+ *
37
+ * @param part - the sheet metal part to cut.
38
+ * @param flangeIdA - the first flange ID.
39
+ * @param flangeIdB - the second flange ID.
40
+ * @param spec - the relief specification (default rectangular).
41
+ * @returns the updated part with the corner relief recorded, or an error.
42
+ */
43
+ export declare function cornerRelief(part: SheetMetalPart, flangeIdA: string, flangeIdB: string, spec?: ReliefSpec): Result<SheetMetalPart>;
44
+ //# sourceMappingURL=reliefFns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reliefFns.d.ts","sourceRoot":"","sources":["../src/reliefFns.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAqBZ,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAIV,UAAU,EACV,cAAc,EACf,MAAM,YAAY,CAAC;AAWpB;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,UAAqC,GAC1C,MAAM,CAAC,cAAc,CAAC,CAiDxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,cAAc,EACpB,IAAI,GAAE,UAAqC,GAC1C,MAAM,CAAC,cAAc,CAAC,CAaxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,cAAc,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,UAAqC,GAC1C,MAAM,CAAC,cAAc,CAAC,CA8DxB"}