@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.
- package/dist/allowanceFns.d.ts +42 -0
- package/dist/allowanceFns.d.ts.map +1 -0
- package/dist/allowanceFns.js +63 -0
- package/dist/allowanceFns.js.map +1 -0
- package/dist/api.d.ts +312 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +367 -0
- package/dist/api.js.map +1 -0
- package/dist/authorFns.d.ts +118 -0
- package/dist/authorFns.d.ts.map +1 -0
- package/dist/authorFns.js +562 -0
- package/dist/authorFns.js.map +1 -0
- package/dist/bendTableFns.d.ts +87 -0
- package/dist/bendTableFns.d.ts.map +1 -0
- package/dist/bendTableFns.js +345 -0
- package/dist/bendTableFns.js.map +1 -0
- package/dist/compat.d.ts +90 -0
- package/dist/compat.d.ts.map +1 -0
- package/dist/compat.js +99 -0
- package/dist/compat.js.map +1 -0
- package/dist/contourFlangeFns.d.ts +122 -0
- package/dist/contourFlangeFns.d.ts.map +1 -0
- package/dist/contourFlangeFns.js +403 -0
- package/dist/contourFlangeFns.js.map +1 -0
- package/dist/cutoutFns.d.ts +95 -0
- package/dist/cutoutFns.d.ts.map +1 -0
- package/dist/cutoutFns.js +300 -0
- package/dist/cutoutFns.js.map +1 -0
- package/dist/dxfFns.d.ts +48 -0
- package/dist/dxfFns.d.ts.map +1 -0
- package/dist/dxfFns.js +236 -0
- package/dist/dxfFns.js.map +1 -0
- package/dist/facade.d.ts +449 -0
- package/dist/facade.d.ts.map +1 -0
- package/dist/facade.js +535 -0
- package/dist/facade.js.map +1 -0
- package/dist/featureTreeFns.d.ts +76 -0
- package/dist/featureTreeFns.d.ts.map +1 -0
- package/dist/featureTreeFns.js +190 -0
- package/dist/featureTreeFns.js.map +1 -0
- package/dist/foldFns.d.ts +83 -0
- package/dist/foldFns.d.ts.map +1 -0
- package/dist/foldFns.js +744 -0
- package/dist/foldFns.js.map +1 -0
- package/dist/foreignUnfoldFns.d.ts +62 -0
- package/dist/foreignUnfoldFns.d.ts.map +1 -0
- package/dist/foreignUnfoldFns.js +854 -0
- package/dist/foreignUnfoldFns.js.map +1 -0
- package/dist/formFns.d.ts +58 -0
- package/dist/formFns.d.ts.map +1 -0
- package/dist/formFns.js +300 -0
- package/dist/formFns.js.map +1 -0
- package/dist/hemFns.d.ts +17 -0
- package/dist/hemFns.d.ts.map +1 -0
- package/dist/hemFns.js +212 -0
- package/dist/hemFns.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +27 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +38 -0
- package/dist/internal.js.map +1 -0
- package/dist/jogFns.d.ts +18 -0
- package/dist/jogFns.d.ts.map +1 -0
- package/dist/jogFns.js +180 -0
- package/dist/jogFns.js.map +1 -0
- package/dist/loftedFlangeFns.d.ts +23 -0
- package/dist/loftedFlangeFns.d.ts.map +1 -0
- package/dist/loftedFlangeFns.js +306 -0
- package/dist/loftedFlangeFns.js.map +1 -0
- package/dist/materials.d.ts +16 -0
- package/dist/materials.d.ts.map +1 -0
- package/dist/materials.js +44 -0
- package/dist/materials.js.map +1 -0
- package/dist/miterFns.d.ts +31 -0
- package/dist/miterFns.d.ts.map +1 -0
- package/dist/miterFns.js +128 -0
- package/dist/miterFns.js.map +1 -0
- package/dist/nestFns.d.ts +116 -0
- package/dist/nestFns.d.ts.map +1 -0
- package/dist/nestFns.js +517 -0
- package/dist/nestFns.js.map +1 -0
- package/dist/polygonFns.d.ts +144 -0
- package/dist/polygonFns.d.ts.map +1 -0
- package/dist/polygonFns.js +324 -0
- package/dist/polygonFns.js.map +1 -0
- package/dist/reliefFns.d.ts +44 -0
- package/dist/reliefFns.d.ts.map +1 -0
- package/dist/reliefFns.js +367 -0
- package/dist/reliefFns.js.map +1 -0
- package/dist/reportFns.d.ts +33 -0
- package/dist/reportFns.d.ts.map +1 -0
- package/dist/reportFns.js +71 -0
- package/dist/reportFns.js.map +1 -0
- package/dist/tabFns.d.ts +43 -0
- package/dist/tabFns.d.ts.map +1 -0
- package/dist/tabFns.js +179 -0
- package/dist/tabFns.js.map +1 -0
- package/dist/types.d.ts +629 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/unfoldFns.d.ts +120 -0
- package/dist/unfoldFns.d.ts.map +1 -0
- package/dist/unfoldFns.js +766 -0
- package/dist/unfoldFns.js.map +1 -0
- package/dist/validateFns.d.ts +18 -0
- package/dist/validateFns.d.ts.map +1 -0
- package/dist/validateFns.js +196 -0
- package/dist/validateFns.js.map +1 -0
- 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":"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","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"}
|