@faicad/sheetmetal 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/allowanceFns.d.ts +42 -0
  2. package/dist/allowanceFns.d.ts.map +1 -0
  3. package/dist/allowanceFns.js +63 -0
  4. package/dist/allowanceFns.js.map +1 -0
  5. package/dist/api.d.ts +312 -0
  6. package/dist/api.d.ts.map +1 -0
  7. package/dist/api.js +367 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/authorFns.d.ts +118 -0
  10. package/dist/authorFns.d.ts.map +1 -0
  11. package/dist/authorFns.js +562 -0
  12. package/dist/authorFns.js.map +1 -0
  13. package/dist/bendTableFns.d.ts +87 -0
  14. package/dist/bendTableFns.d.ts.map +1 -0
  15. package/dist/bendTableFns.js +345 -0
  16. package/dist/bendTableFns.js.map +1 -0
  17. package/dist/compat.d.ts +90 -0
  18. package/dist/compat.d.ts.map +1 -0
  19. package/dist/compat.js +99 -0
  20. package/dist/compat.js.map +1 -0
  21. package/dist/contourFlangeFns.d.ts +122 -0
  22. package/dist/contourFlangeFns.d.ts.map +1 -0
  23. package/dist/contourFlangeFns.js +403 -0
  24. package/dist/contourFlangeFns.js.map +1 -0
  25. package/dist/cutoutFns.d.ts +95 -0
  26. package/dist/cutoutFns.d.ts.map +1 -0
  27. package/dist/cutoutFns.js +300 -0
  28. package/dist/cutoutFns.js.map +1 -0
  29. package/dist/dxfFns.d.ts +48 -0
  30. package/dist/dxfFns.d.ts.map +1 -0
  31. package/dist/dxfFns.js +236 -0
  32. package/dist/dxfFns.js.map +1 -0
  33. package/dist/facade.d.ts +449 -0
  34. package/dist/facade.d.ts.map +1 -0
  35. package/dist/facade.js +535 -0
  36. package/dist/facade.js.map +1 -0
  37. package/dist/featureTreeFns.d.ts +76 -0
  38. package/dist/featureTreeFns.d.ts.map +1 -0
  39. package/dist/featureTreeFns.js +190 -0
  40. package/dist/featureTreeFns.js.map +1 -0
  41. package/dist/foldFns.d.ts +83 -0
  42. package/dist/foldFns.d.ts.map +1 -0
  43. package/dist/foldFns.js +744 -0
  44. package/dist/foldFns.js.map +1 -0
  45. package/dist/foreignUnfoldFns.d.ts +62 -0
  46. package/dist/foreignUnfoldFns.d.ts.map +1 -0
  47. package/dist/foreignUnfoldFns.js +854 -0
  48. package/dist/foreignUnfoldFns.js.map +1 -0
  49. package/dist/formFns.d.ts +58 -0
  50. package/dist/formFns.d.ts.map +1 -0
  51. package/dist/formFns.js +300 -0
  52. package/dist/formFns.js.map +1 -0
  53. package/dist/hemFns.d.ts +17 -0
  54. package/dist/hemFns.d.ts.map +1 -0
  55. package/dist/hemFns.js +212 -0
  56. package/dist/hemFns.js.map +1 -0
  57. package/dist/index.d.ts +43 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +32 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/internal.d.ts +27 -0
  62. package/dist/internal.d.ts.map +1 -0
  63. package/dist/internal.js +38 -0
  64. package/dist/internal.js.map +1 -0
  65. package/dist/jogFns.d.ts +18 -0
  66. package/dist/jogFns.d.ts.map +1 -0
  67. package/dist/jogFns.js +180 -0
  68. package/dist/jogFns.js.map +1 -0
  69. package/dist/loftedFlangeFns.d.ts +23 -0
  70. package/dist/loftedFlangeFns.d.ts.map +1 -0
  71. package/dist/loftedFlangeFns.js +306 -0
  72. package/dist/loftedFlangeFns.js.map +1 -0
  73. package/dist/materials.d.ts +16 -0
  74. package/dist/materials.d.ts.map +1 -0
  75. package/dist/materials.js +44 -0
  76. package/dist/materials.js.map +1 -0
  77. package/dist/miterFns.d.ts +31 -0
  78. package/dist/miterFns.d.ts.map +1 -0
  79. package/dist/miterFns.js +128 -0
  80. package/dist/miterFns.js.map +1 -0
  81. package/dist/nestFns.d.ts +116 -0
  82. package/dist/nestFns.d.ts.map +1 -0
  83. package/dist/nestFns.js +517 -0
  84. package/dist/nestFns.js.map +1 -0
  85. package/dist/polygonFns.d.ts +144 -0
  86. package/dist/polygonFns.d.ts.map +1 -0
  87. package/dist/polygonFns.js +324 -0
  88. package/dist/polygonFns.js.map +1 -0
  89. package/dist/reliefFns.d.ts +44 -0
  90. package/dist/reliefFns.d.ts.map +1 -0
  91. package/dist/reliefFns.js +367 -0
  92. package/dist/reliefFns.js.map +1 -0
  93. package/dist/reportFns.d.ts +33 -0
  94. package/dist/reportFns.d.ts.map +1 -0
  95. package/dist/reportFns.js +71 -0
  96. package/dist/reportFns.js.map +1 -0
  97. package/dist/tabFns.d.ts +43 -0
  98. package/dist/tabFns.d.ts.map +1 -0
  99. package/dist/tabFns.js +179 -0
  100. package/dist/tabFns.js.map +1 -0
  101. package/dist/types.d.ts +629 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/types.js +2 -0
  104. package/dist/types.js.map +1 -0
  105. package/dist/unfoldFns.d.ts +120 -0
  106. package/dist/unfoldFns.d.ts.map +1 -0
  107. package/dist/unfoldFns.js +766 -0
  108. package/dist/unfoldFns.js.map +1 -0
  109. package/dist/validateFns.d.ts +18 -0
  110. package/dist/validateFns.d.ts.map +1 -0
  111. package/dist/validateFns.js +196 -0
  112. package/dist/validateFns.js.map +1 -0
  113. package/package.json +45 -0
