brepjs-sheetmetal 0.1.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.
@@ -0,0 +1,18 @@
1
+ import { Result } from 'brepjs';
2
+ import { LoftedFlangeSpec, SheetMetalPart } from './types.js';
3
+ /**
4
+ * Author a lofted / ruled transition flange between two parallel OPEN profiles.
5
+ * The two profiles are lofted (ruled, straight generators) into a transition surface
6
+ * and thickened to a valid solid; the result is fused onto the part. The developed
7
+ * pattern is produced by TRIANGULATION (the standard transition development): each
8
+ * quad between consecutive vertex pairs is split into two triangles laid flat
9
+ * edge-length-preserving and accumulated into a developed boundary.
10
+ *
11
+ * The triangulated development is EXACT (to tolerance) when the ruled surface is
12
+ * developable — every quad is planar, so the two triangles share their diagonal in
13
+ * 3D and the flat layout preserves all lengths and angles. When a quad is non-planar
14
+ * (twisted ruling) the surface is not developable and the flat layout is an
15
+ * approximation; {@link approximate} is set and the unfold emits a
16
+ * `DEVELOPMENT_APPROXIMATE` warning.
17
+ */
18
+ export declare function authorLoftedFlange(part: SheetMetalPart, spec: LoftedFlangeSpec): Result<SheetMetalPart>;
@@ -0,0 +1,6 @@
1
+ import { MaterialSpec } from './types.js';
2
+ export declare const DEFAULT_K_FACTOR = 0.44;
3
+ export declare const STEEL_GAUGES: Readonly<Record<string, MaterialSpec>>;
4
+ export declare const ALUMINUM_GAUGES: Readonly<Record<string, MaterialSpec>>;
5
+ export declare const MATERIALS: Readonly<Record<string, MaterialSpec>>;
6
+ export declare function getMaterial(name: string): MaterialSpec | undefined;
@@ -0,0 +1,20 @@
1
+ import { Result, Vec3 } from 'brepjs';
2
+ import { SheetMetalPart } from './types.js';
3
+ /** An oriented cutting plane: material on the `+normal` side is removed. */
4
+ export interface MiterPlane {
5
+ origin: Vec3;
6
+ normal: Vec3;
7
+ }
8
+ /**
9
+ * General miter primitive: cut a sheet-metal part by a plane, removing the
10
+ * material on the `+normal` side. The tool is a half-space block sized to the
11
+ * part's bounding box, so the cut is exact regardless of part extent.
12
+ */
13
+ export declare function miterCut(part: SheetMetalPart, plane: MiterPlane): Result<SheetMetalPart>;
14
+ /**
15
+ * Auto corner-miter between two adjacent flanges. The miter plane bisects the two
16
+ * flanges' fold-up directions at their shared corner and is offset by half the gap,
17
+ * so the cut falls on the flat/flange regions and never crosses a bend patch. The
18
+ * single bisector cut trims both flanges to a clean mitered corner.
19
+ */
20
+ export declare function autoMiterCorner(part: SheetMetalPart, flangeIdA: string, flangeIdB: string, gap?: number): Result<SheetMetalPart>;
@@ -0,0 +1,95 @@
1
+ import { Result } from 'brepjs';
2
+ import { FlatPattern, SheetMetalWarning } from './types.js';
3
+ import { DxfOptions } from './dxfFns.js';
4
+ /** Stock sheet the parts are packed onto (the gross blank, before margin). */
5
+ export interface SheetSpec {
6
+ width: number;
7
+ height: number;
8
+ }
9
+ export interface NestOptions {
10
+ sheet: SheetSpec;
11
+ /** Clear border kept empty around the sheet edge (per side). Default `0`. */
12
+ margin?: number | undefined;
13
+ /** Minimum clear gap between any two placed parts (and the bbox padding used
14
+ * for the non-overlap test). Default `0`. */
15
+ spacing?: number | undefined;
16
+ /** Try a 90° rotation per part and keep whichever orientation fits/packs better. */
17
+ allowRotation?: boolean | undefined;
18
+ /**
19
+ * Packing strategy. Default `"bbox"`: each part is packed as its axis-aligned
20
+ * bounding box (the original {@link nest} behavior — fast, parts never interlock).
21
+ * `"nfp"` is true-shape / no-fit-polygon nesting: the actual outline polygons are
22
+ * packed so concave (L-shaped) parts interlock for higher material utilization.
23
+ * The NFP packer is a HEURISTIC (bottom-left-fill, largest-first) — not provably
24
+ * optimal — but it never overlaps parts and never drops a part silently.
25
+ */
26
+ strategy?: 'bbox' | 'nfp' | undefined;
27
+ }
28
+ /**
29
+ * Where a single pattern lands on a sheet. For the bbox strategy `rotationDeg` is
30
+ * `0` or `90`; the true-shape (nfp) strategy may also emit `180` or `270`. `(x, y)`
31
+ * is the lower-left of the part's transformed bounding box in sheet coordinates.
32
+ */
33
+ export interface Placement {
34
+ /** Index into the `patterns` array passed to {@link nest}. */
35
+ patternIndex: number;
36
+ /** Lower-left x of the part's (rotated) bounding box, in sheet coordinates. */
37
+ x: number;
38
+ /** Lower-left y of the part's (rotated) bounding box, in sheet coordinates. */
39
+ y: number;
40
+ rotationDeg: number;
41
+ }
42
+ export interface NestSheet {
43
+ placements: Placement[];
44
+ /**
45
+ * Σ placed part areas ÷ usable-sheet area, in `(0, 1]`. "Usable" =
46
+ * `(width − 2·margin) × (height − 2·margin)`. For the `"bbox"` strategy the part
47
+ * area is its BOUNDING-BOX area (intra-bbox waste of a non-rectangular part is not
48
+ * credited — a conservative measure). For the `"nfp"` true-shape strategy it is the
49
+ * actual OUTLINE-POLYGON area, so an L-shaped part credits only its true material,
50
+ * making the two strategies' utilizations directly comparable on the same parts.
51
+ */
52
+ utilization: number;
53
+ }
54
+ export interface NestResult {
55
+ sheets: NestSheet[];
56
+ /** Indices of patterns too large for the usable sheet even rotated. */
57
+ unplaced: number[];
58
+ warnings: SheetMetalWarning[];
59
+ }
60
+ /** Axis-aligned bounding box of one pattern outline, in developed-plane coords. */
61
+ export interface Bbox {
62
+ minX: number;
63
+ minY: number;
64
+ width: number;
65
+ height: number;
66
+ }
67
+ /**
68
+ * Bounding-box nest: arrange developed flat patterns onto stock sheets to reduce
69
+ * waste. This is BOUNDING-BOX nesting — each part is packed as its axis-aligned
70
+ * outline bounding box, so parts never interlock and concave parts leave their
71
+ * bounding-box waste unused. True-shape (no-fit-polygon) nesting is a follow-up.
72
+ *
73
+ * Algorithm: a shelf (level) packing heuristic. Parts are sorted largest-first by
74
+ * bbox height then area, then placed left-to-right into horizontal shelves; a new
75
+ * shelf opens below when the current row is full, and a NEW sheet opens when the
76
+ * next shelf would overflow the usable height. With `allowRotation`, each part is
77
+ * tried both at 0° and 90° and the orientation that fits the current shelf (or,
78
+ * failing that, fits at all) is kept. A part larger than the usable sheet even
79
+ * rotated is reported in `unplaced` with a warning — never dropped silently, never
80
+ * retried in an infinite loop.
81
+ */
82
+ export declare function nest(patterns: FlatPattern[], options: NestOptions): Result<NestResult>;
83
+ /**
84
+ * Axis-aligned bounding box of a pattern's OUTER outline wire, read from the wire
85
+ * vertices (each edge start point) — the same 2D-coordinate read the DXF/SVG writers
86
+ * use. Only the outer outline is packed; holes ride inside it.
87
+ */
88
+ export declare function patternBbox(pattern: FlatPattern): Result<Bbox>;
89
+ /**
90
+ * Emit one fabrication-ready DXF for a single nested sheet: every pattern placed on
91
+ * `result.sheets[sheetIndex]` is translated and rotated onto the sheet (via the same
92
+ * bounding boxes the packer used) and written through {@link multiPatternToDXF}.
93
+ * `patterns` must be the SAME array passed to {@link nest} (placements index into it).
94
+ */
95
+ export declare function nestToDXF(result: NestResult, patterns: FlatPattern[], sheetIndex: number, options?: DxfOptions): Result<string>;
@@ -0,0 +1,48 @@
1
+ import { Result, Wire } from 'brepjs';
2
+ export type Pt2 = [number, number];
3
+ /** A simple (non-self-intersecting) polygon as an ordered, non-closing vertex loop. */
4
+ export type Polygon = Pt2[];
5
+ /**
6
+ * Ordered vertex loop of a closed outline wire — one vertex per edge start point, the
7
+ * same 2D read the DXF writer (`outlinePoints`) and the bbox nester (`patternBbox`)
8
+ * use. The loop is NOT closed (the last vertex is not repeated); edges are taken as
9
+ * `v[i] → v[(i+1) % n]`.
10
+ */
11
+ export declare function wireToPolygon(outline: Wire): Result<Polygon>;
12
+ /** Signed area (CCW positive) — used to normalise orientation and reject degenerates. */
13
+ export declare function signedArea(poly: Polygon): number;
14
+ /** Axis-aligned bounds `[minX, minY, maxX, maxY]` of a polygon. */
15
+ export declare function polygonBounds(poly: Polygon): [number, number, number, number];
16
+ /** Rotate every vertex (CCW, degrees, about the origin), then translate by (dx, dy). */
17
+ export declare function transformPolygon(poly: Polygon, dx: number, dy: number, rotationDeg: number): Polygon;
18
+ /** Ray-cast point-in-polygon: points strictly inside return true; behaviour for a
19
+ * point exactly on the boundary is undefined (the overlap predicate handles edge
20
+ * contact separately via segment-intersection). */
21
+ export declare function pointInPolygon(poly: Polygon, x: number, y: number): boolean;
22
+ /**
23
+ * Proper-or-improper segment intersection (`p1p2` vs `p3p4`). Returns true when the
24
+ * segments cross OR touch (collinear overlap or a shared endpoint), so it is
25
+ * conservative for the overlap test — never a false negative.
26
+ */
27
+ export declare function segmentsIntersect(p1: Pt2, p2: Pt2, p3: Pt2, p4: Pt2): boolean;
28
+ /**
29
+ * Do two simple polygons overlap? True iff any edge of `a` intersects any edge of
30
+ * `b`, or one polygon fully contains a vertex of the other. This pair of conditions
31
+ * is exhaustive for simple polygons: a disjoint pair has no crossing edges and no
32
+ * mutually-contained vertex; a touching-but-non-overlapping pair (shared edge/vertex,
33
+ * zero interior overlap) is caught by `segmentsIntersect` and so is reported as
34
+ * overlapping — callers wanting a clearance gap should inflate via {@link polygonsOverlapWithClearance}.
35
+ *
36
+ * Centroid containment is NOT sufficient on its own (a concave part's centroid can
37
+ * lie outside it), so a representative INTERIOR vertex of each polygon is tested for
38
+ * containment in the other — combined with the edge-crossing test this covers the
39
+ * nested-without-crossing case.
40
+ */
41
+ export declare function polygonsOverlap(a: Polygon, b: Polygon): boolean;
42
+ /**
43
+ * Overlap test honoring a clearance gap: the two polygons must stay at least
44
+ * `clearance` apart. Implemented by testing raw overlap and, when disjoint, the
45
+ * minimum edge-to-edge distance against the clearance. `clearance <= 0` is the plain
46
+ * {@link polygonsOverlap}.
47
+ */
48
+ export declare function polygonsOverlapWithClearance(a: Polygon, b: Polygon, clearance: number): boolean;
@@ -0,0 +1,28 @@
1
+ import { Result } from 'brepjs';
2
+ import { 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
+ export declare function addBendRelief(part: SheetMetalPart, flangeId: string, spec?: ReliefSpec): Result<SheetMetalPart>;
15
+ /**
16
+ * Add a bend relief to every partial-span bend (a flange that does not span its
17
+ * full parent edge). Full-span flanges are skipped — they have no mid-edge bend
18
+ * terminus to relieve. Convenience over calling {@link addBendRelief} per flange.
19
+ */
20
+ export declare function autoBendReliefs(part: SheetMetalPart, spec?: ReliefSpec): Result<SheetMetalPart>;
21
+ /**
22
+ * Corner relief between two adjacent flanges: a notch cut at their shared corner so
23
+ * the two upright flanges clear each other once folded — the alternative to a 45°
24
+ * miter for the same corner {@link autoMiterCorner} handles. The notch is cut from
25
+ * the solid and recorded as a {@link ReliefFeature}; the corner is also recorded in
26
+ * `miters` (gap 0) so the collision check treats the interference as resolved.
27
+ */
28
+ export declare function cornerRelief(part: SheetMetalPart, flangeIdA: string, flangeIdB: string, spec?: ReliefSpec): Result<SheetMetalPart>;
@@ -0,0 +1,19 @@
1
+ import { Result } from 'brepjs';
2
+ import { BendReport, SheetMetalPart, UnfoldResult } from './types.js';
3
+ /**
4
+ * Build the bend-table JSON report directly from an authored part. Walks the
5
+ * same tree layout the unfold consumes so the per-bend values and the total flat
6
+ * size agree with the flat pattern. `allowance` is the consumed neutral-axis arc
7
+ * length (the bend allowance); `flatLength` is the straight flange leg past the
8
+ * bend — distinct values a downstream nesting/cut-list tool needs separately.
9
+ */
10
+ export declare function buildReport(part: SheetMetalPart): Result<BendReport>;
11
+ /**
12
+ * Project the report already computed by `unfold` into the standalone
13
+ * `BendReport` shape, defensively re-validating its numeric fields. This keeps
14
+ * the report a pure function of a previously-unfolded result without re-walking
15
+ * the tree.
16
+ */
17
+ export declare function reportFromUnfold(result: UnfoldResult): Result<BendReport>;
18
+ /** Serialize a `BendReport` to a stable, pretty-printed JSON string. */
19
+ export declare function reportToJSON(report: BendReport): string;
@@ -0,0 +1,33 @@
1
+ import { Result } from 'brepjs';
2
+ import { TabSpec, SheetMetalPart } from './types.js';
3
+ /**
4
+ * Fuse a rectangular tab onto a named flat region's edge — additive material (the
5
+ * counterpart of a cutout). The tab is built as a region-local rectangle just past
6
+ * the chosen `side`, placed onto the correct folded face via the region's world
7
+ * {@link FlatFrame}, extruded through the sheet thickness and fused to the solid. The
8
+ * same local rectangle, mapped through the region's developed {@link Frame2}, is
9
+ * recorded as a {@link TabFeature} so {@link unfold} extends the OUTER outline by the
10
+ * protrusion (tabs add material, so the developed area grows). Guards a valid,
11
+ * single-bodied solid.
12
+ */
13
+ export declare function addTab(part: SheetMetalPart, spec: TabSpec): Result<SheetMetalPart>;
14
+ /** The mating-slot placement for a {@link tabAndSlot} joint. */
15
+ export interface SlotPlacement {
16
+ region: string;
17
+ x: number;
18
+ y: number;
19
+ clearance?: number | undefined;
20
+ /** In-plane rotation of the slot (deg, CCW) in the mating region's local frame.
21
+ * Default 0 = slot length along the region's +x (bend-axis) direction; set this
22
+ * when the tab meets the slot region at a non-default orientation. */
23
+ angleDeg?: number | undefined;
24
+ }
25
+ /**
26
+ * Self-fixturing tab-and-slot joint: fuse a tab on one region and punch a matching
27
+ * SLOT CUTOUT on the mating region, sized so the tab's cross-section
28
+ * (`width × thickness`) inserts into the slot. The slot is `tab.width + clearance`
29
+ * long by `thickness + clearance` wide, centred at the mating region's local
30
+ * `(x, y)`. The slot is always strictly larger than the tab cross-section, so the
31
+ * joint mates (verified numerically by callers). Clearance defaults to `0.1` mm.
32
+ */
33
+ export declare function tabAndSlot(part: SheetMetalPart, tab: TabSpec, slot: SlotPlacement): Result<SheetMetalPart>;