@faicad/sheetmetal 0.16.0 → 0.17.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 +1 -1
- package/dist/allowanceFns.d.ts.map +1 -1
- package/dist/allowanceFns.js +1 -1
- package/dist/allowanceFns.js.map +1 -1
- package/dist/api.d.ts +21 -20
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +20 -20
- package/dist/api.js.map +1 -1
- package/dist/authorFns.d.ts +2 -2
- package/dist/authorFns.d.ts.map +1 -1
- package/dist/authorFns.js +61 -51
- package/dist/authorFns.js.map +1 -1
- package/dist/bendTableFns.d.ts +1 -1
- package/dist/bendTableFns.d.ts.map +1 -1
- package/dist/bendTableFns.js +1 -1
- package/dist/bendTableFns.js.map +1 -1
- package/dist/contourFlangeFns.d.ts +5 -4
- package/dist/contourFlangeFns.d.ts.map +1 -1
- package/dist/contourFlangeFns.js +59 -44
- package/dist/contourFlangeFns.js.map +1 -1
- package/dist/cutoutFns.d.ts +5 -5
- package/dist/cutoutFns.d.ts.map +1 -1
- package/dist/cutoutFns.js +22 -14
- package/dist/cutoutFns.js.map +1 -1
- package/dist/dxfFns.d.ts +1 -1
- package/dist/dxfFns.d.ts.map +1 -1
- package/dist/dxfFns.js +1 -1
- package/dist/dxfFns.js.map +1 -1
- package/dist/facade.d.ts +43 -42
- package/dist/facade.d.ts.map +1 -1
- package/dist/facade.js +83 -83
- package/dist/facade.js.map +1 -1
- package/dist/featureTreeFns.d.ts +1 -1
- package/dist/featureTreeFns.d.ts.map +1 -1
- package/dist/featureTreeFns.js +1 -1
- package/dist/featureTreeFns.js.map +1 -1
- package/dist/foldFns.d.ts +3 -3
- package/dist/foldFns.d.ts.map +1 -1
- package/dist/foldFns.js +12 -12
- package/dist/foldFns.js.map +1 -1
- package/dist/foreignUnfoldFns.d.ts +2 -1
- package/dist/foreignUnfoldFns.d.ts.map +1 -1
- package/dist/foreignUnfoldFns.js +21 -10
- package/dist/foreignUnfoldFns.js.map +1 -1
- package/dist/formFns.d.ts +4 -4
- package/dist/formFns.d.ts.map +1 -1
- package/dist/formFns.js +91 -76
- package/dist/formFns.js.map +1 -1
- package/dist/geometryOps.d.ts +32 -0
- package/dist/geometryOps.d.ts.map +1 -0
- package/dist/geometryOps.js +83 -0
- package/dist/geometryOps.js.map +1 -0
- package/dist/hemFns.d.ts +2 -2
- package/dist/hemFns.d.ts.map +1 -1
- package/dist/hemFns.js +4 -4
- package/dist/hemFns.js.map +1 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/jogFns.d.ts +2 -2
- package/dist/jogFns.d.ts.map +1 -1
- package/dist/jogFns.js +6 -6
- package/dist/jogFns.js.map +1 -1
- package/dist/loftedFlangeFns.d.ts +2 -2
- package/dist/loftedFlangeFns.d.ts.map +1 -1
- package/dist/loftedFlangeFns.js +35 -25
- package/dist/loftedFlangeFns.js.map +1 -1
- package/dist/miterFns.d.ts +3 -3
- package/dist/miterFns.d.ts.map +1 -1
- package/dist/miterFns.js +30 -19
- package/dist/miterFns.js.map +1 -1
- package/dist/nestFns.d.ts +1 -1
- package/dist/nestFns.d.ts.map +1 -1
- package/dist/nestFns.js +1 -1
- package/dist/nestFns.js.map +1 -1
- package/dist/polygonFns.d.ts +2 -2
- package/dist/polygonFns.d.ts.map +1 -1
- package/dist/polygonFns.js +1 -1
- package/dist/polygonFns.js.map +1 -1
- package/dist/reliefFns.d.ts +4 -4
- package/dist/reliefFns.d.ts.map +1 -1
- package/dist/reliefFns.js +39 -31
- package/dist/reliefFns.js.map +1 -1
- package/dist/reportFns.d.ts +1 -1
- package/dist/reportFns.d.ts.map +1 -1
- package/dist/reportFns.js +1 -1
- package/dist/reportFns.js.map +1 -1
- package/dist/tabFns.d.ts +3 -3
- package/dist/tabFns.d.ts.map +1 -1
- package/dist/tabFns.js +21 -13
- package/dist/tabFns.js.map +1 -1
- package/dist/types.d.ts +21 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/unfoldFns.d.ts +1 -1
- package/dist/unfoldFns.d.ts.map +1 -1
- package/dist/unfoldFns.js +2 -2
- package/dist/unfoldFns.js.map +1 -1
- package/dist/validateFns.d.ts.map +1 -1
- package/dist/validateFns.js +4 -6
- package/dist/validateFns.js.map +1 -1
- package/package.json +2 -2
package/dist/reliefFns.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Result } from '@faicad/faijs/
|
|
1
|
+
import { type Result } from '@faicad/faijs/api';
|
|
2
2
|
import type { ReliefSpec, SheetMetalPart } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Add a bend relief to a flange: a small slot cut into the PARENT flat at each end
|
|
@@ -16,7 +16,7 @@ import type { ReliefSpec, SheetMetalPart } from './types.js';
|
|
|
16
16
|
* @param spec - the relief specification (default rectangular).
|
|
17
17
|
* @returns the updated part with the bend relief recorded, or an error.
|
|
18
18
|
*/
|
|
19
|
-
export declare function addBendRelief(part: SheetMetalPart, flangeId: string, spec?: ReliefSpec): Result<SheetMetalPart
|
|
19
|
+
export declare function addBendRelief(part: SheetMetalPart, flangeId: string, spec?: ReliefSpec): Promise<Result<SheetMetalPart>>;
|
|
20
20
|
/**
|
|
21
21
|
* Add a bend relief to every partial-span bend (a flange that does not span its
|
|
22
22
|
* full parent edge). Full-span flanges are skipped — they have no mid-edge bend
|
|
@@ -26,7 +26,7 @@ export declare function addBendRelief(part: SheetMetalPart, flangeId: string, sp
|
|
|
26
26
|
* @param spec - the relief specification (default rectangular).
|
|
27
27
|
* @returns the updated part with all needed bend reliefs, or an error.
|
|
28
28
|
*/
|
|
29
|
-
export declare function autoBendReliefs(part: SheetMetalPart, spec?: ReliefSpec): Result<SheetMetalPart
|
|
29
|
+
export declare function autoBendReliefs(part: SheetMetalPart, spec?: ReliefSpec): Promise<Result<SheetMetalPart>>;
|
|
30
30
|
/**
|
|
31
31
|
* Corner relief between two adjacent flanges: a notch cut at their shared corner so
|
|
32
32
|
* the two upright flanges clear each other once folded — the alternative to a 45°
|
|
@@ -40,5 +40,5 @@ export declare function autoBendReliefs(part: SheetMetalPart, spec?: ReliefSpec)
|
|
|
40
40
|
* @param spec - the relief specification (default rectangular).
|
|
41
41
|
* @returns the updated part with the corner relief recorded, or an error.
|
|
42
42
|
*/
|
|
43
|
-
export declare function cornerRelief(part: SheetMetalPart, flangeIdA: string, flangeIdB: string, spec?: ReliefSpec): Result<SheetMetalPart
|
|
43
|
+
export declare function cornerRelief(part: SheetMetalPart, flangeIdA: string, flangeIdB: string, spec?: ReliefSpec): Promise<Result<SheetMetalPart>>;
|
|
44
44
|
//# sourceMappingURL=reliefFns.d.ts.map
|
package/dist/reliefFns.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reliefFns.d.ts","sourceRoot":"","sources":["../src/reliefFns.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reliefFns.d.ts","sourceRoot":"","sources":["../src/reliefFns.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,KAAK,MAAM,EAAmL,MAAM,mBAAmB,CAAC;AAChO,OAAO,KAAK,EAIV,UAAU,EACV,cAAc,EACf,MAAM,YAAY,CAAC;AAWpB;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,UAAqC,GAC1C,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAmDjC;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,cAAc,EACpB,IAAI,GAAE,UAAqC,GAC1C,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAajC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,cAAc,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,UAAqC,GAC1C,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAgEjC"}
|
package/dist/reliefFns.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getBounds } from './geometryOps.js';
|
|
2
|
+
import { translate } from './geometryOps.js';
|
|
3
|
+
import { ok, err, kernelError, validationError, box, cut, rotate, getSolids, isValid, curveStartPoint, curveEndPoint, vecAdd, vecSub, vecScale, vecCross, vecDot, vecNormalize } from '@faicad/faijs/api';
|
|
2
4
|
import { normalizeSolid } from './internal.js';
|
|
3
5
|
import { unfold } from './unfoldFns.js';
|
|
4
6
|
import { developedLength } from './allowanceFns.js';
|
|
@@ -20,7 +22,7 @@ const Z_AXIS = [0, 0, 1];
|
|
|
20
22
|
* @param spec - the relief specification (default rectangular).
|
|
21
23
|
* @returns the updated part with the bend relief recorded, or an error.
|
|
22
24
|
*/
|
|
23
|
-
export function addBendRelief(part, flangeId, spec = { shape: 'rectangular' }) {
|
|
25
|
+
export async function addBendRelief(part, flangeId, spec = { shape: 'rectangular' }) {
|
|
24
26
|
if (part.solid === undefined) {
|
|
25
27
|
return err(validationError('NO_SOLID', 'addBendRelief: part has no folded solid to cut'));
|
|
26
28
|
}
|
|
@@ -33,16 +35,19 @@ export function addBendRelief(part, flangeId, spec = { shape: 'rectangular' }) {
|
|
|
33
35
|
if (!dims.ok)
|
|
34
36
|
return dims;
|
|
35
37
|
const { width, depth } = dims.value;
|
|
36
|
-
|
|
37
|
-
if (geo === undefined) {
|
|
38
|
-
return err(validationError('BEND_RELIEF_NOT_NEEDED', `addBendRelief: bend '${flangeId}' spans its full parent edge; no relief end to cut`));
|
|
39
|
-
}
|
|
38
|
+
let geo;
|
|
40
39
|
let solid = part.solid;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
return
|
|
45
|
-
|
|
40
|
+
try {
|
|
41
|
+
geo = await bendReliefGeometry(part, flange, bend, width, depth);
|
|
42
|
+
if (geo === undefined) {
|
|
43
|
+
return err(validationError('BEND_RELIEF_NOT_NEEDED', `addBendRelief: bend '${flangeId}' spans its full parent edge; no relief end to cut`));
|
|
44
|
+
}
|
|
45
|
+
for (const tool of geo.tools3d) {
|
|
46
|
+
solid = normalizeSolid((await cut(solid, tool)));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
return err(kernelError('CUT_FAILED', `bend relief: ${e instanceof Error ? e.message : String(e)}`));
|
|
46
51
|
}
|
|
47
52
|
if (!isValid(solid) || getSolids(solid).length > 1) {
|
|
48
53
|
return err(validationError('RELIEF_SEVERED_SOLID', `addBendRelief: slot (width ${width}, depth ${depth}) severs flange '${flangeId}' into multiple bodies; reduce width/depth`));
|
|
@@ -66,12 +71,12 @@ export function addBendRelief(part, flangeId, spec = { shape: 'rectangular' }) {
|
|
|
66
71
|
* @param spec - the relief specification (default rectangular).
|
|
67
72
|
* @returns the updated part with all needed bend reliefs, or an error.
|
|
68
73
|
*/
|
|
69
|
-
export function autoBendReliefs(part, spec = { shape: 'rectangular' }) {
|
|
74
|
+
export async function autoBendReliefs(part, spec = { shape: 'rectangular' }) {
|
|
70
75
|
let current = part;
|
|
71
76
|
for (const flange of part.flanges) {
|
|
72
77
|
// Let addBendRelief own the needed/not-needed decision rather than recomputing
|
|
73
78
|
// the relief geometry here just to peek (which also allocated throwaway tools).
|
|
74
|
-
const next = addBendRelief(current, flange.id, spec);
|
|
79
|
+
const next = await addBendRelief(current, flange.id, spec);
|
|
75
80
|
if (!next.ok) {
|
|
76
81
|
if (next.error.code === 'BEND_RELIEF_NOT_NEEDED')
|
|
77
82
|
continue;
|
|
@@ -94,7 +99,7 @@ export function autoBendReliefs(part, spec = { shape: 'rectangular' }) {
|
|
|
94
99
|
* @param spec - the relief specification (default rectangular).
|
|
95
100
|
* @returns the updated part with the corner relief recorded, or an error.
|
|
96
101
|
*/
|
|
97
|
-
export function cornerRelief(part, flangeIdA, flangeIdB, spec = { shape: 'rectangular' }) {
|
|
102
|
+
export async function cornerRelief(part, flangeIdA, flangeIdB, spec = { shape: 'rectangular' }) {
|
|
98
103
|
if (part.solid === undefined) {
|
|
99
104
|
return err(validationError('NO_SOLID', 'cornerRelief: part has no folded solid'));
|
|
100
105
|
}
|
|
@@ -120,11 +125,14 @@ export function cornerRelief(part, flangeIdA, flangeIdB, spec = { shape: 'rectan
|
|
|
120
125
|
const flangeA = part.flanges.find((f) => f.id === flangeIdA);
|
|
121
126
|
const flangeB = part.flanges.find((f) => f.id === flangeIdB);
|
|
122
127
|
const geo = cornerReliefGeometry(part, axisA, axisB, bendA, bendB, side, flangeA, flangeB);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
+
let solid;
|
|
129
|
+
try {
|
|
130
|
+
const tool = await squareTool(part.solid, geo.cornerXY, side);
|
|
131
|
+
solid = normalizeSolid((await cut(part.solid, tool)));
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
return err(kernelError('CUT_FAILED', `corner relief: ${e instanceof Error ? e.message : String(e)}`));
|
|
135
|
+
}
|
|
128
136
|
if (!isValid(solid) || getSolids(solid).length > 1) {
|
|
129
137
|
return err(validationError('RELIEF_SEVERED_SOLID', `cornerRelief: notch (side ${side}) severs the part into multiple bodies; reduce width/depth`));
|
|
130
138
|
}
|
|
@@ -174,7 +182,7 @@ function reliefDims(spec, thickness, dev) {
|
|
|
174
182
|
* way. Returns `undefined` when the flange spans its full parent edge (no mid-edge
|
|
175
183
|
* end to relieve).
|
|
176
184
|
*/
|
|
177
|
-
function bendReliefGeometry(part, flange, bend, width, depth) {
|
|
185
|
+
async function bendReliefGeometry(part, flange, bend, width, depth) {
|
|
178
186
|
if (part.solid === undefined)
|
|
179
187
|
return undefined;
|
|
180
188
|
const axis = vecNormalize(bend.axisDir);
|
|
@@ -197,7 +205,7 @@ function bendReliefGeometry(part, flange, bend, width, depth) {
|
|
|
197
205
|
ends.push(span);
|
|
198
206
|
for (const s of ends) {
|
|
199
207
|
const at = vecAdd(edgeBase, vecScale(axis, s));
|
|
200
|
-
tools3d.push(slotTool3d(at, axis, inward, width, depth, part.thickness));
|
|
208
|
+
tools3d.push(await slotTool3d(at, axis, inward, width, depth, part.thickness));
|
|
201
209
|
}
|
|
202
210
|
const notches2d = bendNotches2d(part, flange, width, depth, atStart, atEnd);
|
|
203
211
|
return { tools3d, notches2d };
|
|
@@ -324,7 +332,7 @@ function parentEdgeLength(part, flange) {
|
|
|
324
332
|
* whose `width` axis runs along the parent edge and `depth` axis points inward,
|
|
325
333
|
* placed so it straddles the edge point `at` and cuts through the parent surface.
|
|
326
334
|
*/
|
|
327
|
-
function slotTool3d(at, along, inward, width, depth, thickness) {
|
|
335
|
+
async function slotTool3d(at, along, inward, width, depth, thickness) {
|
|
328
336
|
const margin = thickness + 2;
|
|
329
337
|
// Canonical: width along +X, depth along +Y, height along +Z (cuts through Z).
|
|
330
338
|
// Rotating by `deg` sends +X→along and +Y→(along rotated +90° = [-along.y, along.x]).
|
|
@@ -332,10 +340,10 @@ function slotTool3d(at, along, inward, width, depth, thickness) {
|
|
|
332
340
|
// into the parent rather than out into the flange.
|
|
333
341
|
const rotPerp = [-along[1], along[0], 0];
|
|
334
342
|
const y0 = vecDot(rotPerp, inward) >= 0 ? 0 : -depth;
|
|
335
|
-
let tool = box(width, depth, thickness + 2 * margin);
|
|
336
|
-
tool = translate(tool, [-width / 2, y0, -margin]);
|
|
337
|
-
tool = orientXY(tool, along);
|
|
338
|
-
return translate(tool, [at[0], at[1], 0]);
|
|
343
|
+
let tool = (await box({ width, depth, height: thickness + 2 * margin }));
|
|
344
|
+
tool = (await translate(tool, [-width / 2, y0, -margin]));
|
|
345
|
+
tool = await orientXY(tool, along);
|
|
346
|
+
return (await translate(tool, [at[0], at[1], 0]));
|
|
339
347
|
}
|
|
340
348
|
/**
|
|
341
349
|
* Square corner-relief tool: a `side × side × (height + margins)` block CENTRED on
|
|
@@ -344,19 +352,19 @@ function slotTool3d(at, along, inward, width, depth, thickness) {
|
|
|
344
352
|
* base corners for the supported perpendicular flange shapes, so the tool stays
|
|
345
353
|
* axis-aligned.
|
|
346
354
|
*/
|
|
347
|
-
function squareTool(solid, cornerXY, side) {
|
|
355
|
+
async function squareTool(solid, cornerXY, side) {
|
|
348
356
|
const b = getBounds(solid);
|
|
349
357
|
const margin = 2;
|
|
350
358
|
const h = side / 2;
|
|
351
|
-
const tool = box(side, side, b.zMax - b.zMin + 2 * margin);
|
|
352
|
-
return translate(tool, [cornerXY[0] - h, cornerXY[1] - h, b.zMin - margin]);
|
|
359
|
+
const tool = (await box({ width: side, depth: side, height: b.zMax - b.zMin + 2 * margin }));
|
|
360
|
+
return (await translate(tool, [cornerXY[0] - h, cornerXY[1] - h, b.zMin - margin]));
|
|
353
361
|
}
|
|
354
362
|
/** Rotate a shape about +Z so its local +X axis points along `xT` (in the XY plane). */
|
|
355
|
-
function orientXY(shape, xT) {
|
|
363
|
+
async function orientXY(shape, xT) {
|
|
356
364
|
const deg = (Math.atan2(xT[1], xT[0]) * 180) / Math.PI;
|
|
357
365
|
if (Math.abs(deg) < 1e-9)
|
|
358
366
|
return shape;
|
|
359
|
-
return rotate(shape, deg, { at: [0, 0, 0], axis: Z_AXIS });
|
|
367
|
+
return (await rotate(shape, deg, { at: [0, 0, 0], axis: Z_AXIS }));
|
|
360
368
|
}
|
|
361
369
|
function toPt2(v) {
|
|
362
370
|
return [v[0], v[1]];
|
package/dist/reliefFns.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reliefFns.js","sourceRoot":"","sources":["../src/reliefFns.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 getSolids,\n isValid,\n curveStartPoint,\n curveEndPoint,\n vecAdd,\n vecSub,\n vecScale,\n vecCross,\n vecDot,\n vecNormalize,\n} from '@faicad/faijs/brepjs-compat';\nimport type {\n BendFeature,\n FlangeFeature,\n ReliefFeature,\n ReliefSpec,\n SheetMetalPart,\n} from './types.js';\nimport { normalizeSolid } from './internal.js';\nimport { unfold } from './unfoldFns.js';\nimport { developedLength } from './allowanceFns.js';\n\nconst EPS = 1e-6;\nconst Z_AXIS: Vec3 = [0, 0, 1];\n\ntype Pt2 = [number, number];\ntype NotchRect = [number, number, number, number];\n\n/**\n * Add a bend relief to a flange: a small slot cut into the PARENT flat at each end\n * of the bend line that does not reach the parent edge endpoint (a partial/offset\n * flange). Without it the parent material tears at the corner where the bend\n * terminates mid-edge. Each slot is `width` (≈ thickness) along the parent edge by\n * `depth` (≈ developed length + clearance) into the parent flat, cut from the 3D\n * solid and recorded so {@link unfold} replays the 2D notch.\n *\n * A relief is a recorded feature replayed by unfold — exactly the pattern\n * {@link autoMiterCorner} establishes for corner miters.\n *\n * @param part - the sheet metal part to cut.\n * @param flangeId - the flange (bend) ID to relieve.\n * @param spec - the relief specification (default rectangular).\n * @returns the updated part with the bend relief recorded, or an error.\n */\nexport function addBendRelief(\n part: SheetMetalPart,\n flangeId: string,\n spec: ReliefSpec = { shape: 'rectangular' }\n): Result<SheetMetalPart> {\n if (part.solid === undefined) {\n return err(validationError('NO_SOLID', 'addBendRelief: part has no folded solid to cut'));\n }\n const flange = part.flanges.find((f) => f.id === flangeId);\n const bend = part.bends.find((b) => b.id === flangeId);\n if (flange === undefined || bend === undefined) {\n return err(validationError('UNKNOWN_FLANGE', `addBendRelief: flange '${flangeId}' not found`));\n }\n\n const dims = reliefDims(spec, part.thickness, devOf(bend, part.thickness));\n if (!dims.ok) return dims;\n const { width, depth } = dims.value;\n\n const geo = bendReliefGeometry(part, flange, bend, width, depth);\n if (geo === undefined) {\n return err(\n validationError(\n 'BEND_RELIEF_NOT_NEEDED',\n `addBendRelief: bend '${flangeId}' spans its full parent edge; no relief end to cut`\n )\n );\n }\n\n let solid = part.solid;\n for (const tool of geo.tools3d) {\n const result = cut(solid, tool);\n if (!result.ok) return result;\n solid = normalizeSolid(result.value);\n }\n if (!isValid(solid) || getSolids(solid).length > 1) {\n return err(\n validationError(\n 'RELIEF_SEVERED_SOLID',\n `addBendRelief: slot (width ${width}, depth ${depth}) severs flange '${flangeId}' into multiple bodies; reduce width/depth`\n )\n );\n }\n\n const feature: ReliefFeature = {\n kind: 'bend',\n shape: spec.shape,\n flangeA: flangeId,\n width,\n depth,\n notches: geo.notches2d,\n };\n\n return ok({ ...part, solid, reliefs: [...(part.reliefs ?? []), feature] });\n}\n\n/**\n * Add a bend relief to every partial-span bend (a flange that does not span its\n * full parent edge). Full-span flanges are skipped — they have no mid-edge bend\n * terminus to relieve. Convenience over calling {@link addBendRelief} per flange.\n *\n * @param part - the sheet metal part to process.\n * @param spec - the relief specification (default rectangular).\n * @returns the updated part with all needed bend reliefs, or an error.\n */\nexport function autoBendReliefs(\n part: SheetMetalPart,\n spec: ReliefSpec = { shape: 'rectangular' }\n): Result<SheetMetalPart> {\n let current = part;\n for (const flange of part.flanges) {\n // Let addBendRelief own the needed/not-needed decision rather than recomputing\n // the relief geometry here just to peek (which also allocated throwaway tools).\n const next = addBendRelief(current, flange.id, spec);\n if (!next.ok) {\n if (next.error.code === 'BEND_RELIEF_NOT_NEEDED') continue;\n return next;\n }\n current = next.value;\n }\n return ok(current);\n}\n\n/**\n * Corner relief between two adjacent flanges: a notch cut at their shared corner so\n * the two upright flanges clear each other once folded — the alternative to a 45°\n * miter for the same corner {@link autoMiterCorner} handles. The notch is cut from\n * the solid and recorded as a {@link ReliefFeature}; the corner is also recorded in\n * `miters` (gap 0) so the collision check treats the interference as resolved.\n *\n * @param part - the sheet metal part to cut.\n * @param flangeIdA - the first flange ID.\n * @param flangeIdB - the second flange ID.\n * @param spec - the relief specification (default rectangular).\n * @returns the updated part with the corner relief recorded, or an error.\n */\nexport function cornerRelief(\n part: SheetMetalPart,\n flangeIdA: string,\n flangeIdB: string,\n spec: ReliefSpec = { shape: 'rectangular' }\n): Result<SheetMetalPart> {\n if (part.solid === undefined) {\n return err(validationError('NO_SOLID', 'cornerRelief: part has no folded solid'));\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', `cornerRelief: flange '${flangeIdA}' or '${flangeIdB}' not found`)\n );\n }\n const axisA = vecNormalize(bendA.axisDir);\n const axisB = vecNormalize(bendB.axisDir);\n if (Math.hypot(...vecCross(axisA, axisB)) < EPS) {\n return err(\n validationError('PARALLEL_FLANGES', 'cornerRelief: flange bend axes are parallel; no corner to relieve')\n );\n }\n\n const devClearance = Math.max(devOf(bendA, part.thickness), devOf(bendB, part.thickness));\n const dims = reliefDims(spec, part.thickness, devClearance);\n if (!dims.ok) return dims;\n const { depth } = dims.value;\n\n // The corner notch is a square; its side is the user's `width` when given, else\n // the depth clearance. Both recorded dims are the actual side so a consumer\n // reading width/depth reconstructs the real cut (not a stale clearance value).\n const side = spec.width ?? depth;\n\n const flangeA = part.flanges.find((f) => f.id === flangeIdA);\n const flangeB = part.flanges.find((f) => f.id === flangeIdB);\n const geo = cornerReliefGeometry(part, axisA, axisB, bendA, bendB, side, flangeA, flangeB);\n const tool = squareTool(part.solid, geo.cornerXY, side);\n const result = cut(part.solid, tool);\n if (!result.ok) return result;\n\n const solid = normalizeSolid(result.value);\n if (!isValid(solid) || getSolids(solid).length > 1) {\n return err(\n validationError(\n 'RELIEF_SEVERED_SOLID',\n `cornerRelief: notch (side ${side}) severs the part into multiple bodies; reduce width/depth`\n )\n );\n }\n\n const feature: ReliefFeature = {\n kind: 'corner',\n shape: spec.shape,\n flangeA: flangeIdA,\n flangeB: flangeIdB,\n width: side,\n depth: side,\n notches: [geo.notch2d],\n };\n\n return ok({\n ...part,\n solid,\n reliefs: [...(part.reliefs ?? []), feature],\n miters: [...(part.miters ?? []), { flangeA: flangeIdA, flangeB: flangeIdB, gap: 0 }],\n });\n}\n\n/**\n * Developed strip width of a bend, via the canonical {@link developedLength} so the\n * default relief depth tracks the same neutral-axis arc length the unfold uses.\n * Falls back to the inner radius if the rule is degenerate (the depth is only a\n * heuristic clearance, so a positive fallback keeps a valid default tool).\n */\nfunction devOf(bend: BendFeature, thickness: number): number {\n const dev = developedLength(bend.angleDeg, thickness, bend.rule);\n return dev.ok ? dev.value : bend.rule.innerRadius;\n}\n\n/** Resolve relief width/depth from the spec, defaulting to thickness / (dev + T). */\nfunction reliefDims(\n spec: ReliefSpec,\n thickness: number,\n dev: number\n): Result<{ width: number; depth: number }> {\n const width = spec.width ?? thickness;\n const depth = spec.depth ?? dev + thickness;\n if (!Number.isFinite(width) || width <= 0) {\n return err(validationError('INVALID_RELIEF_WIDTH', `relief width must be positive, got ${width}`));\n }\n if (!Number.isFinite(depth) || depth <= 0) {\n return err(validationError('INVALID_RELIEF_DEPTH', `relief depth must be positive, got ${depth}`));\n }\n return ok({ width, depth });\n}\n\ninterface BendReliefGeometry {\n tools3d: Solid[];\n notches2d: NotchRect[];\n}\n\n/**\n * Build the 3D cut tools and 2D developed-plane notch rectangles for a bend relief.\n * In 3D each slot straddles a bend-line end on the parent edge and cuts inward\n * (toward the parent interior) by `depth`, full thickness. In 2D the slot sits at\n * the corresponding bend-line endpoint and cuts into the parent rectangle the same\n * way. Returns `undefined` when the flange spans its full parent edge (no mid-edge\n * end to relieve).\n */\nfunction bendReliefGeometry(\n part: SheetMetalPart,\n flange: FlangeFeature,\n bend: BendFeature,\n width: number,\n depth: number\n): BendReliefGeometry | undefined {\n if (part.solid === undefined) return undefined;\n\n const axis = vecNormalize(bend.axisDir);\n const span = flange.span;\n const offset = flange.offset ?? flange.baseEdge.offset ?? 0;\n const edgeLen = parentEdgeLength(part, flange);\n\n const atStart = offset > EPS;\n const atEnd = offset + span < edgeLen - EPS;\n if (!atStart && !atEnd) return undefined;\n\n const b = getBounds(part.solid);\n const center: Vec3 = [(b.xMin + b.xMax) / 2, (b.yMin + b.yMax) / 2, 0];\n const inward = inwardRun(bend.axisOrigin, axis, center);\n const edgeBase: Vec3 = [bend.axisOrigin[0], bend.axisOrigin[1], 0];\n\n const tools3d: Solid[] = [];\n const ends: number[] = [];\n if (atStart) ends.push(0);\n if (atEnd) ends.push(span);\n for (const s of ends) {\n const at = vecAdd(edgeBase, vecScale(axis, s));\n tools3d.push(slotTool3d(at, axis, inward, width, depth, part.thickness));\n }\n\n const notches2d = bendNotches2d(part, flange, width, depth, atStart, atEnd);\n return { tools3d, notches2d };\n}\n\n/**\n * Developed-plane notch rectangles for a bend relief, located at the bend-line ends\n * and cutting into the PARENT rectangle (opposite the develop-out direction). The\n * unfold is the source of truth for 2D placement, so we read the developed bend\n * line straight from it and offset inward.\n */\nfunction bendNotches2d(\n part: SheetMetalPart,\n flange: FlangeFeature,\n width: number,\n depth: number,\n atStart: boolean,\n atEnd: boolean\n): NotchRect[] {\n const placed = developedBendLine(part, flange);\n if (placed === undefined) return [];\n const { bendA, bendB, inward } = placed;\n const len = Math.max(dist2(bendA, bendB), EPS);\n const along: Pt2 = [(bendB[0] - bendA[0]) / len, (bendB[1] - bendA[1]) / len];\n const half = width / 2;\n const make = (p: Pt2): NotchRect => {\n const c0: Pt2 = [p[0] - along[0] * half, p[1] - along[1] * half];\n const c1: Pt2 = [\n p[0] + along[0] * half + inward[0] * depth,\n p[1] + along[1] * half + inward[1] * depth,\n ];\n return [Math.min(c0[0], c1[0]), Math.min(c0[1], c1[1]), Math.max(c0[0], c1[0]), Math.max(c0[1], c1[1])];\n };\n const notches: NotchRect[] = [];\n if (atStart) notches.push(make(bendA));\n if (atEnd) notches.push(make(bendB));\n return notches;\n}\n\ninterface DevelopedBendLine {\n bendA: Pt2;\n bendB: Pt2;\n inward: Pt2;\n}\n\n/**\n * Locate a flange's bend line in the developed plane by re-running the unfold and\n * matching by flange id — the unfold owns 2D placement, so the notch always\n * coincides with the real developed geometry. Matching by id (not by geometric\n * signature) keeps placement correct when several flanges share the same\n * span/angle/direction, as in {@link autoBendReliefs}.\n */\nfunction developedBendLine(part: SheetMetalPart, flange: FlangeFeature): DevelopedBendLine | undefined {\n const unfolded = unfold({ ...part, reliefs: undefined });\n if (!unfolded.ok) return undefined;\n const bl = unfolded.value.pattern.bendLines.find((b) => b.id === flange.id);\n if (bl === undefined) return undefined;\n const a = toPt2(curveStartPoint(bl.line));\n const b = toPt2(curveEndPoint(bl.line));\n return { bendA: a, bendB: b, inward: bl.inward };\n}\n\ninterface CornerReliefGeometry {\n cornerXY: Pt2;\n notch2d: NotchRect;\n}\n\n/**\n * Corner relief geometry: the shared-corner point of the two flanges' bend lines in\n * world XY, plus a square developed-plane notch CENTRED on the reflex corner of the\n * two flanges (`(baseLength, width)` for the classic +X/+Y L). Centring the square\n * on the corner bites the base and both flange roots equally, so the developed\n * outline stays a single closed loop (a notch landing its far corner exactly on the\n * reflex vertex would pinch the boundary).\n */\nfunction cornerReliefGeometry(\n part: SheetMetalPart,\n axisA: Vec3,\n axisB: Vec3,\n bendA: BendFeature,\n bendB: BendFeature,\n side: number,\n flangeA: FlangeFeature | undefined,\n flangeB: FlangeFeature | undefined\n): CornerReliefGeometry {\n const corner = cornerPoint(bendA.axisOrigin, axisA, bendB.axisOrigin, axisB);\n const [cx, cy] = developedCorner(part, flangeA, flangeB);\n const h = side / 2;\n const notch2d: NotchRect = [cx - h, cy - h, cx + h, cy + h];\n return { cornerXY: [corner[0], corner[1]], notch2d };\n}\n\n/**\n * Developed-plane base corner shared by two perpendicular base flanges, picked from\n * their sides: an `xmax` flange's corner x is `baseLength`, an `xmin`'s is `0`; a\n * `ymax`'s corner y is `width`, a `ymin`'s is `0`. Defaults to the +X/+Y corner.\n */\nfunction developedCorner(\n part: SheetMetalPart,\n flangeA: FlangeFeature | undefined,\n flangeB: FlangeFeature | undefined\n): Pt2 {\n let cx = part.baseLength;\n let cy = part.width;\n for (const f of [flangeA, flangeB]) {\n const side = f?.baseEdge.side;\n if (side === 'xmin') cx = 0;\n else if (side === 'xmax') cx = part.baseLength;\n else if (side === 'ymin') cy = 0;\n else if (side === 'ymax') cy = part.width;\n }\n return [cx, cy];\n}\n\n/** Point on bend A's line closest to bend B's line (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/** In-plane perpendicular to `axis` pointing toward the part center (into the parent). */\nfunction inwardRun(axisOrigin: Vec3, axis: Vec3, center: Vec3): Vec3 {\n const perp = vecNormalize(vecCross(axis, Z_AXIS));\n const toCenter: Vec3 = [center[0] - axisOrigin[0], center[1] - axisOrigin[1], 0];\n const dot = perp[0] * toCenter[0] + perp[1] * toCenter[1];\n return dot >= 0 ? perp : [-perp[0], -perp[1], -perp[2]];\n}\n\n/** Parent-edge length the flange folds off (base edge for root flanges). */\nfunction parentEdgeLength(part: SheetMetalPart, flange: FlangeFeature): number {\n const side = flange.baseEdge.side ?? 'xmax';\n const onWidthEdge = side === 'xmax' || side === 'xmin';\n const parentId = flange.baseEdge.parentId;\n if (parentId === undefined || parentId === 'root' || parentId === 'face-0') {\n return onWidthEdge ? part.width : part.baseLength;\n }\n const parent = part.flanges.find((f) => f.id === parentId);\n if (parent === undefined) return onWidthEdge ? part.width : part.baseLength;\n return onWidthEdge ? parent.length : parent.span;\n}\n\n/**\n * A box tool for a bend-relief slot: a `width × depth × (thickness + margins)` block\n * whose `width` axis runs along the parent edge and `depth` axis points inward,\n * placed so it straddles the edge point `at` and cuts through the parent surface.\n */\nfunction slotTool3d(\n at: Vec3,\n along: Vec3,\n inward: Vec3,\n width: number,\n depth: number,\n thickness: number\n): Solid {\n const margin = thickness + 2;\n // Canonical: width along +X, depth along +Y, height along +Z (cuts through Z).\n // Rotating by `deg` sends +X→along and +Y→(along rotated +90° = [-along.y, along.x]).\n // If that rotated +Y opposes `inward`, build the block extending in −Y so it cuts\n // into the parent rather than out into the flange.\n const rotPerp: Vec3 = [-along[1], along[0], 0];\n const y0 = vecDot(rotPerp, inward) >= 0 ? 0 : -depth;\n let tool: Solid = box(width, depth, thickness + 2 * margin);\n tool = translate(tool, [-width / 2, y0, -margin]);\n tool = orientXY(tool, along);\n return translate(tool, [at[0], at[1], 0]);\n}\n\n/**\n * Square corner-relief tool: a `side × side × (height + margins)` block CENTRED on\n * the corner point in XY and spanning the full part height, so it removes material\n * from the base corner and both flange roots. Corner reliefs sit at axis-aligned\n * base corners for the supported perpendicular flange shapes, so the tool stays\n * axis-aligned.\n */\nfunction squareTool(solid: Solid, cornerXY: Pt2, side: number): Solid {\n const b = getBounds(solid);\n const margin = 2;\n const h = side / 2;\n const tool = box(side, side, b.zMax - b.zMin + 2 * margin);\n return translate(tool, [cornerXY[0] - h, cornerXY[1] - h, b.zMin - margin]);\n}\n\n/** Rotate a shape about +Z so its local +X axis points along `xT` (in the XY plane). */\nfunction orientXY(shape: Solid, xT: Vec3): Solid {\n const deg = (Math.atan2(xT[1], xT[0]) * 180) / Math.PI;\n if (Math.abs(deg) < 1e-9) return shape;\n return rotate(shape, deg, { at: [0, 0, 0], axis: Z_AXIS });\n}\n\nfunction toPt2(v: Vec3): Pt2 {\n return [v[0], v[1]];\n}\n\nfunction dist2(a: Pt2, b: Pt2): number {\n return Math.hypot(a[0] - b[0], a[1] - b[1]);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reliefFns.js","sourceRoot":"","sources":["../src/reliefFns.ts"],"names":[],"mappings":"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","sourcesContent":["import { getBounds } from './geometryOps.js';\nimport type { Solid } from './types.js';\nimport { translate } from './geometryOps.js';\nimport {type Result, type Vec3, ok, err, kernelError, validationError, box, cut, rotate, getSolids, isValid, curveStartPoint, curveEndPoint, vecAdd, vecSub, vecScale, vecCross, vecDot, vecNormalize} from '@faicad/faijs/api';\nimport type {\n BendFeature,\n FlangeFeature,\n ReliefFeature,\n ReliefSpec,\n SheetMetalPart,\n} from './types.js';\nimport { normalizeSolid } from './internal.js';\nimport { unfold } from './unfoldFns.js';\nimport { developedLength } from './allowanceFns.js';\n\nconst EPS = 1e-6;\nconst Z_AXIS: Vec3 = [0, 0, 1];\n\ntype Pt2 = [number, number];\ntype NotchRect = [number, number, number, number];\n\n/**\n * Add a bend relief to a flange: a small slot cut into the PARENT flat at each end\n * of the bend line that does not reach the parent edge endpoint (a partial/offset\n * flange). Without it the parent material tears at the corner where the bend\n * terminates mid-edge. Each slot is `width` (≈ thickness) along the parent edge by\n * `depth` (≈ developed length + clearance) into the parent flat, cut from the 3D\n * solid and recorded so {@link unfold} replays the 2D notch.\n *\n * A relief is a recorded feature replayed by unfold — exactly the pattern\n * {@link autoMiterCorner} establishes for corner miters.\n *\n * @param part - the sheet metal part to cut.\n * @param flangeId - the flange (bend) ID to relieve.\n * @param spec - the relief specification (default rectangular).\n * @returns the updated part with the bend relief recorded, or an error.\n */\nexport async function addBendRelief(\n part: SheetMetalPart,\n flangeId: string,\n spec: ReliefSpec = { shape: 'rectangular' }\n): Promise<Result<SheetMetalPart>> {\n if (part.solid === undefined) {\n return err(validationError('NO_SOLID', 'addBendRelief: part has no folded solid to cut'));\n }\n const flange = part.flanges.find((f) => f.id === flangeId);\n const bend = part.bends.find((b) => b.id === flangeId);\n if (flange === undefined || bend === undefined) {\n return err(validationError('UNKNOWN_FLANGE', `addBendRelief: flange '${flangeId}' not found`));\n }\n\n const dims = reliefDims(spec, part.thickness, devOf(bend, part.thickness));\n if (!dims.ok) return dims;\n const { width, depth } = dims.value;\n\n let geo: BendReliefGeometry | undefined;\n let solid = part.solid;\n try {\n geo = await bendReliefGeometry(part, flange, bend, width, depth);\n if (geo === undefined) {\n return err(\n validationError(\n 'BEND_RELIEF_NOT_NEEDED',\n `addBendRelief: bend '${flangeId}' spans its full parent edge; no relief end to cut`\n )\n );\n }\n for (const tool of geo.tools3d) {\n solid = normalizeSolid((await cut(solid, tool)) as Solid);\n }\n } catch (e) {\n return err(kernelError('CUT_FAILED', `bend relief: ${e instanceof Error ? e.message : String(e)}`));\n }\n if (!isValid(solid) || getSolids(solid).length > 1) {\n return err(\n validationError(\n 'RELIEF_SEVERED_SOLID',\n `addBendRelief: slot (width ${width}, depth ${depth}) severs flange '${flangeId}' into multiple bodies; reduce width/depth`\n )\n );\n }\n\n const feature: ReliefFeature = {\n kind: 'bend',\n shape: spec.shape,\n flangeA: flangeId,\n width,\n depth,\n notches: geo.notches2d,\n };\n\n return ok({ ...part, solid, reliefs: [...(part.reliefs ?? []), feature] });\n}\n\n/**\n * Add a bend relief to every partial-span bend (a flange that does not span its\n * full parent edge). Full-span flanges are skipped — they have no mid-edge bend\n * terminus to relieve. Convenience over calling {@link addBendRelief} per flange.\n *\n * @param part - the sheet metal part to process.\n * @param spec - the relief specification (default rectangular).\n * @returns the updated part with all needed bend reliefs, or an error.\n */\nexport async function autoBendReliefs(\n part: SheetMetalPart,\n spec: ReliefSpec = { shape: 'rectangular' }\n): Promise<Result<SheetMetalPart>> {\n let current = part;\n for (const flange of part.flanges) {\n // Let addBendRelief own the needed/not-needed decision rather than recomputing\n // the relief geometry here just to peek (which also allocated throwaway tools).\n const next = await addBendRelief(current, flange.id, spec);\n if (!next.ok) {\n if (next.error.code === 'BEND_RELIEF_NOT_NEEDED') continue;\n return next;\n }\n current = next.value;\n }\n return ok(current);\n}\n\n/**\n * Corner relief between two adjacent flanges: a notch cut at their shared corner so\n * the two upright flanges clear each other once folded — the alternative to a 45°\n * miter for the same corner {@link autoMiterCorner} handles. The notch is cut from\n * the solid and recorded as a {@link ReliefFeature}; the corner is also recorded in\n * `miters` (gap 0) so the collision check treats the interference as resolved.\n *\n * @param part - the sheet metal part to cut.\n * @param flangeIdA - the first flange ID.\n * @param flangeIdB - the second flange ID.\n * @param spec - the relief specification (default rectangular).\n * @returns the updated part with the corner relief recorded, or an error.\n */\nexport async function cornerRelief(\n part: SheetMetalPart,\n flangeIdA: string,\n flangeIdB: string,\n spec: ReliefSpec = { shape: 'rectangular' }\n): Promise<Result<SheetMetalPart>> {\n if (part.solid === undefined) {\n return err(validationError('NO_SOLID', 'cornerRelief: part has no folded solid'));\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', `cornerRelief: flange '${flangeIdA}' or '${flangeIdB}' not found`)\n );\n }\n const axisA = vecNormalize(bendA.axisDir);\n const axisB = vecNormalize(bendB.axisDir);\n if (Math.hypot(...vecCross(axisA, axisB)) < EPS) {\n return err(\n validationError('PARALLEL_FLANGES', 'cornerRelief: flange bend axes are parallel; no corner to relieve')\n );\n }\n\n const devClearance = Math.max(devOf(bendA, part.thickness), devOf(bendB, part.thickness));\n const dims = reliefDims(spec, part.thickness, devClearance);\n if (!dims.ok) return dims;\n const { depth } = dims.value;\n\n // The corner notch is a square; its side is the user's `width` when given, else\n // the depth clearance. Both recorded dims are the actual side so a consumer\n // reading width/depth reconstructs the real cut (not a stale clearance value).\n const side = spec.width ?? depth;\n\n const flangeA = part.flanges.find((f) => f.id === flangeIdA);\n const flangeB = part.flanges.find((f) => f.id === flangeIdB);\n const geo = cornerReliefGeometry(part, axisA, axisB, bendA, bendB, side, flangeA, flangeB);\n let solid: Solid;\n try {\n const tool = await squareTool(part.solid, geo.cornerXY, side);\n solid = normalizeSolid((await cut(part.solid, tool)) as Solid);\n } catch (e) {\n return err(kernelError('CUT_FAILED', `corner relief: ${e instanceof Error ? e.message : String(e)}`));\n }\n if (!isValid(solid) || getSolids(solid).length > 1) {\n return err(\n validationError(\n 'RELIEF_SEVERED_SOLID',\n `cornerRelief: notch (side ${side}) severs the part into multiple bodies; reduce width/depth`\n )\n );\n }\n\n const feature: ReliefFeature = {\n kind: 'corner',\n shape: spec.shape,\n flangeA: flangeIdA,\n flangeB: flangeIdB,\n width: side,\n depth: side,\n notches: [geo.notch2d],\n };\n\n return ok({\n ...part,\n solid,\n reliefs: [...(part.reliefs ?? []), feature],\n miters: [...(part.miters ?? []), { flangeA: flangeIdA, flangeB: flangeIdB, gap: 0 }],\n });\n}\n\n/**\n * Developed strip width of a bend, via the canonical {@link developedLength} so the\n * default relief depth tracks the same neutral-axis arc length the unfold uses.\n * Falls back to the inner radius if the rule is degenerate (the depth is only a\n * heuristic clearance, so a positive fallback keeps a valid default tool).\n */\nfunction devOf(bend: BendFeature, thickness: number): number {\n const dev = developedLength(bend.angleDeg, thickness, bend.rule);\n return dev.ok ? dev.value : bend.rule.innerRadius;\n}\n\n/** Resolve relief width/depth from the spec, defaulting to thickness / (dev + T). */\nfunction reliefDims(\n spec: ReliefSpec,\n thickness: number,\n dev: number\n): Result<{ width: number; depth: number }> {\n const width = spec.width ?? thickness;\n const depth = spec.depth ?? dev + thickness;\n if (!Number.isFinite(width) || width <= 0) {\n return err(validationError('INVALID_RELIEF_WIDTH', `relief width must be positive, got ${width}`));\n }\n if (!Number.isFinite(depth) || depth <= 0) {\n return err(validationError('INVALID_RELIEF_DEPTH', `relief depth must be positive, got ${depth}`));\n }\n return ok({ width, depth });\n}\n\ninterface BendReliefGeometry {\n tools3d: Solid[];\n notches2d: NotchRect[];\n}\n\n/**\n * Build the 3D cut tools and 2D developed-plane notch rectangles for a bend relief.\n * In 3D each slot straddles a bend-line end on the parent edge and cuts inward\n * (toward the parent interior) by `depth`, full thickness. In 2D the slot sits at\n * the corresponding bend-line endpoint and cuts into the parent rectangle the same\n * way. Returns `undefined` when the flange spans its full parent edge (no mid-edge\n * end to relieve).\n */\nasync function bendReliefGeometry(\n part: SheetMetalPart,\n flange: FlangeFeature,\n bend: BendFeature,\n width: number,\n depth: number\n): Promise<BendReliefGeometry | undefined> {\n if (part.solid === undefined) return undefined;\n\n const axis = vecNormalize(bend.axisDir);\n const span = flange.span;\n const offset = flange.offset ?? flange.baseEdge.offset ?? 0;\n const edgeLen = parentEdgeLength(part, flange);\n\n const atStart = offset > EPS;\n const atEnd = offset + span < edgeLen - EPS;\n if (!atStart && !atEnd) return undefined;\n\n const b = getBounds(part.solid);\n const center: Vec3 = [(b.xMin + b.xMax) / 2, (b.yMin + b.yMax) / 2, 0];\n const inward = inwardRun(bend.axisOrigin, axis, center);\n const edgeBase: Vec3 = [bend.axisOrigin[0], bend.axisOrigin[1], 0];\n\n const tools3d: Solid[] = [];\n const ends: number[] = [];\n if (atStart) ends.push(0);\n if (atEnd) ends.push(span);\n for (const s of ends) {\n const at = vecAdd(edgeBase, vecScale(axis, s));\n tools3d.push(await slotTool3d(at, axis, inward, width, depth, part.thickness));\n }\n\n const notches2d = bendNotches2d(part, flange, width, depth, atStart, atEnd);\n return { tools3d, notches2d };\n}\n\n/**\n * Developed-plane notch rectangles for a bend relief, located at the bend-line ends\n * and cutting into the PARENT rectangle (opposite the develop-out direction). The\n * unfold is the source of truth for 2D placement, so we read the developed bend\n * line straight from it and offset inward.\n */\nfunction bendNotches2d(\n part: SheetMetalPart,\n flange: FlangeFeature,\n width: number,\n depth: number,\n atStart: boolean,\n atEnd: boolean\n): NotchRect[] {\n const placed = developedBendLine(part, flange);\n if (placed === undefined) return [];\n const { bendA, bendB, inward } = placed;\n const len = Math.max(dist2(bendA, bendB), EPS);\n const along: Pt2 = [(bendB[0] - bendA[0]) / len, (bendB[1] - bendA[1]) / len];\n const half = width / 2;\n const make = (p: Pt2): NotchRect => {\n const c0: Pt2 = [p[0] - along[0] * half, p[1] - along[1] * half];\n const c1: Pt2 = [\n p[0] + along[0] * half + inward[0] * depth,\n p[1] + along[1] * half + inward[1] * depth,\n ];\n return [Math.min(c0[0], c1[0]), Math.min(c0[1], c1[1]), Math.max(c0[0], c1[0]), Math.max(c0[1], c1[1])];\n };\n const notches: NotchRect[] = [];\n if (atStart) notches.push(make(bendA));\n if (atEnd) notches.push(make(bendB));\n return notches;\n}\n\ninterface DevelopedBendLine {\n bendA: Pt2;\n bendB: Pt2;\n inward: Pt2;\n}\n\n/**\n * Locate a flange's bend line in the developed plane by re-running the unfold and\n * matching by flange id — the unfold owns 2D placement, so the notch always\n * coincides with the real developed geometry. Matching by id (not by geometric\n * signature) keeps placement correct when several flanges share the same\n * span/angle/direction, as in {@link autoBendReliefs}.\n */\nfunction developedBendLine(part: SheetMetalPart, flange: FlangeFeature): DevelopedBendLine | undefined {\n const unfolded = unfold({ ...part, reliefs: undefined });\n if (!unfolded.ok) return undefined;\n const bl = unfolded.value.pattern.bendLines.find((b) => b.id === flange.id);\n if (bl === undefined) return undefined;\n const a = toPt2(curveStartPoint(bl.line));\n const b = toPt2(curveEndPoint(bl.line));\n return { bendA: a, bendB: b, inward: bl.inward };\n}\n\ninterface CornerReliefGeometry {\n cornerXY: Pt2;\n notch2d: NotchRect;\n}\n\n/**\n * Corner relief geometry: the shared-corner point of the two flanges' bend lines in\n * world XY, plus a square developed-plane notch CENTRED on the reflex corner of the\n * two flanges (`(baseLength, width)` for the classic +X/+Y L). Centring the square\n * on the corner bites the base and both flange roots equally, so the developed\n * outline stays a single closed loop (a notch landing its far corner exactly on the\n * reflex vertex would pinch the boundary).\n */\nfunction cornerReliefGeometry(\n part: SheetMetalPart,\n axisA: Vec3,\n axisB: Vec3,\n bendA: BendFeature,\n bendB: BendFeature,\n side: number,\n flangeA: FlangeFeature | undefined,\n flangeB: FlangeFeature | undefined\n): CornerReliefGeometry {\n const corner = cornerPoint(bendA.axisOrigin, axisA, bendB.axisOrigin, axisB);\n const [cx, cy] = developedCorner(part, flangeA, flangeB);\n const h = side / 2;\n const notch2d: NotchRect = [cx - h, cy - h, cx + h, cy + h];\n return { cornerXY: [corner[0], corner[1]], notch2d };\n}\n\n/**\n * Developed-plane base corner shared by two perpendicular base flanges, picked from\n * their sides: an `xmax` flange's corner x is `baseLength`, an `xmin`'s is `0`; a\n * `ymax`'s corner y is `width`, a `ymin`'s is `0`. Defaults to the +X/+Y corner.\n */\nfunction developedCorner(\n part: SheetMetalPart,\n flangeA: FlangeFeature | undefined,\n flangeB: FlangeFeature | undefined\n): Pt2 {\n let cx = part.baseLength;\n let cy = part.width;\n for (const f of [flangeA, flangeB]) {\n const side = f?.baseEdge.side;\n if (side === 'xmin') cx = 0;\n else if (side === 'xmax') cx = part.baseLength;\n else if (side === 'ymin') cy = 0;\n else if (side === 'ymax') cy = part.width;\n }\n return [cx, cy];\n}\n\n/** Point on bend A's line closest to bend B's line (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/** In-plane perpendicular to `axis` pointing toward the part center (into the parent). */\nfunction inwardRun(axisOrigin: Vec3, axis: Vec3, center: Vec3): Vec3 {\n const perp = vecNormalize(vecCross(axis, Z_AXIS));\n const toCenter: Vec3 = [center[0] - axisOrigin[0], center[1] - axisOrigin[1], 0];\n const dot = perp[0] * toCenter[0] + perp[1] * toCenter[1];\n return dot >= 0 ? perp : [-perp[0], -perp[1], -perp[2]];\n}\n\n/** Parent-edge length the flange folds off (base edge for root flanges). */\nfunction parentEdgeLength(part: SheetMetalPart, flange: FlangeFeature): number {\n const side = flange.baseEdge.side ?? 'xmax';\n const onWidthEdge = side === 'xmax' || side === 'xmin';\n const parentId = flange.baseEdge.parentId;\n if (parentId === undefined || parentId === 'root' || parentId === 'face-0') {\n return onWidthEdge ? part.width : part.baseLength;\n }\n const parent = part.flanges.find((f) => f.id === parentId);\n if (parent === undefined) return onWidthEdge ? part.width : part.baseLength;\n return onWidthEdge ? parent.length : parent.span;\n}\n\n/**\n * A box tool for a bend-relief slot: a `width × depth × (thickness + margins)` block\n * whose `width` axis runs along the parent edge and `depth` axis points inward,\n * placed so it straddles the edge point `at` and cuts through the parent surface.\n */\nasync function slotTool3d(\n at: Vec3,\n along: Vec3,\n inward: Vec3,\n width: number,\n depth: number,\n thickness: number\n): Promise<Solid> {\n const margin = thickness + 2;\n // Canonical: width along +X, depth along +Y, height along +Z (cuts through Z).\n // Rotating by `deg` sends +X→along and +Y→(along rotated +90° = [-along.y, along.x]).\n // If that rotated +Y opposes `inward`, build the block extending in −Y so it cuts\n // into the parent rather than out into the flange.\n const rotPerp: Vec3 = [-along[1], along[0], 0];\n const y0 = vecDot(rotPerp, inward) >= 0 ? 0 : -depth;\n let tool: Solid = (await box({ width, depth, height: thickness + 2 * margin })) as Solid;\n tool = (await translate(tool, [-width / 2, y0, -margin])) as Solid;\n tool = await orientXY(tool, along);\n return (await translate(tool, [at[0], at[1], 0])) as Solid;\n}\n\n/**\n * Square corner-relief tool: a `side × side × (height + margins)` block CENTRED on\n * the corner point in XY and spanning the full part height, so it removes material\n * from the base corner and both flange roots. Corner reliefs sit at axis-aligned\n * base corners for the supported perpendicular flange shapes, so the tool stays\n * axis-aligned.\n */\nasync function squareTool(solid: Solid, cornerXY: Pt2, side: number): Promise<Solid> {\n const b = getBounds(solid);\n const margin = 2;\n const h = side / 2;\n const tool = (await box({ width: side, depth: side, height: b.zMax - b.zMin + 2 * margin })) as Solid;\n return (await translate(tool, [cornerXY[0] - h, cornerXY[1] - h, b.zMin - margin])) as Solid;\n}\n\n/** Rotate a shape about +Z so its local +X axis points along `xT` (in the XY plane). */\nasync function orientXY(shape: Solid, xT: Vec3): Promise<Solid> {\n const deg = (Math.atan2(xT[1], xT[0]) * 180) / Math.PI;\n if (Math.abs(deg) < 1e-9) return shape;\n return (await rotate(shape, deg, { at: [0, 0, 0], axis: Z_AXIS })) as Solid;\n}\n\nfunction toPt2(v: Vec3): Pt2 {\n return [v[0], v[1]];\n}\n\nfunction dist2(a: Pt2, b: Pt2): number {\n return Math.hypot(a[0] - b[0], a[1] - b[1]);\n}\n"]}
|
package/dist/reportFns.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Result } from '@faicad/faijs/
|
|
1
|
+
import { type Result } from '@faicad/faijs/api';
|
|
2
2
|
import type { BendReport, SheetMetalPart, UnfoldResult } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Build the bend-table JSON report directly from an authored part. Walks the
|
package/dist/reportFns.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reportFns.d.ts","sourceRoot":"","sources":["../src/reportFns.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"reportFns.d.ts","sourceRoot":"","sources":["../src/reportFns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,MAAM,EAA2B,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI3E;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,CAiBpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,UAAU,CAAC,CA0BzE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAEvD"}
|
package/dist/reportFns.js
CHANGED
package/dist/reportFns.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reportFns.js","sourceRoot":"","sources":["../src/reportFns.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"reportFns.js","sourceRoot":"","sources":["../src/reportFns.ts"],"names":[],"mappings":"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","sourcesContent":["import {type Result, ok, err, validationError} from '@faicad/faijs/api';\nimport type { BendReport, SheetMetalPart, UnfoldResult } from './types.js';\nimport { featureTree } from './featureTreeFns.js';\nimport { layoutTree, reportFromLayout } from './unfoldFns.js';\n\n/**\n * Build the bend-table JSON report directly from an authored part. Walks the\n * same tree layout the unfold consumes so the per-bend values and the total flat\n * size agree with the flat pattern. `allowance` is the consumed neutral-axis arc\n * length (the bend allowance); `flatLength` is the straight flange leg past the\n * bend — distinct values a downstream nesting/cut-list tool needs separately.\n *\n * @param part - the authored part to build the report from.\n * @returns the bend-table report, or an error if the part tree or layout cannot\n * be built.\n */\nexport function buildReport(part: SheetMetalPart): Result<BendReport> {\n const treeResult = featureTree(part);\n if (!treeResult.ok) return treeResult;\n const tree = treeResult.value;\n\n const baseLength = part.baseLength;\n if (!Number.isFinite(baseLength) || baseLength <= 0) {\n return err(validationError('INVALID_BASE_LENGTH', `part baseLength must be positive, got ${baseLength}`));\n }\n const width = part.width;\n if (!Number.isFinite(width) || width <= 0) {\n return err(validationError('INVALID_WIDTH', `part width must be positive, got ${width}`));\n }\n\n const layout = layoutTree(part, tree, baseLength, width);\n if (!layout.ok) return layout;\n return ok(reportFromLayout(layout.value));\n}\n\n/**\n * Project the report already computed by `unfold` into the standalone\n * `BendReport` shape, defensively re-validating its numeric fields. This keeps\n * the report a pure function of a previously-unfolded result without re-walking\n * the tree.\n *\n * @param result - the unfold result whose report is projected.\n * @returns the standalone `BendReport`, or an error if a numeric field is not\n * finite or positive.\n */\nexport function reportFromUnfold(result: UnfoldResult): Result<BendReport> {\n const { report } = result;\n for (const bend of report.bends) {\n if (!Number.isFinite(bend.allowance) || bend.allowance < 0) {\n return err(\n validationError('INVALID_ALLOWANCE', `bend '${bend.id}' allowance must be finite, non-negative, got ${bend.allowance}`)\n );\n }\n if (!Number.isFinite(bend.flatLength) || bend.flatLength < 0) {\n return err(\n validationError('INVALID_FLAT_LENGTH', `bend '${bend.id}' flatLength must be finite, non-negative, got ${bend.flatLength}`)\n );\n }\n }\n\n const [run, width] = report.totalFlatSize;\n if (!Number.isFinite(run) || run <= 0 || !Number.isFinite(width) || width <= 0) {\n return err(\n validationError('INVALID_FLAT_SIZE', `totalFlatSize must be finite with positive dimensions, got [${run}, ${width}]`)\n );\n }\n\n return ok({\n bends: report.bends.map((bend) => ({ ...bend })),\n totalFlatSize: [run, width],\n });\n}\n\n/**\n * Serialize a `BendReport` to a stable, pretty-printed JSON string.\n *\n * @param report - the bend report to serialize.\n * @returns the pretty-printed JSON string.\n */\nexport function reportToJSON(report: BendReport): string {\n return JSON.stringify(report, null, 2);\n}\n"]}
|
package/dist/tabFns.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Result } from '@faicad/faijs/
|
|
1
|
+
import { type Result } from '@faicad/faijs/api';
|
|
2
2
|
import type { TabSpec, SheetMetalPart } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Fuse a rectangular tab onto a named flat region's edge — additive material (the
|
|
@@ -14,7 +14,7 @@ import type { TabSpec, SheetMetalPart } from './types.js';
|
|
|
14
14
|
* @param spec - the tab specification.
|
|
15
15
|
* @returns the updated part with the tab feature recorded, or an error.
|
|
16
16
|
*/
|
|
17
|
-
export declare function addTab(part: SheetMetalPart, spec: TabSpec): Result<SheetMetalPart
|
|
17
|
+
export declare function addTab(part: SheetMetalPart, spec: TabSpec): Promise<Result<SheetMetalPart>>;
|
|
18
18
|
/** The mating-slot placement for a {@link tabAndSlot} joint. */
|
|
19
19
|
export interface SlotPlacement {
|
|
20
20
|
region: string;
|
|
@@ -39,5 +39,5 @@ export interface SlotPlacement {
|
|
|
39
39
|
* @param slot - the slot placement.
|
|
40
40
|
* @returns the updated part with the tab-and-slot feature recorded, or an error.
|
|
41
41
|
*/
|
|
42
|
-
export declare function tabAndSlot(part: SheetMetalPart, tab: TabSpec, slot: SlotPlacement): Result<SheetMetalPart
|
|
42
|
+
export declare function tabAndSlot(part: SheetMetalPart, tab: TabSpec, slot: SlotPlacement): Promise<Result<SheetMetalPart>>;
|
|
43
43
|
//# sourceMappingURL=tabFns.d.ts.map
|
package/dist/tabFns.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabFns.d.ts","sourceRoot":"","sources":["../src/tabFns.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tabFns.d.ts","sourceRoot":"","sources":["../src/tabFns.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,KAAK,MAAM,EAA4I,MAAM,mBAAmB,CAAC;AACzL,OAAO,KAAK,EAAE,OAAO,EAAc,cAAc,EAAE,MAAM,YAAY,CAAC;AAUtE;;;;;;;;;;;;;GAaG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CA2DjG;AAED,gEAAgE;AAChE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B;;0EAEsE;IACtE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAqBjC"}
|
package/dist/tabFns.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ok, err, validationError, line, wireLoop, face, extrude, fuse, getSolids, isValid, isPlanarWire, vecAdd, vecScale
|
|
1
|
+
import { ok, err, kernelError, validationError, line, wireLoop, face, extrude, fuse, getSolids, isValid, isPlanarWire, vecAdd, vecScale } from '@faicad/faijs/api';
|
|
2
2
|
import { normalizeSolid } from './internal.js';
|
|
3
3
|
import { regionFrames, regionDevExtent, mapToFrame2, addSlot } from './cutoutFns.js';
|
|
4
4
|
const EPS = 1e-6;
|
|
@@ -16,7 +16,7 @@ const EPS = 1e-6;
|
|
|
16
16
|
* @param spec - the tab specification.
|
|
17
17
|
* @returns the updated part with the tab feature recorded, or an error.
|
|
18
18
|
*/
|
|
19
|
-
export function addTab(part, spec) {
|
|
19
|
+
export async function addTab(part, spec) {
|
|
20
20
|
if (part.solid === undefined) {
|
|
21
21
|
return err(validationError('NO_SOLID', 'addTab: part has no folded solid to fuse onto'));
|
|
22
22
|
}
|
|
@@ -41,13 +41,16 @@ export function addTab(part, spec) {
|
|
|
41
41
|
return err(validationError('TAB_OUT_OF_BOUNDS', `tab [${spec.offset}, ${spec.offset + spec.width}] on side '${spec.side}' exceeds edge length ${edgeLen}`));
|
|
42
42
|
}
|
|
43
43
|
const local = tabLocalRect(spec, ext);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
let solid;
|
|
45
|
+
try {
|
|
46
|
+
const tool = await buildTabSolid(local, world, part.thickness);
|
|
47
|
+
if (!tool.ok)
|
|
48
|
+
return tool;
|
|
49
|
+
solid = normalizeSolid((await fuse(part.solid, tool.value)));
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
return err(kernelError('FUSE_FAILED', `tab: ${e instanceof Error ? e.message : String(e)}`));
|
|
53
|
+
}
|
|
51
54
|
if (!isValid(solid) || getSolids(solid).length > 1) {
|
|
52
55
|
return err(validationError('TAB_INVALID_SOLID', `addTab: tab on region '${spec.region}' did not fuse into a single valid body`));
|
|
53
56
|
}
|
|
@@ -73,12 +76,12 @@ export function addTab(part, spec) {
|
|
|
73
76
|
* @param slot - the slot placement.
|
|
74
77
|
* @returns the updated part with the tab-and-slot feature recorded, or an error.
|
|
75
78
|
*/
|
|
76
|
-
export function tabAndSlot(part, tab, slot) {
|
|
79
|
+
export async function tabAndSlot(part, tab, slot) {
|
|
77
80
|
const clearance = slot.clearance ?? 0.1;
|
|
78
81
|
if (!Number.isFinite(clearance) || clearance < 0) {
|
|
79
82
|
return err(validationError('INVALID_CLEARANCE', `clearance must be non-negative, got ${clearance}`));
|
|
80
83
|
}
|
|
81
|
-
const withTab = addTab(part, tab);
|
|
84
|
+
const withTab = await addTab(part, tab);
|
|
82
85
|
if (!withTab.ok)
|
|
83
86
|
return withTab;
|
|
84
87
|
// The tab's inserted cross-section is `width` (along the edge) by `thickness`
|
|
@@ -135,7 +138,7 @@ function tabLocalRect(spec, ext) {
|
|
|
135
138
|
}
|
|
136
139
|
}
|
|
137
140
|
/** Extrude the region-local tab rectangle through the sheet via the region world frame. */
|
|
138
|
-
function buildTabSolid(local, f, thickness) {
|
|
141
|
+
async function buildTabSolid(local, f, thickness) {
|
|
139
142
|
const worldPts = local.map(([x, y]) => vecAdd(vecAdd(f.origin, vecScale(f.u, x)), vecScale(f.v, y)));
|
|
140
143
|
const edges = [];
|
|
141
144
|
for (let i = 0; i < worldPts.length; i += 1) {
|
|
@@ -155,7 +158,12 @@ function buildTabSolid(local, f, thickness) {
|
|
|
155
158
|
const profile = face(wire.value);
|
|
156
159
|
if (!profile.ok)
|
|
157
160
|
return profile;
|
|
158
|
-
|
|
161
|
+
try {
|
|
162
|
+
return ok((await extrude(profile.value, [f.n[0] * thickness, f.n[1] * thickness, f.n[2] * thickness])));
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
return err(kernelError('TAB_TOOL_FAILED', `extrude tab: ${e instanceof Error ? e.message : String(e)}`));
|
|
166
|
+
}
|
|
159
167
|
}
|
|
160
168
|
/** Developed-plane axis-aligned rectangle `[x0,y0,x1,y1]` of the local tab rect. */
|
|
161
169
|
function devRect(local, dev) {
|
package/dist/tabFns.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabFns.js","sourceRoot":"","sources":["../src/tabFns.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 line,\n wireLoop,\n face,\n extrude,\n fuse,\n getSolids,\n isValid,\n isPlanarWire,\n vecAdd,\n vecScale,\n} from '@faicad/faijs/brepjs-compat';\nimport type { TabSpec, TabFeature, SheetMetalPart } from './types.js';\nimport { normalizeSolid } from './internal.js';\nimport type { FlatFrame } from './authorFns.js';\nimport type { Frame2 } from './unfoldFns.js';\nimport { regionFrames, regionDevExtent, mapToFrame2, addSlot } from './cutoutFns.js';\n\nconst EPS = 1e-6;\n\ntype Pt2 = [number, number];\n\n/**\n * Fuse a rectangular tab onto a named flat region's edge — additive material (the\n * counterpart of a cutout). The tab is built as a region-local rectangle just past\n * the chosen `side`, placed onto the correct folded face via the region's world\n * {@link FlatFrame}, extruded through the sheet thickness and fused to the solid. The\n * same local rectangle, mapped through the region's developed {@link Frame2}, is\n * recorded as a {@link TabFeature} so {@link unfold} extends the OUTER outline by the\n * protrusion (tabs add material, so the developed area grows). Guards a valid,\n * single-bodied solid.\n *\n * @param part - the sheet metal part to attach a tab to.\n * @param spec - the tab specification.\n * @returns the updated part with the tab feature recorded, or an error.\n */\nexport function addTab(part: SheetMetalPart, spec: TabSpec): Result<SheetMetalPart> {\n if (part.solid === undefined) {\n return err(validationError('NO_SOLID', 'addTab: part has no folded solid to fuse onto'));\n }\n if (!Number.isFinite(spec.width) || spec.width <= 0) {\n return err(validationError('INVALID_TAB', `tab width must be positive, got ${spec.width}`));\n }\n if (!Number.isFinite(spec.length) || spec.length <= 0) {\n return err(validationError('INVALID_TAB', `tab length must be positive, got ${spec.length}`));\n }\n if (!Number.isFinite(spec.offset) || spec.offset < -EPS) {\n return err(validationError('INVALID_TAB', `tab offset must be non-negative, got ${spec.offset}`));\n }\n\n const framesResult = regionFrames(part, spec.region);\n if (!framesResult.ok) return framesResult;\n const { regionId, world, dev } = framesResult.value;\n\n const ext = regionDevExtent(part, regionId, world);\n // The tab attaches to a region edge and runs `width` ALONG it; that span must lie\n // within the edge's length, or the protrusion overhangs the region.\n const edgeLen = spec.side === 'xmax' || spec.side === 'xmin' ? ext.vMax : ext.uMax;\n if (spec.offset + spec.width > edgeLen + EPS) {\n return err(\n validationError(\n 'TAB_OUT_OF_BOUNDS',\n `tab [${spec.offset}, ${spec.offset + spec.width}] on side '${spec.side}' exceeds edge length ${edgeLen}`\n )\n );\n }\n\n const local = tabLocalRect(spec, ext);\n\n const tool = buildTabSolid(local, world, part.thickness);\n if (!tool.ok) return tool;\n\n const fused = fuse(part.solid, tool.value);\n if (!fused.ok) return fused;\n const solid = normalizeSolid(fused.value);\n if (!isValid(solid) || getSolids(solid).length > 1) {\n return err(\n validationError(\n 'TAB_INVALID_SOLID',\n `addTab: tab on region '${spec.region}' did not fuse into a single valid body`\n )\n );\n }\n\n const rect = devRect(local, dev);\n const feature: TabFeature = {\n spec,\n region: regionId,\n rect,\n area: spec.width * spec.length,\n };\n\n return ok({ ...part, solid, tabs: [...(part.tabs ?? []), feature] });\n}\n\n/** The mating-slot placement for a {@link tabAndSlot} joint. */\nexport interface SlotPlacement {\n region: string;\n x: number;\n y: number;\n clearance?: number | undefined;\n /** In-plane rotation of the slot (deg, CCW) in the mating region's local frame.\n * Default 0 = slot length along the region's +x (bend-axis) direction; set this\n * when the tab meets the slot region at a non-default orientation. */\n angleDeg?: number | undefined;\n}\n\n/**\n * Self-fixturing tab-and-slot joint: fuse a tab on one region and punch a matching\n * SLOT CUTOUT on the mating region, sized so the tab's cross-section\n * (`width × thickness`) inserts into the slot. The slot is `tab.width + clearance`\n * long by `thickness + clearance` wide, centred at the mating region's local\n * `(x, y)`. The slot is always strictly larger than the tab cross-section, so the\n * joint mates (verified numerically by callers). Clearance defaults to `0.1` mm.\n *\n * @param part - the sheet metal part to process.\n * @param tab - the tab specification.\n * @param slot - the slot placement.\n * @returns the updated part with the tab-and-slot feature recorded, or an error.\n */\nexport function tabAndSlot(\n part: SheetMetalPart,\n tab: TabSpec,\n slot: SlotPlacement\n): Result<SheetMetalPart> {\n const clearance = slot.clearance ?? 0.1;\n if (!Number.isFinite(clearance) || clearance < 0) {\n return err(validationError('INVALID_CLEARANCE', `clearance must be non-negative, got ${clearance}`));\n }\n\n const withTab = addTab(part, tab);\n if (!withTab.ok) return withTab;\n\n // The tab's inserted cross-section is `width` (along the edge) by `thickness`\n // (through the sheet); the slot must clear both, so it's sized + clearance.\n const slotLength = tab.width + clearance;\n const slotWidth = part.thickness + clearance;\n\n return addSlot(withTab.value, slot.region, {\n x: slot.x,\n y: slot.y,\n length: slotLength,\n width: slotWidth,\n ...(slot.angleDeg !== undefined ? { angleDeg: slot.angleDeg } : {}),\n });\n}\n\n/**\n * Region-local rectangle of the tab as four corners (CCW), extending OUTWARD past\n * the chosen edge by `length`, spanning `[offset, offset+width]` along the edge.\n */\nfunction tabLocalRect(spec: TabSpec, ext: { uMax: number; vMax: number }): Pt2[] {\n const o = spec.offset;\n const w = o + spec.width;\n const l = spec.length;\n switch (spec.side) {\n case 'xmax':\n return [\n [ext.uMax, o],\n [ext.uMax + l, o],\n [ext.uMax + l, w],\n [ext.uMax, w],\n ];\n case 'xmin':\n return [\n [-l, o],\n [0, o],\n [0, w],\n [-l, w],\n ];\n case 'ymax':\n return [\n [o, ext.vMax],\n [w, ext.vMax],\n [w, ext.vMax + l],\n [o, ext.vMax + l],\n ];\n case 'ymin':\n return [\n [o, -l],\n [w, -l],\n [w, 0],\n [o, 0],\n ];\n default:\n return spec.side satisfies never;\n }\n}\n\n/** Extrude the region-local tab rectangle through the sheet via the region world frame. */\nfunction buildTabSolid(local: Pt2[], f: FlatFrame, thickness: number): Result<Solid> {\n const worldPts: Vec3[] = local.map(([x, y]) =>\n vecAdd(vecAdd(f.origin, vecScale(f.u, x)), vecScale(f.v, y))\n );\n const edges = [];\n for (let i = 0; i < worldPts.length; i += 1) {\n const a = worldPts[i];\n const b = worldPts[(i + 1) % worldPts.length];\n if (a === undefined || b === undefined) {\n return err(validationError('TAB_TOOL_FAILED', 'failed to index tab rectangle points'));\n }\n edges.push(line(a, b));\n }\n const wire = wireLoop(edges);\n if (!wire.ok) return wire;\n if (!isPlanarWire(wire.value)) {\n return err(validationError('TAB_TOOL_FAILED', 'tab profile wire is not planar'));\n }\n const profile = face(wire.value);\n if (!profile.ok) return profile;\n return extrude(profile.value, [f.n[0] * thickness, f.n[1] * thickness, f.n[2] * thickness]);\n}\n\n/** Developed-plane axis-aligned rectangle `[x0,y0,x1,y1]` of the local tab rect. */\nfunction devRect(local: Pt2[], dev: Frame2): [number, number, number, number] {\n let x0 = Infinity;\n let y0 = Infinity;\n let x1 = -Infinity;\n let y1 = -Infinity;\n for (const p of local) {\n const [x, y] = mapToFrame2(p, dev);\n if (x < x0) x0 = x;\n if (x > x1) x1 = x;\n if (y < y0) y0 = y;\n if (y > y1) y1 = y;\n }\n return [x0, y0, x1, y1];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tabFns.js","sourceRoot":"","sources":["../src/tabFns.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Solid } from './types.js';\nimport {type Result, type Vec3, ok, err, kernelError, validationError, line, wireLoop, face, extrude, fuse, getSolids, isValid, isPlanarWire, vecAdd, vecScale} from '@faicad/faijs/api';\nimport type { TabSpec, TabFeature, SheetMetalPart } from './types.js';\nimport { normalizeSolid } from './internal.js';\nimport type { FlatFrame } from './authorFns.js';\nimport type { Frame2 } from './unfoldFns.js';\nimport { regionFrames, regionDevExtent, mapToFrame2, addSlot } from './cutoutFns.js';\n\nconst EPS = 1e-6;\n\ntype Pt2 = [number, number];\n\n/**\n * Fuse a rectangular tab onto a named flat region's edge — additive material (the\n * counterpart of a cutout). The tab is built as a region-local rectangle just past\n * the chosen `side`, placed onto the correct folded face via the region's world\n * {@link FlatFrame}, extruded through the sheet thickness and fused to the solid. The\n * same local rectangle, mapped through the region's developed {@link Frame2}, is\n * recorded as a {@link TabFeature} so {@link unfold} extends the OUTER outline by the\n * protrusion (tabs add material, so the developed area grows). Guards a valid,\n * single-bodied solid.\n *\n * @param part - the sheet metal part to attach a tab to.\n * @param spec - the tab specification.\n * @returns the updated part with the tab feature recorded, or an error.\n */\nexport async function addTab(part: SheetMetalPart, spec: TabSpec): Promise<Result<SheetMetalPart>> {\n if (part.solid === undefined) {\n return err(validationError('NO_SOLID', 'addTab: part has no folded solid to fuse onto'));\n }\n if (!Number.isFinite(spec.width) || spec.width <= 0) {\n return err(validationError('INVALID_TAB', `tab width must be positive, got ${spec.width}`));\n }\n if (!Number.isFinite(spec.length) || spec.length <= 0) {\n return err(validationError('INVALID_TAB', `tab length must be positive, got ${spec.length}`));\n }\n if (!Number.isFinite(spec.offset) || spec.offset < -EPS) {\n return err(validationError('INVALID_TAB', `tab offset must be non-negative, got ${spec.offset}`));\n }\n\n const framesResult = regionFrames(part, spec.region);\n if (!framesResult.ok) return framesResult;\n const { regionId, world, dev } = framesResult.value;\n\n const ext = regionDevExtent(part, regionId, world);\n // The tab attaches to a region edge and runs `width` ALONG it; that span must lie\n // within the edge's length, or the protrusion overhangs the region.\n const edgeLen = spec.side === 'xmax' || spec.side === 'xmin' ? ext.vMax : ext.uMax;\n if (spec.offset + spec.width > edgeLen + EPS) {\n return err(\n validationError(\n 'TAB_OUT_OF_BOUNDS',\n `tab [${spec.offset}, ${spec.offset + spec.width}] on side '${spec.side}' exceeds edge length ${edgeLen}`\n )\n );\n }\n\n const local = tabLocalRect(spec, ext);\n\n let solid: Solid;\n try {\n const tool = await buildTabSolid(local, world, part.thickness);\n if (!tool.ok) return tool;\n solid = normalizeSolid((await fuse(part.solid, tool.value)) as Solid);\n } catch (e) {\n return err(kernelError('FUSE_FAILED', `tab: ${e instanceof Error ? e.message : String(e)}`));\n }\n if (!isValid(solid) || getSolids(solid).length > 1) {\n return err(\n validationError(\n 'TAB_INVALID_SOLID',\n `addTab: tab on region '${spec.region}' did not fuse into a single valid body`\n )\n );\n }\n\n const rect = devRect(local, dev);\n const feature: TabFeature = {\n spec,\n region: regionId,\n rect,\n area: spec.width * spec.length,\n };\n\n return ok({ ...part, solid, tabs: [...(part.tabs ?? []), feature] });\n}\n\n/** The mating-slot placement for a {@link tabAndSlot} joint. */\nexport interface SlotPlacement {\n region: string;\n x: number;\n y: number;\n clearance?: number | undefined;\n /** In-plane rotation of the slot (deg, CCW) in the mating region's local frame.\n * Default 0 = slot length along the region's +x (bend-axis) direction; set this\n * when the tab meets the slot region at a non-default orientation. */\n angleDeg?: number | undefined;\n}\n\n/**\n * Self-fixturing tab-and-slot joint: fuse a tab on one region and punch a matching\n * SLOT CUTOUT on the mating region, sized so the tab's cross-section\n * (`width × thickness`) inserts into the slot. The slot is `tab.width + clearance`\n * long by `thickness + clearance` wide, centred at the mating region's local\n * `(x, y)`. The slot is always strictly larger than the tab cross-section, so the\n * joint mates (verified numerically by callers). Clearance defaults to `0.1` mm.\n *\n * @param part - the sheet metal part to process.\n * @param tab - the tab specification.\n * @param slot - the slot placement.\n * @returns the updated part with the tab-and-slot feature recorded, or an error.\n */\nexport async function tabAndSlot(\n part: SheetMetalPart,\n tab: TabSpec,\n slot: SlotPlacement\n): Promise<Result<SheetMetalPart>> {\n const clearance = slot.clearance ?? 0.1;\n if (!Number.isFinite(clearance) || clearance < 0) {\n return err(validationError('INVALID_CLEARANCE', `clearance must be non-negative, got ${clearance}`));\n }\n\n const withTab = await addTab(part, tab);\n if (!withTab.ok) return withTab;\n\n // The tab's inserted cross-section is `width` (along the edge) by `thickness`\n // (through the sheet); the slot must clear both, so it's sized + clearance.\n const slotLength = tab.width + clearance;\n const slotWidth = part.thickness + clearance;\n\n return addSlot(withTab.value, slot.region, {\n x: slot.x,\n y: slot.y,\n length: slotLength,\n width: slotWidth,\n ...(slot.angleDeg !== undefined ? { angleDeg: slot.angleDeg } : {}),\n });\n}\n\n/**\n * Region-local rectangle of the tab as four corners (CCW), extending OUTWARD past\n * the chosen edge by `length`, spanning `[offset, offset+width]` along the edge.\n */\nfunction tabLocalRect(spec: TabSpec, ext: { uMax: number; vMax: number }): Pt2[] {\n const o = spec.offset;\n const w = o + spec.width;\n const l = spec.length;\n switch (spec.side) {\n case 'xmax':\n return [\n [ext.uMax, o],\n [ext.uMax + l, o],\n [ext.uMax + l, w],\n [ext.uMax, w],\n ];\n case 'xmin':\n return [\n [-l, o],\n [0, o],\n [0, w],\n [-l, w],\n ];\n case 'ymax':\n return [\n [o, ext.vMax],\n [w, ext.vMax],\n [w, ext.vMax + l],\n [o, ext.vMax + l],\n ];\n case 'ymin':\n return [\n [o, -l],\n [w, -l],\n [w, 0],\n [o, 0],\n ];\n default:\n return spec.side satisfies never;\n }\n}\n\n/** Extrude the region-local tab rectangle through the sheet via the region world frame. */\nasync function buildTabSolid(local: Pt2[], f: FlatFrame, thickness: number): Promise<Result<Solid>> {\n const worldPts: Vec3[] = local.map(([x, y]) =>\n vecAdd(vecAdd(f.origin, vecScale(f.u, x)), vecScale(f.v, y))\n );\n const edges = [];\n for (let i = 0; i < worldPts.length; i += 1) {\n const a = worldPts[i];\n const b = worldPts[(i + 1) % worldPts.length];\n if (a === undefined || b === undefined) {\n return err(validationError('TAB_TOOL_FAILED', 'failed to index tab rectangle points'));\n }\n edges.push(line(a, b));\n }\n const wire = wireLoop(edges);\n if (!wire.ok) return wire;\n if (!isPlanarWire(wire.value)) {\n return err(validationError('TAB_TOOL_FAILED', 'tab profile wire is not planar'));\n }\n const profile = face(wire.value);\n if (!profile.ok) return profile;\n try {\n return ok((await extrude(profile.value, [f.n[0] * thickness, f.n[1] * thickness, f.n[2] * thickness])) as Solid);\n } catch (e) {\n return err(kernelError('TAB_TOOL_FAILED', `extrude tab: ${e instanceof Error ? e.message : String(e)}`));\n }\n}\n\n/** Developed-plane axis-aligned rectangle `[x0,y0,x1,y1]` of the local tab rect. */\nfunction devRect(local: Pt2[], dev: Frame2): [number, number, number, number] {\n let x0 = Infinity;\n let y0 = Infinity;\n let x1 = -Infinity;\n let y1 = -Infinity;\n for (const p of local) {\n const [x, y] = mapToFrame2(p, dev);\n if (x < x0) x0 = x;\n if (x > x1) x1 = x;\n if (y < y0) y0 = y;\n if (y > y1) y1 = y;\n }\n return [x0, y0, x1, y1];\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SolidShape, CurveShape } from '@faicad/faijs/api';
|
|
2
|
+
/** faijs 建模链产物(core-decouple wrapup §2.2):brepjs `Solid` 的 faijs 等价。
|
|
3
|
+
* 建模 op 均为 faijs 异步 op,产物统一为 `SolidShape`。 */
|
|
4
|
+
export type Solid = SolidShape;
|
|
5
|
+
/** 有效实体别名(断言实体性的调用点保留,等价 `SolidShape`)。 */
|
|
6
|
+
export type ValidSolid = SolidShape;
|
|
7
|
+
/** faijs 曲线形状(edge / wire)——brepjs `Wire` 的 faijs 等价。 */
|
|
8
|
+
export type Wire = CurveShape;
|
|
9
|
+
/** faijs 曲线形状(edge)——brepjs `Edge` 的 faijs 等价。 */
|
|
10
|
+
export type Edge = CurveShape;
|
|
11
|
+
/** faijs 面(携带面句柄的 solid shape)——brepjs `Face` 的 faijs 等价。 */
|
|
12
|
+
export type Face = SolidShape;
|
|
13
|
+
/** 轴对齐包围盒(大写字段形态;由 faijs `bounds3D`(brep 引擎包围盒)组合,见 geometryOps.getBounds)。 */
|
|
14
|
+
export interface Bounds3D {
|
|
15
|
+
xMin: number;
|
|
16
|
+
xMax: number;
|
|
17
|
+
yMin: number;
|
|
18
|
+
yMax: number;
|
|
19
|
+
zMin: number;
|
|
20
|
+
zMax: number;
|
|
21
|
+
}
|
|
2
22
|
/** Which edge of a flat a child flange folds off. */
|
|
3
23
|
export type FlatSide = 'xmin' | 'xmax' | 'ymin' | 'ymax';
|
|
4
24
|
/**
|