brepjs-sheetmetal 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brepjs-sheetmetal.cjs +153 -106
- package/dist/brepjs-sheetmetal.js +153 -106
- package/package.json +4 -5
|
@@ -505,7 +505,7 @@ function buildFeatureTree(graph, rootId = ROOT_FLAT_ID) {
|
|
|
505
505
|
adjacency.get(edge.parent)?.push(edge);
|
|
506
506
|
adjacency.get(edge.child)?.push(edge);
|
|
507
507
|
}
|
|
508
|
-
const visited = new Set([rootId]);
|
|
508
|
+
const visited = /* @__PURE__ */ new Set([rootId]);
|
|
509
509
|
const treeEdges = /* @__PURE__ */ new Set();
|
|
510
510
|
const bends = [];
|
|
511
511
|
const queue = [rootId];
|
|
@@ -674,18 +674,20 @@ function unfold(part) {
|
|
|
674
674
|
direction: p.direction,
|
|
675
675
|
inward: [-p.frame.v[0], -p.frame.v[1]]
|
|
676
676
|
})).concat(contourResult.value.bendLines).concat(hemJogResult.value.bendLines);
|
|
677
|
+
const 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
|
+
const report = reportFromLayout(layout);
|
|
677
688
|
return (0, brepjs.ok)({
|
|
678
|
-
pattern
|
|
679
|
-
|
|
680
|
-
bendLines,
|
|
681
|
-
holes: holesResult.value,
|
|
682
|
-
formCuts: formsResult.value.cuts,
|
|
683
|
-
formMarkers: formsResult.value.markers,
|
|
684
|
-
formHinges: formsResult.value.hinges,
|
|
685
|
-
loftedDevelopments: loftedResult.value,
|
|
686
|
-
developedArea: layout.developedArea
|
|
687
|
-
},
|
|
688
|
-
report: reportFromLayout(layout),
|
|
689
|
+
pattern,
|
|
690
|
+
report,
|
|
689
691
|
warnings
|
|
690
692
|
});
|
|
691
693
|
}
|
|
@@ -1308,7 +1310,8 @@ function sampleFace(face, n) {
|
|
|
1308
1310
|
for (let i = 0; i < n; i += 1) {
|
|
1309
1311
|
const u = n === 1 ? .5 : i / (n - 1);
|
|
1310
1312
|
for (let j = 0; j < n; j += 1) {
|
|
1311
|
-
const
|
|
1313
|
+
const v = n === 1 ? .5 : j / (n - 1);
|
|
1314
|
+
const point = (0, brepjs.pointOnSurface)(face, u, v);
|
|
1312
1315
|
const normal = (0, brepjs.normalAt)(face, point);
|
|
1313
1316
|
const len = (0, brepjs.vecLength)(normal);
|
|
1314
1317
|
if (len < GEOM_TOL) continue;
|
|
@@ -1399,7 +1402,7 @@ function axisFromNormals(normals) {
|
|
|
1399
1402
|
}
|
|
1400
1403
|
/** An orthonormal pair spanning the plane perpendicular to `axis`. */
|
|
1401
1404
|
function perpBasis(axis) {
|
|
1402
|
-
const
|
|
1405
|
+
const seed = Math.abs(axis[0]) < .9 ? [
|
|
1403
1406
|
1,
|
|
1404
1407
|
0,
|
|
1405
1408
|
0
|
|
@@ -1407,7 +1410,8 @@ function perpBasis(axis) {
|
|
|
1407
1410
|
0,
|
|
1408
1411
|
1,
|
|
1409
1412
|
0
|
|
1410
|
-
]
|
|
1413
|
+
];
|
|
1414
|
+
const e1 = (0, brepjs.vecNormalize)((0, brepjs.vecCross)(axis, seed));
|
|
1411
1415
|
return {
|
|
1412
1416
|
e1,
|
|
1413
1417
|
e2: (0, brepjs.vecNormalize)((0, brepjs.vecCross)(axis, e1))
|
|
@@ -1416,7 +1420,9 @@ function perpBasis(axis) {
|
|
|
1416
1420
|
/** Perpendicular distance from `p` to the line through `o` with unit direction `d`. */
|
|
1417
1421
|
function distanceToAxis(p, o, d) {
|
|
1418
1422
|
const w = (0, brepjs.vecSub)(p, o);
|
|
1419
|
-
|
|
1423
|
+
const along = (0, brepjs.vecDot)(w, d);
|
|
1424
|
+
const perp = (0, brepjs.vecSub)(w, (0, brepjs.vecScale)(d, along));
|
|
1425
|
+
return (0, brepjs.vecLength)(perp);
|
|
1420
1426
|
}
|
|
1421
1427
|
/** Algebraic (Kåsa) least-squares circle fit to 2D points. */
|
|
1422
1428
|
function fitCircle2(pts) {
|
|
@@ -1621,7 +1627,8 @@ function detectThickness(planar) {
|
|
|
1621
1627
|
const fb = planar[j];
|
|
1622
1628
|
if (fa === void 0 || fb === void 0) continue;
|
|
1623
1629
|
const na = faceNormal(fa);
|
|
1624
|
-
|
|
1630
|
+
const nb = faceNormal(fb);
|
|
1631
|
+
if ((0, brepjs.vecDot)(na, nb) > -.99) continue;
|
|
1625
1632
|
const ca = (0, brepjs.faceCenter)(fa);
|
|
1626
1633
|
const cb = (0, brepjs.faceCenter)(fb);
|
|
1627
1634
|
const gap = Math.abs((0, brepjs.vecDot)((0, brepjs.vecSub)(cb, ca), na));
|
|
@@ -1650,7 +1657,8 @@ function pairFlats(planar, thickness, warnings) {
|
|
|
1650
1657
|
if (j === i || used.has(j)) continue;
|
|
1651
1658
|
const fb = planar[j];
|
|
1652
1659
|
if (fb === void 0) continue;
|
|
1653
|
-
|
|
1660
|
+
const nb = faceNormal(fb);
|
|
1661
|
+
if ((0, brepjs.vecDot)(na, nb) > -.99) continue;
|
|
1654
1662
|
const cb = (0, brepjs.faceCenter)(fb);
|
|
1655
1663
|
const gap = Math.abs((0, brepjs.vecDot)((0, brepjs.vecSub)(cb, ca), na));
|
|
1656
1664
|
const e = Math.abs(gap - thickness);
|
|
@@ -1819,7 +1827,7 @@ function layoutForeign(flats, graph, thickness, kFactor, warnings) {
|
|
|
1819
1827
|
adjacency.get(e.flats[0])?.push(e);
|
|
1820
1828
|
adjacency.get(e.flats[1])?.push(e);
|
|
1821
1829
|
}
|
|
1822
|
-
const visited = new Set([0]);
|
|
1830
|
+
const visited = /* @__PURE__ */ new Set([0]);
|
|
1823
1831
|
const treeEdges = /* @__PURE__ */ new Set();
|
|
1824
1832
|
const treeBends = [];
|
|
1825
1833
|
const queue = [0];
|
|
@@ -1913,23 +1921,26 @@ function layoutForeign(flats, graph, thickness, kFactor, warnings) {
|
|
|
1913
1921
|
});
|
|
1914
1922
|
}
|
|
1915
1923
|
const bbox = aabb2(rects);
|
|
1916
|
-
const
|
|
1924
|
+
const outline = buildRectOutline2(bbox);
|
|
1925
|
+
const outlineWire = (0, brepjs.wireLoop)(outline);
|
|
1917
1926
|
if (!outlineWire.ok) return outlineWire;
|
|
1927
|
+
const pattern = {
|
|
1928
|
+
outline: outlineWire.value,
|
|
1929
|
+
bendLines,
|
|
1930
|
+
holes: [],
|
|
1931
|
+
formCuts: [],
|
|
1932
|
+
formMarkers: [],
|
|
1933
|
+
formHinges: [],
|
|
1934
|
+
loftedDevelopments: [],
|
|
1935
|
+
developedArea
|
|
1936
|
+
};
|
|
1937
|
+
const report = {
|
|
1938
|
+
bends: bendReportRows,
|
|
1939
|
+
totalFlatSize: [bbox.x1 - bbox.x0, bbox.y1 - bbox.y0]
|
|
1940
|
+
};
|
|
1918
1941
|
return (0, brepjs.ok)({
|
|
1919
|
-
pattern
|
|
1920
|
-
|
|
1921
|
-
bendLines,
|
|
1922
|
-
holes: [],
|
|
1923
|
-
formCuts: [],
|
|
1924
|
-
formMarkers: [],
|
|
1925
|
-
formHinges: [],
|
|
1926
|
-
loftedDevelopments: [],
|
|
1927
|
-
developedArea
|
|
1928
|
-
},
|
|
1929
|
-
report: {
|
|
1930
|
-
bends: bendReportRows,
|
|
1931
|
-
totalFlatSize: [bbox.x1 - bbox.x0, bbox.y1 - bbox.y0]
|
|
1932
|
-
}
|
|
1942
|
+
pattern,
|
|
1943
|
+
report
|
|
1933
1944
|
});
|
|
1934
1945
|
}
|
|
1935
1946
|
/** Map a 3D in-plane point through a placed flat's affine map. */
|
|
@@ -2019,7 +2030,9 @@ function parentFootprintCentreAlong(parent, axisInPlane, axisRef) {
|
|
|
2019
2030
|
min = Math.min(min, v);
|
|
2020
2031
|
max = Math.max(max, v);
|
|
2021
2032
|
}
|
|
2022
|
-
|
|
2033
|
+
const mid = (min + max) / 2;
|
|
2034
|
+
const refAlong = (0, brepjs.vecDot)(axisRef, axisInPlane);
|
|
2035
|
+
return (0, brepjs.vecAdd)(axisRef, (0, brepjs.vecScale)(axisInPlane, mid - refAlong));
|
|
2023
2036
|
}
|
|
2024
2037
|
/** In-plane run extent of a flat along `out` (perpendicular to the bend axis). */
|
|
2025
2038
|
function inPlaneRun(flat, out) {
|
|
@@ -2035,7 +2048,8 @@ function inPlaneRun(flat, out) {
|
|
|
2035
2048
|
}
|
|
2036
2049
|
/** Run direction of a child flat pointing away from the bend axis. */
|
|
2037
2050
|
function runDirectionAway(flat, out, axisOrigin) {
|
|
2038
|
-
|
|
2051
|
+
const toCenter = (0, brepjs.vecSub)(flat.center, axisOrigin);
|
|
2052
|
+
return (0, brepjs.vecDot)(toCenter, out) >= 0 ? out : (0, brepjs.vecScale)(out, -1);
|
|
2039
2053
|
}
|
|
2040
2054
|
/**
|
|
2041
2055
|
* The child corner that develops onto the strip-far near point: minimum along the
|
|
@@ -2161,7 +2175,8 @@ function bendAxialExtent(bend) {
|
|
|
2161
2175
|
* parent flat's mid-surface, 'down' otherwise.
|
|
2162
2176
|
*/
|
|
2163
2177
|
function bendDirection(bend, parent) {
|
|
2164
|
-
|
|
2178
|
+
const toAxis = (0, brepjs.vecSub)(bend.fit.axisOrigin, parent.center);
|
|
2179
|
+
return (0, brepjs.vecDot)(toAxis, parent.normal) >= 0 ? "up" : "down";
|
|
2165
2180
|
}
|
|
2166
2181
|
function faceNormal(face) {
|
|
2167
2182
|
return (0, brepjs.vecNormalize)((0, brepjs.normalAt)(face, (0, brepjs.faceCenter)(face)));
|
|
@@ -2495,10 +2510,10 @@ function parentEdge(f, side) {
|
|
|
2495
2510
|
length = f.uLen;
|
|
2496
2511
|
a = o;
|
|
2497
2512
|
b = uTop;
|
|
2498
|
-
break;
|
|
2499
2513
|
}
|
|
2500
2514
|
const dir = (0, brepjs.vecNormalize)((0, brepjs.vecCross)(f.n, out));
|
|
2501
|
-
const
|
|
2515
|
+
const toB = (0, brepjs.vecSub)(b, a);
|
|
2516
|
+
const start = (0, brepjs.vecDot)(toB, dir) >= 0 ? a : b;
|
|
2502
2517
|
return {
|
|
2503
2518
|
dir,
|
|
2504
2519
|
out,
|
|
@@ -2611,14 +2626,15 @@ function frameTransform$1(axisT, runT, nT, edgeStart, offset) {
|
|
|
2611
2626
|
const applyR = (v) => (0, brepjs.vecAdd)((0, brepjs.vecAdd)((0, brepjs.vecScale)(runT, v[0]), (0, brepjs.vecScale)(axisT, v[1])), (0, brepjs.vecScale)(nT, v[2]));
|
|
2612
2627
|
return {
|
|
2613
2628
|
solid: (s) => {
|
|
2614
|
-
|
|
2629
|
+
const rotated = aa.angleDeg === 0 ? s : (0, brepjs.rotate)(s, aa.angleDeg, {
|
|
2615
2630
|
at: [
|
|
2616
2631
|
0,
|
|
2617
2632
|
0,
|
|
2618
2633
|
0
|
|
2619
2634
|
],
|
|
2620
2635
|
axis: aa.axis
|
|
2621
|
-
})
|
|
2636
|
+
});
|
|
2637
|
+
return (0, brepjs.translate)(rotated, target);
|
|
2622
2638
|
},
|
|
2623
2639
|
point: (p) => (0, brepjs.vecAdd)(applyR(p), target),
|
|
2624
2640
|
vector: applyR
|
|
@@ -2680,12 +2696,13 @@ function matrixToAxisAngle$2(m) {
|
|
|
2680
2696
|
};
|
|
2681
2697
|
}
|
|
2682
2698
|
const denom = 2 * Math.sin(angle);
|
|
2699
|
+
const axis = [
|
|
2700
|
+
(m21 - m12) / denom,
|
|
2701
|
+
(m02 - m20) / denom,
|
|
2702
|
+
(m10 - m01) / denom
|
|
2703
|
+
];
|
|
2683
2704
|
return {
|
|
2684
|
-
axis: (0, brepjs.vecNormalize)(
|
|
2685
|
-
(m21 - m12) / denom,
|
|
2686
|
-
(m02 - m20) / denom,
|
|
2687
|
-
(m10 - m01) / denom
|
|
2688
|
-
]),
|
|
2705
|
+
axis: (0, brepjs.vecNormalize)(axis),
|
|
2689
2706
|
angleDeg
|
|
2690
2707
|
};
|
|
2691
2708
|
}
|
|
@@ -2707,14 +2724,15 @@ function buildBendPatch$1(axisOrigin, thetaDeg, r, width, thickness, direction)
|
|
|
2707
2724
|
});
|
|
2708
2725
|
let tube = outer;
|
|
2709
2726
|
if (r > 1e-9) {
|
|
2710
|
-
const
|
|
2727
|
+
const inner = (0, brepjs.cylinder)(r, width, {
|
|
2711
2728
|
at: axisOrigin,
|
|
2712
2729
|
axis: [
|
|
2713
2730
|
0,
|
|
2714
2731
|
1,
|
|
2715
2732
|
0
|
|
2716
2733
|
]
|
|
2717
|
-
})
|
|
2734
|
+
});
|
|
2735
|
+
const carved = (0, brepjs.cut)(outer, inner);
|
|
2718
2736
|
if (!carved.ok) return carved;
|
|
2719
2737
|
tube = carved.value;
|
|
2720
2738
|
}
|
|
@@ -2732,39 +2750,47 @@ function buildBendPatch$1(axisOrigin, thetaDeg, r, width, thickness, direction)
|
|
|
2732
2750
|
function buildWedge$1(axisOrigin, thetaDeg, outerR, width, direction) {
|
|
2733
2751
|
const span = outerR * 2 + 2;
|
|
2734
2752
|
const margin = 1;
|
|
2735
|
-
const
|
|
2753
|
+
const blockA = (0, brepjs.box)(span, width + 2, 2 * span);
|
|
2754
|
+
const halfA = (0, brepjs.translate)(blockA, [
|
|
2736
2755
|
axisOrigin[0],
|
|
2737
2756
|
axisOrigin[1] - margin,
|
|
2738
2757
|
axisOrigin[2] - span
|
|
2739
2758
|
]);
|
|
2740
|
-
|
|
2759
|
+
const halfB = (0, brepjs.rotate)(halfA, (direction === "up" ? 1 : -1) * (180 - thetaDeg), {
|
|
2741
2760
|
at: axisOrigin,
|
|
2742
2761
|
axis: [
|
|
2743
2762
|
0,
|
|
2744
2763
|
1,
|
|
2745
2764
|
0
|
|
2746
2765
|
]
|
|
2747
|
-
})
|
|
2766
|
+
});
|
|
2767
|
+
return (0, brepjs.intersect)(halfA, halfB);
|
|
2748
2768
|
}
|
|
2749
2769
|
/** The flange flat, built lying along +X past the bend, then folded to angle θ. */
|
|
2750
2770
|
function buildFlangeFlat(axisOrigin, thetaDeg, width, thickness, length, direction) {
|
|
2751
|
-
if (direction === "up")
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
at: axisOrigin,
|
|
2757
|
-
axis: [
|
|
2758
|
-
0,
|
|
2759
|
-
1,
|
|
2771
|
+
if (direction === "up") {
|
|
2772
|
+
const flat = (0, brepjs.box)(length, width, thickness);
|
|
2773
|
+
const positioned = (0, brepjs.translate)(flat, [
|
|
2774
|
+
axisOrigin[0],
|
|
2775
|
+
axisOrigin[1],
|
|
2760
2776
|
0
|
|
2761
|
-
]
|
|
2762
|
-
|
|
2763
|
-
|
|
2777
|
+
]);
|
|
2778
|
+
return (0, brepjs.rotate)(positioned, -thetaDeg, {
|
|
2779
|
+
at: axisOrigin,
|
|
2780
|
+
axis: [
|
|
2781
|
+
0,
|
|
2782
|
+
1,
|
|
2783
|
+
0
|
|
2784
|
+
]
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
const flat = (0, brepjs.box)(length, width, thickness);
|
|
2788
|
+
const positioned = (0, brepjs.translate)(flat, [
|
|
2764
2789
|
axisOrigin[0],
|
|
2765
2790
|
axisOrigin[1],
|
|
2766
2791
|
-thickness
|
|
2767
|
-
])
|
|
2792
|
+
]);
|
|
2793
|
+
return (0, brepjs.rotate)(positioned, thetaDeg, {
|
|
2768
2794
|
at: axisOrigin,
|
|
2769
2795
|
axis: [
|
|
2770
2796
|
0,
|
|
@@ -3048,7 +3074,8 @@ function cornerPoint$1(originA, dirA, originB, dirB) {
|
|
|
3048
3074
|
const e = (0, brepjs.vecDot)(dirB, w0);
|
|
3049
3075
|
const denom = a * c - b * b;
|
|
3050
3076
|
if (Math.abs(denom) < 1e-9) return originA;
|
|
3051
|
-
|
|
3077
|
+
const sc = (b * e - c * d) / denom;
|
|
3078
|
+
return (0, brepjs.vecAdd)(originA, (0, brepjs.vecScale)(dirA, sc));
|
|
3052
3079
|
}
|
|
3053
3080
|
/** In-plane perpendicular to `axis` pointing toward the part center (into the parent). */
|
|
3054
3081
|
function inwardRun(axisOrigin, axis, center) {
|
|
@@ -3081,11 +3108,12 @@ function parentEdgeLength(part, flange) {
|
|
|
3081
3108
|
*/
|
|
3082
3109
|
function slotTool3d(at, along, inward, width, depth, thickness) {
|
|
3083
3110
|
const margin = thickness + 2;
|
|
3084
|
-
const
|
|
3111
|
+
const rotPerp = [
|
|
3085
3112
|
-along[1],
|
|
3086
3113
|
along[0],
|
|
3087
3114
|
0
|
|
3088
|
-
]
|
|
3115
|
+
];
|
|
3116
|
+
const y0 = (0, brepjs.vecDot)(rotPerp, inward) >= 0 ? 0 : -depth;
|
|
3089
3117
|
let tool = (0, brepjs.box)(width, depth, thickness + 2 * margin);
|
|
3090
3118
|
tool = (0, brepjs.translate)(tool, [
|
|
3091
3119
|
-width / 2,
|
|
@@ -3110,7 +3138,8 @@ function squareTool(solid, cornerXY, side) {
|
|
|
3110
3138
|
const b = (0, brepjs.getBounds)(solid);
|
|
3111
3139
|
const margin = 2;
|
|
3112
3140
|
const h = side / 2;
|
|
3113
|
-
|
|
3141
|
+
const tool = (0, brepjs.box)(side, side, b.zMax - b.zMin + 4);
|
|
3142
|
+
return (0, brepjs.translate)(tool, [
|
|
3114
3143
|
cornerXY[0] - h,
|
|
3115
3144
|
cornerXY[1] - h,
|
|
3116
3145
|
b.zMin - margin
|
|
@@ -3603,18 +3632,20 @@ function embossForm(part, partSolid, spec, regionId, world, dev, ext) {
|
|
|
3603
3632
|
let solid;
|
|
3604
3633
|
if (spec.form === "emboss") {
|
|
3605
3634
|
const base = (0, brepjs.vecAdd)(faceCentre, (0, brepjs.vecScale)(world.n, -.05));
|
|
3606
|
-
const
|
|
3635
|
+
const bump = (0, brepjs.cylinder)(r, spec.height + HAIR, {
|
|
3607
3636
|
at: base,
|
|
3608
3637
|
axis: world.n
|
|
3609
|
-
})
|
|
3638
|
+
});
|
|
3639
|
+
const fused = (0, brepjs.fuse)(partSolid, bump);
|
|
3610
3640
|
if (!fused.ok) return fused;
|
|
3611
3641
|
solid = normalizeSolid(fused.value);
|
|
3612
3642
|
} else {
|
|
3613
3643
|
const top = (0, brepjs.vecAdd)(faceCentre, (0, brepjs.vecScale)(world.n, HAIR));
|
|
3614
|
-
const
|
|
3644
|
+
const tool = (0, brepjs.cylinder)(r, spec.height + HAIR, {
|
|
3615
3645
|
at: top,
|
|
3616
3646
|
axis: (0, brepjs.vecScale)(world.n, -1)
|
|
3617
|
-
})
|
|
3647
|
+
});
|
|
3648
|
+
const cutResult = (0, brepjs.cut)(partSolid, tool);
|
|
3618
3649
|
if (!cutResult.ok) return cutResult;
|
|
3619
3650
|
solid = normalizeSolid(cutResult.value);
|
|
3620
3651
|
}
|
|
@@ -3706,7 +3737,8 @@ function buildLouverFlap(f, x0, x1, y0, width, height, thickness, sign) {
|
|
|
3706
3737
|
const n = (0, brepjs.vecNormalize)(f.n);
|
|
3707
3738
|
const v = (0, brepjs.vecNormalize)((0, brepjs.vecCross)(n, u));
|
|
3708
3739
|
const hingeCorner = (0, brepjs.vecAdd)((0, brepjs.vecAdd)((0, brepjs.vecAdd)(f.origin, (0, brepjs.vecScale)(f.u, x0)), (0, brepjs.vecScale)(f.v, y0)), (0, brepjs.vecScale)(f.n, thickness));
|
|
3709
|
-
|
|
3740
|
+
const placed = mapLocalToWorld(flap, u, v, n, hingeCorner);
|
|
3741
|
+
return (0, brepjs.ok)(placed);
|
|
3710
3742
|
}
|
|
3711
3743
|
/** Map a solid built in a canonical XYZ frame onto world axes (uT, vT, nT) at origin. */
|
|
3712
3744
|
function mapLocalToWorld(s, uT, vT, nT, origin) {
|
|
@@ -3721,14 +3753,15 @@ function mapLocalToWorld(s, uT, vT, nT, origin) {
|
|
|
3721
3753
|
vT[2],
|
|
3722
3754
|
nT[2]
|
|
3723
3755
|
]);
|
|
3724
|
-
|
|
3756
|
+
const rotated = aa.angleDeg === 0 ? s : (0, brepjs.rotate)(s, aa.angleDeg, {
|
|
3725
3757
|
at: [
|
|
3726
3758
|
0,
|
|
3727
3759
|
0,
|
|
3728
3760
|
0
|
|
3729
3761
|
],
|
|
3730
3762
|
axis: aa.axis
|
|
3731
|
-
})
|
|
3763
|
+
});
|
|
3764
|
+
return (0, brepjs.translate)(rotated, origin);
|
|
3732
3765
|
}
|
|
3733
3766
|
/** Axis-angle of a 3×3 row-major rotation matrix (columns = target basis). */
|
|
3734
3767
|
function matrixToAxisAngle$1(m) {
|
|
@@ -3786,12 +3819,13 @@ function matrixToAxisAngle$1(m) {
|
|
|
3786
3819
|
};
|
|
3787
3820
|
}
|
|
3788
3821
|
const denom = 2 * Math.sin(angle);
|
|
3822
|
+
const axis = [
|
|
3823
|
+
(m21 - m12) / denom,
|
|
3824
|
+
(m02 - m20) / denom,
|
|
3825
|
+
(m10 - m01) / denom
|
|
3826
|
+
];
|
|
3789
3827
|
return {
|
|
3790
|
-
axis: (0, brepjs.vecNormalize)(
|
|
3791
|
-
(m21 - m12) / denom,
|
|
3792
|
-
(m02 - m20) / denom,
|
|
3793
|
-
(m10 - m01) / denom
|
|
3794
|
-
]),
|
|
3828
|
+
axis: (0, brepjs.vecNormalize)(axis),
|
|
3795
3829
|
angleDeg
|
|
3796
3830
|
};
|
|
3797
3831
|
}
|
|
@@ -4706,7 +4740,8 @@ function cornerPoint(originA, dirA, originB, dirB) {
|
|
|
4706
4740
|
const e = (0, brepjs.vecDot)(dirB, w0);
|
|
4707
4741
|
const denom = a * c - b * b;
|
|
4708
4742
|
if (Math.abs(denom) < 1e-9) return originA;
|
|
4709
|
-
|
|
4743
|
+
const sc = (b * e - c * d) / denom;
|
|
4744
|
+
return (0, brepjs.vecAdd)(originA, (0, brepjs.vecScale)(dirA, sc));
|
|
4710
4745
|
}
|
|
4711
4746
|
/**
|
|
4712
4747
|
* Normal of the miter plane between two flanges whose bend axes are `dirA`/`dirB`.
|
|
@@ -4738,13 +4773,14 @@ function halfSpaceTool(solid, plane) {
|
|
|
4738
4773
|
-size / 2,
|
|
4739
4774
|
0
|
|
4740
4775
|
]);
|
|
4741
|
-
|
|
4776
|
+
const aligned = alignZTo(tool, normal);
|
|
4777
|
+
return (0, brepjs.ok)((0, brepjs.translate)(aligned, plane.origin));
|
|
4742
4778
|
}
|
|
4743
4779
|
/** Rotate a shape so its local +Z axis points along `target` (about the origin). */
|
|
4744
4780
|
function alignZTo(shape, target) {
|
|
4745
4781
|
const dot = (0, brepjs.vecDot)(Z_AXIS, target);
|
|
4746
|
-
if (dot >
|
|
4747
|
-
if (dot <
|
|
4782
|
+
if (dot > 1 - 1e-9) return shape;
|
|
4783
|
+
if (dot < -1 + 1e-9) return (0, brepjs.rotate)(shape, 180, {
|
|
4748
4784
|
at: [
|
|
4749
4785
|
0,
|
|
4750
4786
|
0,
|
|
@@ -4757,7 +4793,8 @@ function alignZTo(shape, target) {
|
|
|
4757
4793
|
]
|
|
4758
4794
|
});
|
|
4759
4795
|
const axis = (0, brepjs.vecNormalize)((0, brepjs.vecCross)(Z_AXIS, target));
|
|
4760
|
-
|
|
4796
|
+
const angleDeg = Math.acos(Math.max(-1, Math.min(1, dot))) * 180 / Math.PI;
|
|
4797
|
+
return (0, brepjs.rotate)(shape, angleDeg, {
|
|
4761
4798
|
at: [
|
|
4762
4799
|
0,
|
|
4763
4800
|
0,
|
|
@@ -4888,7 +4925,8 @@ function authorContourFlange(part, spec) {
|
|
|
4888
4925
|
function buildLineLeg(base, frame, span, thickness, legLength) {
|
|
4889
4926
|
const place = frameTransform(frame.run, frame.axis, frame.n, frame.origin);
|
|
4890
4927
|
const canon = (0, brepjs.box)(legLength, span, thickness);
|
|
4891
|
-
const
|
|
4928
|
+
const placed = place.solid(canon);
|
|
4929
|
+
const fused = (0, brepjs.fuse)(base, placed);
|
|
4892
4930
|
if (!fused.ok) return fused;
|
|
4893
4931
|
const nextOrigin = (0, brepjs.vecAdd)(frame.origin, (0, brepjs.vecScale)(frame.run, legLength));
|
|
4894
4932
|
return (0, brepjs.ok)({
|
|
@@ -4920,7 +4958,8 @@ function buildArcBend(base, frame, span, thickness, seg) {
|
|
|
4920
4958
|
const place = frameTransform(frame.run, frame.axis, frame.n, frame.origin);
|
|
4921
4959
|
const patch = buildBendPatch(canonOrigin, seg.angleDeg, r, span, thickness, seg.direction);
|
|
4922
4960
|
if (!patch.ok) return patch;
|
|
4923
|
-
const
|
|
4961
|
+
const placed = place.solid(patch.value);
|
|
4962
|
+
const fused = (0, brepjs.fuse)(base, placed);
|
|
4924
4963
|
if (!fused.ok) return fused;
|
|
4925
4964
|
const theta = seg.angleDeg * Math.PI / 180;
|
|
4926
4965
|
const a = -sign * theta;
|
|
@@ -4977,14 +5016,15 @@ function frameTransform(runT, axisT, nT, origin) {
|
|
|
4977
5016
|
const applyR = (v) => (0, brepjs.vecAdd)((0, brepjs.vecAdd)((0, brepjs.vecScale)(runT, v[0]), (0, brepjs.vecScale)(axisT, v[1])), (0, brepjs.vecScale)(nT, v[2]));
|
|
4978
5017
|
return {
|
|
4979
5018
|
solid: (s) => {
|
|
4980
|
-
|
|
5019
|
+
const rotated = aa.angleDeg === 0 ? s : (0, brepjs.rotate)(s, aa.angleDeg, {
|
|
4981
5020
|
at: [
|
|
4982
5021
|
0,
|
|
4983
5022
|
0,
|
|
4984
5023
|
0
|
|
4985
5024
|
],
|
|
4986
5025
|
axis: aa.axis
|
|
4987
|
-
})
|
|
5026
|
+
});
|
|
5027
|
+
return (0, brepjs.translate)(rotated, origin);
|
|
4988
5028
|
},
|
|
4989
5029
|
point: (p) => (0, brepjs.vecAdd)(applyR(p), origin),
|
|
4990
5030
|
vector: applyR
|
|
@@ -5045,10 +5085,10 @@ function baseEdge(part, side) {
|
|
|
5045
5085
|
length = uLen;
|
|
5046
5086
|
a = o;
|
|
5047
5087
|
b = uTop;
|
|
5048
|
-
break;
|
|
5049
5088
|
}
|
|
5050
5089
|
const dir = (0, brepjs.vecNormalize)((0, brepjs.vecCross)(n, out));
|
|
5051
|
-
const
|
|
5090
|
+
const toB = (0, brepjs.vecSub)(b, a);
|
|
5091
|
+
const start = (0, brepjs.vecDot)(toB, dir) >= 0 ? a : b;
|
|
5052
5092
|
return {
|
|
5053
5093
|
dir,
|
|
5054
5094
|
out,
|
|
@@ -5095,10 +5135,10 @@ function regionEdge(frame, side) {
|
|
|
5095
5135
|
length = frame.uLen;
|
|
5096
5136
|
a = o;
|
|
5097
5137
|
b = uTop;
|
|
5098
|
-
break;
|
|
5099
5138
|
}
|
|
5100
5139
|
const dir = (0, brepjs.vecNormalize)((0, brepjs.vecCross)(frame.n, out));
|
|
5101
|
-
const
|
|
5140
|
+
const toB = (0, brepjs.vecSub)(b, a);
|
|
5141
|
+
const start = (0, brepjs.vecDot)(toB, dir) >= 0 ? a : b;
|
|
5102
5142
|
return {
|
|
5103
5143
|
dir,
|
|
5104
5144
|
out,
|
|
@@ -5132,14 +5172,15 @@ function buildBendPatch(axisOrigin, thetaDeg, r, width, thickness, direction) {
|
|
|
5132
5172
|
});
|
|
5133
5173
|
let tube = outer;
|
|
5134
5174
|
if (r > 1e-9) {
|
|
5135
|
-
const
|
|
5175
|
+
const inner = (0, brepjs.cylinder)(r, width, {
|
|
5136
5176
|
at: axisOrigin,
|
|
5137
5177
|
axis: [
|
|
5138
5178
|
0,
|
|
5139
5179
|
1,
|
|
5140
5180
|
0
|
|
5141
5181
|
]
|
|
5142
|
-
})
|
|
5182
|
+
});
|
|
5183
|
+
const carved = (0, brepjs.cut)(outer, inner);
|
|
5143
5184
|
if (!carved.ok) return carved;
|
|
5144
5185
|
tube = carved.value;
|
|
5145
5186
|
}
|
|
@@ -5150,19 +5191,21 @@ function buildBendPatch(axisOrigin, thetaDeg, r, width, thickness, direction) {
|
|
|
5150
5191
|
function buildWedge(axisOrigin, thetaDeg, outerR, width, direction) {
|
|
5151
5192
|
const span = outerR * 2 + 2;
|
|
5152
5193
|
const margin = 1;
|
|
5153
|
-
const
|
|
5194
|
+
const blockA = (0, brepjs.box)(span, width + 2, 2 * span);
|
|
5195
|
+
const halfA = (0, brepjs.translate)(blockA, [
|
|
5154
5196
|
axisOrigin[0],
|
|
5155
5197
|
axisOrigin[1] - margin,
|
|
5156
5198
|
axisOrigin[2] - span
|
|
5157
5199
|
]);
|
|
5158
|
-
|
|
5200
|
+
const halfB = (0, brepjs.rotate)(halfA, (direction === "up" ? 1 : -1) * (180 - thetaDeg), {
|
|
5159
5201
|
at: axisOrigin,
|
|
5160
5202
|
axis: [
|
|
5161
5203
|
0,
|
|
5162
5204
|
1,
|
|
5163
5205
|
0
|
|
5164
5206
|
]
|
|
5165
|
-
})
|
|
5207
|
+
});
|
|
5208
|
+
return (0, brepjs.intersect)(halfA, halfB);
|
|
5166
5209
|
}
|
|
5167
5210
|
/** Axis-angle of a 3×3 rotation matrix (row-major). */
|
|
5168
5211
|
function matrixToAxisAngle(m) {
|
|
@@ -5220,12 +5263,13 @@ function matrixToAxisAngle(m) {
|
|
|
5220
5263
|
};
|
|
5221
5264
|
}
|
|
5222
5265
|
const denom = 2 * Math.sin(angle);
|
|
5266
|
+
const axis = [
|
|
5267
|
+
(m21 - m12) / denom,
|
|
5268
|
+
(m02 - m20) / denom,
|
|
5269
|
+
(m10 - m01) / denom
|
|
5270
|
+
];
|
|
5223
5271
|
return {
|
|
5224
|
-
axis: (0, brepjs.vecNormalize)(
|
|
5225
|
-
(m21 - m12) / denom,
|
|
5226
|
-
(m02 - m20) / denom,
|
|
5227
|
-
(m10 - m01) / denom
|
|
5228
|
-
]),
|
|
5272
|
+
axis: (0, brepjs.vecNormalize)(axis),
|
|
5229
5273
|
angleDeg
|
|
5230
5274
|
};
|
|
5231
5275
|
}
|
|
@@ -5350,10 +5394,13 @@ function buildQuadPlate(id, a0, a1, b1, b0, thickness) {
|
|
|
5350
5394
|
}
|
|
5351
5395
|
/** A flat triangle (p0,p1,p2) extruded by `thickness` along its own normal. */
|
|
5352
5396
|
function buildTrianglePlate(id, p0, p1, p2, thickness) {
|
|
5397
|
+
const e0 = (0, brepjs.line)(p0, p1);
|
|
5398
|
+
const e1 = (0, brepjs.line)(p1, p2);
|
|
5399
|
+
const e2 = (0, brepjs.line)(p2, p0);
|
|
5353
5400
|
const loopResult = (0, brepjs.wireLoop)([
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5401
|
+
e0,
|
|
5402
|
+
e1,
|
|
5403
|
+
e2
|
|
5357
5404
|
]);
|
|
5358
5405
|
if (!loopResult.ok) return loopResult;
|
|
5359
5406
|
const loop = loopResult.value;
|
|
@@ -504,7 +504,7 @@ function buildFeatureTree(graph, rootId = ROOT_FLAT_ID) {
|
|
|
504
504
|
adjacency.get(edge.parent)?.push(edge);
|
|
505
505
|
adjacency.get(edge.child)?.push(edge);
|
|
506
506
|
}
|
|
507
|
-
const visited = new Set([rootId]);
|
|
507
|
+
const visited = /* @__PURE__ */ new Set([rootId]);
|
|
508
508
|
const treeEdges = /* @__PURE__ */ new Set();
|
|
509
509
|
const bends = [];
|
|
510
510
|
const queue = [rootId];
|
|
@@ -673,18 +673,20 @@ function unfold(part) {
|
|
|
673
673
|
direction: p.direction,
|
|
674
674
|
inward: [-p.frame.v[0], -p.frame.v[1]]
|
|
675
675
|
})).concat(contourResult.value.bendLines).concat(hemJogResult.value.bendLines);
|
|
676
|
+
const pattern = {
|
|
677
|
+
outline: outlineResult.value,
|
|
678
|
+
bendLines,
|
|
679
|
+
holes: holesResult.value,
|
|
680
|
+
formCuts: formsResult.value.cuts,
|
|
681
|
+
formMarkers: formsResult.value.markers,
|
|
682
|
+
formHinges: formsResult.value.hinges,
|
|
683
|
+
loftedDevelopments: loftedResult.value,
|
|
684
|
+
developedArea: layout.developedArea
|
|
685
|
+
};
|
|
686
|
+
const report = reportFromLayout(layout);
|
|
676
687
|
return ok({
|
|
677
|
-
pattern
|
|
678
|
-
|
|
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),
|
|
688
|
+
pattern,
|
|
689
|
+
report,
|
|
688
690
|
warnings
|
|
689
691
|
});
|
|
690
692
|
}
|
|
@@ -1307,7 +1309,8 @@ function sampleFace(face, n) {
|
|
|
1307
1309
|
for (let i = 0; i < n; i += 1) {
|
|
1308
1310
|
const u = n === 1 ? .5 : i / (n - 1);
|
|
1309
1311
|
for (let j = 0; j < n; j += 1) {
|
|
1310
|
-
const
|
|
1312
|
+
const v = n === 1 ? .5 : j / (n - 1);
|
|
1313
|
+
const point = pointOnSurface(face, u, v);
|
|
1311
1314
|
const normal = normalAt(face, point);
|
|
1312
1315
|
const len = vecLength(normal);
|
|
1313
1316
|
if (len < GEOM_TOL) continue;
|
|
@@ -1398,7 +1401,7 @@ function axisFromNormals(normals) {
|
|
|
1398
1401
|
}
|
|
1399
1402
|
/** An orthonormal pair spanning the plane perpendicular to `axis`. */
|
|
1400
1403
|
function perpBasis(axis) {
|
|
1401
|
-
const
|
|
1404
|
+
const seed = Math.abs(axis[0]) < .9 ? [
|
|
1402
1405
|
1,
|
|
1403
1406
|
0,
|
|
1404
1407
|
0
|
|
@@ -1406,7 +1409,8 @@ function perpBasis(axis) {
|
|
|
1406
1409
|
0,
|
|
1407
1410
|
1,
|
|
1408
1411
|
0
|
|
1409
|
-
]
|
|
1412
|
+
];
|
|
1413
|
+
const e1 = vecNormalize(vecCross(axis, seed));
|
|
1410
1414
|
return {
|
|
1411
1415
|
e1,
|
|
1412
1416
|
e2: vecNormalize(vecCross(axis, e1))
|
|
@@ -1415,7 +1419,9 @@ function perpBasis(axis) {
|
|
|
1415
1419
|
/** Perpendicular distance from `p` to the line through `o` with unit direction `d`. */
|
|
1416
1420
|
function distanceToAxis(p, o, d) {
|
|
1417
1421
|
const w = vecSub(p, o);
|
|
1418
|
-
|
|
1422
|
+
const along = vecDot(w, d);
|
|
1423
|
+
const perp = vecSub(w, vecScale(d, along));
|
|
1424
|
+
return vecLength(perp);
|
|
1419
1425
|
}
|
|
1420
1426
|
/** Algebraic (Kåsa) least-squares circle fit to 2D points. */
|
|
1421
1427
|
function fitCircle2(pts) {
|
|
@@ -1620,7 +1626,8 @@ function detectThickness(planar) {
|
|
|
1620
1626
|
const fb = planar[j];
|
|
1621
1627
|
if (fa === void 0 || fb === void 0) continue;
|
|
1622
1628
|
const na = faceNormal(fa);
|
|
1623
|
-
|
|
1629
|
+
const nb = faceNormal(fb);
|
|
1630
|
+
if (vecDot(na, nb) > -.99) continue;
|
|
1624
1631
|
const ca = faceCenter(fa);
|
|
1625
1632
|
const cb = faceCenter(fb);
|
|
1626
1633
|
const gap = Math.abs(vecDot(vecSub(cb, ca), na));
|
|
@@ -1649,7 +1656,8 @@ function pairFlats(planar, thickness, warnings) {
|
|
|
1649
1656
|
if (j === i || used.has(j)) continue;
|
|
1650
1657
|
const fb = planar[j];
|
|
1651
1658
|
if (fb === void 0) continue;
|
|
1652
|
-
|
|
1659
|
+
const nb = faceNormal(fb);
|
|
1660
|
+
if (vecDot(na, nb) > -.99) continue;
|
|
1653
1661
|
const cb = faceCenter(fb);
|
|
1654
1662
|
const gap = Math.abs(vecDot(vecSub(cb, ca), na));
|
|
1655
1663
|
const e = Math.abs(gap - thickness);
|
|
@@ -1818,7 +1826,7 @@ function layoutForeign(flats, graph, thickness, kFactor, warnings) {
|
|
|
1818
1826
|
adjacency.get(e.flats[0])?.push(e);
|
|
1819
1827
|
adjacency.get(e.flats[1])?.push(e);
|
|
1820
1828
|
}
|
|
1821
|
-
const visited = new Set([0]);
|
|
1829
|
+
const visited = /* @__PURE__ */ new Set([0]);
|
|
1822
1830
|
const treeEdges = /* @__PURE__ */ new Set();
|
|
1823
1831
|
const treeBends = [];
|
|
1824
1832
|
const queue = [0];
|
|
@@ -1912,23 +1920,26 @@ function layoutForeign(flats, graph, thickness, kFactor, warnings) {
|
|
|
1912
1920
|
});
|
|
1913
1921
|
}
|
|
1914
1922
|
const bbox = aabb2(rects);
|
|
1915
|
-
const
|
|
1923
|
+
const outline = buildRectOutline2(bbox);
|
|
1924
|
+
const outlineWire = wireLoop(outline);
|
|
1916
1925
|
if (!outlineWire.ok) return outlineWire;
|
|
1926
|
+
const pattern = {
|
|
1927
|
+
outline: outlineWire.value,
|
|
1928
|
+
bendLines,
|
|
1929
|
+
holes: [],
|
|
1930
|
+
formCuts: [],
|
|
1931
|
+
formMarkers: [],
|
|
1932
|
+
formHinges: [],
|
|
1933
|
+
loftedDevelopments: [],
|
|
1934
|
+
developedArea
|
|
1935
|
+
};
|
|
1936
|
+
const report = {
|
|
1937
|
+
bends: bendReportRows,
|
|
1938
|
+
totalFlatSize: [bbox.x1 - bbox.x0, bbox.y1 - bbox.y0]
|
|
1939
|
+
};
|
|
1917
1940
|
return ok({
|
|
1918
|
-
pattern
|
|
1919
|
-
|
|
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
|
-
}
|
|
1941
|
+
pattern,
|
|
1942
|
+
report
|
|
1932
1943
|
});
|
|
1933
1944
|
}
|
|
1934
1945
|
/** Map a 3D in-plane point through a placed flat's affine map. */
|
|
@@ -2018,7 +2029,9 @@ function parentFootprintCentreAlong(parent, axisInPlane, axisRef) {
|
|
|
2018
2029
|
min = Math.min(min, v);
|
|
2019
2030
|
max = Math.max(max, v);
|
|
2020
2031
|
}
|
|
2021
|
-
|
|
2032
|
+
const mid = (min + max) / 2;
|
|
2033
|
+
const refAlong = vecDot(axisRef, axisInPlane);
|
|
2034
|
+
return vecAdd(axisRef, vecScale(axisInPlane, mid - refAlong));
|
|
2022
2035
|
}
|
|
2023
2036
|
/** In-plane run extent of a flat along `out` (perpendicular to the bend axis). */
|
|
2024
2037
|
function inPlaneRun(flat, out) {
|
|
@@ -2034,7 +2047,8 @@ function inPlaneRun(flat, out) {
|
|
|
2034
2047
|
}
|
|
2035
2048
|
/** Run direction of a child flat pointing away from the bend axis. */
|
|
2036
2049
|
function runDirectionAway(flat, out, axisOrigin) {
|
|
2037
|
-
|
|
2050
|
+
const toCenter = vecSub(flat.center, axisOrigin);
|
|
2051
|
+
return vecDot(toCenter, out) >= 0 ? out : vecScale(out, -1);
|
|
2038
2052
|
}
|
|
2039
2053
|
/**
|
|
2040
2054
|
* The child corner that develops onto the strip-far near point: minimum along the
|
|
@@ -2160,7 +2174,8 @@ function bendAxialExtent(bend) {
|
|
|
2160
2174
|
* parent flat's mid-surface, 'down' otherwise.
|
|
2161
2175
|
*/
|
|
2162
2176
|
function bendDirection(bend, parent) {
|
|
2163
|
-
|
|
2177
|
+
const toAxis = vecSub(bend.fit.axisOrigin, parent.center);
|
|
2178
|
+
return vecDot(toAxis, parent.normal) >= 0 ? "up" : "down";
|
|
2164
2179
|
}
|
|
2165
2180
|
function faceNormal(face) {
|
|
2166
2181
|
return vecNormalize(normalAt(face, faceCenter(face)));
|
|
@@ -2494,10 +2509,10 @@ function parentEdge(f, side) {
|
|
|
2494
2509
|
length = f.uLen;
|
|
2495
2510
|
a = o;
|
|
2496
2511
|
b = uTop;
|
|
2497
|
-
break;
|
|
2498
2512
|
}
|
|
2499
2513
|
const dir = vecNormalize(vecCross(f.n, out));
|
|
2500
|
-
const
|
|
2514
|
+
const toB = vecSub(b, a);
|
|
2515
|
+
const start = vecDot(toB, dir) >= 0 ? a : b;
|
|
2501
2516
|
return {
|
|
2502
2517
|
dir,
|
|
2503
2518
|
out,
|
|
@@ -2610,14 +2625,15 @@ function frameTransform$1(axisT, runT, nT, edgeStart, offset) {
|
|
|
2610
2625
|
const applyR = (v) => vecAdd(vecAdd(vecScale(runT, v[0]), vecScale(axisT, v[1])), vecScale(nT, v[2]));
|
|
2611
2626
|
return {
|
|
2612
2627
|
solid: (s) => {
|
|
2613
|
-
|
|
2628
|
+
const rotated = aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, {
|
|
2614
2629
|
at: [
|
|
2615
2630
|
0,
|
|
2616
2631
|
0,
|
|
2617
2632
|
0
|
|
2618
2633
|
],
|
|
2619
2634
|
axis: aa.axis
|
|
2620
|
-
})
|
|
2635
|
+
});
|
|
2636
|
+
return translate(rotated, target);
|
|
2621
2637
|
},
|
|
2622
2638
|
point: (p) => vecAdd(applyR(p), target),
|
|
2623
2639
|
vector: applyR
|
|
@@ -2679,12 +2695,13 @@ function matrixToAxisAngle$2(m) {
|
|
|
2679
2695
|
};
|
|
2680
2696
|
}
|
|
2681
2697
|
const denom = 2 * Math.sin(angle);
|
|
2698
|
+
const axis = [
|
|
2699
|
+
(m21 - m12) / denom,
|
|
2700
|
+
(m02 - m20) / denom,
|
|
2701
|
+
(m10 - m01) / denom
|
|
2702
|
+
];
|
|
2682
2703
|
return {
|
|
2683
|
-
axis: vecNormalize(
|
|
2684
|
-
(m21 - m12) / denom,
|
|
2685
|
-
(m02 - m20) / denom,
|
|
2686
|
-
(m10 - m01) / denom
|
|
2687
|
-
]),
|
|
2704
|
+
axis: vecNormalize(axis),
|
|
2688
2705
|
angleDeg
|
|
2689
2706
|
};
|
|
2690
2707
|
}
|
|
@@ -2706,14 +2723,15 @@ function buildBendPatch$1(axisOrigin, thetaDeg, r, width, thickness, direction)
|
|
|
2706
2723
|
});
|
|
2707
2724
|
let tube = outer;
|
|
2708
2725
|
if (r > 1e-9) {
|
|
2709
|
-
const
|
|
2726
|
+
const inner = cylinder(r, width, {
|
|
2710
2727
|
at: axisOrigin,
|
|
2711
2728
|
axis: [
|
|
2712
2729
|
0,
|
|
2713
2730
|
1,
|
|
2714
2731
|
0
|
|
2715
2732
|
]
|
|
2716
|
-
})
|
|
2733
|
+
});
|
|
2734
|
+
const carved = cut(outer, inner);
|
|
2717
2735
|
if (!carved.ok) return carved;
|
|
2718
2736
|
tube = carved.value;
|
|
2719
2737
|
}
|
|
@@ -2731,39 +2749,47 @@ function buildBendPatch$1(axisOrigin, thetaDeg, r, width, thickness, direction)
|
|
|
2731
2749
|
function buildWedge$1(axisOrigin, thetaDeg, outerR, width, direction) {
|
|
2732
2750
|
const span = outerR * 2 + 2;
|
|
2733
2751
|
const margin = 1;
|
|
2734
|
-
const
|
|
2752
|
+
const blockA = box(span, width + 2, 2 * span);
|
|
2753
|
+
const halfA = translate(blockA, [
|
|
2735
2754
|
axisOrigin[0],
|
|
2736
2755
|
axisOrigin[1] - margin,
|
|
2737
2756
|
axisOrigin[2] - span
|
|
2738
2757
|
]);
|
|
2739
|
-
|
|
2758
|
+
const halfB = rotate(halfA, (direction === "up" ? 1 : -1) * (180 - thetaDeg), {
|
|
2740
2759
|
at: axisOrigin,
|
|
2741
2760
|
axis: [
|
|
2742
2761
|
0,
|
|
2743
2762
|
1,
|
|
2744
2763
|
0
|
|
2745
2764
|
]
|
|
2746
|
-
})
|
|
2765
|
+
});
|
|
2766
|
+
return intersect(halfA, halfB);
|
|
2747
2767
|
}
|
|
2748
2768
|
/** The flange flat, built lying along +X past the bend, then folded to angle θ. */
|
|
2749
2769
|
function buildFlangeFlat(axisOrigin, thetaDeg, width, thickness, length, direction) {
|
|
2750
|
-
if (direction === "up")
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
at: axisOrigin,
|
|
2756
|
-
axis: [
|
|
2757
|
-
0,
|
|
2758
|
-
1,
|
|
2770
|
+
if (direction === "up") {
|
|
2771
|
+
const flat = box(length, width, thickness);
|
|
2772
|
+
const positioned = translate(flat, [
|
|
2773
|
+
axisOrigin[0],
|
|
2774
|
+
axisOrigin[1],
|
|
2759
2775
|
0
|
|
2760
|
-
]
|
|
2761
|
-
|
|
2762
|
-
|
|
2776
|
+
]);
|
|
2777
|
+
return rotate(positioned, -thetaDeg, {
|
|
2778
|
+
at: axisOrigin,
|
|
2779
|
+
axis: [
|
|
2780
|
+
0,
|
|
2781
|
+
1,
|
|
2782
|
+
0
|
|
2783
|
+
]
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
const flat = box(length, width, thickness);
|
|
2787
|
+
const positioned = translate(flat, [
|
|
2763
2788
|
axisOrigin[0],
|
|
2764
2789
|
axisOrigin[1],
|
|
2765
2790
|
-thickness
|
|
2766
|
-
])
|
|
2791
|
+
]);
|
|
2792
|
+
return rotate(positioned, thetaDeg, {
|
|
2767
2793
|
at: axisOrigin,
|
|
2768
2794
|
axis: [
|
|
2769
2795
|
0,
|
|
@@ -3047,7 +3073,8 @@ function cornerPoint$1(originA, dirA, originB, dirB) {
|
|
|
3047
3073
|
const e = vecDot(dirB, w0);
|
|
3048
3074
|
const denom = a * c - b * b;
|
|
3049
3075
|
if (Math.abs(denom) < 1e-9) return originA;
|
|
3050
|
-
|
|
3076
|
+
const sc = (b * e - c * d) / denom;
|
|
3077
|
+
return vecAdd(originA, vecScale(dirA, sc));
|
|
3051
3078
|
}
|
|
3052
3079
|
/** In-plane perpendicular to `axis` pointing toward the part center (into the parent). */
|
|
3053
3080
|
function inwardRun(axisOrigin, axis, center) {
|
|
@@ -3080,11 +3107,12 @@ function parentEdgeLength(part, flange) {
|
|
|
3080
3107
|
*/
|
|
3081
3108
|
function slotTool3d(at, along, inward, width, depth, thickness) {
|
|
3082
3109
|
const margin = thickness + 2;
|
|
3083
|
-
const
|
|
3110
|
+
const rotPerp = [
|
|
3084
3111
|
-along[1],
|
|
3085
3112
|
along[0],
|
|
3086
3113
|
0
|
|
3087
|
-
]
|
|
3114
|
+
];
|
|
3115
|
+
const y0 = vecDot(rotPerp, inward) >= 0 ? 0 : -depth;
|
|
3088
3116
|
let tool = box(width, depth, thickness + 2 * margin);
|
|
3089
3117
|
tool = translate(tool, [
|
|
3090
3118
|
-width / 2,
|
|
@@ -3109,7 +3137,8 @@ function squareTool(solid, cornerXY, side) {
|
|
|
3109
3137
|
const b = getBounds(solid);
|
|
3110
3138
|
const margin = 2;
|
|
3111
3139
|
const h = side / 2;
|
|
3112
|
-
|
|
3140
|
+
const tool = box(side, side, b.zMax - b.zMin + 4);
|
|
3141
|
+
return translate(tool, [
|
|
3113
3142
|
cornerXY[0] - h,
|
|
3114
3143
|
cornerXY[1] - h,
|
|
3115
3144
|
b.zMin - margin
|
|
@@ -3602,18 +3631,20 @@ function embossForm(part, partSolid, spec, regionId, world, dev, ext) {
|
|
|
3602
3631
|
let solid;
|
|
3603
3632
|
if (spec.form === "emboss") {
|
|
3604
3633
|
const base = vecAdd(faceCentre, vecScale(world.n, -.05));
|
|
3605
|
-
const
|
|
3634
|
+
const bump = cylinder(r, spec.height + HAIR, {
|
|
3606
3635
|
at: base,
|
|
3607
3636
|
axis: world.n
|
|
3608
|
-
})
|
|
3637
|
+
});
|
|
3638
|
+
const fused = fuse(partSolid, bump);
|
|
3609
3639
|
if (!fused.ok) return fused;
|
|
3610
3640
|
solid = normalizeSolid(fused.value);
|
|
3611
3641
|
} else {
|
|
3612
3642
|
const top = vecAdd(faceCentre, vecScale(world.n, HAIR));
|
|
3613
|
-
const
|
|
3643
|
+
const tool = cylinder(r, spec.height + HAIR, {
|
|
3614
3644
|
at: top,
|
|
3615
3645
|
axis: vecScale(world.n, -1)
|
|
3616
|
-
})
|
|
3646
|
+
});
|
|
3647
|
+
const cutResult = cut(partSolid, tool);
|
|
3617
3648
|
if (!cutResult.ok) return cutResult;
|
|
3618
3649
|
solid = normalizeSolid(cutResult.value);
|
|
3619
3650
|
}
|
|
@@ -3705,7 +3736,8 @@ function buildLouverFlap(f, x0, x1, y0, width, height, thickness, sign) {
|
|
|
3705
3736
|
const n = vecNormalize(f.n);
|
|
3706
3737
|
const v = vecNormalize(vecCross(n, u));
|
|
3707
3738
|
const hingeCorner = vecAdd(vecAdd(vecAdd(f.origin, vecScale(f.u, x0)), vecScale(f.v, y0)), vecScale(f.n, thickness));
|
|
3708
|
-
|
|
3739
|
+
const placed = mapLocalToWorld(flap, u, v, n, hingeCorner);
|
|
3740
|
+
return ok(placed);
|
|
3709
3741
|
}
|
|
3710
3742
|
/** Map a solid built in a canonical XYZ frame onto world axes (uT, vT, nT) at origin. */
|
|
3711
3743
|
function mapLocalToWorld(s, uT, vT, nT, origin) {
|
|
@@ -3720,14 +3752,15 @@ function mapLocalToWorld(s, uT, vT, nT, origin) {
|
|
|
3720
3752
|
vT[2],
|
|
3721
3753
|
nT[2]
|
|
3722
3754
|
]);
|
|
3723
|
-
|
|
3755
|
+
const rotated = aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, {
|
|
3724
3756
|
at: [
|
|
3725
3757
|
0,
|
|
3726
3758
|
0,
|
|
3727
3759
|
0
|
|
3728
3760
|
],
|
|
3729
3761
|
axis: aa.axis
|
|
3730
|
-
})
|
|
3762
|
+
});
|
|
3763
|
+
return translate(rotated, origin);
|
|
3731
3764
|
}
|
|
3732
3765
|
/** Axis-angle of a 3×3 row-major rotation matrix (columns = target basis). */
|
|
3733
3766
|
function matrixToAxisAngle$1(m) {
|
|
@@ -3785,12 +3818,13 @@ function matrixToAxisAngle$1(m) {
|
|
|
3785
3818
|
};
|
|
3786
3819
|
}
|
|
3787
3820
|
const denom = 2 * Math.sin(angle);
|
|
3821
|
+
const axis = [
|
|
3822
|
+
(m21 - m12) / denom,
|
|
3823
|
+
(m02 - m20) / denom,
|
|
3824
|
+
(m10 - m01) / denom
|
|
3825
|
+
];
|
|
3788
3826
|
return {
|
|
3789
|
-
axis: vecNormalize(
|
|
3790
|
-
(m21 - m12) / denom,
|
|
3791
|
-
(m02 - m20) / denom,
|
|
3792
|
-
(m10 - m01) / denom
|
|
3793
|
-
]),
|
|
3827
|
+
axis: vecNormalize(axis),
|
|
3794
3828
|
angleDeg
|
|
3795
3829
|
};
|
|
3796
3830
|
}
|
|
@@ -4705,7 +4739,8 @@ function cornerPoint(originA, dirA, originB, dirB) {
|
|
|
4705
4739
|
const e = vecDot(dirB, w0);
|
|
4706
4740
|
const denom = a * c - b * b;
|
|
4707
4741
|
if (Math.abs(denom) < 1e-9) return originA;
|
|
4708
|
-
|
|
4742
|
+
const sc = (b * e - c * d) / denom;
|
|
4743
|
+
return vecAdd(originA, vecScale(dirA, sc));
|
|
4709
4744
|
}
|
|
4710
4745
|
/**
|
|
4711
4746
|
* Normal of the miter plane between two flanges whose bend axes are `dirA`/`dirB`.
|
|
@@ -4737,13 +4772,14 @@ function halfSpaceTool(solid, plane) {
|
|
|
4737
4772
|
-size / 2,
|
|
4738
4773
|
0
|
|
4739
4774
|
]);
|
|
4740
|
-
|
|
4775
|
+
const aligned = alignZTo(tool, normal);
|
|
4776
|
+
return ok(translate(aligned, plane.origin));
|
|
4741
4777
|
}
|
|
4742
4778
|
/** Rotate a shape so its local +Z axis points along `target` (about the origin). */
|
|
4743
4779
|
function alignZTo(shape, target) {
|
|
4744
4780
|
const dot = vecDot(Z_AXIS, target);
|
|
4745
|
-
if (dot >
|
|
4746
|
-
if (dot <
|
|
4781
|
+
if (dot > 1 - 1e-9) return shape;
|
|
4782
|
+
if (dot < -1 + 1e-9) return rotate(shape, 180, {
|
|
4747
4783
|
at: [
|
|
4748
4784
|
0,
|
|
4749
4785
|
0,
|
|
@@ -4756,7 +4792,8 @@ function alignZTo(shape, target) {
|
|
|
4756
4792
|
]
|
|
4757
4793
|
});
|
|
4758
4794
|
const axis = vecNormalize(vecCross(Z_AXIS, target));
|
|
4759
|
-
|
|
4795
|
+
const angleDeg = Math.acos(Math.max(-1, Math.min(1, dot))) * 180 / Math.PI;
|
|
4796
|
+
return rotate(shape, angleDeg, {
|
|
4760
4797
|
at: [
|
|
4761
4798
|
0,
|
|
4762
4799
|
0,
|
|
@@ -4887,7 +4924,8 @@ function authorContourFlange(part, spec) {
|
|
|
4887
4924
|
function buildLineLeg(base, frame, span, thickness, legLength) {
|
|
4888
4925
|
const place = frameTransform(frame.run, frame.axis, frame.n, frame.origin);
|
|
4889
4926
|
const canon = box(legLength, span, thickness);
|
|
4890
|
-
const
|
|
4927
|
+
const placed = place.solid(canon);
|
|
4928
|
+
const fused = fuse(base, placed);
|
|
4891
4929
|
if (!fused.ok) return fused;
|
|
4892
4930
|
const nextOrigin = vecAdd(frame.origin, vecScale(frame.run, legLength));
|
|
4893
4931
|
return ok({
|
|
@@ -4919,7 +4957,8 @@ function buildArcBend(base, frame, span, thickness, seg) {
|
|
|
4919
4957
|
const place = frameTransform(frame.run, frame.axis, frame.n, frame.origin);
|
|
4920
4958
|
const patch = buildBendPatch(canonOrigin, seg.angleDeg, r, span, thickness, seg.direction);
|
|
4921
4959
|
if (!patch.ok) return patch;
|
|
4922
|
-
const
|
|
4960
|
+
const placed = place.solid(patch.value);
|
|
4961
|
+
const fused = fuse(base, placed);
|
|
4923
4962
|
if (!fused.ok) return fused;
|
|
4924
4963
|
const theta = seg.angleDeg * Math.PI / 180;
|
|
4925
4964
|
const a = -sign * theta;
|
|
@@ -4976,14 +5015,15 @@ function frameTransform(runT, axisT, nT, origin) {
|
|
|
4976
5015
|
const applyR = (v) => vecAdd(vecAdd(vecScale(runT, v[0]), vecScale(axisT, v[1])), vecScale(nT, v[2]));
|
|
4977
5016
|
return {
|
|
4978
5017
|
solid: (s) => {
|
|
4979
|
-
|
|
5018
|
+
const rotated = aa.angleDeg === 0 ? s : rotate(s, aa.angleDeg, {
|
|
4980
5019
|
at: [
|
|
4981
5020
|
0,
|
|
4982
5021
|
0,
|
|
4983
5022
|
0
|
|
4984
5023
|
],
|
|
4985
5024
|
axis: aa.axis
|
|
4986
|
-
})
|
|
5025
|
+
});
|
|
5026
|
+
return translate(rotated, origin);
|
|
4987
5027
|
},
|
|
4988
5028
|
point: (p) => vecAdd(applyR(p), origin),
|
|
4989
5029
|
vector: applyR
|
|
@@ -5044,10 +5084,10 @@ function baseEdge(part, side) {
|
|
|
5044
5084
|
length = uLen;
|
|
5045
5085
|
a = o;
|
|
5046
5086
|
b = uTop;
|
|
5047
|
-
break;
|
|
5048
5087
|
}
|
|
5049
5088
|
const dir = vecNormalize(vecCross(n, out));
|
|
5050
|
-
const
|
|
5089
|
+
const toB = vecSub(b, a);
|
|
5090
|
+
const start = vecDot(toB, dir) >= 0 ? a : b;
|
|
5051
5091
|
return {
|
|
5052
5092
|
dir,
|
|
5053
5093
|
out,
|
|
@@ -5094,10 +5134,10 @@ function regionEdge(frame, side) {
|
|
|
5094
5134
|
length = frame.uLen;
|
|
5095
5135
|
a = o;
|
|
5096
5136
|
b = uTop;
|
|
5097
|
-
break;
|
|
5098
5137
|
}
|
|
5099
5138
|
const dir = vecNormalize(vecCross(frame.n, out));
|
|
5100
|
-
const
|
|
5139
|
+
const toB = vecSub(b, a);
|
|
5140
|
+
const start = vecDot(toB, dir) >= 0 ? a : b;
|
|
5101
5141
|
return {
|
|
5102
5142
|
dir,
|
|
5103
5143
|
out,
|
|
@@ -5131,14 +5171,15 @@ function buildBendPatch(axisOrigin, thetaDeg, r, width, thickness, direction) {
|
|
|
5131
5171
|
});
|
|
5132
5172
|
let tube = outer;
|
|
5133
5173
|
if (r > 1e-9) {
|
|
5134
|
-
const
|
|
5174
|
+
const inner = cylinder(r, width, {
|
|
5135
5175
|
at: axisOrigin,
|
|
5136
5176
|
axis: [
|
|
5137
5177
|
0,
|
|
5138
5178
|
1,
|
|
5139
5179
|
0
|
|
5140
5180
|
]
|
|
5141
|
-
})
|
|
5181
|
+
});
|
|
5182
|
+
const carved = cut(outer, inner);
|
|
5142
5183
|
if (!carved.ok) return carved;
|
|
5143
5184
|
tube = carved.value;
|
|
5144
5185
|
}
|
|
@@ -5149,19 +5190,21 @@ function buildBendPatch(axisOrigin, thetaDeg, r, width, thickness, direction) {
|
|
|
5149
5190
|
function buildWedge(axisOrigin, thetaDeg, outerR, width, direction) {
|
|
5150
5191
|
const span = outerR * 2 + 2;
|
|
5151
5192
|
const margin = 1;
|
|
5152
|
-
const
|
|
5193
|
+
const blockA = box(span, width + 2, 2 * span);
|
|
5194
|
+
const halfA = translate(blockA, [
|
|
5153
5195
|
axisOrigin[0],
|
|
5154
5196
|
axisOrigin[1] - margin,
|
|
5155
5197
|
axisOrigin[2] - span
|
|
5156
5198
|
]);
|
|
5157
|
-
|
|
5199
|
+
const halfB = rotate(halfA, (direction === "up" ? 1 : -1) * (180 - thetaDeg), {
|
|
5158
5200
|
at: axisOrigin,
|
|
5159
5201
|
axis: [
|
|
5160
5202
|
0,
|
|
5161
5203
|
1,
|
|
5162
5204
|
0
|
|
5163
5205
|
]
|
|
5164
|
-
})
|
|
5206
|
+
});
|
|
5207
|
+
return intersect(halfA, halfB);
|
|
5165
5208
|
}
|
|
5166
5209
|
/** Axis-angle of a 3×3 rotation matrix (row-major). */
|
|
5167
5210
|
function matrixToAxisAngle(m) {
|
|
@@ -5219,12 +5262,13 @@ function matrixToAxisAngle(m) {
|
|
|
5219
5262
|
};
|
|
5220
5263
|
}
|
|
5221
5264
|
const denom = 2 * Math.sin(angle);
|
|
5265
|
+
const axis = [
|
|
5266
|
+
(m21 - m12) / denom,
|
|
5267
|
+
(m02 - m20) / denom,
|
|
5268
|
+
(m10 - m01) / denom
|
|
5269
|
+
];
|
|
5222
5270
|
return {
|
|
5223
|
-
axis: vecNormalize(
|
|
5224
|
-
(m21 - m12) / denom,
|
|
5225
|
-
(m02 - m20) / denom,
|
|
5226
|
-
(m10 - m01) / denom
|
|
5227
|
-
]),
|
|
5271
|
+
axis: vecNormalize(axis),
|
|
5228
5272
|
angleDeg
|
|
5229
5273
|
};
|
|
5230
5274
|
}
|
|
@@ -5349,10 +5393,13 @@ function buildQuadPlate(id, a0, a1, b1, b0, thickness) {
|
|
|
5349
5393
|
}
|
|
5350
5394
|
/** A flat triangle (p0,p1,p2) extruded by `thickness` along its own normal. */
|
|
5351
5395
|
function buildTrianglePlate(id, p0, p1, p2, thickness) {
|
|
5396
|
+
const e0 = line(p0, p1);
|
|
5397
|
+
const e1 = line(p1, p2);
|
|
5398
|
+
const e2 = line(p2, p0);
|
|
5352
5399
|
const loopResult = wireLoop([
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5400
|
+
e0,
|
|
5401
|
+
e1,
|
|
5402
|
+
e2
|
|
5356
5403
|
]);
|
|
5357
5404
|
if (!loopResult.ok) return loopResult;
|
|
5358
5405
|
const loop = loopResult.value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brepjs-sheetmetal",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Sheet-metal CAD domain for brepjs — flange authoring, auto-miter, fold/unfold flat patterns",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sheet-metal",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
],
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "vite build",
|
|
46
|
-
"typecheck": "
|
|
46
|
+
"typecheck": "tsgo --noEmit",
|
|
47
47
|
"test": "vitest run",
|
|
48
48
|
"snapshot": "tsx harness/snapshot.ts",
|
|
49
49
|
"lint": "eslint src tests"
|
|
@@ -53,10 +53,9 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@eslint/js": "*",
|
|
56
|
-
"@types/node": "
|
|
57
|
-
"brepjs-opencascade": ">=0.16.0",
|
|
56
|
+
"@types/node": "26.2.0",
|
|
58
57
|
"eslint": "*",
|
|
59
|
-
"tsx": "4.
|
|
58
|
+
"tsx": "4.23.11",
|
|
60
59
|
"typescript": "*",
|
|
61
60
|
"typescript-eslint": "*",
|
|
62
61
|
"vite": "^8.0.0",
|