brepjs-sheetmetal 0.3.0 → 0.4.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.
@@ -1,5 +1,5 @@
1
1
  import { box, curveEndPoint, curveStartPoint, cut, cylinder, err, extrude, face, faceCenter, fuse, getBounds, getEdges, getFaces, getSolids, getSurfaceType, intersect, isErr, isOk, isPlanarWire, isSolid, isValid, line, measureArea, measureVolume, normalAt, ok, pointOnSurface, rotate, sharedEdges, translate, validationError, vecAdd, vecCross, vecDot, vecLength, vecNormalize, vecScale, vecSub, wire, wireLoop } from "brepjs";
2
- //#region src/materials.ts
2
+ // src/materials.ts
3
3
  var DEFAULT_K_FACTOR = .44;
4
4
  function gauge(name, thickness, kFactor = DEFAULT_K_FACTOR) {
5
5
  return {
@@ -37,8 +37,7 @@ var MATERIALS = {
37
37
  function getMaterial(name) {
38
38
  return MATERIALS[name];
39
39
  }
40
- //#endregion
41
- //#region src/bendTableFns.ts
40
+ // src/bendTableFns.ts
42
41
  var DEG_TO_RAD = Math.PI / 180;
43
42
  var registry = /* @__PURE__ */ new Map();
44
43
  /**
@@ -377,8 +376,7 @@ function ensureStarterTables() {
377
376
  });
378
377
  }
379
378
  }
380
- //#endregion
381
- //#region src/allowanceFns.ts
379
+ // src/allowanceFns.ts
382
380
  function validateRule(rule) {
383
381
  if (!Number.isFinite(rule.innerRadius) || rule.innerRadius < 0) return err(validationError("INVALID_RADIUS", `innerRadius must be a finite, non-negative number, got ${rule.innerRadius}`));
384
382
  if (!Number.isFinite(rule.kFactor) || rule.kFactor < 0 || rule.kFactor > 1) return err(validationError("INVALID_K_FACTOR", `kFactor must be in [0, 1], got ${rule.kFactor}`));
@@ -412,8 +410,7 @@ function neutralRadius(thickness, rule) {
412
410
  if (!ruleCheck.ok) return ruleCheck;
413
411
  return ok(rule.innerRadius + rule.kFactor * thickness);
414
412
  }
415
- //#endregion
416
- //#region src/featureTreeFns.ts
413
+ // src/featureTreeFns.ts
417
414
  var ROOT_FLAT_ID = "root";
418
415
  /**
419
416
  * Build the feature graph for an authored part. Each flange contributes a flat
@@ -504,7 +501,7 @@ function buildFeatureTree(graph, rootId = ROOT_FLAT_ID) {
504
501
  adjacency.get(edge.parent)?.push(edge);
505
502
  adjacency.get(edge.child)?.push(edge);
506
503
  }
507
- const visited = new Set([rootId]);
504
+ const visited = /* @__PURE__ */ new Set([rootId]);
508
505
  const treeEdges = /* @__PURE__ */ new Set();
509
506
  const bends = [];
510
507
  const queue = [rootId];
@@ -558,8 +555,7 @@ function featureTree(part, rootId = ROOT_FLAT_ID) {
558
555
  if (!graph.ok) return graph;
559
556
  return buildFeatureTree(graph.value, rootId);
560
557
  }
561
- //#endregion
562
- //#region src/internal.ts
558
+ // src/internal.ts
563
559
  /**
564
560
  * OCCT boolean fuse returns a compound wrapping the unioned solid. Extract the
565
561
  * single solid so a part's cached shape is a true `Solid` (clean STEP/GLB
@@ -582,8 +578,7 @@ function sideFromAxisDir(axisDir) {
582
578
  if (Math.abs(axisDir[1]) > .5) return "xmax";
583
579
  if (Math.abs(axisDir[0]) > .5) return "ymax";
584
580
  }
585
- //#endregion
586
- //#region src/unfoldFns.ts
581
+ // src/unfoldFns.ts
587
582
  /**
588
583
  * Tree-driven analytic unfold of an arbitrary straight-bend part into its flat
589
584
  * pattern. Walks the feature tree in BFS order placing every flat in the flat
@@ -673,18 +668,20 @@ function unfold(part) {
673
668
  direction: p.direction,
674
669
  inward: [-p.frame.v[0], -p.frame.v[1]]
675
670
  })).concat(contourResult.value.bendLines).concat(hemJogResult.value.bendLines);
671
+ const pattern = {
672
+ outline: outlineResult.value,
673
+ bendLines,
674
+ holes: holesResult.value,
675
+ formCuts: formsResult.value.cuts,
676
+ formMarkers: formsResult.value.markers,
677
+ formHinges: formsResult.value.hinges,
678
+ loftedDevelopments: loftedResult.value,
679
+ developedArea: layout.developedArea
680
+ };
681
+ const report = reportFromLayout(layout);
676
682
  return ok({
677
- pattern: {
678
- outline: outlineResult.value,
679
- bendLines,
680
- holes: holesResult.value,
681
- formCuts: formsResult.value.cuts,
682
- formMarkers: formsResult.value.markers,
683
- formHinges: formsResult.value.hinges,
684
- loftedDevelopments: loftedResult.value,
685
- developedArea: layout.developedArea
686
- },
687
- report: reportFromLayout(layout),
683
+ pattern,
684
+ report,
688
685
  warnings
689
686
  });
690
687
  }
@@ -1288,8 +1285,7 @@ function reportFromLayout(layout) {
1288
1285
  totalFlatSize: [maxX - minX, maxY - minY]
1289
1286
  };
1290
1287
  }
1291
- //#endregion
1292
- //#region src/foreignUnfoldFns.ts
1288
+ // src/foreignUnfoldFns.ts
1293
1289
  /** Default neutral-axis K-factor for a purely geometric unfold (mid-surface). */
1294
1290
  var DEFAULT_FOREIGN_K = .5;
1295
1291
  /** Relative tolerance for the cylinder-fit residual (fraction of the radius). */
@@ -1307,7 +1303,8 @@ function sampleFace(face, n) {
1307
1303
  for (let i = 0; i < n; i += 1) {
1308
1304
  const u = n === 1 ? .5 : i / (n - 1);
1309
1305
  for (let j = 0; j < n; j += 1) {
1310
- const point = pointOnSurface(face, u, n === 1 ? .5 : j / (n - 1));
1306
+ const v = n === 1 ? .5 : j / (n - 1);
1307
+ const point = pointOnSurface(face, u, v);
1311
1308
  const normal = normalAt(face, point);
1312
1309
  const len = vecLength(normal);
1313
1310
  if (len < GEOM_TOL) continue;
@@ -1398,7 +1395,7 @@ function axisFromNormals(normals) {
1398
1395
  }
1399
1396
  /** An orthonormal pair spanning the plane perpendicular to `axis`. */
1400
1397
  function perpBasis(axis) {
1401
- const e1 = vecNormalize(vecCross(axis, Math.abs(axis[0]) < .9 ? [
1398
+ const seed = Math.abs(axis[0]) < .9 ? [
1402
1399
  1,
1403
1400
  0,
1404
1401
  0
@@ -1406,7 +1403,8 @@ function perpBasis(axis) {
1406
1403
  0,
1407
1404
  1,
1408
1405
  0
1409
- ]));
1406
+ ];
1407
+ const e1 = vecNormalize(vecCross(axis, seed));
1410
1408
  return {
1411
1409
  e1,
1412
1410
  e2: vecNormalize(vecCross(axis, e1))
@@ -1415,7 +1413,9 @@ function perpBasis(axis) {
1415
1413
  /** Perpendicular distance from `p` to the line through `o` with unit direction `d`. */
1416
1414
  function distanceToAxis(p, o, d) {
1417
1415
  const w = vecSub(p, o);
1418
- return vecLength(vecSub(w, vecScale(d, vecDot(w, d))));
1416
+ const along = vecDot(w, d);
1417
+ const perp = vecSub(w, vecScale(d, along));
1418
+ return vecLength(perp);
1419
1419
  }
1420
1420
  /** Algebraic (Kåsa) least-squares circle fit to 2D points. */
1421
1421
  function fitCircle2(pts) {
@@ -1620,7 +1620,8 @@ function detectThickness(planar) {
1620
1620
  const fb = planar[j];
1621
1621
  if (fa === void 0 || fb === void 0) continue;
1622
1622
  const na = faceNormal(fa);
1623
- if (vecDot(na, faceNormal(fb)) > -.99) continue;
1623
+ const nb = faceNormal(fb);
1624
+ if (vecDot(na, nb) > -.99) continue;
1624
1625
  const ca = faceCenter(fa);
1625
1626
  const cb = faceCenter(fb);
1626
1627
  const gap = Math.abs(vecDot(vecSub(cb, ca), na));
@@ -1649,7 +1650,8 @@ function pairFlats(planar, thickness, warnings) {
1649
1650
  if (j === i || used.has(j)) continue;
1650
1651
  const fb = planar[j];
1651
1652
  if (fb === void 0) continue;
1652
- if (vecDot(na, faceNormal(fb)) > -.99) continue;
1653
+ const nb = faceNormal(fb);
1654
+ if (vecDot(na, nb) > -.99) continue;
1653
1655
  const cb = faceCenter(fb);
1654
1656
  const gap = Math.abs(vecDot(vecSub(cb, ca), na));
1655
1657
  const e = Math.abs(gap - thickness);
@@ -1818,7 +1820,7 @@ function layoutForeign(flats, graph, thickness, kFactor, warnings) {
1818
1820
  adjacency.get(e.flats[0])?.push(e);
1819
1821
  adjacency.get(e.flats[1])?.push(e);
1820
1822
  }
1821
- const visited = new Set([0]);
1823
+ const visited = /* @__PURE__ */ new Set([0]);
1822
1824
  const treeEdges = /* @__PURE__ */ new Set();
1823
1825
  const treeBends = [];
1824
1826
  const queue = [0];
@@ -1912,23 +1914,26 @@ function layoutForeign(flats, graph, thickness, kFactor, warnings) {
1912
1914
  });
1913
1915
  }
1914
1916
  const bbox = aabb2(rects);
1915
- const outlineWire = wireLoop(buildRectOutline2(bbox));
1917
+ const outline = buildRectOutline2(bbox);
1918
+ const outlineWire = wireLoop(outline);
1916
1919
  if (!outlineWire.ok) return outlineWire;
1920
+ const pattern = {
1921
+ outline: outlineWire.value,
1922
+ bendLines,
1923
+ holes: [],
1924
+ formCuts: [],
1925
+ formMarkers: [],
1926
+ formHinges: [],
1927
+ loftedDevelopments: [],
1928
+ developedArea
1929
+ };
1930
+ const report = {
1931
+ bends: bendReportRows,
1932
+ totalFlatSize: [bbox.x1 - bbox.x0, bbox.y1 - bbox.y0]
1933
+ };
1917
1934
  return ok({
1918
- pattern: {
1919
- outline: outlineWire.value,
1920
- bendLines,
1921
- holes: [],
1922
- formCuts: [],
1923
- formMarkers: [],
1924
- formHinges: [],
1925
- loftedDevelopments: [],
1926
- developedArea
1927
- },
1928
- report: {
1929
- bends: bendReportRows,
1930
- totalFlatSize: [bbox.x1 - bbox.x0, bbox.y1 - bbox.y0]
1931
- }
1935
+ pattern,
1936
+ report
1932
1937
  });
1933
1938
  }
1934
1939
  /** Map a 3D in-plane point through a placed flat's affine map. */
@@ -2018,7 +2023,9 @@ function parentFootprintCentreAlong(parent, axisInPlane, axisRef) {
2018
2023
  min = Math.min(min, v);
2019
2024
  max = Math.max(max, v);
2020
2025
  }
2021
- return vecAdd(axisRef, vecScale(axisInPlane, (min + max) / 2 - vecDot(axisRef, axisInPlane)));
2026
+ const mid = (min + max) / 2;
2027
+ const refAlong = vecDot(axisRef, axisInPlane);
2028
+ return vecAdd(axisRef, vecScale(axisInPlane, mid - refAlong));
2022
2029
  }
2023
2030
  /** In-plane run extent of a flat along `out` (perpendicular to the bend axis). */
2024
2031
  function inPlaneRun(flat, out) {
@@ -2034,7 +2041,8 @@ function inPlaneRun(flat, out) {
2034
2041
  }
2035
2042
  /** Run direction of a child flat pointing away from the bend axis. */
2036
2043
  function runDirectionAway(flat, out, axisOrigin) {
2037
- return vecDot(vecSub(flat.center, axisOrigin), out) >= 0 ? out : vecScale(out, -1);
2044
+ const toCenter = vecSub(flat.center, axisOrigin);
2045
+ return vecDot(toCenter, out) >= 0 ? out : vecScale(out, -1);
2038
2046
  }
2039
2047
  /**
2040
2048
  * The child corner that develops onto the strip-far near point: minimum along the
@@ -2160,13 +2168,13 @@ function bendAxialExtent(bend) {
2160
2168
  * parent flat's mid-surface, 'down' otherwise.
2161
2169
  */
2162
2170
  function bendDirection(bend, parent) {
2163
- return vecDot(vecSub(bend.fit.axisOrigin, parent.center), parent.normal) >= 0 ? "up" : "down";
2171
+ const toAxis = vecSub(bend.fit.axisOrigin, parent.center);
2172
+ return vecDot(toAxis, parent.normal) >= 0 ? "up" : "down";
2164
2173
  }
2165
2174
  function faceNormal(face) {
2166
2175
  return vecNormalize(normalAt(face, faceCenter(face)));
2167
2176
  }
2168
- //#endregion
2169
- //#region src/authorFns.ts
2177
+ // src/authorFns.ts
2170
2178
  /**
2171
2179
  * Stable edge reference for flange attachment. The base flat is `face-0`; every
2172
2180
  * flange face is `face-<n+1>` in authoring order. The leading edge of a flat is
@@ -2494,10 +2502,10 @@ function parentEdge(f, side) {
2494
2502
  length = f.uLen;
2495
2503
  a = o;
2496
2504
  b = uTop;
2497
- break;
2498
2505
  }
2499
2506
  const dir = vecNormalize(vecCross(f.n, out));
2500
- const start = vecDot(vecSub(b, a), dir) >= 0 ? a : b;
2507
+ const toB = vecSub(b, a);
2508
+ const start = vecDot(toB, dir) >= 0 ? a : b;
2501
2509
  return {
2502
2510
  dir,
2503
2511
  out,
@@ -2610,14 +2618,15 @@ function frameTransform$1(axisT, runT, nT, edgeStart, offset) {
2610
2618
  const applyR = (v) => vecAdd(vecAdd(vecScale(runT, v[0]), vecScale(axisT, v[1])), vecScale(nT, v[2]));
2611
2619
  return {
2612
2620
  solid: (s) => {
2613
- return translate(aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, {
2621
+ const rotated = aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, {
2614
2622
  at: [
2615
2623
  0,
2616
2624
  0,
2617
2625
  0
2618
2626
  ],
2619
2627
  axis: aa.axis
2620
- }), target);
2628
+ });
2629
+ return translate(rotated, target);
2621
2630
  },
2622
2631
  point: (p) => vecAdd(applyR(p), target),
2623
2632
  vector: applyR
@@ -2679,12 +2688,13 @@ function matrixToAxisAngle$2(m) {
2679
2688
  };
2680
2689
  }
2681
2690
  const denom = 2 * Math.sin(angle);
2691
+ const axis = [
2692
+ (m21 - m12) / denom,
2693
+ (m02 - m20) / denom,
2694
+ (m10 - m01) / denom
2695
+ ];
2682
2696
  return {
2683
- axis: vecNormalize([
2684
- (m21 - m12) / denom,
2685
- (m02 - m20) / denom,
2686
- (m10 - m01) / denom
2687
- ]),
2697
+ axis: vecNormalize(axis),
2688
2698
  angleDeg
2689
2699
  };
2690
2700
  }
@@ -2706,14 +2716,15 @@ function buildBendPatch$1(axisOrigin, thetaDeg, r, width, thickness, direction)
2706
2716
  });
2707
2717
  let tube = outer;
2708
2718
  if (r > 1e-9) {
2709
- const carved = cut(outer, cylinder(r, width, {
2719
+ const inner = cylinder(r, width, {
2710
2720
  at: axisOrigin,
2711
2721
  axis: [
2712
2722
  0,
2713
2723
  1,
2714
2724
  0
2715
2725
  ]
2716
- }));
2726
+ });
2727
+ const carved = cut(outer, inner);
2717
2728
  if (!carved.ok) return carved;
2718
2729
  tube = carved.value;
2719
2730
  }
@@ -2731,39 +2742,47 @@ function buildBendPatch$1(axisOrigin, thetaDeg, r, width, thickness, direction)
2731
2742
  function buildWedge$1(axisOrigin, thetaDeg, outerR, width, direction) {
2732
2743
  const span = outerR * 2 + 2;
2733
2744
  const margin = 1;
2734
- const halfA = translate(box(span, width + 2 * margin, 2 * span), [
2745
+ const blockA = box(span, width + 2, 2 * span);
2746
+ const halfA = translate(blockA, [
2735
2747
  axisOrigin[0],
2736
2748
  axisOrigin[1] - margin,
2737
2749
  axisOrigin[2] - span
2738
2750
  ]);
2739
- return intersect(halfA, rotate(halfA, (direction === "up" ? 1 : -1) * (180 - thetaDeg), {
2751
+ const halfB = rotate(halfA, (direction === "up" ? 1 : -1) * (180 - thetaDeg), {
2740
2752
  at: axisOrigin,
2741
2753
  axis: [
2742
2754
  0,
2743
2755
  1,
2744
2756
  0
2745
2757
  ]
2746
- }));
2758
+ });
2759
+ return intersect(halfA, halfB);
2747
2760
  }
2748
2761
  /** The flange flat, built lying along +X past the bend, then folded to angle θ. */
2749
2762
  function buildFlangeFlat(axisOrigin, thetaDeg, width, thickness, length, direction) {
2750
- if (direction === "up") return rotate(translate(box(length, width, thickness), [
2751
- axisOrigin[0],
2752
- axisOrigin[1],
2753
- 0
2754
- ]), -thetaDeg, {
2755
- at: axisOrigin,
2756
- axis: [
2757
- 0,
2758
- 1,
2763
+ if (direction === "up") {
2764
+ const flat = box(length, width, thickness);
2765
+ const positioned = translate(flat, [
2766
+ axisOrigin[0],
2767
+ axisOrigin[1],
2759
2768
  0
2760
- ]
2761
- });
2762
- return rotate(translate(box(length, width, thickness), [
2769
+ ]);
2770
+ return rotate(positioned, -thetaDeg, {
2771
+ at: axisOrigin,
2772
+ axis: [
2773
+ 0,
2774
+ 1,
2775
+ 0
2776
+ ]
2777
+ });
2778
+ }
2779
+ const flat = box(length, width, thickness);
2780
+ const positioned = translate(flat, [
2763
2781
  axisOrigin[0],
2764
2782
  axisOrigin[1],
2765
2783
  -thickness
2766
- ]), thetaDeg, {
2784
+ ]);
2785
+ return rotate(positioned, thetaDeg, {
2767
2786
  at: axisOrigin,
2768
2787
  axis: [
2769
2788
  0,
@@ -2772,8 +2791,7 @@ function buildFlangeFlat(axisOrigin, thetaDeg, width, thickness, length, directi
2772
2791
  ]
2773
2792
  });
2774
2793
  }
2775
- //#endregion
2776
- //#region src/reliefFns.ts
2794
+ // src/reliefFns.ts
2777
2795
  var EPS$6 = 1e-6;
2778
2796
  var Z_AXIS$1 = [
2779
2797
  0,
@@ -3047,7 +3065,8 @@ function cornerPoint$1(originA, dirA, originB, dirB) {
3047
3065
  const e = vecDot(dirB, w0);
3048
3066
  const denom = a * c - b * b;
3049
3067
  if (Math.abs(denom) < 1e-9) return originA;
3050
- return vecAdd(originA, vecScale(dirA, (b * e - c * d) / denom));
3068
+ const sc = (b * e - c * d) / denom;
3069
+ return vecAdd(originA, vecScale(dirA, sc));
3051
3070
  }
3052
3071
  /** In-plane perpendicular to `axis` pointing toward the part center (into the parent). */
3053
3072
  function inwardRun(axisOrigin, axis, center) {
@@ -3080,11 +3099,12 @@ function parentEdgeLength(part, flange) {
3080
3099
  */
3081
3100
  function slotTool3d(at, along, inward, width, depth, thickness) {
3082
3101
  const margin = thickness + 2;
3083
- const y0 = vecDot([
3102
+ const rotPerp = [
3084
3103
  -along[1],
3085
3104
  along[0],
3086
3105
  0
3087
- ], inward) >= 0 ? 0 : -depth;
3106
+ ];
3107
+ const y0 = vecDot(rotPerp, inward) >= 0 ? 0 : -depth;
3088
3108
  let tool = box(width, depth, thickness + 2 * margin);
3089
3109
  tool = translate(tool, [
3090
3110
  -width / 2,
@@ -3109,7 +3129,8 @@ function squareTool(solid, cornerXY, side) {
3109
3129
  const b = getBounds(solid);
3110
3130
  const margin = 2;
3111
3131
  const h = side / 2;
3112
- return translate(box(side, side, b.zMax - b.zMin + 2 * margin), [
3132
+ const tool = box(side, side, b.zMax - b.zMin + 4);
3133
+ return translate(tool, [
3113
3134
  cornerXY[0] - h,
3114
3135
  cornerXY[1] - h,
3115
3136
  b.zMin - margin
@@ -3134,8 +3155,7 @@ function toPt2$1(v) {
3134
3155
  function dist2(a, b) {
3135
3156
  return Math.hypot(a[0] - b[0], a[1] - b[1]);
3136
3157
  }
3137
- //#endregion
3138
- //#region src/cutoutFns.ts
3158
+ // src/cutoutFns.ts
3139
3159
  var EPS$5 = 1e-6;
3140
3160
  /** A hair of over-cut so the tool pokes through both faces cleanly. */
3141
3161
  var HAIR$1 = .05;
@@ -3363,8 +3383,7 @@ function polygonArea(loop) {
3363
3383
  }
3364
3384
  return Math.abs(sum) / 2;
3365
3385
  }
3366
- //#endregion
3367
- //#region src/tabFns.ts
3386
+ // src/tabFns.ts
3368
3387
  var EPS$4 = 1e-6;
3369
3388
  /**
3370
3389
  * Fuse a rectangular tab onto a named flat region's edge — additive material (the
@@ -3506,8 +3525,7 @@ function devRect(local, dev) {
3506
3525
  y1
3507
3526
  ];
3508
3527
  }
3509
- //#endregion
3510
- //#region src/formFns.ts
3528
+ // src/formFns.ts
3511
3529
  var EPS$3 = 1e-6;
3512
3530
  var HAIR = .05;
3513
3531
  var CIRCLE_SEGMENTS = 48;
@@ -3602,18 +3620,20 @@ function embossForm(part, partSolid, spec, regionId, world, dev, ext) {
3602
3620
  let solid;
3603
3621
  if (spec.form === "emboss") {
3604
3622
  const base = vecAdd(faceCentre, vecScale(world.n, -.05));
3605
- const fused = fuse(partSolid, cylinder(r, spec.height + HAIR, {
3623
+ const bump = cylinder(r, spec.height + HAIR, {
3606
3624
  at: base,
3607
3625
  axis: world.n
3608
- }));
3626
+ });
3627
+ const fused = fuse(partSolid, bump);
3609
3628
  if (!fused.ok) return fused;
3610
3629
  solid = normalizeSolid(fused.value);
3611
3630
  } else {
3612
3631
  const top = vecAdd(faceCentre, vecScale(world.n, HAIR));
3613
- const cutResult = cut(partSolid, cylinder(r, spec.height + HAIR, {
3632
+ const tool = cylinder(r, spec.height + HAIR, {
3614
3633
  at: top,
3615
3634
  axis: vecScale(world.n, -1)
3616
- }));
3635
+ });
3636
+ const cutResult = cut(partSolid, tool);
3617
3637
  if (!cutResult.ok) return cutResult;
3618
3638
  solid = normalizeSolid(cutResult.value);
3619
3639
  }
@@ -3705,7 +3725,8 @@ function buildLouverFlap(f, x0, x1, y0, width, height, thickness, sign) {
3705
3725
  const n = vecNormalize(f.n);
3706
3726
  const v = vecNormalize(vecCross(n, u));
3707
3727
  const hingeCorner = vecAdd(vecAdd(vecAdd(f.origin, vecScale(f.u, x0)), vecScale(f.v, y0)), vecScale(f.n, thickness));
3708
- return ok(mapLocalToWorld(flap, u, v, n, hingeCorner));
3728
+ const placed = mapLocalToWorld(flap, u, v, n, hingeCorner);
3729
+ return ok(placed);
3709
3730
  }
3710
3731
  /** Map a solid built in a canonical XYZ frame onto world axes (uT, vT, nT) at origin. */
3711
3732
  function mapLocalToWorld(s, uT, vT, nT, origin) {
@@ -3720,14 +3741,15 @@ function mapLocalToWorld(s, uT, vT, nT, origin) {
3720
3741
  vT[2],
3721
3742
  nT[2]
3722
3743
  ]);
3723
- return translate(aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, {
3744
+ const rotated = aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, {
3724
3745
  at: [
3725
3746
  0,
3726
3747
  0,
3727
3748
  0
3728
3749
  ],
3729
3750
  axis: aa.axis
3730
- }), origin);
3751
+ });
3752
+ return translate(rotated, origin);
3731
3753
  }
3732
3754
  /** Axis-angle of a 3×3 row-major rotation matrix (columns = target basis). */
3733
3755
  function matrixToAxisAngle$1(m) {
@@ -3785,12 +3807,13 @@ function matrixToAxisAngle$1(m) {
3785
3807
  };
3786
3808
  }
3787
3809
  const denom = 2 * Math.sin(angle);
3810
+ const axis = [
3811
+ (m21 - m12) / denom,
3812
+ (m02 - m20) / denom,
3813
+ (m10 - m01) / denom
3814
+ ];
3788
3815
  return {
3789
- axis: vecNormalize([
3790
- (m21 - m12) / denom,
3791
- (m02 - m20) / denom,
3792
- (m10 - m01) / denom
3793
- ]),
3816
+ axis: vecNormalize(axis),
3794
3817
  angleDeg
3795
3818
  };
3796
3819
  }
@@ -3803,8 +3826,7 @@ function circleLocal(c, r) {
3803
3826
  }
3804
3827
  return pts;
3805
3828
  }
3806
- //#endregion
3807
- //#region src/validateFns.ts
3829
+ // src/validateFns.ts
3808
3830
  var OVERLAP_TOL = 1e-6;
3809
3831
  /**
3810
3832
  * Manufacturability checks for an authored sheet-metal part. Returns a list of
@@ -3986,8 +4008,7 @@ function pairKey(a, b) {
3986
4008
  function boundsOverlap(a, b) {
3987
4009
  return a.xMin < b.xMax - OVERLAP_TOL && b.xMin < a.xMax - OVERLAP_TOL && a.yMin < b.yMax - OVERLAP_TOL && b.yMin < a.yMax - OVERLAP_TOL && a.zMin < b.zMax - OVERLAP_TOL && b.zMin < a.zMax - OVERLAP_TOL;
3988
4010
  }
3989
- //#endregion
3990
- //#region src/foldFns.ts
4011
+ // src/foldFns.ts
3991
4012
  /**
3992
4013
  * Fold a flat pattern up into a 3D part. Each {@link FoldRegion} folds about its
3993
4014
  * fold line off its parent region, exactly inverting {@link unfold}: folding by an
@@ -4645,8 +4666,7 @@ function uniqueSorted(values) {
4645
4666
  }
4646
4667
  return out;
4647
4668
  }
4648
- //#endregion
4649
- //#region src/miterFns.ts
4669
+ // src/miterFns.ts
4650
4670
  /**
4651
4671
  * General miter primitive: cut a sheet-metal part by a plane, removing the
4652
4672
  * material on the `+normal` side. The tool is a half-space block sized to the
@@ -4705,7 +4725,8 @@ function cornerPoint(originA, dirA, originB, dirB) {
4705
4725
  const e = vecDot(dirB, w0);
4706
4726
  const denom = a * c - b * b;
4707
4727
  if (Math.abs(denom) < 1e-9) return originA;
4708
- return vecAdd(originA, vecScale(dirA, (b * e - c * d) / denom));
4728
+ const sc = (b * e - c * d) / denom;
4729
+ return vecAdd(originA, vecScale(dirA, sc));
4709
4730
  }
4710
4731
  /**
4711
4732
  * Normal of the miter plane between two flanges whose bend axes are `dirA`/`dirB`.
@@ -4737,13 +4758,14 @@ function halfSpaceTool(solid, plane) {
4737
4758
  -size / 2,
4738
4759
  0
4739
4760
  ]);
4740
- return ok(translate(alignZTo(tool, normal), plane.origin));
4761
+ const aligned = alignZTo(tool, normal);
4762
+ return ok(translate(aligned, plane.origin));
4741
4763
  }
4742
4764
  /** Rotate a shape so its local +Z axis points along `target` (about the origin). */
4743
4765
  function alignZTo(shape, target) {
4744
4766
  const dot = vecDot(Z_AXIS, target);
4745
- if (dot > .999999999) return shape;
4746
- if (dot < -.999999999) return rotate(shape, 180, {
4767
+ if (dot > 1 - 1e-9) return shape;
4768
+ if (dot < -1 + 1e-9) return rotate(shape, 180, {
4747
4769
  at: [
4748
4770
  0,
4749
4771
  0,
@@ -4756,7 +4778,8 @@ function alignZTo(shape, target) {
4756
4778
  ]
4757
4779
  });
4758
4780
  const axis = vecNormalize(vecCross(Z_AXIS, target));
4759
- return rotate(shape, Math.acos(Math.max(-1, Math.min(1, dot))) * 180 / Math.PI, {
4781
+ const angleDeg = Math.acos(Math.max(-1, Math.min(1, dot))) * 180 / Math.PI;
4782
+ return rotate(shape, angleDeg, {
4760
4783
  at: [
4761
4784
  0,
4762
4785
  0,
@@ -4765,8 +4788,7 @@ function alignZTo(shape, target) {
4765
4788
  axis
4766
4789
  });
4767
4790
  }
4768
- //#endregion
4769
- //#region src/contourFlangeFns.ts
4791
+ // src/contourFlangeFns.ts
4770
4792
  /**
4771
4793
  * Author a contour flange: an OPEN 2D profile (alternating line/arc segments) swept
4772
4794
  * along one straight edge of the base flat. Each arc is a cylindrical bend, each
@@ -4887,7 +4909,8 @@ function authorContourFlange(part, spec) {
4887
4909
  function buildLineLeg(base, frame, span, thickness, legLength) {
4888
4910
  const place = frameTransform(frame.run, frame.axis, frame.n, frame.origin);
4889
4911
  const canon = box(legLength, span, thickness);
4890
- const fused = fuse(base, place.solid(canon));
4912
+ const placed = place.solid(canon);
4913
+ const fused = fuse(base, placed);
4891
4914
  if (!fused.ok) return fused;
4892
4915
  const nextOrigin = vecAdd(frame.origin, vecScale(frame.run, legLength));
4893
4916
  return ok({
@@ -4919,7 +4942,8 @@ function buildArcBend(base, frame, span, thickness, seg) {
4919
4942
  const place = frameTransform(frame.run, frame.axis, frame.n, frame.origin);
4920
4943
  const patch = buildBendPatch(canonOrigin, seg.angleDeg, r, span, thickness, seg.direction);
4921
4944
  if (!patch.ok) return patch;
4922
- const fused = fuse(base, place.solid(patch.value));
4945
+ const placed = place.solid(patch.value);
4946
+ const fused = fuse(base, placed);
4923
4947
  if (!fused.ok) return fused;
4924
4948
  const theta = seg.angleDeg * Math.PI / 180;
4925
4949
  const a = -sign * theta;
@@ -4976,14 +5000,15 @@ function frameTransform(runT, axisT, nT, origin) {
4976
5000
  const applyR = (v) => vecAdd(vecAdd(vecScale(runT, v[0]), vecScale(axisT, v[1])), vecScale(nT, v[2]));
4977
5001
  return {
4978
5002
  solid: (s) => {
4979
- return translate(aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, {
5003
+ const rotated = aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, {
4980
5004
  at: [
4981
5005
  0,
4982
5006
  0,
4983
5007
  0
4984
5008
  ],
4985
5009
  axis: aa.axis
4986
- }), origin);
5010
+ });
5011
+ return translate(rotated, origin);
4987
5012
  },
4988
5013
  point: (p) => vecAdd(applyR(p), origin),
4989
5014
  vector: applyR
@@ -5044,10 +5069,10 @@ function baseEdge(part, side) {
5044
5069
  length = uLen;
5045
5070
  a = o;
5046
5071
  b = uTop;
5047
- break;
5048
5072
  }
5049
5073
  const dir = vecNormalize(vecCross(n, out));
5050
- const start = vecDot(vecSub(b, a), dir) >= 0 ? a : b;
5074
+ const toB = vecSub(b, a);
5075
+ const start = vecDot(toB, dir) >= 0 ? a : b;
5051
5076
  return {
5052
5077
  dir,
5053
5078
  out,
@@ -5094,10 +5119,10 @@ function regionEdge(frame, side) {
5094
5119
  length = frame.uLen;
5095
5120
  a = o;
5096
5121
  b = uTop;
5097
- break;
5098
5122
  }
5099
5123
  const dir = vecNormalize(vecCross(frame.n, out));
5100
- const start = vecDot(vecSub(b, a), dir) >= 0 ? a : b;
5124
+ const toB = vecSub(b, a);
5125
+ const start = vecDot(toB, dir) >= 0 ? a : b;
5101
5126
  return {
5102
5127
  dir,
5103
5128
  out,
@@ -5131,14 +5156,15 @@ function buildBendPatch(axisOrigin, thetaDeg, r, width, thickness, direction) {
5131
5156
  });
5132
5157
  let tube = outer;
5133
5158
  if (r > 1e-9) {
5134
- const carved = cut(outer, cylinder(r, width, {
5159
+ const inner = cylinder(r, width, {
5135
5160
  at: axisOrigin,
5136
5161
  axis: [
5137
5162
  0,
5138
5163
  1,
5139
5164
  0
5140
5165
  ]
5141
- }));
5166
+ });
5167
+ const carved = cut(outer, inner);
5142
5168
  if (!carved.ok) return carved;
5143
5169
  tube = carved.value;
5144
5170
  }
@@ -5149,19 +5175,21 @@ function buildBendPatch(axisOrigin, thetaDeg, r, width, thickness, direction) {
5149
5175
  function buildWedge(axisOrigin, thetaDeg, outerR, width, direction) {
5150
5176
  const span = outerR * 2 + 2;
5151
5177
  const margin = 1;
5152
- const halfA = translate(box(span, width + 2 * margin, 2 * span), [
5178
+ const blockA = box(span, width + 2, 2 * span);
5179
+ const halfA = translate(blockA, [
5153
5180
  axisOrigin[0],
5154
5181
  axisOrigin[1] - margin,
5155
5182
  axisOrigin[2] - span
5156
5183
  ]);
5157
- return intersect(halfA, rotate(halfA, (direction === "up" ? 1 : -1) * (180 - thetaDeg), {
5184
+ const halfB = rotate(halfA, (direction === "up" ? 1 : -1) * (180 - thetaDeg), {
5158
5185
  at: axisOrigin,
5159
5186
  axis: [
5160
5187
  0,
5161
5188
  1,
5162
5189
  0
5163
5190
  ]
5164
- }));
5191
+ });
5192
+ return intersect(halfA, halfB);
5165
5193
  }
5166
5194
  /** Axis-angle of a 3×3 rotation matrix (row-major). */
5167
5195
  function matrixToAxisAngle(m) {
@@ -5219,17 +5247,17 @@ function matrixToAxisAngle(m) {
5219
5247
  };
5220
5248
  }
5221
5249
  const denom = 2 * Math.sin(angle);
5250
+ const axis = [
5251
+ (m21 - m12) / denom,
5252
+ (m02 - m20) / denom,
5253
+ (m10 - m01) / denom
5254
+ ];
5222
5255
  return {
5223
- axis: vecNormalize([
5224
- (m21 - m12) / denom,
5225
- (m02 - m20) / denom,
5226
- (m10 - m01) / denom
5227
- ]),
5256
+ axis: vecNormalize(axis),
5228
5257
  angleDeg
5229
5258
  };
5230
5259
  }
5231
- //#endregion
5232
- //#region src/loftedFlangeFns.ts
5260
+ // src/loftedFlangeFns.ts
5233
5261
  /**
5234
5262
  * Relative out-of-plane tolerance for the per-quad developability test: a quad is
5235
5263
  * twisted (non-developable) when its far corner sits off the diagonal-triangle plane
@@ -5349,10 +5377,13 @@ function buildQuadPlate(id, a0, a1, b1, b0, thickness) {
5349
5377
  }
5350
5378
  /** A flat triangle (p0,p1,p2) extruded by `thickness` along its own normal. */
5351
5379
  function buildTrianglePlate(id, p0, p1, p2, thickness) {
5380
+ const e0 = line(p0, p1);
5381
+ const e1 = line(p1, p2);
5382
+ const e2 = line(p2, p0);
5352
5383
  const loopResult = wireLoop([
5353
- line(p0, p1),
5354
- line(p1, p2),
5355
- line(p2, p0)
5384
+ e0,
5385
+ e1,
5386
+ e2
5356
5387
  ]);
5357
5388
  if (!loopResult.ok) return loopResult;
5358
5389
  const loop = loopResult.value;
@@ -5538,8 +5569,7 @@ function closedLoopWire(loop) {
5538
5569
  }
5539
5570
  return wireLoop(edges);
5540
5571
  }
5541
- //#endregion
5542
- //#region src/hemFns.ts
5572
+ // src/hemFns.ts
5543
5573
  /**
5544
5574
  * A small clearance baked into a CLOSED hem's curl radius. A true closed hem folds
5545
5575
  * the return flat against the parent with zero gap; modelling that exactly makes the
@@ -5732,8 +5762,7 @@ function splitAngle(totalDeg) {
5732
5762
  const per = totalDeg / n;
5733
5763
  return Array.from({ length: n }, () => per);
5734
5764
  }
5735
- //#endregion
5736
- //#region src/jogFns.ts
5765
+ // src/jogFns.ts
5737
5766
  /**
5738
5767
  * Author a jog (joggle): two opposite bends (`+θ` then `−θ`) that step a flat by
5739
5768
  * `offsetHeight` perpendicular to its plane, then continue parallel past the step.
@@ -5892,8 +5921,7 @@ function jogId(spec) {
5892
5921
  function resolveRegion(region) {
5893
5922
  return region === "base" || region === "face-0" ? ROOT_FLAT_ID : region;
5894
5923
  }
5895
- //#endregion
5896
- //#region src/dxfFns.ts
5924
+ // src/dxfFns.ts
5897
5925
  var DEFAULT_TEXT_HEIGHT = 2.5;
5898
5926
  var LAYER_OUTLINE = "OUTLINE";
5899
5927
  var LAYER_BEND_UP = "BEND_UP";
@@ -6102,8 +6130,7 @@ function pathPoints(wire) {
6102
6130
  function toPt2(v) {
6103
6131
  return [v[0], v[1]];
6104
6132
  }
6105
- //#endregion
6106
- //#region src/polygonFns.ts
6133
+ // src/polygonFns.ts
6107
6134
  /**
6108
6135
  * 2D simple-polygon geometry for true-shape (no-fit-polygon) nesting: an ordered
6109
6136
  * vertex loop plus the overlap predicate that is the correctness backbone of the
@@ -6346,8 +6373,7 @@ function pointSegmentDistance(p, a, b) {
6346
6373
  const dy = p[1] - (a[1] + t * vy);
6347
6374
  return Math.hypot(dx, dy);
6348
6375
  }
6349
- //#endregion
6350
- //#region src/nestFns.ts
6376
+ // src/nestFns.ts
6351
6377
  var EPS = 1e-9;
6352
6378
  /**
6353
6379
  * Bounding-box nest: arrange developed flat patterns onto stock sheets to reduce
@@ -6813,8 +6839,7 @@ function nestToDXF$1(result, patterns, sheetIndex, options) {
6813
6839
  }
6814
6840
  return multiPatternToDXF(placed, options ?? {});
6815
6841
  }
6816
- //#endregion
6817
- //#region src/reportFns.ts
6842
+ // src/reportFns.ts
6818
6843
  /**
6819
6844
  * Build the bend-table JSON report directly from an authored part. Walks the
6820
6845
  * same tree layout the unfold consumes so the per-bend values and the total flat
@@ -6857,8 +6882,7 @@ function reportFromUnfold(result) {
6857
6882
  function reportToJSON(report) {
6858
6883
  return JSON.stringify(report, null, 2);
6859
6884
  }
6860
- //#endregion
6861
- //#region src/api.ts
6885
+ // src/api.ts
6862
6886
  /** Author a straight-bend part: a base flat plus folded-up flanges. */
6863
6887
  function author(spec) {
6864
6888
  return authorPart(spec);
@@ -7020,8 +7044,7 @@ function bendTable(id) {
7020
7044
  function resolveAllowance(rule, angleDeg, thickness, onWarning) {
7021
7045
  return resolveBendAllowance(rule, angleDeg, thickness, onWarning);
7022
7046
  }
7023
- //#endregion
7024
- //#region src/facade.ts
7047
+ // src/facade.ts
7025
7048
  /** Thrown by the fluent facade when an underlying `Result<T>` is an `Err`. */
7026
7049
  var SheetMetalError = class extends Error {
7027
7050
  code;
@@ -7291,5 +7314,4 @@ function foldFlat(input) {
7291
7314
  function fromSolid(solid, opts) {
7292
7315
  return new ForeignSolidHandle(solid, opts);
7293
7316
  }
7294
- //#endregion
7295
7317
  export { ALUMINUM_GAUGES, DEFAULT_K_FACTOR, ForeignSolidHandle, MATERIALS, ROOT_FLAT_ID, STEEL_GAUGES, SheetMetalBuilder, SheetMetalError, SheetMetalPartHandle, addBendRelief, addBendTable, addCutout, addForm, addHole, addPolygonCutout, addSlot, addTab, allowance, author, authorContourFlange, authorLoftedFlange, authorPart, autoBendReliefs, autoMiterCorner, autoReliefs, baseEdgeRef, bendAllowance, bendRelief, bendTable, buildFeatureGraph, buildFeatureTree, buildReport, contourFlange, cornerRelief, developed, developedLength, emboss, featureTree, fitCylinder, flatPatternToDXF, flatPatternToPolylines, fold, foldFlat, fold$1 as foldPart, foldWithWarnings, fromPart, fromSolid, getBendTable, getMaterial, hem, jog, loftedFlange, louver, miter, miterCorner, miterCut, multiPatternToDXF, nest, nestToDXF, neutralRadius, partToFlatInput, patternBbox, patternToFlatInput, pointInPolygon, polygonBounds, polygonsOverlap, polygonsOverlapWithClearance, registerBendTable, relieveCorner, report, reportFrom, reportFromUnfold, reportJSON, reportToJSON, resolveAllowance, resolveBendAllowance, segmentsIntersect, sheetMetal, signedArea, tabAndSlot, toDXF, transformPolygon, unfold, unfoldForeignSolid, unfold$1 as unfoldPart, unfoldSolid, validate, validatePart, wireToPolygon };