brepjs-sheetmetal 0.3.1 → 0.4.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/README.md +7 -3
- package/dist/brepjs-sheetmetal.cjs +175 -95
- package/dist/brepjs-sheetmetal.js +176 -96
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -300,7 +300,10 @@ authored path. It reads only the B-rep:
|
|
|
300
300
|
other surface type (cone, sphere, spline, …) is reported `UNSUPPORTED_FACE` and skipped (the unfold never
|
|
301
301
|
silently invents a flat for an unrecognised face).
|
|
302
302
|
- **Pair flats**: the two large parallel planar faces of a panel, a sheet thickness apart, are the
|
|
303
|
-
mid-surface flat.
|
|
303
|
+
mid-surface flat. Two antiparallel faces count as opposed only when they face each other (an interior
|
|
304
|
+
point of one projects inside the other), so a flange's end cap and the adjacent flange's inner face, a
|
|
305
|
+
bend radius apart across a corner, never pair. Thickness is detected as the smallest opposed-planar-pair
|
|
306
|
+
gap.
|
|
304
307
|
- **Fit bends**: `fitCylinder(face)` recovers a bend's axis + radius + swept angle from sampled points and
|
|
305
308
|
normals. The axis direction is the common perpendicular of the sampled normals (every cylinder normal is
|
|
306
309
|
⟂ the axis), recovered as the sign-aligned average of cross products of non-parallel normal pairs; the
|
|
@@ -320,8 +323,9 @@ non-uniform thickness, conical/spline bend faces, non-manifold / non-sheet topol
|
|
|
320
323
|
fit a cylinder within tolerance all surface as warnings inside the `Ok` payload (or a clear `Err` when no
|
|
321
324
|
sheet-metal structure is detected at all, e.g. a bare sphere). The detection is validated by an oracle:
|
|
322
325
|
author a part, take only its `.solid`, run `unfoldSolid`, and confirm the detected unfold reproduces the
|
|
323
|
-
authored unfold's developed area, bend count, flat count, and total flat bbox (single bend,
|
|
324
|
-
U-channel
|
|
326
|
+
authored unfold's developed area, bend count, flat count, and total flat bbox with no warnings (single bend,
|
|
327
|
+
plus the L-bracket, U-channel, and four-sided tray at thicknesses 1, 1.5, 2, and 3 against a 2 mm inner
|
|
328
|
+
radius), reading only the solid, never the feature tree.
|
|
325
329
|
|
|
326
330
|
## Bend tables
|
|
327
331
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let brepjs = require("brepjs");
|
|
3
|
-
|
|
3
|
+
// src/materials.ts
|
|
4
4
|
var DEFAULT_K_FACTOR = .44;
|
|
5
5
|
function gauge(name, thickness, kFactor = DEFAULT_K_FACTOR) {
|
|
6
6
|
return {
|
|
@@ -38,8 +38,7 @@ var MATERIALS = {
|
|
|
38
38
|
function getMaterial(name) {
|
|
39
39
|
return MATERIALS[name];
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
//#region src/bendTableFns.ts
|
|
41
|
+
// src/bendTableFns.ts
|
|
43
42
|
var DEG_TO_RAD = Math.PI / 180;
|
|
44
43
|
var registry = /* @__PURE__ */ new Map();
|
|
45
44
|
/**
|
|
@@ -378,8 +377,7 @@ function ensureStarterTables() {
|
|
|
378
377
|
});
|
|
379
378
|
}
|
|
380
379
|
}
|
|
381
|
-
|
|
382
|
-
//#region src/allowanceFns.ts
|
|
380
|
+
// src/allowanceFns.ts
|
|
383
381
|
function validateRule(rule) {
|
|
384
382
|
if (!Number.isFinite(rule.innerRadius) || rule.innerRadius < 0) return (0, brepjs.err)((0, brepjs.validationError)("INVALID_RADIUS", `innerRadius must be a finite, non-negative number, got ${rule.innerRadius}`));
|
|
385
383
|
if (!Number.isFinite(rule.kFactor) || rule.kFactor < 0 || rule.kFactor > 1) return (0, brepjs.err)((0, brepjs.validationError)("INVALID_K_FACTOR", `kFactor must be in [0, 1], got ${rule.kFactor}`));
|
|
@@ -413,8 +411,7 @@ function neutralRadius(thickness, rule) {
|
|
|
413
411
|
if (!ruleCheck.ok) return ruleCheck;
|
|
414
412
|
return (0, brepjs.ok)(rule.innerRadius + rule.kFactor * thickness);
|
|
415
413
|
}
|
|
416
|
-
|
|
417
|
-
//#region src/featureTreeFns.ts
|
|
414
|
+
// src/featureTreeFns.ts
|
|
418
415
|
var ROOT_FLAT_ID = "root";
|
|
419
416
|
/**
|
|
420
417
|
* Build the feature graph for an authored part. Each flange contributes a flat
|
|
@@ -559,8 +556,7 @@ function featureTree(part, rootId = ROOT_FLAT_ID) {
|
|
|
559
556
|
if (!graph.ok) return graph;
|
|
560
557
|
return buildFeatureTree(graph.value, rootId);
|
|
561
558
|
}
|
|
562
|
-
|
|
563
|
-
//#region src/internal.ts
|
|
559
|
+
// src/internal.ts
|
|
564
560
|
/**
|
|
565
561
|
* OCCT boolean fuse returns a compound wrapping the unioned solid. Extract the
|
|
566
562
|
* single solid so a part's cached shape is a true `Solid` (clean STEP/GLB
|
|
@@ -583,8 +579,7 @@ function sideFromAxisDir(axisDir) {
|
|
|
583
579
|
if (Math.abs(axisDir[1]) > .5) return "xmax";
|
|
584
580
|
if (Math.abs(axisDir[0]) > .5) return "ymax";
|
|
585
581
|
}
|
|
586
|
-
|
|
587
|
-
//#region src/unfoldFns.ts
|
|
582
|
+
// src/unfoldFns.ts
|
|
588
583
|
/**
|
|
589
584
|
* Tree-driven analytic unfold of an arbitrary straight-bend part into its flat
|
|
590
585
|
* pattern. Walks the feature tree in BFS order placing every flat in the flat
|
|
@@ -1291,8 +1286,62 @@ function reportFromLayout(layout) {
|
|
|
1291
1286
|
totalFlatSize: [maxX - minX, maxY - minY]
|
|
1292
1287
|
};
|
|
1293
1288
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1289
|
+
// \0@oxc-project+runtime@0.155.0/helpers/esm/usingCtx.js
|
|
1290
|
+
function _usingCtx() {
|
|
1291
|
+
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
1292
|
+
var n = Error();
|
|
1293
|
+
return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
|
|
1294
|
+
}, e = {}, n = [];
|
|
1295
|
+
function using(r, e) {
|
|
1296
|
+
if (null != e) {
|
|
1297
|
+
if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
|
|
1298
|
+
if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
|
|
1299
|
+
if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
|
|
1300
|
+
if ("function" != typeof o) throw new TypeError("Object is not disposable.");
|
|
1301
|
+
t && (o = function o() {
|
|
1302
|
+
try {
|
|
1303
|
+
t.call(e);
|
|
1304
|
+
} catch (r) {
|
|
1305
|
+
return Promise.reject(r);
|
|
1306
|
+
}
|
|
1307
|
+
}), n.push({
|
|
1308
|
+
v: e,
|
|
1309
|
+
d: o,
|
|
1310
|
+
a: r
|
|
1311
|
+
});
|
|
1312
|
+
} else r && n.push({
|
|
1313
|
+
d: e,
|
|
1314
|
+
a: r
|
|
1315
|
+
});
|
|
1316
|
+
return e;
|
|
1317
|
+
}
|
|
1318
|
+
return {
|
|
1319
|
+
e,
|
|
1320
|
+
u: using.bind(null, !1),
|
|
1321
|
+
a: using.bind(null, !0),
|
|
1322
|
+
d: function d() {
|
|
1323
|
+
var o, t = this.e, s = 0;
|
|
1324
|
+
function next() {
|
|
1325
|
+
for (; o = n.pop();) try {
|
|
1326
|
+
if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
|
|
1327
|
+
if (o.d) {
|
|
1328
|
+
var r = o.d.call(o.v);
|
|
1329
|
+
if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
|
|
1330
|
+
} else s |= 1;
|
|
1331
|
+
} catch (r) {
|
|
1332
|
+
return err(r);
|
|
1333
|
+
}
|
|
1334
|
+
if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
|
|
1335
|
+
if (t !== e) throw t;
|
|
1336
|
+
}
|
|
1337
|
+
function err(n) {
|
|
1338
|
+
return t = t !== e ? new r(n, t) : n, next();
|
|
1339
|
+
}
|
|
1340
|
+
return next();
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
// src/foreignUnfoldFns.ts
|
|
1296
1345
|
/** Default neutral-axis K-factor for a purely geometric unfold (mid-surface). */
|
|
1297
1346
|
var DEFAULT_FOREIGN_K = .5;
|
|
1298
1347
|
/** Relative tolerance for the cylinder-fit residual (fraction of the radius). */
|
|
@@ -1602,10 +1651,11 @@ function unfoldForeignSolid(solid, opts) {
|
|
|
1602
1651
|
message: `UNSUPPORTED_FACE: surface type '${t.value}' is not a planar panel or cylindrical bend; the unfold ignores it and may be incomplete`
|
|
1603
1652
|
});
|
|
1604
1653
|
}
|
|
1605
|
-
const
|
|
1654
|
+
const planar = planarFaces.map(describePlanar);
|
|
1655
|
+
const thicknessResult = detectThickness(planar);
|
|
1606
1656
|
if (!thicknessResult.ok) return thicknessResult;
|
|
1607
1657
|
const thickness = thicknessResult.value;
|
|
1608
|
-
const flats = pairFlats(
|
|
1658
|
+
const flats = pairFlats(planar, thickness, warnings);
|
|
1609
1659
|
if (flats.length === 0) return (0, brepjs.err)((0, brepjs.validationError)("NO_FLATS", "no planar panel pairs detected; not a recognizable sheet-metal solid"));
|
|
1610
1660
|
const layout = layoutForeign(flats, buildBendGraph(flats, pairBends(cylFaces, warnings), warnings), thickness, kFactor, warnings);
|
|
1611
1661
|
if (!layout.ok) return layout;
|
|
@@ -1615,59 +1665,106 @@ function unfoldForeignSolid(solid, opts) {
|
|
|
1615
1665
|
warnings
|
|
1616
1666
|
});
|
|
1617
1667
|
}
|
|
1668
|
+
function describePlanar(face) {
|
|
1669
|
+
const center = (0, brepjs.faceCenter)(face);
|
|
1670
|
+
const n = 4;
|
|
1671
|
+
const candidates = [center];
|
|
1672
|
+
for (let i = 0; i < n; i += 1) for (let j = 0; j < n; j += 1) candidates.push((0, brepjs.pointOnSurface)(face, (i + .5) / n, (j + .5) / n));
|
|
1673
|
+
const interior = candidates.filter((p) => liesOnFace(face, p));
|
|
1674
|
+
return {
|
|
1675
|
+
face,
|
|
1676
|
+
normal: faceNormal(face),
|
|
1677
|
+
center,
|
|
1678
|
+
interior
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
/** True if `point` (already on the face's surface) lies within the face's trimmed boundary. */
|
|
1682
|
+
function liesOnFace(face, point) {
|
|
1683
|
+
try {
|
|
1684
|
+
var _usingCtx$1 = _usingCtx();
|
|
1685
|
+
const probe = _usingCtx$1.u((0, brepjs.vertex)(point));
|
|
1686
|
+
const d = (0, brepjs.measureDistance)(probe, face);
|
|
1687
|
+
return d.ok && d.value < GEOM_TOL;
|
|
1688
|
+
} catch (_) {
|
|
1689
|
+
_usingCtx$1.e = _;
|
|
1690
|
+
} finally {
|
|
1691
|
+
_usingCtx$1.d();
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
/** Normal-direction gap between two planar faces, or `null` unless they are antiparallel. */
|
|
1695
|
+
function antiparallelGap(a, b) {
|
|
1696
|
+
if ((0, brepjs.vecDot)(a.normal, b.normal) > -.99) return null;
|
|
1697
|
+
return Math.abs((0, brepjs.vecDot)((0, brepjs.vecSub)(b.center, a.center), a.normal));
|
|
1698
|
+
}
|
|
1618
1699
|
/**
|
|
1619
|
-
*
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1700
|
+
* Antiparallel faces are the two sides of one panel only if they face each other:
|
|
1701
|
+
* an interior point of one projects inside the other. The normal-direction gap
|
|
1702
|
+
* alone also matches faces that merely sit on parallel planes, e.g. a flange's end
|
|
1703
|
+
* cap and the adjacent flange's inner face across a corner. Their gap is the bend's
|
|
1704
|
+
* inner radius, which passes for the sheet thickness as T approaches or exceeds R.
|
|
1705
|
+
*/
|
|
1706
|
+
function facesOppose(a, b) {
|
|
1707
|
+
return b.interior.some((p) => liesOnFace(a.face, projectToPlane(p, a.normal, a.center))) || a.interior.some((p) => liesOnFace(b.face, projectToPlane(p, b.normal, b.center)));
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Detect the sheet thickness as the smallest gap between two antiparallel planar
|
|
1711
|
+
* faces that oppose each other (the two faces of one flat panel).
|
|
1622
1712
|
*/
|
|
1623
1713
|
function detectThickness(planar) {
|
|
1624
|
-
const
|
|
1714
|
+
const pairs = [];
|
|
1625
1715
|
for (let i = 0; i < planar.length; i += 1) for (let j = i + 1; j < planar.length; j += 1) {
|
|
1626
|
-
const
|
|
1627
|
-
const
|
|
1628
|
-
if (
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1716
|
+
const a = planar[i];
|
|
1717
|
+
const b = planar[j];
|
|
1718
|
+
if (a === void 0 || b === void 0) continue;
|
|
1719
|
+
const gap = antiparallelGap(a, b);
|
|
1720
|
+
if (gap !== null && gap > GEOM_TOL) pairs.push({
|
|
1721
|
+
gap,
|
|
1722
|
+
a,
|
|
1723
|
+
b
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
pairs.sort((p, q) => p.gap - q.gap);
|
|
1727
|
+
const thinnest = pairs.find((p) => facesOppose(p.a, p.b));
|
|
1728
|
+
if (thinnest === void 0) return (0, brepjs.err)((0, brepjs.validationError)("NO_THICKNESS", "could not detect a sheet thickness (no opposed planar pair)"));
|
|
1729
|
+
return (0, brepjs.ok)(thinnest.gap);
|
|
1730
|
+
}
|
|
1731
|
+
/** Unused face opposing `a` whose gap is nearest the sheet thickness, within tolerance. */
|
|
1732
|
+
function bestOpposedPartner(planar, i, thickness, used) {
|
|
1733
|
+
const a = planar[i];
|
|
1734
|
+
if (a === void 0) return null;
|
|
1735
|
+
const candidates = [];
|
|
1736
|
+
for (let j = 0; j < planar.length; j += 1) {
|
|
1737
|
+
const b = planar[j];
|
|
1738
|
+
if (j === i || used.has(j) || b === void 0) continue;
|
|
1739
|
+
const gap = antiparallelGap(a, b);
|
|
1740
|
+
if (gap === null) continue;
|
|
1741
|
+
const e = Math.abs(gap - thickness);
|
|
1742
|
+
if (e <= thickness * .5 + GEOM_TOL) candidates.push({
|
|
1743
|
+
j,
|
|
1744
|
+
e
|
|
1745
|
+
});
|
|
1636
1746
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1747
|
+
candidates.sort((p, q) => p.e - q.e);
|
|
1748
|
+
const match = candidates.find((c) => {
|
|
1749
|
+
const b = planar[c.j];
|
|
1750
|
+
return b !== void 0 && facesOppose(a, b);
|
|
1751
|
+
});
|
|
1752
|
+
return match === void 0 ? null : match.j;
|
|
1640
1753
|
}
|
|
1641
1754
|
/**
|
|
1642
|
-
* Pair planar faces into flat panels: each panel is two
|
|
1643
|
-
* `thickness` apart
|
|
1755
|
+
* Pair planar faces into flat panels: each panel is two antiparallel faces a
|
|
1756
|
+
* `thickness` apart that oppose each other. Greedy nearest-match by gap.
|
|
1644
1757
|
*/
|
|
1645
1758
|
function pairFlats(planar, thickness, warnings) {
|
|
1646
1759
|
const used = /* @__PURE__ */ new Set();
|
|
1647
1760
|
const flats = [];
|
|
1648
1761
|
for (let i = 0; i < planar.length; i += 1) {
|
|
1649
1762
|
if (used.has(i)) continue;
|
|
1650
|
-
const
|
|
1651
|
-
if (
|
|
1652
|
-
const
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
let bestErr = Infinity;
|
|
1656
|
-
for (let j = 0; j < planar.length; j += 1) {
|
|
1657
|
-
if (j === i || used.has(j)) continue;
|
|
1658
|
-
const fb = planar[j];
|
|
1659
|
-
if (fb === void 0) continue;
|
|
1660
|
-
const nb = faceNormal(fb);
|
|
1661
|
-
if ((0, brepjs.vecDot)(na, nb) > -.99) continue;
|
|
1662
|
-
const cb = (0, brepjs.faceCenter)(fb);
|
|
1663
|
-
const gap = Math.abs((0, brepjs.vecDot)((0, brepjs.vecSub)(cb, ca), na));
|
|
1664
|
-
const e = Math.abs(gap - thickness);
|
|
1665
|
-
if (e < bestErr) {
|
|
1666
|
-
bestErr = e;
|
|
1667
|
-
best = j;
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
if (best < 0 || bestErr > thickness * .5 + GEOM_TOL) {
|
|
1763
|
+
const pa = planar[i];
|
|
1764
|
+
if (pa === void 0) continue;
|
|
1765
|
+
const fa = pa.face;
|
|
1766
|
+
const best = bestOpposedPartner(planar, i, thickness, used);
|
|
1767
|
+
if (best === null) {
|
|
1671
1768
|
const db = (0, brepjs.getBounds)(fa);
|
|
1672
1769
|
const ext = [
|
|
1673
1770
|
db.xMax - db.xMin,
|
|
@@ -1681,7 +1778,9 @@ function pairFlats(planar, thickness, warnings) {
|
|
|
1681
1778
|
});
|
|
1682
1779
|
continue;
|
|
1683
1780
|
}
|
|
1684
|
-
const
|
|
1781
|
+
const pb = planar[best];
|
|
1782
|
+
if (pb === void 0) continue;
|
|
1783
|
+
const fb = pb.face;
|
|
1685
1784
|
used.add(i);
|
|
1686
1785
|
used.add(best);
|
|
1687
1786
|
const areaA = (0, brepjs.measureArea)(fa);
|
|
@@ -1704,8 +1803,8 @@ function pairFlats(planar, thickness, warnings) {
|
|
|
1704
1803
|
flats.push({
|
|
1705
1804
|
id: `flat-${flats.length}`,
|
|
1706
1805
|
faces: [fa, fb],
|
|
1707
|
-
normal:
|
|
1708
|
-
center: (0, brepjs.vecScale)((0, brepjs.vecAdd)(
|
|
1806
|
+
normal: pa.normal,
|
|
1807
|
+
center: (0, brepjs.vecScale)((0, brepjs.vecAdd)(pa.center, pb.center), .5),
|
|
1709
1808
|
area
|
|
1710
1809
|
});
|
|
1711
1810
|
}
|
|
@@ -2181,8 +2280,7 @@ function bendDirection(bend, parent) {
|
|
|
2181
2280
|
function faceNormal(face) {
|
|
2182
2281
|
return (0, brepjs.vecNormalize)((0, brepjs.normalAt)(face, (0, brepjs.faceCenter)(face)));
|
|
2183
2282
|
}
|
|
2184
|
-
|
|
2185
|
-
//#region src/authorFns.ts
|
|
2283
|
+
// src/authorFns.ts
|
|
2186
2284
|
/**
|
|
2187
2285
|
* Stable edge reference for flange attachment. The base flat is `face-0`; every
|
|
2188
2286
|
* flange face is `face-<n+1>` in authoring order. The leading edge of a flat is
|
|
@@ -2799,8 +2897,7 @@ function buildFlangeFlat(axisOrigin, thetaDeg, width, thickness, length, directi
|
|
|
2799
2897
|
]
|
|
2800
2898
|
});
|
|
2801
2899
|
}
|
|
2802
|
-
|
|
2803
|
-
//#region src/reliefFns.ts
|
|
2900
|
+
// src/reliefFns.ts
|
|
2804
2901
|
var EPS$6 = 1e-6;
|
|
2805
2902
|
var Z_AXIS$1 = [
|
|
2806
2903
|
0,
|
|
@@ -3164,8 +3261,7 @@ function toPt2$1(v) {
|
|
|
3164
3261
|
function dist2(a, b) {
|
|
3165
3262
|
return Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
3166
3263
|
}
|
|
3167
|
-
|
|
3168
|
-
//#region src/cutoutFns.ts
|
|
3264
|
+
// src/cutoutFns.ts
|
|
3169
3265
|
var EPS$5 = 1e-6;
|
|
3170
3266
|
/** A hair of over-cut so the tool pokes through both faces cleanly. */
|
|
3171
3267
|
var HAIR$1 = .05;
|
|
@@ -3393,8 +3489,7 @@ function polygonArea(loop) {
|
|
|
3393
3489
|
}
|
|
3394
3490
|
return Math.abs(sum) / 2;
|
|
3395
3491
|
}
|
|
3396
|
-
|
|
3397
|
-
//#region src/tabFns.ts
|
|
3492
|
+
// src/tabFns.ts
|
|
3398
3493
|
var EPS$4 = 1e-6;
|
|
3399
3494
|
/**
|
|
3400
3495
|
* Fuse a rectangular tab onto a named flat region's edge — additive material (the
|
|
@@ -3536,8 +3631,7 @@ function devRect(local, dev) {
|
|
|
3536
3631
|
y1
|
|
3537
3632
|
];
|
|
3538
3633
|
}
|
|
3539
|
-
|
|
3540
|
-
//#region src/formFns.ts
|
|
3634
|
+
// src/formFns.ts
|
|
3541
3635
|
var EPS$3 = 1e-6;
|
|
3542
3636
|
var HAIR = .05;
|
|
3543
3637
|
var CIRCLE_SEGMENTS = 48;
|
|
@@ -3838,8 +3932,7 @@ function circleLocal(c, r) {
|
|
|
3838
3932
|
}
|
|
3839
3933
|
return pts;
|
|
3840
3934
|
}
|
|
3841
|
-
|
|
3842
|
-
//#region src/validateFns.ts
|
|
3935
|
+
// src/validateFns.ts
|
|
3843
3936
|
var OVERLAP_TOL = 1e-6;
|
|
3844
3937
|
/**
|
|
3845
3938
|
* Manufacturability checks for an authored sheet-metal part. Returns a list of
|
|
@@ -4021,8 +4114,7 @@ function pairKey(a, b) {
|
|
|
4021
4114
|
function boundsOverlap(a, b) {
|
|
4022
4115
|
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;
|
|
4023
4116
|
}
|
|
4024
|
-
|
|
4025
|
-
//#region src/foldFns.ts
|
|
4117
|
+
// src/foldFns.ts
|
|
4026
4118
|
/**
|
|
4027
4119
|
* Fold a flat pattern up into a 3D part. Each {@link FoldRegion} folds about its
|
|
4028
4120
|
* fold line off its parent region, exactly inverting {@link unfold}: folding by an
|
|
@@ -4680,8 +4772,7 @@ function uniqueSorted(values) {
|
|
|
4680
4772
|
}
|
|
4681
4773
|
return out;
|
|
4682
4774
|
}
|
|
4683
|
-
|
|
4684
|
-
//#region src/miterFns.ts
|
|
4775
|
+
// src/miterFns.ts
|
|
4685
4776
|
/**
|
|
4686
4777
|
* General miter primitive: cut a sheet-metal part by a plane, removing the
|
|
4687
4778
|
* material on the `+normal` side. The tool is a half-space block sized to the
|
|
@@ -4803,8 +4894,7 @@ function alignZTo(shape, target) {
|
|
|
4803
4894
|
axis
|
|
4804
4895
|
});
|
|
4805
4896
|
}
|
|
4806
|
-
|
|
4807
|
-
//#region src/contourFlangeFns.ts
|
|
4897
|
+
// src/contourFlangeFns.ts
|
|
4808
4898
|
/**
|
|
4809
4899
|
* Author a contour flange: an OPEN 2D profile (alternating line/arc segments) swept
|
|
4810
4900
|
* along one straight edge of the base flat. Each arc is a cylindrical bend, each
|
|
@@ -5273,8 +5363,7 @@ function matrixToAxisAngle(m) {
|
|
|
5273
5363
|
angleDeg
|
|
5274
5364
|
};
|
|
5275
5365
|
}
|
|
5276
|
-
|
|
5277
|
-
//#region src/loftedFlangeFns.ts
|
|
5366
|
+
// src/loftedFlangeFns.ts
|
|
5278
5367
|
/**
|
|
5279
5368
|
* Relative out-of-plane tolerance for the per-quad developability test: a quad is
|
|
5280
5369
|
* twisted (non-developable) when its far corner sits off the diagonal-triangle plane
|
|
@@ -5586,8 +5675,7 @@ function closedLoopWire(loop) {
|
|
|
5586
5675
|
}
|
|
5587
5676
|
return (0, brepjs.wireLoop)(edges);
|
|
5588
5677
|
}
|
|
5589
|
-
|
|
5590
|
-
//#region src/hemFns.ts
|
|
5678
|
+
// src/hemFns.ts
|
|
5591
5679
|
/**
|
|
5592
5680
|
* A small clearance baked into a CLOSED hem's curl radius. A true closed hem folds
|
|
5593
5681
|
* the return flat against the parent with zero gap; modelling that exactly makes the
|
|
@@ -5780,8 +5868,7 @@ function splitAngle(totalDeg) {
|
|
|
5780
5868
|
const per = totalDeg / n;
|
|
5781
5869
|
return Array.from({ length: n }, () => per);
|
|
5782
5870
|
}
|
|
5783
|
-
|
|
5784
|
-
//#region src/jogFns.ts
|
|
5871
|
+
// src/jogFns.ts
|
|
5785
5872
|
/**
|
|
5786
5873
|
* Author a jog (joggle): two opposite bends (`+θ` then `−θ`) that step a flat by
|
|
5787
5874
|
* `offsetHeight` perpendicular to its plane, then continue parallel past the step.
|
|
@@ -5940,8 +6027,7 @@ function jogId(spec) {
|
|
|
5940
6027
|
function resolveRegion(region) {
|
|
5941
6028
|
return region === "base" || region === "face-0" ? ROOT_FLAT_ID : region;
|
|
5942
6029
|
}
|
|
5943
|
-
|
|
5944
|
-
//#region src/dxfFns.ts
|
|
6030
|
+
// src/dxfFns.ts
|
|
5945
6031
|
var DEFAULT_TEXT_HEIGHT = 2.5;
|
|
5946
6032
|
var LAYER_OUTLINE = "OUTLINE";
|
|
5947
6033
|
var LAYER_BEND_UP = "BEND_UP";
|
|
@@ -6150,8 +6236,7 @@ function pathPoints(wire) {
|
|
|
6150
6236
|
function toPt2(v) {
|
|
6151
6237
|
return [v[0], v[1]];
|
|
6152
6238
|
}
|
|
6153
|
-
|
|
6154
|
-
//#region src/polygonFns.ts
|
|
6239
|
+
// src/polygonFns.ts
|
|
6155
6240
|
/**
|
|
6156
6241
|
* 2D simple-polygon geometry for true-shape (no-fit-polygon) nesting: an ordered
|
|
6157
6242
|
* vertex loop plus the overlap predicate that is the correctness backbone of the
|
|
@@ -6394,8 +6479,7 @@ function pointSegmentDistance(p, a, b) {
|
|
|
6394
6479
|
const dy = p[1] - (a[1] + t * vy);
|
|
6395
6480
|
return Math.hypot(dx, dy);
|
|
6396
6481
|
}
|
|
6397
|
-
|
|
6398
|
-
//#region src/nestFns.ts
|
|
6482
|
+
// src/nestFns.ts
|
|
6399
6483
|
var EPS = 1e-9;
|
|
6400
6484
|
/**
|
|
6401
6485
|
* Bounding-box nest: arrange developed flat patterns onto stock sheets to reduce
|
|
@@ -6861,8 +6945,7 @@ function nestToDXF$1(result, patterns, sheetIndex, options) {
|
|
|
6861
6945
|
}
|
|
6862
6946
|
return multiPatternToDXF(placed, options ?? {});
|
|
6863
6947
|
}
|
|
6864
|
-
|
|
6865
|
-
//#region src/reportFns.ts
|
|
6948
|
+
// src/reportFns.ts
|
|
6866
6949
|
/**
|
|
6867
6950
|
* Build the bend-table JSON report directly from an authored part. Walks the
|
|
6868
6951
|
* same tree layout the unfold consumes so the per-bend values and the total flat
|
|
@@ -6905,8 +6988,7 @@ function reportFromUnfold(result) {
|
|
|
6905
6988
|
function reportToJSON(report) {
|
|
6906
6989
|
return JSON.stringify(report, null, 2);
|
|
6907
6990
|
}
|
|
6908
|
-
|
|
6909
|
-
//#region src/api.ts
|
|
6991
|
+
// src/api.ts
|
|
6910
6992
|
/** Author a straight-bend part: a base flat plus folded-up flanges. */
|
|
6911
6993
|
function author(spec) {
|
|
6912
6994
|
return authorPart(spec);
|
|
@@ -7068,8 +7150,7 @@ function bendTable(id) {
|
|
|
7068
7150
|
function resolveAllowance(rule, angleDeg, thickness, onWarning) {
|
|
7069
7151
|
return resolveBendAllowance(rule, angleDeg, thickness, onWarning);
|
|
7070
7152
|
}
|
|
7071
|
-
|
|
7072
|
-
//#region src/facade.ts
|
|
7153
|
+
// src/facade.ts
|
|
7073
7154
|
/** Thrown by the fluent facade when an underlying `Result<T>` is an `Err`. */
|
|
7074
7155
|
var SheetMetalError = class extends Error {
|
|
7075
7156
|
code;
|
|
@@ -7339,7 +7420,6 @@ function foldFlat(input) {
|
|
|
7339
7420
|
function fromSolid(solid, opts) {
|
|
7340
7421
|
return new ForeignSolidHandle(solid, opts);
|
|
7341
7422
|
}
|
|
7342
|
-
//#endregion
|
|
7343
7423
|
exports.ALUMINUM_GAUGES = ALUMINUM_GAUGES;
|
|
7344
7424
|
exports.DEFAULT_K_FACTOR = DEFAULT_K_FACTOR;
|
|
7345
7425
|
exports.ForeignSolidHandle = ForeignSolidHandle;
|
|
@@ -1,5 +1,5 @@
|
|
|
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
|
-
|
|
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, measureDistance, measureVolume, normalAt, ok, pointOnSurface, rotate, sharedEdges, translate, validationError, vecAdd, vecCross, vecDot, vecLength, vecNormalize, vecScale, vecSub, vertex, wire, wireLoop } from "brepjs";
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -1290,8 +1285,62 @@ function reportFromLayout(layout) {
|
|
|
1290
1285
|
totalFlatSize: [maxX - minX, maxY - minY]
|
|
1291
1286
|
};
|
|
1292
1287
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1288
|
+
// \0@oxc-project+runtime@0.155.0/helpers/esm/usingCtx.js
|
|
1289
|
+
function _usingCtx() {
|
|
1290
|
+
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
1291
|
+
var n = Error();
|
|
1292
|
+
return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
|
|
1293
|
+
}, e = {}, n = [];
|
|
1294
|
+
function using(r, e) {
|
|
1295
|
+
if (null != e) {
|
|
1296
|
+
if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
|
|
1297
|
+
if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
|
|
1298
|
+
if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
|
|
1299
|
+
if ("function" != typeof o) throw new TypeError("Object is not disposable.");
|
|
1300
|
+
t && (o = function o() {
|
|
1301
|
+
try {
|
|
1302
|
+
t.call(e);
|
|
1303
|
+
} catch (r) {
|
|
1304
|
+
return Promise.reject(r);
|
|
1305
|
+
}
|
|
1306
|
+
}), n.push({
|
|
1307
|
+
v: e,
|
|
1308
|
+
d: o,
|
|
1309
|
+
a: r
|
|
1310
|
+
});
|
|
1311
|
+
} else r && n.push({
|
|
1312
|
+
d: e,
|
|
1313
|
+
a: r
|
|
1314
|
+
});
|
|
1315
|
+
return e;
|
|
1316
|
+
}
|
|
1317
|
+
return {
|
|
1318
|
+
e,
|
|
1319
|
+
u: using.bind(null, !1),
|
|
1320
|
+
a: using.bind(null, !0),
|
|
1321
|
+
d: function d() {
|
|
1322
|
+
var o, t = this.e, s = 0;
|
|
1323
|
+
function next() {
|
|
1324
|
+
for (; o = n.pop();) try {
|
|
1325
|
+
if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
|
|
1326
|
+
if (o.d) {
|
|
1327
|
+
var r = o.d.call(o.v);
|
|
1328
|
+
if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
|
|
1329
|
+
} else s |= 1;
|
|
1330
|
+
} catch (r) {
|
|
1331
|
+
return err(r);
|
|
1332
|
+
}
|
|
1333
|
+
if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
|
|
1334
|
+
if (t !== e) throw t;
|
|
1335
|
+
}
|
|
1336
|
+
function err(n) {
|
|
1337
|
+
return t = t !== e ? new r(n, t) : n, next();
|
|
1338
|
+
}
|
|
1339
|
+
return next();
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
// src/foreignUnfoldFns.ts
|
|
1295
1344
|
/** Default neutral-axis K-factor for a purely geometric unfold (mid-surface). */
|
|
1296
1345
|
var DEFAULT_FOREIGN_K = .5;
|
|
1297
1346
|
/** Relative tolerance for the cylinder-fit residual (fraction of the radius). */
|
|
@@ -1601,10 +1650,11 @@ function unfoldForeignSolid(solid, opts) {
|
|
|
1601
1650
|
message: `UNSUPPORTED_FACE: surface type '${t.value}' is not a planar panel or cylindrical bend; the unfold ignores it and may be incomplete`
|
|
1602
1651
|
});
|
|
1603
1652
|
}
|
|
1604
|
-
const
|
|
1653
|
+
const planar = planarFaces.map(describePlanar);
|
|
1654
|
+
const thicknessResult = detectThickness(planar);
|
|
1605
1655
|
if (!thicknessResult.ok) return thicknessResult;
|
|
1606
1656
|
const thickness = thicknessResult.value;
|
|
1607
|
-
const flats = pairFlats(
|
|
1657
|
+
const flats = pairFlats(planar, thickness, warnings);
|
|
1608
1658
|
if (flats.length === 0) return err(validationError("NO_FLATS", "no planar panel pairs detected; not a recognizable sheet-metal solid"));
|
|
1609
1659
|
const layout = layoutForeign(flats, buildBendGraph(flats, pairBends(cylFaces, warnings), warnings), thickness, kFactor, warnings);
|
|
1610
1660
|
if (!layout.ok) return layout;
|
|
@@ -1614,59 +1664,106 @@ function unfoldForeignSolid(solid, opts) {
|
|
|
1614
1664
|
warnings
|
|
1615
1665
|
});
|
|
1616
1666
|
}
|
|
1667
|
+
function describePlanar(face) {
|
|
1668
|
+
const center = faceCenter(face);
|
|
1669
|
+
const n = 4;
|
|
1670
|
+
const candidates = [center];
|
|
1671
|
+
for (let i = 0; i < n; i += 1) for (let j = 0; j < n; j += 1) candidates.push(pointOnSurface(face, (i + .5) / n, (j + .5) / n));
|
|
1672
|
+
const interior = candidates.filter((p) => liesOnFace(face, p));
|
|
1673
|
+
return {
|
|
1674
|
+
face,
|
|
1675
|
+
normal: faceNormal(face),
|
|
1676
|
+
center,
|
|
1677
|
+
interior
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
/** True if `point` (already on the face's surface) lies within the face's trimmed boundary. */
|
|
1681
|
+
function liesOnFace(face, point) {
|
|
1682
|
+
try {
|
|
1683
|
+
var _usingCtx$1 = _usingCtx();
|
|
1684
|
+
const probe = _usingCtx$1.u(vertex(point));
|
|
1685
|
+
const d = measureDistance(probe, face);
|
|
1686
|
+
return d.ok && d.value < GEOM_TOL;
|
|
1687
|
+
} catch (_) {
|
|
1688
|
+
_usingCtx$1.e = _;
|
|
1689
|
+
} finally {
|
|
1690
|
+
_usingCtx$1.d();
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
/** Normal-direction gap between two planar faces, or `null` unless they are antiparallel. */
|
|
1694
|
+
function antiparallelGap(a, b) {
|
|
1695
|
+
if (vecDot(a.normal, b.normal) > -.99) return null;
|
|
1696
|
+
return Math.abs(vecDot(vecSub(b.center, a.center), a.normal));
|
|
1697
|
+
}
|
|
1617
1698
|
/**
|
|
1618
|
-
*
|
|
1619
|
-
*
|
|
1620
|
-
*
|
|
1699
|
+
* Antiparallel faces are the two sides of one panel only if they face each other:
|
|
1700
|
+
* an interior point of one projects inside the other. The normal-direction gap
|
|
1701
|
+
* alone also matches faces that merely sit on parallel planes, e.g. a flange's end
|
|
1702
|
+
* cap and the adjacent flange's inner face across a corner. Their gap is the bend's
|
|
1703
|
+
* inner radius, which passes for the sheet thickness as T approaches or exceeds R.
|
|
1704
|
+
*/
|
|
1705
|
+
function facesOppose(a, b) {
|
|
1706
|
+
return b.interior.some((p) => liesOnFace(a.face, projectToPlane(p, a.normal, a.center))) || a.interior.some((p) => liesOnFace(b.face, projectToPlane(p, b.normal, b.center)));
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Detect the sheet thickness as the smallest gap between two antiparallel planar
|
|
1710
|
+
* faces that oppose each other (the two faces of one flat panel).
|
|
1621
1711
|
*/
|
|
1622
1712
|
function detectThickness(planar) {
|
|
1623
|
-
const
|
|
1713
|
+
const pairs = [];
|
|
1624
1714
|
for (let i = 0; i < planar.length; i += 1) for (let j = i + 1; j < planar.length; j += 1) {
|
|
1625
|
-
const
|
|
1626
|
-
const
|
|
1627
|
-
if (
|
|
1628
|
-
const
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1715
|
+
const a = planar[i];
|
|
1716
|
+
const b = planar[j];
|
|
1717
|
+
if (a === void 0 || b === void 0) continue;
|
|
1718
|
+
const gap = antiparallelGap(a, b);
|
|
1719
|
+
if (gap !== null && gap > GEOM_TOL) pairs.push({
|
|
1720
|
+
gap,
|
|
1721
|
+
a,
|
|
1722
|
+
b
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
pairs.sort((p, q) => p.gap - q.gap);
|
|
1726
|
+
const thinnest = pairs.find((p) => facesOppose(p.a, p.b));
|
|
1727
|
+
if (thinnest === void 0) return err(validationError("NO_THICKNESS", "could not detect a sheet thickness (no opposed planar pair)"));
|
|
1728
|
+
return ok(thinnest.gap);
|
|
1729
|
+
}
|
|
1730
|
+
/** Unused face opposing `a` whose gap is nearest the sheet thickness, within tolerance. */
|
|
1731
|
+
function bestOpposedPartner(planar, i, thickness, used) {
|
|
1732
|
+
const a = planar[i];
|
|
1733
|
+
if (a === void 0) return null;
|
|
1734
|
+
const candidates = [];
|
|
1735
|
+
for (let j = 0; j < planar.length; j += 1) {
|
|
1736
|
+
const b = planar[j];
|
|
1737
|
+
if (j === i || used.has(j) || b === void 0) continue;
|
|
1738
|
+
const gap = antiparallelGap(a, b);
|
|
1739
|
+
if (gap === null) continue;
|
|
1740
|
+
const e = Math.abs(gap - thickness);
|
|
1741
|
+
if (e <= thickness * .5 + GEOM_TOL) candidates.push({
|
|
1742
|
+
j,
|
|
1743
|
+
e
|
|
1744
|
+
});
|
|
1635
1745
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1746
|
+
candidates.sort((p, q) => p.e - q.e);
|
|
1747
|
+
const match = candidates.find((c) => {
|
|
1748
|
+
const b = planar[c.j];
|
|
1749
|
+
return b !== void 0 && facesOppose(a, b);
|
|
1750
|
+
});
|
|
1751
|
+
return match === void 0 ? null : match.j;
|
|
1639
1752
|
}
|
|
1640
1753
|
/**
|
|
1641
|
-
* Pair planar faces into flat panels: each panel is two
|
|
1642
|
-
* `thickness` apart
|
|
1754
|
+
* Pair planar faces into flat panels: each panel is two antiparallel faces a
|
|
1755
|
+
* `thickness` apart that oppose each other. Greedy nearest-match by gap.
|
|
1643
1756
|
*/
|
|
1644
1757
|
function pairFlats(planar, thickness, warnings) {
|
|
1645
1758
|
const used = /* @__PURE__ */ new Set();
|
|
1646
1759
|
const flats = [];
|
|
1647
1760
|
for (let i = 0; i < planar.length; i += 1) {
|
|
1648
1761
|
if (used.has(i)) continue;
|
|
1649
|
-
const
|
|
1650
|
-
if (
|
|
1651
|
-
const
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
let bestErr = Infinity;
|
|
1655
|
-
for (let j = 0; j < planar.length; j += 1) {
|
|
1656
|
-
if (j === i || used.has(j)) continue;
|
|
1657
|
-
const fb = planar[j];
|
|
1658
|
-
if (fb === void 0) continue;
|
|
1659
|
-
const nb = faceNormal(fb);
|
|
1660
|
-
if (vecDot(na, nb) > -.99) continue;
|
|
1661
|
-
const cb = faceCenter(fb);
|
|
1662
|
-
const gap = Math.abs(vecDot(vecSub(cb, ca), na));
|
|
1663
|
-
const e = Math.abs(gap - thickness);
|
|
1664
|
-
if (e < bestErr) {
|
|
1665
|
-
bestErr = e;
|
|
1666
|
-
best = j;
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
if (best < 0 || bestErr > thickness * .5 + GEOM_TOL) {
|
|
1762
|
+
const pa = planar[i];
|
|
1763
|
+
if (pa === void 0) continue;
|
|
1764
|
+
const fa = pa.face;
|
|
1765
|
+
const best = bestOpposedPartner(planar, i, thickness, used);
|
|
1766
|
+
if (best === null) {
|
|
1670
1767
|
const db = getBounds(fa);
|
|
1671
1768
|
const ext = [
|
|
1672
1769
|
db.xMax - db.xMin,
|
|
@@ -1680,7 +1777,9 @@ function pairFlats(planar, thickness, warnings) {
|
|
|
1680
1777
|
});
|
|
1681
1778
|
continue;
|
|
1682
1779
|
}
|
|
1683
|
-
const
|
|
1780
|
+
const pb = planar[best];
|
|
1781
|
+
if (pb === void 0) continue;
|
|
1782
|
+
const fb = pb.face;
|
|
1684
1783
|
used.add(i);
|
|
1685
1784
|
used.add(best);
|
|
1686
1785
|
const areaA = measureArea(fa);
|
|
@@ -1703,8 +1802,8 @@ function pairFlats(planar, thickness, warnings) {
|
|
|
1703
1802
|
flats.push({
|
|
1704
1803
|
id: `flat-${flats.length}`,
|
|
1705
1804
|
faces: [fa, fb],
|
|
1706
|
-
normal:
|
|
1707
|
-
center: vecScale(vecAdd(
|
|
1805
|
+
normal: pa.normal,
|
|
1806
|
+
center: vecScale(vecAdd(pa.center, pb.center), .5),
|
|
1708
1807
|
area
|
|
1709
1808
|
});
|
|
1710
1809
|
}
|
|
@@ -2180,8 +2279,7 @@ function bendDirection(bend, parent) {
|
|
|
2180
2279
|
function faceNormal(face) {
|
|
2181
2280
|
return vecNormalize(normalAt(face, faceCenter(face)));
|
|
2182
2281
|
}
|
|
2183
|
-
|
|
2184
|
-
//#region src/authorFns.ts
|
|
2282
|
+
// src/authorFns.ts
|
|
2185
2283
|
/**
|
|
2186
2284
|
* Stable edge reference for flange attachment. The base flat is `face-0`; every
|
|
2187
2285
|
* flange face is `face-<n+1>` in authoring order. The leading edge of a flat is
|
|
@@ -2798,8 +2896,7 @@ function buildFlangeFlat(axisOrigin, thetaDeg, width, thickness, length, directi
|
|
|
2798
2896
|
]
|
|
2799
2897
|
});
|
|
2800
2898
|
}
|
|
2801
|
-
|
|
2802
|
-
//#region src/reliefFns.ts
|
|
2899
|
+
// src/reliefFns.ts
|
|
2803
2900
|
var EPS$6 = 1e-6;
|
|
2804
2901
|
var Z_AXIS$1 = [
|
|
2805
2902
|
0,
|
|
@@ -3163,8 +3260,7 @@ function toPt2$1(v) {
|
|
|
3163
3260
|
function dist2(a, b) {
|
|
3164
3261
|
return Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
3165
3262
|
}
|
|
3166
|
-
|
|
3167
|
-
//#region src/cutoutFns.ts
|
|
3263
|
+
// src/cutoutFns.ts
|
|
3168
3264
|
var EPS$5 = 1e-6;
|
|
3169
3265
|
/** A hair of over-cut so the tool pokes through both faces cleanly. */
|
|
3170
3266
|
var HAIR$1 = .05;
|
|
@@ -3392,8 +3488,7 @@ function polygonArea(loop) {
|
|
|
3392
3488
|
}
|
|
3393
3489
|
return Math.abs(sum) / 2;
|
|
3394
3490
|
}
|
|
3395
|
-
|
|
3396
|
-
//#region src/tabFns.ts
|
|
3491
|
+
// src/tabFns.ts
|
|
3397
3492
|
var EPS$4 = 1e-6;
|
|
3398
3493
|
/**
|
|
3399
3494
|
* Fuse a rectangular tab onto a named flat region's edge — additive material (the
|
|
@@ -3535,8 +3630,7 @@ function devRect(local, dev) {
|
|
|
3535
3630
|
y1
|
|
3536
3631
|
];
|
|
3537
3632
|
}
|
|
3538
|
-
|
|
3539
|
-
//#region src/formFns.ts
|
|
3633
|
+
// src/formFns.ts
|
|
3540
3634
|
var EPS$3 = 1e-6;
|
|
3541
3635
|
var HAIR = .05;
|
|
3542
3636
|
var CIRCLE_SEGMENTS = 48;
|
|
@@ -3837,8 +3931,7 @@ function circleLocal(c, r) {
|
|
|
3837
3931
|
}
|
|
3838
3932
|
return pts;
|
|
3839
3933
|
}
|
|
3840
|
-
|
|
3841
|
-
//#region src/validateFns.ts
|
|
3934
|
+
// src/validateFns.ts
|
|
3842
3935
|
var OVERLAP_TOL = 1e-6;
|
|
3843
3936
|
/**
|
|
3844
3937
|
* Manufacturability checks for an authored sheet-metal part. Returns a list of
|
|
@@ -4020,8 +4113,7 @@ function pairKey(a, b) {
|
|
|
4020
4113
|
function boundsOverlap(a, b) {
|
|
4021
4114
|
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;
|
|
4022
4115
|
}
|
|
4023
|
-
|
|
4024
|
-
//#region src/foldFns.ts
|
|
4116
|
+
// src/foldFns.ts
|
|
4025
4117
|
/**
|
|
4026
4118
|
* Fold a flat pattern up into a 3D part. Each {@link FoldRegion} folds about its
|
|
4027
4119
|
* fold line off its parent region, exactly inverting {@link unfold}: folding by an
|
|
@@ -4679,8 +4771,7 @@ function uniqueSorted(values) {
|
|
|
4679
4771
|
}
|
|
4680
4772
|
return out;
|
|
4681
4773
|
}
|
|
4682
|
-
|
|
4683
|
-
//#region src/miterFns.ts
|
|
4774
|
+
// src/miterFns.ts
|
|
4684
4775
|
/**
|
|
4685
4776
|
* General miter primitive: cut a sheet-metal part by a plane, removing the
|
|
4686
4777
|
* material on the `+normal` side. The tool is a half-space block sized to the
|
|
@@ -4802,8 +4893,7 @@ function alignZTo(shape, target) {
|
|
|
4802
4893
|
axis
|
|
4803
4894
|
});
|
|
4804
4895
|
}
|
|
4805
|
-
|
|
4806
|
-
//#region src/contourFlangeFns.ts
|
|
4896
|
+
// src/contourFlangeFns.ts
|
|
4807
4897
|
/**
|
|
4808
4898
|
* Author a contour flange: an OPEN 2D profile (alternating line/arc segments) swept
|
|
4809
4899
|
* along one straight edge of the base flat. Each arc is a cylindrical bend, each
|
|
@@ -5272,8 +5362,7 @@ function matrixToAxisAngle(m) {
|
|
|
5272
5362
|
angleDeg
|
|
5273
5363
|
};
|
|
5274
5364
|
}
|
|
5275
|
-
|
|
5276
|
-
//#region src/loftedFlangeFns.ts
|
|
5365
|
+
// src/loftedFlangeFns.ts
|
|
5277
5366
|
/**
|
|
5278
5367
|
* Relative out-of-plane tolerance for the per-quad developability test: a quad is
|
|
5279
5368
|
* twisted (non-developable) when its far corner sits off the diagonal-triangle plane
|
|
@@ -5585,8 +5674,7 @@ function closedLoopWire(loop) {
|
|
|
5585
5674
|
}
|
|
5586
5675
|
return wireLoop(edges);
|
|
5587
5676
|
}
|
|
5588
|
-
|
|
5589
|
-
//#region src/hemFns.ts
|
|
5677
|
+
// src/hemFns.ts
|
|
5590
5678
|
/**
|
|
5591
5679
|
* A small clearance baked into a CLOSED hem's curl radius. A true closed hem folds
|
|
5592
5680
|
* the return flat against the parent with zero gap; modelling that exactly makes the
|
|
@@ -5779,8 +5867,7 @@ function splitAngle(totalDeg) {
|
|
|
5779
5867
|
const per = totalDeg / n;
|
|
5780
5868
|
return Array.from({ length: n }, () => per);
|
|
5781
5869
|
}
|
|
5782
|
-
|
|
5783
|
-
//#region src/jogFns.ts
|
|
5870
|
+
// src/jogFns.ts
|
|
5784
5871
|
/**
|
|
5785
5872
|
* Author a jog (joggle): two opposite bends (`+θ` then `−θ`) that step a flat by
|
|
5786
5873
|
* `offsetHeight` perpendicular to its plane, then continue parallel past the step.
|
|
@@ -5939,8 +6026,7 @@ function jogId(spec) {
|
|
|
5939
6026
|
function resolveRegion(region) {
|
|
5940
6027
|
return region === "base" || region === "face-0" ? ROOT_FLAT_ID : region;
|
|
5941
6028
|
}
|
|
5942
|
-
|
|
5943
|
-
//#region src/dxfFns.ts
|
|
6029
|
+
// src/dxfFns.ts
|
|
5944
6030
|
var DEFAULT_TEXT_HEIGHT = 2.5;
|
|
5945
6031
|
var LAYER_OUTLINE = "OUTLINE";
|
|
5946
6032
|
var LAYER_BEND_UP = "BEND_UP";
|
|
@@ -6149,8 +6235,7 @@ function pathPoints(wire) {
|
|
|
6149
6235
|
function toPt2(v) {
|
|
6150
6236
|
return [v[0], v[1]];
|
|
6151
6237
|
}
|
|
6152
|
-
|
|
6153
|
-
//#region src/polygonFns.ts
|
|
6238
|
+
// src/polygonFns.ts
|
|
6154
6239
|
/**
|
|
6155
6240
|
* 2D simple-polygon geometry for true-shape (no-fit-polygon) nesting: an ordered
|
|
6156
6241
|
* vertex loop plus the overlap predicate that is the correctness backbone of the
|
|
@@ -6393,8 +6478,7 @@ function pointSegmentDistance(p, a, b) {
|
|
|
6393
6478
|
const dy = p[1] - (a[1] + t * vy);
|
|
6394
6479
|
return Math.hypot(dx, dy);
|
|
6395
6480
|
}
|
|
6396
|
-
|
|
6397
|
-
//#region src/nestFns.ts
|
|
6481
|
+
// src/nestFns.ts
|
|
6398
6482
|
var EPS = 1e-9;
|
|
6399
6483
|
/**
|
|
6400
6484
|
* Bounding-box nest: arrange developed flat patterns onto stock sheets to reduce
|
|
@@ -6860,8 +6944,7 @@ function nestToDXF$1(result, patterns, sheetIndex, options) {
|
|
|
6860
6944
|
}
|
|
6861
6945
|
return multiPatternToDXF(placed, options ?? {});
|
|
6862
6946
|
}
|
|
6863
|
-
|
|
6864
|
-
//#region src/reportFns.ts
|
|
6947
|
+
// src/reportFns.ts
|
|
6865
6948
|
/**
|
|
6866
6949
|
* Build the bend-table JSON report directly from an authored part. Walks the
|
|
6867
6950
|
* same tree layout the unfold consumes so the per-bend values and the total flat
|
|
@@ -6904,8 +6987,7 @@ function reportFromUnfold(result) {
|
|
|
6904
6987
|
function reportToJSON(report) {
|
|
6905
6988
|
return JSON.stringify(report, null, 2);
|
|
6906
6989
|
}
|
|
6907
|
-
|
|
6908
|
-
//#region src/api.ts
|
|
6990
|
+
// src/api.ts
|
|
6909
6991
|
/** Author a straight-bend part: a base flat plus folded-up flanges. */
|
|
6910
6992
|
function author(spec) {
|
|
6911
6993
|
return authorPart(spec);
|
|
@@ -7067,8 +7149,7 @@ function bendTable(id) {
|
|
|
7067
7149
|
function resolveAllowance(rule, angleDeg, thickness, onWarning) {
|
|
7068
7150
|
return resolveBendAllowance(rule, angleDeg, thickness, onWarning);
|
|
7069
7151
|
}
|
|
7070
|
-
|
|
7071
|
-
//#region src/facade.ts
|
|
7152
|
+
// src/facade.ts
|
|
7072
7153
|
/** Thrown by the fluent facade when an underlying `Result<T>` is an `Err`. */
|
|
7073
7154
|
var SheetMetalError = class extends Error {
|
|
7074
7155
|
code;
|
|
@@ -7338,5 +7419,4 @@ function foldFlat(input) {
|
|
|
7338
7419
|
function fromSolid(solid, opts) {
|
|
7339
7420
|
return new ForeignSolidHandle(solid, opts);
|
|
7340
7421
|
}
|
|
7341
|
-
//#endregion
|
|
7342
7422
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brepjs-sheetmetal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@eslint/js": "*",
|
|
56
|
-
"@types/node": "26.
|
|
56
|
+
"@types/node": "26.6.5",
|
|
57
57
|
"eslint": "*",
|
|
58
|
-
"tsx": "4.23.
|
|
58
|
+
"tsx": "4.23.15",
|
|
59
59
|
"typescript": "*",
|
|
60
60
|
"typescript-eslint": "*",
|
|
61
61
|
"vite": "^8.0.0",
|
|
62
62
|
"vite-plugin-dts": "^5.0.0",
|
|
63
|
-
"vitest": "^
|
|
63
|
+
"vitest": "^5.0.0"
|
|
64
64
|
}
|
|
65
65
|
}
|