@@ -0,0 +1,367 @@
1
+ import { ok, err, validationError, box, cut, rotate, translate, getBounds, getSolids, isValid, curveStartPoint, curveEndPoint, vecAdd, vecSub, vecScale, vecCross, vecDot, vecNormalize, } from '@faicad/faijs/brepjs-compat';
2
+ import { normalizeSolid } from './internal.js';
3
+ import { unfold } from './unfoldFns.js';
4
+ import { developedLength } from './allowanceFns.js';
5
+ const EPS = 1e-6;
6
+ const Z_AXIS = [0, 0, 1];
7
+ /**
8
+ * Add a bend relief to a flange: a small slot cut into the PARENT flat at each end
9
+ * of the bend line that does not reach the parent edge endpoint (a partial/offset
10
+ * flange). Without it the parent material tears at the corner where the bend
11
+ * terminates mid-edge. Each slot is `width` (≈ thickness) along the parent edge by
12
+ * `depth` (≈ developed length + clearance) into the parent flat, cut from the 3D
13
+ * solid and recorded so {@link unfold} replays the 2D notch.
14
+ *
15
+ * A relief is a recorded feature replayed by unfold — exactly the pattern
16
+ * {@link autoMiterCorner} establishes for corner miters.
17
+ *
18
+ * @param part - the sheet metal part to cut.
19
+ * @param flangeId - the flange (bend) ID to relieve.
20
+ * @param spec - the relief specification (default rectangular).
21
+ * @returns the updated part with the bend relief recorded, or an error.
22
+ */
23
+ export function addBendRelief(part, flangeId, spec = { shape: 'rectangular' }) {
24
+ if (part.solid === undefined) {
25
+ return err(validationError('NO_SOLID', 'addBendRelief: part has no folded solid to cut'));
26
+ }
27
+ const flange = part.flanges.find((f) => f.id === flangeId);
28
+ const bend = part.bends.find((b) => b.id === flangeId);
29
+ if (flange === undefined || bend === undefined) {
30
+ return err(validationError('UNKNOWN_FLANGE', `addBendRelief: flange '${flangeId}' not found`));
31
+ }
32
+ const dims = reliefDims(spec, part.thickness, devOf(bend, part.thickness));
33
+ if (!dims.ok)
34
+ return dims;
35
+ const { width, depth } = dims.value;
36
+ const geo = bendReliefGeometry(part, flange, bend, width, depth);
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
+ }
40
+ let solid = part.solid;
41
+ for (const tool of geo.tools3d) {
42
+ const result = cut(solid, tool);
43
+ if (!result.ok)
44
+ return result;
45
+ solid = normalizeSolid(result.value);
46
+ }
47
+ if (!isValid(solid) || getSolids(solid).length > 1) {
48
+ return err(validationError('RELIEF_SEVERED_SOLID', `addBendRelief: slot (width ${width}, depth ${depth}) severs flange '${flangeId}' into multiple bodies; reduce width/depth`));
49
+ }
50
+ const feature = {
51
+ kind: 'bend',
52
+ shape: spec.shape,
53
+ flangeA: flangeId,
54
+ width,
55
+ depth,
56
+ notches: geo.notches2d,
57
+ };
58
+ return ok({ ...part, solid, reliefs: [...(part.reliefs ?? []), feature] });
59
+ }
60
+ /**
61
+ * Add a bend relief to every partial-span bend (a flange that does not span its
62
+ * full parent edge). Full-span flanges are skipped — they have no mid-edge bend
63
+ * terminus to relieve. Convenience over calling {@link addBendRelief} per flange.
64
+ *
65
+ * @param part - the sheet metal part to process.
66
+ * @param spec - the relief specification (default rectangular).
67
+ * @returns the updated part with all needed bend reliefs, or an error.
68
+ */
69
+ export function autoBendReliefs(part, spec = { shape: 'rectangular' }) {
70
+ let current = part;
71
+ for (const flange of part.flanges) {
72
+ // Let addBendRelief own the needed/not-needed decision rather than recomputing
73
+ // the relief geometry here just to peek (which also allocated throwaway tools).
74
+ const next = addBendRelief(current, flange.id, spec);
75
+ if (!next.ok) {
76
+ if (next.error.code === 'BEND_RELIEF_NOT_NEEDED')
77
+ continue;
78
+ return next;
79
+ }
80
+ current = next.value;
81
+ }
82
+ return ok(current);
83
+ }
84
+ /**
85
+ * Corner relief between two adjacent flanges: a notch cut at their shared corner so
86
+ * the two upright flanges clear each other once folded — the alternative to a 45°
87
+ * miter for the same corner {@link autoMiterCorner} handles. The notch is cut from
88
+ * the solid and recorded as a {@link ReliefFeature}; the corner is also recorded in
89
+ * `miters` (gap 0) so the collision check treats the interference as resolved.
90
+ *
91
+ * @param part - the sheet metal part to cut.
92
+ * @param flangeIdA - the first flange ID.
93
+ * @param flangeIdB - the second flange ID.
94
+ * @param spec - the relief specification (default rectangular).
95
+ * @returns the updated part with the corner relief recorded, or an error.
96
+ */
97
+ export function cornerRelief(part, flangeIdA, flangeIdB, spec = { shape: 'rectangular' }) {
98
+ if (part.solid === undefined) {
99
+ return err(validationError('NO_SOLID', 'cornerRelief: part has no folded solid'));
100
+ }
101
+ const bendA = part.bends.find((b) => b.id === flangeIdA);
102
+ const bendB = part.bends.find((b) => b.id === flangeIdB);
103
+ if (bendA === undefined || bendB === undefined) {
104
+ return err(validationError('UNKNOWN_FLANGE', `cornerRelief: flange '${flangeIdA}' or '${flangeIdB}' not found`));
105
+ }
106
+ const axisA = vecNormalize(bendA.axisDir);
107
+ const axisB = vecNormalize(bendB.axisDir);
108
+ if (Math.hypot(...vecCross(axisA, axisB)) < EPS) {
109
+ return err(validationError('PARALLEL_FLANGES', 'cornerRelief: flange bend axes are parallel; no corner to relieve'));
110
+ }
111
+ const devClearance = Math.max(devOf(bendA, part.thickness), devOf(bendB, part.thickness));
112
+ const dims = reliefDims(spec, part.thickness, devClearance);
113
+ if (!dims.ok)
114
+ return dims;
115
+ const { depth } = dims.value;
116
+ // The corner notch is a square; its side is the user's `width` when given, else
117
+ // the depth clearance. Both recorded dims are the actual side so a consumer
118
+ // reading width/depth reconstructs the real cut (not a stale clearance value).
119
+ const side = spec.width ?? depth;
120
+ const flangeA = part.flanges.find((f) => f.id === flangeIdA);
121
+ const flangeB = part.flanges.find((f) => f.id === flangeIdB);
122
+ const geo = cornerReliefGeometry(part, axisA, axisB, bendA, bendB, side, flangeA, flangeB);
123
+ const tool = squareTool(part.solid, geo.cornerXY, side);
124
+ const result = cut(part.solid, tool);
125
+ if (!result.ok)
126
+ return result;
127
+ const solid = normalizeSolid(result.value);
128
+ if (!isValid(solid) || getSolids(solid).length > 1) {
129
+ return err(validationError('RELIEF_SEVERED_SOLID', `cornerRelief: notch (side ${side}) severs the part into multiple bodies; reduce width/depth`));
130
+ }
131
+ const feature = {
132
+ kind: 'corner',
133
+ shape: spec.shape,
134
+ flangeA: flangeIdA,
135
+ flangeB: flangeIdB,
136
+ width: side,
137
+ depth: side,
138
+ notches: [geo.notch2d],
139
+ };
140
+ return ok({
141
+ ...part,
142
+ solid,
143
+ reliefs: [...(part.reliefs ?? []), feature],
144
+ miters: [...(part.miters ?? []), { flangeA: flangeIdA, flangeB: flangeIdB, gap: 0 }],
145
+ });
146
+ }
147
+ /**
148
+ * Developed strip width of a bend, via the canonical {@link developedLength} so the
149
+ * default relief depth tracks the same neutral-axis arc length the unfold uses.
150
+ * Falls back to the inner radius if the rule is degenerate (the depth is only a
151
+ * heuristic clearance, so a positive fallback keeps a valid default tool).
152
+ */
153
+ function devOf(bend, thickness) {
154
+ const dev = developedLength(bend.angleDeg, thickness, bend.rule);
155
+ return dev.ok ? dev.value : bend.rule.innerRadius;
156
+ }
157
+ /** Resolve relief width/depth from the spec, defaulting to thickness / (dev + T). */
158
+ function reliefDims(spec, thickness, dev) {
159
+ const width = spec.width ?? thickness;
160
+ const depth = spec.depth ?? dev + thickness;
161
+ if (!Number.isFinite(width) || width <= 0) {
162
+ return err(validationError('INVALID_RELIEF_WIDTH', `relief width must be positive, got ${width}`));
163
+ }
164
+ if (!Number.isFinite(depth) || depth <= 0) {
165
+ return err(validationError('INVALID_RELIEF_DEPTH', `relief depth must be positive, got ${depth}`));
166
+ }
167
+ return ok({ width, depth });
168
+ }
169
+ /**
170
+ * Build the 3D cut tools and 2D developed-plane notch rectangles for a bend relief.
171
+ * In 3D each slot straddles a bend-line end on the parent edge and cuts inward
172
+ * (toward the parent interior) by `depth`, full thickness. In 2D the slot sits at
173
+ * the corresponding bend-line endpoint and cuts into the parent rectangle the same
174
+ * way. Returns `undefined` when the flange spans its full parent edge (no mid-edge
175
+ * end to relieve).
176
+ */
177
+ function bendReliefGeometry(part, flange, bend, width, depth) {
178
+ if (part.solid === undefined)
179
+ return undefined;
180
+ const axis = vecNormalize(bend.axisDir);
181
+ const span = flange.span;
182
+ const offset = flange.offset ?? flange.baseEdge.offset ?? 0;
183
+ const edgeLen = parentEdgeLength(part, flange);
184
+ const atStart = offset > EPS;
185
+ const atEnd = offset + span < edgeLen - EPS;
186
+ if (!atStart && !atEnd)
187
+ return undefined;
188
+ const b = getBounds(part.solid);
189
+ const center = [(b.xMin + b.xMax) / 2, (b.yMin + b.yMax) / 2, 0];
190
+ const inward = inwardRun(bend.axisOrigin, axis, center);
191
+ const edgeBase = [bend.axisOrigin[0], bend.axisOrigin[1], 0];
192
+ const tools3d = [];
193
+ const ends = [];
194
+ if (atStart)
195
+ ends.push(0);
196
+ if (atEnd)
197
+ ends.push(span);
198
+ for (const s of ends) {
199
+ const at = vecAdd(edgeBase, vecScale(axis, s));
200
+ tools3d.push(slotTool3d(at, axis, inward, width, depth, part.thickness));
201
+ }
202
+ const notches2d = bendNotches2d(part, flange, width, depth, atStart, atEnd);
203
+ return { tools3d, notches2d };
204
+ }
205
+ /**
206
+ * Developed-plane notch rectangles for a bend relief, located at the bend-line ends
207
+ * and cutting into the PARENT rectangle (opposite the develop-out direction). The
208
+ * unfold is the source of truth for 2D placement, so we read the developed bend
209
+ * line straight from it and offset inward.
210
+ */
211
+ function bendNotches2d(part, flange, width, depth, atStart, atEnd) {
212
+ const placed = developedBendLine(part, flange);
213
+ if (placed === undefined)
214
+ return [];
215
+ const { bendA, bendB, inward } = placed;
216
+ const len = Math.max(dist2(bendA, bendB), EPS);
217
+ const along = [(bendB[0] - bendA[0]) / len, (bendB[1] - bendA[1]) / len];
218
+ const half = width / 2;
219
+ const make = (p) => {
220
+ const c0 = [p[0] - along[0] * half, p[1] - along[1] * half];
221
+ const c1 = [
222
+ p[0] + along[0] * half + inward[0] * depth,
223
+ p[1] + along[1] * half + inward[1] * depth,
224
+ ];
225
+ 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])];
226
+ };
227
+ const notches = [];
228
+ if (atStart)
229
+ notches.push(make(bendA));
230
+ if (atEnd)
231
+ notches.push(make(bendB));
232
+ return notches;
233
+ }
234
+ /**
235
+ * Locate a flange's bend line in the developed plane by re-running the unfold and
236
+ * matching by flange id — the unfold owns 2D placement, so the notch always
237
+ * coincides with the real developed geometry. Matching by id (not by geometric
238
+ * signature) keeps placement correct when several flanges share the same
239
+ * span/angle/direction, as in {@link autoBendReliefs}.
240
+ */
241
+ function developedBendLine(part, flange) {
242
+ const unfolded = unfold({ ...part, reliefs: undefined });
243
+ if (!unfolded.ok)
244
+ return undefined;
245
+ const bl = unfolded.value.pattern.bendLines.find((b) => b.id === flange.id);
246
+ if (bl === undefined)
247
+ return undefined;
248
+ const a = toPt2(curveStartPoint(bl.line));
249
+ const b = toPt2(curveEndPoint(bl.line));
250
+ return { bendA: a, bendB: b, inward: bl.inward };
251
+ }
252
+ /**
253
+ * Corner relief geometry: the shared-corner point of the two flanges' bend lines in
254
+ * world XY, plus a square developed-plane notch CENTRED on the reflex corner of the
255
+ * two flanges (`(baseLength, width)` for the classic +X/+Y L). Centring the square
256
+ * on the corner bites the base and both flange roots equally, so the developed
257
+ * outline stays a single closed loop (a notch landing its far corner exactly on the
258
+ * reflex vertex would pinch the boundary).
259
+ */
260
+ function cornerReliefGeometry(part, axisA, axisB, bendA, bendB, side, flangeA, flangeB) {
261
+ const corner = cornerPoint(bendA.axisOrigin, axisA, bendB.axisOrigin, axisB);
262
+ const [cx, cy] = developedCorner(part, flangeA, flangeB);
263
+ const h = side / 2;
264
+ const notch2d = [cx - h, cy - h, cx + h, cy + h];
265
+ return { cornerXY: [corner[0], corner[1]], notch2d };
266
+ }
267
+ /**
268
+ * Developed-plane base corner shared by two perpendicular base flanges, picked from
269
+ * their sides: an `xmax` flange's corner x is `baseLength`, an `xmin`'s is `0`; a
270
+ * `ymax`'s corner y is `width`, a `ymin`'s is `0`. Defaults to the +X/+Y corner.
271
+ */
272
+ function developedCorner(part, flangeA, flangeB) {
273
+ let cx = part.baseLength;
274
+ let cy = part.width;
275
+ for (const f of [flangeA, flangeB]) {
276
+ const side = f?.baseEdge.side;
277
+ if (side === 'xmin')
278
+ cx = 0;
279
+ else if (side === 'xmax')
280
+ cx = part.baseLength;
281
+ else if (side === 'ymin')
282
+ cy = 0;
283
+ else if (side === 'ymax')
284
+ cy = part.width;
285
+ }
286
+ return [cx, cy];
287
+ }
288
+ /** Point on bend A's line closest to bend B's line (their shared corner). */
289
+ function cornerPoint(originA, dirA, originB, dirB) {
290
+ const w0 = vecSub(originA, originB);
291
+ const a = vecDot(dirA, dirA);
292
+ const b = vecDot(dirA, dirB);
293
+ const c = vecDot(dirB, dirB);
294
+ const d = vecDot(dirA, w0);
295
+ const e = vecDot(dirB, w0);
296
+ const denom = a * c - b * b;
297
+ if (Math.abs(denom) < 1e-9)
298
+ return originA;
299
+ const sc = (b * e - c * d) / denom;
300
+ return vecAdd(originA, vecScale(dirA, sc));
301
+ }
302
+ /** In-plane perpendicular to `axis` pointing toward the part center (into the parent). */
303
+ function inwardRun(axisOrigin, axis, center) {
304
+ const perp = vecNormalize(vecCross(axis, Z_AXIS));
305
+ const toCenter = [center[0] - axisOrigin[0], center[1] - axisOrigin[1], 0];
306
+ const dot = perp[0] * toCenter[0] + perp[1] * toCenter[1];
307
+ return dot >= 0 ? perp : [-perp[0], -perp[1], -perp[2]];
308
+ }
309
+ /** Parent-edge length the flange folds off (base edge for root flanges). */
310
+ function parentEdgeLength(part, flange) {
311
+ const side = flange.baseEdge.side ?? 'xmax';
312
+ const onWidthEdge = side === 'xmax' || side === 'xmin';
313
+ const parentId = flange.baseEdge.parentId;
314
+ if (parentId === undefined || parentId === 'root' || parentId === 'face-0') {
315
+ return onWidthEdge ? part.width : part.baseLength;
316
+ }
317
+ const parent = part.flanges.find((f) => f.id === parentId);
318
+ if (parent === undefined)
319
+ return onWidthEdge ? part.width : part.baseLength;
320
+ return onWidthEdge ? parent.length : parent.span;
321
+ }
322
+ /**
323
+ * A box tool for a bend-relief slot: a `width × depth × (thickness + margins)` block
324
+ * whose `width` axis runs along the parent edge and `depth` axis points inward,
325
+ * placed so it straddles the edge point `at` and cuts through the parent surface.
326
+ */
327
+ function slotTool3d(at, along, inward, width, depth, thickness) {
328
+ const margin = thickness + 2;
329
+ // Canonical: width along +X, depth along +Y, height along +Z (cuts through Z).
330
+ // Rotating by `deg` sends +X→along and +Y→(along rotated +90° = [-along.y, along.x]).
331
+ // If that rotated +Y opposes `inward`, build the block extending in −Y so it cuts
332
+ // into the parent rather than out into the flange.
333
+ const rotPerp = [-along[1], along[0], 0];
334
+ 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]);
339
+ }
340
+ /**
341
+ * Square corner-relief tool: a `side × side × (height + margins)` block CENTRED on
342
+ * the corner point in XY and spanning the full part height, so it removes material
343
+ * from the base corner and both flange roots. Corner reliefs sit at axis-aligned
344
+ * base corners for the supported perpendicular flange shapes, so the tool stays
345
+ * axis-aligned.
346
+ */
347
+ function squareTool(solid, cornerXY, side) {
348
+ const b = getBounds(solid);
349
+ const margin = 2;
350
+ 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]);
353
+ }
354
+ /** Rotate a shape about +Z so its local +X axis points along `xT` (in the XY plane). */
355
+ function orientXY(shape, xT) {
356
+ const deg = (Math.atan2(xT[1], xT[0]) * 180) / Math.PI;
357
+ if (Math.abs(deg) < 1e-9)
358
+ return shape;
359
+ return rotate(shape, deg, { at: [0, 0, 0], axis: Z_AXIS });
360
+ }
361
+ function toPt2(v) {
362
+ return [v[0], v[1]];
363
+ }
364
+ function dist2(a, b) {
365
+ return Math.hypot(a[0] - b[0], a[1] - b[1]);
366
+ }
367
+ //# sourceMappingURL=reliefFns.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,33 @@
1
+ import { type Result } from '@faicad/faijs/brepjs-compat';
2
+ import type { BendReport, SheetMetalPart, UnfoldResult } from './types.js';
3
+ /**
4
+ * Build the bend-table JSON report directly from an authored part. Walks the
5
+ * same tree layout the unfold consumes so the per-bend values and the total flat
6
+ * size agree with the flat pattern. `allowance` is the consumed neutral-axis arc
7
+ * length (the bend allowance); `flatLength` is the straight flange leg past the
8
+ * bend — distinct values a downstream nesting/cut-list tool needs separately.
9
+ *
10
+ * @param part - the authored part to build the report from.
11
+ * @returns the bend-table report, or an error if the part tree or layout cannot
12
+ * be built.
13
+ */
14
+ export declare function buildReport(part: SheetMetalPart): Result<BendReport>;
15
+ /**
16
+ * Project the report already computed by `unfold` into the standalone
17
+ * `BendReport` shape, defensively re-validating its numeric fields. This keeps
18
+ * the report a pure function of a previously-unfolded result without re-walking
19
+ * the tree.
20
+ *
21
+ * @param result - the unfold result whose report is projected.
22
+ * @returns the standalone `BendReport`, or an error if a numeric field is not
23
+ * finite or positive.
24
+ */
25
+ export declare function reportFromUnfold(result: UnfoldResult): Result<BendReport>;
26
+ /**
27
+ * Serialize a `BendReport` to a stable, pretty-printed JSON string.
28
+ *
29
+ * @param report - the bend report to serialize.
30
+ * @returns the pretty-printed JSON string.
31
+ */
32
+ export declare function reportToJSON(report: BendReport): string;
33
+ //# sourceMappingURL=reportFns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportFns.d.ts","sourceRoot":"","sources":["../src/reportFns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAA4B,MAAM,6BAA6B,CAAC;AACpF,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"}
@@ -0,0 +1,71 @@
1
+ import { ok, err, validationError } from '@faicad/faijs/brepjs-compat';
2
+ import { featureTree } from './featureTreeFns.js';
3
+ import { layoutTree, reportFromLayout } from './unfoldFns.js';
4
+ /**
5
+ * Build the bend-table JSON report directly from an authored part. Walks the
6
+ * same tree layout the unfold consumes so the per-bend values and the total flat
7
+ * size agree with the flat pattern. `allowance` is the consumed neutral-axis arc
8
+ * length (the bend allowance); `flatLength` is the straight flange leg past the
9
+ * bend — distinct values a downstream nesting/cut-list tool needs separately.
10
+ *
11
+ * @param part - the authored part to build the report from.
12
+ * @returns the bend-table report, or an error if the part tree or layout cannot
13
+ * be built.
14
+ */
15
+ export function buildReport(part) {
16
+ const treeResult = featureTree(part);
17
+ if (!treeResult.ok)
18
+ return treeResult;
19
+ const tree = treeResult.value;
20
+ const baseLength = part.baseLength;
21
+ if (!Number.isFinite(baseLength) || baseLength <= 0) {
22
+ return err(validationError('INVALID_BASE_LENGTH', `part baseLength must be positive, got ${baseLength}`));
23
+ }
24
+ const width = part.width;
25
+ if (!Number.isFinite(width) || width <= 0) {
26
+ return err(validationError('INVALID_WIDTH', `part width must be positive, got ${width}`));
27
+ }
28
+ const layout = layoutTree(part, tree, baseLength, width);
29
+ if (!layout.ok)
30
+ return layout;
31
+ return ok(reportFromLayout(layout.value));
32
+ }
33
+ /**
34
+ * Project the report already computed by `unfold` into the standalone
35
+ * `BendReport` shape, defensively re-validating its numeric fields. This keeps
36
+ * the report a pure function of a previously-unfolded result without re-walking
37
+ * the tree.
38
+ *
39
+ * @param result - the unfold result whose report is projected.
40
+ * @returns the standalone `BendReport`, or an error if a numeric field is not
41
+ * finite or positive.
42
+ */
43
+ export function reportFromUnfold(result) {
44
+ const { report } = result;
45
+ for (const bend of report.bends) {
46
+ if (!Number.isFinite(bend.allowance) || bend.allowance < 0) {
47
+ return err(validationError('INVALID_ALLOWANCE', `bend '${bend.id}' allowance must be finite, non-negative, got ${bend.allowance}`));
48
+ }
49
+ if (!Number.isFinite(bend.flatLength) || bend.flatLength < 0) {
50
+ return err(validationError('INVALID_FLAT_LENGTH', `bend '${bend.id}' flatLength must be finite, non-negative, got ${bend.flatLength}`));
51
+ }
52
+ }
53
+ const [run, width] = report.totalFlatSize;
54
+ if (!Number.isFinite(run) || run <= 0 || !Number.isFinite(width) || width <= 0) {
55
+ return err(validationError('INVALID_FLAT_SIZE', `totalFlatSize must be finite with positive dimensions, got [${run}, ${width}]`));
56
+ }
57
+ return ok({
58
+ bends: report.bends.map((bend) => ({ ...bend })),
59
+ totalFlatSize: [run, width],
60
+ });
61
+ }
62
+ /**
63
+ * Serialize a `BendReport` to a stable, pretty-printed JSON string.
64
+ *
65
+ * @param report - the bend report to serialize.
66
+ * @returns the pretty-printed JSON string.
67
+ */
68
+ export function reportToJSON(report) {
69
+ return JSON.stringify(report, null, 2);
70
+ }
71
+ //# sourceMappingURL=reportFns.js.map
@@ -0,0 +1 @@
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/brepjs-compat';\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"]}
@@ -0,0 +1,43 @@
1
+ import { type Result } from '@faicad/faijs/brepjs-compat';
2
+ import type { TabSpec, SheetMetalPart } from './types.js';
3
+ /**
4
+ * Fuse a rectangular tab onto a named flat region's edge — additive material (the
5
+ * counterpart of a cutout). The tab is built as a region-local rectangle just past
6
+ * the chosen `side`, placed onto the correct folded face via the region's world
7
+ * {@link FlatFrame}, extruded through the sheet thickness and fused to the solid. The
8
+ * same local rectangle, mapped through the region's developed {@link Frame2}, is
9
+ * recorded as a {@link TabFeature} so {@link unfold} extends the OUTER outline by the
10
+ * protrusion (tabs add material, so the developed area grows). Guards a valid,
11
+ * single-bodied solid.
12
+ *
13
+ * @param part - the sheet metal part to attach a tab to.
14
+ * @param spec - the tab specification.
15
+ * @returns the updated part with the tab feature recorded, or an error.
16
+ */
17
+ export declare function addTab(part: SheetMetalPart, spec: TabSpec): Result<SheetMetalPart>;
18
+ /** The mating-slot placement for a {@link tabAndSlot} joint. */
19
+ export interface SlotPlacement {
20
+ region: string;
21
+ x: number;
22
+ y: number;
23
+ clearance?: number | undefined;
24
+ /** In-plane rotation of the slot (deg, CCW) in the mating region's local frame.
25
+ * Default 0 = slot length along the region's +x (bend-axis) direction; set this
26
+ * when the tab meets the slot region at a non-default orientation. */
27
+ angleDeg?: number | undefined;
28
+ }
29
+ /**
30
+ * Self-fixturing tab-and-slot joint: fuse a tab on one region and punch a matching
31
+ * SLOT CUTOUT on the mating region, sized so the tab's cross-section
32
+ * (`width × thickness`) inserts into the slot. The slot is `tab.width + clearance`
33
+ * long by `thickness + clearance` wide, centred at the mating region's local
34
+ * `(x, y)`. The slot is always strictly larger than the tab cross-section, so the
35
+ * joint mates (verified numerically by callers). Clearance defaults to `0.1` mm.
36
+ *
37
+ * @param part - the sheet metal part to process.
38
+ * @param tab - the tab specification.
39
+ * @param slot - the slot placement.
40
+ * @returns the updated part with the tab-and-slot feature recorded, or an error.
41
+ */
42
+ export declare function tabAndSlot(part: SheetMetalPart, tab: TabSpec, slot: SlotPlacement): Result<SheetMetalPart>;
43
+ //# sourceMappingURL=tabFns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabFns.d.ts","sourceRoot":"","sources":["../src/tabFns.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAgBZ,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,OAAO,EAAc,cAAc,EAAE,MAAM,YAAY,CAAC;AAUtE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC,CAyDlF;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,wBAAgB,UAAU,CACxB,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,aAAa,GAClB,MAAM,CAAC,cAAc,CAAC,CAqBxB"}