@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,403 @@
1
+ import { ok, err, validationError, box, cylinder, fuse, cut, intersect, rotate, translate, vecAdd, vecScale, vecSub, vecDot, vecCross, vecNormalize, } from '@faicad/faijs/brepjs-compat';
2
+ import { normalizeSolid } from './internal.js';
3
+ import { developedLength } from './allowanceFns.js';
4
+ /**
5
+ * Author a contour flange: an OPEN 2D profile (alternating line/arc segments) swept
6
+ * along one straight edge of the base flat. Each arc is a cylindrical bend, each
7
+ * line a flat leg; consecutive segments chain frame-to-frame exactly as
8
+ * {@link authorPart} chains flanges, building one connected multi-bend cross-section
9
+ * (a return, a hat, a J). The recorded {@link ContourFlangeFeature} carries each
10
+ * segment's EXACT developed length (lines: `length`; arcs: the canonical
11
+ * {@link developedLength}) so the unfold lays the strip out straight with no error.
12
+ * Construction stays on the public, OCCT-WASM-safe API.
13
+ * @param part the sheet-metal part the contour flange is added to (must already carry a solid)
14
+ * @param spec the contour-flange specification: profile segments, side, offset, width and optional bend rule
15
+ * @returns a `Result<SheetMetalPart>` — `ok` with the part plus the fused solid and the recorded
16
+ * {@link ContourFlangeFeature}, or `err` with a `validationError` on invalid input
17
+ */
18
+ export function authorContourFlange(part, spec) {
19
+ if (part.solid === undefined) {
20
+ return err(validationError('NO_SOLID', `part has no solid to attach contour flange '${spec.id}'`));
21
+ }
22
+ if (spec.id === '' || spec.id.includes('::')) {
23
+ return err(validationError('INVALID_CONTOUR_ID', `contour flange id must be non-empty and must not contain '::', got '${spec.id}'`));
24
+ }
25
+ for (const existing of part.contourFlanges ?? []) {
26
+ if (existing.id === spec.id) {
27
+ return err(validationError('DUPLICATE_CONTOUR', `duplicate contour flange id '${spec.id}'`));
28
+ }
29
+ }
30
+ if (spec.profile.length === 0) {
31
+ return err(validationError('EMPTY_PROFILE', `contour flange '${spec.id}' profile is empty`));
32
+ }
33
+ const thickness = part.thickness;
34
+ const edge = baseEdge(part, spec.side);
35
+ const offset = spec.offset ?? 0;
36
+ const span = spec.width ?? edge.length;
37
+ if (!Number.isFinite(offset) || offset < -1e-9) {
38
+ return err(validationError('INVALID_OFFSET', `contour flange '${spec.id}' offset must be non-negative`));
39
+ }
40
+ if (!Number.isFinite(span) || span <= 0) {
41
+ return err(validationError('INVALID_CONTOUR_WIDTH', `contour flange '${spec.id}' width must be positive`));
42
+ }
43
+ if (offset + span > edge.length + 1e-6) {
44
+ return err(validationError('CONTOUR_OUT_OF_BOUNDS', `contour flange '${spec.id}' [${offset}, ${offset + span}] exceeds base edge length ${edge.length}`));
45
+ }
46
+ // The profile is swept across `span` along the base edge starting at `offset`.
47
+ const stripStart = vecAdd(edge.start, vecScale(edge.dir, offset));
48
+ let frame = {
49
+ origin: stripStart,
50
+ run: edge.out,
51
+ n: [0, 0, 1],
52
+ axis: edge.dir,
53
+ };
54
+ let solid = part.solid;
55
+ const bends = [];
56
+ const segments = [];
57
+ let devTotal = 0;
58
+ let arcIndex = 0;
59
+ for (let i = 0; i < spec.profile.length; i += 1) {
60
+ const seg = spec.profile[i];
61
+ if (seg === undefined)
62
+ continue;
63
+ if (seg.kind === 'line') {
64
+ if (!Number.isFinite(seg.length) || seg.length <= 0) {
65
+ return err(validationError('INVALID_SEGMENT_LENGTH', `contour flange '${spec.id}' line segment ${i} length must be positive`));
66
+ }
67
+ const built = buildLineLeg(solid, frame, span, thickness, seg.length);
68
+ if (!built.ok)
69
+ return built;
70
+ solid = built.value.solid;
71
+ frame = built.value.frame;
72
+ segments.push({ kind: 'line', dev: seg.length });
73
+ devTotal += seg.length;
74
+ }
75
+ else {
76
+ const rule = spec.rule ?? { innerRadius: seg.radius, kFactor: 0.44 };
77
+ const built = buildArcBend(solid, frame, span, thickness, seg);
78
+ if (!built.ok)
79
+ return built;
80
+ solid = built.value.solid;
81
+ frame = built.value.frame;
82
+ const devResult = developedLength(seg.angleDeg, thickness, { ...rule, innerRadius: seg.radius });
83
+ if (!devResult.ok)
84
+ return devResult;
85
+ const dev = devResult.value;
86
+ const bendId = `contour::${spec.id}::${arcIndex}`;
87
+ arcIndex += 1;
88
+ bends.push({
89
+ id: bendId,
90
+ axisOrigin: [built.value.axisOrigin[0], built.value.axisOrigin[1], built.value.axisOrigin[2]],
91
+ axisDir: [edge.dir[0], edge.dir[1], edge.dir[2]],
92
+ angleDeg: seg.angleDeg,
93
+ direction: seg.direction,
94
+ rule: { ...rule, innerRadius: seg.radius },
95
+ });
96
+ segments.push({ kind: 'arc', dev, angleDeg: seg.angleDeg, direction: seg.direction, bendId });
97
+ devTotal += dev;
98
+ }
99
+ }
100
+ const feature = {
101
+ id: spec.id,
102
+ side: spec.side,
103
+ offset,
104
+ span,
105
+ developedLength: devTotal,
106
+ segments,
107
+ };
108
+ return ok({
109
+ ...part,
110
+ solid: normalizeSolid(solid),
111
+ bends: [...part.bends, ...bends],
112
+ contourFlanges: [...(part.contourFlanges ?? []), feature],
113
+ });
114
+ }
115
+ /**
116
+ * Add one straight leg of `legLength` extending from `frame.origin` along
117
+ * `frame.run`, full thickness along `frame.n`, across `span` along `frame.axis`.
118
+ * The next segment's frame starts at the leg's far end.
119
+ * @param base the solid the new leg is fused onto
120
+ * @param frame the current segment frame the leg is built in
121
+ * @param span the flange width along the bend axis
122
+ * @param thickness the sheet thickness
123
+ * @param legLength the straight-run length of the leg
124
+ * @returns a `Result<BuiltSegment>` — `ok` with the fused solid and the next segment's
125
+ * frame, or `err` when the boolean fuse fails
126
+ */
127
+ export function buildLineLeg(base, frame, span, thickness, legLength) {
128
+ // Canonical box (legLength × span × thickness) at the origin, mapped onto the
129
+ // current segment frame: +X→run, +Y→axis, +Z→n.
130
+ const place = frameTransform(frame.run, frame.axis, frame.n, frame.origin);
131
+ const canon = box(legLength, span, thickness);
132
+ const placed = place.solid(canon);
133
+ const fused = fuse(base, placed);
134
+ if (!fused.ok)
135
+ return fused;
136
+ const nextOrigin = vecAdd(frame.origin, vecScale(frame.run, legLength));
137
+ return ok({ solid: fused.value, frame: { ...frame, origin: nextOrigin } });
138
+ }
139
+ /**
140
+ * Add one cylindrical bend turning the running direction by `angleDeg`. Mirrors
141
+ * {@link authorPart}'s bend patch: a hollow tube (outer R+T, inner R) intersected
142
+ * with the fold wedge, in a canonical frame mapped onto the current segment frame.
143
+ * The returned frame's run/normal are rotated by the fold so the next leg continues
144
+ * tangentially.
145
+ * @param base the solid the bend is fused onto
146
+ * @param frame the current segment frame the bend turns from
147
+ * @param span the flange width along the bend axis
148
+ * @param thickness the sheet thickness
149
+ * @param seg the arc profile segment: radius, angle and fold direction
150
+ * @returns a `Result<BuiltArc>` — `ok` with the fused solid, the rotated next frame and the
151
+ * world-space bend axis origin, or `err` with a `validationError` on invalid geometry
152
+ */
153
+ export function buildArcBend(base, frame, span, thickness, seg) {
154
+ const r = seg.radius;
155
+ if (!Number.isFinite(r) || r < 0) {
156
+ return err(validationError('INVALID_RADIUS', `contour arc radius must be non-negative, got ${r}`));
157
+ }
158
+ if (!Number.isFinite(seg.angleDeg) || seg.angleDeg <= 0 || seg.angleDeg > 180) {
159
+ return err(validationError('INVALID_ARC_ANGLE', `contour arc angleDeg must be in (0, 180], got ${seg.angleDeg}`));
160
+ }
161
+ const sign = seg.direction === 'up' ? 1 : -1;
162
+ // Canonical assembly (authorPart convention): bend axis +Y, run +X, top +Z, with
163
+ // the bend-axis centre line at z = T+r (up) / −r (down). The current segment frame
164
+ // maps +X→run, +Y→axis, +Z→n.
165
+ const canonAxisZ = sign > 0 ? thickness + r : -r;
166
+ const canonOrigin = [0, 0, canonAxisZ];
167
+ const place = frameTransform(frame.run, frame.axis, frame.n, frame.origin);
168
+ const patch = buildBendPatch(canonOrigin, seg.angleDeg, r, span, thickness, seg.direction);
169
+ if (!patch.ok)
170
+ return patch;
171
+ const placed = place.solid(patch.value);
172
+ const fused = fuse(base, placed);
173
+ if (!fused.ok)
174
+ return fused;
175
+ // Fold run/normal by ∓θ about the axis (authorPart: rotate −sign·θ about +Y).
176
+ const theta = (seg.angleDeg * Math.PI) / 180;
177
+ const a = -sign * theta;
178
+ const ca = Math.cos(a);
179
+ const sa = Math.sin(a);
180
+ // Canonical run +X folds to [ca, 0, −sa]; normal +Z to [sa, 0, ca].
181
+ const runC = [ca, 0, -sa];
182
+ const nC = [sa, 0, ca];
183
+ // The post-bend exit point in canonical coords: the near-bottom corner [0,0,0]
184
+ // rotated about the pivot [0,0,axisZ].
185
+ const dz = -canonAxisZ;
186
+ const cornerC = [dz * sa, 0, canonAxisZ + dz * ca];
187
+ const nextOrigin = place.point(cornerC);
188
+ const nextRun = place.vector(runC);
189
+ const nextN = place.vector(nC);
190
+ const axisOrigin = place.point(canonOrigin);
191
+ return ok({
192
+ solid: fused.value,
193
+ frame: { origin: nextOrigin, run: nextRun, n: nextN, axis: frame.axis },
194
+ axisOrigin,
195
+ });
196
+ }
197
+ /**
198
+ * Rigid map of a canonical assembly (run +X, axis +Y, normal +Z, contact at the
199
+ * origin) onto a world frame: align +X→`runT`, +Y→`axisT`, +Z→`nT`, then translate
200
+ * the origin to `origin`.
201
+ */
202
+ function frameTransform(runT, axisT, nT, origin) {
203
+ const m = [
204
+ runT[0], axisT[0], nT[0],
205
+ runT[1], axisT[1], nT[1],
206
+ runT[2], axisT[2], nT[2],
207
+ ];
208
+ const aa = matrixToAxisAngle(m);
209
+ const applyR = (v) => vecAdd(vecAdd(vecScale(runT, v[0]), vecScale(axisT, v[1])), vecScale(nT, v[2]));
210
+ return {
211
+ solid: (s) => {
212
+ const rotated = aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, { at: [0, 0, 0], axis: aa.axis });
213
+ return translate(rotated, origin);
214
+ },
215
+ point: (p) => vecAdd(applyR(p), origin),
216
+ vector: applyR,
217
+ };
218
+ }
219
+ /**
220
+ * Resolve one of the four edges of the base flat (the contour-flange root).
221
+ * @param part the sheet-metal part whose base flat edge is resolved
222
+ * @param side which of the four base-flat edges to resolve (`'xmax'`/`'xmin'`/`'ymax'`/`'ymin'`)
223
+ * @returns the resolved {@link BaseEdge} — edge direction, outward normal, start point and length
224
+ */
225
+ export function baseEdge(part, side) {
226
+ const o = [0, 0, 0];
227
+ const u = [1, 0, 0];
228
+ const v = [0, 1, 0];
229
+ const n = [0, 0, 1];
230
+ const uLen = part.baseLength;
231
+ const vLen = part.width;
232
+ const uTop = vecAdd(o, vecScale(u, uLen));
233
+ const vTop = vecAdd(o, vecScale(v, vLen));
234
+ const uvTop = vecAdd(uTop, vecScale(v, vLen));
235
+ let out;
236
+ let length;
237
+ let a;
238
+ let b;
239
+ switch (side) {
240
+ case 'xmax':
241
+ out = u;
242
+ length = vLen;
243
+ a = uTop;
244
+ b = uvTop;
245
+ break;
246
+ case 'xmin':
247
+ out = vecScale(u, -1);
248
+ length = vLen;
249
+ a = o;
250
+ b = vTop;
251
+ break;
252
+ case 'ymax':
253
+ out = v;
254
+ length = uLen;
255
+ a = vTop;
256
+ b = uvTop;
257
+ break;
258
+ case 'ymin':
259
+ out = vecScale(v, -1);
260
+ length = uLen;
261
+ a = o;
262
+ b = uTop;
263
+ break;
264
+ }
265
+ const dir = vecNormalize(vecCross(n, out));
266
+ const toB = vecSub(b, a);
267
+ const start = vecDot(toB, dir) >= 0 ? a : b;
268
+ return { dir, out, start, length };
269
+ }
270
+ /**
271
+ * Resolve one of the four edges of an arbitrary flat region (its world top-surface
272
+ * frame), the region-aware generalization of {@link baseEdge}: the base is the
273
+ * special case where `origin = 0`, `u = +X`, `v = +Y`, `n = +Z`. Used by hems and
274
+ * jogs to chain their profile off a named flange (or the base) edge.
275
+ * @param frame the region's world top-surface frame (origin, u/v/n axes and extents)
276
+ * @param side which of the four region edges to resolve
277
+ * @returns the resolved {@link BaseEdge} — edge direction, outward normal, start point and length
278
+ */
279
+ export function regionEdge(frame, side) {
280
+ const o = frame.origin;
281
+ const uTop = vecAdd(o, vecScale(frame.u, frame.uLen));
282
+ const vTop = vecAdd(o, vecScale(frame.v, frame.vLen));
283
+ const uvTop = vecAdd(uTop, vecScale(frame.v, frame.vLen));
284
+ let out;
285
+ let length;
286
+ let a;
287
+ let b;
288
+ switch (side) {
289
+ case 'xmax':
290
+ out = frame.u;
291
+ length = frame.vLen;
292
+ a = uTop;
293
+ b = uvTop;
294
+ break;
295
+ case 'xmin':
296
+ out = vecScale(frame.u, -1);
297
+ length = frame.vLen;
298
+ a = o;
299
+ b = vTop;
300
+ break;
301
+ case 'ymax':
302
+ out = frame.v;
303
+ length = frame.uLen;
304
+ a = vTop;
305
+ b = uvTop;
306
+ break;
307
+ case 'ymin':
308
+ out = vecScale(frame.v, -1);
309
+ length = frame.uLen;
310
+ a = o;
311
+ b = uTop;
312
+ break;
313
+ }
314
+ const dir = vecNormalize(vecCross(frame.n, out));
315
+ const toB = vecSub(b, a);
316
+ const start = vecDot(toB, dir) >= 0 ? a : b;
317
+ return { dir, out, start, length };
318
+ }
319
+ /**
320
+ * The starting {@link SegmentFrame} for a profile chained off `edge` at `offset`
321
+ * along it: contact at the edge, run pointing outward, normal = the region normal,
322
+ * bend axis along the edge. Shared by the contour-flange, hem and jog builders.
323
+ * @param edge the resolved region edge the profile starts from
324
+ * @param n the region normal, used as the frame's +Z
325
+ * @param offset the start position along the edge, measured from `edge.start`
326
+ * @returns the {@link SegmentFrame} with contact at the edge at `offset`, run pointing
327
+ * outward and axis along the edge
328
+ */
329
+ export function initialSegmentFrame(edge, n, offset) {
330
+ return {
331
+ origin: vecAdd(edge.start, vecScale(edge.dir, offset)),
332
+ run: edge.out,
333
+ n,
334
+ axis: edge.dir,
335
+ };
336
+ }
337
+ /** Cylindrical bend patch (hollow tube ∩ fold wedge), mirroring authorFns. */
338
+ function buildBendPatch(axisOrigin, thetaDeg, r, width, thickness, direction) {
339
+ const outerR = r + thickness;
340
+ const outer = cylinder(outerR, width, { at: axisOrigin, axis: [0, 1, 0] });
341
+ let tube = outer;
342
+ if (r > 1e-9) {
343
+ const inner = cylinder(r, width, { at: axisOrigin, axis: [0, 1, 0] });
344
+ const carved = cut(outer, inner);
345
+ if (!carved.ok)
346
+ return carved;
347
+ tube = carved.value;
348
+ }
349
+ const wedge = buildWedge(axisOrigin, thetaDeg, outerR, width, direction);
350
+ if (!wedge.ok)
351
+ return wedge;
352
+ return intersect(tube, wedge.value);
353
+ }
354
+ function buildWedge(axisOrigin, thetaDeg, outerR, width, direction) {
355
+ const span = outerR * 2 + 2;
356
+ const margin = 1;
357
+ const blockA = box(span, width + 2 * margin, 2 * span);
358
+ const halfA = translate(blockA, [axisOrigin[0], axisOrigin[1] - margin, axisOrigin[2] - span]);
359
+ const sign = direction === 'up' ? 1 : -1;
360
+ const halfB = rotate(halfA, sign * (180 - thetaDeg), { at: axisOrigin, axis: [0, 1, 0] });
361
+ return intersect(halfA, halfB);
362
+ }
363
+ /** Axis-angle of a 3×3 rotation matrix (row-major). */
364
+ function matrixToAxisAngle(m) {
365
+ const m00 = m[0] ?? 0;
366
+ const m01 = m[1] ?? 0;
367
+ const m02 = m[2] ?? 0;
368
+ const m10 = m[3] ?? 0;
369
+ const m11 = m[4] ?? 0;
370
+ const m12 = m[5] ?? 0;
371
+ const m20 = m[6] ?? 0;
372
+ const m21 = m[7] ?? 0;
373
+ const m22 = m[8] ?? 0;
374
+ const trace = m00 + m11 + m22;
375
+ const cos = Math.max(-1, Math.min(1, (trace - 1) / 2));
376
+ const angle = Math.acos(cos);
377
+ const angleDeg = (angle * 180) / Math.PI;
378
+ if (angle < 1e-9)
379
+ return { axis: [0, 0, 1], angleDeg: 0 };
380
+ if (Math.PI - angle < 1e-6) {
381
+ const xx = (m00 + 1) / 2;
382
+ const yy = (m11 + 1) / 2;
383
+ const zz = (m22 + 1) / 2;
384
+ let axis;
385
+ if (xx >= yy && xx >= zz) {
386
+ const x = Math.sqrt(Math.max(xx, 0));
387
+ axis = [x, (m01 + m10) / (4 * x), (m02 + m20) / (4 * x)];
388
+ }
389
+ else if (yy >= zz) {
390
+ const y = Math.sqrt(Math.max(yy, 0));
391
+ axis = [(m01 + m10) / (4 * y), y, (m12 + m21) / (4 * y)];
392
+ }
393
+ else {
394
+ const z = Math.sqrt(Math.max(zz, 0));
395
+ axis = [(m02 + m20) / (4 * z), (m12 + m21) / (4 * z), z];
396
+ }
397
+ return { axis: vecNormalize(axis), angleDeg: 180 };
398
+ }
399
+ const denom = 2 * Math.sin(angle);
400
+ const axis = [(m21 - m12) / denom, (m02 - m20) / denom, (m10 - m01) / denom];
401
+ return { axis: vecNormalize(axis), angleDeg };
402
+ }
403
+ //# sourceMappingURL=contourFlangeFns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contourFlangeFns.js","sourceRoot":"","sources":["../src/contourFlangeFns.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Result,\n type Vec3,\n type Solid,\n type ValidSolid,\n ok,\n err,\n validationError,\n box,\n cylinder,\n fuse,\n cut,\n intersect,\n rotate,\n translate,\n vecAdd,\n vecScale,\n vecSub,\n vecDot,\n vecCross,\n vecNormalize,\n} from '@faicad/faijs/brepjs-compat';\nimport type {\n BendFeature,\n BendRule,\n ContourFlangeFeature,\n ContourFlangeSpec,\n FlatSide,\n ProfileSegment,\n SheetMetalPart,\n} from './types.js';\nimport { normalizeSolid } from './internal.js';\nimport { developedLength } from './allowanceFns.js';\n\n/**\n * The local frame of one contour-flange segment: the contact origin plus the\n * segment's run, normal and shared bend-axis directions.\n */\nexport interface SegmentFrame {\n /** Origin of the −normal (bottom) surface at the start of this segment. */\n origin: Vec3;\n /** Outward run direction (the segment's local +X). */\n run: Vec3;\n /** Top-surface normal (+Z), n = bendAxis × run. */\n n: Vec3;\n /** Bend axis along the base edge (+Y), shared by every segment. */\n axis: Vec3;\n}\n\n/**\n * Author a contour flange: an OPEN 2D profile (alternating line/arc segments) swept\n * along one straight edge of the base flat. Each arc is a cylindrical bend, each\n * line a flat leg; consecutive segments chain frame-to-frame exactly as\n * {@link authorPart} chains flanges, building one connected multi-bend cross-section\n * (a return, a hat, a J). The recorded {@link ContourFlangeFeature} carries each\n * segment's EXACT developed length (lines: `length`; arcs: the canonical\n * {@link developedLength}) so the unfold lays the strip out straight with no error.\n * Construction stays on the public, OCCT-WASM-safe API.\n * @param part the sheet-metal part the contour flange is added to (must already carry a solid)\n * @param spec the contour-flange specification: profile segments, side, offset, width and optional bend rule\n * @returns a `Result<SheetMetalPart>` — `ok` with the part plus the fused solid and the recorded\n * {@link ContourFlangeFeature}, or `err` with a `validationError` on invalid input\n */\nexport function authorContourFlange(\n part: SheetMetalPart,\n spec: ContourFlangeSpec\n): Result<SheetMetalPart> {\n if (part.solid === undefined) {\n return err(validationError('NO_SOLID', `part has no solid to attach contour flange '${spec.id}'`));\n }\n if (spec.id === '' || spec.id.includes('::')) {\n return err(\n validationError('INVALID_CONTOUR_ID', `contour flange id must be non-empty and must not contain '::', got '${spec.id}'`)\n );\n }\n for (const existing of part.contourFlanges ?? []) {\n if (existing.id === spec.id) {\n return err(validationError('DUPLICATE_CONTOUR', `duplicate contour flange id '${spec.id}'`));\n }\n }\n if (spec.profile.length === 0) {\n return err(validationError('EMPTY_PROFILE', `contour flange '${spec.id}' profile is empty`));\n }\n\n const thickness = part.thickness;\n const edge = baseEdge(part, spec.side);\n const offset = spec.offset ?? 0;\n const span = spec.width ?? edge.length;\n if (!Number.isFinite(offset) || offset < -1e-9) {\n return err(validationError('INVALID_OFFSET', `contour flange '${spec.id}' offset must be non-negative`));\n }\n if (!Number.isFinite(span) || span <= 0) {\n return err(validationError('INVALID_CONTOUR_WIDTH', `contour flange '${spec.id}' width must be positive`));\n }\n if (offset + span > edge.length + 1e-6) {\n return err(\n validationError('CONTOUR_OUT_OF_BOUNDS', `contour flange '${spec.id}' [${offset}, ${offset + span}] exceeds base edge length ${edge.length}`)\n );\n }\n\n // The profile is swept across `span` along the base edge starting at `offset`.\n const stripStart = vecAdd(edge.start, vecScale(edge.dir, offset));\n let frame: SegmentFrame = {\n origin: stripStart,\n run: edge.out,\n n: [0, 0, 1],\n axis: edge.dir,\n };\n\n let solid: Solid = part.solid;\n const bends: BendFeature[] = [];\n const segments: ContourFlangeFeature['segments'] = [];\n let devTotal = 0;\n let arcIndex = 0;\n\n for (let i = 0; i < spec.profile.length; i += 1) {\n const seg = spec.profile[i];\n if (seg === undefined) continue;\n if (seg.kind === 'line') {\n if (!Number.isFinite(seg.length) || seg.length <= 0) {\n return err(validationError('INVALID_SEGMENT_LENGTH', `contour flange '${spec.id}' line segment ${i} length must be positive`));\n }\n const built = buildLineLeg(solid, frame, span, thickness, seg.length);\n if (!built.ok) return built;\n solid = built.value.solid;\n frame = built.value.frame;\n segments.push({ kind: 'line', dev: seg.length });\n devTotal += seg.length;\n } else {\n const rule: BendRule = spec.rule ?? { innerRadius: seg.radius, kFactor: 0.44 };\n const built = buildArcBend(solid, frame, span, thickness, seg);\n if (!built.ok) return built;\n solid = built.value.solid;\n frame = built.value.frame;\n\n const devResult = developedLength(seg.angleDeg, thickness, { ...rule, innerRadius: seg.radius });\n if (!devResult.ok) return devResult;\n const dev = devResult.value;\n\n const bendId = `contour::${spec.id}::${arcIndex}`;\n arcIndex += 1;\n bends.push({\n id: bendId,\n axisOrigin: [built.value.axisOrigin[0], built.value.axisOrigin[1], built.value.axisOrigin[2]],\n axisDir: [edge.dir[0], edge.dir[1], edge.dir[2]],\n angleDeg: seg.angleDeg,\n direction: seg.direction,\n rule: { ...rule, innerRadius: seg.radius },\n });\n segments.push({ kind: 'arc', dev, angleDeg: seg.angleDeg, direction: seg.direction, bendId });\n devTotal += dev;\n }\n }\n\n const feature: ContourFlangeFeature = {\n id: spec.id,\n side: spec.side,\n offset,\n span,\n developedLength: devTotal,\n segments,\n };\n\n return ok({\n ...part,\n solid: normalizeSolid(solid),\n bends: [...part.bends, ...bends],\n contourFlanges: [...(part.contourFlanges ?? []), feature],\n });\n}\n\n/**\n * Result of building one contour-flange segment: the fused solid and the next\n * segment's chained frame.\n */\nexport interface BuiltSegment {\n solid: Solid;\n frame: SegmentFrame;\n}\n\n/**\n * Result of building a cylindrical bend: {@link BuiltSegment} plus the world-space\n * origin of the bend axis.\n */\nexport interface BuiltArc extends BuiltSegment {\n axisOrigin: Vec3;\n}\n\n/**\n * Add one straight leg of `legLength` extending from `frame.origin` along\n * `frame.run`, full thickness along `frame.n`, across `span` along `frame.axis`.\n * The next segment's frame starts at the leg's far end.\n * @param base the solid the new leg is fused onto\n * @param frame the current segment frame the leg is built in\n * @param span the flange width along the bend axis\n * @param thickness the sheet thickness\n * @param legLength the straight-run length of the leg\n * @returns a `Result<BuiltSegment>` — `ok` with the fused solid and the next segment's\n * frame, or `err` when the boolean fuse fails\n */\nexport function buildLineLeg(\n base: Solid,\n frame: SegmentFrame,\n span: number,\n thickness: number,\n legLength: number\n): Result<BuiltSegment> {\n // Canonical box (legLength × span × thickness) at the origin, mapped onto the\n // current segment frame: +X→run, +Y→axis, +Z→n.\n const place = frameTransform(frame.run, frame.axis, frame.n, frame.origin);\n const canon = box(legLength, span, thickness);\n const placed = place.solid(canon);\n const fused = fuse(base, placed);\n if (!fused.ok) return fused;\n\n const nextOrigin = vecAdd(frame.origin, vecScale(frame.run, legLength));\n return ok({ solid: fused.value, frame: { ...frame, origin: nextOrigin } });\n}\n\n/**\n * Add one cylindrical bend turning the running direction by `angleDeg`. Mirrors\n * {@link authorPart}'s bend patch: a hollow tube (outer R+T, inner R) intersected\n * with the fold wedge, in a canonical frame mapped onto the current segment frame.\n * The returned frame's run/normal are rotated by the fold so the next leg continues\n * tangentially.\n * @param base the solid the bend is fused onto\n * @param frame the current segment frame the bend turns from\n * @param span the flange width along the bend axis\n * @param thickness the sheet thickness\n * @param seg the arc profile segment: radius, angle and fold direction\n * @returns a `Result<BuiltArc>` — `ok` with the fused solid, the rotated next frame and the\n * world-space bend axis origin, or `err` with a `validationError` on invalid geometry\n */\nexport function buildArcBend(\n base: Solid,\n frame: SegmentFrame,\n span: number,\n thickness: number,\n seg: Extract<ProfileSegment, { kind: 'arc' }>\n): Result<BuiltArc> {\n const r = seg.radius;\n if (!Number.isFinite(r) || r < 0) {\n return err(validationError('INVALID_RADIUS', `contour arc radius must be non-negative, got ${r}`));\n }\n if (!Number.isFinite(seg.angleDeg) || seg.angleDeg <= 0 || seg.angleDeg > 180) {\n return err(validationError('INVALID_ARC_ANGLE', `contour arc angleDeg must be in (0, 180], got ${seg.angleDeg}`));\n }\n\n const sign = seg.direction === 'up' ? 1 : -1;\n // Canonical assembly (authorPart convention): bend axis +Y, run +X, top +Z, with\n // the bend-axis centre line at z = T+r (up) / −r (down). The current segment frame\n // maps +X→run, +Y→axis, +Z→n.\n const canonAxisZ = sign > 0 ? thickness + r : -r;\n const canonOrigin: Vec3 = [0, 0, canonAxisZ];\n const place = frameTransform(frame.run, frame.axis, frame.n, frame.origin);\n\n const patch = buildBendPatch(canonOrigin, seg.angleDeg, r, span, thickness, seg.direction);\n if (!patch.ok) return patch;\n const placed = place.solid(patch.value);\n const fused = fuse(base, placed);\n if (!fused.ok) return fused;\n\n // Fold run/normal by ∓θ about the axis (authorPart: rotate −sign·θ about +Y).\n const theta = (seg.angleDeg * Math.PI) / 180;\n const a = -sign * theta;\n const ca = Math.cos(a);\n const sa = Math.sin(a);\n // Canonical run +X folds to [ca, 0, −sa]; normal +Z to [sa, 0, ca].\n const runC: Vec3 = [ca, 0, -sa];\n const nC: Vec3 = [sa, 0, ca];\n // The post-bend exit point in canonical coords: the near-bottom corner [0,0,0]\n // rotated about the pivot [0,0,axisZ].\n const dz = -canonAxisZ;\n const cornerC: Vec3 = [dz * sa, 0, canonAxisZ + dz * ca];\n\n const nextOrigin = place.point(cornerC);\n const nextRun = place.vector(runC);\n const nextN = place.vector(nC);\n const axisOrigin = place.point(canonOrigin);\n\n return ok({\n solid: fused.value,\n frame: { origin: nextOrigin, run: nextRun, n: nextN, axis: frame.axis },\n axisOrigin,\n });\n}\n\ninterface PlacedTransform {\n solid: (s: Solid) => Solid;\n point: (p: Vec3) => Vec3;\n vector: (v: Vec3) => Vec3;\n}\n\n/**\n * Rigid map of a canonical assembly (run +X, axis +Y, normal +Z, contact at the\n * origin) onto a world frame: align +X→`runT`, +Y→`axisT`, +Z→`nT`, then translate\n * the origin to `origin`.\n */\nfunction frameTransform(runT: Vec3, axisT: Vec3, nT: Vec3, origin: Vec3): PlacedTransform {\n const m: number[] = [\n runT[0], axisT[0], nT[0],\n runT[1], axisT[1], nT[1],\n runT[2], axisT[2], nT[2],\n ];\n const aa = matrixToAxisAngle(m);\n const applyR = (v: Vec3): Vec3 =>\n vecAdd(vecAdd(vecScale(runT, v[0]), vecScale(axisT, v[1])), vecScale(nT, v[2]));\n return {\n solid: (s: Solid) => {\n const rotated = aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, { at: [0, 0, 0], axis: aa.axis });\n return translate(rotated, origin);\n },\n point: (p: Vec3) => vecAdd(applyR(p), origin),\n vector: applyR,\n };\n}\n\n/**\n * One of the four straight edges of a flat region, resolved into the start line a\n * profile is chained from: edge direction, outward normal, start point and length.\n */\nexport interface BaseEdge {\n dir: Vec3;\n out: Vec3;\n start: Vec3;\n length: number;\n}\n\n/**\n * Resolve one of the four edges of the base flat (the contour-flange root).\n * @param part the sheet-metal part whose base flat edge is resolved\n * @param side which of the four base-flat edges to resolve (`'xmax'`/`'xmin'`/`'ymax'`/`'ymin'`)\n * @returns the resolved {@link BaseEdge} — edge direction, outward normal, start point and length\n */\nexport function baseEdge(part: SheetMetalPart, side: FlatSide): BaseEdge {\n const o: Vec3 = [0, 0, 0];\n const u: Vec3 = [1, 0, 0];\n const v: Vec3 = [0, 1, 0];\n const n: Vec3 = [0, 0, 1];\n const uLen = part.baseLength;\n const vLen = part.width;\n\n const uTop = vecAdd(o, vecScale(u, uLen));\n const vTop = vecAdd(o, vecScale(v, vLen));\n const uvTop = vecAdd(uTop, vecScale(v, vLen));\n\n let out: Vec3;\n let length: number;\n let a: Vec3;\n let b: Vec3;\n switch (side) {\n case 'xmax':\n out = u;\n length = vLen;\n a = uTop;\n b = uvTop;\n break;\n case 'xmin':\n out = vecScale(u, -1);\n length = vLen;\n a = o;\n b = vTop;\n break;\n case 'ymax':\n out = v;\n length = uLen;\n a = vTop;\n b = uvTop;\n break;\n case 'ymin':\n out = vecScale(v, -1);\n length = uLen;\n a = o;\n b = uTop;\n break;\n }\n const dir = vecNormalize(vecCross(n, out));\n const toB = vecSub(b, a);\n const start = vecDot(toB, dir) >= 0 ? a : b;\n return { dir, out, start, length };\n}\n\n/**\n * Resolve one of the four edges of an arbitrary flat region (its world top-surface\n * frame), the region-aware generalization of {@link baseEdge}: the base is the\n * special case where `origin = 0`, `u = +X`, `v = +Y`, `n = +Z`. Used by hems and\n * jogs to chain their profile off a named flange (or the base) edge.\n * @param frame the region's world top-surface frame (origin, u/v/n axes and extents)\n * @param side which of the four region edges to resolve\n * @returns the resolved {@link BaseEdge} — edge direction, outward normal, start point and length\n */\nexport function regionEdge(\n frame: { origin: Vec3; u: Vec3; v: Vec3; n: Vec3; uLen: number; vLen: number },\n side: FlatSide\n): BaseEdge {\n const o = frame.origin;\n const uTop = vecAdd(o, vecScale(frame.u, frame.uLen));\n const vTop = vecAdd(o, vecScale(frame.v, frame.vLen));\n const uvTop = vecAdd(uTop, vecScale(frame.v, frame.vLen));\n\n let out: Vec3;\n let length: number;\n let a: Vec3;\n let b: Vec3;\n switch (side) {\n case 'xmax':\n out = frame.u;\n length = frame.vLen;\n a = uTop;\n b = uvTop;\n break;\n case 'xmin':\n out = vecScale(frame.u, -1);\n length = frame.vLen;\n a = o;\n b = vTop;\n break;\n case 'ymax':\n out = frame.v;\n length = frame.uLen;\n a = vTop;\n b = uvTop;\n break;\n case 'ymin':\n out = vecScale(frame.v, -1);\n length = frame.uLen;\n a = o;\n b = uTop;\n break;\n }\n const dir = vecNormalize(vecCross(frame.n, out));\n const toB = vecSub(b, a);\n const start = vecDot(toB, dir) >= 0 ? a : b;\n return { dir, out, start, length };\n}\n\n/**\n * The starting {@link SegmentFrame} for a profile chained off `edge` at `offset`\n * along it: contact at the edge, run pointing outward, normal = the region normal,\n * bend axis along the edge. Shared by the contour-flange, hem and jog builders.\n * @param edge the resolved region edge the profile starts from\n * @param n the region normal, used as the frame's +Z\n * @param offset the start position along the edge, measured from `edge.start`\n * @returns the {@link SegmentFrame} with contact at the edge at `offset`, run pointing\n * outward and axis along the edge\n */\nexport function initialSegmentFrame(edge: BaseEdge, n: Vec3, offset: number): SegmentFrame {\n return {\n origin: vecAdd(edge.start, vecScale(edge.dir, offset)),\n run: edge.out,\n n,\n axis: edge.dir,\n };\n}\n\n/** Cylindrical bend patch (hollow tube ∩ fold wedge), mirroring authorFns. */\nfunction buildBendPatch(\n axisOrigin: Vec3,\n thetaDeg: number,\n r: number,\n width: number,\n thickness: number,\n direction: 'up' | 'down'\n): Result<ValidSolid> {\n const outerR = r + thickness;\n const outer = cylinder(outerR, width, { at: axisOrigin, axis: [0, 1, 0] });\n let tube: Solid = outer;\n if (r > 1e-9) {\n const inner = cylinder(r, width, { at: axisOrigin, axis: [0, 1, 0] });\n const carved = cut(outer, inner);\n if (!carved.ok) return carved;\n tube = carved.value;\n }\n const wedge = buildWedge(axisOrigin, thetaDeg, outerR, width, direction);\n if (!wedge.ok) return wedge;\n return intersect(tube, wedge.value) as Result<ValidSolid>;\n}\n\nfunction buildWedge(\n axisOrigin: Vec3,\n thetaDeg: number,\n outerR: number,\n width: number,\n direction: 'up' | 'down'\n): Result<Solid> {\n const span = outerR * 2 + 2;\n const margin = 1;\n const blockA = box(span, width + 2 * margin, 2 * span);\n const halfA: Solid = translate(blockA, [axisOrigin[0], axisOrigin[1] - margin, axisOrigin[2] - span]);\n const sign = direction === 'up' ? 1 : -1;\n const halfB = rotate(halfA, sign * (180 - thetaDeg), { at: axisOrigin, axis: [0, 1, 0] });\n return intersect(halfA, halfB);\n}\n\n/** Axis-angle of a 3×3 rotation matrix (row-major). */\nfunction matrixToAxisAngle(m: number[]): { axis: Vec3; angleDeg: number } {\n const m00 = m[0] ?? 0;\n const m01 = m[1] ?? 0;\n const m02 = m[2] ?? 0;\n const m10 = m[3] ?? 0;\n const m11 = m[4] ?? 0;\n const m12 = m[5] ?? 0;\n const m20 = m[6] ?? 0;\n const m21 = m[7] ?? 0;\n const m22 = m[8] ?? 0;\n\n const trace = m00 + m11 + m22;\n const cos = Math.max(-1, Math.min(1, (trace - 1) / 2));\n const angle = Math.acos(cos);\n const angleDeg = (angle * 180) / Math.PI;\n if (angle < 1e-9) return { axis: [0, 0, 1], angleDeg: 0 };\n\n if (Math.PI - angle < 1e-6) {\n const xx = (m00 + 1) / 2;\n const yy = (m11 + 1) / 2;\n const zz = (m22 + 1) / 2;\n let axis: Vec3;\n if (xx >= yy && xx >= zz) {\n const x = Math.sqrt(Math.max(xx, 0));\n axis = [x, (m01 + m10) / (4 * x), (m02 + m20) / (4 * x)];\n } else if (yy >= zz) {\n const y = Math.sqrt(Math.max(yy, 0));\n axis = [(m01 + m10) / (4 * y), y, (m12 + m21) / (4 * y)];\n } else {\n const z = Math.sqrt(Math.max(zz, 0));\n axis = [(m02 + m20) / (4 * z), (m12 + m21) / (4 * z), z];\n }\n return { axis: vecNormalize(axis), angleDeg: 180 };\n }\n\n const denom = 2 * Math.sin(angle);\n const axis: Vec3 = [(m21 - m12) / denom, (m02 - m20) / denom, (m10 - m01) / denom];\n return { axis: vecNormalize(axis), angleDeg };\n}\n"]}
@@ -0,0 +1,95 @@
1
+ import { type Result } from '@faicad/faijs/brepjs-compat';
2
+ import type { CutoutSpec, SheetMetalPart } from './types.js';
3
+ import { type FlatFrame } from './authorFns.js';
4
+ import { type Frame2 } from './unfoldFns.js';
5
+ type Pt2 = [number, number];
6
+ /**
7
+ * Punch a cutout (hole / slot / polygon) through a named flat region's thickness.
8
+ * The 2D profile is built in the region's LOCAL frame, then placed onto the correct
9
+ * folded face via the region's world {@link FlatFrame} (origin/u/v/n) and extruded
10
+ * through the sheet, so a feature authored at local `(x, y)` lands on the matching
11
+ * face whether the region is the base or a folded flange. The same local profile is
12
+ * mapped through the region's developed {@link Frame2} and recorded as a
13
+ * {@link CutoutFeature}, so {@link unfold} emits the matching loop in the flat
14
+ * pattern and {@link fold} can replay it. Guards a valid, single-bodied solid.
15
+ *
16
+ * @param part - the sheet metal part to cut.
17
+ * @param spec - the cutout specification (kind, region, geometry).
18
+ * @returns the updated part with the cutout feature recorded, or an error.
19
+ */
20
+ export declare function addCutout(part: SheetMetalPart, spec: CutoutSpec): Result<SheetMetalPart>;
21
+ /**
22
+ * Punch a circular hole of `diameter` centred at region-local `(x, y)`.
23
+ * @param part - the sheet metal part to cut.
24
+ * @param region - the region name to cut into.
25
+ * @param x - the region-local x coordinate of the hole centre.
26
+ * @param y - the region-local y coordinate of the hole centre.
27
+ * @param diameter - the hole diameter.
28
+ * @returns the updated part with the hole feature recorded, or an error.
29
+ */
30
+ export declare function addHole(part: SheetMetalPart, region: string, x: number, y: number, diameter: number): Result<SheetMetalPart>;
31
+ /**
32
+ * Punch a slot centred at `(x, y)`: `length` along the slot axis by `width` across.
33
+ * @param part - the sheet metal part to cut.
34
+ * @param region - the region name to cut into.
35
+ * @param opts - slot geometry (x, y, length, width, angleDeg?, round?).
36
+ * @returns the updated part with the slot feature recorded, or an error.
37
+ */
38
+ export declare function addSlot(part: SheetMetalPart, region: string, opts: {
39
+ x: number;
40
+ y: number;
41
+ length: number;
42
+ width: number;
43
+ angleDeg?: number;
44
+ round?: boolean;
45
+ }): Result<SheetMetalPart>;
46
+ /**
47
+ * Punch an arbitrary polygon cutout from its region-local `points` (≥ 3).
48
+ * @param part - the sheet metal part to cut.
49
+ * @param region - the region name to cut into.
50
+ * @param points - the polygon vertices in region-local coordinates (≥ 3).
51
+ * @returns the updated part with the polygon cutout feature recorded, or an error.
52
+ */
53
+ export declare function addPolygonCutout(part: SheetMetalPart, region: string, points: [number, number][]): Result<SheetMetalPart>;
54
+ /**
55
+ * `'base'` is an alias for the base region; everything else passes through.
56
+ * @param region - the region name to resolve.
57
+ * @returns the canonical region ID.
58
+ */
59
+ export declare function resolveRegionId(region: string): string;
60
+ /**
61
+ * Resolve a region's world {@link FlatFrame} and developed {@link Frame2} in one
62
+ * shared feature-tree walk — the lookup tabs/forms need to place geometry on the
63
+ * correct folded face and emit the matching developed-plane loops. Mirrors the
64
+ * frame resolution {@link addCutout} performs inline.
65
+ * @param part - the sheet metal part.
66
+ * @param region - the region name.
67
+ * @returns the region ID, world frame, and developed frame, or an error.
68
+ */
69
+ export declare function regionFrames(part: SheetMetalPart, region: string): Result<{
70
+ regionId: string;
71
+ world: FlatFrame;
72
+ dev: Frame2;
73
+ }>;
74
+ /**
75
+ * Developed-plane (u, v) extent of a region: base dims, or a flange frame's lengths.
76
+ * @param part - the sheet metal part.
77
+ * @param regionId - the canonical region ID.
78
+ * @param world - the region's world frame.
79
+ * @returns the developed-plane extent.
80
+ */
81
+ export declare function regionDevExtent(part: SheetMetalPart, regionId: string, world: FlatFrame): Extent;
82
+ /** Local-frame extent the cutout must fit within. */
83
+ export interface Extent {
84
+ uMax: number;
85
+ vMax: number;
86
+ }
87
+ /**
88
+ * Map a region-local `(x, y)` into developed-plane coordinates via its {@link Frame2}.
89
+ * @param p - the region-local point.
90
+ * @param f - the developed frame.
91
+ * @returns the developed-plane point.
92
+ */
93
+ export declare function mapToFrame2(p: Pt2, f: Frame2): Pt2;
94
+ export {};
95
+ //# sourceMappingURL=cutoutFns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cutoutFns.d.ts","sourceRoot":"","sources":["../src/cutoutFns.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAgBZ,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,UAAU,EAAiB,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5E,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AASzD,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE5B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC,cAAc,CAAC,CAuDxF;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EACd,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,cAAc,CAAC,CAExB;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAChG,MAAM,CAAC,cAAc,CAAC,CAExB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GACzB,MAAM,CAAC,cAAc,CAAC,CAExB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,GACb,MAAM,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAc7D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAEhG;AAsED,qDAAqD;AACrD,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AA8DD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,GAAG,CAKlD"}