@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
package/dist/miterFns.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ok, err, validationError, box, cut, rotate, translate, getBounds, vecSub, vecAdd, vecScale, vecCross, vecDot, vecNormalize, } from '@faicad/faijs/brepjs-compat';
|
|
2
|
+
import { normalizeSolid } from './internal.js';
|
|
3
|
+
/**
|
|
4
|
+
* General miter primitive: cut a sheet-metal part by a plane, removing the
|
|
5
|
+
* material on the `+normal` side. The tool is a half-space block sized to the
|
|
6
|
+
* part's bounding box, so the cut is exact regardless of part extent.
|
|
7
|
+
*
|
|
8
|
+
* @param part - the sheet metal part to cut.
|
|
9
|
+
* @param plane - the miter plane definition.
|
|
10
|
+
* @returns the updated part with the miter cut applied, or an error.
|
|
11
|
+
*/
|
|
12
|
+
export function miterCut(part, plane) {
|
|
13
|
+
const solid = part.solid;
|
|
14
|
+
if (solid === undefined) {
|
|
15
|
+
return err(validationError('NO_SOLID', 'miterCut: part has no folded solid to cut'));
|
|
16
|
+
}
|
|
17
|
+
const tool = halfSpaceTool(solid, plane);
|
|
18
|
+
if (!tool.ok)
|
|
19
|
+
return tool;
|
|
20
|
+
const result = cut(solid, tool.value);
|
|
21
|
+
if (!result.ok)
|
|
22
|
+
return result;
|
|
23
|
+
return ok({ ...part, solid: normalizeSolid(result.value) });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Auto corner-miter between two adjacent flanges. The miter plane bisects the two
|
|
27
|
+
* flanges' fold-up directions at their shared corner and is offset by half the gap,
|
|
28
|
+
* so the cut falls on the flat/flange regions and never crosses a bend patch. The
|
|
29
|
+
* single bisector cut trims both flanges to a clean mitered corner.
|
|
30
|
+
*
|
|
31
|
+
* @param part - the sheet metal part to miter.
|
|
32
|
+
* @param flangeIdA - the first flange ID.
|
|
33
|
+
* @param flangeIdB - the second flange ID.
|
|
34
|
+
* @param gap - the gap between flanges (default 0).
|
|
35
|
+
* @returns the updated part with the miter corner applied, or an error.
|
|
36
|
+
*/
|
|
37
|
+
export function autoMiterCorner(part, flangeIdA, flangeIdB, gap = 0) {
|
|
38
|
+
if (part.solid === undefined) {
|
|
39
|
+
return err(validationError('NO_SOLID', 'autoMiterCorner: part has no folded solid'));
|
|
40
|
+
}
|
|
41
|
+
if (!Number.isFinite(gap) || gap < 0) {
|
|
42
|
+
return err(validationError('INVALID_GAP', `miter gap must be a finite, non-negative number, got ${gap}`));
|
|
43
|
+
}
|
|
44
|
+
const bendA = part.bends.find((b) => b.id === flangeIdA);
|
|
45
|
+
const bendB = part.bends.find((b) => b.id === flangeIdB);
|
|
46
|
+
if (bendA === undefined || bendB === undefined) {
|
|
47
|
+
return err(validationError('UNKNOWN_FLANGE', `autoMiterCorner: flange '${flangeIdA}' or '${flangeIdB}' not found`));
|
|
48
|
+
}
|
|
49
|
+
const axisA = vecNormalize(bendA.axisDir);
|
|
50
|
+
const axisB = vecNormalize(bendB.axisDir);
|
|
51
|
+
const corner = cornerPoint(bendA.axisOrigin, axisA, bendB.axisOrigin, axisB);
|
|
52
|
+
// Each flange runs perpendicular to its own bend axis. The miter plane contains
|
|
53
|
+
// the corner and is spanned by the two bend axes; its normal is their bisector in
|
|
54
|
+
// the run plane, which is exactly the bisector of the two flange run directions.
|
|
55
|
+
const planeNormal = miterNormal(axisA, axisB);
|
|
56
|
+
if (planeNormal === undefined) {
|
|
57
|
+
return err(validationError('PARALLEL_FLANGES', 'autoMiterCorner: flange bend axes are parallel; no corner to miter'));
|
|
58
|
+
}
|
|
59
|
+
const origin = vecAdd(corner, vecScale(planeNormal, gap * 0.5));
|
|
60
|
+
const cutResult = miterCut(part, { origin, normal: planeNormal });
|
|
61
|
+
if (!cutResult.ok)
|
|
62
|
+
return cutResult;
|
|
63
|
+
return ok({
|
|
64
|
+
...cutResult.value,
|
|
65
|
+
miters: [...(part.miters ?? []), { flangeA: flangeIdA, flangeB: flangeIdB, gap }],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Point on the line of bend A closest to the line of bend B (their shared corner). */
|
|
69
|
+
function cornerPoint(originA, dirA, originB, dirB) {
|
|
70
|
+
const w0 = vecSub(originA, originB);
|
|
71
|
+
const a = vecDot(dirA, dirA);
|
|
72
|
+
const b = vecDot(dirA, dirB);
|
|
73
|
+
const c = vecDot(dirB, dirB);
|
|
74
|
+
const d = vecDot(dirA, w0);
|
|
75
|
+
const e = vecDot(dirB, w0);
|
|
76
|
+
const denom = a * c - b * b;
|
|
77
|
+
if (Math.abs(denom) < 1e-9)
|
|
78
|
+
return originA;
|
|
79
|
+
const sc = (b * e - c * d) / denom;
|
|
80
|
+
return vecAdd(originA, vecScale(dirA, sc));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Normal of the miter plane between two flanges whose bend axes are `dirA`/`dirB`.
|
|
84
|
+
* The plane is spanned by the two axes (it contains the corner edge of each
|
|
85
|
+
* flange); its normal is `dirA × dirB` rotated to bisect — equivalently the
|
|
86
|
+
* normalized sum of the two run directions, which both lie perpendicular to the
|
|
87
|
+
* respective axis in the part's run plane.
|
|
88
|
+
*/
|
|
89
|
+
function miterNormal(dirA, dirB) {
|
|
90
|
+
const cross = vecCross(dirA, dirB);
|
|
91
|
+
if (Math.hypot(cross[0], cross[1], cross[2]) < 1e-9)
|
|
92
|
+
return undefined;
|
|
93
|
+
const sum = vecAdd(dirA, dirB);
|
|
94
|
+
if (Math.hypot(sum[0], sum[1], sum[2]) < 1e-9)
|
|
95
|
+
return vecNormalize(cross);
|
|
96
|
+
return vecNormalize(sum);
|
|
97
|
+
}
|
|
98
|
+
const Z_AXIS = [0, 0, 1];
|
|
99
|
+
function halfSpaceTool(solid, plane) {
|
|
100
|
+
const normal = vecNormalize(plane.normal);
|
|
101
|
+
if (Math.hypot(normal[0], normal[1], normal[2]) < 1e-9) {
|
|
102
|
+
return err(validationError('INVALID_PLANE', 'miter plane normal must be non-zero'));
|
|
103
|
+
}
|
|
104
|
+
const b = getBounds(solid);
|
|
105
|
+
const diag = Math.hypot(b.xMax - b.xMin, b.yMax - b.yMin, b.zMax - b.zMin);
|
|
106
|
+
const size = diag * 2 + 2;
|
|
107
|
+
// A cube whose bottom (−Z) face lies on the XY plane through the origin, extending
|
|
108
|
+
// up in +Z, then rotated so +Z maps onto the plane normal and translated onto the
|
|
109
|
+
// plane origin. After alignment its open boundary is the miter plane and its body
|
|
110
|
+
// fills the +normal half-space.
|
|
111
|
+
let tool = box(size, size, size);
|
|
112
|
+
tool = translate(tool, [-size / 2, -size / 2, 0]);
|
|
113
|
+
const aligned = alignZTo(tool, normal);
|
|
114
|
+
return ok(translate(aligned, plane.origin));
|
|
115
|
+
}
|
|
116
|
+
/** Rotate a shape so its local +Z axis points along `target` (about the origin). */
|
|
117
|
+
function alignZTo(shape, target) {
|
|
118
|
+
const dot = vecDot(Z_AXIS, target);
|
|
119
|
+
if (dot > 1 - 1e-9)
|
|
120
|
+
return shape;
|
|
121
|
+
if (dot < -1 + 1e-9) {
|
|
122
|
+
return rotate(shape, 180, { at: [0, 0, 0], axis: [1, 0, 0] });
|
|
123
|
+
}
|
|
124
|
+
const axis = vecNormalize(vecCross(Z_AXIS, target));
|
|
125
|
+
const angleDeg = (Math.acos(Math.max(-1, Math.min(1, dot))) * 180) / Math.PI;
|
|
126
|
+
return rotate(shape, angleDeg, { at: [0, 0, 0], axis });
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=miterFns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"miterFns.js","sourceRoot":"","sources":["../src/miterFns.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Result,\n type Vec3,\n type Solid,\n ok,\n err,\n validationError,\n box,\n cut,\n rotate,\n translate,\n getBounds,\n vecSub,\n vecAdd,\n vecScale,\n vecCross,\n vecDot,\n vecNormalize,\n} from '@faicad/faijs/brepjs-compat';\nimport type { SheetMetalPart } from './types.js';\nimport { normalizeSolid } from './internal.js';\n\n/** An oriented cutting plane: material on the `+normal` side is removed. */\nexport interface MiterPlane {\n origin: Vec3;\n normal: Vec3;\n}\n\n/**\n * General miter primitive: cut a sheet-metal part by a plane, removing the\n * material on the `+normal` side. The tool is a half-space block sized to the\n * part's bounding box, so the cut is exact regardless of part extent.\n *\n * @param part - the sheet metal part to cut.\n * @param plane - the miter plane definition.\n * @returns the updated part with the miter cut applied, or an error.\n */\nexport function miterCut(part: SheetMetalPart, plane: MiterPlane): Result<SheetMetalPart> {\n const solid = part.solid;\n if (solid === undefined) {\n return err(validationError('NO_SOLID', 'miterCut: part has no folded solid to cut'));\n }\n const tool = halfSpaceTool(solid, plane);\n if (!tool.ok) return tool;\n\n const result = cut(solid, tool.value);\n if (!result.ok) return result;\n\n return ok({ ...part, solid: normalizeSolid(result.value) });\n}\n\n/**\n * Auto corner-miter between two adjacent flanges. The miter plane bisects the two\n * flanges' fold-up directions at their shared corner and is offset by half the gap,\n * so the cut falls on the flat/flange regions and never crosses a bend patch. The\n * single bisector cut trims both flanges to a clean mitered corner.\n *\n * @param part - the sheet metal part to miter.\n * @param flangeIdA - the first flange ID.\n * @param flangeIdB - the second flange ID.\n * @param gap - the gap between flanges (default 0).\n * @returns the updated part with the miter corner applied, or an error.\n */\nexport function autoMiterCorner(\n part: SheetMetalPart,\n flangeIdA: string,\n flangeIdB: string,\n gap = 0\n): Result<SheetMetalPart> {\n if (part.solid === undefined) {\n return err(validationError('NO_SOLID', 'autoMiterCorner: part has no folded solid'));\n }\n if (!Number.isFinite(gap) || gap < 0) {\n return err(validationError('INVALID_GAP', `miter gap must be a finite, non-negative number, got ${gap}`));\n }\n const bendA = part.bends.find((b) => b.id === flangeIdA);\n const bendB = part.bends.find((b) => b.id === flangeIdB);\n if (bendA === undefined || bendB === undefined) {\n return err(\n validationError('UNKNOWN_FLANGE', `autoMiterCorner: flange '${flangeIdA}' or '${flangeIdB}' not found`)\n );\n }\n\n const axisA = vecNormalize(bendA.axisDir);\n const axisB = vecNormalize(bendB.axisDir);\n const corner = cornerPoint(bendA.axisOrigin, axisA, bendB.axisOrigin, axisB);\n\n // Each flange runs perpendicular to its own bend axis. The miter plane contains\n // the corner and is spanned by the two bend axes; its normal is their bisector in\n // the run plane, which is exactly the bisector of the two flange run directions.\n const planeNormal = miterNormal(axisA, axisB);\n if (planeNormal === undefined) {\n return err(\n validationError('PARALLEL_FLANGES', 'autoMiterCorner: flange bend axes are parallel; no corner to miter')\n );\n }\n const origin = vecAdd(corner, vecScale(planeNormal, gap * 0.5));\n\n const cutResult = miterCut(part, { origin, normal: planeNormal });\n if (!cutResult.ok) return cutResult;\n\n return ok({\n ...cutResult.value,\n miters: [...(part.miters ?? []), { flangeA: flangeIdA, flangeB: flangeIdB, gap }],\n });\n}\n\n/** Point on the line of bend A closest to the line of bend B (their shared corner). */\nfunction cornerPoint(originA: Vec3, dirA: Vec3, originB: Vec3, dirB: Vec3): Vec3 {\n const w0 = vecSub(originA, originB);\n const a = vecDot(dirA, dirA);\n const b = vecDot(dirA, dirB);\n const c = vecDot(dirB, dirB);\n const d = vecDot(dirA, w0);\n const e = vecDot(dirB, w0);\n const denom = a * c - b * b;\n if (Math.abs(denom) < 1e-9) return originA;\n const sc = (b * e - c * d) / denom;\n return vecAdd(originA, vecScale(dirA, sc));\n}\n\n/**\n * Normal of the miter plane between two flanges whose bend axes are `dirA`/`dirB`.\n * The plane is spanned by the two axes (it contains the corner edge of each\n * flange); its normal is `dirA × dirB` rotated to bisect — equivalently the\n * normalized sum of the two run directions, which both lie perpendicular to the\n * respective axis in the part's run plane.\n */\nfunction miterNormal(dirA: Vec3, dirB: Vec3): Vec3 | undefined {\n const cross = vecCross(dirA, dirB);\n if (Math.hypot(cross[0], cross[1], cross[2]) < 1e-9) return undefined;\n const sum = vecAdd(dirA, dirB);\n if (Math.hypot(sum[0], sum[1], sum[2]) < 1e-9) return vecNormalize(cross);\n return vecNormalize(sum);\n}\n\nconst Z_AXIS: Vec3 = [0, 0, 1];\n\nfunction halfSpaceTool(solid: Solid, plane: MiterPlane): Result<Solid> {\n const normal = vecNormalize(plane.normal);\n if (Math.hypot(normal[0], normal[1], normal[2]) < 1e-9) {\n return err(validationError('INVALID_PLANE', 'miter plane normal must be non-zero'));\n }\n\n const b = getBounds(solid);\n const diag = Math.hypot(b.xMax - b.xMin, b.yMax - b.yMin, b.zMax - b.zMin);\n const size = diag * 2 + 2;\n\n // A cube whose bottom (−Z) face lies on the XY plane through the origin, extending\n // up in +Z, then rotated so +Z maps onto the plane normal and translated onto the\n // plane origin. After alignment its open boundary is the miter plane and its body\n // fills the +normal half-space.\n let tool: Solid = box(size, size, size);\n tool = translate(tool, [-size / 2, -size / 2, 0]);\n\n const aligned = alignZTo(tool, normal);\n return ok(translate(aligned, plane.origin));\n}\n\n/** Rotate a shape so its local +Z axis points along `target` (about the origin). */\nfunction alignZTo(shape: Solid, target: Vec3): Solid {\n const dot = vecDot(Z_AXIS, target);\n if (dot > 1 - 1e-9) return shape;\n if (dot < -1 + 1e-9) {\n return rotate(shape, 180, { at: [0, 0, 0], axis: [1, 0, 0] });\n }\n const axis = vecNormalize(vecCross(Z_AXIS, target));\n const angleDeg = (Math.acos(Math.max(-1, Math.min(1, dot))) * 180) / Math.PI;\n return rotate(shape, angleDeg, { at: [0, 0, 0], axis });\n}\n"]}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { type Result } from '@faicad/faijs/brepjs-compat';
|
|
2
|
+
import type { FlatPattern, SheetMetalWarning } from './types.js';
|
|
3
|
+
import { type 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
|
+
/** Nesting options: the stock sheet plus optional margin, spacing, rotation and strategy. */
|
|
10
|
+
export interface NestOptions {
|
|
11
|
+
sheet: SheetSpec;
|
|
12
|
+
/** Clear border kept empty around the sheet edge (per side). Default `0`. */
|
|
13
|
+
margin?: number | undefined;
|
|
14
|
+
/** Minimum clear gap between any two placed parts (and the bbox padding used
|
|
15
|
+
* for the non-overlap test). Default `0`. */
|
|
16
|
+
spacing?: number | undefined;
|
|
17
|
+
/** Try a 90° rotation per part and keep whichever orientation fits/packs better. */
|
|
18
|
+
allowRotation?: boolean | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Packing strategy. Default `"bbox"`: each part is packed as its axis-aligned
|
|
21
|
+
* bounding box (the original {@link nest} behavior — fast, parts never interlock).
|
|
22
|
+
* `"nfp"` is true-shape / no-fit-polygon nesting: the actual outline polygons are
|
|
23
|
+
* packed so concave (L-shaped) parts interlock for higher material utilization.
|
|
24
|
+
* The NFP packer is a HEURISTIC (bottom-left-fill, largest-first) — not provably
|
|
25
|
+
* optimal — but it never overlaps parts and never drops a part silently.
|
|
26
|
+
*/
|
|
27
|
+
strategy?: 'bbox' | 'nfp' | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Where a single pattern lands on a sheet. For the bbox strategy `rotationDeg` is
|
|
31
|
+
* `0` or `90`; the true-shape (nfp) strategy may also emit `180` or `270`. `(x, y)`
|
|
32
|
+
* is the lower-left of the part's transformed bounding box in sheet coordinates.
|
|
33
|
+
*/
|
|
34
|
+
export interface Placement {
|
|
35
|
+
/** Index into the `patterns` array passed to {@link nest}. */
|
|
36
|
+
patternIndex: number;
|
|
37
|
+
/** Lower-left x of the part's (rotated) bounding box, in sheet coordinates. */
|
|
38
|
+
x: number;
|
|
39
|
+
/** Lower-left y of the part's (rotated) bounding box, in sheet coordinates. */
|
|
40
|
+
y: number;
|
|
41
|
+
rotationDeg: number;
|
|
42
|
+
}
|
|
43
|
+
/** One populated sheet produced by {@link nest}: where each pattern landed and how full it is. */
|
|
44
|
+
export interface NestSheet {
|
|
45
|
+
placements: Placement[];
|
|
46
|
+
/**
|
|
47
|
+
* Σ placed part areas ÷ usable-sheet area, in `(0, 1]`. "Usable" =
|
|
48
|
+
* `(width − 2·margin) × (height − 2·margin)`. For the `"bbox"` strategy the part
|
|
49
|
+
* area is its BOUNDING-BOX area (intra-bbox waste of a non-rectangular part is not
|
|
50
|
+
* credited — a conservative measure). For the `"nfp"` true-shape strategy it is the
|
|
51
|
+
* actual OUTLINE-POLYGON area, so an L-shaped part credits only its true material,
|
|
52
|
+
* making the two strategies' utilizations directly comparable on the same parts.
|
|
53
|
+
*/
|
|
54
|
+
utilization: number;
|
|
55
|
+
}
|
|
56
|
+
/** Outcome of {@link nest}: the placed sheets, any unplaced patterns, and warnings. */
|
|
57
|
+
export interface NestResult {
|
|
58
|
+
sheets: NestSheet[];
|
|
59
|
+
/** Indices of patterns too large for the usable sheet even rotated. */
|
|
60
|
+
unplaced: number[];
|
|
61
|
+
warnings: SheetMetalWarning[];
|
|
62
|
+
}
|
|
63
|
+
/** Axis-aligned bounding box of one pattern outline, in developed-plane coords. */
|
|
64
|
+
export interface Bbox {
|
|
65
|
+
minX: number;
|
|
66
|
+
minY: number;
|
|
67
|
+
width: number;
|
|
68
|
+
height: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Bounding-box nest: arrange developed flat patterns onto stock sheets to reduce
|
|
72
|
+
* waste. This is BOUNDING-BOX nesting — each part is packed as its axis-aligned
|
|
73
|
+
* outline bounding box, so parts never interlock and concave parts leave their
|
|
74
|
+
* bounding-box waste unused. True-shape (no-fit-polygon) nesting is a follow-up.
|
|
75
|
+
*
|
|
76
|
+
* Algorithm: a shelf (level) packing heuristic. Parts are sorted largest-first by
|
|
77
|
+
* bbox height then area, then placed left-to-right into horizontal shelves; a new
|
|
78
|
+
* shelf opens below when the current row is full, and a NEW sheet opens when the
|
|
79
|
+
* next shelf would overflow the usable height. With `allowRotation`, each part is
|
|
80
|
+
* tried both at 0° and 90° and the orientation that fits the current shelf (or,
|
|
81
|
+
* failing that, fits at all) is kept. A part larger than the usable sheet even
|
|
82
|
+
* rotated is reported in `unplaced` with a warning — never dropped silently, never
|
|
83
|
+
* retried in an infinite loop.
|
|
84
|
+
*
|
|
85
|
+
* @param patterns - developed flat patterns to place onto the sheets.
|
|
86
|
+
* @param options - sheet dimensions plus margin, spacing, rotation and strategy.
|
|
87
|
+
* @returns `Result<NestResult>` - the populated sheets plus any unplaced patterns and
|
|
88
|
+
* warnings, or an error for invalid sheet/options or an unmeasurable outline.
|
|
89
|
+
*/
|
|
90
|
+
export declare function nest(patterns: FlatPattern[], options: NestOptions): Result<NestResult>;
|
|
91
|
+
/**
|
|
92
|
+
* Axis-aligned bounding box of a pattern's OUTER outline wire, read from the wire
|
|
93
|
+
* vertices (each edge start point) — the same 2D-coordinate read the DXF/SVG writers
|
|
94
|
+
* use. Only the outer outline is packed; holes ride inside it.
|
|
95
|
+
*
|
|
96
|
+
* @param pattern - the flat pattern whose outer outline is measured.
|
|
97
|
+
* @returns `Result<Bbox>` - the axis-aligned bounding box of the outer outline, or an
|
|
98
|
+
* error if the outline is empty or degenerate.
|
|
99
|
+
*/
|
|
100
|
+
export declare function patternBbox(pattern: FlatPattern): Result<Bbox>;
|
|
101
|
+
/**
|
|
102
|
+
* Emit one fabrication-ready DXF for a single nested sheet: every pattern placed on
|
|
103
|
+
* `result.sheets[sheetIndex]` is translated and rotated onto the sheet (via the same
|
|
104
|
+
* bounding boxes the packer used) and written through {@link multiPatternToDXF}.
|
|
105
|
+
* `patterns` must be the SAME array passed to {@link nest} (placements index into it).
|
|
106
|
+
*
|
|
107
|
+
* @param result - the {@link NestResult} returned by {@link nest}.
|
|
108
|
+
* @param patterns - the same `FlatPattern[]` array that was passed to {@link nest}.
|
|
109
|
+
* @param sheetIndex - index into `result.sheets` of the sheet to emit.
|
|
110
|
+
* @param options - optional DXF options forwarded to {@link multiPatternToDXF}.
|
|
111
|
+
* @returns `Result<string>` - the DXF text for the selected sheet, or an error if the
|
|
112
|
+
* sheet index is invalid, a placement references a missing pattern, or a pattern
|
|
113
|
+
* outline cannot be measured.
|
|
114
|
+
*/
|
|
115
|
+
export declare function nestToDXF(result: NestResult, patterns: FlatPattern[], sheetIndex: number, options?: DxfOptions): Result<string>;
|
|
116
|
+
//# sourceMappingURL=nestFns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nestFns.d.ts","sourceRoot":"","sources":["../src/nestFns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAuD,MAAM,6BAA6B,CAAC;AAC/G,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAqB,KAAK,UAAU,EAAuC,MAAM,aAAa,CAAC;AAUtG,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,6FAA6F;AAC7F,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,SAAS,CAAC;IACjB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B;iDAC6C;IAC7C,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,oFAAoF;IACpF,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;CACvC;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,CAAC,EAAE,MAAM,CAAC;IACV,+EAA+E;IAC/E,CAAC,EAAE,MAAM,CAAC;IACV,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,kGAAkG;AAClG,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB;;;;;;;OAOG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,uFAAuF;AACvF,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAED,mFAAmF;AACnF,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAID;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,CA4EtF;AAsYD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,CAsB9D;AAoDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,WAAW,EAAE,EACvB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,UAAU,GACnB,MAAM,CAAC,MAAM,CAAC,CAgBhB"}
|