forgecad 0.9.15 → 0.9.16
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/assets/{AdminPage-CDyGUinA.js → AdminPage-CXvls4-J.js} +1 -1
- package/dist/assets/{BenchmarkPage-DfPMY_-d.js → BenchmarkPage-B27zk8xL.js} +1 -1
- package/dist/assets/{BlogPage-kF0fkdJT.js → BlogPage-CMAVvgQL.js} +1 -1
- package/dist/assets/{DocsPage-B954L3YN.js → DocsPage-knf4I4h7.js} +1 -1
- package/dist/assets/EditorApp-BHMQlJ-D.js +14686 -0
- package/dist/assets/{EditorApp-CuDLxKqL.css → EditorApp-BpjZgzk0.css} +148 -0
- package/dist/assets/{EmbedViewer-C77B-TrF.js → EmbedViewer-D7ZGlFjx.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-Cr6fXMDj.js → LandingPageProofDriven-CnevhTE8.js} +2 -2
- package/dist/assets/{LegalPage-Dzklqmmg.js → LegalPage-BPTUmqeg.js} +1 -1
- package/dist/assets/{PricingPage-zWXkvlwl.js → PricingPage-B0D4goG_.js} +1 -1
- package/dist/assets/{SettingsPage-Bz0of4KQ.js → SettingsPage-CFF-UgjI.js} +1 -1
- package/dist/assets/{app-D3kDkggg.js → app-T0pDcSX4.js} +1184 -218
- package/dist/assets/cli/{render-DSY3mMQa.js → render-C5pcIISc.js} +144 -26
- package/dist/assets/{constructionHistoryWorker-gpDo-uH2.js → constructionHistoryWorker-Ba2Hm58b.js} +1 -0
- package/dist/assets/{evalWorker-CU0Ke6DP.js → evalWorker-vkx310U2.js} +1380 -2173
- package/dist/assets/{inspectWorker-COyp8XXA.js → inspectWorker-BuTJDVX6.js} +252 -30
- package/dist/assets/{targets-B9sGB5nB.js → jointPose-B_Cgedn9.js} +71 -3
- package/dist/assets/{manifold-DNkrUWpA.js → manifold-BWgsjmAM.js} +1 -1
- package/dist/assets/{manifold-C-3h2M7p.js → manifold-D6IFSkhH.js} +2 -2
- package/dist/assets/{manifold-BRI5prcH.js → manifold-rZexZI0G.js} +1 -1
- package/dist/assets/{reportWorker-CdBz5bNg.js → reportWorker-0AGij1Ru.js} +1373 -2166
- package/dist/assets/{scalar-sampling-budget-wJF98aY9.js → scalar-sampling-budget-J5cuzxT1.js} +1494 -2251
- package/dist/assets/{scanProxyWorker-B-9VbLIs.js → scanProxyWorker-Vl4Wxa1y.js} +18 -5
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +1 -1
- package/dist/docs-raw/AI/usage.md +2 -0
- package/dist/docs-raw/CLI.md +4 -0
- package/dist/docs-raw/generated/assembly.md +104 -6
- package/dist/docs-raw/generated/concepts.md +14 -4
- package/dist/docs-raw/generated/lib.md +2 -18
- package/dist/docs-raw/generated/output.md +14 -4
- package/dist/docs-raw/generated/runtime-names.md +27 -19
- package/dist/docs-raw/skills/forgecad-make-a-model.md +39 -38
- package/dist/docs-raw/skills/forgecad-project.md +2 -0
- package/dist/docs-raw/welcome.md +2 -0
- package/dist/index.html +1 -1
- package/dist/sitemap.xml +13 -13
- package/dist-cli/{check-compiler-SDX5QIXI.js → check-compiler-SYQ2PWOB.js} +1 -1
- package/dist-cli/{check-query-propagation-EAYEFT77.js → check-query-propagation-HIAGV62W.js} +1 -1
- package/dist-cli/{chunk-N4O47JLF.js → chunk-SPZE3DUY.js} +1591 -2356
- package/dist-cli/forgecad.js +1698 -487
- package/dist-skill/CONTEXT.md +117 -46
- package/dist-skill/docs/CLI.md +4 -0
- package/dist-skill/docs/generated/assembly.md +83 -5
- package/dist-skill/docs/generated/lib.md +2 -18
- package/dist-skill/docs/generated/output.md +14 -4
- package/dist-skill/docs/generated/runtime-names.md +18 -19
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +39 -38
- package/dist-skill/library/forgecad-project/SKILL.md +2 -0
- package/examples/api/helix-basics.forge.js +2 -2
- package/examples/api/route3d-elbow.forge.js +3 -0
- package/examples/api/variable-sweep-test.forge.js +3 -1
- package/package.json +4 -1
- package/dist/assets/EditorApp-Beb-IZ0y.js +0 -14014
- package/examples/api/bolted-service-cover.forge.js +0 -17
- package/examples/api/cable-gland-anchor.forge.js +0 -14
- package/examples/api/captured-cartridge-guide.forge.js +0 -14
- package/examples/api/captured-linear-slide.forge.js +0 -13
- package/examples/api/clevis-pin-joint.forge.js +0 -13
- package/examples/api/datum-enclosure.forge.js +0 -16
- package/examples/api/hose-barb-port.forge.js +0 -14
- package/examples/api/knuckled-hinge-assembly.forge.js +0 -15
- package/examples/api/living-hinge-cover.forge.js +0 -14
- package/examples/api/pcb-terminal-block.forge.js +0 -22
- package/examples/api/pinned-lever-pivot-stack.forge.js +0 -14
- package/examples/api/retained-shaft-knob-stack.forge.js +0 -15
- package/examples/api/routed-tube-clip.forge.js +0 -15
- package/examples/api/seated-bearing-stack.forge.js +0 -30
- package/examples/api/snap-latch-cover.forge.js +0 -14
- package/examples/api/thumb-screw-clamp.forge.js +0 -15
|
@@ -1381,7 +1381,7 @@ function requireFiniteVec2(value, label) {
|
|
|
1381
1381
|
}
|
|
1382
1382
|
return [requireFiniteNumber$1(value[0], `${label}[0]`), requireFiniteNumber$1(value[1], `${label}[1]`)];
|
|
1383
1383
|
}
|
|
1384
|
-
function requireFiniteVec3$
|
|
1384
|
+
function requireFiniteVec3$4(value, label) {
|
|
1385
1385
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
1386
1386
|
throw new Error(`${label} must be [x, y, z] with finite numbers, got ${formatValidationValue(value)}`);
|
|
1387
1387
|
}
|
|
@@ -1397,7 +1397,7 @@ function requireNonZeroFiniteVec2(value, label) {
|
|
|
1397
1397
|
return v;
|
|
1398
1398
|
}
|
|
1399
1399
|
function requireNonZeroFiniteVec3(value, label) {
|
|
1400
|
-
const v = requireFiniteVec3$
|
|
1400
|
+
const v = requireFiniteVec3$4(value, label);
|
|
1401
1401
|
if (v[0] === 0 && v[1] === 0 && v[2] === 0) throw new Error(`${label} must not be [0, 0, 0]`);
|
|
1402
1402
|
return v;
|
|
1403
1403
|
}
|
|
@@ -1415,7 +1415,7 @@ function requireNonZeroFiniteScale2(value, label) {
|
|
|
1415
1415
|
return scale2;
|
|
1416
1416
|
}
|
|
1417
1417
|
function requireNonZeroFiniteScale3(value, label) {
|
|
1418
|
-
const scale2 = typeof value === "number" ? [requireFiniteNumber$1(value, label), requireFiniteNumber$1(value, label), requireFiniteNumber$1(value, label)] : requireFiniteVec3$
|
|
1418
|
+
const scale2 = typeof value === "number" ? [requireFiniteNumber$1(value, label), requireFiniteNumber$1(value, label), requireFiniteNumber$1(value, label)] : requireFiniteVec3$4(value, label);
|
|
1419
1419
|
if (Math.abs(scale2[0]) < 1e-12 || Math.abs(scale2[1]) < 1e-12 || Math.abs(scale2[2]) < 1e-12) {
|
|
1420
1420
|
throw new Error(`${label} must have finite non-zero components, got ${formatValidationValue(value)}`);
|
|
1421
1421
|
}
|
|
@@ -1610,7 +1610,7 @@ class Transform {
|
|
|
1610
1610
|
static rotationAxis(axis, angleDeg, pivot = [0, 0, 0]) {
|
|
1611
1611
|
const [ux, uy, uz] = normalizeVec3$6(requireNonZeroFiniteVec3(axis, "Transform.rotationAxis() axis"));
|
|
1612
1612
|
const degrees2 = requireFiniteNumber$1(angleDeg, "Transform.rotationAxis() angleDeg");
|
|
1613
|
-
const [px, py, pz] = requireFiniteVec3$
|
|
1613
|
+
const [px, py, pz] = requireFiniteVec3$4(pivot, "Transform.rotationAxis() pivot");
|
|
1614
1614
|
const rad = degrees2 * Math.PI / 180;
|
|
1615
1615
|
const cos2 = Math.cos(rad);
|
|
1616
1616
|
const sin2 = Math.sin(rad);
|
|
@@ -1631,9 +1631,9 @@ class Transform {
|
|
|
1631
1631
|
/** Solve the rotation needed to move one point onto a target line or plane. */
|
|
1632
1632
|
static rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
|
|
1633
1633
|
const rotateAxis = requireNonZeroFiniteVec3(axis, "Transform.rotateAroundTo() axis");
|
|
1634
|
-
const rotatePivot = requireFiniteVec3$
|
|
1635
|
-
const moving = requireFiniteVec3$
|
|
1636
|
-
const target = requireFiniteVec3$
|
|
1634
|
+
const rotatePivot = requireFiniteVec3$4(pivot, "Transform.rotateAroundTo() pivot");
|
|
1635
|
+
const moving = requireFiniteVec3$4(movingPoint, "Transform.rotateAroundTo() movingPoint");
|
|
1636
|
+
const target = requireFiniteVec3$4(targetPoint, "Transform.rotateAroundTo() targetPoint");
|
|
1637
1637
|
const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
|
|
1638
1638
|
return Transform.rotationAxis(rotateAxis, angleDeg, rotatePivot);
|
|
1639
1639
|
}
|
|
@@ -1663,6 +1663,7 @@ class Transform {
|
|
|
1663
1663
|
return this.rotateAxis([0, 0, 1], angleDeg, pivot);
|
|
1664
1664
|
}
|
|
1665
1665
|
/** Scale after the current transform. */
|
|
1666
|
+
// biome-ignore lint/suspicious/useAdjacentOverloadSignatures: Static Transform.scale() and chainable instance scale() intentionally share the CAD API name.
|
|
1666
1667
|
scale(v) {
|
|
1667
1668
|
return this.mul(Transform.scale(v));
|
|
1668
1669
|
}
|
|
@@ -1672,11 +1673,11 @@ class Transform {
|
|
|
1672
1673
|
}
|
|
1673
1674
|
/** Transform a point using homogeneous coordinates. */
|
|
1674
1675
|
point(p2) {
|
|
1675
|
-
return transformPoint$1(this.m, requireFiniteVec3$
|
|
1676
|
+
return transformPoint$1(this.m, requireFiniteVec3$4(p2, "Transform.point() point"), 1);
|
|
1676
1677
|
}
|
|
1677
1678
|
/** Transform a direction vector without translation. */
|
|
1678
1679
|
vector(v) {
|
|
1679
|
-
return transformPoint$1(this.m, requireFiniteVec3$
|
|
1680
|
+
return transformPoint$1(this.m, requireFiniteVec3$4(v, "Transform.vector() vector"), 0);
|
|
1680
1681
|
}
|
|
1681
1682
|
/** Return the transform as a raw 4x4 matrix array. */
|
|
1682
1683
|
toArray() {
|
|
@@ -3421,7 +3422,7 @@ const EPSILON$4 = 1e-9;
|
|
|
3421
3422
|
function add$7(a2, b) {
|
|
3422
3423
|
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
3423
3424
|
}
|
|
3424
|
-
function scale$
|
|
3425
|
+
function scale$8(v, factor) {
|
|
3425
3426
|
return [v[0] * factor, v[1] * factor, v[2] * factor];
|
|
3426
3427
|
}
|
|
3427
3428
|
function sub$9(a2, b) {
|
|
@@ -3436,9 +3437,9 @@ function cross$a(a2, b) {
|
|
|
3436
3437
|
function rotateAroundAxis(v, axis, angleRad) {
|
|
3437
3438
|
const c2 = Math.cos(angleRad);
|
|
3438
3439
|
const s = Math.sin(angleRad);
|
|
3439
|
-
const term1 = scale$
|
|
3440
|
-
const term2 = scale$
|
|
3441
|
-
const term3 = scale$
|
|
3440
|
+
const term1 = scale$8(v, c2);
|
|
3441
|
+
const term2 = scale$8(cross$a(axis, v), s);
|
|
3442
|
+
const term3 = scale$8(axis, dot$9(axis, v) * (1 - c2));
|
|
3442
3443
|
return add$7(add$7(term1, term2), term3);
|
|
3443
3444
|
}
|
|
3444
3445
|
function arcPointAt(segment, t) {
|
|
@@ -3466,7 +3467,7 @@ function routePointAt(plan, t) {
|
|
|
3466
3467
|
const next = station + segment.length;
|
|
3467
3468
|
if (target <= next || segment === plan.segments[plan.segments.length - 1]) {
|
|
3468
3469
|
const localT = segment.length <= EPSILON$4 ? 1 : Math.max(0, Math.min(1, (target - station) / segment.length));
|
|
3469
|
-
if (segment.kind === "line") return add$7(segment.from, scale$
|
|
3470
|
+
if (segment.kind === "line") return add$7(segment.from, scale$8(sub$9(segment.to, segment.from), localT));
|
|
3470
3471
|
return arcPointAt(segment, localT);
|
|
3471
3472
|
}
|
|
3472
3473
|
station = next;
|
|
@@ -3486,7 +3487,7 @@ function sampleRoute3DCompilePlan(plan, options) {
|
|
|
3486
3487
|
pushUnique(points, start);
|
|
3487
3488
|
for (let i = 1; i <= intervals; i += 1) {
|
|
3488
3489
|
if (segment.kind === "line") {
|
|
3489
|
-
pushUnique(points, add$7(segment.from, scale$
|
|
3490
|
+
pushUnique(points, add$7(segment.from, scale$8(sub$9(segment.to, segment.from), i / intervals)));
|
|
3490
3491
|
} else {
|
|
3491
3492
|
pushUnique(points, arcPointAt(segment, i / intervals));
|
|
3492
3493
|
}
|
|
@@ -6519,11 +6520,11 @@ function distSq$1(a2, b) {
|
|
|
6519
6520
|
const dz = a2[2] - b[2];
|
|
6520
6521
|
return dx * dx + dy * dy + dz * dz;
|
|
6521
6522
|
}
|
|
6522
|
-
function vecLength$
|
|
6523
|
+
function vecLength$4(v) {
|
|
6523
6524
|
return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
6524
6525
|
}
|
|
6525
6526
|
function normalize$6(v) {
|
|
6526
|
-
const len2 = vecLength$
|
|
6527
|
+
const len2 = vecLength$4(v);
|
|
6527
6528
|
if (len2 < 1e-12) return [0, 0, 0];
|
|
6528
6529
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
6529
6530
|
}
|
|
@@ -7253,14 +7254,14 @@ function resolveSdfMeshingSettings(tree, bounds, options = {}) {
|
|
|
7253
7254
|
const quality = options.quality ?? "preview";
|
|
7254
7255
|
const minEdgeLength = positiveOrDefault(options.minEdgeLength, DEFAULT_MIN_EDGE_LENGTH);
|
|
7255
7256
|
const maxGridPoints = positiveOrDefault(options.maxGridPoints, DEFAULT_MAX_GRID_POINTS);
|
|
7256
|
-
const tolerance = options.tolerance !== void 0 ? requirePositiveFinite$
|
|
7257
|
-
const minFeatureSize = options.minFeatureSize !== void 0 ? requirePositiveFinite$
|
|
7258
|
-
const maxTriangles = options.maxTriangles !== void 0 ? Math.floor(requirePositiveFinite$
|
|
7257
|
+
const tolerance = options.tolerance !== void 0 ? requirePositiveFinite$4(options.tolerance, "SDF tolerance") : void 0;
|
|
7258
|
+
const minFeatureSize = options.minFeatureSize !== void 0 ? requirePositiveFinite$4(options.minFeatureSize, "SDF minFeatureSize") : void 0;
|
|
7259
|
+
const maxTriangles = options.maxTriangles !== void 0 ? Math.floor(requirePositiveFinite$4(options.maxTriangles, "SDF maxTriangles")) : void 0;
|
|
7259
7260
|
const analysis = analyzeSdfTree(tree);
|
|
7260
7261
|
const warnings = [];
|
|
7261
7262
|
let edgeLength2;
|
|
7262
7263
|
if (options.edgeLength !== void 0) {
|
|
7263
|
-
edgeLength2 = requirePositiveFinite$
|
|
7264
|
+
edgeLength2 = requirePositiveFinite$4(options.edgeLength, "SDF edgeLength");
|
|
7264
7265
|
if (edgeLength2 < minEdgeLength) {
|
|
7265
7266
|
warnings.push(`edgeLength ${formatMm(edgeLength2)} was clamped to minimum ${formatMm(minEdgeLength)}.`);
|
|
7266
7267
|
edgeLength2 = minEdgeLength;
|
|
@@ -7353,8 +7354,8 @@ function resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, opti
|
|
|
7353
7354
|
const maxDim = Math.max(dx, dy, dz, minEdgeLength);
|
|
7354
7355
|
const divisor = quality === "draft" ? 60 : quality === "export" ? 160 : 100;
|
|
7355
7356
|
const candidates = [maxDim / divisor];
|
|
7356
|
-
if (options.tolerance !== void 0) candidates.push(requirePositiveFinite$
|
|
7357
|
-
if (options.minFeatureSize !== void 0) candidates.push(requirePositiveFinite$
|
|
7357
|
+
if (options.tolerance !== void 0) candidates.push(requirePositiveFinite$4(options.tolerance, "SDF tolerance") * 2);
|
|
7358
|
+
if (options.minFeatureSize !== void 0) candidates.push(requirePositiveFinite$4(options.minFeatureSize, "SDF minFeatureSize") / 2.5);
|
|
7358
7359
|
if (analysis.minMetricTpmsThickness < Infinity) candidates.push(analysis.minMetricTpmsThickness / 2);
|
|
7359
7360
|
if (analysis.minTpmsCellSize < Infinity) candidates.push(analysis.minTpmsCellSize / 10);
|
|
7360
7361
|
if (analysis.minRepeatSpacing < Infinity) candidates.push(analysis.minRepeatSpacing / 8);
|
|
@@ -7494,9 +7495,9 @@ function visitSdfNode(node, analysis) {
|
|
|
7494
7495
|
}
|
|
7495
7496
|
function positiveOrDefault(value, fallback) {
|
|
7496
7497
|
if (value === void 0) return fallback;
|
|
7497
|
-
return requirePositiveFinite$
|
|
7498
|
+
return requirePositiveFinite$4(value, "SDF meshing option");
|
|
7498
7499
|
}
|
|
7499
|
-
function requirePositiveFinite$
|
|
7500
|
+
function requirePositiveFinite$4(value, name) {
|
|
7500
7501
|
if (!Number.isFinite(value) || value <= 0) {
|
|
7501
7502
|
throw new Error(`${name} must be a positive finite number.`);
|
|
7502
7503
|
}
|
|
@@ -9480,7 +9481,7 @@ function midpoint$3(a2, b) {
|
|
|
9480
9481
|
function add$6(a2, b) {
|
|
9481
9482
|
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
9482
9483
|
}
|
|
9483
|
-
function scale$
|
|
9484
|
+
function scale$7(v, s) {
|
|
9484
9485
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
9485
9486
|
}
|
|
9486
9487
|
function sub$8(a2, b) {
|
|
@@ -9509,7 +9510,7 @@ function average$1(points) {
|
|
|
9509
9510
|
for (const point2 of points) {
|
|
9510
9511
|
acc = add$6(acc, point2);
|
|
9511
9512
|
}
|
|
9512
|
-
return scale$
|
|
9513
|
+
return scale$7(acc, 1 / points.length);
|
|
9513
9514
|
}
|
|
9514
9515
|
function buildSurfaceSheetTopology(boundaries, options = {}) {
|
|
9515
9516
|
var _a3, _b3, _c2, _d2;
|
|
@@ -9655,7 +9656,7 @@ function edgeCurveFaceName(curve) {
|
|
|
9655
9656
|
function dotVec3$4(a2, b) {
|
|
9656
9657
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
9657
9658
|
}
|
|
9658
|
-
function vecLength$
|
|
9659
|
+
function vecLength$3(v) {
|
|
9659
9660
|
return Math.hypot(v[0], v[1], v[2]);
|
|
9660
9661
|
}
|
|
9661
9662
|
function sameScalar$2(a2, b) {
|
|
@@ -9665,9 +9666,9 @@ function uniformDistanceScale(matrix) {
|
|
|
9665
9666
|
const xAxis = [matrix[0], matrix[1], matrix[2]];
|
|
9666
9667
|
const yAxis = [matrix[4], matrix[5], matrix[6]];
|
|
9667
9668
|
const zAxis = [matrix[8], matrix[9], matrix[10]];
|
|
9668
|
-
const sx = vecLength$
|
|
9669
|
-
const sy = vecLength$
|
|
9670
|
-
const sz = vecLength$
|
|
9669
|
+
const sx = vecLength$3(xAxis);
|
|
9670
|
+
const sy = vecLength$3(yAxis);
|
|
9671
|
+
const sz = vecLength$3(zAxis);
|
|
9671
9672
|
if (sx <= 1e-9 || sy <= 1e-9 || sz <= 1e-9) return null;
|
|
9672
9673
|
if (!sameScalar$2(sx, sy) || !sameScalar$2(sx, sz)) return null;
|
|
9673
9674
|
const orthoTolerance = 1e-9 * Math.max(1, sx * sx);
|
|
@@ -9678,7 +9679,7 @@ function uniformDistanceScale(matrix) {
|
|
|
9678
9679
|
}
|
|
9679
9680
|
function transformSurfaceAxis(tx, axis) {
|
|
9680
9681
|
const transformed = tx.vector(axis);
|
|
9681
|
-
const length4 = vecLength$
|
|
9682
|
+
const length4 = vecLength$3(transformed);
|
|
9682
9683
|
return length4 > 1e-9 ? [transformed[0] / length4, transformed[1] / length4, transformed[2] / length4] : null;
|
|
9683
9684
|
}
|
|
9684
9685
|
function transformFaceSurface(surface, tx) {
|
|
@@ -10916,7 +10917,7 @@ function rotateVector$1(v, axis, cosAngle, sinAngle) {
|
|
|
10916
10917
|
v[2] * cosAngle + axisCross[2] * sinAngle + axis[2] * axisDot * (1 - cosAngle)
|
|
10917
10918
|
];
|
|
10918
10919
|
}
|
|
10919
|
-
function orthonormalizeFrame(xCandidate, tangent, fallbackUp) {
|
|
10920
|
+
function orthonormalizeFrame$1(xCandidate, tangent, fallbackUp) {
|
|
10920
10921
|
let x2 = vec3Sub$1(xCandidate, vec3Scale(tangent, vec3Dot$2(xCandidate, tangent)));
|
|
10921
10922
|
if (vec3Len$2(x2) < 1e-8) {
|
|
10922
10923
|
return makeSweepFrame(tangent, fallbackUp);
|
|
@@ -10964,7 +10965,7 @@ function computeParallelTransportFramesFromSamples(samples, preferredUp) {
|
|
|
10964
10965
|
xCandidate = previous.x;
|
|
10965
10966
|
yCandidate = previous.y;
|
|
10966
10967
|
}
|
|
10967
|
-
({ x: x2, y: y2 } = orthonormalizeFrame(xCandidate, tangent, yCandidate));
|
|
10968
|
+
({ x: x2, y: y2 } = orthonormalizeFrame$1(xCandidate, tangent, yCandidate));
|
|
10968
10969
|
frames.push({ origin: samples[index2].origin, x: x2, y: y2, t: tangent });
|
|
10969
10970
|
}
|
|
10970
10971
|
return frames;
|
|
@@ -11027,7 +11028,7 @@ function interpolateSweepFrame(segment, alpha, origin) {
|
|
|
11027
11028
|
const tangent = vec3Len$2(tangentCandidate) > 1e-8 ? vec3Norm$1(tangentCandidate) : segment.t;
|
|
11028
11029
|
const xCandidate = vec3Lerp(segment.frameA.x, segment.frameB.x, alpha);
|
|
11029
11030
|
const fallbackUp = vec3Lerp(segment.frameA.y, segment.frameB.y, alpha);
|
|
11030
|
-
const { x: x2, y: y2 } = orthonormalizeFrame(xCandidate, tangent, fallbackUp);
|
|
11031
|
+
const { x: x2, y: y2 } = orthonormalizeFrame$1(xCandidate, tangent, fallbackUp);
|
|
11031
11032
|
return { origin, x: x2, y: y2, t: tangent };
|
|
11032
11033
|
}
|
|
11033
11034
|
function findNearestSweepPoint(point2, segments) {
|
|
@@ -11168,7 +11169,7 @@ function buildCatmullRomSweepPathRuntime(path2, preferredUp, edgeLength2) {
|
|
|
11168
11169
|
const tangent2 = vec3Norm$1(catmullRom3DTangentAtNormalizedT(path2.controlPoints, path2.closed, path2.tension, wrapped));
|
|
11169
11170
|
const xCandidate2 = vec3Lerp(frames[base2].x, frames[next].x, alpha2);
|
|
11170
11171
|
const fallbackUp2 = vec3Lerp(frames[base2].y, frames[next].y, alpha2);
|
|
11171
|
-
const { x: x22, y: y22 } = orthonormalizeFrame(xCandidate2, tangent2, fallbackUp2);
|
|
11172
|
+
const { x: x22, y: y22 } = orthonormalizeFrame$1(xCandidate2, tangent2, fallbackUp2);
|
|
11172
11173
|
return { origin, x: x22, y: y22, t: tangent2 };
|
|
11173
11174
|
}
|
|
11174
11175
|
const clamped = clamp$8(tParam, 0, 1);
|
|
@@ -11178,7 +11179,7 @@ function buildCatmullRomSweepPathRuntime(path2, preferredUp, edgeLength2) {
|
|
|
11178
11179
|
const tangent = vec3Norm$1(catmullRom3DTangentAtNormalizedT(path2.controlPoints, path2.closed, path2.tension, clamped));
|
|
11179
11180
|
const xCandidate = vec3Lerp(frames[base].x, frames[base + 1].x, alpha);
|
|
11180
11181
|
const fallbackUp = vec3Lerp(frames[base].y, frames[base + 1].y, alpha);
|
|
11181
|
-
const { x: x2, y: y2 } = orthonormalizeFrame(xCandidate, tangent, fallbackUp);
|
|
11182
|
+
const { x: x2, y: y2 } = orthonormalizeFrame$1(xCandidate, tangent, fallbackUp);
|
|
11182
11183
|
return { origin, x: x2, y: y2, t: tangent };
|
|
11183
11184
|
}
|
|
11184
11185
|
function evaluatePoint(tParam) {
|
|
@@ -11723,7 +11724,7 @@ let _wasm$1 = null;
|
|
|
11723
11724
|
async function initManifoldWasm() {
|
|
11724
11725
|
if (_wasm$1) return _wasm$1;
|
|
11725
11726
|
performance.mark("manifold:start");
|
|
11726
|
-
const Module = (await import("./manifold-
|
|
11727
|
+
const Module = (await import("./manifold-BWgsjmAM.js")).default;
|
|
11727
11728
|
performance.mark("manifold:imported");
|
|
11728
11729
|
const wasm = await Module();
|
|
11729
11730
|
wasm.setup();
|
|
@@ -11976,7 +11977,7 @@ function computeParallelTransportFrames(path2, preferredUp) {
|
|
|
11976
11977
|
const vLen = length$3(v);
|
|
11977
11978
|
const c2 = dot$8(prevT, nextT);
|
|
11978
11979
|
if (vLen > 1e-10) {
|
|
11979
|
-
const axis = scale$
|
|
11980
|
+
const axis = scale$6(v, 1 / vLen);
|
|
11980
11981
|
x2 = rotateVector(frames[i - 1].x, axis, c2, vLen);
|
|
11981
11982
|
y2 = rotateVector(frames[i - 1].y, axis, c2, vLen);
|
|
11982
11983
|
} else {
|
|
@@ -12054,7 +12055,7 @@ function sub$7(a2, b) {
|
|
|
12054
12055
|
function add$5(a2, b) {
|
|
12055
12056
|
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
12056
12057
|
}
|
|
12057
|
-
function scale$
|
|
12058
|
+
function scale$6(v, s) {
|
|
12058
12059
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
12059
12060
|
}
|
|
12060
12061
|
function dot$8(a2, b) {
|
|
@@ -25221,7 +25222,7 @@ function add$4(a2, b) {
|
|
|
25221
25222
|
function sub$6(a2, b) {
|
|
25222
25223
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
25223
25224
|
}
|
|
25224
|
-
function scale$
|
|
25225
|
+
function scale$5(v, scalar) {
|
|
25225
25226
|
return [v[0] * scalar, v[1] * scalar, v[2] * scalar];
|
|
25226
25227
|
}
|
|
25227
25228
|
function cross$6(a2, b) {
|
|
@@ -25470,7 +25471,7 @@ function createBoundedHalfSpace(bounds, normal, originOffset) {
|
|
|
25470
25471
|
const n = [normal[0] / normalLength, normal[1] / normalLength, normal[2] / normalLength];
|
|
25471
25472
|
const signedOffset = originOffset / normalLength;
|
|
25472
25473
|
const corners2 = boundsCorners(bounds);
|
|
25473
|
-
const planeOrigin = scale$
|
|
25474
|
+
const planeOrigin = scale$5(n, signedOffset);
|
|
25474
25475
|
const { uAxis, vAxis } = perpendicularAxes(n);
|
|
25475
25476
|
const diagonal = Math.hypot(bounds.max[0] - bounds.min[0], bounds.max[1] - bounds.min[1], bounds.max[2] - bounds.min[2]);
|
|
25476
25477
|
const margin = Math.max(diagonal * 0.05, 1e-3);
|
|
@@ -25495,7 +25496,7 @@ function createBoundedHalfSpace(bounds, normal, originOffset) {
|
|
|
25495
25496
|
const height = Math.max(maxDistance + margin, margin);
|
|
25496
25497
|
const centerU = (minU + maxU) / 2;
|
|
25497
25498
|
const centerV = (minV + maxV) / 2;
|
|
25498
|
-
const translation = add$4(add$4(planeOrigin, scale$
|
|
25499
|
+
const translation = add$4(add$4(planeOrigin, scale$5(uAxis, centerU)), scale$5(vAxis, centerV));
|
|
25499
25500
|
const matrix = [
|
|
25500
25501
|
uAxis[0],
|
|
25501
25502
|
uAxis[1],
|
|
@@ -34683,7 +34684,7 @@ function placementReferenceNames(refs, kind) {
|
|
|
34683
34684
|
(entryKind) => Object.keys(refs[entryKind]).sort().map((name) => `${entryKind}.${name}`)
|
|
34684
34685
|
);
|
|
34685
34686
|
}
|
|
34686
|
-
function requireFiniteVec3$
|
|
34687
|
+
function requireFiniteVec3$3(v, label) {
|
|
34687
34688
|
const [x2, y2, z2] = v;
|
|
34688
34689
|
if (!Number.isFinite(x2) || !Number.isFinite(y2) || !Number.isFinite(z2)) {
|
|
34689
34690
|
throw new Error(`${label} must contain finite numbers, got [${x2}, ${y2}, ${z2}]`);
|
|
@@ -34723,8 +34724,8 @@ function normalizePortInput(input) {
|
|
|
34723
34724
|
const hasStartEnd = input.start != null && input.end != null;
|
|
34724
34725
|
const hasOriginAxis = input.origin != null && input.axis != null;
|
|
34725
34726
|
if (hasStartEnd) {
|
|
34726
|
-
const start = requireFiniteVec3$
|
|
34727
|
-
const end = requireFiniteVec3$
|
|
34727
|
+
const start = requireFiniteVec3$3(input.start, "port start");
|
|
34728
|
+
const end = requireFiniteVec3$3(input.end, "port end");
|
|
34728
34729
|
origin = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2, (start[2] + end[2]) / 2];
|
|
34729
34730
|
const dir = [end[0] - start[0], end[1] - start[1], end[2] - start[2]];
|
|
34730
34731
|
const dirLen = len3$2(dir);
|
|
@@ -34733,11 +34734,11 @@ function normalizePortInput(input) {
|
|
|
34733
34734
|
}
|
|
34734
34735
|
axis = normalize3$4(dir);
|
|
34735
34736
|
extent = dirLen / 2;
|
|
34736
|
-
if (input.origin != null) origin = requireFiniteVec3$
|
|
34737
|
-
if (input.axis != null) axis = normalize3$4(requireFiniteVec3$
|
|
34737
|
+
if (input.origin != null) origin = requireFiniteVec3$3(input.origin, "port origin");
|
|
34738
|
+
if (input.axis != null) axis = normalize3$4(requireFiniteVec3$3(input.axis, "port axis"));
|
|
34738
34739
|
} else if (hasOriginAxis) {
|
|
34739
|
-
origin = requireFiniteVec3$
|
|
34740
|
-
const rawAxis = requireFiniteVec3$
|
|
34740
|
+
origin = requireFiniteVec3$3(input.origin, "port origin");
|
|
34741
|
+
const rawAxis = requireFiniteVec3$3(input.axis, "port axis");
|
|
34741
34742
|
if (len3$2(rawAxis) < 1e-10) {
|
|
34742
34743
|
throw new Error("Port axis must be non-zero");
|
|
34743
34744
|
}
|
|
@@ -34746,14 +34747,14 @@ function normalizePortInput(input) {
|
|
|
34746
34747
|
extent = input.extent;
|
|
34747
34748
|
}
|
|
34748
34749
|
} else if (input.origin != null) {
|
|
34749
|
-
origin = requireFiniteVec3$
|
|
34750
|
+
origin = requireFiniteVec3$3(input.origin, "port origin");
|
|
34750
34751
|
axis = [0, 0, 1];
|
|
34751
34752
|
} else {
|
|
34752
34753
|
throw new Error("Port requires either { origin, axis } or { start, end }");
|
|
34753
34754
|
}
|
|
34754
34755
|
let up;
|
|
34755
34756
|
if (input.up != null) {
|
|
34756
|
-
const rawUp = requireFiniteVec3$
|
|
34757
|
+
const rawUp = requireFiniteVec3$3(input.up, "port up");
|
|
34757
34758
|
if (len3$2(rawUp) < 1e-10) {
|
|
34758
34759
|
throw new Error("Port up vector must be non-zero");
|
|
34759
34760
|
}
|
|
@@ -36176,7 +36177,7 @@ class ShapeGroup {
|
|
|
36176
36177
|
return { min: bb.min, max: bb.max };
|
|
36177
36178
|
}
|
|
36178
36179
|
resolveRotatePoint(point2) {
|
|
36179
|
-
if (Array.isArray(point2)) return requireFiniteVec3$
|
|
36180
|
+
if (Array.isArray(point2)) return requireFiniteVec3$4(point2, "ShapeGroup.rotateAroundTo() point");
|
|
36180
36181
|
const bb = this._bbox();
|
|
36181
36182
|
return resolveAnchor3D(bb.min, bb.max, point2);
|
|
36182
36183
|
}
|
|
@@ -36229,7 +36230,7 @@ class ShapeGroup {
|
|
|
36229
36230
|
const sp = resolveAnchor3D(sbb.min, sbb.max, selfAnchor);
|
|
36230
36231
|
let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
|
|
36231
36232
|
if (offset2) {
|
|
36232
|
-
const offsetPoint = requireFiniteVec3$
|
|
36233
|
+
const offsetPoint = requireFiniteVec3$4(offset2, "ShapeGroup.attachTo() offset");
|
|
36233
36234
|
dx += offsetPoint[0];
|
|
36234
36235
|
dy += offsetPoint[1];
|
|
36235
36236
|
dz += offsetPoint[2];
|
|
@@ -36284,7 +36285,7 @@ class ShapeGroup {
|
|
|
36284
36285
|
rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
|
|
36285
36286
|
const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundAxis() axis");
|
|
36286
36287
|
const degrees2 = requireFiniteNumber$1(angleDeg, "ShapeGroup.rotateAroundAxis() angleDeg");
|
|
36287
|
-
const rotatePivot = requireFiniteVec3$
|
|
36288
|
+
const rotatePivot = requireFiniteVec3$4(pivot, "ShapeGroup.rotateAroundAxis() pivot");
|
|
36288
36289
|
return this.transform(Transform.rotationAxis(rotateAxis, degrees2, rotatePivot));
|
|
36289
36290
|
}
|
|
36290
36291
|
/**
|
|
@@ -36293,7 +36294,7 @@ class ShapeGroup {
|
|
|
36293
36294
|
*/
|
|
36294
36295
|
rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
|
|
36295
36296
|
const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundTo() axis");
|
|
36296
|
-
const rotatePivot = requireFiniteVec3$
|
|
36297
|
+
const rotatePivot = requireFiniteVec3$4(pivot, "ShapeGroup.rotateAroundTo() pivot");
|
|
36297
36298
|
return this.transform(
|
|
36298
36299
|
Transform.rotateAroundTo(
|
|
36299
36300
|
rotateAxis,
|
|
@@ -36344,7 +36345,7 @@ class ShapeGroup {
|
|
|
36344
36345
|
/** Scale uniformly or per-axis from an explicit pivot point. */
|
|
36345
36346
|
scaleAround(pivot, v) {
|
|
36346
36347
|
const scale2 = requireNonZeroFiniteScale3(v, "ShapeGroup.scaleAround() scale");
|
|
36347
|
-
const scalePivot = requireFiniteVec3$
|
|
36348
|
+
const scalePivot = requireFiniteVec3$4(pivot, "ShapeGroup.scaleAround() pivot");
|
|
36348
36349
|
const matrix = Transform.scale(scale2).toArray();
|
|
36349
36350
|
if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
|
|
36350
36351
|
return this.mapChildrenTransform((c2) => {
|
|
@@ -36367,7 +36368,7 @@ class ShapeGroup {
|
|
|
36367
36368
|
}
|
|
36368
36369
|
/** Mirror across a plane through an explicit point. */
|
|
36369
36370
|
mirrorThrough(point2, normal) {
|
|
36370
|
-
const mirrorPoint = requireFiniteVec3$
|
|
36371
|
+
const mirrorPoint = requireFiniteVec3$4(point2, "ShapeGroup.mirrorThrough() point");
|
|
36371
36372
|
const mirrorNormal = requireNonZeroFiniteVec3(normal, "ShapeGroup.mirrorThrough() normal");
|
|
36372
36373
|
const matrix = mirrorPlaneMatrix(mirrorNormal);
|
|
36373
36374
|
if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
|
|
@@ -36455,8 +36456,8 @@ class ShapeGroup {
|
|
|
36455
36456
|
* ```
|
|
36456
36457
|
*/
|
|
36457
36458
|
placeReference(ref, target, offset2) {
|
|
36458
|
-
const targetPoint = requireFiniteVec3$
|
|
36459
|
-
const offsetPoint = offset2 === void 0 ? void 0 : requireFiniteVec3$
|
|
36459
|
+
const targetPoint = requireFiniteVec3$4(target, "ShapeGroup.placeReference() target");
|
|
36460
|
+
const offsetPoint = offset2 === void 0 ? void 0 : requireFiniteVec3$4(offset2, "ShapeGroup.placeReference() offset");
|
|
36460
36461
|
const sourcePoint = this.referencePoint(ref);
|
|
36461
36462
|
let dx = targetPoint[0] - sourcePoint[0];
|
|
36462
36463
|
let dy = targetPoint[1] - sourcePoint[1];
|
|
@@ -39013,7 +39014,7 @@ function buildSdfFunctionDefinition(source, options) {
|
|
|
39013
39014
|
jsExpression: expression,
|
|
39014
39015
|
...shader.ok ? { shaderExpression: shader.expression } : { shaderUnsupportedReason: shader.reason },
|
|
39015
39016
|
raymarchStepLimit: resolveRaymarchStepLimit(options.bounds, options.maxStep),
|
|
39016
|
-
...options.lipschitz !== void 0 ? { raymarchLipschitz: requirePositiveFinite$
|
|
39017
|
+
...options.lipschitz !== void 0 ? { raymarchLipschitz: requirePositiveFinite$3(options.lipschitz, "sdf.fromFunction() lipschitz") } : {}
|
|
39017
39018
|
};
|
|
39018
39019
|
}
|
|
39019
39020
|
function extractSdfExpression(source) {
|
|
@@ -39181,7 +39182,7 @@ function formatNumericLiteralsForGlsl(source) {
|
|
|
39181
39182
|
return result;
|
|
39182
39183
|
}
|
|
39183
39184
|
function resolveRaymarchStepLimit(bounds, maxStep) {
|
|
39184
|
-
if (maxStep !== void 0) return requirePositiveFinite$
|
|
39185
|
+
if (maxStep !== void 0) return requirePositiveFinite$3(maxStep, "sdf.fromFunction() maxStep");
|
|
39185
39186
|
const dx = bounds.max[0] - bounds.min[0];
|
|
39186
39187
|
const dy = bounds.max[1] - bounds.min[1];
|
|
39187
39188
|
const dz = bounds.max[2] - bounds.min[2];
|
|
@@ -39189,7 +39190,7 @@ function resolveRaymarchStepLimit(bounds, maxStep) {
|
|
|
39189
39190
|
if (!Number.isFinite(diagonal) || diagonal <= 0) return 0.1;
|
|
39190
39191
|
return Math.max(0.025, Math.min(0.5, diagonal / 240));
|
|
39191
39192
|
}
|
|
39192
|
-
function requirePositiveFinite$
|
|
39193
|
+
function requirePositiveFinite$3(value, label) {
|
|
39193
39194
|
if (!Number.isFinite(value) || value <= 0) throw new Error(`${label} must be a positive finite number.`);
|
|
39194
39195
|
return value;
|
|
39195
39196
|
}
|
|
@@ -39302,7 +39303,7 @@ class Pattern2DBuilder {
|
|
|
39302
39303
|
return new Pattern2DImpl({
|
|
39303
39304
|
kind: "surfacePattern:sineWave",
|
|
39304
39305
|
direction: normalizeDirection$1(options.direction ?? [1, 0], "sdf.pattern2d().sineWave() direction"),
|
|
39305
|
-
wavelength: requirePositiveFinite$
|
|
39306
|
+
wavelength: requirePositiveFinite$2(options.wavelength, "sdf.pattern2d().sineWave() wavelength"),
|
|
39306
39307
|
amplitude: requireFinite$a(options.amplitude ?? 1, "sdf.pattern2d().sineWave() amplitude"),
|
|
39307
39308
|
phase: requireFinite$a(options.phase ?? 0, "sdf.pattern2d().sineWave() phase"),
|
|
39308
39309
|
bias: requireFinite$a(options.bias ?? 0, "sdf.pattern2d().sineWave() bias")
|
|
@@ -39313,19 +39314,19 @@ class Pattern2DBuilder {
|
|
|
39313
39314
|
return new Pattern2DImpl({
|
|
39314
39315
|
kind: "surfacePattern:stripes",
|
|
39315
39316
|
direction: normalizeDirection$1(options.direction ?? [1, 0], "sdf.pattern2d().stripes() direction"),
|
|
39316
|
-
spacing: requirePositiveFinite$
|
|
39317
|
-
width: requirePositiveFinite$
|
|
39318
|
-
depth: requireNonNegativeFinite$
|
|
39317
|
+
spacing: requirePositiveFinite$2(options.spacing, "sdf.pattern2d().stripes() spacing"),
|
|
39318
|
+
width: requirePositiveFinite$2(options.width, "sdf.pattern2d().stripes() width"),
|
|
39319
|
+
depth: requireNonNegativeFinite$2(options.depth ?? 1, "sdf.pattern2d().stripes() depth")
|
|
39319
39320
|
});
|
|
39320
39321
|
}
|
|
39321
39322
|
/** Create an over-under woven relief pattern in UV space. */
|
|
39322
39323
|
overUnderWeave(options) {
|
|
39323
39324
|
return new Pattern2DImpl({
|
|
39324
39325
|
kind: "surfacePattern:overUnderWeave",
|
|
39325
|
-
spacing: normalizeVec2(options.spacing, "sdf.pattern2d().overUnderWeave() spacing", requirePositiveFinite$
|
|
39326
|
-
threadWidth: normalizeVec2(options.threadWidth, "sdf.pattern2d().overUnderWeave() threadWidth", requirePositiveFinite$
|
|
39327
|
-
depth: requireNonNegativeFinite$
|
|
39328
|
-
underScale: requireNonNegativeFinite$
|
|
39326
|
+
spacing: normalizeVec2(options.spacing, "sdf.pattern2d().overUnderWeave() spacing", requirePositiveFinite$2),
|
|
39327
|
+
threadWidth: normalizeVec2(options.threadWidth, "sdf.pattern2d().overUnderWeave() threadWidth", requirePositiveFinite$2),
|
|
39328
|
+
depth: requireNonNegativeFinite$2(options.depth ?? 0.8, "sdf.pattern2d().overUnderWeave() depth"),
|
|
39329
|
+
underScale: requireNonNegativeFinite$2(options.underScale ?? 0.15, "sdf.pattern2d().overUnderWeave() underScale")
|
|
39329
39330
|
});
|
|
39330
39331
|
}
|
|
39331
39332
|
}
|
|
@@ -39348,13 +39349,13 @@ function requireFinite$a(value, label) {
|
|
|
39348
39349
|
}
|
|
39349
39350
|
return value;
|
|
39350
39351
|
}
|
|
39351
|
-
function requirePositiveFinite$
|
|
39352
|
+
function requirePositiveFinite$2(value, label) {
|
|
39352
39353
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
39353
39354
|
throw new Error(`${label} must be a positive finite number. Received: ${String(value)}`);
|
|
39354
39355
|
}
|
|
39355
39356
|
return value;
|
|
39356
39357
|
}
|
|
39357
|
-
function requireNonNegativeFinite$
|
|
39358
|
+
function requireNonNegativeFinite$2(value, label) {
|
|
39358
39359
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
39359
39360
|
throw new Error(`${label} must be a non-negative finite number. Received: ${String(value)}`);
|
|
39360
39361
|
}
|
|
@@ -39472,7 +39473,7 @@ Fix: use a known preset or pass material props directly.`
|
|
|
39472
39473
|
material: validateMaterialProps(material)
|
|
39473
39474
|
};
|
|
39474
39475
|
}
|
|
39475
|
-
function requirePositiveFinite(value, label) {
|
|
39476
|
+
function requirePositiveFinite$1(value, label) {
|
|
39476
39477
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
39477
39478
|
throw new Error(`${label} must be a positive finite number. Received: ${String(value)}`);
|
|
39478
39479
|
}
|
|
@@ -39484,15 +39485,15 @@ function requirePositiveInteger(value, label) {
|
|
|
39484
39485
|
}
|
|
39485
39486
|
return value;
|
|
39486
39487
|
}
|
|
39487
|
-
function requireNonNegativeFinite(value, label) {
|
|
39488
|
+
function requireNonNegativeFinite$1(value, label) {
|
|
39488
39489
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
39489
39490
|
throw new Error(`${label} must be a non-negative finite number. Received: ${String(value)}`);
|
|
39490
39491
|
}
|
|
39491
39492
|
return value;
|
|
39492
39493
|
}
|
|
39493
39494
|
function resolveBlendRadius(input, label, fallback = 4) {
|
|
39494
|
-
if (typeof input === "number") return requirePositiveFinite(input, `${label} radius`);
|
|
39495
|
-
if ((input == null ? void 0 : input.radius) !== void 0) return requirePositiveFinite(input.radius, `${label} radius`);
|
|
39495
|
+
if (typeof input === "number") return requirePositiveFinite$1(input, `${label} radius`);
|
|
39496
|
+
if ((input == null ? void 0 : input.radius) !== void 0) return requirePositiveFinite$1(input.radius, `${label} radius`);
|
|
39496
39497
|
return fallback;
|
|
39497
39498
|
}
|
|
39498
39499
|
function formatExpressionNumber(value) {
|
|
@@ -39502,7 +39503,7 @@ function formatExpressionNumber(value) {
|
|
|
39502
39503
|
}
|
|
39503
39504
|
function roundedBoxNode(halfExtents, radius) {
|
|
39504
39505
|
const minHalf = Math.min(halfExtents[0], halfExtents[1], halfExtents[2]);
|
|
39505
|
-
const r = Math.min(requirePositiveFinite(radius, "SdfShape.round() radius"), Math.max(1e-3, minHalf - 1e-3));
|
|
39506
|
+
const r = Math.min(requirePositiveFinite$1(radius, "SdfShape.round() radius"), Math.max(1e-3, minHalf - 1e-3));
|
|
39506
39507
|
const inner = [halfExtents[0] - r, halfExtents[1] - r, halfExtents[2] - r];
|
|
39507
39508
|
const [ix, iy, iz] = inner.map(formatExpressionNumber);
|
|
39508
39509
|
const rr = formatExpressionNumber(r);
|
|
@@ -39745,9 +39746,9 @@ class SdfShape {
|
|
|
39745
39746
|
clipBox(x2, y2, z2) {
|
|
39746
39747
|
return this.intersect(
|
|
39747
39748
|
box$1(
|
|
39748
|
-
requirePositiveFinite(x2, "SdfShape.clipBox() x"),
|
|
39749
|
-
requirePositiveFinite(y2, "SdfShape.clipBox() y"),
|
|
39750
|
-
requirePositiveFinite(z2, "SdfShape.clipBox() z")
|
|
39749
|
+
requirePositiveFinite$1(x2, "SdfShape.clipBox() x"),
|
|
39750
|
+
requirePositiveFinite$1(y2, "SdfShape.clipBox() y"),
|
|
39751
|
+
requirePositiveFinite$1(z2, "SdfShape.clipBox() z")
|
|
39751
39752
|
)
|
|
39752
39753
|
);
|
|
39753
39754
|
}
|
|
@@ -39779,7 +39780,7 @@ class SdfShape {
|
|
|
39779
39780
|
return this.withNode({
|
|
39780
39781
|
kind: "sdf:smoothUnion",
|
|
39781
39782
|
children: [this._node, other._node],
|
|
39782
|
-
radius: requirePositiveFinite(radius, "SdfShape.smoothUnion() radius")
|
|
39783
|
+
radius: requirePositiveFinite$1(radius, "SdfShape.smoothUnion() radius")
|
|
39783
39784
|
});
|
|
39784
39785
|
}
|
|
39785
39786
|
/** Smooth difference — smoothly carves other from this. */
|
|
@@ -39787,7 +39788,7 @@ class SdfShape {
|
|
|
39787
39788
|
return this.withNode({
|
|
39788
39789
|
kind: "sdf:smoothDifference",
|
|
39789
39790
|
children: [this._node, other._node],
|
|
39790
|
-
radius: requirePositiveFinite(radius, "SdfShape.smoothSubtract() radius")
|
|
39791
|
+
radius: requirePositiveFinite$1(radius, "SdfShape.smoothSubtract() radius")
|
|
39791
39792
|
});
|
|
39792
39793
|
}
|
|
39793
39794
|
/** Smooth intersection — smoothly intersects. */
|
|
@@ -39795,7 +39796,7 @@ class SdfShape {
|
|
|
39795
39796
|
return this.withNode({
|
|
39796
39797
|
kind: "sdf:smoothIntersection",
|
|
39797
39798
|
children: [this._node, other._node],
|
|
39798
|
-
radius: requirePositiveFinite(radius, "SdfShape.smoothIntersect() radius")
|
|
39799
|
+
radius: requirePositiveFinite$1(radius, "SdfShape.smoothIntersect() radius")
|
|
39799
39800
|
});
|
|
39800
39801
|
}
|
|
39801
39802
|
/** Morph between this shape and another. t=0 → this, t=1 → other. */
|
|
@@ -39853,7 +39854,7 @@ class SdfShape {
|
|
|
39853
39854
|
}
|
|
39854
39855
|
/** Uniformly scale this SDF around the origin. */
|
|
39855
39856
|
scale(factor) {
|
|
39856
|
-
return this.withNode({ kind: "sdf:scale", child: this._node, factor: requirePositiveFinite(factor, "SdfShape.scale() factor") });
|
|
39857
|
+
return this.withNode({ kind: "sdf:scale", child: this._node, factor: requirePositiveFinite$1(factor, "SdfShape.scale() factor") });
|
|
39857
39858
|
}
|
|
39858
39859
|
// ── Domain operations ──
|
|
39859
39860
|
/** Twist around the Z axis. */
|
|
@@ -39866,15 +39867,15 @@ class SdfShape {
|
|
|
39866
39867
|
}
|
|
39867
39868
|
/** Bend around the Z axis with given radius. */
|
|
39868
39869
|
bend(radius) {
|
|
39869
|
-
return this.withNode({ kind: "sdf:bend", child: this._node, radius: requirePositiveFinite(radius, "SdfShape.bend() radius") });
|
|
39870
|
+
return this.withNode({ kind: "sdf:bend", child: this._node, radius: requirePositiveFinite$1(radius, "SdfShape.bend() radius") });
|
|
39870
39871
|
}
|
|
39871
39872
|
/** Repeat in space. Spacing of 0 on an axis means no repetition. Count of 0 = infinite. */
|
|
39872
39873
|
repeat(spacing, count) {
|
|
39873
39874
|
return this.withNode({
|
|
39874
39875
|
kind: "sdf:repeat",
|
|
39875
39876
|
child: this._node,
|
|
39876
|
-
spacing: requireFiniteVec3$
|
|
39877
|
-
count: count === void 0 ? [0, 0, 0] : requireFiniteVec3$
|
|
39877
|
+
spacing: requireFiniteVec3$4(spacing, "SdfShape.repeat() spacing"),
|
|
39878
|
+
count: count === void 0 ? [0, 0, 0] : requireFiniteVec3$4(count, "SdfShape.repeat() count")
|
|
39878
39879
|
});
|
|
39879
39880
|
}
|
|
39880
39881
|
/**
|
|
@@ -39889,7 +39890,7 @@ class SdfShape {
|
|
|
39889
39890
|
kind: "sdf:circularArray",
|
|
39890
39891
|
child: this._node,
|
|
39891
39892
|
count: requirePositiveInteger(count, "SdfShape.circularArray() count"),
|
|
39892
|
-
offset: requireNonNegativeFinite(offset2, "SdfShape.circularArray() offset")
|
|
39893
|
+
offset: requireNonNegativeFinite$1(offset2, "SdfShape.circularArray() offset")
|
|
39893
39894
|
});
|
|
39894
39895
|
}
|
|
39895
39896
|
/** Hollow out, keeping only a shell of given thickness. */
|
|
@@ -39897,7 +39898,7 @@ class SdfShape {
|
|
|
39897
39898
|
return this.withNode({
|
|
39898
39899
|
kind: "sdf:shell",
|
|
39899
39900
|
child: this._node,
|
|
39900
|
-
thickness: requirePositiveFinite(thickness, "SdfShape.shell() thickness")
|
|
39901
|
+
thickness: requirePositiveFinite$1(thickness, "SdfShape.shell() thickness")
|
|
39901
39902
|
});
|
|
39902
39903
|
}
|
|
39903
39904
|
/**
|
|
@@ -39977,70 +39978,70 @@ class SdfShape {
|
|
|
39977
39978
|
kind: "sdf:onion",
|
|
39978
39979
|
child: this._node,
|
|
39979
39980
|
layers: requirePositiveInteger(layers, "SdfShape.onion() layers"),
|
|
39980
|
-
thickness: requirePositiveFinite(thickness, "SdfShape.onion() thickness")
|
|
39981
|
+
thickness: requirePositiveFinite$1(thickness, "SdfShape.onion() thickness")
|
|
39981
39982
|
});
|
|
39982
39983
|
}
|
|
39983
39984
|
}
|
|
39984
39985
|
function sphere$1(radius) {
|
|
39985
|
-
return new SdfShape({ kind: "sdf:sphere", radius: requirePositiveFinite(radius, "sdf.sphere() radius") });
|
|
39986
|
+
return new SdfShape({ kind: "sdf:sphere", radius: requirePositiveFinite$1(radius, "sdf.sphere() radius") });
|
|
39986
39987
|
}
|
|
39987
39988
|
function box$1(x2, y2, z2) {
|
|
39988
39989
|
return new SdfShape({
|
|
39989
39990
|
kind: "sdf:box",
|
|
39990
39991
|
halfExtents: [
|
|
39991
|
-
requirePositiveFinite(x2, "sdf.box() x") / 2,
|
|
39992
|
-
requirePositiveFinite(y2, "sdf.box() y") / 2,
|
|
39993
|
-
requirePositiveFinite(z2, "sdf.box() z") / 2
|
|
39992
|
+
requirePositiveFinite$1(x2, "sdf.box() x") / 2,
|
|
39993
|
+
requirePositiveFinite$1(y2, "sdf.box() y") / 2,
|
|
39994
|
+
requirePositiveFinite$1(z2, "sdf.box() z") / 2
|
|
39994
39995
|
]
|
|
39995
39996
|
});
|
|
39996
39997
|
}
|
|
39997
39998
|
function cylinder$1(height, radius) {
|
|
39998
39999
|
return new SdfShape({
|
|
39999
40000
|
kind: "sdf:cylinder",
|
|
40000
|
-
height: requirePositiveFinite(height, "sdf.cylinder() height"),
|
|
40001
|
-
radius: requirePositiveFinite(radius, "sdf.cylinder() radius")
|
|
40001
|
+
height: requirePositiveFinite$1(height, "sdf.cylinder() height"),
|
|
40002
|
+
radius: requirePositiveFinite$1(radius, "sdf.cylinder() radius")
|
|
40002
40003
|
});
|
|
40003
40004
|
}
|
|
40004
40005
|
function torus$1(majorRadius, minorRadius) {
|
|
40005
40006
|
return new SdfShape({
|
|
40006
40007
|
kind: "sdf:torus",
|
|
40007
|
-
majorRadius: requirePositiveFinite(majorRadius, "sdf.torus() majorRadius"),
|
|
40008
|
-
minorRadius: requirePositiveFinite(minorRadius, "sdf.torus() minorRadius")
|
|
40008
|
+
majorRadius: requirePositiveFinite$1(majorRadius, "sdf.torus() majorRadius"),
|
|
40009
|
+
minorRadius: requirePositiveFinite$1(minorRadius, "sdf.torus() minorRadius")
|
|
40009
40010
|
});
|
|
40010
40011
|
}
|
|
40011
40012
|
function capsule(height, radius) {
|
|
40012
40013
|
return new SdfShape({
|
|
40013
40014
|
kind: "sdf:capsule",
|
|
40014
|
-
height: requirePositiveFinite(height, "sdf.capsule() height"),
|
|
40015
|
-
radius: requirePositiveFinite(radius, "sdf.capsule() radius")
|
|
40015
|
+
height: requirePositiveFinite$1(height, "sdf.capsule() height"),
|
|
40016
|
+
radius: requirePositiveFinite$1(radius, "sdf.capsule() radius")
|
|
40016
40017
|
});
|
|
40017
40018
|
}
|
|
40018
40019
|
function cone(height, radius) {
|
|
40019
40020
|
return new SdfShape({
|
|
40020
40021
|
kind: "sdf:cone",
|
|
40021
|
-
height: requirePositiveFinite(height, "sdf.cone() height"),
|
|
40022
|
-
radius: requirePositiveFinite(radius, "sdf.cone() radius")
|
|
40022
|
+
height: requirePositiveFinite$1(height, "sdf.cone() height"),
|
|
40023
|
+
radius: requirePositiveFinite$1(radius, "sdf.cone() radius")
|
|
40023
40024
|
});
|
|
40024
40025
|
}
|
|
40025
40026
|
function smoothUnion(a2, b, options) {
|
|
40026
40027
|
return new SdfShape({
|
|
40027
40028
|
kind: "sdf:smoothUnion",
|
|
40028
40029
|
children: [a2._node, b._node],
|
|
40029
|
-
radius: requirePositiveFinite(options.radius, "sdf.smoothUnion() radius")
|
|
40030
|
+
radius: requirePositiveFinite$1(options.radius, "sdf.smoothUnion() radius")
|
|
40030
40031
|
});
|
|
40031
40032
|
}
|
|
40032
40033
|
function smoothDifference(a2, b, options) {
|
|
40033
40034
|
return new SdfShape({
|
|
40034
40035
|
kind: "sdf:smoothDifference",
|
|
40035
40036
|
children: [a2._node, b._node],
|
|
40036
|
-
radius: requirePositiveFinite(options.radius, "sdf.smoothDifference() radius")
|
|
40037
|
+
radius: requirePositiveFinite$1(options.radius, "sdf.smoothDifference() radius")
|
|
40037
40038
|
});
|
|
40038
40039
|
}
|
|
40039
40040
|
function smoothIntersection(a2, b, options) {
|
|
40040
40041
|
return new SdfShape({
|
|
40041
40042
|
kind: "sdf:smoothIntersection",
|
|
40042
40043
|
children: [a2._node, b._node],
|
|
40043
|
-
radius: requirePositiveFinite(options.radius, "sdf.smoothIntersection() radius")
|
|
40044
|
+
radius: requirePositiveFinite$1(options.radius, "sdf.smoothIntersection() radius")
|
|
40044
40045
|
});
|
|
40045
40046
|
}
|
|
40046
40047
|
function morph(a2, b, t) {
|
|
@@ -40223,9 +40224,9 @@ function weave(options) {
|
|
|
40223
40224
|
});
|
|
40224
40225
|
}
|
|
40225
40226
|
function basketWeave(options) {
|
|
40226
|
-
const SP = requirePositiveFinite((options == null ? void 0 : options.spacing) ?? 3, "sdf.basketWeave() spacing");
|
|
40227
|
-
const TW = requirePositiveFinite((options == null ? void 0 : options.threadWidth) ?? 1.5, "sdf.basketWeave() threadWidth");
|
|
40228
|
-
const D2 = requireNonNegativeFinite((options == null ? void 0 : options.depth) ?? 0.8, "sdf.basketWeave() depth");
|
|
40227
|
+
const SP = requirePositiveFinite$1((options == null ? void 0 : options.spacing) ?? 3, "sdf.basketWeave() spacing");
|
|
40228
|
+
const TW = requirePositiveFinite$1((options == null ? void 0 : options.threadWidth) ?? 1.5, "sdf.basketWeave() threadWidth");
|
|
40229
|
+
const D2 = requireNonNegativeFinite$1((options == null ? void 0 : options.depth) ?? 0.8, "sdf.basketWeave() depth");
|
|
40229
40230
|
return pattern2d().overUnderWeave({ spacing: SP, threadWidth: TW, depth: D2 });
|
|
40230
40231
|
}
|
|
40231
40232
|
function fromFunction(fn, options) {
|
|
@@ -40254,18 +40255,18 @@ function twist(shape, degreesPerUnit) {
|
|
|
40254
40255
|
return shape.twist(requireFiniteNumber$1(degreesPerUnit, "sdf.twist() degreesPerUnit"));
|
|
40255
40256
|
}
|
|
40256
40257
|
function bend(shape, radius) {
|
|
40257
|
-
return shape.bend(requirePositiveFinite(radius, "sdf.bend() radius"));
|
|
40258
|
+
return shape.bend(requirePositiveFinite$1(radius, "sdf.bend() radius"));
|
|
40258
40259
|
}
|
|
40259
40260
|
function repeat(shape, spacing, count) {
|
|
40260
40261
|
return shape.repeat(
|
|
40261
|
-
requireFiniteVec3$
|
|
40262
|
-
count === void 0 ? void 0 : requireFiniteVec3$
|
|
40262
|
+
requireFiniteVec3$4(spacing, "sdf.repeat() spacing"),
|
|
40263
|
+
count === void 0 ? void 0 : requireFiniteVec3$4(count, "sdf.repeat() count")
|
|
40263
40264
|
);
|
|
40264
40265
|
}
|
|
40265
40266
|
function circularArray(shape, count, offset2 = 0) {
|
|
40266
40267
|
return shape.circularArray(
|
|
40267
40268
|
requirePositiveInteger(count, "sdf.circularArray() count"),
|
|
40268
|
-
requireNonNegativeFinite(offset2, "sdf.circularArray() offset")
|
|
40269
|
+
requireNonNegativeFinite$1(offset2, "sdf.circularArray() offset")
|
|
40269
40270
|
);
|
|
40270
40271
|
}
|
|
40271
40272
|
function resolveTpmsOptions(options) {
|
|
@@ -41065,11 +41066,11 @@ function mergeTopology(base, overlay) {
|
|
|
41065
41066
|
return merged;
|
|
41066
41067
|
}
|
|
41067
41068
|
const ANALYTIC_SURFACE_EPS = 1e-9;
|
|
41068
|
-
function vecLength$
|
|
41069
|
+
function vecLength$2(v) {
|
|
41069
41070
|
return Math.hypot(v[0], v[1], v[2]);
|
|
41070
41071
|
}
|
|
41071
41072
|
function normalizeVec3OrNull(v) {
|
|
41072
|
-
const length4 = vecLength$
|
|
41073
|
+
const length4 = vecLength$2(v);
|
|
41073
41074
|
return length4 > ANALYTIC_SURFACE_EPS ? [v[0] / length4, v[1] / length4, v[2] / length4] : null;
|
|
41074
41075
|
}
|
|
41075
41076
|
function dotVec3$1(a2, b) {
|
|
@@ -41121,9 +41122,9 @@ function matrixUniformDistanceScale(matrix) {
|
|
|
41121
41122
|
const xAxis = [matrix[0], matrix[1], matrix[2]];
|
|
41122
41123
|
const yAxis = [matrix[4], matrix[5], matrix[6]];
|
|
41123
41124
|
const zAxis = [matrix[8], matrix[9], matrix[10]];
|
|
41124
|
-
const sx = vecLength$
|
|
41125
|
-
const sy = vecLength$
|
|
41126
|
-
const sz = vecLength$
|
|
41125
|
+
const sx = vecLength$2(xAxis);
|
|
41126
|
+
const sy = vecLength$2(yAxis);
|
|
41127
|
+
const sz = vecLength$2(zAxis);
|
|
41127
41128
|
if (sx <= ANALYTIC_SURFACE_EPS || sy <= ANALYTIC_SURFACE_EPS || sz <= ANALYTIC_SURFACE_EPS) return null;
|
|
41128
41129
|
if (!sameScalar(sx, sy) || !sameScalar(sx, sz)) return null;
|
|
41129
41130
|
const orthoTolerance = ANALYTIC_SURFACE_EPS * Math.max(1, sx * sx);
|
|
@@ -41811,7 +41812,7 @@ function withBaseDimensionsAndMergedSourceSpans(base, sources, out, preserveOutp
|
|
|
41811
41812
|
return result;
|
|
41812
41813
|
}
|
|
41813
41814
|
function resolveRotationPoint(shape, point2) {
|
|
41814
|
-
if (Array.isArray(point2)) return requireFiniteVec3$
|
|
41815
|
+
if (Array.isArray(point2)) return requireFiniteVec3$4(point2, "rotateAroundTo(): point");
|
|
41815
41816
|
return shape.referencePoint(point2);
|
|
41816
41817
|
}
|
|
41817
41818
|
function setShapeDimensions(shape, dims, options = {}) {
|
|
@@ -42731,8 +42732,8 @@ class Shape {
|
|
|
42731
42732
|
* ```
|
|
42732
42733
|
*/
|
|
42733
42734
|
placeReference(ref, target, offset2) {
|
|
42734
|
-
const targetPoint = requireFiniteVec3$
|
|
42735
|
-
const offsetPoint = offset2 === void 0 ? void 0 : requireFiniteVec3$
|
|
42735
|
+
const targetPoint = requireFiniteVec3$4(target, "Shape.placeReference() target");
|
|
42736
|
+
const offsetPoint = offset2 === void 0 ? void 0 : requireFiniteVec3$4(offset2, "Shape.placeReference() offset");
|
|
42736
42737
|
const sourcePoint = this.referencePoint(ref);
|
|
42737
42738
|
let dx = targetPoint[0] - sourcePoint[0];
|
|
42738
42739
|
let dy = targetPoint[1] - sourcePoint[1];
|
|
@@ -42837,7 +42838,7 @@ class Shape {
|
|
|
42837
42838
|
/** Scale the shape uniformly or per-axis from an explicit pivot point. */
|
|
42838
42839
|
scaleAround(pivot, v) {
|
|
42839
42840
|
const scale2 = requireNonZeroFiniteScale3(v, "Shape.scaleAround() scale");
|
|
42840
|
-
const scalePivot = requireFiniteVec3$
|
|
42841
|
+
const scalePivot = requireFiniteVec3$4(pivot, "Shape.scaleAround() pivot");
|
|
42841
42842
|
if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
|
|
42842
42843
|
const nextPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
|
|
42843
42844
|
kind: "scale",
|
|
@@ -42875,7 +42876,7 @@ class Shape {
|
|
|
42875
42876
|
}
|
|
42876
42877
|
/** Mirror across a plane through an explicit point, defined by its normal vector. */
|
|
42877
42878
|
mirrorThrough(point2, normal) {
|
|
42878
|
-
const mirrorPoint = requireFiniteVec3$
|
|
42879
|
+
const mirrorPoint = requireFiniteVec3$4(point2, "Shape.mirrorThrough() point");
|
|
42879
42880
|
const mirrorNormal = requireNonZeroFiniteVec3(normal, "Shape.mirrorThrough() normal");
|
|
42880
42881
|
if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
|
|
42881
42882
|
const transformedPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
|
|
@@ -42933,7 +42934,7 @@ class Shape {
|
|
|
42933
42934
|
rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
|
|
42934
42935
|
const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundAxis() axis");
|
|
42935
42936
|
const degrees2 = requireFiniteNumber$1(angleDeg, "Shape.rotateAroundAxis() angleDeg");
|
|
42936
|
-
const rotatePivot = requireFiniteVec3$
|
|
42937
|
+
const rotatePivot = requireFiniteVec3$4(pivot, "Shape.rotateAroundAxis() pivot");
|
|
42937
42938
|
const len2 = Math.sqrt(rotateAxis[0] ** 2 + rotateAxis[1] ** 2 + rotateAxis[2] ** 2) || 1;
|
|
42938
42939
|
const normalizedAxis = [rotateAxis[0] / len2, rotateAxis[1] / len2, rotateAxis[2] / len2];
|
|
42939
42940
|
const matrix = rotationAroundAxisMatrix(normalizedAxis, degrees2, rotatePivot);
|
|
@@ -42958,7 +42959,7 @@ class Shape {
|
|
|
42958
42959
|
*/
|
|
42959
42960
|
rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
|
|
42960
42961
|
const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundTo() axis");
|
|
42961
|
-
const rotatePivot = requireFiniteVec3$
|
|
42962
|
+
const rotatePivot = requireFiniteVec3$4(pivot, "Shape.rotateAroundTo() pivot");
|
|
42962
42963
|
const moving = resolveRotationPoint(this, movingPoint);
|
|
42963
42964
|
const target = resolveRotationPoint(this, targetPoint);
|
|
42964
42965
|
const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
|
|
@@ -43281,7 +43282,7 @@ class Shape {
|
|
|
43281
43282
|
const sp = this.referencePoint(selfAnchor);
|
|
43282
43283
|
let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
|
|
43283
43284
|
if (offset2) {
|
|
43284
|
-
const offsetPoint = requireFiniteVec3$
|
|
43285
|
+
const offsetPoint = requireFiniteVec3$4(offset2, "Shape.attachTo() offset");
|
|
43285
43286
|
dx += offsetPoint[0];
|
|
43286
43287
|
dy += offsetPoint[1];
|
|
43287
43288
|
dz += offsetPoint[2];
|
|
@@ -45533,6 +45534,107 @@ function collectJointsView(options = {}, base = null, motionSource) {
|
|
|
45533
45534
|
function jointsView(options = {}) {
|
|
45534
45535
|
_collected$8 = collectJointsView(options, _collected$8);
|
|
45535
45536
|
}
|
|
45537
|
+
function requireFiniteVec3$2(input, label) {
|
|
45538
|
+
if (!Array.isArray(input) || input.length !== 3) throw new Error(`${label} must be a [x, y, z] tuple`);
|
|
45539
|
+
const out = [input[0], input[1], input[2]];
|
|
45540
|
+
if (!Number.isFinite(out[0]) || !Number.isFinite(out[1]) || !Number.isFinite(out[2])) {
|
|
45541
|
+
throw new Error(`${label} must contain finite numbers`);
|
|
45542
|
+
}
|
|
45543
|
+
return out;
|
|
45544
|
+
}
|
|
45545
|
+
function vecDot$1(a2, b) {
|
|
45546
|
+
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
45547
|
+
}
|
|
45548
|
+
function vecCross$1(a2, b) {
|
|
45549
|
+
return [a2[1] * b[2] - a2[2] * b[1], a2[2] * b[0] - a2[0] * b[2], a2[0] * b[1] - a2[1] * b[0]];
|
|
45550
|
+
}
|
|
45551
|
+
function vecLength$1(v) {
|
|
45552
|
+
return Math.hypot(v[0], v[1], v[2]);
|
|
45553
|
+
}
|
|
45554
|
+
function vecNormalize$1(v, label) {
|
|
45555
|
+
const length4 = vecLength$1(v);
|
|
45556
|
+
if (length4 < 1e-10) throw new Error(`${label} must be non-zero`);
|
|
45557
|
+
return [v[0] / length4, v[1] / length4, v[2] / length4];
|
|
45558
|
+
}
|
|
45559
|
+
function orthonormalizeFrame(axisInput, upInput) {
|
|
45560
|
+
const axis = vecNormalize$1(axisInput, "frame axis");
|
|
45561
|
+
if (vecLength$1(upInput) < 1e-10) throw new Error("frame up vector must be non-zero");
|
|
45562
|
+
const upProjection = vecDot$1(upInput, axis);
|
|
45563
|
+
const upOrtho = [upInput[0] - upProjection * axis[0], upInput[1] - upProjection * axis[1], upInput[2] - upProjection * axis[2]];
|
|
45564
|
+
if (vecLength$1(upOrtho) < 1e-10) throw new Error("frame axis and up must not be parallel");
|
|
45565
|
+
const up = vecNormalize$1(upOrtho, "frame up vector");
|
|
45566
|
+
const right = vecNormalize$1(vecCross$1(up, axis), "frame right vector");
|
|
45567
|
+
return { axis, up, right };
|
|
45568
|
+
}
|
|
45569
|
+
function frameTransform(origin, axisInput, upInput) {
|
|
45570
|
+
const { axis, up, right } = orthonormalizeFrame(axisInput, upInput);
|
|
45571
|
+
return Transform.from([
|
|
45572
|
+
right[0],
|
|
45573
|
+
right[1],
|
|
45574
|
+
right[2],
|
|
45575
|
+
0,
|
|
45576
|
+
up[0],
|
|
45577
|
+
up[1],
|
|
45578
|
+
up[2],
|
|
45579
|
+
0,
|
|
45580
|
+
axis[0],
|
|
45581
|
+
axis[1],
|
|
45582
|
+
axis[2],
|
|
45583
|
+
0,
|
|
45584
|
+
origin[0],
|
|
45585
|
+
origin[1],
|
|
45586
|
+
origin[2],
|
|
45587
|
+
1
|
|
45588
|
+
]);
|
|
45589
|
+
}
|
|
45590
|
+
function normalizeAssemblyFrame(name, input) {
|
|
45591
|
+
if (!input || typeof input !== "object") throw new Error(`frame("${name}") options are required`);
|
|
45592
|
+
const origin = requireFiniteVec3$2(input.origin, `frame("${name}") origin`);
|
|
45593
|
+
const rawAxis = requireFiniteVec3$2(input.axis, `frame("${name}") axis`);
|
|
45594
|
+
const rawUp = requireFiniteVec3$2(input.up, `frame("${name}") up`);
|
|
45595
|
+
const { axis, up } = orthonormalizeFrame(rawAxis, rawUp);
|
|
45596
|
+
return {
|
|
45597
|
+
name,
|
|
45598
|
+
origin,
|
|
45599
|
+
axis,
|
|
45600
|
+
up,
|
|
45601
|
+
fixed: input.fixed ?? false,
|
|
45602
|
+
metadata: input.metadata ? { ...input.metadata } : void 0
|
|
45603
|
+
};
|
|
45604
|
+
}
|
|
45605
|
+
function cloneAssemblyFrame(frame) {
|
|
45606
|
+
return {
|
|
45607
|
+
name: frame.name,
|
|
45608
|
+
origin: [...frame.origin],
|
|
45609
|
+
axis: [...frame.axis],
|
|
45610
|
+
up: [...frame.up],
|
|
45611
|
+
fixed: frame.fixed,
|
|
45612
|
+
metadata: frame.metadata ? { ...frame.metadata } : void 0
|
|
45613
|
+
};
|
|
45614
|
+
}
|
|
45615
|
+
function cloneAssemblyFrameEdge(edge) {
|
|
45616
|
+
return {
|
|
45617
|
+
name: edge.name,
|
|
45618
|
+
a: edge.a,
|
|
45619
|
+
b: edge.b,
|
|
45620
|
+
metadata: edge.metadata ? { ...edge.metadata } : void 0
|
|
45621
|
+
};
|
|
45622
|
+
}
|
|
45623
|
+
function frameDefTransform(frame) {
|
|
45624
|
+
return frameTransform(frame.origin, frame.axis, frame.up);
|
|
45625
|
+
}
|
|
45626
|
+
function frameFromTransform(frame, transform) {
|
|
45627
|
+
return {
|
|
45628
|
+
...cloneAssemblyFrame(frame),
|
|
45629
|
+
origin: transform.point([0, 0, 0]),
|
|
45630
|
+
up: vecNormalize$1(transform.vector([0, 1, 0]), `solved frame "${frame.name}" up`),
|
|
45631
|
+
axis: vecNormalize$1(transform.vector([0, 0, 1]), `solved frame "${frame.name}" axis`),
|
|
45632
|
+
transform
|
|
45633
|
+
};
|
|
45634
|
+
}
|
|
45635
|
+
function relativeFrameTransform(parent, child) {
|
|
45636
|
+
return composeChain(frameDefTransform(child), frameDefTransform(parent).inverse());
|
|
45637
|
+
}
|
|
45536
45638
|
var define_process_env_default$1 = {};
|
|
45537
45639
|
let collectedAssemblies = [];
|
|
45538
45640
|
function resetCollectedAssemblies() {
|
|
@@ -45666,12 +45768,22 @@ function motionTransform(joint2, value) {
|
|
|
45666
45768
|
const dz = joint2.axis[2] * value;
|
|
45667
45769
|
return Transform.identity().translate(dx, dy, dz);
|
|
45668
45770
|
}
|
|
45771
|
+
function frameJointMotionTransform(joint2, value) {
|
|
45772
|
+
if (joint2.type === "fixed") return Transform.identity();
|
|
45773
|
+
if (joint2.type === "revolute") return Transform.rotationAxis([0, 0, 1], value);
|
|
45774
|
+
return Transform.translation(0, 0, value);
|
|
45775
|
+
}
|
|
45669
45776
|
function clampJointValue(joint2, value) {
|
|
45670
45777
|
let clamped = Number.isFinite(value) ? value : joint2.defaultValue;
|
|
45671
45778
|
if (joint2.min != null) clamped = Math.max(joint2.min, clamped);
|
|
45672
45779
|
if (joint2.max != null) clamped = Math.min(joint2.max, clamped);
|
|
45673
45780
|
return { value: clamped, wasClamped: clamped !== value };
|
|
45674
45781
|
}
|
|
45782
|
+
function clampFrameJointDefault(jointName, value, min2, max2) {
|
|
45783
|
+
if (min2 !== void 0 && value < min2) throw new Error(`Frame joint "${jointName}" default must be >= min`);
|
|
45784
|
+
if (max2 !== void 0 && value > max2) throw new Error(`Frame joint "${jointName}" default must be <= max`);
|
|
45785
|
+
return value;
|
|
45786
|
+
}
|
|
45675
45787
|
function finiteVec3(input, label) {
|
|
45676
45788
|
if (!Array.isArray(input) || input.length !== 3) throw new Error(`${label} must be a [x, y, z] tuple`);
|
|
45677
45789
|
const out = [input[0], input[1], input[2]];
|
|
@@ -45683,6 +45795,9 @@ function finiteVec3(input, label) {
|
|
|
45683
45795
|
function cloneVec3$3(v) {
|
|
45684
45796
|
return [v[0], v[1], v[2]];
|
|
45685
45797
|
}
|
|
45798
|
+
function cloneAngleReference(reference) {
|
|
45799
|
+
return reference ? { kind: "worldDirection", direction: cloneVec3$3(reference.direction) } : void 0;
|
|
45800
|
+
}
|
|
45686
45801
|
function vecDistance(a2, b) {
|
|
45687
45802
|
return Math.hypot(b[0] - a2[0], b[1] - a2[1], b[2] - a2[2]);
|
|
45688
45803
|
}
|
|
@@ -45702,12 +45817,10 @@ function vecNormalize(a2) {
|
|
|
45702
45817
|
const len2 = vecLength(a2);
|
|
45703
45818
|
return len2 < 1e-12 ? [0, 0, 0] : [a2[0] / len2, a2[1] / len2, a2[2] / len2];
|
|
45704
45819
|
}
|
|
45705
|
-
function
|
|
45706
|
-
const
|
|
45707
|
-
const bc = vecSub(c2, b);
|
|
45708
|
-
const cross2 = vecCross(ba, bc);
|
|
45820
|
+
function signedAngleBetweenVectorsAboutAxis(reference, target, axisUnit) {
|
|
45821
|
+
const cross2 = vecCross(reference, target);
|
|
45709
45822
|
const sin2 = vecDot(cross2, axisUnit);
|
|
45710
|
-
const cos2 = vecDot(
|
|
45823
|
+
const cos2 = vecDot(reference, target);
|
|
45711
45824
|
return Math.atan2(sin2, cos2) * 180 / Math.PI;
|
|
45712
45825
|
}
|
|
45713
45826
|
function normalizeAngleDeltaDeg(value) {
|
|
@@ -45828,7 +45941,7 @@ function deriveExplodeHintsFromMates(constraints, result, bodies, ctx) {
|
|
|
45828
45941
|
return hints;
|
|
45829
45942
|
}
|
|
45830
45943
|
class SolvedAssembly {
|
|
45831
|
-
constructor(name, parts, transforms, jointValues, solveWarnings, _mateMetadata = null, _kinematics = null, usedPortRefs) {
|
|
45944
|
+
constructor(name, parts, transforms, jointValues, solveWarnings, _mateMetadata = null, _kinematics = null, _frames = /* @__PURE__ */ new Map(), usedPortRefs) {
|
|
45832
45945
|
__publicField(this, "_usedPortRefs");
|
|
45833
45946
|
this.name = name;
|
|
45834
45947
|
this.parts = parts;
|
|
@@ -45837,6 +45950,7 @@ class SolvedAssembly {
|
|
|
45837
45950
|
this.solveWarnings = solveWarnings;
|
|
45838
45951
|
this._mateMetadata = _mateMetadata;
|
|
45839
45952
|
this._kinematics = _kinematics;
|
|
45953
|
+
this._frames = _frames;
|
|
45840
45954
|
this._usedPortRefs = usedPortRefs ?? /* @__PURE__ */ new Set();
|
|
45841
45955
|
}
|
|
45842
45956
|
/** Return any warnings generated during solve (clamped joints, unconverged mates, etc.). */
|
|
@@ -45862,7 +45976,7 @@ class SolvedAssembly {
|
|
|
45862
45976
|
var _a3;
|
|
45863
45977
|
return ((_a3 = this._mateMetadata) == null ? void 0 : _a3.converged) ?? null;
|
|
45864
45978
|
}
|
|
45865
|
-
/** Solved assembly-native kinematic
|
|
45979
|
+
/** Solved assembly-native kinematic or frame-edge overlay data, or null when no rig overlay data was declared. */
|
|
45866
45980
|
get kinematics() {
|
|
45867
45981
|
return this._kinematics;
|
|
45868
45982
|
}
|
|
@@ -45873,6 +45987,19 @@ class SolvedAssembly {
|
|
|
45873
45987
|
if (!link) throw new Error(`Unknown kinematic link "${linkName}"`);
|
|
45874
45988
|
return cloneVec3$3(link.position);
|
|
45875
45989
|
}
|
|
45990
|
+
/** Return the solved world transform for a named rig frame. */
|
|
45991
|
+
getFrame(frameName) {
|
|
45992
|
+
const frame = this._frames.get(frameName);
|
|
45993
|
+
if (!frame) throw new Error(`Unknown frame "${frameName}"`);
|
|
45994
|
+
return frame.transform;
|
|
45995
|
+
}
|
|
45996
|
+
/** Return solved rig frames, including origin, axis, up, and transform. */
|
|
45997
|
+
get frames() {
|
|
45998
|
+
return [...this._frames.values()].map((frame) => ({
|
|
45999
|
+
...cloneAssemblyFrame(frame),
|
|
46000
|
+
transform: frame.transform
|
|
46001
|
+
}));
|
|
46002
|
+
}
|
|
45876
46003
|
/**
|
|
45877
46004
|
* Return the world-space `Transform` for the named part at the solved pose.
|
|
45878
46005
|
*
|
|
@@ -46125,14 +46252,19 @@ class Assembly {
|
|
|
46125
46252
|
__publicField(this, "parts", /* @__PURE__ */ new Map());
|
|
46126
46253
|
__publicField(this, "joints", /* @__PURE__ */ new Map());
|
|
46127
46254
|
__publicField(this, "jointCouplings", /* @__PURE__ */ new Map());
|
|
46255
|
+
__publicField(this, "frames", /* @__PURE__ */ new Map());
|
|
46256
|
+
__publicField(this, "frameJoints", /* @__PURE__ */ new Map());
|
|
46257
|
+
__publicField(this, "frameEdges", /* @__PURE__ */ new Map());
|
|
46128
46258
|
__publicField(this, "links", /* @__PURE__ */ new Map());
|
|
46129
46259
|
__publicField(this, "linkEdges", /* @__PURE__ */ new Map());
|
|
46130
46260
|
__publicField(this, "linkAngles", /* @__PURE__ */ new Map());
|
|
46261
|
+
__publicField(this, "derivedLinks", /* @__PURE__ */ new Map());
|
|
46131
46262
|
__publicField(this, "_mateFns", []);
|
|
46132
46263
|
__publicField(this, "_refs", createPlacementReferences());
|
|
46133
46264
|
__publicField(this, "_portsByPart", /* @__PURE__ */ new Map());
|
|
46134
46265
|
__publicField(this, "_usedPortRefs", /* @__PURE__ */ new Set());
|
|
46135
46266
|
__publicField(this, "_connectCounter", 0);
|
|
46267
|
+
__publicField(this, "_frameEdgeCounter", 0);
|
|
46136
46268
|
__publicField(this, "_linkEdgeCounter", 0);
|
|
46137
46269
|
__publicField(this, "_linkAngleCounter", 0);
|
|
46138
46270
|
this.name = name;
|
|
@@ -46142,6 +46274,7 @@ class Assembly {
|
|
|
46142
46274
|
const next = new Assembly(this.name);
|
|
46143
46275
|
next._refs = clonePlacementReferences(this._refs);
|
|
46144
46276
|
next._connectCounter = this._connectCounter;
|
|
46277
|
+
next._frameEdgeCounter = this._frameEdgeCounter;
|
|
46145
46278
|
next._linkEdgeCounter = this._linkEdgeCounter;
|
|
46146
46279
|
next._linkAngleCounter = this._linkAngleCounter;
|
|
46147
46280
|
next._mateFns.push(...this._mateFns);
|
|
@@ -46151,7 +46284,8 @@ class Assembly {
|
|
|
46151
46284
|
part: record.part,
|
|
46152
46285
|
base: record.base,
|
|
46153
46286
|
metadata: record.metadata ? { ...record.metadata } : void 0,
|
|
46154
|
-
mates: record.mates.map((mate) => ({ ...mate }))
|
|
46287
|
+
mates: record.mates.map((mate) => ({ ...mate })),
|
|
46288
|
+
bindToFrame: record.bindToFrame
|
|
46155
46289
|
});
|
|
46156
46290
|
}
|
|
46157
46291
|
for (const [name, joint2] of this.joints) {
|
|
@@ -46180,6 +46314,27 @@ class Assembly {
|
|
|
46180
46314
|
offset: coupling.offset
|
|
46181
46315
|
});
|
|
46182
46316
|
}
|
|
46317
|
+
for (const [name, frame] of this.frames) {
|
|
46318
|
+
next.frames.set(name, cloneAssemblyFrame(frame));
|
|
46319
|
+
}
|
|
46320
|
+
for (const [name, joint2] of this.frameJoints) {
|
|
46321
|
+
next.frameJoints.set(name, {
|
|
46322
|
+
name: joint2.name,
|
|
46323
|
+
type: joint2.type,
|
|
46324
|
+
parent: joint2.parent,
|
|
46325
|
+
child: joint2.child,
|
|
46326
|
+
rest: joint2.rest,
|
|
46327
|
+
min: joint2.min,
|
|
46328
|
+
max: joint2.max,
|
|
46329
|
+
defaultValue: joint2.defaultValue,
|
|
46330
|
+
unit: joint2.unit,
|
|
46331
|
+
control: joint2.control,
|
|
46332
|
+
metadata: joint2.metadata ? { ...joint2.metadata } : void 0
|
|
46333
|
+
});
|
|
46334
|
+
}
|
|
46335
|
+
for (const [name, edge] of this.frameEdges) {
|
|
46336
|
+
next.frameEdges.set(name, cloneAssemblyFrameEdge(edge));
|
|
46337
|
+
}
|
|
46183
46338
|
for (const [name, link] of this.links) {
|
|
46184
46339
|
next.links.set(name, {
|
|
46185
46340
|
name: link.name,
|
|
@@ -46207,6 +46362,7 @@ class Assembly {
|
|
|
46207
46362
|
a: angle.a,
|
|
46208
46363
|
b: angle.b,
|
|
46209
46364
|
c: angle.c,
|
|
46365
|
+
reference: cloneAngleReference(angle.reference),
|
|
46210
46366
|
target: angle.target,
|
|
46211
46367
|
min: angle.min,
|
|
46212
46368
|
max: angle.max,
|
|
@@ -46214,6 +46370,9 @@ class Assembly {
|
|
|
46214
46370
|
metadata: angle.metadata ? { ...angle.metadata } : void 0
|
|
46215
46371
|
});
|
|
46216
46372
|
}
|
|
46373
|
+
for (const [name, derived] of this.derivedLinks) {
|
|
46374
|
+
next.derivedLinks.set(name, { ...derived });
|
|
46375
|
+
}
|
|
46217
46376
|
for (const [partName, ports] of this._portsByPart) {
|
|
46218
46377
|
next._portsByPart.set(partName, clonePortMap(ports));
|
|
46219
46378
|
}
|
|
@@ -46351,6 +46510,129 @@ class Assembly {
|
|
|
46351
46510
|
port: resolved.connector
|
|
46352
46511
|
};
|
|
46353
46512
|
}
|
|
46513
|
+
/**
|
|
46514
|
+
* Add a named rig frame to the assembly.
|
|
46515
|
+
*
|
|
46516
|
+
* A frame is a solved pose: `origin` plus orientation. `axis` is the frame's
|
|
46517
|
+
* primary direction and `up` fixes roll around that axis. Use frames for
|
|
46518
|
+
* robot links, joint axes, and parts that must carry orientation. Use
|
|
46519
|
+
* `link()` for solved points in distance/angle graphs.
|
|
46520
|
+
*
|
|
46521
|
+
* @category Assembly
|
|
46522
|
+
*/
|
|
46523
|
+
frame(name, options) {
|
|
46524
|
+
const id = typeof name === "string" ? name.trim() : "";
|
|
46525
|
+
if (!id) throw new Error("frame() name must be non-empty");
|
|
46526
|
+
if (this.frames.has(id)) throw new Error(`Frame "${id}" already exists`);
|
|
46527
|
+
this.frames.set(id, normalizeAssemblyFrame(id, options));
|
|
46528
|
+
return this;
|
|
46529
|
+
}
|
|
46530
|
+
resolveFrameJointEndpoint(jointName, value, role) {
|
|
46531
|
+
const frameName = typeof value === "string" ? value.trim() : "";
|
|
46532
|
+
if (!frameName) throw new Error(`Frame joint "${jointName}" ${role} must be non-empty`);
|
|
46533
|
+
const frame = this.frames.get(frameName);
|
|
46534
|
+
if (!frame) throw new Error(`Frame joint "${jointName}" unknown ${role} frame "${frameName}"`);
|
|
46535
|
+
return frame;
|
|
46536
|
+
}
|
|
46537
|
+
addFrameJoint(type, name, options) {
|
|
46538
|
+
const id = typeof name === "string" ? name.trim() : "";
|
|
46539
|
+
if (!id) throw new Error(`${type}Joint() name must be non-empty`);
|
|
46540
|
+
if (!options || typeof options !== "object") throw new Error(`${type}Joint("${id}") options are required`);
|
|
46541
|
+
if (this.frameJoints.has(id) || this.joints.has(id)) throw new Error(`Joint "${id}" already exists`);
|
|
46542
|
+
const parent = this.resolveFrameJointEndpoint(id, options.parent, "parent");
|
|
46543
|
+
const child = this.resolveFrameJointEndpoint(id, options.child, "child");
|
|
46544
|
+
if (parent.name === child.name) throw new Error(`Frame joint "${id}" cannot connect a frame to itself`);
|
|
46545
|
+
if (child.fixed) throw new Error(`Frame joint "${id}" cannot drive fixed frame "${child.name}"`);
|
|
46546
|
+
for (const joint2 of this.frameJoints.values()) {
|
|
46547
|
+
if (joint2.child === child.name) throw new Error(`Frame "${child.name}" already has parent joint "${joint2.name}"`);
|
|
46548
|
+
}
|
|
46549
|
+
const movingOptions = options;
|
|
46550
|
+
const min2 = movingOptions.min;
|
|
46551
|
+
const max2 = movingOptions.max;
|
|
46552
|
+
if (min2 !== void 0 && !Number.isFinite(min2)) throw new Error(`Frame joint "${id}" min must be finite`);
|
|
46553
|
+
if (max2 !== void 0 && !Number.isFinite(max2)) throw new Error(`Frame joint "${id}" max must be finite`);
|
|
46554
|
+
if (min2 !== void 0 && max2 !== void 0 && min2 > max2) throw new Error(`Frame joint "${id}" min must be <= max`);
|
|
46555
|
+
const defaultValue = type === "fixed" ? 0 : movingOptions.default ?? 0;
|
|
46556
|
+
if (!Number.isFinite(defaultValue)) throw new Error(`Frame joint "${id}" default must be finite`);
|
|
46557
|
+
this.frameJoints.set(id, {
|
|
46558
|
+
name: id,
|
|
46559
|
+
type,
|
|
46560
|
+
parent: parent.name,
|
|
46561
|
+
child: child.name,
|
|
46562
|
+
rest: relativeFrameTransform(parent, child),
|
|
46563
|
+
min: min2,
|
|
46564
|
+
max: max2,
|
|
46565
|
+
defaultValue: clampFrameJointDefault(id, defaultValue, min2, max2),
|
|
46566
|
+
unit: movingOptions.unit,
|
|
46567
|
+
control: type !== "fixed" && movingOptions.control !== false,
|
|
46568
|
+
metadata: options.metadata ? { ...options.metadata } : void 0
|
|
46569
|
+
});
|
|
46570
|
+
return this;
|
|
46571
|
+
}
|
|
46572
|
+
/**
|
|
46573
|
+
* Rigidly attach a child rig frame to a parent rig frame.
|
|
46574
|
+
*
|
|
46575
|
+
* Fixed joints carry frame hierarchy but do not expose a Motion control.
|
|
46576
|
+
*
|
|
46577
|
+
* @category Assembly
|
|
46578
|
+
*/
|
|
46579
|
+
fixedJoint(name, options) {
|
|
46580
|
+
return this.addFrameJoint("fixed", name, options);
|
|
46581
|
+
}
|
|
46582
|
+
/**
|
|
46583
|
+
* Add a revolute rig-frame joint.
|
|
46584
|
+
*
|
|
46585
|
+
* The child frame rotates around the parent frame's `axis` direction. Moving
|
|
46586
|
+
* frame joints appear in Motion by default; pass `control: false` to keep the
|
|
46587
|
+
* joint solved at its default value without showing a Motion control.
|
|
46588
|
+
*
|
|
46589
|
+
* @category Assembly
|
|
46590
|
+
*/
|
|
46591
|
+
revoluteJoint(name, options) {
|
|
46592
|
+
return this.addFrameJoint("revolute", name, options);
|
|
46593
|
+
}
|
|
46594
|
+
/**
|
|
46595
|
+
* Add a prismatic rig-frame joint.
|
|
46596
|
+
*
|
|
46597
|
+
* The child frame translates along the parent frame's `axis` direction. Moving
|
|
46598
|
+
* frame joints appear in Motion by default; pass `control: false` to keep the
|
|
46599
|
+
* joint solved at its default value without showing a Motion control.
|
|
46600
|
+
*
|
|
46601
|
+
* @category Assembly
|
|
46602
|
+
*/
|
|
46603
|
+
prismaticJoint(name, options) {
|
|
46604
|
+
return this.addFrameJoint("prismatic", name, options);
|
|
46605
|
+
}
|
|
46606
|
+
/**
|
|
46607
|
+
* Add a visual skeleton edge between two rig frame origins.
|
|
46608
|
+
*
|
|
46609
|
+
* Frame edges follow the solved frame poses produced by `fixedJoint()`,
|
|
46610
|
+
* `revoluteJoint()`, and `prismaticJoint()`. They do not add constraints,
|
|
46611
|
+
* degrees of freedom, parts, or geometry; use them to make a frame-only rig
|
|
46612
|
+
* readable in the Motion/rig inspection overlay.
|
|
46613
|
+
*
|
|
46614
|
+
* @category Assembly
|
|
46615
|
+
*/
|
|
46616
|
+
edgeBetweenFrames(a2, b, options = {}) {
|
|
46617
|
+
const frameA = typeof a2 === "string" ? a2.trim() : "";
|
|
46618
|
+
const frameB = typeof b === "string" ? b.trim() : "";
|
|
46619
|
+
if (!frameA) throw new Error("edgeBetweenFrames() first frame must be non-empty");
|
|
46620
|
+
if (!frameB) throw new Error("edgeBetweenFrames() second frame must be non-empty");
|
|
46621
|
+
if (!this.frames.has(frameA)) throw new Error(`edgeBetweenFrames() unknown frame "${frameA}"`);
|
|
46622
|
+
if (!this.frames.has(frameB)) throw new Error(`edgeBetweenFrames() unknown frame "${frameB}"`);
|
|
46623
|
+
if (frameA === frameB) throw new Error("edgeBetweenFrames() requires two different frames");
|
|
46624
|
+
if (options.name !== void 0 && typeof options.name !== "string") throw new Error("edgeBetweenFrames() name must be a string");
|
|
46625
|
+
const name = (options.name ?? `${frameA}_${frameB}_${this._frameEdgeCounter++}`).trim();
|
|
46626
|
+
if (!name) throw new Error("edgeBetweenFrames() name must be non-empty");
|
|
46627
|
+
if (this.frameEdges.has(name)) throw new Error(`Frame edge "${name}" already exists`);
|
|
46628
|
+
this.frameEdges.set(name, {
|
|
46629
|
+
name,
|
|
46630
|
+
a: frameA,
|
|
46631
|
+
b: frameB,
|
|
46632
|
+
metadata: options.metadata ? { ...options.metadata } : void 0
|
|
46633
|
+
});
|
|
46634
|
+
return this;
|
|
46635
|
+
}
|
|
46354
46636
|
/**
|
|
46355
46637
|
* Add a named kinematic link to the assembly graph.
|
|
46356
46638
|
*
|
|
@@ -46398,6 +46680,11 @@ class Assembly {
|
|
|
46398
46680
|
if (options.min !== void 0 && options.max !== void 0 && options.min > options.max) {
|
|
46399
46681
|
throw new Error(`Link edge "${name}" min must be <= max`);
|
|
46400
46682
|
}
|
|
46683
|
+
const isStructural = !options.visualOnly && options.length !== "free";
|
|
46684
|
+
if (isStructural) {
|
|
46685
|
+
this.assertNotDerivedStructuralLink(a2, "edgeBetweenLinks()");
|
|
46686
|
+
this.assertNotDerivedStructuralLink(b, "edgeBetweenLinks()");
|
|
46687
|
+
}
|
|
46401
46688
|
let length4;
|
|
46402
46689
|
if (options.visualOnly || options.length === "free") {
|
|
46403
46690
|
length4 = null;
|
|
@@ -46406,6 +46693,11 @@ class Assembly {
|
|
|
46406
46693
|
length4 = options.length;
|
|
46407
46694
|
} else {
|
|
46408
46695
|
length4 = vecDistance(linkA.at, linkB.at);
|
|
46696
|
+
if (length4 <= 1e-10) {
|
|
46697
|
+
throw new Error(
|
|
46698
|
+
`edgeBetweenLinks("${a2}", "${b}") captured a zero structural length. Pass an explicit numeric length, move the links apart with link(..., { at }), or use { length: "free" } / { visualOnly: true }.`
|
|
46699
|
+
);
|
|
46700
|
+
}
|
|
46409
46701
|
}
|
|
46410
46702
|
this.linkEdges.set(name, {
|
|
46411
46703
|
name,
|
|
@@ -46430,13 +46722,135 @@ class Assembly {
|
|
|
46430
46722
|
* @category Assembly
|
|
46431
46723
|
*/
|
|
46432
46724
|
addAngleBetweenLinks(a2, b, c2, options = {}) {
|
|
46433
|
-
var _a3, _b3;
|
|
46434
46725
|
for (const id of [a2, b, c2]) {
|
|
46435
46726
|
if (!this.links.has(id)) throw new Error(`addAngleBetweenLinks() unknown link "${id}"`);
|
|
46727
|
+
this.assertNotDerivedStructuralLink(id, "addAngleBetweenLinks()");
|
|
46436
46728
|
}
|
|
46437
46729
|
if (a2 === b || b === c2 || a2 === c2) throw new Error("addAngleBetweenLinks() requires three different links");
|
|
46438
46730
|
const name = options.name ?? `${a2}_${b}_${c2}_${this._linkAngleCounter++}`;
|
|
46439
46731
|
if (this.linkAngles.has(name)) throw new Error(`Link angle "${name}" already exists`);
|
|
46732
|
+
const normalized = this.normalizeAngleOptions(name, options);
|
|
46733
|
+
this.linkAngles.set(name, {
|
|
46734
|
+
name,
|
|
46735
|
+
a: a2,
|
|
46736
|
+
b,
|
|
46737
|
+
c: c2,
|
|
46738
|
+
...normalized
|
|
46739
|
+
});
|
|
46740
|
+
return this;
|
|
46741
|
+
}
|
|
46742
|
+
/**
|
|
46743
|
+
* Add an absolute angle relationship from a world direction to a link segment.
|
|
46744
|
+
*
|
|
46745
|
+
* The first link is the vertex/pivot and the second link is the moving point.
|
|
46746
|
+
* A value of `0` places `fromLink -> toLink` along `direction` in the
|
|
46747
|
+
* mechanism plane; positive angles rotate counter-clockwise in that plane.
|
|
46748
|
+
*
|
|
46749
|
+
* Use `Points.polar(1, angleDeg)` when the reference direction is planar and
|
|
46750
|
+
* angle-based instead of axis-aligned.
|
|
46751
|
+
*
|
|
46752
|
+
* @category Assembly
|
|
46753
|
+
*/
|
|
46754
|
+
addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, direction2, options = {}) {
|
|
46755
|
+
const caller = "addAngleBetweenLinkSegmentAndWorldDirection()";
|
|
46756
|
+
for (const id of [fromLink, toLink]) {
|
|
46757
|
+
if (!this.links.has(id)) throw new Error(`${caller} unknown link "${id}"`);
|
|
46758
|
+
this.assertNotDerivedStructuralLink(id, caller);
|
|
46759
|
+
}
|
|
46760
|
+
if (fromLink === toLink) throw new Error(`${caller} requires two different links`);
|
|
46761
|
+
const referenceDirection = finiteVec3(direction2, `${caller} direction`);
|
|
46762
|
+
if (vecLength(referenceDirection) <= 1e-10) throw new Error(`${caller} direction must be non-zero`);
|
|
46763
|
+
const name = options.name ?? `${fromLink}_${toLink}_from_direction_${this._linkAngleCounter++}`;
|
|
46764
|
+
if (this.linkAngles.has(name)) throw new Error(`Link angle "${name}" already exists`);
|
|
46765
|
+
this.linkAngles.set(name, {
|
|
46766
|
+
name,
|
|
46767
|
+
b: fromLink,
|
|
46768
|
+
c: toLink,
|
|
46769
|
+
reference: { kind: "worldDirection", direction: vecNormalize(referenceDirection) },
|
|
46770
|
+
...this.normalizeAngleOptions(name, options)
|
|
46771
|
+
});
|
|
46772
|
+
return this;
|
|
46773
|
+
}
|
|
46774
|
+
/**
|
|
46775
|
+
* @deprecated Use `addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [1, 0, 0], options)`.
|
|
46776
|
+
* @skillSuppress Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead.
|
|
46777
|
+
*/
|
|
46778
|
+
addAngleOfLinkSegmentFromXAxis(fromLink, toLink, options = {}) {
|
|
46779
|
+
throw new Error(
|
|
46780
|
+
"addAngleOfLinkSegmentFromXAxis() has been replaced by addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [1, 0, 0], options). Update your script."
|
|
46781
|
+
);
|
|
46782
|
+
}
|
|
46783
|
+
/**
|
|
46784
|
+
* @deprecated Use `addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [0, 1, 0], options)`.
|
|
46785
|
+
* @skillSuppress Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead.
|
|
46786
|
+
*/
|
|
46787
|
+
addAngleOfLinkSegmentFromYAxis(fromLink, toLink, options = {}) {
|
|
46788
|
+
throw new Error(
|
|
46789
|
+
"addAngleOfLinkSegmentFromYAxis() has been replaced by addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [0, 1, 0], options). Update your script."
|
|
46790
|
+
);
|
|
46791
|
+
}
|
|
46792
|
+
assertNotDerivedStructuralLink(name, caller) {
|
|
46793
|
+
if (!this.derivedLinks.has(name)) return;
|
|
46794
|
+
throw new Error(
|
|
46795
|
+
`${caller} cannot structurally constrain derived link "${name}". Derived links are recomputed after the primary link solve.`
|
|
46796
|
+
);
|
|
46797
|
+
}
|
|
46798
|
+
assertCanBecomeDerivedLink(name) {
|
|
46799
|
+
const link = this.links.get(name);
|
|
46800
|
+
if (link == null ? void 0 : link.fixed) throw new Error(`Derived link "${name}" cannot be fixed`);
|
|
46801
|
+
for (const edge of this.linkEdges.values()) {
|
|
46802
|
+
if (edge.visualOnly || edge.length == null) continue;
|
|
46803
|
+
if (edge.a === name || edge.b === name) throw new Error(`Derived link "${name}" cannot already have structural edge "${edge.name}"`);
|
|
46804
|
+
}
|
|
46805
|
+
for (const angle of this.linkAngles.values()) {
|
|
46806
|
+
if (angle.a === name || angle.b === name || angle.c === name) {
|
|
46807
|
+
throw new Error(`Derived link "${name}" cannot already participate in angle "${angle.name}"`);
|
|
46808
|
+
}
|
|
46809
|
+
}
|
|
46810
|
+
}
|
|
46811
|
+
addDerivedLink(direction2, name, fromLink, referenceLink, distance2) {
|
|
46812
|
+
const id = typeof name === "string" ? name.trim() : "";
|
|
46813
|
+
if (!id) throw new Error(`${direction2 === "toward" ? "linkToward" : "linkAwayFrom"}() name must be non-empty`);
|
|
46814
|
+
if (!this.links.has(fromLink)) throw new Error(`Derived link "${id}" unknown fromLink "${fromLink}"`);
|
|
46815
|
+
if (!this.links.has(referenceLink)) throw new Error(`Derived link "${id}" unknown reference link "${referenceLink}"`);
|
|
46816
|
+
if (id === fromLink || id === referenceLink || fromLink === referenceLink) {
|
|
46817
|
+
throw new Error(`Derived link "${id}" requires three different links`);
|
|
46818
|
+
}
|
|
46819
|
+
if (!Number.isFinite(distance2) || distance2 < 0) {
|
|
46820
|
+
throw new Error(`Derived link "${id}" distance must be a finite value >= 0`);
|
|
46821
|
+
}
|
|
46822
|
+
if (!this.links.has(id)) {
|
|
46823
|
+
this.link(id);
|
|
46824
|
+
}
|
|
46825
|
+
this.assertCanBecomeDerivedLink(id);
|
|
46826
|
+
this.derivedLinks.set(id, { name: id, fromLink, referenceLink, distance: distance2, direction: direction2 });
|
|
46827
|
+
return this;
|
|
46828
|
+
}
|
|
46829
|
+
/**
|
|
46830
|
+
* Create a derived link at a fixed distance from `fromLink` toward `towardLink`.
|
|
46831
|
+
*
|
|
46832
|
+
* Derived links are trace/reference points. They are recomputed after the
|
|
46833
|
+
* primary link solve and cannot participate in structural edges or angle
|
|
46834
|
+
* constraints.
|
|
46835
|
+
*
|
|
46836
|
+
* @category Assembly
|
|
46837
|
+
*/
|
|
46838
|
+
linkToward(name, fromLink, towardLink, distance2) {
|
|
46839
|
+
return this.addDerivedLink("toward", name, fromLink, towardLink, distance2);
|
|
46840
|
+
}
|
|
46841
|
+
/**
|
|
46842
|
+
* Create a derived link at a fixed distance from `fromLink` away from `awayFromLink`.
|
|
46843
|
+
*
|
|
46844
|
+
* Use this for coupler trace/extension points such as the Chebyshev lambda
|
|
46845
|
+
* linkage's point beyond the rocker joint.
|
|
46846
|
+
*
|
|
46847
|
+
* @category Assembly
|
|
46848
|
+
*/
|
|
46849
|
+
linkAwayFrom(name, fromLink, awayFromLink, distance2) {
|
|
46850
|
+
return this.addDerivedLink("awayFrom", name, fromLink, awayFromLink, distance2);
|
|
46851
|
+
}
|
|
46852
|
+
normalizeAngleOptions(name, options) {
|
|
46853
|
+
var _a3, _b3;
|
|
46440
46854
|
const control = options.control === true ? { min: options.min, max: options.max, default: options.value } : options.control ? { ...options.control } : void 0;
|
|
46441
46855
|
const min2 = ((_a3 = options.limit) == null ? void 0 : _a3.min) ?? options.min;
|
|
46442
46856
|
const max2 = ((_b3 = options.limit) == null ? void 0 : _b3.max) ?? options.max;
|
|
@@ -46445,18 +46859,7 @@ class Assembly {
|
|
|
46445
46859
|
if (min2 !== void 0 && max2 !== void 0 && min2 > max2) throw new Error(`Link angle "${name}" min must be <= max`);
|
|
46446
46860
|
const target = options.value ?? (control == null ? void 0 : control.default);
|
|
46447
46861
|
if (target !== void 0 && !Number.isFinite(target)) throw new Error(`Link angle "${name}" value must be finite`);
|
|
46448
|
-
|
|
46449
|
-
name,
|
|
46450
|
-
a: a2,
|
|
46451
|
-
b,
|
|
46452
|
-
c: c2,
|
|
46453
|
-
target,
|
|
46454
|
-
min: min2,
|
|
46455
|
-
max: max2,
|
|
46456
|
-
control,
|
|
46457
|
-
metadata: options.metadata ? { ...options.metadata } : void 0
|
|
46458
|
-
});
|
|
46459
|
-
return this;
|
|
46862
|
+
return { target, min: min2, max: max2, control, metadata: options.metadata ? { ...options.metadata } : void 0 };
|
|
46460
46863
|
}
|
|
46461
46864
|
/** Return the assembly-native kinematic graph definition. */
|
|
46462
46865
|
describeKinematics() {
|
|
@@ -46483,30 +46886,23 @@ class Assembly {
|
|
|
46483
46886
|
a: angle.a,
|
|
46484
46887
|
b: angle.b,
|
|
46485
46888
|
c: angle.c,
|
|
46889
|
+
reference: cloneAngleReference(angle.reference),
|
|
46486
46890
|
target: angle.target,
|
|
46487
46891
|
min: angle.min,
|
|
46488
46892
|
max: angle.max,
|
|
46489
46893
|
control: angle.control ? { ...angle.control } : void 0,
|
|
46490
46894
|
metadata: angle.metadata ? { ...angle.metadata } : void 0
|
|
46491
|
-
}))
|
|
46895
|
+
})),
|
|
46896
|
+
derivedLinks: [...this.derivedLinks.values()].map((derived) => ({ ...derived }))
|
|
46492
46897
|
};
|
|
46493
46898
|
}
|
|
46494
46899
|
/**
|
|
46495
|
-
*
|
|
46496
|
-
*
|
|
46497
|
-
*
|
|
46498
|
-
*
|
|
46499
|
-
* Useful when you need a named pivot point or coordinate frame that has no
|
|
46500
|
-
* visual geometry. Acts like a zero-volume part and can be connected to
|
|
46501
|
-
* other parts via joints.
|
|
46502
|
-
*
|
|
46503
|
-
* @param name - Unique part name for the frame in the assembly graph
|
|
46504
|
-
* @param options - Optional transform and metadata
|
|
46505
|
-
* @returns `this` for chaining
|
|
46506
|
-
* @category Assembly
|
|
46900
|
+
* @deprecated `addFrame()` has been removed. Use `frame()` for rig frames, or
|
|
46901
|
+
* `addPart(name, group())` for an empty placeholder part.
|
|
46902
|
+
* @internal
|
|
46507
46903
|
*/
|
|
46508
|
-
addFrame(
|
|
46509
|
-
|
|
46904
|
+
addFrame(_name2, _options = {}) {
|
|
46905
|
+
throw new Error("addFrame() has been removed. Use frame() for rig frames, or addPart(name, group()) for an empty placeholder part.");
|
|
46510
46906
|
}
|
|
46511
46907
|
/**
|
|
46512
46908
|
* Add a named part to the assembly.
|
|
@@ -46546,6 +46942,16 @@ class Assembly {
|
|
|
46546
46942
|
addPart(name, part, options = {}) {
|
|
46547
46943
|
if (this.parts.has(name)) throw new Error(`Part "${name}" already exists`);
|
|
46548
46944
|
const mates = options.mate == null ? [] : Array.isArray(options.mate) ? options.mate : [options.mate];
|
|
46945
|
+
if (options.bindToFrame != null && typeof options.bindToFrame !== "string") {
|
|
46946
|
+
throw new Error(`addPart("${name}") bindToFrame must be a frame name string`);
|
|
46947
|
+
}
|
|
46948
|
+
const bindToFrame = typeof options.bindToFrame === "string" ? options.bindToFrame.trim() : "";
|
|
46949
|
+
if (bindToFrame && mates.length > 0) {
|
|
46950
|
+
throw new Error(`addPart("${name}") cannot use both bindToFrame and mate`);
|
|
46951
|
+
}
|
|
46952
|
+
if (bindToFrame && !this.frames.has(bindToFrame)) {
|
|
46953
|
+
throw new Error(`addPart("${name}") bindToFrame references unknown frame "${bindToFrame}"`);
|
|
46954
|
+
}
|
|
46549
46955
|
this.parts.set(name, {
|
|
46550
46956
|
name,
|
|
46551
46957
|
part,
|
|
@@ -46563,7 +46969,8 @@ class Assembly {
|
|
|
46563
46969
|
if (aimLink === toLink) throw new Error(`addPart("${name}") mate[${index2}] aimLink must differ from toLink`);
|
|
46564
46970
|
}
|
|
46565
46971
|
return aimLink ? { connector, toLink, aimLink } : { connector, toLink };
|
|
46566
|
-
})
|
|
46972
|
+
}),
|
|
46973
|
+
bindToFrame: bindToFrame || void 0
|
|
46567
46974
|
});
|
|
46568
46975
|
let ports = {};
|
|
46569
46976
|
if (part instanceof Shape) {
|
|
@@ -46609,10 +47016,15 @@ class Assembly {
|
|
|
46609
47016
|
* @internal
|
|
46610
47017
|
*/
|
|
46611
47018
|
addJoint(name, type, parent, child, options = {}) {
|
|
46612
|
-
if (this.joints.has(name)) throw new Error(`Joint "${name}" already exists`);
|
|
47019
|
+
if (this.joints.has(name) || this.frameJoints.has(name)) throw new Error(`Joint "${name}" already exists`);
|
|
46613
47020
|
if (!this.parts.has(parent)) throw new Error(`Unknown parent part "${parent}"`);
|
|
46614
47021
|
if (!this.parts.has(child)) throw new Error(`Unknown child part "${child}"`);
|
|
46615
47022
|
if (parent === child) throw new Error(`Joint "${name}" cannot connect a part to itself`);
|
|
47023
|
+
const parentRecord = this.parts.get(parent);
|
|
47024
|
+
const childRecord = this.parts.get(child);
|
|
47025
|
+
if (parentRecord.bindToFrame || childRecord.bindToFrame) {
|
|
47026
|
+
throw new Error(`Joint "${name}" cannot connect frame-bound parts. Use frame joints for parts added with bindToFrame.`);
|
|
47027
|
+
}
|
|
46616
47028
|
if (options.frame && options.origin) {
|
|
46617
47029
|
throw new Error(`Joint "${name}" cannot have both frame and origin`);
|
|
46618
47030
|
}
|
|
@@ -46889,7 +47301,578 @@ class Assembly {
|
|
|
46889
47301
|
"addGearCoupling() has been removed from the modeling API. Express gear behavior through assembly kinematic constraints instead."
|
|
46890
47302
|
);
|
|
46891
47303
|
}
|
|
47304
|
+
solveFrames(state) {
|
|
47305
|
+
const transforms = /* @__PURE__ */ new Map();
|
|
47306
|
+
const frames = /* @__PURE__ */ new Map();
|
|
47307
|
+
const jointValues = {};
|
|
47308
|
+
const warnings = [];
|
|
47309
|
+
if (this.frames.size === 0) return { frames, transforms, jointValues, warnings };
|
|
47310
|
+
const incoming = /* @__PURE__ */ new Map();
|
|
47311
|
+
const jointsByParent = /* @__PURE__ */ new Map();
|
|
47312
|
+
for (const joint2 of this.frameJoints.values()) {
|
|
47313
|
+
if (incoming.has(joint2.child)) {
|
|
47314
|
+
throw new Error(`Frame "${joint2.child}" has multiple parent joints`);
|
|
47315
|
+
}
|
|
47316
|
+
incoming.set(joint2.child, joint2.name);
|
|
47317
|
+
const list = jointsByParent.get(joint2.parent) ?? [];
|
|
47318
|
+
list.push(joint2);
|
|
47319
|
+
jointsByParent.set(joint2.parent, list);
|
|
47320
|
+
}
|
|
47321
|
+
const roots = [...this.frames.keys()].filter((name) => !incoming.has(name));
|
|
47322
|
+
if (roots.length === 0) throw new Error("Frame graph has no root frame");
|
|
47323
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
47324
|
+
const visited = /* @__PURE__ */ new Set();
|
|
47325
|
+
const resolveJointValue = (joint2) => {
|
|
47326
|
+
if (joint2.type === "fixed") {
|
|
47327
|
+
jointValues[joint2.name] = 0;
|
|
47328
|
+
return 0;
|
|
47329
|
+
}
|
|
47330
|
+
const hasStateValue = Object.prototype.hasOwnProperty.call(state, joint2.name);
|
|
47331
|
+
let raw = joint2.defaultValue;
|
|
47332
|
+
if (joint2.control === false) {
|
|
47333
|
+
if (hasStateValue) {
|
|
47334
|
+
warnings.push(`Frame joint "${joint2.name}" state override ignored because control is false`);
|
|
47335
|
+
}
|
|
47336
|
+
} else {
|
|
47337
|
+
raw = state[joint2.name] ?? joint2.defaultValue;
|
|
47338
|
+
}
|
|
47339
|
+
const finite = Number.isFinite(raw) ? raw : joint2.defaultValue;
|
|
47340
|
+
let clamped = finite;
|
|
47341
|
+
if (joint2.min != null) clamped = Math.max(joint2.min, clamped);
|
|
47342
|
+
if (joint2.max != null) clamped = Math.min(joint2.max, clamped);
|
|
47343
|
+
if (clamped !== raw) {
|
|
47344
|
+
warnings.push(`Frame joint "${joint2.name}" clamped from ${raw} to ${clamped}${joint2.unit ?? ""}`);
|
|
47345
|
+
}
|
|
47346
|
+
jointValues[joint2.name] = clamped;
|
|
47347
|
+
return clamped;
|
|
47348
|
+
};
|
|
47349
|
+
const visit = (frameName, worldTransform) => {
|
|
47350
|
+
if (visiting.has(frameName)) throw new Error(`Frame joint cycle detected at "${frameName}"`);
|
|
47351
|
+
const frame = this.frames.get(frameName);
|
|
47352
|
+
if (!frame) throw new Error(`Unknown frame "${frameName}"`);
|
|
47353
|
+
visiting.add(frameName);
|
|
47354
|
+
transforms.set(frameName, worldTransform);
|
|
47355
|
+
frames.set(frameName, frameFromTransform(frame, worldTransform));
|
|
47356
|
+
visited.add(frameName);
|
|
47357
|
+
for (const joint2 of jointsByParent.get(frameName) ?? []) {
|
|
47358
|
+
const value = resolveJointValue(joint2);
|
|
47359
|
+
const childWorld = composeChain(joint2.rest, frameJointMotionTransform(joint2, value), worldTransform);
|
|
47360
|
+
visit(joint2.child, childWorld);
|
|
47361
|
+
}
|
|
47362
|
+
visiting.delete(frameName);
|
|
47363
|
+
};
|
|
47364
|
+
for (const rootName of roots) {
|
|
47365
|
+
const root = this.frames.get(rootName);
|
|
47366
|
+
visit(rootName, frameDefTransform(root));
|
|
47367
|
+
}
|
|
47368
|
+
if (visited.size !== this.frames.size) {
|
|
47369
|
+
const missing = [...this.frames.keys()].filter((name) => !visited.has(name));
|
|
47370
|
+
throw new Error(`Frame graph unresolved for frames: ${missing.join(", ")}`);
|
|
47371
|
+
}
|
|
47372
|
+
return { frames, transforms, jointValues, warnings };
|
|
47373
|
+
}
|
|
47374
|
+
solveFrameEdges(frames) {
|
|
47375
|
+
const solved = [];
|
|
47376
|
+
for (const edge of this.frameEdges.values()) {
|
|
47377
|
+
const a2 = frames.get(edge.a);
|
|
47378
|
+
const b = frames.get(edge.b);
|
|
47379
|
+
if (!a2) throw new Error(`Frame edge "${edge.name}" references unresolved frame "${edge.a}"`);
|
|
47380
|
+
if (!b) throw new Error(`Frame edge "${edge.name}" references unresolved frame "${edge.b}"`);
|
|
47381
|
+
const start = cloneVec3$3(a2.origin);
|
|
47382
|
+
const end = cloneVec3$3(b.origin);
|
|
47383
|
+
solved.push({
|
|
47384
|
+
...cloneAssemblyFrameEdge(edge),
|
|
47385
|
+
start,
|
|
47386
|
+
end,
|
|
47387
|
+
solvedLength: vecDistance(start, end)
|
|
47388
|
+
});
|
|
47389
|
+
}
|
|
47390
|
+
return solved;
|
|
47391
|
+
}
|
|
47392
|
+
attachFrameEdgesToKinematics(metadata, frameEdges) {
|
|
47393
|
+
if (frameEdges.length === 0) return metadata;
|
|
47394
|
+
if (metadata) return { ...metadata, frameEdges };
|
|
47395
|
+
return {
|
|
47396
|
+
links: [],
|
|
47397
|
+
edges: [],
|
|
47398
|
+
angles: [],
|
|
47399
|
+
derivedLinks: [],
|
|
47400
|
+
frameEdges,
|
|
47401
|
+
controls: {},
|
|
47402
|
+
floatingComponents: [],
|
|
47403
|
+
diagnostics: [],
|
|
47404
|
+
maxResidual: 0,
|
|
47405
|
+
converged: true
|
|
47406
|
+
};
|
|
47407
|
+
}
|
|
46892
47408
|
solveKinematicLinks(state) {
|
|
47409
|
+
if (this.links.size === 0) {
|
|
47410
|
+
return { metadata: null, linkPositions: /* @__PURE__ */ new Map(), warnings: [] };
|
|
47411
|
+
}
|
|
47412
|
+
const plane = this.buildKinematicWorkplane();
|
|
47413
|
+
if (!plane) {
|
|
47414
|
+
return this.solveKinematicLinksLegacy(state);
|
|
47415
|
+
}
|
|
47416
|
+
return this.solveKinematicLinksPlanar(state, plane);
|
|
47417
|
+
}
|
|
47418
|
+
buildKinematicWorkplane() {
|
|
47419
|
+
const derivedNames = new Set(this.derivedLinks.keys());
|
|
47420
|
+
const primaryLinks = [...this.links.values()].filter((link) => !derivedNames.has(link.name));
|
|
47421
|
+
const originLink = primaryLinks.find((link) => link.fixed) ?? primaryLinks[0];
|
|
47422
|
+
const origin = originLink ? cloneVec3$3(originLink.at) : [0, 0, 0];
|
|
47423
|
+
let normal = null;
|
|
47424
|
+
for (const angle of this.linkAngles.values()) {
|
|
47425
|
+
const b = this.links.get(angle.b).at;
|
|
47426
|
+
const c2 = this.links.get(angle.c).at;
|
|
47427
|
+
const reference = angle.reference ? angle.reference.direction : angle.a ? vecSub(this.links.get(angle.a).at, b) : null;
|
|
47428
|
+
if (!reference) continue;
|
|
47429
|
+
const n = vecCross(reference, vecSub(c2, b));
|
|
47430
|
+
if (vecLength(n) < 1e-9) continue;
|
|
47431
|
+
const unit = vecNormalize(n);
|
|
47432
|
+
if (!normal) {
|
|
47433
|
+
normal = unit;
|
|
47434
|
+
} else if (Math.abs(vecDot(normal, unit)) < 0.999) {
|
|
47435
|
+
return null;
|
|
47436
|
+
}
|
|
47437
|
+
}
|
|
47438
|
+
normal ?? (normal = [0, 0, 1]);
|
|
47439
|
+
const projectDir = (v) => {
|
|
47440
|
+
const d2 = vecDot(v, normal);
|
|
47441
|
+
const projected = [v[0] - normal[0] * d2, v[1] - normal[1] * d2, v[2] - normal[2] * d2];
|
|
47442
|
+
const len2 = vecLength(projected);
|
|
47443
|
+
return len2 > 1e-9 ? [projected[0] / len2, projected[1] / len2, projected[2] / len2] : null;
|
|
47444
|
+
};
|
|
47445
|
+
let xAxis = null;
|
|
47446
|
+
const fixed = primaryLinks.filter((link) => link.fixed);
|
|
47447
|
+
for (let i = 0; i < fixed.length && !xAxis; i++) {
|
|
47448
|
+
for (let j = i + 1; j < fixed.length && !xAxis; j++) {
|
|
47449
|
+
xAxis = projectDir(vecSub(fixed[j].at, fixed[i].at));
|
|
47450
|
+
}
|
|
47451
|
+
}
|
|
47452
|
+
for (const edge of this.linkEdges.values()) {
|
|
47453
|
+
if (xAxis) break;
|
|
47454
|
+
if (derivedNames.has(edge.a) || derivedNames.has(edge.b)) continue;
|
|
47455
|
+
const a2 = this.links.get(edge.a);
|
|
47456
|
+
const b = this.links.get(edge.b);
|
|
47457
|
+
if (a2 && b) xAxis = projectDir(vecSub(b.at, a2.at));
|
|
47458
|
+
}
|
|
47459
|
+
for (const link of primaryLinks) {
|
|
47460
|
+
if (xAxis) break;
|
|
47461
|
+
xAxis = projectDir(vecSub(link.at, origin));
|
|
47462
|
+
}
|
|
47463
|
+
xAxis ?? (xAxis = projectDir(Math.abs(normal[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0]) ?? [1, 0, 0]);
|
|
47464
|
+
const yAxis = vecNormalize(vecCross(normal, xAxis));
|
|
47465
|
+
for (const link of primaryLinks) {
|
|
47466
|
+
const offPlane = Math.abs(vecDot(vecSub(link.at, origin), normal));
|
|
47467
|
+
if (offPlane > 1e-6) return null;
|
|
47468
|
+
}
|
|
47469
|
+
return { origin, xAxis, yAxis, normal };
|
|
47470
|
+
}
|
|
47471
|
+
solveKinematicLinksPlanar(state, plane) {
|
|
47472
|
+
var _a3, _b3, _c2, _d2, _e2, _f2, _g2;
|
|
47473
|
+
const derivedNames = new Set(this.derivedLinks.keys());
|
|
47474
|
+
const primaryLinks = [...this.links.values()].filter((link) => !derivedNames.has(link.name));
|
|
47475
|
+
const controls = {};
|
|
47476
|
+
const diagnosticSet = /* @__PURE__ */ new Set();
|
|
47477
|
+
const diagnostics = [];
|
|
47478
|
+
const addDiagnostic = (message) => {
|
|
47479
|
+
if (diagnosticSet.has(message)) return;
|
|
47480
|
+
diagnosticSet.add(message);
|
|
47481
|
+
diagnostics.push(message);
|
|
47482
|
+
};
|
|
47483
|
+
const project = (p2) => {
|
|
47484
|
+
const rel = vecSub(p2, plane.origin);
|
|
47485
|
+
return [vecDot(rel, plane.xAxis), vecDot(rel, plane.yAxis)];
|
|
47486
|
+
};
|
|
47487
|
+
const unproject = (p2) => [
|
|
47488
|
+
plane.origin[0] + plane.xAxis[0] * p2[0] + plane.yAxis[0] * p2[1],
|
|
47489
|
+
plane.origin[1] + plane.xAxis[1] * p2[0] + plane.yAxis[1] * p2[1],
|
|
47490
|
+
plane.origin[2] + plane.xAxis[2] * p2[0] + plane.yAxis[2] * p2[1]
|
|
47491
|
+
];
|
|
47492
|
+
const unprojectVector = (v) => [
|
|
47493
|
+
plane.xAxis[0] * v[0] + plane.yAxis[0] * v[1],
|
|
47494
|
+
plane.xAxis[1] * v[0] + plane.yAxis[1] * v[1],
|
|
47495
|
+
plane.xAxis[2] * v[0] + plane.yAxis[2] * v[1]
|
|
47496
|
+
];
|
|
47497
|
+
const projectVector = (v) => [vecDot(v, plane.xAxis), vecDot(v, plane.yAxis)];
|
|
47498
|
+
const initialPositions2d = /* @__PURE__ */ new Map();
|
|
47499
|
+
for (const link of primaryLinks) initialPositions2d.set(link.name, project(link.at));
|
|
47500
|
+
const neighbors = /* @__PURE__ */ new Map();
|
|
47501
|
+
for (const link of primaryLinks) neighbors.set(link.name, /* @__PURE__ */ new Set());
|
|
47502
|
+
for (const edge of this.linkEdges.values()) {
|
|
47503
|
+
if (derivedNames.has(edge.a) || derivedNames.has(edge.b)) continue;
|
|
47504
|
+
(_a3 = neighbors.get(edge.a)) == null ? void 0 : _a3.add(edge.b);
|
|
47505
|
+
(_b3 = neighbors.get(edge.b)) == null ? void 0 : _b3.add(edge.a);
|
|
47506
|
+
}
|
|
47507
|
+
for (const angle of this.linkAngles.values()) {
|
|
47508
|
+
if (angle.a) {
|
|
47509
|
+
(_c2 = neighbors.get(angle.a)) == null ? void 0 : _c2.add(angle.b);
|
|
47510
|
+
(_d2 = neighbors.get(angle.b)) == null ? void 0 : _d2.add(angle.a);
|
|
47511
|
+
}
|
|
47512
|
+
(_e2 = neighbors.get(angle.b)) == null ? void 0 : _e2.add(angle.c);
|
|
47513
|
+
(_f2 = neighbors.get(angle.c)) == null ? void 0 : _f2.add(angle.b);
|
|
47514
|
+
}
|
|
47515
|
+
const floatingComponents = [];
|
|
47516
|
+
const gaugeFixed = /* @__PURE__ */ new Set();
|
|
47517
|
+
const seen = /* @__PURE__ */ new Set();
|
|
47518
|
+
for (const link of primaryLinks.map((link2) => link2.name)) {
|
|
47519
|
+
if (seen.has(link)) continue;
|
|
47520
|
+
const stack = [link];
|
|
47521
|
+
const component = [];
|
|
47522
|
+
seen.add(link);
|
|
47523
|
+
while (stack.length > 0) {
|
|
47524
|
+
const next = stack.pop();
|
|
47525
|
+
component.push(next);
|
|
47526
|
+
for (const neighbor of neighbors.get(next) ?? []) {
|
|
47527
|
+
if (seen.has(neighbor)) continue;
|
|
47528
|
+
seen.add(neighbor);
|
|
47529
|
+
stack.push(neighbor);
|
|
47530
|
+
}
|
|
47531
|
+
}
|
|
47532
|
+
if (!component.some((name) => {
|
|
47533
|
+
var _a4;
|
|
47534
|
+
return (_a4 = this.links.get(name)) == null ? void 0 : _a4.fixed;
|
|
47535
|
+
})) {
|
|
47536
|
+
const sorted = [...component].sort();
|
|
47537
|
+
const gaugeLink = sorted[0];
|
|
47538
|
+
gaugeFixed.add(gaugeLink);
|
|
47539
|
+
floatingComponents.push({ links: sorted, gaugeLink });
|
|
47540
|
+
}
|
|
47541
|
+
}
|
|
47542
|
+
const angleValueForState = (angle, inputState, recordControl) => {
|
|
47543
|
+
var _a4, _b4, _c3, _d3;
|
|
47544
|
+
if (!angle.control && angle.target === void 0) return null;
|
|
47545
|
+
const raw = inputState[angle.name] ?? angle.target ?? ((_a4 = angle.control) == null ? void 0 : _a4.default) ?? 0;
|
|
47546
|
+
let value = Number.isFinite(raw) ? raw : ((_b4 = angle.control) == null ? void 0 : _b4.default) ?? angle.target ?? 0;
|
|
47547
|
+
const min2 = ((_c3 = angle.control) == null ? void 0 : _c3.min) ?? angle.min;
|
|
47548
|
+
const max2 = ((_d3 = angle.control) == null ? void 0 : _d3.max) ?? angle.max;
|
|
47549
|
+
if (min2 != null) value = Math.max(min2, value);
|
|
47550
|
+
if (max2 != null) value = Math.min(max2, value);
|
|
47551
|
+
if (recordControl) controls[angle.name] = value;
|
|
47552
|
+
return value;
|
|
47553
|
+
};
|
|
47554
|
+
const controlledAngleValue = (angle) => angleValueForState(angle, state, true);
|
|
47555
|
+
const edgeLengthBetween = (x2, y2) => {
|
|
47556
|
+
for (const edge of this.linkEdges.values()) {
|
|
47557
|
+
if (edge.visualOnly || edge.length == null) continue;
|
|
47558
|
+
if (edge.a === x2 && edge.b === y2 || edge.a === y2 && edge.b === x2) return edge.length;
|
|
47559
|
+
}
|
|
47560
|
+
return null;
|
|
47561
|
+
};
|
|
47562
|
+
const seedDirection = (index2) => {
|
|
47563
|
+
const angle = index2 * Math.PI * (3 - Math.sqrt(5));
|
|
47564
|
+
return [Math.cos(angle), Math.sin(angle)];
|
|
47565
|
+
};
|
|
47566
|
+
const rotate2d = (v, angleDeg) => {
|
|
47567
|
+
const rad = angleDeg * Math.PI / 180;
|
|
47568
|
+
const c2 = Math.cos(rad);
|
|
47569
|
+
const s = Math.sin(rad);
|
|
47570
|
+
return [v[0] * c2 - v[1] * s, v[0] * s + v[1] * c2];
|
|
47571
|
+
};
|
|
47572
|
+
const angleReferenceVector2d = (angle, seedPositions) => {
|
|
47573
|
+
if (angle.reference) return projectVector(angle.reference.direction);
|
|
47574
|
+
if (!angle.a) return null;
|
|
47575
|
+
const a2 = seedPositions.get(angle.a);
|
|
47576
|
+
const b = seedPositions.get(angle.b);
|
|
47577
|
+
if (!a2 || !b) return null;
|
|
47578
|
+
return [a2[0] - b[0], a2[1] - b[1]];
|
|
47579
|
+
};
|
|
47580
|
+
const angleReferenceLabel = (angle) => angle.reference ? `world direction [${angle.reference.direction.join(", ")}]` : `reference links "${angle.a}" and "${angle.b}"`;
|
|
47581
|
+
const signedArea2d2 = (a2, b, p2) => (b[0] - a2[0]) * (p2[1] - a2[1]) - (b[1] - a2[1]) * (p2[0] - a2[0]);
|
|
47582
|
+
const circleCircleCandidates = (a2, radiusA, b, radiusB) => {
|
|
47583
|
+
const dx = b[0] - a2[0];
|
|
47584
|
+
const dy = b[1] - a2[1];
|
|
47585
|
+
const d2 = Math.hypot(dx, dy);
|
|
47586
|
+
if (d2 <= 1e-10) return [];
|
|
47587
|
+
const along = (radiusA * radiusA - radiusB * radiusB + d2 * d2) / (2 * d2);
|
|
47588
|
+
const h2 = radiusA * radiusA - along * along;
|
|
47589
|
+
if (h2 < -1e-7) return [];
|
|
47590
|
+
const h = Math.sqrt(Math.max(0, h2));
|
|
47591
|
+
const ux = dx / d2;
|
|
47592
|
+
const uy = dy / d2;
|
|
47593
|
+
const base = [a2[0] + ux * along, a2[1] + uy * along];
|
|
47594
|
+
const offset2 = [-uy * h, ux * h];
|
|
47595
|
+
return [
|
|
47596
|
+
[base[0] + offset2[0], base[1] + offset2[1]],
|
|
47597
|
+
[base[0] - offset2[0], base[1] - offset2[1]]
|
|
47598
|
+
];
|
|
47599
|
+
};
|
|
47600
|
+
const chooseCandidate = (targetName, anchors, candidates, seedPositions, branchReference2) => {
|
|
47601
|
+
if (candidates.length === 0) return null;
|
|
47602
|
+
const anchorAName = anchors[0].name;
|
|
47603
|
+
const anchorBName = anchors[1].name;
|
|
47604
|
+
const anchorA = seedPositions.get(anchorAName);
|
|
47605
|
+
const anchorB = seedPositions.get(anchorBName);
|
|
47606
|
+
let referenceSign = 0;
|
|
47607
|
+
if (branchReference2) {
|
|
47608
|
+
const refA = branchReference2.get(anchorAName);
|
|
47609
|
+
const refB = branchReference2.get(anchorBName);
|
|
47610
|
+
const refTarget = branchReference2.get(targetName);
|
|
47611
|
+
if (refA && refB && refTarget) {
|
|
47612
|
+
const area2 = signedArea2d2(refA, refB, refTarget);
|
|
47613
|
+
if (Math.abs(area2) > 1e-7) referenceSign = Math.sign(area2);
|
|
47614
|
+
}
|
|
47615
|
+
}
|
|
47616
|
+
const authored = initialPositions2d.get(targetName) ?? [0, 0];
|
|
47617
|
+
let best = null;
|
|
47618
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
47619
|
+
for (const candidate of candidates) {
|
|
47620
|
+
const candidateSign = Math.sign(signedArea2d2(anchorA, anchorB, candidate));
|
|
47621
|
+
const branchPenalty = referenceSign !== 0 && candidateSign !== referenceSign ? 1e12 : 0;
|
|
47622
|
+
const authoredDistance = Math.hypot(candidate[0] - authored[0], candidate[1] - authored[1]);
|
|
47623
|
+
const constraintResidual = anchors.reduce((sum2, anchor) => {
|
|
47624
|
+
const p2 = seedPositions.get(anchor.name);
|
|
47625
|
+
return sum2 + Math.abs(Math.hypot(candidate[0] - p2[0], candidate[1] - p2[1]) - anchor.length);
|
|
47626
|
+
}, 0);
|
|
47627
|
+
const score = branchPenalty + constraintResidual * 1e6 + authoredDistance;
|
|
47628
|
+
if (score < bestScore) {
|
|
47629
|
+
bestScore = score;
|
|
47630
|
+
best = candidate;
|
|
47631
|
+
}
|
|
47632
|
+
}
|
|
47633
|
+
return best;
|
|
47634
|
+
};
|
|
47635
|
+
const buildSeedPositions = (inputState, branchReference2, diagnosticPrefix = "") => {
|
|
47636
|
+
const seedPositions = new Map(initialPositions2d);
|
|
47637
|
+
const seeded = /* @__PURE__ */ new Set();
|
|
47638
|
+
const angleDriven = /* @__PURE__ */ new Set();
|
|
47639
|
+
const isLocked = (name) => {
|
|
47640
|
+
var _a4;
|
|
47641
|
+
return ((_a4 = this.links.get(name)) == null ? void 0 : _a4.fixed) === true || gaugeFixed.has(name);
|
|
47642
|
+
};
|
|
47643
|
+
for (const link of primaryLinks) {
|
|
47644
|
+
const p2 = seedPositions.get(link.name);
|
|
47645
|
+
if (link.fixed || gaugeFixed.has(link.name) || Math.hypot(p2[0], p2[1]) > 1e-9) seeded.add(link.name);
|
|
47646
|
+
}
|
|
47647
|
+
if (seeded.size === 0 && primaryLinks.length > 0) seeded.add(primaryLinks[0].name);
|
|
47648
|
+
let seedIndex = 1;
|
|
47649
|
+
let changed = true;
|
|
47650
|
+
for (let pass = 0; pass < primaryLinks.length * 3 && changed; pass++) {
|
|
47651
|
+
changed = false;
|
|
47652
|
+
for (const angle of this.linkAngles.values()) {
|
|
47653
|
+
const target = angleValueForState(angle, inputState, false);
|
|
47654
|
+
const radius = edgeLengthBetween(angle.b, angle.c);
|
|
47655
|
+
const hasReference = !!angle.reference || angle.a !== void 0 && seeded.has(angle.a);
|
|
47656
|
+
if (target == null || radius == null || isLocked(angle.c) || !hasReference || !seeded.has(angle.b)) continue;
|
|
47657
|
+
const b = seedPositions.get(angle.b);
|
|
47658
|
+
const ref = angleReferenceVector2d(angle, seedPositions);
|
|
47659
|
+
if (!ref) continue;
|
|
47660
|
+
const refLen = Math.hypot(ref[0], ref[1]);
|
|
47661
|
+
if (refLen <= 1e-10) {
|
|
47662
|
+
addDiagnostic(
|
|
47663
|
+
`${diagnosticPrefix}Angle "${angle.name}" cannot drive link "${angle.c}" because ${angleReferenceLabel(angle)} has no usable direction in the mechanism plane.`
|
|
47664
|
+
);
|
|
47665
|
+
continue;
|
|
47666
|
+
}
|
|
47667
|
+
const dir = rotate2d([ref[0] / refLen, ref[1] / refLen], target);
|
|
47668
|
+
seedPositions.set(angle.c, [b[0] + dir[0] * radius, b[1] + dir[1] * radius]);
|
|
47669
|
+
seeded.add(angle.c);
|
|
47670
|
+
angleDriven.add(angle.c);
|
|
47671
|
+
changed = true;
|
|
47672
|
+
}
|
|
47673
|
+
for (const link of primaryLinks) {
|
|
47674
|
+
if (isLocked(link.name) || angleDriven.has(link.name)) continue;
|
|
47675
|
+
const anchors = [];
|
|
47676
|
+
for (const edge of this.linkEdges.values()) {
|
|
47677
|
+
if (edge.visualOnly || edge.length == null || derivedNames.has(edge.a) || derivedNames.has(edge.b)) continue;
|
|
47678
|
+
if (edge.a === link.name && seeded.has(edge.b)) anchors.push({ name: edge.b, length: edge.length });
|
|
47679
|
+
if (edge.b === link.name && seeded.has(edge.a)) anchors.push({ name: edge.a, length: edge.length });
|
|
47680
|
+
}
|
|
47681
|
+
if (anchors.length < 2) continue;
|
|
47682
|
+
const a2 = anchors[0];
|
|
47683
|
+
const b = anchors[1];
|
|
47684
|
+
const candidates = circleCircleCandidates(seedPositions.get(a2.name), a2.length, seedPositions.get(b.name), b.length);
|
|
47685
|
+
const chosen = chooseCandidate(link.name, anchors, candidates, seedPositions, branchReference2);
|
|
47686
|
+
if (!chosen) {
|
|
47687
|
+
const pa = seedPositions.get(a2.name);
|
|
47688
|
+
const pb = seedPositions.get(b.name);
|
|
47689
|
+
const centerDistance = Math.hypot(pb[0] - pa[0], pb[1] - pa[1]);
|
|
47690
|
+
const minDistance = Math.abs(a2.length - b.length);
|
|
47691
|
+
const maxDistance = a2.length + b.length;
|
|
47692
|
+
addDiagnostic(
|
|
47693
|
+
`${diagnosticPrefix}Link "${link.name}" cannot be placed from anchors "${a2.name}" and "${b.name}": distance between anchors is ${centerDistance.toFixed(6)}, but required circle intersection range is ${minDistance.toFixed(6)}..${maxDistance.toFixed(6)}. This control state is outside the selected assembly mode, or the model needs more initial link positions to select a different mode.`
|
|
47694
|
+
);
|
|
47695
|
+
continue;
|
|
47696
|
+
}
|
|
47697
|
+
seedPositions.set(link.name, chosen);
|
|
47698
|
+
seeded.add(link.name);
|
|
47699
|
+
changed = true;
|
|
47700
|
+
}
|
|
47701
|
+
for (const edge of this.linkEdges.values()) {
|
|
47702
|
+
if (edge.visualOnly || edge.length == null || derivedNames.has(edge.a) || derivedNames.has(edge.b)) continue;
|
|
47703
|
+
const aSeeded = seeded.has(edge.a);
|
|
47704
|
+
const bSeeded = seeded.has(edge.b);
|
|
47705
|
+
if (aSeeded === bSeeded) continue;
|
|
47706
|
+
const anchorName = aSeeded ? edge.a : edge.b;
|
|
47707
|
+
const targetName = aSeeded ? edge.b : edge.a;
|
|
47708
|
+
if (isLocked(targetName) || angleDriven.has(targetName)) continue;
|
|
47709
|
+
const anchor = seedPositions.get(anchorName);
|
|
47710
|
+
const current = seedPositions.get(targetName);
|
|
47711
|
+
const delta = [current[0] - anchor[0], current[1] - anchor[1]];
|
|
47712
|
+
const deltaLen = Math.hypot(delta[0], delta[1]);
|
|
47713
|
+
const dir = deltaLen > 1e-9 ? [delta[0] / deltaLen, delta[1] / deltaLen] : seedDirection(seedIndex++);
|
|
47714
|
+
seedPositions.set(targetName, [anchor[0] + dir[0] * edge.length, anchor[1] + dir[1] * edge.length]);
|
|
47715
|
+
seeded.add(targetName);
|
|
47716
|
+
changed = true;
|
|
47717
|
+
}
|
|
47718
|
+
}
|
|
47719
|
+
const refLength = Math.max(1, ...[...this.linkEdges.values()].map((edge) => edge.length ?? 0));
|
|
47720
|
+
for (const link of primaryLinks) {
|
|
47721
|
+
if (seeded.has(link.name)) continue;
|
|
47722
|
+
if (this.linkEdges.size > 0 || this.linkAngles.size > 0) {
|
|
47723
|
+
addDiagnostic(
|
|
47724
|
+
`${diagnosticPrefix}Link "${link.name}" could not be constructed from the current kinematic graph. Give it an initial position, add a structural distance to an already-placed link, or add a controlling angle.`
|
|
47725
|
+
);
|
|
47726
|
+
} else {
|
|
47727
|
+
const dir = seedDirection(seedIndex++);
|
|
47728
|
+
seedPositions.set(link.name, [dir[0] * refLength, dir[1] * refLength]);
|
|
47729
|
+
seeded.add(link.name);
|
|
47730
|
+
}
|
|
47731
|
+
}
|
|
47732
|
+
return seedPositions;
|
|
47733
|
+
};
|
|
47734
|
+
const defaultControlState = {};
|
|
47735
|
+
let hasDefaultControlState = false;
|
|
47736
|
+
for (const angle of this.linkAngles.values()) {
|
|
47737
|
+
if (!angle.control && angle.target === void 0) continue;
|
|
47738
|
+
const value = ((_g2 = angle.control) == null ? void 0 : _g2.default) ?? angle.target;
|
|
47739
|
+
if (Number.isFinite(value)) {
|
|
47740
|
+
defaultControlState[angle.name] = value;
|
|
47741
|
+
hasDefaultControlState = true;
|
|
47742
|
+
}
|
|
47743
|
+
}
|
|
47744
|
+
const branchReference = hasDefaultControlState ? buildSeedPositions(defaultControlState, void 0, "Default assembly mode: ") : void 0;
|
|
47745
|
+
const positions2d = buildSeedPositions(state, branchReference);
|
|
47746
|
+
const positions = /* @__PURE__ */ new Map();
|
|
47747
|
+
for (const link of primaryLinks) {
|
|
47748
|
+
const point2 = positions2d.get(link.name);
|
|
47749
|
+
if (!point2) {
|
|
47750
|
+
addDiagnostic(`Link "${link.name}" has no solved planar position after kinematic construction.`);
|
|
47751
|
+
continue;
|
|
47752
|
+
}
|
|
47753
|
+
positions.set(link.name, unproject(point2));
|
|
47754
|
+
}
|
|
47755
|
+
const solvedDerivedLinks = this.evaluateDerivedLinks(positions);
|
|
47756
|
+
for (const derived of solvedDerivedLinks) positions.set(derived.name, cloneVec3$3(derived.position));
|
|
47757
|
+
const solvedEdges = [];
|
|
47758
|
+
const solvedAngles = [];
|
|
47759
|
+
let maxResidual = diagnostics.length > 0 ? Number.POSITIVE_INFINITY : 0;
|
|
47760
|
+
for (const edge of this.linkEdges.values()) {
|
|
47761
|
+
const edgeA = positions.get(edge.a);
|
|
47762
|
+
const edgeB = positions.get(edge.b);
|
|
47763
|
+
const solvedLength = edgeA && edgeB ? vecDistance(edgeA, edgeB) : Number.NaN;
|
|
47764
|
+
let residual = 0;
|
|
47765
|
+
if (!edgeA || !edgeB) {
|
|
47766
|
+
residual = Number.POSITIVE_INFINITY;
|
|
47767
|
+
addDiagnostic(`Edge "${edge.name}" could not be evaluated because one of its links has no solved position.`);
|
|
47768
|
+
} else {
|
|
47769
|
+
if (edge.length != null) residual = solvedLength - edge.length;
|
|
47770
|
+
if (edge.min != null && solvedLength < edge.min) residual = Math.min(residual, solvedLength - edge.min);
|
|
47771
|
+
if (edge.max != null && solvedLength > edge.max) residual = Math.max(residual, solvedLength - edge.max);
|
|
47772
|
+
}
|
|
47773
|
+
maxResidual = Math.max(maxResidual, Math.abs(residual));
|
|
47774
|
+
solvedEdges.push({
|
|
47775
|
+
name: edge.name,
|
|
47776
|
+
a: edge.a,
|
|
47777
|
+
b: edge.b,
|
|
47778
|
+
length: edge.length,
|
|
47779
|
+
solvedLength,
|
|
47780
|
+
residual,
|
|
47781
|
+
visualOnly: edge.visualOnly
|
|
47782
|
+
});
|
|
47783
|
+
}
|
|
47784
|
+
for (const angle of this.linkAngles.values()) {
|
|
47785
|
+
const angleB = positions.get(angle.b);
|
|
47786
|
+
const angleC = positions.get(angle.c);
|
|
47787
|
+
const reference = angleReferenceVector2d(angle, positions2d);
|
|
47788
|
+
const solvedValue = reference && angleB && angleC ? signedAngleBetweenVectorsAboutAxis(unprojectVector(reference), vecSub(angleC, angleB), plane.normal) : Number.NaN;
|
|
47789
|
+
const target = controlledAngleValue(angle) ?? angle.target;
|
|
47790
|
+
let residual = target == null ? 0 : normalizeAngleDeltaDeg(solvedValue - target);
|
|
47791
|
+
if (!reference || !angleB || !angleC) {
|
|
47792
|
+
residual = Number.POSITIVE_INFINITY;
|
|
47793
|
+
addDiagnostic(`Angle "${angle.name}" could not be evaluated because its reference or one of its links has no solved position.`);
|
|
47794
|
+
} else {
|
|
47795
|
+
if (angle.min != null && solvedValue < angle.min) residual = Math.min(residual, solvedValue - angle.min);
|
|
47796
|
+
if (angle.max != null && solvedValue > angle.max) residual = Math.max(residual, solvedValue - angle.max);
|
|
47797
|
+
}
|
|
47798
|
+
maxResidual = Math.max(maxResidual, Math.abs(residual));
|
|
47799
|
+
solvedAngles.push({
|
|
47800
|
+
name: angle.name,
|
|
47801
|
+
a: angle.a,
|
|
47802
|
+
b: angle.b,
|
|
47803
|
+
c: angle.c,
|
|
47804
|
+
reference: cloneAngleReference(angle.reference),
|
|
47805
|
+
target,
|
|
47806
|
+
solvedValue,
|
|
47807
|
+
residual,
|
|
47808
|
+
control: angle.control ? { ...angle.control } : void 0
|
|
47809
|
+
});
|
|
47810
|
+
}
|
|
47811
|
+
const warnings = [];
|
|
47812
|
+
for (const component of floatingComponents) {
|
|
47813
|
+
warnings.push(`Kinematic component is floating; using link "${component.gaugeLink}" as a numerical display gauge`);
|
|
47814
|
+
}
|
|
47815
|
+
if (maxResidual > 1e-3) {
|
|
47816
|
+
warnings.push(`Kinematic graph residual ${maxResidual.toFixed(6)} exceeds tolerance`);
|
|
47817
|
+
}
|
|
47818
|
+
return {
|
|
47819
|
+
metadata: {
|
|
47820
|
+
links: [...this.links.values()].map((link) => ({
|
|
47821
|
+
name: link.name,
|
|
47822
|
+
position: cloneVec3$3(positions.get(link.name) ?? link.at),
|
|
47823
|
+
fixed: link.fixed,
|
|
47824
|
+
gaugeFixed: gaugeFixed.has(link.name) || void 0
|
|
47825
|
+
})),
|
|
47826
|
+
edges: solvedEdges,
|
|
47827
|
+
angles: solvedAngles,
|
|
47828
|
+
derivedLinks: solvedDerivedLinks,
|
|
47829
|
+
controls,
|
|
47830
|
+
floatingComponents,
|
|
47831
|
+
diagnostics,
|
|
47832
|
+
maxResidual,
|
|
47833
|
+
converged: diagnostics.length === 0 && maxResidual <= 1e-3
|
|
47834
|
+
},
|
|
47835
|
+
linkPositions: positions,
|
|
47836
|
+
warnings
|
|
47837
|
+
};
|
|
47838
|
+
}
|
|
47839
|
+
evaluateDerivedLinks(basePositions) {
|
|
47840
|
+
const output = /* @__PURE__ */ new Map();
|
|
47841
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
47842
|
+
const resolve = (name) => {
|
|
47843
|
+
const existing = basePositions.get(name);
|
|
47844
|
+
if (existing && !this.derivedLinks.has(name)) return existing;
|
|
47845
|
+
const cached = output.get(name);
|
|
47846
|
+
if (cached) return cached.position;
|
|
47847
|
+
const derived = this.derivedLinks.get(name);
|
|
47848
|
+
if (!derived) {
|
|
47849
|
+
const position2 = basePositions.get(name);
|
|
47850
|
+
if (!position2) throw new Error(`Derived link references unknown solved link "${name}"`);
|
|
47851
|
+
return position2;
|
|
47852
|
+
}
|
|
47853
|
+
if (visiting.has(name)) throw new Error(`Derived link cycle detected at "${name}"`);
|
|
47854
|
+
visiting.add(name);
|
|
47855
|
+
const from = resolve(derived.fromLink);
|
|
47856
|
+
const reference = resolve(derived.referenceLink);
|
|
47857
|
+
const rawDir = derived.direction === "toward" ? vecSub(reference, from) : vecSub(from, reference);
|
|
47858
|
+
const dir = vecNormalize(rawDir);
|
|
47859
|
+
if (vecLength(dir) < 1e-10) {
|
|
47860
|
+
throw new Error(`Derived link "${name}" cannot be solved because "${derived.fromLink}" and "${derived.referenceLink}" coincide`);
|
|
47861
|
+
}
|
|
47862
|
+
const position = [
|
|
47863
|
+
from[0] + dir[0] * derived.distance,
|
|
47864
|
+
from[1] + dir[1] * derived.distance,
|
|
47865
|
+
from[2] + dir[2] * derived.distance
|
|
47866
|
+
];
|
|
47867
|
+
visiting.delete(name);
|
|
47868
|
+
const solved = { ...derived, position };
|
|
47869
|
+
output.set(name, solved);
|
|
47870
|
+
return position;
|
|
47871
|
+
};
|
|
47872
|
+
for (const name of this.derivedLinks.keys()) resolve(name);
|
|
47873
|
+
return [...output.values()];
|
|
47874
|
+
}
|
|
47875
|
+
solveKinematicLinksLegacy(state) {
|
|
46893
47876
|
var _a3, _b3, _c2, _d2, _e2, _f2;
|
|
46894
47877
|
if (this.links.size === 0) {
|
|
46895
47878
|
return { metadata: null, linkPositions: /* @__PURE__ */ new Map(), warnings: [] };
|
|
@@ -46905,8 +47888,10 @@ class Assembly {
|
|
|
46905
47888
|
(_b3 = neighbors.get(edge.b)) == null ? void 0 : _b3.add(edge.a);
|
|
46906
47889
|
}
|
|
46907
47890
|
for (const angle of this.linkAngles.values()) {
|
|
46908
|
-
|
|
46909
|
-
|
|
47891
|
+
if (angle.a) {
|
|
47892
|
+
(_c2 = neighbors.get(angle.a)) == null ? void 0 : _c2.add(angle.b);
|
|
47893
|
+
(_d2 = neighbors.get(angle.b)) == null ? void 0 : _d2.add(angle.a);
|
|
47894
|
+
}
|
|
46910
47895
|
(_e2 = neighbors.get(angle.b)) == null ? void 0 : _e2.add(angle.c);
|
|
46911
47896
|
(_f2 = neighbors.get(angle.c)) == null ? void 0 : _f2.add(angle.b);
|
|
46912
47897
|
}
|
|
@@ -46961,27 +47946,36 @@ class Assembly {
|
|
|
46961
47946
|
const link = this.links.get(name);
|
|
46962
47947
|
return !link.fixed && !gaugeFixed.has(name) && !controlledLinks.has(name);
|
|
46963
47948
|
};
|
|
47949
|
+
const angleReferenceVector = (angle, source) => {
|
|
47950
|
+
if (angle.reference) return cloneVec3$3(angle.reference.direction);
|
|
47951
|
+
if (!angle.a) return null;
|
|
47952
|
+
const a2 = source.get(angle.a);
|
|
47953
|
+
const b = source.get(angle.b);
|
|
47954
|
+
if (!a2 || !b) return null;
|
|
47955
|
+
return vecSub(a2, b);
|
|
47956
|
+
};
|
|
46964
47957
|
const anglePlaneNormal = (angle) => {
|
|
46965
|
-
const a2 = this.links.get(angle.a).at;
|
|
46966
47958
|
const b = this.links.get(angle.b).at;
|
|
46967
47959
|
const c2 = this.links.get(angle.c).at;
|
|
46968
|
-
const
|
|
47960
|
+
const reference = angle.reference ? angle.reference.direction : angle.a ? vecSub(this.links.get(angle.a).at, b) : null;
|
|
47961
|
+
if (!reference) return [0, 0, 1];
|
|
47962
|
+
const n = vecCross(reference, vecSub(c2, b));
|
|
46969
47963
|
return vecLength(n) < 1e-9 ? [0, 0, 1] : vecNormalize(n);
|
|
46970
47964
|
};
|
|
46971
47965
|
const applyAngleControls = () => {
|
|
46972
47966
|
for (const angle of this.linkAngles.values()) {
|
|
46973
47967
|
const target = controlledAngleValue(angle);
|
|
46974
47968
|
if (target == null) continue;
|
|
46975
|
-
const a2 = positions.get(angle.a);
|
|
46976
47969
|
const b = positions.get(angle.b);
|
|
46977
47970
|
const c2 = positions.get(angle.c);
|
|
46978
47971
|
if (!isMovable(angle.c) && !controlledLinks.has(angle.c)) continue;
|
|
46979
47972
|
const radius = edgeLengthBetween(angle.b, angle.c) ?? vecDistance(b, c2);
|
|
46980
47973
|
if (radius <= 1e-10) continue;
|
|
46981
|
-
const
|
|
46982
|
-
|
|
47974
|
+
const reference = angleReferenceVector(angle, positions);
|
|
47975
|
+
if (!reference) continue;
|
|
47976
|
+
const len2 = vecLength(reference);
|
|
46983
47977
|
if (len2 <= 1e-10) continue;
|
|
46984
|
-
const baUnit = [
|
|
47978
|
+
const baUnit = [reference[0] / len2, reference[1] / len2, reference[2] / len2];
|
|
46985
47979
|
const rotated = rotateAboutAxis(baUnit, anglePlaneNormal(angle), target);
|
|
46986
47980
|
positions.set(angle.c, [b[0] + rotated[0] * radius, b[1] + rotated[1] * radius, b[2] + rotated[2] * radius]);
|
|
46987
47981
|
controlledLinks.add(angle.c);
|
|
@@ -47011,6 +48005,8 @@ class Assembly {
|
|
|
47011
48005
|
}
|
|
47012
48006
|
controlledLinks.clear();
|
|
47013
48007
|
applyAngleControls();
|
|
48008
|
+
const solvedDerivedLinks = this.evaluateDerivedLinks(positions);
|
|
48009
|
+
for (const derived of solvedDerivedLinks) positions.set(derived.name, cloneVec3$3(derived.position));
|
|
47014
48010
|
const solvedEdges = [];
|
|
47015
48011
|
const solvedAngles = [];
|
|
47016
48012
|
let maxResidual = 0;
|
|
@@ -47032,14 +48028,13 @@ class Assembly {
|
|
|
47032
48028
|
});
|
|
47033
48029
|
}
|
|
47034
48030
|
for (const angle of this.linkAngles.values()) {
|
|
47035
|
-
const
|
|
47036
|
-
|
|
47037
|
-
|
|
47038
|
-
|
|
47039
|
-
anglePlaneNormal(angle)
|
|
47040
|
-
);
|
|
48031
|
+
const reference = angleReferenceVector(angle, positions);
|
|
48032
|
+
const b = positions.get(angle.b);
|
|
48033
|
+
const c2 = positions.get(angle.c);
|
|
48034
|
+
const solvedValue = reference ? signedAngleBetweenVectorsAboutAxis(reference, vecSub(c2, b), anglePlaneNormal(angle)) : Number.NaN;
|
|
47041
48035
|
const target = controlledAngleValue(angle) ?? angle.target;
|
|
47042
48036
|
let residual = target == null ? 0 : normalizeAngleDeltaDeg(solvedValue - target);
|
|
48037
|
+
if (!reference) residual = Number.POSITIVE_INFINITY;
|
|
47043
48038
|
if (angle.min != null && solvedValue < angle.min) residual = Math.min(residual, solvedValue - angle.min);
|
|
47044
48039
|
if (angle.max != null && solvedValue > angle.max) residual = Math.max(residual, solvedValue - angle.max);
|
|
47045
48040
|
maxResidual = Math.max(maxResidual, Math.abs(residual));
|
|
@@ -47048,6 +48043,7 @@ class Assembly {
|
|
|
47048
48043
|
a: angle.a,
|
|
47049
48044
|
b: angle.b,
|
|
47050
48045
|
c: angle.c,
|
|
48046
|
+
reference: cloneAngleReference(angle.reference),
|
|
47051
48047
|
target,
|
|
47052
48048
|
solvedValue,
|
|
47053
48049
|
residual,
|
|
@@ -47071,8 +48067,10 @@ class Assembly {
|
|
|
47071
48067
|
})),
|
|
47072
48068
|
edges: solvedEdges,
|
|
47073
48069
|
angles: solvedAngles,
|
|
48070
|
+
derivedLinks: solvedDerivedLinks,
|
|
47074
48071
|
controls,
|
|
47075
48072
|
floatingComponents,
|
|
48073
|
+
diagnostics: [],
|
|
47076
48074
|
maxResidual,
|
|
47077
48075
|
converged: maxResidual <= 1e-3
|
|
47078
48076
|
},
|
|
@@ -47120,8 +48118,12 @@ class Assembly {
|
|
|
47120
48118
|
const incoming = /* @__PURE__ */ new Map();
|
|
47121
48119
|
const jointsByParent = /* @__PURE__ */ new Map();
|
|
47122
48120
|
const warnings = [];
|
|
48121
|
+
const frameSolve = this.solveFrames(state);
|
|
48122
|
+
warnings.push(...frameSolve.warnings);
|
|
47123
48123
|
const kinematicSolve = this.solveKinematicLinks(state);
|
|
47124
48124
|
warnings.push(...kinematicSolve.warnings);
|
|
48125
|
+
const solvedFrameEdges = this.solveFrameEdges(frameSolve.frames);
|
|
48126
|
+
const kinematicMetadata = this.attachFrameEdgesToKinematics(kinematicSolve.metadata, solvedFrameEdges);
|
|
47125
48127
|
for (const joint2 of this.joints.values()) {
|
|
47126
48128
|
if (incoming.has(joint2.child)) {
|
|
47127
48129
|
throw new Error(`Part "${joint2.child}" has multiple parent joints`);
|
|
@@ -47131,8 +48133,9 @@ class Assembly {
|
|
|
47131
48133
|
list.push(joint2);
|
|
47132
48134
|
jointsByParent.set(joint2.parent, list);
|
|
47133
48135
|
}
|
|
47134
|
-
const
|
|
47135
|
-
|
|
48136
|
+
const unboundPartNames = [...this.parts.values()].filter((part) => !part.bindToFrame).map((part) => part.name);
|
|
48137
|
+
const roots = unboundPartNames.filter((name) => !incoming.has(name));
|
|
48138
|
+
if (roots.length === 0 && unboundPartNames.length > 0) {
|
|
47136
48139
|
throw new Error("Assembly has no root part (cyclic joint graph)");
|
|
47137
48140
|
}
|
|
47138
48141
|
const mateBaseOverrides = /* @__PURE__ */ new Map();
|
|
@@ -47235,7 +48238,7 @@ class Assembly {
|
|
|
47235
48238
|
const world = /* @__PURE__ */ new Map();
|
|
47236
48239
|
const visiting = /* @__PURE__ */ new Set();
|
|
47237
48240
|
const visited = /* @__PURE__ */ new Set();
|
|
47238
|
-
const jointValues = {};
|
|
48241
|
+
const jointValues = { ...frameSolve.jointValues };
|
|
47239
48242
|
const resolvingJointValues = /* @__PURE__ */ new Set();
|
|
47240
48243
|
const resolveJointValue = (jointName) => {
|
|
47241
48244
|
const cached = jointValues[jointName];
|
|
@@ -47285,6 +48288,13 @@ class Assembly {
|
|
|
47285
48288
|
const rootBase = mateBaseOverrides.get(rootName) ?? kinematicBaseForPart(root) ?? root.base;
|
|
47286
48289
|
dfs(rootName, rootBase);
|
|
47287
48290
|
}
|
|
48291
|
+
for (const rec of this.parts.values()) {
|
|
48292
|
+
if (!rec.bindToFrame) continue;
|
|
48293
|
+
const frameTransform2 = frameSolve.transforms.get(rec.bindToFrame);
|
|
48294
|
+
if (!frameTransform2) throw new Error(`Part "${rec.name}" references unresolved frame "${rec.bindToFrame}"`);
|
|
48295
|
+
world.set(rec.name, composeChain(rec.base, frameTransform2));
|
|
48296
|
+
visited.add(rec.name);
|
|
48297
|
+
}
|
|
47288
48298
|
if (visited.size !== this.parts.size) {
|
|
47289
48299
|
const missing = [...this.parts.keys()].filter((name) => !visited.has(name));
|
|
47290
48300
|
throw new Error(`Assembly graph unresolved for parts: ${missing.join(", ")}`);
|
|
@@ -47340,7 +48350,8 @@ class Assembly {
|
|
|
47340
48350
|
jointValues,
|
|
47341
48351
|
warnings,
|
|
47342
48352
|
mateMetadata,
|
|
47343
|
-
|
|
48353
|
+
kinematicMetadata,
|
|
48354
|
+
frameSolve.frames,
|
|
47344
48355
|
this._usedPortRefs
|
|
47345
48356
|
);
|
|
47346
48357
|
}
|
|
@@ -47453,7 +48464,7 @@ class Assembly {
|
|
|
47453
48464
|
return collectJointsView(this.buildJointsViewOptions(options, state, false), null, "assembly");
|
|
47454
48465
|
}
|
|
47455
48466
|
buildJointsViewOptions(options, state, relativeLinkControls) {
|
|
47456
|
-
var _a3, _b3, _c2, _d2;
|
|
48467
|
+
var _a3, _b3, _c2, _d2, _e2, _f2;
|
|
47457
48468
|
const solved = this.solve(state);
|
|
47458
48469
|
const def = this.describe();
|
|
47459
48470
|
const joints = [];
|
|
@@ -47497,7 +48508,31 @@ class Assembly {
|
|
|
47497
48508
|
}
|
|
47498
48509
|
joints.push(entry);
|
|
47499
48510
|
}
|
|
47500
|
-
|
|
48511
|
+
for (const j of def.frameJoints) {
|
|
48512
|
+
if (j.type === "fixed" || j.control === false) continue;
|
|
48513
|
+
const parentWorld = solved.getFrame(j.parent);
|
|
48514
|
+
const pivot = parentWorld.point([0, 0, 0]);
|
|
48515
|
+
const axisWorld = parentWorld.vector([0, 0, 1]);
|
|
48516
|
+
const axisLen = Math.hypot(axisWorld[0], axisWorld[1], axisWorld[2]);
|
|
48517
|
+
const normalizedAxis = axisLen > 1e-10 ? [axisWorld[0] / axisLen, axisWorld[1] / axisLen, axisWorld[2] / axisLen] : [0, 0, 1];
|
|
48518
|
+
const entry = {
|
|
48519
|
+
name: j.name,
|
|
48520
|
+
child: j.child,
|
|
48521
|
+
parent: j.parent,
|
|
48522
|
+
type: j.type,
|
|
48523
|
+
axis: normalizedAxis,
|
|
48524
|
+
pivot,
|
|
48525
|
+
min: j.min,
|
|
48526
|
+
max: j.max,
|
|
48527
|
+
default: ((_c2 = options.defaults) == null ? void 0 : _c2[j.name]) ?? j.defaultValue,
|
|
48528
|
+
unit: j.unit
|
|
48529
|
+
};
|
|
48530
|
+
if ((_d2 = options.overrides) == null ? void 0 : _d2[j.name]) {
|
|
48531
|
+
Object.assign(entry, options.overrides[j.name]);
|
|
48532
|
+
}
|
|
48533
|
+
joints.push(entry);
|
|
48534
|
+
}
|
|
48535
|
+
if (this.linkAngles.size > 0) {
|
|
47501
48536
|
const mateLinksOf = /* @__PURE__ */ new Map();
|
|
47502
48537
|
for (const part of this.parts.values()) {
|
|
47503
48538
|
if (part.mates.length > 0) mateLinksOf.set(part.name, new Set(part.mates.map((m2) => m2.toLink)));
|
|
@@ -47518,18 +48553,20 @@ class Assembly {
|
|
|
47518
48553
|
for (const angle of this.linkAngles.values()) {
|
|
47519
48554
|
if (angle.control) controlMoving.set(angle.c, angle);
|
|
47520
48555
|
}
|
|
48556
|
+
const controlledAngleChild = (angle) => spanPart(angle.b, angle.c) ?? firstPartMatedTo(angle.c) ?? angle.c;
|
|
47521
48557
|
const normalAtSolvedPose = (angle) => {
|
|
47522
|
-
const a2 = solved.getLinkPosition(angle.a);
|
|
47523
48558
|
const b = solved.getLinkPosition(angle.b);
|
|
47524
48559
|
const c2 = solved.getLinkPosition(angle.c);
|
|
47525
|
-
const
|
|
48560
|
+
const reference = angle.reference ? angle.reference.direction : angle.a ? vecSub(solved.getLinkPosition(angle.a), b) : null;
|
|
48561
|
+
if (!reference) return [0, 0, 1];
|
|
48562
|
+
const n = vecCross(reference, vecSub(c2, b));
|
|
47526
48563
|
return vecLength(n) < 1e-9 ? [0, 0, 1] : vecNormalize(n);
|
|
47527
48564
|
};
|
|
47528
48565
|
for (const angle of this.linkAngles.values()) {
|
|
47529
48566
|
if (!angle.control) continue;
|
|
47530
|
-
const child =
|
|
48567
|
+
const child = controlledAngleChild(angle);
|
|
47531
48568
|
const parentAngle = controlMoving.get(angle.b);
|
|
47532
|
-
const parent = parentAngle ?
|
|
48569
|
+
const parent = parentAngle ? controlledAngleChild(parentAngle) : firstPartMatedTo(angle.b);
|
|
47533
48570
|
const defValue = angle.control.default ?? angle.target ?? 0;
|
|
47534
48571
|
const absMin = angle.control.min ?? angle.min;
|
|
47535
48572
|
const absMax = angle.control.max ?? angle.max;
|
|
@@ -47542,10 +48579,10 @@ class Assembly {
|
|
|
47542
48579
|
pivot: solved.getLinkPosition(angle.b),
|
|
47543
48580
|
min: absMin !== void 0 && relativeLinkControls ? absMin - defValue : absMin,
|
|
47544
48581
|
max: absMax !== void 0 && relativeLinkControls ? absMax - defValue : absMax,
|
|
47545
|
-
default: relativeLinkControls ? 0 : ((
|
|
48582
|
+
default: relativeLinkControls ? 0 : ((_e2 = options.defaults) == null ? void 0 : _e2[angle.name]) ?? defValue,
|
|
47546
48583
|
unit: angle.control.unit
|
|
47547
48584
|
};
|
|
47548
|
-
if ((
|
|
48585
|
+
if ((_f2 = options.overrides) == null ? void 0 : _f2[angle.name]) {
|
|
47549
48586
|
Object.assign(entry, options.overrides[angle.name]);
|
|
47550
48587
|
}
|
|
47551
48588
|
joints.push(entry);
|
|
@@ -47607,7 +48644,8 @@ class Assembly {
|
|
|
47607
48644
|
part: part.part,
|
|
47608
48645
|
base: part.base,
|
|
47609
48646
|
metadata: part.metadata ? { ...part.metadata } : void 0,
|
|
47610
|
-
mates: part.mates.map((mate) => ({ ...mate }))
|
|
48647
|
+
mates: part.mates.map((mate) => ({ ...mate })),
|
|
48648
|
+
bindToFrame: part.bindToFrame
|
|
47611
48649
|
})),
|
|
47612
48650
|
joints: [...this.joints.values()].map((joint2) => ({
|
|
47613
48651
|
name: joint2.name,
|
|
@@ -47631,7 +48669,22 @@ class Assembly {
|
|
|
47631
48669
|
terms: coupling.terms.map((term) => ({ joint: term.joint, ratio: term.ratio })),
|
|
47632
48670
|
offset: coupling.offset
|
|
47633
48671
|
})),
|
|
47634
|
-
kinematics: this.describeKinematics()
|
|
48672
|
+
kinematics: this.describeKinematics(),
|
|
48673
|
+
frames: [...this.frames.values()].map((frame) => cloneAssemblyFrame(frame)),
|
|
48674
|
+
frameJoints: [...this.frameJoints.values()].map((joint2) => ({
|
|
48675
|
+
name: joint2.name,
|
|
48676
|
+
type: joint2.type,
|
|
48677
|
+
parent: joint2.parent,
|
|
48678
|
+
child: joint2.child,
|
|
48679
|
+
rest: joint2.rest,
|
|
48680
|
+
min: joint2.min,
|
|
48681
|
+
max: joint2.max,
|
|
48682
|
+
defaultValue: joint2.defaultValue,
|
|
48683
|
+
unit: joint2.unit,
|
|
48684
|
+
control: joint2.control,
|
|
48685
|
+
metadata: joint2.metadata ? { ...joint2.metadata } : void 0
|
|
48686
|
+
})),
|
|
48687
|
+
frameEdges: [...this.frameEdges.values()].map((edge) => cloneAssemblyFrameEdge(edge))
|
|
47635
48688
|
};
|
|
47636
48689
|
}
|
|
47637
48690
|
}
|
|
@@ -47858,6 +48911,9 @@ class ImportedAssembly {
|
|
|
47858
48911
|
mergeInto(parent, options) {
|
|
47859
48912
|
const def = this._assembly.describe();
|
|
47860
48913
|
const pfx = options.prefix ? `${options.prefix}.` : "";
|
|
48914
|
+
if (def.frames.length > 0 || def.frameJoints.length > 0 || def.frameEdges.length > 0) {
|
|
48915
|
+
throw new Error("mergeInto() does not yet support rig frames. Import and solve the frame-based sub-assembly directly for now.");
|
|
48916
|
+
}
|
|
47861
48917
|
const childSet = new Set(def.joints.map((j) => j.child));
|
|
47862
48918
|
const roots = def.parts.filter((p2) => !childSet.has(p2.name));
|
|
47863
48919
|
if (roots.length === 0) {
|
|
@@ -47888,14 +48944,26 @@ class ImportedAssembly {
|
|
|
47888
48944
|
});
|
|
47889
48945
|
}
|
|
47890
48946
|
for (const angle of def.kinematics.angles) {
|
|
47891
|
-
|
|
48947
|
+
const options2 = {
|
|
47892
48948
|
name: `${pfx}${angle.name}`,
|
|
47893
48949
|
value: angle.target,
|
|
47894
48950
|
min: angle.min,
|
|
47895
48951
|
max: angle.max,
|
|
47896
48952
|
control: angle.control,
|
|
47897
48953
|
metadata: angle.metadata
|
|
47898
|
-
}
|
|
48954
|
+
};
|
|
48955
|
+
if (angle.reference) {
|
|
48956
|
+
parent.addAngleBetweenLinkSegmentAndWorldDirection(`${pfx}${angle.b}`, `${pfx}${angle.c}`, angle.reference.direction, options2);
|
|
48957
|
+
} else if (angle.a) {
|
|
48958
|
+
parent.addAngleBetweenLinks(`${pfx}${angle.a}`, `${pfx}${angle.b}`, `${pfx}${angle.c}`, options2);
|
|
48959
|
+
}
|
|
48960
|
+
}
|
|
48961
|
+
for (const derived of def.kinematics.derivedLinks) {
|
|
48962
|
+
if (derived.direction === "toward") {
|
|
48963
|
+
parent.linkToward(`${pfx}${derived.name}`, `${pfx}${derived.fromLink}`, `${pfx}${derived.referenceLink}`, derived.distance);
|
|
48964
|
+
} else {
|
|
48965
|
+
parent.linkAwayFrom(`${pfx}${derived.name}`, `${pfx}${derived.fromLink}`, `${pfx}${derived.referenceLink}`, derived.distance);
|
|
48966
|
+
}
|
|
47899
48967
|
}
|
|
47900
48968
|
for (const p2 of def.parts) {
|
|
47901
48969
|
parent.addPart(`${pfx}${p2.name}`, p2.part, {
|
|
@@ -52662,7 +53730,7 @@ const EPSILON$2 = 1e-9;
|
|
|
52662
53730
|
function add$3(a2, b) {
|
|
52663
53731
|
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
52664
53732
|
}
|
|
52665
|
-
function scale$
|
|
53733
|
+
function scale$4(v, factor) {
|
|
52666
53734
|
return [v[0] * factor, v[1] * factor, v[2] * factor];
|
|
52667
53735
|
}
|
|
52668
53736
|
function sub$5(a2, b) {
|
|
@@ -52737,9 +53805,9 @@ function buildBend(points, directions, cornerIndex, radius) {
|
|
|
52737
53805
|
const angle = Math.acos(turnDot);
|
|
52738
53806
|
const trim = radius * Math.tan(angle / 2);
|
|
52739
53807
|
const axis = normalize$3(cross$4(dIn, dOut), `corner ${cornerIndex} bend axis`);
|
|
52740
|
-
const start = add$3(points[cornerIndex], scale$
|
|
52741
|
-
const end = add$3(points[cornerIndex], scale$
|
|
52742
|
-
const center = add$3(start, scale$
|
|
53808
|
+
const start = add$3(points[cornerIndex], scale$4(dIn, -trim));
|
|
53809
|
+
const end = add$3(points[cornerIndex], scale$4(dOut, trim));
|
|
53810
|
+
const center = add$3(start, scale$4(normalize$3(cross$4(axis, dIn), `corner ${cornerIndex} bend normal`), radius));
|
|
52743
53811
|
return { cornerIndex, trim, start, end, center, axis, sweepDeg: angle * 180 / Math.PI, length: radius * angle };
|
|
52744
53812
|
}
|
|
52745
53813
|
function assertBendFits(bend2, radius, segmentLengths, bends) {
|
|
@@ -52770,7 +53838,7 @@ function buildRoute3DPlanFromPolyline(pointsInput, options = {}) {
|
|
|
52770
53838
|
const segmentLengths = points.slice(1).map((point2, index2) => length$1(sub$5(point2, points[index2])));
|
|
52771
53839
|
const directions = segmentLengths.map((segmentLength, index2) => {
|
|
52772
53840
|
if (segmentLength <= EPSILON$2) throw new Error(`Curve.Route.fromPolyline: segment ${index2} is zero length.`);
|
|
52773
|
-
return scale$
|
|
53841
|
+
return scale$4(sub$5(points[index2 + 1], points[index2]), 1 / segmentLength);
|
|
52774
53842
|
});
|
|
52775
53843
|
const bends = new Array(points.length).fill(null);
|
|
52776
53844
|
if (radius > EPSILON$2) {
|
|
@@ -52803,7 +53871,7 @@ function buildRoute3DPlanFromPolyline(pointsInput, options = {}) {
|
|
|
52803
53871
|
segments,
|
|
52804
53872
|
length: routeLength,
|
|
52805
53873
|
ports: {
|
|
52806
|
-
[startPort]: portFrame(startPort, points[0], scale$
|
|
53874
|
+
[startPort]: portFrame(startPort, points[0], scale$4(directions[0], -1), 0, up),
|
|
52807
53875
|
[endPort]: portFrame(endPort, points[points.length - 1], directions[directions.length - 1], routeLength, up)
|
|
52808
53876
|
}
|
|
52809
53877
|
};
|
|
@@ -53311,7 +54379,7 @@ function variableSweep(spine, sections, options = {}) {
|
|
|
53311
54379
|
sources: isTruckVariableSweep ? ["sweep"] : ["sweep", "level-set"]
|
|
53312
54380
|
});
|
|
53313
54381
|
}
|
|
53314
|
-
function requirePositive$
|
|
54382
|
+
function requirePositive$8(value, label) {
|
|
53315
54383
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
53316
54384
|
throw new Error(`${label} must be a finite number > 0, got ${JSON.stringify(value)}`);
|
|
53317
54385
|
}
|
|
@@ -53330,10 +54398,10 @@ function requireIntegerAtLeast(value, label, min2) {
|
|
|
53330
54398
|
return value;
|
|
53331
54399
|
}
|
|
53332
54400
|
function normalizeHelixOptions(options, apiName) {
|
|
53333
|
-
const radius = requirePositive$
|
|
53334
|
-
const pitch = options.pitch == null ? void 0 : requirePositive$
|
|
53335
|
-
const turns = options.turns == null ? void 0 : requirePositive$
|
|
53336
|
-
const height = options.height == null ? void 0 : requirePositive$
|
|
54401
|
+
const radius = requirePositive$8(options.radius, `${apiName}.radius`);
|
|
54402
|
+
const pitch = options.pitch == null ? void 0 : requirePositive$8(options.pitch, `${apiName}.pitch`);
|
|
54403
|
+
const turns = options.turns == null ? void 0 : requirePositive$8(options.turns, `${apiName}.turns`);
|
|
54404
|
+
const height = options.height == null ? void 0 : requirePositive$8(options.height, `${apiName}.height`);
|
|
53337
54405
|
const provided = [pitch != null, turns != null, height != null].filter(Boolean).length;
|
|
53338
54406
|
if (provided < 2) {
|
|
53339
54407
|
throw new Error(`${apiName}: provide any two of pitch, turns, and height so the third can be derived.`);
|
|
@@ -53419,7 +54487,7 @@ function buildHelixCoil(profileOrOptions, maybeOptions) {
|
|
|
53419
54487
|
if (!options) throw new Error("Helix.coil: options are required.");
|
|
53420
54488
|
const spec2 = normalizeHelixOptions(options, "Helix.coil");
|
|
53421
54489
|
const profile = hasCustomProfile ? profileOrOptions : circle2d(
|
|
53422
|
-
requirePositive$
|
|
54490
|
+
requirePositive$8(options.wireRadius, "Helix.coil.wireRadius"),
|
|
53423
54491
|
requireIntegerAtLeast(options.profileSegments ?? 24, "Helix.coil.profileSegments", 8)
|
|
53424
54492
|
);
|
|
53425
54493
|
if (profile.isEmpty()) throw new Error("Helix.coil: profile must not be empty.");
|
|
@@ -54002,6 +55070,12 @@ const Curve = {
|
|
|
54002
55070
|
function sub$4(a2, b) {
|
|
54003
55071
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
54004
55072
|
}
|
|
55073
|
+
function addVec(a2, b) {
|
|
55074
|
+
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
55075
|
+
}
|
|
55076
|
+
function scale$3(v, s) {
|
|
55077
|
+
return [v[0] * s, v[1] * s, v[2] * s];
|
|
55078
|
+
}
|
|
54005
55079
|
function dot$4(a2, b) {
|
|
54006
55080
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
54007
55081
|
}
|
|
@@ -54018,6 +55092,56 @@ function normalize$2(v) {
|
|
|
54018
55092
|
function clampDot(d2) {
|
|
54019
55093
|
return Math.max(-1, Math.min(1, d2));
|
|
54020
55094
|
}
|
|
55095
|
+
function requirePositiveFinite(value, label) {
|
|
55096
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(`${label} must be a finite number > 0`);
|
|
55097
|
+
return value;
|
|
55098
|
+
}
|
|
55099
|
+
function requireNonNegativeFinite(value, label) {
|
|
55100
|
+
if (!Number.isFinite(value) || value < 0) throw new Error(`${label} must be a finite number >= 0`);
|
|
55101
|
+
return value;
|
|
55102
|
+
}
|
|
55103
|
+
function requireFiniteVec3$1(value, label) {
|
|
55104
|
+
if (value == null) return void 0;
|
|
55105
|
+
if (!Array.isArray(value) || value.length !== 3) throw new Error(`${label} must be [x, y, z]`);
|
|
55106
|
+
const result = [Number(value[0]), Number(value[1]), Number(value[2])];
|
|
55107
|
+
if (!Number.isFinite(result[0]) || !Number.isFinite(result[1]) || !Number.isFinite(result[2])) {
|
|
55108
|
+
throw new Error(`${label} must contain finite coordinates`);
|
|
55109
|
+
}
|
|
55110
|
+
return result;
|
|
55111
|
+
}
|
|
55112
|
+
function normalizedDirection(value, label) {
|
|
55113
|
+
const len2 = vecLen(value);
|
|
55114
|
+
if (len2 < 1e-12) throw new Error(`${label} must not be the zero vector`);
|
|
55115
|
+
return [value[0] / len2, value[1] / len2, value[2] / len2];
|
|
55116
|
+
}
|
|
55117
|
+
function stablePerpendicularAxis$1(direction2) {
|
|
55118
|
+
const seed = Math.abs(direction2[1]) < 0.9 ? [0, 1, 0] : [1, 0, 0];
|
|
55119
|
+
return normalizedDirection(sub$4(seed, scale$3(direction2, dot$4(direction2, seed))), "elbow: turn plane");
|
|
55120
|
+
}
|
|
55121
|
+
function rotateDirection(v, axis, angleRad) {
|
|
55122
|
+
const c2 = Math.cos(angleRad);
|
|
55123
|
+
const s = Math.sin(angleRad);
|
|
55124
|
+
const axisDot = dot$4(axis, v);
|
|
55125
|
+
const axisCross = cross$3(axis, v);
|
|
55126
|
+
return [
|
|
55127
|
+
v[0] * c2 + axisCross[0] * s + axis[0] * axisDot * (1 - c2),
|
|
55128
|
+
v[1] * c2 + axisCross[1] * s + axis[1] * axisDot * (1 - c2),
|
|
55129
|
+
v[2] * c2 + axisCross[2] * s + axis[2] * axisDot * (1 - c2)
|
|
55130
|
+
];
|
|
55131
|
+
}
|
|
55132
|
+
function angleBetweenDirections(from, to) {
|
|
55133
|
+
return Math.acos(clampDot(dot$4(from, to)));
|
|
55134
|
+
}
|
|
55135
|
+
function turnAxisForDirections(from, to) {
|
|
55136
|
+
const axis = cross$3(from, to);
|
|
55137
|
+
if (vecLen(axis) >= 1e-12) return normalizedDirection(axis, "elbow: turn axis");
|
|
55138
|
+
if (dot$4(from, to) > 0) throw new Error("elbow: from and to directions are collinear; angle too small");
|
|
55139
|
+
return stablePerpendicularAxis$1(from);
|
|
55140
|
+
}
|
|
55141
|
+
function profileForPipeRadius(pipeRadius, wall, segments) {
|
|
55142
|
+
const outer = circle2d(pipeRadius, segments);
|
|
55143
|
+
return wall != null && wall > 0 ? difference2d(outer, circle2d(pipeRadius - wall, segments)) : outer;
|
|
55144
|
+
}
|
|
54021
55145
|
function pipeRoute(points, radius, options) {
|
|
54022
55146
|
if (points.length < 2) throw new Error("pipeRoute needs at least 2 points");
|
|
54023
55147
|
const bendR = (options == null ? void 0 : options.bendRadius) ?? radius * 4;
|
|
@@ -54049,88 +55173,40 @@ function simplifyStraightPipeRoutePoints(points) {
|
|
|
54049
55173
|
return simplified;
|
|
54050
55174
|
}
|
|
54051
55175
|
function elbow(pipeRadius, bendRadius, angle, options) {
|
|
54052
|
-
|
|
54053
|
-
|
|
54054
|
-
|
|
54055
|
-
|
|
54056
|
-
|
|
54057
|
-
if (
|
|
54058
|
-
|
|
54059
|
-
wall
|
|
54060
|
-
|
|
54061
|
-
|
|
54062
|
-
|
|
55176
|
+
requirePositiveFinite(pipeRadius, "elbow: pipeRadius");
|
|
55177
|
+
requirePositiveFinite(bendRadius, "elbow: bendRadius");
|
|
55178
|
+
const opts = typeof angle === "object" && angle !== null ? angle : options;
|
|
55179
|
+
const wall = opts == null ? void 0 : opts.wall;
|
|
55180
|
+
const segments = (opts == null ? void 0 : opts.segments) ?? 32;
|
|
55181
|
+
if (!Number.isFinite(segments) || segments < 3) throw new Error("elbow: segments must be a finite number >= 3");
|
|
55182
|
+
if (wall != null && requireNonNegativeFinite(wall, "elbow: wall") >= pipeRadius) {
|
|
55183
|
+
throw new Error("elbow: wall must be smaller than pipeRadius");
|
|
55184
|
+
}
|
|
55185
|
+
const from = normalizedDirection(requireFiniteVec3$1(opts == null ? void 0 : opts.from, "elbow: from") ?? [0, 0, 1], "elbow: from");
|
|
55186
|
+
const toInput = requireFiniteVec3$1(opts == null ? void 0 : opts.to, "elbow: to");
|
|
55187
|
+
const angleDegInput = typeof angle === "number" ? angle : void 0;
|
|
55188
|
+
const angleRadInput = (angleDegInput ?? 90) * Math.PI / 180;
|
|
55189
|
+
let angleRad;
|
|
55190
|
+
let turnAxis;
|
|
55191
|
+
if (toInput) {
|
|
55192
|
+
const to = normalizedDirection(toInput, "elbow: to");
|
|
55193
|
+
angleRad = angleBetweenDirections(from, to);
|
|
55194
|
+
turnAxis = turnAxisForDirections(from, to);
|
|
54063
55195
|
} else {
|
|
54064
|
-
|
|
54065
|
-
|
|
54066
|
-
|
|
54067
|
-
|
|
54068
|
-
|
|
54069
|
-
}
|
|
54070
|
-
if (fromDir && toDir) {
|
|
54071
|
-
const nFrom = normalize$2(fromDir);
|
|
54072
|
-
const nTo = normalize$2(toDir);
|
|
54073
|
-
const d2 = clampDot(dot$4(nFrom, nTo));
|
|
54074
|
-
angleDeg = Math.acos(d2) * 180 / Math.PI;
|
|
54075
|
-
}
|
|
54076
|
-
if (angleDeg < 0.01) throw new Error("elbow: angle too small");
|
|
54077
|
-
const circlePts = [];
|
|
54078
|
-
for (let i = 0; i < segs; i++) {
|
|
54079
|
-
const a2 = i / segs * Math.PI * 2;
|
|
54080
|
-
circlePts.push([bendRadius + pipeRadius * Math.cos(a2), pipeRadius * Math.sin(a2)]);
|
|
54081
|
-
}
|
|
54082
|
-
const bendSegs = Math.max(4, Math.ceil(segs * angleDeg / 360));
|
|
54083
|
-
const outerPlan = {
|
|
54084
|
-
kind: "revolve",
|
|
54085
|
-
profile: { kind: "polygon", points: circlePts, transforms: [] },
|
|
54086
|
-
degrees: angleDeg,
|
|
54087
|
-
segments: bendSegs
|
|
54088
|
-
};
|
|
54089
|
-
let bendShape = buildShapeFromCompilePlan(outerPlan);
|
|
54090
|
-
if (wall != null && wall > 0) {
|
|
54091
|
-
const innerPts = [];
|
|
54092
|
-
const innerR = pipeRadius - wall;
|
|
54093
|
-
for (let i = 0; i < segs; i++) {
|
|
54094
|
-
const a2 = i / segs * Math.PI * 2;
|
|
54095
|
-
innerPts.push([bendRadius + innerR * Math.cos(a2), innerR * Math.sin(a2)]);
|
|
54096
|
-
}
|
|
54097
|
-
const innerPlan = {
|
|
54098
|
-
kind: "revolve",
|
|
54099
|
-
profile: { kind: "polygon", points: innerPts, transforms: [] },
|
|
54100
|
-
degrees: angleDeg,
|
|
54101
|
-
segments: bendSegs
|
|
54102
|
-
};
|
|
54103
|
-
const innerBend = buildShapeFromCompilePlan(innerPlan);
|
|
54104
|
-
bendShape = bendShape.subtract(innerBend);
|
|
54105
|
-
}
|
|
54106
|
-
if (fromDir && toDir) {
|
|
54107
|
-
const nFrom = normalize$2(fromDir);
|
|
54108
|
-
const nTo = normalize$2(toDir);
|
|
54109
|
-
const crossVec = cross$3(nFrom, nTo);
|
|
54110
|
-
const crossLen = vecLen(crossVec);
|
|
54111
|
-
if (crossLen < 1e-10) return bendShape;
|
|
54112
|
-
const axis = normalize$2(crossVec);
|
|
54113
|
-
const perpDir = cross$3(axis, nFrom);
|
|
54114
|
-
bendShape = bendShape.transform([
|
|
54115
|
-
perpDir[0],
|
|
54116
|
-
perpDir[1],
|
|
54117
|
-
perpDir[2],
|
|
54118
|
-
0,
|
|
54119
|
-
nFrom[0],
|
|
54120
|
-
nFrom[1],
|
|
54121
|
-
nFrom[2],
|
|
54122
|
-
0,
|
|
54123
|
-
axis[0],
|
|
54124
|
-
axis[1],
|
|
54125
|
-
axis[2],
|
|
54126
|
-
0,
|
|
54127
|
-
0,
|
|
54128
|
-
0,
|
|
54129
|
-
0,
|
|
54130
|
-
1
|
|
54131
|
-
]);
|
|
55196
|
+
if (!Number.isFinite(angleRadInput) || angleRadInput <= 1e-6 || angleRadInput > Math.PI) {
|
|
55197
|
+
throw new Error("elbow: angle must be finite and in the range (0, 180] degrees");
|
|
55198
|
+
}
|
|
55199
|
+
turnAxis = stablePerpendicularAxis$1(from);
|
|
55200
|
+
angleRad = angleRadInput;
|
|
54132
55201
|
}
|
|
54133
|
-
|
|
55202
|
+
if (angleRad <= 1e-6) throw new Error("elbow: angle too small");
|
|
55203
|
+
const inward = normalizedDirection(cross$3(turnAxis, from), "elbow: bend normal");
|
|
55204
|
+
const center = scale$3(inward, bendRadius);
|
|
55205
|
+
const endRadial = rotateDirection(scale$3(inward, -bendRadius), turnAxis, angleRad);
|
|
55206
|
+
const end = addVec(center, endRadial);
|
|
55207
|
+
const profile = profileForPipeRadius(pipeRadius, wall, Math.floor(segments));
|
|
55208
|
+
const pathSamples = Math.max(6, Math.ceil(segments * (angleRad * 180 / Math.PI) / 360) + 1);
|
|
55209
|
+
return sweep(profile, Curve.Arc({ start: [0, 0, 0], end, tangent: from }), { samples: pathSamples, up: turnAxis });
|
|
54134
55210
|
}
|
|
54135
55211
|
const EPS$7 = 1e-9;
|
|
54136
55212
|
function assertFinitePositive(apiName, name, value) {
|
|
@@ -54629,7 +55705,7 @@ function spurGear(options) {
|
|
|
54629
55705
|
});
|
|
54630
55706
|
return attachGearMeta(shapeWithConnectors, meta2);
|
|
54631
55707
|
}
|
|
54632
|
-
function requirePositive$
|
|
55708
|
+
function requirePositive$7(scope, name, value) {
|
|
54633
55709
|
if (!isFinitePositive(value)) throw new Error(`${scope}: "${name}" must be > 0`);
|
|
54634
55710
|
}
|
|
54635
55711
|
function requireOptionalBore(scope, boreDiameter, maxDiameter) {
|
|
@@ -54651,8 +55727,8 @@ function cutBore$1(shape, boreDiameter) {
|
|
|
54651
55727
|
return shape.subtract(cutter);
|
|
54652
55728
|
}
|
|
54653
55729
|
function gearBodyDisk(options) {
|
|
54654
|
-
requirePositive$
|
|
54655
|
-
requirePositive$
|
|
55730
|
+
requirePositive$7("gearBodyDisk", "outerRadius", options.outerRadius);
|
|
55731
|
+
requirePositive$7("gearBodyDisk", "faceWidth", options.faceWidth);
|
|
54656
55732
|
const bore = requireOptionalBore("gearBodyDisk", options.boreDiameter, options.outerRadius * 2);
|
|
54657
55733
|
const segments = resolveSegments(options.segments);
|
|
54658
55734
|
const outer = circle2d(options.outerRadius, segments);
|
|
@@ -54660,14 +55736,14 @@ function gearBodyDisk(options) {
|
|
|
54660
55736
|
return sketchExtrude(profile, options.faceWidth);
|
|
54661
55737
|
}
|
|
54662
55738
|
function gearBodyDiskWithHub(options) {
|
|
54663
|
-
requirePositive$
|
|
55739
|
+
requirePositive$7("gearBodyDiskWithHub", "hubDiameter", options.hubDiameter);
|
|
54664
55740
|
if (options.hubDiameter >= options.outerRadius * 2) {
|
|
54665
55741
|
throw new Error('gearBodyDiskWithHub: "hubDiameter" must be smaller than the outer diameter');
|
|
54666
55742
|
}
|
|
54667
55743
|
const bore = requireOptionalBore("gearBodyDiskWithHub", options.boreDiameter, options.hubDiameter);
|
|
54668
55744
|
const base = gearBodyDisk({ ...options, boreDiameter: 0 });
|
|
54669
55745
|
const hubFaceWidth = options.hubFaceWidth ?? options.faceWidth * 1.5;
|
|
54670
|
-
requirePositive$
|
|
55746
|
+
requirePositive$7("gearBodyDiskWithHub", "hubFaceWidth", hubFaceWidth);
|
|
54671
55747
|
const hub = cylinder(hubFaceWidth, options.hubDiameter * 0.5, void 0, options.segments).translate(
|
|
54672
55748
|
0,
|
|
54673
55749
|
0,
|
|
@@ -54676,11 +55752,11 @@ function gearBodyDiskWithHub(options) {
|
|
|
54676
55752
|
return cutBore$1(base.add(hub), bore);
|
|
54677
55753
|
}
|
|
54678
55754
|
function gearBodySpoked(options) {
|
|
54679
|
-
requirePositive$
|
|
54680
|
-
requirePositive$
|
|
54681
|
-
requirePositive$
|
|
54682
|
-
requirePositive$
|
|
54683
|
-
requirePositive$
|
|
55755
|
+
requirePositive$7("gearBodySpoked", "outerRadius", options.outerRadius);
|
|
55756
|
+
requirePositive$7("gearBodySpoked", "faceWidth", options.faceWidth);
|
|
55757
|
+
requirePositive$7("gearBodySpoked", "rimWidth", options.rimWidth);
|
|
55758
|
+
requirePositive$7("gearBodySpoked", "hubDiameter", options.hubDiameter);
|
|
55759
|
+
requirePositive$7("gearBodySpoked", "spokeWidth", options.spokeWidth);
|
|
54684
55760
|
if (!Number.isInteger(options.spokeCount) || options.spokeCount < 2) {
|
|
54685
55761
|
throw new Error('gearBodySpoked: "spokeCount" must be an integer >= 2');
|
|
54686
55762
|
}
|
|
@@ -54703,12 +55779,12 @@ function gearBodySpoked(options) {
|
|
|
54703
55779
|
}
|
|
54704
55780
|
function gearBodyFromProfile(profile, options) {
|
|
54705
55781
|
if (!(profile instanceof Sketch)) throw new Error('gearBodyFromProfile: "profile" must be a Sketch');
|
|
54706
|
-
requirePositive$
|
|
55782
|
+
requirePositive$7("gearBodyFromProfile", "faceWidth", options.faceWidth);
|
|
54707
55783
|
const bore = options.boreDiameter ?? 0;
|
|
54708
55784
|
if (!Number.isFinite(bore) || bore < 0) throw new Error('gearBodyFromProfile: "boreDiameter" must be >= 0');
|
|
54709
55785
|
return cutBore$1(sketchExtrude(profile, options.faceWidth), bore);
|
|
54710
55786
|
}
|
|
54711
|
-
function requirePositive$
|
|
55787
|
+
function requirePositive$6(scope, name, value) {
|
|
54712
55788
|
if (!isFinitePositive(value)) throw new Error(`${scope}: "${name}" must be > 0`);
|
|
54713
55789
|
}
|
|
54714
55790
|
function requireFiniteAngle(scope, name, value) {
|
|
@@ -54770,7 +55846,7 @@ function buildSpurTeethRegion(options, name, faceWidth) {
|
|
|
54770
55846
|
}
|
|
54771
55847
|
function buildSolidArcRegion(options, name, faceWidth) {
|
|
54772
55848
|
const scope = "driveWheel.addSolidArcBetween";
|
|
54773
|
-
requirePositive$
|
|
55849
|
+
requirePositive$6(scope, "outerRadius", options.outerRadius);
|
|
54774
55850
|
const innerRadius = options.innerRadius ?? 0;
|
|
54775
55851
|
if (!Number.isFinite(innerRadius) || innerRadius < 0) throw new Error(`${scope}: "innerRadius" must be >= 0`);
|
|
54776
55852
|
if (innerRadius >= options.outerRadius) throw new Error(`${scope}: "innerRadius" must be smaller than "outerRadius"`);
|
|
@@ -54836,7 +55912,7 @@ class DriveWheelBuilder {
|
|
|
54836
55912
|
__publicField(this, "boreDiameter");
|
|
54837
55913
|
__publicField(this, "regions", []);
|
|
54838
55914
|
if (options.body !== void 0 && !(options.body instanceof Shape)) throw new Error('driveWheel: "body" must be a Shape');
|
|
54839
|
-
if (options.faceWidth !== void 0) requirePositive$
|
|
55915
|
+
if (options.faceWidth !== void 0) requirePositive$6("driveWheel", "faceWidth", options.faceWidth);
|
|
54840
55916
|
const boreDiameter = options.boreDiameter ?? 0;
|
|
54841
55917
|
if (!Number.isFinite(boreDiameter) || boreDiameter < 0) throw new Error('driveWheel: "boreDiameter" must be >= 0');
|
|
54842
55918
|
this.body = options.body;
|
|
@@ -54871,7 +55947,7 @@ class DriveWheelBuilder {
|
|
|
54871
55947
|
if (options.innerRadius !== void 0 && (!Number.isFinite(options.innerRadius) || options.innerRadius < 0)) {
|
|
54872
55948
|
throw new Error(`${scope}: "innerRadius" must be >= 0`);
|
|
54873
55949
|
}
|
|
54874
|
-
if (options.outerRadius !== void 0) requirePositive$
|
|
55950
|
+
if (options.outerRadius !== void 0) requirePositive$6(scope, "outerRadius", options.outerRadius);
|
|
54875
55951
|
this.regions.push({
|
|
54876
55952
|
shape: shape.clone(),
|
|
54877
55953
|
meta: {
|
|
@@ -54937,7 +56013,7 @@ class DriveWheelBuilder {
|
|
|
54937
56013
|
resolveFaceWidth(scope, localFaceWidth) {
|
|
54938
56014
|
const faceWidth = localFaceWidth ?? this.faceWidth;
|
|
54939
56015
|
if (faceWidth === void 0) throw new Error(`${scope}: "faceWidth" is required unless driveWheel({ faceWidth }) was set`);
|
|
54940
|
-
requirePositive$
|
|
56016
|
+
requirePositive$6(scope, "faceWidth", faceWidth);
|
|
54941
56017
|
if (this.faceWidth !== void 0 && localFaceWidth !== void 0 && Math.abs(this.faceWidth - localFaceWidth) > EPSILON$1) {
|
|
54942
56018
|
throw new Error(`${scope}: region faceWidth must match driveWheel faceWidth`);
|
|
54943
56019
|
}
|
|
@@ -56090,1860 +57166,6 @@ function washer(size, options) {
|
|
|
56090
57166
|
const bore = cylinder(dims.t + 1, dims.id / 2, void 0, segs);
|
|
56091
57167
|
return outer.subtract(bore);
|
|
56092
57168
|
}
|
|
56093
|
-
function requirePositive$6(value, name) {
|
|
56094
|
-
if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive finite number`);
|
|
56095
|
-
return value;
|
|
56096
|
-
}
|
|
56097
|
-
function requireNonNegative(value, name) {
|
|
56098
|
-
if (!Number.isFinite(value) || value < 0) throw new Error(`${name} must be a non-negative finite number`);
|
|
56099
|
-
return value;
|
|
56100
|
-
}
|
|
56101
|
-
function metricWasherSizeForPin(pinDiameter) {
|
|
56102
|
-
if (pinDiameter <= 2) return "M2";
|
|
56103
|
-
if (pinDiameter <= 2.5) return "M2.5";
|
|
56104
|
-
if (pinDiameter <= 3) return "M3";
|
|
56105
|
-
if (pinDiameter <= 4) return "M4";
|
|
56106
|
-
if (pinDiameter <= 5) return "M5";
|
|
56107
|
-
if (pinDiameter <= 6) return "M6";
|
|
56108
|
-
if (pinDiameter <= 8) return "M8";
|
|
56109
|
-
return "M10";
|
|
56110
|
-
}
|
|
56111
|
-
function cylinderAlongX(length4, radius, xCenter, segments) {
|
|
56112
|
-
return cylinder(length4, radius, void 0, segments).pointAlong([1, 0, 0]).translate(xCenter - length4 / 2, 0, 0);
|
|
56113
|
-
}
|
|
56114
|
-
function tubeAlongX(length4, outerRadius, innerRadius, xCenter, segments) {
|
|
56115
|
-
return cylinderAlongX(length4, outerRadius, xCenter, segments).subtract(cylinderAlongX(length4 + 0.4, innerRadius, xCenter, segments));
|
|
56116
|
-
}
|
|
56117
|
-
function cylinderAlongY(length4, radius, yCenter, segments) {
|
|
56118
|
-
return cylinder(length4, radius, void 0, segments).pointAlong([0, 1, 0]).translate(0, yCenter - length4 / 2, 0);
|
|
56119
|
-
}
|
|
56120
|
-
function tubeAlongY(length4, outerRadius, innerRadius, yCenter, segments) {
|
|
56121
|
-
return cylinderAlongY(length4, outerRadius, yCenter, segments).subtract(cylinderAlongY(length4 + 0.4, innerRadius, yCenter, segments));
|
|
56122
|
-
}
|
|
56123
|
-
function tubeAlongZ(height, outerRadius, innerRadius, segments) {
|
|
56124
|
-
return cylinder(height, outerRadius, void 0, segments).subtract(
|
|
56125
|
-
cylinder(height + 0.4, innerRadius, void 0, segments).translate(0, 0, -0.2)
|
|
56126
|
-
);
|
|
56127
|
-
}
|
|
56128
|
-
function washerAlongX(size, xCenter, segments) {
|
|
56129
|
-
const dims = WASHER_TABLE[size];
|
|
56130
|
-
return washer(size, { segments }).pointAlong([1, 0, 0]).translate(xCenter - dims.t / 2, 0, 0);
|
|
56131
|
-
}
|
|
56132
|
-
function resolveBoltInset(raw, fallback) {
|
|
56133
|
-
if (raw === void 0) return [fallback, fallback];
|
|
56134
|
-
if (typeof raw === "number") return [requirePositive$6(raw, "boltInset"), requirePositive$6(raw, "boltInset")];
|
|
56135
|
-
if (raw.length !== 2) throw new Error("boltInset tuple must be [x, y]");
|
|
56136
|
-
return [requirePositive$6(raw[0], "boltInset[0]"), requirePositive$6(raw[1], "boltInset[1]")];
|
|
56137
|
-
}
|
|
56138
|
-
function validateBoltPositionsForServiceCover(args) {
|
|
56139
|
-
args.positions.forEach(([x2, y2], index2) => {
|
|
56140
|
-
if (!Number.isFinite(x2) || !Number.isFinite(y2)) {
|
|
56141
|
-
throw new Error(`boltedServiceCover: boltPositions[${index2}] must contain finite numbers`);
|
|
56142
|
-
}
|
|
56143
|
-
if (Math.abs(x2) + args.holeRadius >= args.coverWidth / 2 || Math.abs(y2) + args.holeRadius >= args.coverDepth / 2) {
|
|
56144
|
-
throw new Error(`boltedServiceCover: boltPositions[${index2}] is too close to the cover edge`);
|
|
56145
|
-
}
|
|
56146
|
-
const overlapsOpening = Math.abs(x2) - args.holeRadius <= args.openingWidth / 2 && Math.abs(y2) - args.holeRadius <= args.openingDepth / 2;
|
|
56147
|
-
if (overlapsOpening) {
|
|
56148
|
-
throw new Error(
|
|
56149
|
-
`boltedServiceCover: boltPositions[${index2}] lands over the service opening; decrease boltInset, increase ledgeWidth, or provide a smaller opening`
|
|
56150
|
-
);
|
|
56151
|
-
}
|
|
56152
|
-
});
|
|
56153
|
-
}
|
|
56154
|
-
function placeCutterAtPositions(cutter, positions, z2) {
|
|
56155
|
-
return union(...positions.map(([x2, y2]) => cutter.translate(x2, y2, z2)));
|
|
56156
|
-
}
|
|
56157
|
-
function boltedServiceCover(options) {
|
|
56158
|
-
const width = requirePositive$6(options.width, "width");
|
|
56159
|
-
const depth = requirePositive$6(options.depth, "depth");
|
|
56160
|
-
const coverThickness = requirePositive$6(options.coverThickness ?? 3, "coverThickness");
|
|
56161
|
-
const parentThickness = requirePositive$6(options.parentThickness ?? 8, "parentThickness");
|
|
56162
|
-
const ledgeWidth = requirePositive$6(options.ledgeWidth ?? 8, "ledgeWidth");
|
|
56163
|
-
const gasketThickness = Math.max(0, options.gasketThickness ?? 0.8);
|
|
56164
|
-
const gasketInset = Math.max(0, options.gasketInset ?? 2);
|
|
56165
|
-
const screwSize = options.screwSize ?? "M4";
|
|
56166
|
-
const segments = options.segments ?? 36;
|
|
56167
|
-
const sizeData = METRIC_HOLE_TABLE[screwSize];
|
|
56168
|
-
if (!sizeData) throw new Error(`boltedServiceCover: unsupported screwSize "${screwSize}"`);
|
|
56169
|
-
const screwLength = requirePositive$6(options.screwLength ?? parentThickness + gasketThickness + coverThickness + 4, "screwLength");
|
|
56170
|
-
const coverFit = options.coverFit ?? "normal";
|
|
56171
|
-
const counterboreEnabled = options.counterbore ?? true;
|
|
56172
|
-
const [insetX, insetY] = resolveBoltInset(options.boltInset, Math.max(ledgeWidth * 0.65, sizeData.head * 0.75));
|
|
56173
|
-
if (insetX * 2 >= width || insetY * 2 >= depth) {
|
|
56174
|
-
throw new Error("boltedServiceCover: boltInset leaves no room for a four-corner bolt pattern");
|
|
56175
|
-
}
|
|
56176
|
-
const boltPositions = options.boltPositions ?? [
|
|
56177
|
-
[-width / 2 + insetX, -depth / 2 + insetY],
|
|
56178
|
-
[width / 2 - insetX, -depth / 2 + insetY],
|
|
56179
|
-
[-width / 2 + insetX, depth / 2 - insetY],
|
|
56180
|
-
[width / 2 - insetX, depth / 2 - insetY]
|
|
56181
|
-
];
|
|
56182
|
-
if (boltPositions.length === 0) throw new Error("boltedServiceCover: boltPositions must contain at least one point");
|
|
56183
|
-
const parentWidth = width + ledgeWidth * 2;
|
|
56184
|
-
const parentDepth = depth + ledgeWidth * 2;
|
|
56185
|
-
const openingWidth = Math.max(1, width - ledgeWidth * 2);
|
|
56186
|
-
const openingDepth = Math.max(1, depth - ledgeWidth * 2);
|
|
56187
|
-
validateBoltPositionsForServiceCover({
|
|
56188
|
-
positions: boltPositions,
|
|
56189
|
-
coverWidth: width,
|
|
56190
|
-
coverDepth: depth,
|
|
56191
|
-
openingWidth,
|
|
56192
|
-
openingDepth,
|
|
56193
|
-
holeRadius: sizeData[coverFit] / 2
|
|
56194
|
-
});
|
|
56195
|
-
const coverHole = fastenerHole({
|
|
56196
|
-
size: screwSize,
|
|
56197
|
-
fit: coverFit,
|
|
56198
|
-
depth: coverThickness + 0.6,
|
|
56199
|
-
center: true,
|
|
56200
|
-
segments,
|
|
56201
|
-
...counterboreEnabled ? { counterbore: { depth: Math.min(coverThickness * 0.6, Math.max(0.6, coverThickness - 0.4)) } } : {}
|
|
56202
|
-
});
|
|
56203
|
-
const parentTap = fastenerHole({ size: screwSize, fit: "tap", depth: parentThickness + 0.6, center: true, segments });
|
|
56204
|
-
const parentThreadEnvelope = fastenerHole({
|
|
56205
|
-
size: screwSize,
|
|
56206
|
-
fit: "close",
|
|
56207
|
-
depth: parentThickness + 0.6,
|
|
56208
|
-
center: true,
|
|
56209
|
-
segments
|
|
56210
|
-
});
|
|
56211
|
-
const openingCutter = box(openingWidth, openingDepth, parentThickness + 1).translate(0, 0, -0.5);
|
|
56212
|
-
const parentTappedPattern = placeCutterAtPositions(parentTap, boltPositions, parentThickness / 2);
|
|
56213
|
-
const parentThreadEnvelopePattern = placeCutterAtPositions(parentThreadEnvelope, boltPositions, parentThickness / 2);
|
|
56214
|
-
const parent = box(parentWidth, parentDepth, parentThickness).subtract(openingCutter).subtract(parentThreadEnvelopePattern).color("#4b5563");
|
|
56215
|
-
let coverBlank = box(width, depth, coverThickness);
|
|
56216
|
-
if (options.pullTabs ?? true) {
|
|
56217
|
-
const tabWidth = Math.min(width * 0.18, Math.max(sizeData.head * 1.6, 12));
|
|
56218
|
-
const tabDepth = Math.max(4, coverThickness * 1.4);
|
|
56219
|
-
const tabOverlap = Math.min(0.5, tabDepth * 0.25);
|
|
56220
|
-
const tabY = -depth / 2 - tabDepth / 2 + tabOverlap;
|
|
56221
|
-
const tabX = width * 0.23;
|
|
56222
|
-
coverBlank = union(
|
|
56223
|
-
coverBlank,
|
|
56224
|
-
box(tabWidth, tabDepth, coverThickness).translate(-tabX, tabY, 0),
|
|
56225
|
-
box(tabWidth, tabDepth, coverThickness).translate(tabX, tabY, 0)
|
|
56226
|
-
);
|
|
56227
|
-
}
|
|
56228
|
-
const coverClearancePattern = placeCutterAtPositions(coverHole, boltPositions, coverThickness / 2);
|
|
56229
|
-
const cover = coverBlank.subtract(coverClearancePattern).translate(0, 0, parentThickness + gasketThickness).color("#334155");
|
|
56230
|
-
const gasket = gasketThickness > 0 ? box(Math.max(1, width - gasketInset * 2), Math.max(1, depth - gasketInset * 2), gasketThickness).subtract(placeCutterAtPositions(coverHole, boltPositions, gasketThickness / 2)).translate(0, 0, parentThickness).color("#111827") : null;
|
|
56231
|
-
const hardware = fastenerSet(screwSize, screwLength, { washerUnderHead: false, washerUnderNut: false, fit: coverFit, segments });
|
|
56232
|
-
const screwOriginZ = parentThickness + gasketThickness + coverThickness;
|
|
56233
|
-
const screws = boltPositions.map(([x2, y2]) => hardware.bolt.translate(x2, y2, screwOriginZ).color("#94a3b8"));
|
|
56234
|
-
const parts = [
|
|
56235
|
-
{ name: "service cover parent ledge with threaded hole envelopes", shape: parent },
|
|
56236
|
-
...gasket ? [{ name: "service cover gasket seated on ledge", shape: gasket }] : [],
|
|
56237
|
-
{ name: "bolted service cover plate with fused pull tabs", shape: cover },
|
|
56238
|
-
...screws.map((shape, index2) => ({ name: `installed ${screwSize} cover screw ${index2 + 1}`, shape }))
|
|
56239
|
-
];
|
|
56240
|
-
return {
|
|
56241
|
-
parts,
|
|
56242
|
-
parent,
|
|
56243
|
-
cover,
|
|
56244
|
-
gasket,
|
|
56245
|
-
screws,
|
|
56246
|
-
boltPositions,
|
|
56247
|
-
cutters: {
|
|
56248
|
-
coverClearance: coverClearancePattern,
|
|
56249
|
-
parentTapped: parentTappedPattern,
|
|
56250
|
-
parentThreadEnvelope: parentThreadEnvelopePattern
|
|
56251
|
-
},
|
|
56252
|
-
dims: {
|
|
56253
|
-
width,
|
|
56254
|
-
depth,
|
|
56255
|
-
coverThickness,
|
|
56256
|
-
parentThickness,
|
|
56257
|
-
ledgeWidth,
|
|
56258
|
-
gasketThickness,
|
|
56259
|
-
screwSize,
|
|
56260
|
-
screwLength,
|
|
56261
|
-
clearanceDia: sizeData[coverFit],
|
|
56262
|
-
tapDia: sizeData.tap,
|
|
56263
|
-
threadEnvelopeDia: sizeData.close
|
|
56264
|
-
}
|
|
56265
|
-
};
|
|
56266
|
-
}
|
|
56267
|
-
function datumEnclosureAssembly(options) {
|
|
56268
|
-
const width = requirePositive$6(options.width, "width");
|
|
56269
|
-
const depth = requirePositive$6(options.depth, "depth");
|
|
56270
|
-
const height = requirePositive$6(options.height, "height");
|
|
56271
|
-
const wallThickness = requirePositive$6(options.wallThickness ?? 2.4, "wallThickness");
|
|
56272
|
-
const baseThickness = requirePositive$6(options.baseThickness ?? wallThickness, "baseThickness");
|
|
56273
|
-
const coverThickness = requirePositive$6(options.coverThickness ?? 2.4, "coverThickness");
|
|
56274
|
-
const ledgeWidth = requirePositive$6(options.ledgeWidth ?? Math.max(3.6, wallThickness * 1.35), "ledgeWidth");
|
|
56275
|
-
const gasketThickness = requireNonNegative(options.gasketThickness ?? 0.8, "gasketThickness");
|
|
56276
|
-
const faceClearance = requirePositive$6(options.faceClearance ?? 0.04, "faceClearance");
|
|
56277
|
-
const screwSize = options.screwSize ?? "M3";
|
|
56278
|
-
const coverFit = options.coverFit ?? "normal";
|
|
56279
|
-
const segments = options.segments ?? 32;
|
|
56280
|
-
const sizeData = METRIC_HOLE_TABLE[screwSize];
|
|
56281
|
-
if (!sizeData) throw new Error(`datumEnclosureAssembly: unsupported screwSize "${screwSize}"`);
|
|
56282
|
-
const innerWidth = width - wallThickness * 2;
|
|
56283
|
-
const innerDepth = depth - wallThickness * 2;
|
|
56284
|
-
if (innerWidth <= ledgeWidth * 2 + 8 || innerDepth <= ledgeWidth * 2 + 8) {
|
|
56285
|
-
throw new Error("datumEnclosureAssembly: wallThickness and ledgeWidth leave too little internal opening");
|
|
56286
|
-
}
|
|
56287
|
-
if (height <= baseThickness + coverThickness + 4) {
|
|
56288
|
-
throw new Error("datumEnclosureAssembly: height must leave room for internal ribs and standoffs");
|
|
56289
|
-
}
|
|
56290
|
-
const standoffDiameter = requirePositive$6(
|
|
56291
|
-
options.standoffDiameter ?? Math.max(sizeData.head * 1.65, sizeData.close * 2.2),
|
|
56292
|
-
"standoffDiameter"
|
|
56293
|
-
);
|
|
56294
|
-
const minInset = wallThickness + Math.max(ledgeWidth, standoffDiameter / 2 + 1.2);
|
|
56295
|
-
const [insetX, insetY] = resolveBoltInset(options.screwInset, minInset);
|
|
56296
|
-
if (insetX * 2 >= width || insetY * 2 >= depth) {
|
|
56297
|
-
throw new Error("datumEnclosureAssembly: screwInset leaves no room for the standoff datum");
|
|
56298
|
-
}
|
|
56299
|
-
const screwPositions = options.screwPositions ?? [
|
|
56300
|
-
[-width / 2 + insetX, -depth / 2 + insetY],
|
|
56301
|
-
[width / 2 - insetX, -depth / 2 + insetY],
|
|
56302
|
-
[-width / 2 + insetX, depth / 2 - insetY],
|
|
56303
|
-
[width / 2 - insetX, depth / 2 - insetY]
|
|
56304
|
-
];
|
|
56305
|
-
if (screwPositions.length === 0) throw new Error("datumEnclosureAssembly: screwPositions must contain at least one point");
|
|
56306
|
-
for (const [index2, [x2, y2]] of screwPositions.entries()) {
|
|
56307
|
-
if (!Number.isFinite(x2) || !Number.isFinite(y2)) {
|
|
56308
|
-
throw new Error(`datumEnclosureAssembly: screwPositions[${index2}] must contain finite numbers`);
|
|
56309
|
-
}
|
|
56310
|
-
if (Math.abs(x2) + standoffDiameter / 2 > innerWidth / 2 || Math.abs(y2) + standoffDiameter / 2 > innerDepth / 2) {
|
|
56311
|
-
throw new Error(`datumEnclosureAssembly: screwPositions[${index2}] does not fit inside the enclosure walls`);
|
|
56312
|
-
}
|
|
56313
|
-
}
|
|
56314
|
-
const ribHeight = requirePositive$6(options.ribHeight ?? Math.min(height * 0.24, Math.max(2.4, baseThickness * 1.4)), "ribHeight");
|
|
56315
|
-
const ribThickness = requirePositive$6(options.ribThickness ?? Math.max(1.2, wallThickness * 0.75), "ribThickness");
|
|
56316
|
-
const portWidth = requirePositive$6(options.portWidth ?? Math.min(innerWidth * 0.28, Math.max(12, width * 0.16)), "portWidth");
|
|
56317
|
-
const portHeight = requirePositive$6(options.portHeight ?? Math.min(height * 0.42, Math.max(5, height * 0.28)), "portHeight");
|
|
56318
|
-
if (portWidth >= innerWidth - ledgeWidth * 2) {
|
|
56319
|
-
throw new Error("datumEnclosureAssembly: portWidth must fit between internal ledges and standoffs");
|
|
56320
|
-
}
|
|
56321
|
-
if (portHeight >= height - baseThickness - 1) {
|
|
56322
|
-
throw new Error("datumEnclosureAssembly: portHeight must leave material above and below the service port");
|
|
56323
|
-
}
|
|
56324
|
-
const screwLength = requirePositive$6(options.screwLength ?? coverThickness + gasketThickness + Math.max(6, height * 0.45), "screwLength");
|
|
56325
|
-
const coverHole = fastenerHole({
|
|
56326
|
-
size: screwSize,
|
|
56327
|
-
fit: coverFit,
|
|
56328
|
-
depth: coverThickness + 0.6,
|
|
56329
|
-
center: true,
|
|
56330
|
-
segments,
|
|
56331
|
-
counterbore: { depth: Math.min(coverThickness * 0.6, Math.max(0.6, coverThickness - 0.35)) }
|
|
56332
|
-
});
|
|
56333
|
-
const standoffTap = fastenerHole({ size: screwSize, fit: "tap", depth: height + 0.8, center: true, segments });
|
|
56334
|
-
const standoffThreadEnvelope = fastenerHole({ size: screwSize, fit: "close", depth: height + 0.8, center: true, segments });
|
|
56335
|
-
const coverClearance = placeCutterAtPositions(coverHole, screwPositions, coverThickness / 2);
|
|
56336
|
-
const standoffTappedPattern = placeCutterAtPositions(standoffTap, screwPositions, height / 2);
|
|
56337
|
-
const standoffThreadEnvelopePattern = placeCutterAtPositions(standoffThreadEnvelope, screwPositions, height / 2);
|
|
56338
|
-
const fuseOverlap = Math.min(0.06, Math.max(0.02, wallThickness * 0.02));
|
|
56339
|
-
const ledgeThickness = Math.min(Math.max(1.1, coverThickness * 0.45), height * 0.2);
|
|
56340
|
-
const sideX = width / 2 - wallThickness / 2;
|
|
56341
|
-
const sideY = depth / 2 - wallThickness / 2;
|
|
56342
|
-
const ledgeZ = height - ledgeThickness;
|
|
56343
|
-
const baseSolids = [
|
|
56344
|
-
box(width, depth, baseThickness),
|
|
56345
|
-
box(wallThickness, depth, height).translate(sideX, 0, 0),
|
|
56346
|
-
box(wallThickness, depth, height).translate(-sideX, 0, 0),
|
|
56347
|
-
box(width, wallThickness, height).translate(0, sideY, 0),
|
|
56348
|
-
box(width, wallThickness, height).translate(0, -sideY, 0),
|
|
56349
|
-
box(ledgeWidth, innerDepth, ledgeThickness).translate(-width / 2 + wallThickness + ledgeWidth / 2, 0, ledgeZ),
|
|
56350
|
-
box(ledgeWidth, innerDepth, ledgeThickness).translate(width / 2 - wallThickness - ledgeWidth / 2, 0, ledgeZ),
|
|
56351
|
-
box(innerWidth, ledgeWidth, ledgeThickness).translate(0, -depth / 2 + wallThickness + ledgeWidth / 2, ledgeZ),
|
|
56352
|
-
box(innerWidth, ledgeWidth, ledgeThickness).translate(0, depth / 2 - wallThickness - ledgeWidth / 2, ledgeZ),
|
|
56353
|
-
box(Math.max(1, innerWidth - standoffDiameter * 1.8), ribThickness, ribHeight + fuseOverlap).translate(
|
|
56354
|
-
0,
|
|
56355
|
-
0,
|
|
56356
|
-
baseThickness - fuseOverlap
|
|
56357
|
-
),
|
|
56358
|
-
box(ribThickness, Math.max(1, innerDepth - standoffDiameter * 1.8), ribHeight + fuseOverlap).translate(
|
|
56359
|
-
0,
|
|
56360
|
-
0,
|
|
56361
|
-
baseThickness - fuseOverlap
|
|
56362
|
-
),
|
|
56363
|
-
...screwPositions.map(
|
|
56364
|
-
([x2, y2]) => cylinder(height - baseThickness + fuseOverlap, standoffDiameter / 2, void 0, segments).translate(
|
|
56365
|
-
x2,
|
|
56366
|
-
y2,
|
|
56367
|
-
baseThickness - fuseOverlap
|
|
56368
|
-
)
|
|
56369
|
-
)
|
|
56370
|
-
];
|
|
56371
|
-
const servicePort = box(portWidth, wallThickness + 1, portHeight).translate(
|
|
56372
|
-
0,
|
|
56373
|
-
-depth / 2 + wallThickness / 2,
|
|
56374
|
-
baseThickness + Math.max(0.8, (height - baseThickness - portHeight) * 0.35)
|
|
56375
|
-
);
|
|
56376
|
-
const base = union(...baseSolids).subtract(standoffThreadEnvelopePattern).subtract(servicePort).color("#475569");
|
|
56377
|
-
const gasketFrameCutter = box(Math.max(1, width - ledgeWidth * 2), Math.max(1, depth - ledgeWidth * 2), gasketThickness + 0.6).translate(
|
|
56378
|
-
0,
|
|
56379
|
-
0,
|
|
56380
|
-
-0.3
|
|
56381
|
-
);
|
|
56382
|
-
const gasket = gasketThickness > 0 ? box(width, depth, gasketThickness).subtract(gasketFrameCutter).subtract(placeCutterAtPositions(coverHole, screwPositions, gasketThickness / 2)).translate(0, 0, height + faceClearance).color("#111827") : null;
|
|
56383
|
-
const coverZ = height + faceClearance + (gasket ? gasketThickness + faceClearance : 0);
|
|
56384
|
-
const cover = box(width, depth, coverThickness).subtract(coverClearance).translate(0, 0, coverZ).color("#334155");
|
|
56385
|
-
const hardware = fastenerSet(screwSize, screwLength, { washerUnderHead: false, washerUnderNut: false, fit: coverFit, segments });
|
|
56386
|
-
const screwOriginZ = coverZ + coverThickness;
|
|
56387
|
-
const screws = screwPositions.map(([x2, y2]) => hardware.bolt.translate(x2, y2, screwOriginZ).color("#94a3b8"));
|
|
56388
|
-
const parts = [
|
|
56389
|
-
{ name: "datum enclosure base tray with walls ribs standoffs and service port", shape: base },
|
|
56390
|
-
...gasket ? [{ name: "datum enclosure gasket seated on continuous ledge", shape: gasket }] : [],
|
|
56391
|
-
{ name: "datum enclosure cover plate with matched screw pattern", shape: cover },
|
|
56392
|
-
...screws.map((shape, index2) => ({ name: `installed ${screwSize} enclosure screw ${index2 + 1}`, shape }))
|
|
56393
|
-
];
|
|
56394
|
-
return {
|
|
56395
|
-
parts,
|
|
56396
|
-
base,
|
|
56397
|
-
cover,
|
|
56398
|
-
gasket,
|
|
56399
|
-
screws,
|
|
56400
|
-
screwPositions,
|
|
56401
|
-
cutters: {
|
|
56402
|
-
coverClearance,
|
|
56403
|
-
standoffTapped: standoffTappedPattern,
|
|
56404
|
-
standoffThreadEnvelope: standoffThreadEnvelopePattern,
|
|
56405
|
-
servicePort
|
|
56406
|
-
},
|
|
56407
|
-
dims: {
|
|
56408
|
-
width,
|
|
56409
|
-
depth,
|
|
56410
|
-
height,
|
|
56411
|
-
innerWidth,
|
|
56412
|
-
innerDepth,
|
|
56413
|
-
wallThickness,
|
|
56414
|
-
baseThickness,
|
|
56415
|
-
coverThickness,
|
|
56416
|
-
ledgeWidth,
|
|
56417
|
-
gasketThickness,
|
|
56418
|
-
faceClearance,
|
|
56419
|
-
screwSize,
|
|
56420
|
-
screwLength,
|
|
56421
|
-
standoffDiameter,
|
|
56422
|
-
ribHeight,
|
|
56423
|
-
ribThickness,
|
|
56424
|
-
portWidth,
|
|
56425
|
-
portHeight,
|
|
56426
|
-
clearanceDia: sizeData[coverFit],
|
|
56427
|
-
tapDia: sizeData.tap,
|
|
56428
|
-
threadEnvelopeDia: sizeData.close
|
|
56429
|
-
}
|
|
56430
|
-
};
|
|
56431
|
-
}
|
|
56432
|
-
function snapLatchCoverAssembly(options) {
|
|
56433
|
-
const width = requirePositive$6(options.width, "width");
|
|
56434
|
-
const depth = requirePositive$6(options.depth, "depth");
|
|
56435
|
-
const coverThickness = requirePositive$6(options.coverThickness ?? 2.4, "coverThickness");
|
|
56436
|
-
const parentThickness = requirePositive$6(options.parentThickness ?? 6, "parentThickness");
|
|
56437
|
-
const ledgeWidth = requirePositive$6(options.ledgeWidth ?? 8, "ledgeWidth");
|
|
56438
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.25, "runningClearance");
|
|
56439
|
-
const faceClearance = requirePositive$6(options.faceClearance ?? 0.04, "faceClearance");
|
|
56440
|
-
const latchWidth = requirePositive$6(options.latchWidth ?? Math.min(width * 0.22, Math.max(12, width * 0.16)), "latchWidth");
|
|
56441
|
-
const latchThickness = requirePositive$6(options.latchThickness ?? 1.6, "latchThickness");
|
|
56442
|
-
const hookThrow = requirePositive$6(options.hookThrow ?? 3.2, "hookThrow");
|
|
56443
|
-
const hookThickness = requirePositive$6(options.hookThickness ?? 1.6, "hookThickness");
|
|
56444
|
-
const openingWidth = width - ledgeWidth * 2;
|
|
56445
|
-
const openingDepth = depth - ledgeWidth * 2;
|
|
56446
|
-
if (openingWidth <= Math.max(8, latchWidth * 0.8) || openingDepth <= 8) {
|
|
56447
|
-
throw new Error("snapLatchCoverAssembly: ledgeWidth leaves too little service opening under the cover");
|
|
56448
|
-
}
|
|
56449
|
-
if (latchWidth >= openingWidth) {
|
|
56450
|
-
throw new Error("snapLatchCoverAssembly: latchWidth must fit along the receiver opening");
|
|
56451
|
-
}
|
|
56452
|
-
if (latchThickness + runningClearance * 2 >= ledgeWidth) {
|
|
56453
|
-
throw new Error("snapLatchCoverAssembly: latchThickness and clearance must fit inside the receiver ledge");
|
|
56454
|
-
}
|
|
56455
|
-
if (hookThrow + latchThickness / 2 + runningClearance >= ledgeWidth * 1.5) {
|
|
56456
|
-
throw new Error("snapLatchCoverAssembly: hookThrow is too large for the available underside catch land");
|
|
56457
|
-
}
|
|
56458
|
-
const parentWidth = width + ledgeWidth * 2;
|
|
56459
|
-
const parentDepth = depth + ledgeWidth * 2;
|
|
56460
|
-
const fuseOverlap = Math.min(0.04, faceClearance * 0.7);
|
|
56461
|
-
const hookClearance = Math.min(0.08, runningClearance * 0.32);
|
|
56462
|
-
const coverMinZ = parentThickness + faceClearance;
|
|
56463
|
-
const stemMinZ = -hookClearance - hookThickness;
|
|
56464
|
-
const stemHeight = coverMinZ + fuseOverlap - stemMinZ;
|
|
56465
|
-
const slotY = openingDepth / 2 + ledgeWidth / 2;
|
|
56466
|
-
const latchWindow = (sign2) => box(latchWidth + runningClearance * 2, latchThickness + runningClearance * 2, parentThickness + 0.8).translate(0, sign2 * slotY, -0.4);
|
|
56467
|
-
const latchWindows = union(latchWindow(1), latchWindow(-1));
|
|
56468
|
-
const serviceOpening = box(openingWidth, openingDepth, parentThickness + 1).translate(0, 0, -0.5);
|
|
56469
|
-
const parent = box(parentWidth, parentDepth, parentThickness).subtract(serviceOpening).subtract(latchWindows).color("#475569");
|
|
56470
|
-
const coverPlate = box(width, depth, coverThickness).translate(0, 0, coverMinZ);
|
|
56471
|
-
const snapHook = (sign2) => {
|
|
56472
|
-
const y2 = sign2 * slotY;
|
|
56473
|
-
const stem = box(latchWidth, latchThickness, stemHeight).translate(0, y2, stemMinZ);
|
|
56474
|
-
const barb = box(latchWidth, latchThickness + hookThrow, hookThickness).translate(0, y2 + sign2 * (hookThrow / 2), stemMinZ);
|
|
56475
|
-
const rootRib = box(latchWidth, Math.max(latchThickness, hookThrow * 0.55), coverThickness * 0.65).translate(
|
|
56476
|
-
0,
|
|
56477
|
-
y2 - sign2 * (ledgeWidth * 0.18),
|
|
56478
|
-
coverMinZ
|
|
56479
|
-
);
|
|
56480
|
-
return union(stem, barb, rootRib);
|
|
56481
|
-
};
|
|
56482
|
-
const cover = union(coverPlate, snapHook(1), snapHook(-1)).color("#111827");
|
|
56483
|
-
const parts = [
|
|
56484
|
-
{ name: "snap cover receiver frame with latch windows and catch lands", shape: parent },
|
|
56485
|
-
{ name: "one-piece snap cover with fused hooks and underside barbs", shape: cover }
|
|
56486
|
-
];
|
|
56487
|
-
return {
|
|
56488
|
-
parts,
|
|
56489
|
-
parent,
|
|
56490
|
-
cover,
|
|
56491
|
-
cutters: {
|
|
56492
|
-
serviceOpening,
|
|
56493
|
-
latchWindows
|
|
56494
|
-
},
|
|
56495
|
-
dims: {
|
|
56496
|
-
width,
|
|
56497
|
-
depth,
|
|
56498
|
-
parentWidth,
|
|
56499
|
-
parentDepth,
|
|
56500
|
-
openingWidth,
|
|
56501
|
-
openingDepth,
|
|
56502
|
-
coverThickness,
|
|
56503
|
-
parentThickness,
|
|
56504
|
-
ledgeWidth,
|
|
56505
|
-
latchWidth,
|
|
56506
|
-
latchThickness,
|
|
56507
|
-
hookThrow,
|
|
56508
|
-
hookThickness,
|
|
56509
|
-
runningClearance,
|
|
56510
|
-
faceClearance
|
|
56511
|
-
}
|
|
56512
|
-
};
|
|
56513
|
-
}
|
|
56514
|
-
function pinnedLeverAssembly(options) {
|
|
56515
|
-
const armLength = requirePositive$6(options.armLength, "armLength");
|
|
56516
|
-
const armWidth = requirePositive$6(options.armWidth ?? 10, "armWidth");
|
|
56517
|
-
const leverThickness = requirePositive$6(options.leverThickness ?? 5, "leverThickness");
|
|
56518
|
-
const pinDiameter = requirePositive$6(options.pinDiameter ?? 5, "pinDiameter");
|
|
56519
|
-
const pinClearance = requireNonNegative(options.pinClearance ?? 0.25, "pinClearance");
|
|
56520
|
-
const boreDiameter = pinDiameter + pinClearance;
|
|
56521
|
-
const hubRadius = requirePositive$6(options.hubRadius ?? Math.max(armWidth * 0.85, pinDiameter * 1.8), "hubRadius");
|
|
56522
|
-
const supportThickness = requirePositive$6(options.supportThickness ?? Math.max(6, pinDiameter * 1.4), "supportThickness");
|
|
56523
|
-
const supportWidth = requirePositive$6(options.supportWidth ?? hubRadius * 2 + 18, "supportWidth");
|
|
56524
|
-
const supportDepth = requirePositive$6(options.supportDepth ?? Math.max(armWidth + 18, hubRadius * 2 + 10), "supportDepth");
|
|
56525
|
-
const washerSize = options.washerSize ?? metricWasherSizeForPin(pinDiameter);
|
|
56526
|
-
const washerDims = WASHER_TABLE[washerSize];
|
|
56527
|
-
if (!washerDims) throw new Error(`pinnedLeverAssembly: unsupported washerSize "${washerSize}"`);
|
|
56528
|
-
if (washerDims.id <= pinDiameter) {
|
|
56529
|
-
throw new Error(`pinnedLeverAssembly: ${washerSize} washer inner diameter is too small for a ${pinDiameter} mm pin`);
|
|
56530
|
-
}
|
|
56531
|
-
if (hubRadius <= boreDiameter / 2 + Math.max(1, pinDiameter * 0.25)) {
|
|
56532
|
-
throw new Error("pinnedLeverAssembly: hubRadius leaves too little material around the pivot bore");
|
|
56533
|
-
}
|
|
56534
|
-
if (supportWidth <= boreDiameter + 4 || supportDepth <= boreDiameter + 4) {
|
|
56535
|
-
throw new Error("pinnedLeverAssembly: support dimensions leave too little material around the pivot bore");
|
|
56536
|
-
}
|
|
56537
|
-
const segments = options.segments ?? 40;
|
|
56538
|
-
const gripLength = requirePositive$6(options.gripLength ?? Math.min(armLength * 0.32, Math.max(16, armWidth * 2.4)), "gripLength");
|
|
56539
|
-
const gripWidth = requirePositive$6(options.gripWidth ?? armWidth * 1.55, "gripWidth");
|
|
56540
|
-
if (gripLength >= armLength) throw new Error("pinnedLeverAssembly: gripLength must be shorter than armLength");
|
|
56541
|
-
const armOverlap = Math.min(hubRadius * 0.65, armLength * 0.25);
|
|
56542
|
-
const armStartX = hubRadius - armOverlap;
|
|
56543
|
-
const armCenterX = armStartX + armLength / 2;
|
|
56544
|
-
const gripCenterX = armStartX + armLength - gripLength / 2;
|
|
56545
|
-
const runningClearance = 0.03;
|
|
56546
|
-
const lowerWasherZ = supportThickness + runningClearance;
|
|
56547
|
-
const leverZ = lowerWasherZ + washerDims.t + runningClearance;
|
|
56548
|
-
const upperWasherZ = leverZ + leverThickness + runningClearance;
|
|
56549
|
-
const stackHeight = upperWasherZ + washerDims.t;
|
|
56550
|
-
const pinHeadThickness = Math.max(washerDims.t, pinDiameter * 0.35);
|
|
56551
|
-
const pinHeadRadius = Math.max(washerDims.od * 0.42, pinDiameter * 0.8);
|
|
56552
|
-
const supportBore = cylinder(supportThickness + 1, boreDiameter / 2, void 0, segments).translate(0, 0, -0.5);
|
|
56553
|
-
let supportBlank = box(supportWidth, supportDepth, supportThickness);
|
|
56554
|
-
if (options.stopBlock ?? true) {
|
|
56555
|
-
const stopLength = Math.min(armLength * 0.22, Math.max(10, armWidth * 1.4));
|
|
56556
|
-
const stopWidth = Math.max(4, pinDiameter * 0.7);
|
|
56557
|
-
const stopHeight = supportThickness;
|
|
56558
|
-
const stopX = hubRadius + stopLength / 2;
|
|
56559
|
-
const stopY = armWidth / 2 + stopWidth / 2 + runningClearance;
|
|
56560
|
-
supportBlank = union(supportBlank, box(stopLength, stopWidth, stopHeight).translate(stopX, stopY, 0));
|
|
56561
|
-
}
|
|
56562
|
-
const support = supportBlank.subtract(supportBore).color("#475569");
|
|
56563
|
-
const hub = cylinder(leverThickness, hubRadius, void 0, segments);
|
|
56564
|
-
const arm = box(armLength, armWidth, leverThickness).translate(armCenterX, 0, 0);
|
|
56565
|
-
const grip = box(gripLength, gripWidth, leverThickness).translate(gripCenterX, 0, 0);
|
|
56566
|
-
const leverSolids = [hub, arm, grip];
|
|
56567
|
-
if (options.detentBoss ?? true) {
|
|
56568
|
-
const bossRadius = Math.min(armWidth * 0.42, hubRadius * 0.42);
|
|
56569
|
-
const bossX = hubRadius + Math.min(armLength * 0.22, armWidth * 2);
|
|
56570
|
-
const bossY = -armWidth / 2 - bossRadius * 0.45;
|
|
56571
|
-
leverSolids.push(cylinder(leverThickness, bossRadius, void 0, segments).translate(bossX, bossY, 0));
|
|
56572
|
-
}
|
|
56573
|
-
const leverBore = cylinder(leverThickness + 1, boreDiameter / 2, void 0, segments).translate(0, 0, -0.5);
|
|
56574
|
-
const lever = union(...leverSolids).subtract(leverBore).translate(0, 0, leverZ).color("#7f1d1d");
|
|
56575
|
-
const lowerWasher = washer(washerSize, { segments }).translate(0, 0, lowerWasherZ).color("#94a3b8");
|
|
56576
|
-
const upperWasher = washer(washerSize, { segments }).translate(0, 0, upperWasherZ).color("#94a3b8");
|
|
56577
|
-
const shaft = cylinder(stackHeight, pinDiameter / 2, void 0, segments);
|
|
56578
|
-
const lowerRetainer = cylinder(pinHeadThickness, pinHeadRadius, void 0, segments).translate(
|
|
56579
|
-
0,
|
|
56580
|
-
0,
|
|
56581
|
-
-pinHeadThickness - runningClearance
|
|
56582
|
-
);
|
|
56583
|
-
const upperHead = cylinder(pinHeadThickness, pinHeadRadius, void 0, segments).translate(0, 0, stackHeight + runningClearance);
|
|
56584
|
-
const pin = union(shaft, lowerRetainer, upperHead).color("#cbd5e1");
|
|
56585
|
-
const pivotBore = cylinder(stackHeight + 1, boreDiameter / 2, void 0, segments).translate(0, 0, -0.5);
|
|
56586
|
-
const parts = [
|
|
56587
|
-
{ name: "pivot support block with bearing bore and low stop land", shape: support },
|
|
56588
|
-
{ name: "lower thrust washer under pinned lever", shape: lowerWasher },
|
|
56589
|
-
{ name: "fused pinned lever with hub arm grip and detent boss", shape: lever },
|
|
56590
|
-
{ name: "upper thrust washer over pinned lever", shape: upperWasher },
|
|
56591
|
-
{ name: "retained pivot pin through lever stack", shape: pin }
|
|
56592
|
-
];
|
|
56593
|
-
return {
|
|
56594
|
-
parts,
|
|
56595
|
-
support,
|
|
56596
|
-
lever,
|
|
56597
|
-
pin,
|
|
56598
|
-
washers: {
|
|
56599
|
-
lower: lowerWasher,
|
|
56600
|
-
upper: upperWasher
|
|
56601
|
-
},
|
|
56602
|
-
cutters: {
|
|
56603
|
-
pivotBore
|
|
56604
|
-
},
|
|
56605
|
-
dims: {
|
|
56606
|
-
armLength,
|
|
56607
|
-
armWidth,
|
|
56608
|
-
leverThickness,
|
|
56609
|
-
hubRadius,
|
|
56610
|
-
pinDiameter,
|
|
56611
|
-
boreDiameter,
|
|
56612
|
-
supportWidth,
|
|
56613
|
-
supportDepth,
|
|
56614
|
-
supportThickness,
|
|
56615
|
-
washerSize,
|
|
56616
|
-
washerThickness: washerDims.t,
|
|
56617
|
-
stackHeight
|
|
56618
|
-
}
|
|
56619
|
-
};
|
|
56620
|
-
}
|
|
56621
|
-
function retainedShaftAssembly(options) {
|
|
56622
|
-
const supportSpacing = requirePositive$6(options.supportSpacing, "supportSpacing");
|
|
56623
|
-
const shaftDiameter = requirePositive$6(options.shaftDiameter ?? 8, "shaftDiameter");
|
|
56624
|
-
const boreClearance = requireNonNegative(options.boreClearance ?? 0.35, "boreClearance");
|
|
56625
|
-
const boreDiameter = shaftDiameter + boreClearance;
|
|
56626
|
-
const supportThickness = requirePositive$6(options.supportThickness ?? Math.max(5, shaftDiameter * 0.75), "supportThickness");
|
|
56627
|
-
const washerSize = options.washerSize ?? metricWasherSizeForPin(shaftDiameter);
|
|
56628
|
-
const washerDims = WASHER_TABLE[washerSize];
|
|
56629
|
-
if (!washerDims) throw new Error(`retainedShaftAssembly: unsupported washerSize "${washerSize}"`);
|
|
56630
|
-
if (washerDims.id <= shaftDiameter) {
|
|
56631
|
-
throw new Error(`retainedShaftAssembly: ${washerSize} washer inner diameter is too small for a ${shaftDiameter} mm shaft`);
|
|
56632
|
-
}
|
|
56633
|
-
const knobDiameter = requirePositive$6(options.knobDiameter ?? shaftDiameter * 3, "knobDiameter");
|
|
56634
|
-
const knobThickness = requirePositive$6(options.knobThickness ?? Math.max(8, shaftDiameter), "knobThickness");
|
|
56635
|
-
const retainerThickness = requirePositive$6(options.retainerThickness ?? Math.max(washerDims.t, shaftDiameter * 0.35), "retainerThickness");
|
|
56636
|
-
const runningClearance = requireNonNegative(options.runningClearance ?? 0.05, "runningClearance");
|
|
56637
|
-
const supportWidth = requirePositive$6(options.supportWidth ?? Math.max(28, knobDiameter * 1.25), "supportWidth");
|
|
56638
|
-
const supportHeight = requirePositive$6(options.supportHeight ?? Math.max(34, knobDiameter * 1.45), "supportHeight");
|
|
56639
|
-
const segments = options.segments ?? 40;
|
|
56640
|
-
if (supportSpacing <= supportThickness) {
|
|
56641
|
-
throw new Error("retainedShaftAssembly: supportSpacing must leave a gap between support cheeks");
|
|
56642
|
-
}
|
|
56643
|
-
if (supportWidth <= boreDiameter + 4 || supportHeight <= boreDiameter + 4) {
|
|
56644
|
-
throw new Error("retainedShaftAssembly: support dimensions leave too little material around the shaft bore");
|
|
56645
|
-
}
|
|
56646
|
-
const leftSupportX = -supportSpacing / 2;
|
|
56647
|
-
const rightSupportX = supportSpacing / 2;
|
|
56648
|
-
const leftOuterFaceX = leftSupportX - supportThickness / 2;
|
|
56649
|
-
const rightOuterFaceX = rightSupportX + supportThickness / 2;
|
|
56650
|
-
const leftWasherX = leftOuterFaceX - runningClearance - washerDims.t / 2;
|
|
56651
|
-
const rightWasherX = rightOuterFaceX + runningClearance + washerDims.t / 2;
|
|
56652
|
-
const leftKnobX = leftOuterFaceX - runningClearance * 2 - washerDims.t - knobThickness / 2;
|
|
56653
|
-
const rightKnobX = rightOuterFaceX + runningClearance * 2 + washerDims.t + knobThickness / 2;
|
|
56654
|
-
const leftStackOuterX = leftKnobX - knobThickness / 2;
|
|
56655
|
-
const rightStackOuterX = rightKnobX + knobThickness / 2;
|
|
56656
|
-
const minimumShaftLength = rightStackOuterX - leftStackOuterX + retainerThickness * 2 + runningClearance * 2;
|
|
56657
|
-
const shaftLength = requirePositive$6(options.shaftLength ?? minimumShaftLength, "shaftLength");
|
|
56658
|
-
if (shaftLength < minimumShaftLength) {
|
|
56659
|
-
throw new Error("retainedShaftAssembly: shaftLength is too short to retain both supports, washers, and knobs");
|
|
56660
|
-
}
|
|
56661
|
-
const supportBore = cylinderAlongX(supportThickness + 1, boreDiameter / 2, 0, segments);
|
|
56662
|
-
const makeSupport = (x2) => box(supportThickness, supportWidth, supportHeight).translate(x2, 0, -supportHeight / 2).subtract(supportBore.translate(x2, 0, 0)).color("#334155");
|
|
56663
|
-
const knobBore = cylinder(knobThickness + 1, boreDiameter / 2, void 0, segments).translate(0, 0, -0.5);
|
|
56664
|
-
const makeKnob = (x2) => cylinder(knobThickness, knobDiameter / 2, void 0, 18).subtract(knobBore).pointAlong([1, 0, 0]).translate(x2 - knobThickness / 2, 0, 0).color("#111827");
|
|
56665
|
-
const retainerRadius = Math.max(shaftDiameter * 0.85, knobDiameter * 0.36);
|
|
56666
|
-
const shaftCore = cylinderAlongX(shaftLength, shaftDiameter / 2, 0, segments);
|
|
56667
|
-
const leftRetainer = cylinderAlongX(retainerThickness, retainerRadius, -shaftLength / 2 + retainerThickness / 2, segments);
|
|
56668
|
-
const rightRetainer = cylinderAlongX(retainerThickness, retainerRadius, shaftLength / 2 - retainerThickness / 2, segments);
|
|
56669
|
-
const shaft = union(shaftCore, leftRetainer, rightRetainer).color("#cbd5e1");
|
|
56670
|
-
const leftSupport = makeSupport(leftSupportX);
|
|
56671
|
-
const rightSupport = makeSupport(rightSupportX);
|
|
56672
|
-
const leftWasher = washerAlongX(washerSize, leftWasherX, segments).color("#94a3b8");
|
|
56673
|
-
const rightWasher = washerAlongX(washerSize, rightWasherX, segments).color("#94a3b8");
|
|
56674
|
-
const leftKnob = makeKnob(leftKnobX);
|
|
56675
|
-
const rightKnob = makeKnob(rightKnobX);
|
|
56676
|
-
const shaftBore = cylinderAlongX(supportThickness + knobThickness + 2, boreDiameter / 2, 0, segments);
|
|
56677
|
-
const parts = [
|
|
56678
|
-
{ name: "left bored support cheek for retained shaft", shape: leftSupport },
|
|
56679
|
-
{ name: "right bored support cheek for retained shaft", shape: rightSupport },
|
|
56680
|
-
{ name: "retained through shaft with end heads", shape: shaft },
|
|
56681
|
-
{ name: `left ${washerSize} thrust washer on shaft`, shape: leftWasher },
|
|
56682
|
-
{ name: `right ${washerSize} thrust washer on shaft`, shape: rightWasher },
|
|
56683
|
-
{ name: "left retained hand knob with shaft bore", shape: leftKnob },
|
|
56684
|
-
{ name: "right retained hand knob with shaft bore", shape: rightKnob }
|
|
56685
|
-
];
|
|
56686
|
-
return {
|
|
56687
|
-
parts,
|
|
56688
|
-
supports: {
|
|
56689
|
-
left: leftSupport,
|
|
56690
|
-
right: rightSupport
|
|
56691
|
-
},
|
|
56692
|
-
shaft,
|
|
56693
|
-
washers: {
|
|
56694
|
-
left: leftWasher,
|
|
56695
|
-
right: rightWasher
|
|
56696
|
-
},
|
|
56697
|
-
knobs: {
|
|
56698
|
-
left: leftKnob,
|
|
56699
|
-
right: rightKnob
|
|
56700
|
-
},
|
|
56701
|
-
cutters: {
|
|
56702
|
-
shaftBore
|
|
56703
|
-
},
|
|
56704
|
-
dims: {
|
|
56705
|
-
supportSpacing,
|
|
56706
|
-
supportThickness,
|
|
56707
|
-
supportWidth,
|
|
56708
|
-
supportHeight,
|
|
56709
|
-
shaftDiameter,
|
|
56710
|
-
shaftLength,
|
|
56711
|
-
boreDiameter,
|
|
56712
|
-
washerSize,
|
|
56713
|
-
washerThickness: washerDims.t,
|
|
56714
|
-
knobDiameter,
|
|
56715
|
-
knobThickness,
|
|
56716
|
-
retainerThickness,
|
|
56717
|
-
runningClearance
|
|
56718
|
-
}
|
|
56719
|
-
};
|
|
56720
|
-
}
|
|
56721
|
-
function capturedLinearSlide(options) {
|
|
56722
|
-
const length4 = requirePositive$6(options.length, "length");
|
|
56723
|
-
const railWidth = requirePositive$6(options.railWidth ?? 38, "railWidth");
|
|
56724
|
-
const baseThickness = requirePositive$6(options.baseThickness ?? 2.4, "baseThickness");
|
|
56725
|
-
const wallThickness = requirePositive$6(options.wallThickness ?? 2, "wallThickness");
|
|
56726
|
-
const wallHeight = requirePositive$6(options.wallHeight ?? 9, "wallHeight");
|
|
56727
|
-
const lipWidth = requirePositive$6(options.lipWidth ?? 4, "lipWidth");
|
|
56728
|
-
const lipThickness = requirePositive$6(options.lipThickness ?? 1.8, "lipThickness");
|
|
56729
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.35, "runningClearance");
|
|
56730
|
-
const endStopLength = requirePositive$6(options.endStopLength ?? 6, "endStopLength");
|
|
56731
|
-
const carriageLength = requirePositive$6(options.carriageLength ?? length4 * 0.32, "carriageLength");
|
|
56732
|
-
const innerWidth = railWidth - wallThickness * 2;
|
|
56733
|
-
const throatWidth = innerWidth - lipWidth * 2;
|
|
56734
|
-
if (innerWidth <= 0) throw new Error("capturedLinearSlide: wallThickness leaves no inner rail width");
|
|
56735
|
-
if (throatWidth <= 0) throw new Error("capturedLinearSlide: lipWidth closes the rail throat");
|
|
56736
|
-
const carriageWidth = requirePositive$6(options.carriageWidth ?? innerWidth - runningClearance * 2, "carriageWidth");
|
|
56737
|
-
const carriageThickness = requirePositive$6(options.carriageThickness ?? 4, "carriageThickness");
|
|
56738
|
-
if (carriageWidth >= innerWidth - runningClearance) {
|
|
56739
|
-
throw new Error("capturedLinearSlide: carriageWidth leaves too little side clearance inside the rail");
|
|
56740
|
-
}
|
|
56741
|
-
if (carriageWidth <= throatWidth + runningClearance) {
|
|
56742
|
-
throw new Error("capturedLinearSlide: carriageWidth must be wider than the lip throat so the rail actually captures it");
|
|
56743
|
-
}
|
|
56744
|
-
if (carriageThickness + runningClearance * 2 >= wallHeight) {
|
|
56745
|
-
throw new Error("capturedLinearSlide: carriage is too tall to clear the return lips");
|
|
56746
|
-
}
|
|
56747
|
-
const maxTravel = length4 - endStopLength * 2 - carriageLength;
|
|
56748
|
-
if (maxTravel <= 0) {
|
|
56749
|
-
throw new Error("capturedLinearSlide: rail length, end stops, and carriage length leave no travel");
|
|
56750
|
-
}
|
|
56751
|
-
const travel = options.travel ?? maxTravel / 2;
|
|
56752
|
-
if (!Number.isFinite(travel) || travel < 0 || travel > maxTravel) {
|
|
56753
|
-
throw new Error(`capturedLinearSlide: travel must be between 0 and ${maxTravel}`);
|
|
56754
|
-
}
|
|
56755
|
-
const carriageCenterX = -maxTravel / 2 + travel;
|
|
56756
|
-
const fuseOverlap = Math.min(0.04, runningClearance * 0.1);
|
|
56757
|
-
const sideY = railWidth / 2 - wallThickness / 2;
|
|
56758
|
-
const lipY = railWidth / 2 - wallThickness - lipWidth / 2 + fuseOverlap / 2;
|
|
56759
|
-
const stopZ = baseThickness - fuseOverlap;
|
|
56760
|
-
const rail2 = union(
|
|
56761
|
-
box(length4, railWidth, baseThickness),
|
|
56762
|
-
box(length4, wallThickness, wallHeight + fuseOverlap).translate(0, sideY, baseThickness - fuseOverlap),
|
|
56763
|
-
box(length4, wallThickness, wallHeight + fuseOverlap).translate(0, -sideY, baseThickness - fuseOverlap),
|
|
56764
|
-
box(length4, lipWidth, lipThickness + fuseOverlap).translate(0, lipY, baseThickness + wallHeight - fuseOverlap),
|
|
56765
|
-
box(length4, lipWidth, lipThickness + fuseOverlap).translate(0, -lipY, baseThickness + wallHeight - fuseOverlap),
|
|
56766
|
-
box(endStopLength, throatWidth, carriageThickness + fuseOverlap).translate(-length4 / 2 + endStopLength / 2, 0, stopZ),
|
|
56767
|
-
box(endStopLength, throatWidth, carriageThickness + fuseOverlap).translate(length4 / 2 - endStopLength / 2, 0, stopZ)
|
|
56768
|
-
).color("#475569");
|
|
56769
|
-
const carriage = union(
|
|
56770
|
-
box(carriageLength, carriageWidth, carriageThickness),
|
|
56771
|
-
box(carriageLength * 0.78, throatWidth - runningClearance * 2, Math.max(1, carriageThickness * 0.38)).translate(
|
|
56772
|
-
0,
|
|
56773
|
-
0,
|
|
56774
|
-
carriageThickness
|
|
56775
|
-
)
|
|
56776
|
-
).translate(carriageCenterX, 0, baseThickness + runningClearance).color("#111827");
|
|
56777
|
-
const parts = [
|
|
56778
|
-
{ name: "captured linear rail with return lips and end stops", shape: rail2 },
|
|
56779
|
-
{ name: "sliding carriage captured under rail lips", shape: carriage }
|
|
56780
|
-
];
|
|
56781
|
-
return {
|
|
56782
|
-
parts,
|
|
56783
|
-
rail: rail2,
|
|
56784
|
-
carriage,
|
|
56785
|
-
dims: {
|
|
56786
|
-
length: length4,
|
|
56787
|
-
railWidth,
|
|
56788
|
-
innerWidth,
|
|
56789
|
-
throatWidth,
|
|
56790
|
-
baseThickness,
|
|
56791
|
-
wallThickness,
|
|
56792
|
-
wallHeight,
|
|
56793
|
-
lipWidth,
|
|
56794
|
-
lipThickness,
|
|
56795
|
-
carriageLength,
|
|
56796
|
-
carriageWidth,
|
|
56797
|
-
carriageThickness,
|
|
56798
|
-
endStopLength,
|
|
56799
|
-
runningClearance,
|
|
56800
|
-
maxTravel,
|
|
56801
|
-
travel,
|
|
56802
|
-
carriageCenterX
|
|
56803
|
-
}
|
|
56804
|
-
};
|
|
56805
|
-
}
|
|
56806
|
-
function capturedCartridgeGuideAssembly(options) {
|
|
56807
|
-
const length4 = requirePositive$6(options.length, "length");
|
|
56808
|
-
const guideWidth = requirePositive$6(options.guideWidth ?? 42, "guideWidth");
|
|
56809
|
-
const baseThickness = requirePositive$6(options.baseThickness ?? 3, "baseThickness");
|
|
56810
|
-
const wallThickness = requirePositive$6(options.wallThickness ?? 2.5, "wallThickness");
|
|
56811
|
-
const wallHeight = requirePositive$6(options.wallHeight ?? 12, "wallHeight");
|
|
56812
|
-
const lipWidth = requirePositive$6(options.lipWidth ?? 4, "lipWidth");
|
|
56813
|
-
const lipThickness = requirePositive$6(options.lipThickness ?? 2, "lipThickness");
|
|
56814
|
-
const rearStopLength = requirePositive$6(options.rearStopLength ?? 7, "rearStopLength");
|
|
56815
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.35, "runningClearance");
|
|
56816
|
-
const cartridgeLength = requirePositive$6(options.cartridgeLength ?? length4 * 0.58, "cartridgeLength");
|
|
56817
|
-
const cartridgeHeight = requirePositive$6(options.cartridgeHeight ?? 10, "cartridgeHeight");
|
|
56818
|
-
const flangeThickness = requirePositive$6(options.flangeThickness ?? 3, "flangeThickness");
|
|
56819
|
-
const pullTabLength = requirePositive$6(options.pullTabLength ?? 10, "pullTabLength");
|
|
56820
|
-
const innerWidth = guideWidth - wallThickness * 2;
|
|
56821
|
-
const throatWidth = innerWidth - lipWidth * 2;
|
|
56822
|
-
if (innerWidth <= 0) throw new Error("capturedCartridgeGuideAssembly: wallThickness leaves no inner guide width");
|
|
56823
|
-
if (throatWidth <= 0) throw new Error("capturedCartridgeGuideAssembly: lipWidth closes the guide throat");
|
|
56824
|
-
if (wallHeight <= lipThickness + flangeThickness + runningClearance * 2) {
|
|
56825
|
-
throw new Error("capturedCartridgeGuideAssembly: wallHeight leaves too little vertical capture clearance");
|
|
56826
|
-
}
|
|
56827
|
-
const cartridgeWidth = requirePositive$6(options.cartridgeWidth ?? innerWidth - runningClearance * 2, "cartridgeWidth");
|
|
56828
|
-
const cartridgeBodyWidth = throatWidth - runningClearance * 2;
|
|
56829
|
-
if (cartridgeBodyWidth <= 0) {
|
|
56830
|
-
throw new Error("capturedCartridgeGuideAssembly: throatWidth and runningClearance leave no cartridge body width");
|
|
56831
|
-
}
|
|
56832
|
-
if (cartridgeWidth >= innerWidth - runningClearance) {
|
|
56833
|
-
throw new Error("capturedCartridgeGuideAssembly: cartridgeWidth leaves too little side clearance inside the guide");
|
|
56834
|
-
}
|
|
56835
|
-
if (cartridgeWidth <= throatWidth + runningClearance) {
|
|
56836
|
-
throw new Error("capturedCartridgeGuideAssembly: cartridge flange must be wider than the guide throat so the cartridge is captured");
|
|
56837
|
-
}
|
|
56838
|
-
const maxInsertion = length4 - rearStopLength - cartridgeLength;
|
|
56839
|
-
if (maxInsertion <= 0) {
|
|
56840
|
-
throw new Error("capturedCartridgeGuideAssembly: length, rearStopLength, and cartridgeLength leave no insertion travel");
|
|
56841
|
-
}
|
|
56842
|
-
const insertion = options.insertion ?? maxInsertion * 0.4;
|
|
56843
|
-
if (!Number.isFinite(insertion) || insertion < 0 || insertion > maxInsertion) {
|
|
56844
|
-
throw new Error(`capturedCartridgeGuideAssembly: insertion must be between 0 and ${maxInsertion}`);
|
|
56845
|
-
}
|
|
56846
|
-
const cartridgeCenterX = -length4 / 2 + cartridgeLength / 2 + insertion;
|
|
56847
|
-
const fuseOverlap = Math.min(0.04, runningClearance * 0.1);
|
|
56848
|
-
const sideY = guideWidth / 2 - wallThickness / 2;
|
|
56849
|
-
const lipY = guideWidth / 2 - wallThickness - lipWidth / 2 + fuseOverlap / 2;
|
|
56850
|
-
const guide = union(
|
|
56851
|
-
box(length4, guideWidth, baseThickness),
|
|
56852
|
-
box(length4, wallThickness, wallHeight + fuseOverlap).translate(0, sideY, baseThickness - fuseOverlap),
|
|
56853
|
-
box(length4, wallThickness, wallHeight + fuseOverlap).translate(0, -sideY, baseThickness - fuseOverlap),
|
|
56854
|
-
box(length4, lipWidth, lipThickness + fuseOverlap).translate(0, lipY, baseThickness + wallHeight - fuseOverlap),
|
|
56855
|
-
box(length4, lipWidth, lipThickness + fuseOverlap).translate(0, -lipY, baseThickness + wallHeight - fuseOverlap),
|
|
56856
|
-
box(rearStopLength, throatWidth, Math.max(flangeThickness + runningClearance, 4)).translate(
|
|
56857
|
-
length4 / 2 - rearStopLength / 2,
|
|
56858
|
-
0,
|
|
56859
|
-
baseThickness - fuseOverlap
|
|
56860
|
-
)
|
|
56861
|
-
).color("#475569");
|
|
56862
|
-
const flangeZ = baseThickness + runningClearance;
|
|
56863
|
-
const bodyHeight = Math.max(1, cartridgeHeight - flangeThickness);
|
|
56864
|
-
const bodyZ = flangeZ + flangeThickness;
|
|
56865
|
-
const tabOverlap = Math.min(0.6, pullTabLength * 0.15);
|
|
56866
|
-
const pullTabX = cartridgeCenterX - cartridgeLength / 2 - pullTabLength / 2 + tabOverlap;
|
|
56867
|
-
const pullTabWidth = Math.max(cartridgeBodyWidth * 0.55, 12);
|
|
56868
|
-
const cartridge = union(
|
|
56869
|
-
box(cartridgeLength, cartridgeWidth, flangeThickness).translate(cartridgeCenterX, 0, flangeZ),
|
|
56870
|
-
box(cartridgeLength * 0.88, cartridgeBodyWidth, bodyHeight).translate(cartridgeCenterX, 0, bodyZ),
|
|
56871
|
-
box(pullTabLength, pullTabWidth, Math.max(flangeThickness, 3)).translate(pullTabX, 0, flangeZ)
|
|
56872
|
-
).color("#111827");
|
|
56873
|
-
const parts = [
|
|
56874
|
-
{ name: "captured cartridge guide with return lips and rear stop", shape: guide },
|
|
56875
|
-
{ name: "removable cartridge with captured flange and pull tab", shape: cartridge }
|
|
56876
|
-
];
|
|
56877
|
-
return {
|
|
56878
|
-
parts,
|
|
56879
|
-
guide,
|
|
56880
|
-
cartridge,
|
|
56881
|
-
dims: {
|
|
56882
|
-
length: length4,
|
|
56883
|
-
guideWidth,
|
|
56884
|
-
innerWidth,
|
|
56885
|
-
throatWidth,
|
|
56886
|
-
baseThickness,
|
|
56887
|
-
wallThickness,
|
|
56888
|
-
wallHeight,
|
|
56889
|
-
lipWidth,
|
|
56890
|
-
lipThickness,
|
|
56891
|
-
rearStopLength,
|
|
56892
|
-
cartridgeLength,
|
|
56893
|
-
cartridgeWidth,
|
|
56894
|
-
cartridgeBodyWidth,
|
|
56895
|
-
cartridgeHeight,
|
|
56896
|
-
flangeThickness,
|
|
56897
|
-
pullTabLength,
|
|
56898
|
-
runningClearance,
|
|
56899
|
-
maxInsertion,
|
|
56900
|
-
insertion,
|
|
56901
|
-
cartridgeCenterX
|
|
56902
|
-
}
|
|
56903
|
-
};
|
|
56904
|
-
}
|
|
56905
|
-
function livingHingeCoverAssembly(options) {
|
|
56906
|
-
const width = requirePositive$6(options.width, "width");
|
|
56907
|
-
const coverDepth = requirePositive$6(options.coverDepth ?? 42, "coverDepth");
|
|
56908
|
-
const fixedLeafDepth = requirePositive$6(options.fixedLeafDepth ?? 18, "fixedLeafDepth");
|
|
56909
|
-
const leafThickness = requirePositive$6(options.leafThickness ?? 2, "leafThickness");
|
|
56910
|
-
const hingeWebWidth = requirePositive$6(options.hingeWebWidth ?? 3.2, "hingeWebWidth");
|
|
56911
|
-
const hingeWebThickness = requirePositive$6(options.hingeWebThickness ?? 0.45, "hingeWebThickness");
|
|
56912
|
-
const pullLipDepth = requirePositive$6(options.pullLipDepth ?? 5, "pullLipDepth");
|
|
56913
|
-
const snapBarbWidth = requirePositive$6(options.snapBarbWidth ?? width * 0.35, "snapBarbWidth");
|
|
56914
|
-
const snapBarbDepth = requirePositive$6(options.snapBarbDepth ?? 2.4, "snapBarbDepth");
|
|
56915
|
-
const snapBarbHeight = requirePositive$6(options.snapBarbHeight ?? 1.4, "snapBarbHeight");
|
|
56916
|
-
const catchLandDepth = requirePositive$6(options.catchLandDepth ?? 2.4, "catchLandDepth");
|
|
56917
|
-
if (hingeWebThickness >= leafThickness * 0.55) {
|
|
56918
|
-
throw new Error("livingHingeCoverAssembly: hingeWebThickness must be much thinner than the rigid leaves");
|
|
56919
|
-
}
|
|
56920
|
-
if (hingeWebWidth >= Math.min(coverDepth, fixedLeafDepth) * 0.45) {
|
|
56921
|
-
throw new Error("livingHingeCoverAssembly: hingeWebWidth is too wide for the selected leaves");
|
|
56922
|
-
}
|
|
56923
|
-
if (snapBarbWidth >= width - 2) {
|
|
56924
|
-
throw new Error("livingHingeCoverAssembly: snapBarbWidth must leave side material on the cover leaf");
|
|
56925
|
-
}
|
|
56926
|
-
const fuseOverlap = Math.min(0.04, hingeWebWidth * 0.02);
|
|
56927
|
-
const fixedCenterY = -hingeWebWidth / 2 - fixedLeafDepth / 2 + fuseOverlap / 2;
|
|
56928
|
-
const coverCenterY = hingeWebWidth / 2 + coverDepth / 2 - fuseOverlap / 2;
|
|
56929
|
-
const fixedLeaf = box(width, fixedLeafDepth + fuseOverlap, leafThickness).translate(0, fixedCenterY, 0);
|
|
56930
|
-
const movingLeaf = box(width, coverDepth + fuseOverlap, leafThickness).translate(0, coverCenterY, 0);
|
|
56931
|
-
const hingeWeb = box(width, hingeWebWidth + fuseOverlap * 2, hingeWebThickness).translate(0, 0, 0);
|
|
56932
|
-
const pullLip = box(width * 0.92, pullLipDepth, leafThickness).translate(
|
|
56933
|
-
0,
|
|
56934
|
-
coverCenterY + coverDepth / 2 + pullLipDepth / 2 - fuseOverlap,
|
|
56935
|
-
0
|
|
56936
|
-
);
|
|
56937
|
-
const snapBarb = box(snapBarbWidth, snapBarbDepth, snapBarbHeight).translate(
|
|
56938
|
-
0,
|
|
56939
|
-
coverCenterY + coverDepth / 2 - snapBarbDepth / 2,
|
|
56940
|
-
leafThickness
|
|
56941
|
-
);
|
|
56942
|
-
const catchLand = box(width * 0.55, catchLandDepth, Math.max(0.8, leafThickness * 0.45)).translate(
|
|
56943
|
-
0,
|
|
56944
|
-
fixedCenterY - fixedLeafDepth / 2 + catchLandDepth / 2,
|
|
56945
|
-
leafThickness
|
|
56946
|
-
);
|
|
56947
|
-
const cover = union(fixedLeaf, movingLeaf, hingeWeb, pullLip, snapBarb, catchLand).color("#0f766e");
|
|
56948
|
-
const overallDepth = fixedLeafDepth + hingeWebWidth + coverDepth + pullLipDepth;
|
|
56949
|
-
const flexRatio = leafThickness / hingeWebThickness;
|
|
56950
|
-
return {
|
|
56951
|
-
parts: [{ name: "one-piece molded living hinge cover with snap barb", shape: cover }],
|
|
56952
|
-
cover,
|
|
56953
|
-
fixedLeaf,
|
|
56954
|
-
movingLeaf,
|
|
56955
|
-
hingeWeb,
|
|
56956
|
-
snapBarb,
|
|
56957
|
-
catchLand,
|
|
56958
|
-
dims: {
|
|
56959
|
-
width,
|
|
56960
|
-
coverDepth,
|
|
56961
|
-
fixedLeafDepth,
|
|
56962
|
-
leafThickness,
|
|
56963
|
-
hingeWebWidth,
|
|
56964
|
-
hingeWebThickness,
|
|
56965
|
-
pullLipDepth,
|
|
56966
|
-
snapBarbWidth,
|
|
56967
|
-
snapBarbDepth,
|
|
56968
|
-
snapBarbHeight,
|
|
56969
|
-
catchLandDepth,
|
|
56970
|
-
flexRatio,
|
|
56971
|
-
overallDepth
|
|
56972
|
-
}
|
|
56973
|
-
};
|
|
56974
|
-
}
|
|
56975
|
-
function knuckledHingeAssembly(options) {
|
|
56976
|
-
const length4 = requirePositive$6(options.length, "length");
|
|
56977
|
-
const leafLength = requirePositive$6(options.leafLength ?? 36, "leafLength");
|
|
56978
|
-
const leafThickness = requirePositive$6(options.leafThickness ?? 1.6, "leafThickness");
|
|
56979
|
-
const barrelOuterRadius = requirePositive$6(options.barrelOuterRadius ?? 3, "barrelOuterRadius");
|
|
56980
|
-
const pinDiameter = requirePositive$6(options.pinDiameter ?? 2, "pinDiameter");
|
|
56981
|
-
const pinClearance = requireNonNegative(options.pinClearance ?? 0.25, "pinClearance");
|
|
56982
|
-
const boreDiameter = pinDiameter + pinClearance;
|
|
56983
|
-
const knuckleGap = requireNonNegative(options.knuckleGap ?? 0.45, "knuckleGap");
|
|
56984
|
-
const openAngleDeg = Number.isFinite(options.openAngleDeg ?? 35) ? options.openAngleDeg ?? 35 : 35;
|
|
56985
|
-
const retainerThickness = requirePositive$6(options.retainerThickness ?? Math.max(leafThickness, pinDiameter * 0.7), "retainerThickness");
|
|
56986
|
-
const segments = options.segments ?? 36;
|
|
56987
|
-
const knuckleCount = options.knuckleCount ?? 5;
|
|
56988
|
-
if (!Number.isInteger(knuckleCount) || knuckleCount < 3 || knuckleCount % 2 === 0) {
|
|
56989
|
-
throw new Error("knuckledHingeAssembly: knuckleCount must be an odd integer >= 3");
|
|
56990
|
-
}
|
|
56991
|
-
if (barrelOuterRadius <= boreDiameter / 2 + Math.max(0.35, pinDiameter * 0.18)) {
|
|
56992
|
-
throw new Error("knuckledHingeAssembly: barrelOuterRadius leaves too little wall around the pin bore");
|
|
56993
|
-
}
|
|
56994
|
-
const knuckleLength = (length4 - knuckleGap * (knuckleCount - 1)) / knuckleCount;
|
|
56995
|
-
if (knuckleLength <= pinDiameter * 1.4) {
|
|
56996
|
-
throw new Error("knuckledHingeAssembly: length, knuckleCount, and knuckleGap make knuckles too short");
|
|
56997
|
-
}
|
|
56998
|
-
const leafRootClearance = Math.max(0.12, Math.min(knuckleGap * 0.35, 0.35));
|
|
56999
|
-
const barrelLeafOverlap = Math.min(barrelOuterRadius * 0.18, leafThickness * 0.35);
|
|
57000
|
-
const bridgeDepth = leafRootClearance + barrelLeafOverlap + 0.2;
|
|
57001
|
-
const fixedLeafPlate = box(length4, leafLength, leafThickness).translate(
|
|
57002
|
-
0,
|
|
57003
|
-
barrelOuterRadius + leafRootClearance + leafLength / 2,
|
|
57004
|
-
-leafThickness / 2
|
|
57005
|
-
);
|
|
57006
|
-
const movingLeafPlate = box(length4, leafLength, leafThickness).translate(
|
|
57007
|
-
0,
|
|
57008
|
-
-barrelOuterRadius - leafRootClearance - leafLength / 2,
|
|
57009
|
-
-leafThickness / 2
|
|
57010
|
-
);
|
|
57011
|
-
const fixedKnuckles = [];
|
|
57012
|
-
const movingKnuckles = [];
|
|
57013
|
-
const fixedBridges = [];
|
|
57014
|
-
const movingBridges = [];
|
|
57015
|
-
for (let index2 = 0; index2 < knuckleCount; index2 += 1) {
|
|
57016
|
-
const xStart = -length4 / 2 + index2 * (knuckleLength + knuckleGap);
|
|
57017
|
-
const xCenter = xStart + knuckleLength / 2;
|
|
57018
|
-
const knuckle = tubeAlongX(knuckleLength, barrelOuterRadius, boreDiameter / 2, xCenter, segments);
|
|
57019
|
-
if (index2 % 2 === 0) {
|
|
57020
|
-
fixedKnuckles.push(knuckle);
|
|
57021
|
-
fixedBridges.push(
|
|
57022
|
-
box(knuckleLength, bridgeDepth, leafThickness).translate(
|
|
57023
|
-
xCenter,
|
|
57024
|
-
barrelOuterRadius - barrelLeafOverlap + bridgeDepth / 2,
|
|
57025
|
-
-leafThickness / 2
|
|
57026
|
-
)
|
|
57027
|
-
);
|
|
57028
|
-
} else {
|
|
57029
|
-
movingKnuckles.push(knuckle);
|
|
57030
|
-
movingBridges.push(
|
|
57031
|
-
box(knuckleLength, bridgeDepth, leafThickness).translate(
|
|
57032
|
-
xCenter,
|
|
57033
|
-
-barrelOuterRadius + barrelLeafOverlap - bridgeDepth / 2,
|
|
57034
|
-
-leafThickness / 2
|
|
57035
|
-
)
|
|
57036
|
-
);
|
|
57037
|
-
}
|
|
57038
|
-
}
|
|
57039
|
-
const fixedLeaf = union(fixedLeafPlate, ...fixedKnuckles, ...fixedBridges).color("#475569");
|
|
57040
|
-
const movingLeaf = union(movingLeafPlate, ...movingKnuckles, ...movingBridges).rotateX(openAngleDeg).color("#111827");
|
|
57041
|
-
const pinCore = cylinderAlongX(length4 + retainerThickness * 2, pinDiameter / 2, 0, segments);
|
|
57042
|
-
const retainerRadius = Math.max(barrelOuterRadius * 0.85, pinDiameter);
|
|
57043
|
-
const leftHead = cylinderAlongX(retainerThickness, retainerRadius, -length4 / 2 - retainerThickness / 2, segments);
|
|
57044
|
-
const rightHead = cylinderAlongX(retainerThickness, retainerRadius, length4 / 2 + retainerThickness / 2, segments);
|
|
57045
|
-
const pin = union(pinCore, leftHead, rightHead).color("#cbd5e1");
|
|
57046
|
-
const pinBore = cylinderAlongX(length4 + retainerThickness * 2, boreDiameter / 2, 0, segments);
|
|
57047
|
-
const parts = [
|
|
57048
|
-
{ name: "fixed hinge leaf with alternating knuckles", shape: fixedLeaf },
|
|
57049
|
-
{ name: "moving hinge leaf with alternating knuckles", shape: movingLeaf },
|
|
57050
|
-
{ name: "retained hinge pin through knuckle stack", shape: pin }
|
|
57051
|
-
];
|
|
57052
|
-
return {
|
|
57053
|
-
parts,
|
|
57054
|
-
fixedLeaf,
|
|
57055
|
-
movingLeaf,
|
|
57056
|
-
pin,
|
|
57057
|
-
cutters: {
|
|
57058
|
-
pinBore
|
|
57059
|
-
},
|
|
57060
|
-
dims: {
|
|
57061
|
-
length: length4,
|
|
57062
|
-
leafLength,
|
|
57063
|
-
leafThickness,
|
|
57064
|
-
barrelOuterRadius,
|
|
57065
|
-
pinDiameter,
|
|
57066
|
-
boreDiameter,
|
|
57067
|
-
knuckleGap,
|
|
57068
|
-
knuckleCount,
|
|
57069
|
-
knuckleLength,
|
|
57070
|
-
openAngleDeg,
|
|
57071
|
-
retainerThickness
|
|
57072
|
-
}
|
|
57073
|
-
};
|
|
57074
|
-
}
|
|
57075
|
-
function clevisPinJointAssembly(options = {}) {
|
|
57076
|
-
const pinDiameter = requirePositive$6(options.pinDiameter ?? 4, "pinDiameter");
|
|
57077
|
-
const pinClearance = requireNonNegative(options.pinClearance ?? 0.3, "pinClearance");
|
|
57078
|
-
const boreDiameter = pinDiameter + pinClearance;
|
|
57079
|
-
const linkThickness = requirePositive$6(options.linkThickness ?? Math.max(5, pinDiameter * 1.5), "linkThickness");
|
|
57080
|
-
const earThickness = requirePositive$6(options.earThickness ?? Math.max(3.5, pinDiameter), "earThickness");
|
|
57081
|
-
const runningClearance = requireNonNegative(options.runningClearance ?? 0.25, "runningClearance");
|
|
57082
|
-
const linkArmWidth = requirePositive$6(options.linkArmWidth ?? pinDiameter * 2.4, "linkArmWidth");
|
|
57083
|
-
const eyeOuterRadius = requirePositive$6(options.eyeOuterRadius ?? Math.max(pinDiameter * 1.8, linkArmWidth / 2 + 1.4), "eyeOuterRadius");
|
|
57084
|
-
const earLength = requirePositive$6(options.earLength ?? Math.max(eyeOuterRadius * 2.55, pinDiameter * 4.2), "earLength");
|
|
57085
|
-
const earHeight = requirePositive$6(options.earHeight ?? Math.max(eyeOuterRadius * 2.25, pinDiameter * 4.4), "earHeight");
|
|
57086
|
-
const linkArmLength = requirePositive$6(options.linkArmLength ?? 34, "linkArmLength");
|
|
57087
|
-
const retainerThickness = requirePositive$6(options.retainerThickness ?? Math.max(1.2, pinDiameter * 0.35), "retainerThickness");
|
|
57088
|
-
const segments = options.segments ?? 40;
|
|
57089
|
-
if (eyeOuterRadius <= boreDiameter / 2 + Math.max(0.8, pinDiameter * 0.25)) {
|
|
57090
|
-
throw new Error("clevisPinJointAssembly: eyeOuterRadius leaves too little material around the pin bore");
|
|
57091
|
-
}
|
|
57092
|
-
if (earHeight <= boreDiameter + Math.max(3, pinDiameter)) {
|
|
57093
|
-
throw new Error("clevisPinJointAssembly: earHeight leaves too little material around the pin bore");
|
|
57094
|
-
}
|
|
57095
|
-
if (earLength / 2 <= eyeOuterRadius + runningClearance) {
|
|
57096
|
-
throw new Error("clevisPinJointAssembly: earLength must extend behind the link eye for a rear clevis bridge");
|
|
57097
|
-
}
|
|
57098
|
-
const clevisGap = linkThickness + runningClearance * 2;
|
|
57099
|
-
const earCenterY = clevisGap / 2 + earThickness / 2;
|
|
57100
|
-
const totalStackY = clevisGap + earThickness * 2;
|
|
57101
|
-
const pinLength = totalStackY + retainerThickness * 2 + runningClearance * 2;
|
|
57102
|
-
const bridgeClearX = -eyeOuterRadius - runningClearance;
|
|
57103
|
-
const bridgeLength = Math.max(pinDiameter * 2.2, 4);
|
|
57104
|
-
const bridgeHeight = Math.min(earHeight * 0.48, Math.max(pinDiameter * 1.4, eyeOuterRadius * 0.75));
|
|
57105
|
-
const bridgeCenterX = bridgeClearX - bridgeLength / 2;
|
|
57106
|
-
const bridgeCenterZ = -earHeight / 2 + bridgeHeight / 2;
|
|
57107
|
-
const pinBore = cylinderAlongY(totalStackY + 0.8, boreDiameter / 2, 0, segments);
|
|
57108
|
-
const clevisBlank = union(
|
|
57109
|
-
box(earLength, earThickness, earHeight).translate(0, earCenterY, -earHeight / 2),
|
|
57110
|
-
box(earLength, earThickness, earHeight).translate(0, -earCenterY, -earHeight / 2),
|
|
57111
|
-
box(bridgeLength, totalStackY, bridgeHeight).translate(bridgeCenterX, 0, bridgeCenterZ)
|
|
57112
|
-
);
|
|
57113
|
-
const clevis = clevisBlank.subtract(pinBore).color("#475569");
|
|
57114
|
-
const eye = tubeAlongY(linkThickness, eyeOuterRadius, boreDiameter / 2, 0, segments);
|
|
57115
|
-
const armOverlap = Math.min(eyeOuterRadius * 0.65, linkArmLength * 0.25);
|
|
57116
|
-
const armCenterX = eyeOuterRadius - armOverlap + linkArmLength / 2;
|
|
57117
|
-
const linkArm = box(linkArmLength, linkThickness, linkArmWidth).translate(armCenterX, 0, -linkArmWidth / 2);
|
|
57118
|
-
const link = union(eye, linkArm).color("#111827");
|
|
57119
|
-
const pinCore = cylinderAlongY(pinLength, pinDiameter / 2, 0, segments);
|
|
57120
|
-
const headRadius = Math.max(pinDiameter * 0.9, boreDiameter / 2 + 0.8);
|
|
57121
|
-
const headY = totalStackY / 2 + runningClearance + retainerThickness / 2;
|
|
57122
|
-
const headA = cylinderAlongY(retainerThickness, headRadius, headY, segments);
|
|
57123
|
-
const headB = cylinderAlongY(retainerThickness, headRadius, -headY, segments);
|
|
57124
|
-
const pin = union(pinCore, headA, headB).color("#cbd5e1");
|
|
57125
|
-
const cutter = cylinderAlongY(pinLength + 1, boreDiameter / 2, 0, segments);
|
|
57126
|
-
const parts = [
|
|
57127
|
-
{ name: "bored clevis yoke with rear bridge", shape: clevis },
|
|
57128
|
-
{ name: "center link eye captured in clevis", shape: link },
|
|
57129
|
-
{ name: "retained clevis pin through link eye", shape: pin }
|
|
57130
|
-
];
|
|
57131
|
-
return {
|
|
57132
|
-
parts,
|
|
57133
|
-
clevis,
|
|
57134
|
-
link,
|
|
57135
|
-
pin,
|
|
57136
|
-
cutters: {
|
|
57137
|
-
pinBore: cutter
|
|
57138
|
-
},
|
|
57139
|
-
dims: {
|
|
57140
|
-
pinDiameter,
|
|
57141
|
-
boreDiameter,
|
|
57142
|
-
linkThickness,
|
|
57143
|
-
earThickness,
|
|
57144
|
-
runningClearance,
|
|
57145
|
-
earLength,
|
|
57146
|
-
earHeight,
|
|
57147
|
-
linkArmLength,
|
|
57148
|
-
linkArmWidth,
|
|
57149
|
-
eyeOuterRadius,
|
|
57150
|
-
retainerThickness,
|
|
57151
|
-
pinLength,
|
|
57152
|
-
clevisGap
|
|
57153
|
-
}
|
|
57154
|
-
};
|
|
57155
|
-
}
|
|
57156
|
-
function seatedBearingAssembly(options) {
|
|
57157
|
-
const bearingOuterDiameter = requirePositive$6(options.bearingOuterDiameter, "bearingOuterDiameter");
|
|
57158
|
-
const bearingInnerDiameter = requirePositive$6(options.bearingInnerDiameter, "bearingInnerDiameter");
|
|
57159
|
-
const bearingWidth = requirePositive$6(options.bearingWidth, "bearingWidth");
|
|
57160
|
-
const shaftDiameter = requirePositive$6(options.shaftDiameter ?? Math.max(1, bearingInnerDiameter - 0.4), "shaftDiameter");
|
|
57161
|
-
const pocketClearance = requireNonNegative(options.pocketClearance ?? 0.2, "pocketClearance");
|
|
57162
|
-
const shaftClearance = requireNonNegative(options.shaftClearance ?? 0.35, "shaftClearance");
|
|
57163
|
-
const runningClearance = requireNonNegative(options.runningClearance ?? 0.05, "runningClearance");
|
|
57164
|
-
const housingThickness = requirePositive$6(options.housingThickness ?? bearingWidth + 5, "housingThickness");
|
|
57165
|
-
const bossHeight = requirePositive$6(options.bossHeight ?? Math.max(2, bearingWidth * 0.45), "bossHeight");
|
|
57166
|
-
const bossOuterDiameter = requirePositive$6(
|
|
57167
|
-
options.bossOuterDiameter ?? bearingOuterDiameter + Math.max(8, bearingOuterDiameter * 0.36),
|
|
57168
|
-
"bossOuterDiameter"
|
|
57169
|
-
);
|
|
57170
|
-
const housingWidth = requirePositive$6(
|
|
57171
|
-
options.housingWidth ?? Math.max(bossOuterDiameter + 12, bearingOuterDiameter * 2.1),
|
|
57172
|
-
"housingWidth"
|
|
57173
|
-
);
|
|
57174
|
-
const housingDepth = requirePositive$6(
|
|
57175
|
-
options.housingDepth ?? Math.max(bossOuterDiameter + 12, bearingOuterDiameter * 1.8),
|
|
57176
|
-
"housingDepth"
|
|
57177
|
-
);
|
|
57178
|
-
const shaftOverhang = requirePositive$6(options.shaftOverhang ?? Math.max(8, bearingOuterDiameter * 0.45), "shaftOverhang");
|
|
57179
|
-
const shoulderDiameter = requirePositive$6(
|
|
57180
|
-
options.shoulderDiameter ?? Math.max(shaftDiameter * 1.65, bearingInnerDiameter + 2),
|
|
57181
|
-
"shoulderDiameter"
|
|
57182
|
-
);
|
|
57183
|
-
const shoulderThickness = requirePositive$6(options.shoulderThickness ?? Math.max(1.5, shaftDiameter * 0.32), "shoulderThickness");
|
|
57184
|
-
const segments = options.segments ?? 48;
|
|
57185
|
-
if (bearingOuterDiameter <= bearingInnerDiameter + Math.max(1, bearingOuterDiameter * 0.08)) {
|
|
57186
|
-
throw new Error("seatedBearingAssembly: bearingOuterDiameter leaves too little bearing wall around the bore");
|
|
57187
|
-
}
|
|
57188
|
-
if (shaftDiameter + shaftClearance >= bearingInnerDiameter) {
|
|
57189
|
-
throw new Error("seatedBearingAssembly: shaftDiameter plus shaftClearance must fit inside the bearing bore");
|
|
57190
|
-
}
|
|
57191
|
-
if (shoulderDiameter >= bearingOuterDiameter - runningClearance * 2) {
|
|
57192
|
-
throw new Error("seatedBearingAssembly: shoulderDiameter must stay smaller than the bearing outer race");
|
|
57193
|
-
}
|
|
57194
|
-
const pocketDiameter = bearingOuterDiameter + pocketClearance;
|
|
57195
|
-
const shaftBoreDiameter = shaftDiameter + shaftClearance;
|
|
57196
|
-
const totalHousingHeight = housingThickness + bossHeight;
|
|
57197
|
-
const pocketDepth = bearingWidth + runningClearance * 2;
|
|
57198
|
-
if (pocketDepth >= totalHousingHeight - runningClearance) {
|
|
57199
|
-
throw new Error("seatedBearingAssembly: housingThickness and bossHeight must leave a shoulder below the bearing pocket");
|
|
57200
|
-
}
|
|
57201
|
-
if (bossOuterDiameter <= pocketDiameter + Math.max(2, bearingOuterDiameter * 0.12)) {
|
|
57202
|
-
throw new Error("seatedBearingAssembly: bossOuterDiameter leaves too little wall around the bearing pocket");
|
|
57203
|
-
}
|
|
57204
|
-
if (housingWidth <= pocketDiameter + 6 || housingDepth <= pocketDiameter + 6) {
|
|
57205
|
-
throw new Error("seatedBearingAssembly: housing dimensions leave too little material around the bearing pocket");
|
|
57206
|
-
}
|
|
57207
|
-
if (shoulderThickness * 2 + runningClearance * 2 >= shaftOverhang) {
|
|
57208
|
-
throw new Error("seatedBearingAssembly: shaftOverhang must leave room for retaining collars outside the housing");
|
|
57209
|
-
}
|
|
57210
|
-
const pocketBottomZ = totalHousingHeight - pocketDepth;
|
|
57211
|
-
const bearingZ = pocketBottomZ + runningClearance;
|
|
57212
|
-
const lowerShoulderZ = -runningClearance - shoulderThickness;
|
|
57213
|
-
const upperShoulderZ = totalHousingHeight + runningClearance;
|
|
57214
|
-
const shaftLength = totalHousingHeight + shaftOverhang * 2;
|
|
57215
|
-
const bossFuseOverlap = Math.min(0.08, Math.max(0.02, bossHeight * 0.03));
|
|
57216
|
-
const bearingPocket = cylinder(pocketDepth + 0.4, pocketDiameter / 2, void 0, segments).translate(0, 0, pocketBottomZ - 0.2);
|
|
57217
|
-
const shaftBore = cylinder(totalHousingHeight + 1, shaftBoreDiameter / 2, void 0, segments).translate(0, 0, -0.5);
|
|
57218
|
-
const housingBase = box(housingWidth, housingDepth, housingThickness).subtract(bearingPocket).subtract(shaftBore);
|
|
57219
|
-
const housingBoss = cylinder(bossHeight + bossFuseOverlap, bossOuterDiameter / 2, void 0, segments).translate(0, 0, housingThickness - bossFuseOverlap).subtract(bearingPocket);
|
|
57220
|
-
const housing = union(housingBase, housingBoss).color("#475569");
|
|
57221
|
-
const bearingRing = tubeAlongZ(bearingWidth, bearingOuterDiameter / 2, bearingInnerDiameter / 2, segments);
|
|
57222
|
-
const shieldInset = Math.min(bearingWidth * 0.18, 0.7);
|
|
57223
|
-
const shieldOuterRadius = bearingOuterDiameter / 2 - Math.max(0.45, (bearingOuterDiameter - bearingInnerDiameter) * 0.08);
|
|
57224
|
-
const shieldInnerRadius = bearingInnerDiameter / 2 + Math.max(0.2, (bearingOuterDiameter - bearingInnerDiameter) * 0.035);
|
|
57225
|
-
const bearingShield = shieldOuterRadius > shieldInnerRadius + 0.2 ? union(
|
|
57226
|
-
tubeAlongZ(Math.min(0.35, bearingWidth * 0.08), shieldOuterRadius, shieldInnerRadius, segments).translate(0, 0, shieldInset),
|
|
57227
|
-
tubeAlongZ(Math.min(0.35, bearingWidth * 0.08), shieldOuterRadius, shieldInnerRadius, segments).translate(
|
|
57228
|
-
0,
|
|
57229
|
-
0,
|
|
57230
|
-
bearingWidth - shieldInset - Math.min(0.35, bearingWidth * 0.08)
|
|
57231
|
-
)
|
|
57232
|
-
) : null;
|
|
57233
|
-
const bearing = (bearingShield ? union(bearingRing, bearingShield) : bearingRing).translate(0, 0, bearingZ).color("#111827");
|
|
57234
|
-
const shaftCore = cylinder(shaftLength, shaftDiameter / 2, void 0, segments).translate(0, 0, -shaftOverhang);
|
|
57235
|
-
const lowerShoulder = cylinder(shoulderThickness, shoulderDiameter / 2, void 0, segments).translate(0, 0, lowerShoulderZ);
|
|
57236
|
-
const upperShoulder = cylinder(shoulderThickness, shoulderDiameter / 2, void 0, segments).translate(0, 0, upperShoulderZ);
|
|
57237
|
-
const shaft = union(shaftCore, lowerShoulder, upperShoulder).color("#cbd5e1");
|
|
57238
|
-
const parts = [
|
|
57239
|
-
{ name: "bearing housing with counterbore pocket and shoulder", shape: housing },
|
|
57240
|
-
{ name: "purchased radial bearing seated in counterbore", shape: bearing },
|
|
57241
|
-
{ name: "shaft through bearing bore with retaining collars", shape: shaft }
|
|
57242
|
-
];
|
|
57243
|
-
return {
|
|
57244
|
-
parts,
|
|
57245
|
-
housing,
|
|
57246
|
-
bearing,
|
|
57247
|
-
shaft,
|
|
57248
|
-
cutters: {
|
|
57249
|
-
bearingPocket,
|
|
57250
|
-
shaftBore
|
|
57251
|
-
},
|
|
57252
|
-
dims: {
|
|
57253
|
-
bearingOuterDiameter,
|
|
57254
|
-
bearingInnerDiameter,
|
|
57255
|
-
bearingWidth,
|
|
57256
|
-
shaftDiameter,
|
|
57257
|
-
housingWidth,
|
|
57258
|
-
housingDepth,
|
|
57259
|
-
housingThickness,
|
|
57260
|
-
bossOuterDiameter,
|
|
57261
|
-
bossHeight,
|
|
57262
|
-
totalHousingHeight,
|
|
57263
|
-
pocketDiameter,
|
|
57264
|
-
pocketDepth,
|
|
57265
|
-
shaftBoreDiameter,
|
|
57266
|
-
runningClearance,
|
|
57267
|
-
shaftLength,
|
|
57268
|
-
shoulderDiameter,
|
|
57269
|
-
shoulderThickness
|
|
57270
|
-
}
|
|
57271
|
-
};
|
|
57272
|
-
}
|
|
57273
|
-
function cableGlandAnchorAssembly(options) {
|
|
57274
|
-
const cableDiameter = requirePositive$6(options.cableDiameter, "cableDiameter");
|
|
57275
|
-
const panelThickness = requirePositive$6(options.panelThickness ?? 3, "panelThickness");
|
|
57276
|
-
const panelWidth = requirePositive$6(options.panelWidth ?? Math.max(54, cableDiameter * 7), "panelWidth");
|
|
57277
|
-
const panelHeight = requirePositive$6(options.panelHeight ?? Math.max(38, cableDiameter * 5), "panelHeight");
|
|
57278
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.35, "runningClearance");
|
|
57279
|
-
const panelHoleClearance = requirePositive$6(options.panelHoleClearance ?? 0.25, "panelHoleClearance");
|
|
57280
|
-
const cableBoreDiameter = cableDiameter + runningClearance * 2;
|
|
57281
|
-
const glandOuterDiameter = requirePositive$6(
|
|
57282
|
-
options.glandOuterDiameter ?? cableDiameter + Math.max(6, cableDiameter * 0.9),
|
|
57283
|
-
"glandOuterDiameter"
|
|
57284
|
-
);
|
|
57285
|
-
const nutOuterDiameter = requirePositive$6(
|
|
57286
|
-
options.nutOuterDiameter ?? glandOuterDiameter + Math.max(6, cableDiameter * 0.8),
|
|
57287
|
-
"nutOuterDiameter"
|
|
57288
|
-
);
|
|
57289
|
-
const nutThickness = requirePositive$6(options.nutThickness ?? Math.max(4, cableDiameter * 0.8), "nutThickness");
|
|
57290
|
-
const flangeDiameter = requirePositive$6(options.flangeDiameter ?? glandOuterDiameter + Math.max(5, cableDiameter * 0.7), "flangeDiameter");
|
|
57291
|
-
const flangeThickness = requirePositive$6(options.flangeThickness ?? Math.max(2, panelThickness * 0.45), "flangeThickness");
|
|
57292
|
-
const minGlandLength = panelThickness + nutThickness + flangeThickness + runningClearance * 4;
|
|
57293
|
-
const glandLength = requirePositive$6(options.glandLength ?? minGlandLength + Math.max(8, cableDiameter), "glandLength");
|
|
57294
|
-
const cableLength = requirePositive$6(options.cableLength ?? glandLength + Math.max(36, cableDiameter * 5), "cableLength");
|
|
57295
|
-
const segments = options.segments ?? 40;
|
|
57296
|
-
if (glandOuterDiameter <= cableBoreDiameter + Math.max(1.2, cableDiameter * 0.18)) {
|
|
57297
|
-
throw new Error("cableGlandAnchorAssembly: glandOuterDiameter leaves too little wall around the cable bore");
|
|
57298
|
-
}
|
|
57299
|
-
if (nutOuterDiameter <= glandOuterDiameter + Math.max(1.5, cableDiameter * 0.2)) {
|
|
57300
|
-
throw new Error("cableGlandAnchorAssembly: nutOuterDiameter must leave material around the gland body");
|
|
57301
|
-
}
|
|
57302
|
-
if (flangeDiameter <= glandOuterDiameter + Math.max(1.2, cableDiameter * 0.16)) {
|
|
57303
|
-
throw new Error("cableGlandAnchorAssembly: flangeDiameter must be larger than the gland body");
|
|
57304
|
-
}
|
|
57305
|
-
if (panelWidth <= flangeDiameter + 8 || panelHeight <= flangeDiameter + 8) {
|
|
57306
|
-
throw new Error("cableGlandAnchorAssembly: panel dimensions leave too little material around the gland hole");
|
|
57307
|
-
}
|
|
57308
|
-
if (glandLength <= minGlandLength) {
|
|
57309
|
-
throw new Error("cableGlandAnchorAssembly: glandLength must span the panel, flange, compression nut, and clearances");
|
|
57310
|
-
}
|
|
57311
|
-
if (cableLength <= glandLength + runningClearance * 2) {
|
|
57312
|
-
throw new Error("cableGlandAnchorAssembly: cableLength must extend beyond the gland body");
|
|
57313
|
-
}
|
|
57314
|
-
const panelHoleDiameter = glandOuterDiameter + panelHoleClearance * 2;
|
|
57315
|
-
const glandOuterRadius = glandOuterDiameter / 2;
|
|
57316
|
-
const cableBoreRadius = cableBoreDiameter / 2;
|
|
57317
|
-
const faceClearance = Math.min(0.05, runningClearance * 0.15);
|
|
57318
|
-
const flangePocketDepth = Math.min(Math.max(0.35, panelThickness * 0.18), panelThickness * 0.4, flangeThickness * 0.55);
|
|
57319
|
-
const panelHole = cylinderAlongX(panelThickness + 0.8, panelHoleDiameter / 2, 0, segments);
|
|
57320
|
-
const flangeSeatPocket = cylinderAlongX(
|
|
57321
|
-
flangePocketDepth + 0.2,
|
|
57322
|
-
flangeDiameter / 2 + panelHoleClearance,
|
|
57323
|
-
panelThickness / 2 - flangePocketDepth / 2,
|
|
57324
|
-
segments
|
|
57325
|
-
);
|
|
57326
|
-
const cableBore = cylinderAlongX(glandLength + 0.8, cableBoreRadius, 0, segments);
|
|
57327
|
-
const panel = box(panelThickness, panelWidth, panelHeight).translate(0, 0, -panelHeight / 2).subtract(panelHole).subtract(flangeSeatPocket).color("#475569");
|
|
57328
|
-
const glandBody = tubeAlongX(glandLength, glandOuterRadius, cableBoreRadius, 0, segments);
|
|
57329
|
-
const flangeCenterX = panelThickness / 2 - flangePocketDepth + faceClearance + flangeThickness / 2;
|
|
57330
|
-
const flange = tubeAlongX(flangeThickness, flangeDiameter / 2, cableBoreRadius, flangeCenterX, segments);
|
|
57331
|
-
const gland = union(glandBody, flange).color("#94a3b8");
|
|
57332
|
-
const nutInnerRadius = glandOuterRadius + Math.min(0.12, runningClearance * 0.4);
|
|
57333
|
-
const nutCenterX = -panelThickness / 2 - faceClearance - nutThickness / 2;
|
|
57334
|
-
const compressionNut = tubeAlongX(nutThickness, nutOuterDiameter / 2, nutInnerRadius, nutCenterX, segments).color("#cbd5e1");
|
|
57335
|
-
const cable = cylinderAlongX(cableLength, cableDiameter / 2, 0, segments).color("#111827");
|
|
57336
|
-
const parts = [
|
|
57337
|
-
{ name: "panel with gland clearance hole", shape: panel },
|
|
57338
|
-
{ name: "hollow cable gland body with panel flange", shape: gland },
|
|
57339
|
-
{ name: "compression nut around gland body", shape: compressionNut },
|
|
57340
|
-
{ name: "routed cable through gland bore", shape: cable }
|
|
57341
|
-
];
|
|
57342
|
-
return {
|
|
57343
|
-
parts,
|
|
57344
|
-
panel,
|
|
57345
|
-
gland,
|
|
57346
|
-
compressionNut,
|
|
57347
|
-
cable,
|
|
57348
|
-
cutters: {
|
|
57349
|
-
panelHole,
|
|
57350
|
-
flangeSeatPocket,
|
|
57351
|
-
cableBore
|
|
57352
|
-
},
|
|
57353
|
-
dims: {
|
|
57354
|
-
cableDiameter,
|
|
57355
|
-
cableBoreDiameter,
|
|
57356
|
-
panelThickness,
|
|
57357
|
-
panelWidth,
|
|
57358
|
-
panelHeight,
|
|
57359
|
-
glandOuterDiameter,
|
|
57360
|
-
glandLength,
|
|
57361
|
-
nutOuterDiameter,
|
|
57362
|
-
nutThickness,
|
|
57363
|
-
flangeDiameter,
|
|
57364
|
-
flangeThickness,
|
|
57365
|
-
runningClearance,
|
|
57366
|
-
faceClearance,
|
|
57367
|
-
flangePocketDepth,
|
|
57368
|
-
panelHoleDiameter,
|
|
57369
|
-
cableLength
|
|
57370
|
-
}
|
|
57371
|
-
};
|
|
57372
|
-
}
|
|
57373
|
-
function hoseBarbPortAssembly(options) {
|
|
57374
|
-
const hoseInnerDiameter = requirePositive$6(options.hoseInnerDiameter, "hoseInnerDiameter");
|
|
57375
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.18, "runningClearance");
|
|
57376
|
-
const faceClearance = requirePositive$6(options.faceClearance ?? 0.04, "faceClearance");
|
|
57377
|
-
const barbRootDiameter = requirePositive$6(
|
|
57378
|
-
options.barbRootDiameter ?? Math.max(1, hoseInnerDiameter - Math.max(0.25, hoseInnerDiameter * 0.06)),
|
|
57379
|
-
"barbRootDiameter"
|
|
57380
|
-
);
|
|
57381
|
-
const barbPeakDiameter = requirePositive$6(
|
|
57382
|
-
options.barbPeakDiameter ?? hoseInnerDiameter + Math.max(0.65, hoseInnerDiameter * 0.12),
|
|
57383
|
-
"barbPeakDiameter"
|
|
57384
|
-
);
|
|
57385
|
-
const installedHoseBoreDiameter = barbPeakDiameter + runningClearance * 2;
|
|
57386
|
-
const hoseOuterDiameter = requirePositive$6(
|
|
57387
|
-
options.hoseOuterDiameter ?? Math.max(installedHoseBoreDiameter + 2.4, hoseInnerDiameter + Math.max(3, hoseInnerDiameter * 0.55)),
|
|
57388
|
-
"hoseOuterDiameter"
|
|
57389
|
-
);
|
|
57390
|
-
const fluidBoreDiameter = requirePositive$6(options.fluidBoreDiameter ?? hoseInnerDiameter * 0.65, "fluidBoreDiameter");
|
|
57391
|
-
const blockThickness = requirePositive$6(options.blockThickness ?? Math.max(7, hoseInnerDiameter * 1.2), "blockThickness");
|
|
57392
|
-
const barbCount = options.barbCount ?? 3;
|
|
57393
|
-
const barbLength = requirePositive$6(options.barbLength ?? Math.max(2.6, hoseInnerDiameter * 0.55), "barbLength");
|
|
57394
|
-
const barbStackLength = barbCount * barbLength;
|
|
57395
|
-
const shoulderDiameter = requirePositive$6(
|
|
57396
|
-
options.shoulderDiameter ?? barbPeakDiameter + Math.max(4, hoseInnerDiameter * 0.65),
|
|
57397
|
-
"shoulderDiameter"
|
|
57398
|
-
);
|
|
57399
|
-
const shoulderThickness = requirePositive$6(options.shoulderThickness ?? Math.max(2, hoseInnerDiameter * 0.35), "shoulderThickness");
|
|
57400
|
-
const bossDiameter = requirePositive$6(options.bossDiameter ?? shoulderDiameter + Math.max(4, hoseInnerDiameter * 0.6), "bossDiameter");
|
|
57401
|
-
const bossHeight = requirePositive$6(options.bossHeight ?? Math.max(2.4, hoseInnerDiameter * 0.45), "bossHeight");
|
|
57402
|
-
const blockWidth = requirePositive$6(options.blockWidth ?? bossDiameter + Math.max(14, hoseInnerDiameter * 2.4), "blockWidth");
|
|
57403
|
-
const blockHeight = requirePositive$6(options.blockHeight ?? bossDiameter + Math.max(12, hoseInnerDiameter * 2.1), "blockHeight");
|
|
57404
|
-
const hoseLength = requirePositive$6(options.hoseLength ?? barbStackLength + Math.max(32, hoseInnerDiameter * 5), "hoseLength");
|
|
57405
|
-
const clampWidth = requirePositive$6(options.clampWidth ?? Math.max(4, hoseOuterDiameter * 0.45), "clampWidth");
|
|
57406
|
-
const clampThickness = requirePositive$6(options.clampThickness ?? 0.9, "clampThickness");
|
|
57407
|
-
const segments = options.segments ?? 40;
|
|
57408
|
-
if (!Number.isInteger(barbCount) || barbCount < 1 || barbCount > 8) {
|
|
57409
|
-
throw new Error("hoseBarbPortAssembly: barbCount must be an integer from 1 to 8");
|
|
57410
|
-
}
|
|
57411
|
-
if (barbPeakDiameter <= hoseInnerDiameter) {
|
|
57412
|
-
throw new Error("hoseBarbPortAssembly: barbPeakDiameter must exceed hoseInnerDiameter so the barb retains the hose");
|
|
57413
|
-
}
|
|
57414
|
-
if (barbRootDiameter >= barbPeakDiameter - Math.max(0.25, hoseInnerDiameter * 0.04)) {
|
|
57415
|
-
throw new Error("hoseBarbPortAssembly: barbRootDiameter must leave a visible barb rise");
|
|
57416
|
-
}
|
|
57417
|
-
if (fluidBoreDiameter >= barbRootDiameter - Math.max(0.8, hoseInnerDiameter * 0.12)) {
|
|
57418
|
-
throw new Error("hoseBarbPortAssembly: fluidBoreDiameter leaves too little wall in the barb fitting");
|
|
57419
|
-
}
|
|
57420
|
-
if (hoseOuterDiameter <= installedHoseBoreDiameter + Math.max(1.2, hoseInnerDiameter * 0.16)) {
|
|
57421
|
-
throw new Error("hoseBarbPortAssembly: hoseOuterDiameter leaves too little hose wall around the installed barb envelope");
|
|
57422
|
-
}
|
|
57423
|
-
if (shoulderDiameter <= barbPeakDiameter + Math.max(1.5, hoseInnerDiameter * 0.2)) {
|
|
57424
|
-
throw new Error("hoseBarbPortAssembly: shoulderDiameter must be larger than the barb peaks");
|
|
57425
|
-
}
|
|
57426
|
-
if (bossDiameter <= shoulderDiameter + Math.max(1.5, hoseInnerDiameter * 0.2)) {
|
|
57427
|
-
throw new Error("hoseBarbPortAssembly: bossDiameter must leave material around the shoulder seat");
|
|
57428
|
-
}
|
|
57429
|
-
if (blockWidth <= bossDiameter + 8 || blockHeight <= bossDiameter + 8) {
|
|
57430
|
-
throw new Error("hoseBarbPortAssembly: receiver block dimensions leave too little material around the port boss");
|
|
57431
|
-
}
|
|
57432
|
-
const portBoreDiameter = barbRootDiameter + runningClearance * 2;
|
|
57433
|
-
const portBore = cylinderAlongX(blockThickness + bossHeight + 0.8, portBoreDiameter / 2, bossHeight / 2, segments);
|
|
57434
|
-
const fuseOverlap = Math.min(0.04, faceClearance * 0.7);
|
|
57435
|
-
const bossCenterX = blockThickness / 2 + bossHeight / 2 - fuseOverlap;
|
|
57436
|
-
const receiver = union(
|
|
57437
|
-
box(blockThickness, blockWidth, blockHeight).translate(0, 0, -blockHeight / 2),
|
|
57438
|
-
cylinderAlongX(bossHeight + fuseOverlap, bossDiameter / 2, bossCenterX, segments)
|
|
57439
|
-
).subtract(portBore).color("#475569");
|
|
57440
|
-
const bossFaceX = blockThickness / 2 + bossHeight;
|
|
57441
|
-
const shoulderCenterX = bossFaceX + faceClearance + shoulderThickness / 2;
|
|
57442
|
-
const barbStartX = shoulderCenterX + shoulderThickness / 2;
|
|
57443
|
-
const fittingStartX = -blockThickness / 2 - runningClearance;
|
|
57444
|
-
const fittingEndX = barbStartX + barbStackLength;
|
|
57445
|
-
const fittingCore = tubeAlongX(
|
|
57446
|
-
fittingEndX - fittingStartX,
|
|
57447
|
-
barbRootDiameter / 2,
|
|
57448
|
-
fluidBoreDiameter / 2,
|
|
57449
|
-
(fittingStartX + fittingEndX) / 2,
|
|
57450
|
-
segments
|
|
57451
|
-
);
|
|
57452
|
-
const shoulder = tubeAlongX(shoulderThickness, shoulderDiameter / 2, fluidBoreDiameter / 2, shoulderCenterX, segments);
|
|
57453
|
-
const barbSolids = [];
|
|
57454
|
-
const ridgeLength = Math.max(0.8, Math.min(barbLength * 0.45, hoseInnerDiameter * 0.28));
|
|
57455
|
-
for (let index2 = 0; index2 < barbCount; index2 += 1) {
|
|
57456
|
-
const startX = barbStartX + index2 * barbLength;
|
|
57457
|
-
const ridgeCenterX = startX + barbLength - ridgeLength / 2;
|
|
57458
|
-
barbSolids.push(tubeAlongX(ridgeLength, barbPeakDiameter / 2, fluidBoreDiameter / 2, ridgeCenterX, segments));
|
|
57459
|
-
}
|
|
57460
|
-
const fitting = union(fittingCore, shoulder, ...barbSolids).color("#94a3b8");
|
|
57461
|
-
const hoseStartX = barbStartX + faceClearance;
|
|
57462
|
-
const hoseCenterX = hoseStartX + hoseLength / 2;
|
|
57463
|
-
const installedHoseBore = cylinderAlongX(hoseLength + 0.8, installedHoseBoreDiameter / 2, hoseCenterX, segments);
|
|
57464
|
-
const hose = tubeAlongX(hoseLength, hoseOuterDiameter / 2, installedHoseBoreDiameter / 2, hoseCenterX, segments).color("#111827");
|
|
57465
|
-
const clampCenterX = barbStartX + Math.min(barbStackLength * 0.55, Math.max(barbLength, clampWidth));
|
|
57466
|
-
const clamp2 = tubeAlongX(
|
|
57467
|
-
clampWidth,
|
|
57468
|
-
hoseOuterDiameter / 2 + clampThickness,
|
|
57469
|
-
hoseOuterDiameter / 2 + Math.min(0.08, runningClearance * 0.45),
|
|
57470
|
-
clampCenterX,
|
|
57471
|
-
segments
|
|
57472
|
-
).color("#cbd5e1");
|
|
57473
|
-
const parts = [
|
|
57474
|
-
{ name: "bored pump or filter body with raised hose-port boss", shape: receiver },
|
|
57475
|
-
{ name: "hollow hose barb fitting with shoulder and retention ridges", shape: fitting },
|
|
57476
|
-
{ name: "installed flexible hose over barb tail", shape: hose },
|
|
57477
|
-
{ name: "clamp band over hose and barb ridges", shape: clamp2 }
|
|
57478
|
-
];
|
|
57479
|
-
return {
|
|
57480
|
-
parts,
|
|
57481
|
-
receiver,
|
|
57482
|
-
fitting,
|
|
57483
|
-
hose,
|
|
57484
|
-
clamp: clamp2,
|
|
57485
|
-
cutters: {
|
|
57486
|
-
portBore,
|
|
57487
|
-
installedHoseBore
|
|
57488
|
-
},
|
|
57489
|
-
dims: {
|
|
57490
|
-
hoseInnerDiameter,
|
|
57491
|
-
hoseOuterDiameter,
|
|
57492
|
-
installedHoseBoreDiameter,
|
|
57493
|
-
blockThickness,
|
|
57494
|
-
blockWidth,
|
|
57495
|
-
blockHeight,
|
|
57496
|
-
bossDiameter,
|
|
57497
|
-
bossHeight,
|
|
57498
|
-
fluidBoreDiameter,
|
|
57499
|
-
barbRootDiameter,
|
|
57500
|
-
barbPeakDiameter,
|
|
57501
|
-
barbCount,
|
|
57502
|
-
barbLength,
|
|
57503
|
-
barbStackLength,
|
|
57504
|
-
shoulderDiameter,
|
|
57505
|
-
shoulderThickness,
|
|
57506
|
-
hoseLength,
|
|
57507
|
-
clampWidth,
|
|
57508
|
-
clampThickness,
|
|
57509
|
-
runningClearance,
|
|
57510
|
-
faceClearance
|
|
57511
|
-
}
|
|
57512
|
-
};
|
|
57513
|
-
}
|
|
57514
|
-
function routedTubeClipAssembly(options) {
|
|
57515
|
-
const tubeDiameter = requirePositive$6(options.tubeDiameter, "tubeDiameter");
|
|
57516
|
-
const tubeLength = requirePositive$6(options.tubeLength ?? 120, "tubeLength");
|
|
57517
|
-
const panelThickness = requirePositive$6(options.panelThickness ?? 3, "panelThickness");
|
|
57518
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.35, "runningClearance");
|
|
57519
|
-
const screwSize = options.screwSize ?? "M3";
|
|
57520
|
-
const segments = options.segments ?? 32;
|
|
57521
|
-
const sizeData = METRIC_HOLE_TABLE[screwSize];
|
|
57522
|
-
if (!sizeData) throw new Error(`routedTubeClipAssembly: unsupported screwSize "${screwSize}"`);
|
|
57523
|
-
const clipCount = options.clipCount ?? 3;
|
|
57524
|
-
if (!Number.isInteger(clipCount) || clipCount < 1 || clipCount > 8) {
|
|
57525
|
-
throw new Error("routedTubeClipAssembly: clipCount must be an integer from 1 to 8");
|
|
57526
|
-
}
|
|
57527
|
-
const screwDiameter = parseFloat(screwSize.replace("M", ""));
|
|
57528
|
-
const screwHeadDiameter = sizeData.head;
|
|
57529
|
-
const tubeBoreDiameter = tubeDiameter + runningClearance * 2;
|
|
57530
|
-
const clipWallThickness = requirePositive$6(
|
|
57531
|
-
options.clipWallThickness ?? Math.max(screwHeadDiameter + 1.2, tubeDiameter * 0.45, 5),
|
|
57532
|
-
"clipWallThickness"
|
|
57533
|
-
);
|
|
57534
|
-
const clipWidth = requirePositive$6(options.clipWidth ?? Math.max(screwHeadDiameter + 3, tubeDiameter * 1.4, 10), "clipWidth");
|
|
57535
|
-
const clipDepth = tubeBoreDiameter + clipWallThickness * 2;
|
|
57536
|
-
const bottomWall = Math.max(1.2, clipWallThickness * 0.35);
|
|
57537
|
-
const topWall = Math.max(2, clipWallThickness * 0.45);
|
|
57538
|
-
const clipHeight = bottomWall + tubeBoreDiameter + topWall;
|
|
57539
|
-
const tubeCenterZ = panelThickness + bottomWall + tubeBoreDiameter / 2;
|
|
57540
|
-
const panelLength = requirePositive$6(options.panelLength ?? tubeLength + 24, "panelLength");
|
|
57541
|
-
const panelWidth = requirePositive$6(options.panelWidth ?? clipDepth + Math.max(14, screwHeadDiameter * 2), "panelWidth");
|
|
57542
|
-
if (tubeLength <= clipWidth + 8) {
|
|
57543
|
-
throw new Error("routedTubeClipAssembly: tubeLength must leave visible tube beyond the clip body");
|
|
57544
|
-
}
|
|
57545
|
-
const defaultSpacing = clipCount === 1 ? 0 : Math.max(clipWidth + 8, (tubeLength - clipWidth * 2) / (clipCount - 1));
|
|
57546
|
-
const clipSpacing = options.clipSpacing === void 0 ? defaultSpacing : requirePositive$6(options.clipSpacing, "clipSpacing");
|
|
57547
|
-
const clipCenters = Array.from({ length: clipCount }, (_2, index2) => (index2 - (clipCount - 1) / 2) * clipSpacing);
|
|
57548
|
-
const maxClipExtent = Math.max(...clipCenters.map((x2) => Math.abs(x2) + clipWidth / 2));
|
|
57549
|
-
if (maxClipExtent > tubeLength / 2 - 2) {
|
|
57550
|
-
throw new Error("routedTubeClipAssembly: clipSpacing places a clip beyond the routed tube length");
|
|
57551
|
-
}
|
|
57552
|
-
if (maxClipExtent > panelLength / 2 - 2) {
|
|
57553
|
-
throw new Error("routedTubeClipAssembly: panelLength is too short for the clip pattern");
|
|
57554
|
-
}
|
|
57555
|
-
const boreRadius = tubeBoreDiameter / 2;
|
|
57556
|
-
const screwY = boreRadius + clipWallThickness / 2;
|
|
57557
|
-
if (screwY + screwHeadDiameter / 2 > clipDepth / 2 - 0.2) {
|
|
57558
|
-
throw new Error("routedTubeClipAssembly: clipWallThickness leaves too little land for screw heads");
|
|
57559
|
-
}
|
|
57560
|
-
if (clipDepth > panelWidth - Math.max(4, screwHeadDiameter * 0.5)) {
|
|
57561
|
-
throw new Error("routedTubeClipAssembly: panelWidth leaves too little material beside the clips");
|
|
57562
|
-
}
|
|
57563
|
-
const screwPositions = clipCenters.flatMap((x2) => [[x2, -screwY], [x2, screwY]]);
|
|
57564
|
-
const screwClearanceDiameter = Math.max(sizeData.loose, screwDiameter + 0.8);
|
|
57565
|
-
const panelThreadEnvelopeDiameter = screwClearanceDiameter;
|
|
57566
|
-
const clipTopZ = panelThickness + clipHeight;
|
|
57567
|
-
const clipTubeBores = union(
|
|
57568
|
-
...clipCenters.map((x2) => cylinderAlongX(clipWidth + 0.8, boreRadius, x2, segments).translate(0, 0, tubeCenterZ))
|
|
57569
|
-
);
|
|
57570
|
-
const clipScrewClearances = union(
|
|
57571
|
-
...screwPositions.map(
|
|
57572
|
-
([x2, y2]) => cylinder(clipHeight + 0.8, screwClearanceDiameter / 2, void 0, segments).translate(x2, y2, panelThickness - 0.4)
|
|
57573
|
-
)
|
|
57574
|
-
);
|
|
57575
|
-
const panelThreadEnvelopes = union(
|
|
57576
|
-
...screwPositions.map(
|
|
57577
|
-
([x2, y2]) => cylinder(panelThickness + 0.8, panelThreadEnvelopeDiameter / 2, void 0, segments).translate(x2, y2, -0.4)
|
|
57578
|
-
)
|
|
57579
|
-
);
|
|
57580
|
-
const panel = box(panelLength, panelWidth, panelThickness).subtract(panelThreadEnvelopes).color("#475569");
|
|
57581
|
-
const tube2 = cylinderAlongX(tubeLength, tubeDiameter / 2, 0, segments).translate(0, 0, tubeCenterZ).color("#0f172a");
|
|
57582
|
-
const clips = clipCenters.map((x2) => {
|
|
57583
|
-
const body = box(clipWidth, clipDepth, clipHeight).translate(x2, 0, panelThickness);
|
|
57584
|
-
const tubeBore = cylinderAlongX(clipWidth + 0.8, boreRadius, x2, segments).translate(0, 0, tubeCenterZ);
|
|
57585
|
-
const screwHoles = union(
|
|
57586
|
-
cylinder(clipHeight + 0.8, screwClearanceDiameter / 2, void 0, segments).translate(x2, -screwY, panelThickness - 0.4),
|
|
57587
|
-
cylinder(clipHeight + 0.8, screwClearanceDiameter / 2, void 0, segments).translate(x2, screwY, panelThickness - 0.4)
|
|
57588
|
-
);
|
|
57589
|
-
return body.subtract(tubeBore).subtract(screwHoles).color("#94a3b8");
|
|
57590
|
-
});
|
|
57591
|
-
const screwLength = clipHeight + panelThickness * 0.65;
|
|
57592
|
-
const screwHeadHeight = Math.max(1.2, screwDiameter * 0.55);
|
|
57593
|
-
const screwBlank = union(
|
|
57594
|
-
cylinder(screwLength, screwDiameter / 2, void 0, segments).translate(0, 0, clipTopZ - screwLength),
|
|
57595
|
-
cylinder(screwHeadHeight, screwHeadDiameter / 2, void 0, segments).translate(0, 0, clipTopZ)
|
|
57596
|
-
).color("#cbd5e1");
|
|
57597
|
-
const screws = screwPositions.map(([x2, y2]) => screwBlank.translate(x2, y2, 0));
|
|
57598
|
-
const parts = [
|
|
57599
|
-
{ name: "panel with tube-clip screw receiving holes", shape: panel },
|
|
57600
|
-
{ name: "routed flexible tube through retained clip bores", shape: tube2 },
|
|
57601
|
-
...clips.map((shape, index2) => ({ name: `saddle tube clip ${index2 + 1} with through-bore`, shape })),
|
|
57602
|
-
...screws.map((shape, index2) => ({ name: `installed ${screwSize} tube clip screw ${index2 + 1}`, shape }))
|
|
57603
|
-
];
|
|
57604
|
-
return {
|
|
57605
|
-
parts,
|
|
57606
|
-
panel,
|
|
57607
|
-
tube: tube2,
|
|
57608
|
-
clips,
|
|
57609
|
-
screws,
|
|
57610
|
-
clipCenters,
|
|
57611
|
-
screwPositions,
|
|
57612
|
-
cutters: {
|
|
57613
|
-
clipTubeBores,
|
|
57614
|
-
clipScrewClearances,
|
|
57615
|
-
panelThreadEnvelopes
|
|
57616
|
-
},
|
|
57617
|
-
dims: {
|
|
57618
|
-
tubeDiameter,
|
|
57619
|
-
tubeLength,
|
|
57620
|
-
tubeBoreDiameter,
|
|
57621
|
-
panelLength,
|
|
57622
|
-
panelWidth,
|
|
57623
|
-
panelThickness,
|
|
57624
|
-
clipCount,
|
|
57625
|
-
clipWidth,
|
|
57626
|
-
clipDepth,
|
|
57627
|
-
clipHeight,
|
|
57628
|
-
clipWallThickness,
|
|
57629
|
-
tubeCenterZ,
|
|
57630
|
-
screwSize,
|
|
57631
|
-
screwDiameter,
|
|
57632
|
-
screwHeadDiameter,
|
|
57633
|
-
screwLength,
|
|
57634
|
-
screwClearanceDiameter,
|
|
57635
|
-
panelThreadEnvelopeDiameter,
|
|
57636
|
-
runningClearance
|
|
57637
|
-
}
|
|
57638
|
-
};
|
|
57639
|
-
}
|
|
57640
|
-
function pcbTerminalBlockAssembly(options = {}) {
|
|
57641
|
-
const terminalCount = options.terminalCount ?? 4;
|
|
57642
|
-
if (!Number.isInteger(terminalCount) || terminalCount < 1 || terminalCount > 24) {
|
|
57643
|
-
throw new Error("pcbTerminalBlockAssembly: terminalCount must be an integer from 1 to 24");
|
|
57644
|
-
}
|
|
57645
|
-
const terminalPitch = requirePositive$6(options.terminalPitch ?? 5.08, "terminalPitch");
|
|
57646
|
-
const terminalBlockWidth = terminalPitch * terminalCount + 3;
|
|
57647
|
-
const boardWidth = requirePositive$6(options.boardWidth ?? Math.max(50, terminalBlockWidth + 28), "boardWidth");
|
|
57648
|
-
const boardDepth = requirePositive$6(options.boardDepth ?? 38, "boardDepth");
|
|
57649
|
-
const boardThickness = requirePositive$6(options.boardThickness ?? 1.6, "boardThickness");
|
|
57650
|
-
const backplateThickness = requirePositive$6(options.backplateThickness ?? 3, "backplateThickness");
|
|
57651
|
-
const backplateMargin = requirePositive$6(options.backplateMargin ?? 5, "backplateMargin");
|
|
57652
|
-
const standoffHeight = requirePositive$6(options.standoffHeight ?? 6, "standoffHeight");
|
|
57653
|
-
const screwSize = options.screwSize ?? "M3";
|
|
57654
|
-
const segments = options.segments ?? 28;
|
|
57655
|
-
const sizeData = METRIC_HOLE_TABLE[screwSize];
|
|
57656
|
-
if (!sizeData) throw new Error(`pcbTerminalBlockAssembly: unsupported screwSize "${screwSize}"`);
|
|
57657
|
-
const screwDiameter = parseFloat(screwSize.replace("M", ""));
|
|
57658
|
-
const screwHeadDiameter = sizeData.head;
|
|
57659
|
-
const screwHeadHeight = Math.max(1.2, screwDiameter * 0.55);
|
|
57660
|
-
const standoffDiameter = requirePositive$6(
|
|
57661
|
-
options.standoffDiameter ?? Math.max(screwHeadDiameter * 1.45, sizeData.normal + 3),
|
|
57662
|
-
"standoffDiameter"
|
|
57663
|
-
);
|
|
57664
|
-
const [mountInsetX, mountInsetY] = resolveBoltInset(
|
|
57665
|
-
options.mountingInset,
|
|
57666
|
-
Math.max(standoffDiameter / 2 + 1.2, screwHeadDiameter * 0.75)
|
|
57667
|
-
);
|
|
57668
|
-
if (mountInsetX * 2 >= boardWidth || mountInsetY * 2 >= boardDepth) {
|
|
57669
|
-
throw new Error("pcbTerminalBlockAssembly: mountingInset leaves no room for the PCB mounting pattern");
|
|
57670
|
-
}
|
|
57671
|
-
const terminalBlockDepth = requirePositive$6(options.terminalBlockDepth ?? 10, "terminalBlockDepth");
|
|
57672
|
-
const terminalBlockHeight = requirePositive$6(options.terminalBlockHeight ?? 9, "terminalBlockHeight");
|
|
57673
|
-
const terminalEdgeInset = requirePositive$6(options.terminalEdgeInset ?? 5, "terminalEdgeInset");
|
|
57674
|
-
const pinDiameter = requirePositive$6(options.pinDiameter ?? 0.9, "pinDiameter");
|
|
57675
|
-
const pinClearance = requirePositive$6(options.pinClearance ?? 0.25, "pinClearance");
|
|
57676
|
-
const pinTailLength = requireNonNegative(options.pinTailLength ?? 0, "pinTailLength");
|
|
57677
|
-
const wirePortDiameter = requirePositive$6(options.wirePortDiameter ?? 2.6, "wirePortDiameter");
|
|
57678
|
-
const pinHoleDiameter = pinDiameter + pinClearance;
|
|
57679
|
-
const terminalCenterY = -boardDepth / 2 + terminalEdgeInset + terminalBlockDepth / 2;
|
|
57680
|
-
const pinY = terminalCenterY + terminalBlockDepth * 0.24;
|
|
57681
|
-
const firstPinX = -((terminalCount - 1) * terminalPitch) / 2;
|
|
57682
|
-
const pinPositions = Array.from({ length: terminalCount }, (_2, index2) => [firstPinX + index2 * terminalPitch, pinY]);
|
|
57683
|
-
const mountingPositions = [
|
|
57684
|
-
[-boardWidth / 2 + mountInsetX, -boardDepth / 2 + mountInsetY],
|
|
57685
|
-
[boardWidth / 2 - mountInsetX, -boardDepth / 2 + mountInsetY],
|
|
57686
|
-
[-boardWidth / 2 + mountInsetX, boardDepth / 2 - mountInsetY],
|
|
57687
|
-
[boardWidth / 2 - mountInsetX, boardDepth / 2 - mountInsetY]
|
|
57688
|
-
];
|
|
57689
|
-
if (terminalBlockWidth >= boardWidth - mountInsetX * 2) {
|
|
57690
|
-
throw new Error("pcbTerminalBlockAssembly: terminal block is too wide for the PCB mounting pattern");
|
|
57691
|
-
}
|
|
57692
|
-
if (terminalEdgeInset + terminalBlockDepth >= boardDepth - mountInsetY * 2) {
|
|
57693
|
-
throw new Error("pcbTerminalBlockAssembly: terminal block depth collides with the rear mounting datum");
|
|
57694
|
-
}
|
|
57695
|
-
if (pinHoleDiameter >= terminalPitch * 0.55) {
|
|
57696
|
-
throw new Error("pcbTerminalBlockAssembly: pinDiameter and pinClearance leave too little PCB web between terminal holes");
|
|
57697
|
-
}
|
|
57698
|
-
if (wirePortDiameter >= Math.min(terminalPitch * 0.72, terminalBlockHeight * 0.65)) {
|
|
57699
|
-
throw new Error("pcbTerminalBlockAssembly: wirePortDiameter is too large for the terminal pitch or body height");
|
|
57700
|
-
}
|
|
57701
|
-
for (const [index2, [x2, y2]] of [...mountingPositions, ...pinPositions].entries()) {
|
|
57702
|
-
if (!Number.isFinite(x2) || !Number.isFinite(y2)) {
|
|
57703
|
-
throw new Error(`pcbTerminalBlockAssembly: generated datum position ${index2} is not finite`);
|
|
57704
|
-
}
|
|
57705
|
-
}
|
|
57706
|
-
const backplateWidth = boardWidth + backplateMargin * 2;
|
|
57707
|
-
const backplateDepth = boardDepth + backplateMargin * 2;
|
|
57708
|
-
const boardBottomZ = backplateThickness + standoffHeight;
|
|
57709
|
-
const boardTopZ = boardBottomZ + boardThickness;
|
|
57710
|
-
const standoffOverlap = Math.min(0.08, standoffHeight * 0.03);
|
|
57711
|
-
const standoffThreadEnvelopeDiameter = Math.max(sizeData.loose, screwDiameter + 1);
|
|
57712
|
-
const standoffThreadEnvelope = cylinder(standoffHeight + 0.8, standoffThreadEnvelopeDiameter / 2, void 0, segments).translate(
|
|
57713
|
-
0,
|
|
57714
|
-
0,
|
|
57715
|
-
backplateThickness - 0.4
|
|
57716
|
-
);
|
|
57717
|
-
const standoffThreadEnvelopes = union(...mountingPositions.map(([x2, y2]) => standoffThreadEnvelope.translate(x2, y2, 0)));
|
|
57718
|
-
const standoff = cylinder(standoffHeight + standoffOverlap, standoffDiameter / 2, void 0, segments).translate(0, 0, backplateThickness - standoffOverlap).subtract(standoffThreadEnvelope);
|
|
57719
|
-
const standoffs = union(...mountingPositions.map(([x2, y2]) => standoff.translate(x2, y2, 0)));
|
|
57720
|
-
const backplate = union(box(backplateWidth, backplateDepth, backplateThickness), standoffs).color("#475569");
|
|
57721
|
-
const boardMountingHoleDiameter = sizeData.normal;
|
|
57722
|
-
const boardMountHole = cylinder(boardThickness + 0.8, boardMountingHoleDiameter / 2, void 0, segments).translate(
|
|
57723
|
-
0,
|
|
57724
|
-
0,
|
|
57725
|
-
boardBottomZ - 0.4
|
|
57726
|
-
);
|
|
57727
|
-
const pcbMountingHoles = union(...mountingPositions.map(([x2, y2]) => boardMountHole.translate(x2, y2, 0)));
|
|
57728
|
-
const pinHole = cylinder(boardThickness + 0.8, pinHoleDiameter / 2, void 0, segments).translate(0, 0, boardBottomZ - 0.4);
|
|
57729
|
-
const pcbPinHoles = union(...pinPositions.map(([x2, y2]) => pinHole.translate(x2, y2, 0)));
|
|
57730
|
-
const pcb = box(boardWidth, boardDepth, boardThickness).translate(0, 0, boardBottomZ).subtract(pcbMountingHoles).subtract(pcbPinHoles).color("#166534");
|
|
57731
|
-
const terminalBodyBlank = box(terminalBlockWidth, terminalBlockDepth, terminalBlockHeight).translate(0, terminalCenterY, boardTopZ);
|
|
57732
|
-
const wirePort = cylinderAlongY(terminalBlockDepth + 0.8, wirePortDiameter / 2, terminalCenterY, segments).translate(
|
|
57733
|
-
0,
|
|
57734
|
-
0,
|
|
57735
|
-
boardTopZ + terminalBlockHeight * 0.42
|
|
57736
|
-
);
|
|
57737
|
-
const wirePorts = union(...pinPositions.map(([x2]) => wirePort.translate(x2, 0, 0)));
|
|
57738
|
-
const clampScrewPockets = union(
|
|
57739
|
-
...pinPositions.map(
|
|
57740
|
-
([x2]) => cylinder(
|
|
57741
|
-
Math.max(0.6, terminalBlockHeight * 0.22),
|
|
57742
|
-
Math.min(terminalPitch * 0.22, wirePortDiameter * 0.42),
|
|
57743
|
-
void 0,
|
|
57744
|
-
segments
|
|
57745
|
-
).translate(x2, terminalCenterY + terminalBlockDepth * 0.12, boardTopZ + terminalBlockHeight * 0.76)
|
|
57746
|
-
)
|
|
57747
|
-
);
|
|
57748
|
-
const pinLength = boardThickness + pinTailLength + Math.min(0.6, terminalBlockHeight * 0.08);
|
|
57749
|
-
const pinStartZ = boardBottomZ - pinTailLength;
|
|
57750
|
-
const pins = union(...pinPositions.map(([x2, y2]) => cylinder(pinLength, pinDiameter / 2, void 0, segments).translate(x2, y2, pinStartZ)));
|
|
57751
|
-
const terminalBlock = union(terminalBodyBlank.subtract(wirePorts).subtract(clampScrewPockets), pins).color("#16a34a");
|
|
57752
|
-
const screwShaftLength = boardThickness + standoffHeight * 0.85;
|
|
57753
|
-
const mountingHardware = fastenerSet(screwSize, screwShaftLength, {
|
|
57754
|
-
washerUnderHead: false,
|
|
57755
|
-
washerUnderNut: false,
|
|
57756
|
-
fit: "normal",
|
|
57757
|
-
segments
|
|
57758
|
-
});
|
|
57759
|
-
const screws = mountingPositions.map(([x2, y2]) => mountingHardware.bolt.translate(x2, y2, boardTopZ).color("#cbd5e1"));
|
|
57760
|
-
const parts = [
|
|
57761
|
-
{ name: "electronics backplate with fused PCB standoffs", shape: backplate },
|
|
57762
|
-
{ name: "PCB with mounting holes and terminal pin clearances", shape: pcb },
|
|
57763
|
-
{ name: "seated purchased terminal block with through-board pins", shape: terminalBlock },
|
|
57764
|
-
...screws.map((shape, index2) => ({ name: `installed ${screwSize} PCB mounting screw ${index2 + 1}`, shape }))
|
|
57765
|
-
];
|
|
57766
|
-
return {
|
|
57767
|
-
parts,
|
|
57768
|
-
backplate,
|
|
57769
|
-
pcb,
|
|
57770
|
-
terminalBlock,
|
|
57771
|
-
screws,
|
|
57772
|
-
mountingPositions,
|
|
57773
|
-
pinPositions,
|
|
57774
|
-
cutters: {
|
|
57775
|
-
pcbMountingHoles,
|
|
57776
|
-
pcbPinHoles,
|
|
57777
|
-
standoffThreadEnvelopes
|
|
57778
|
-
},
|
|
57779
|
-
dims: {
|
|
57780
|
-
terminalCount,
|
|
57781
|
-
terminalPitch,
|
|
57782
|
-
boardWidth,
|
|
57783
|
-
boardDepth,
|
|
57784
|
-
boardThickness,
|
|
57785
|
-
backplateWidth,
|
|
57786
|
-
backplateDepth,
|
|
57787
|
-
backplateThickness,
|
|
57788
|
-
standoffHeight,
|
|
57789
|
-
standoffDiameter,
|
|
57790
|
-
screwSize,
|
|
57791
|
-
screwDiameter,
|
|
57792
|
-
screwHeadDiameter,
|
|
57793
|
-
screwHeadHeight,
|
|
57794
|
-
screwShaftLength,
|
|
57795
|
-
boardMountingHoleDiameter,
|
|
57796
|
-
standoffThreadEnvelopeDiameter,
|
|
57797
|
-
terminalBlockWidth,
|
|
57798
|
-
terminalBlockDepth,
|
|
57799
|
-
terminalBlockHeight,
|
|
57800
|
-
terminalEdgeInset,
|
|
57801
|
-
pinDiameter,
|
|
57802
|
-
pinClearance,
|
|
57803
|
-
pinHoleDiameter,
|
|
57804
|
-
pinTailLength,
|
|
57805
|
-
wirePortDiameter
|
|
57806
|
-
}
|
|
57807
|
-
};
|
|
57808
|
-
}
|
|
57809
|
-
function thumbScrewClampAssembly(options = {}) {
|
|
57810
|
-
const screwSize = options.screwSize ?? "M6";
|
|
57811
|
-
const segments = options.segments ?? 36;
|
|
57812
|
-
const sizeData = METRIC_HOLE_TABLE[screwSize];
|
|
57813
|
-
if (!sizeData) throw new Error(`thumbScrewClampAssembly: unsupported screwSize "${screwSize}"`);
|
|
57814
|
-
const screwDiameter = parseFloat(screwSize.replace("M", ""));
|
|
57815
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.35, "runningClearance");
|
|
57816
|
-
const faceClearance = requireNonNegative(options.faceClearance ?? 0, "faceClearance");
|
|
57817
|
-
const threadEnvelopeDiameter = Math.max(sizeData.normal, screwDiameter + runningClearance * 2);
|
|
57818
|
-
const pressurePadDiameter = requirePositive$6(options.pressurePadDiameter ?? Math.max(screwDiameter * 3.2, 18), "pressurePadDiameter");
|
|
57819
|
-
const pressurePadThickness = requirePositive$6(options.pressurePadThickness ?? Math.max(screwDiameter * 0.72, 4), "pressurePadThickness");
|
|
57820
|
-
const knobDiameter = requirePositive$6(options.knobDiameter ?? Math.max(screwDiameter * 4.2, 24), "knobDiameter");
|
|
57821
|
-
const knobThickness = requirePositive$6(options.knobThickness ?? Math.max(screwDiameter * 0.9, 7), "knobThickness");
|
|
57822
|
-
const workpieceThickness = requirePositive$6(options.workpieceThickness ?? 18, "workpieceThickness");
|
|
57823
|
-
const workpieceDepth = requirePositive$6(options.workpieceDepth ?? Math.max(46, pressurePadDiameter * 1.5), "workpieceDepth");
|
|
57824
|
-
const workpieceHeight = requirePositive$6(options.workpieceHeight ?? Math.max(pressurePadDiameter * 1.35, 24), "workpieceHeight");
|
|
57825
|
-
const frameDepth = requirePositive$6(options.frameDepth ?? Math.max(workpieceDepth + 12, pressurePadDiameter + 16), "frameDepth");
|
|
57826
|
-
const baseThickness = requirePositive$6(options.baseThickness ?? Math.max(screwDiameter, 6), "baseThickness");
|
|
57827
|
-
const jawThickness = requirePositive$6(options.jawThickness ?? Math.max(screwDiameter * 1.35, 9), "jawThickness");
|
|
57828
|
-
const supportThickness = requirePositive$6(options.supportThickness ?? Math.max(screwDiameter * 1.8, 12), "supportThickness");
|
|
57829
|
-
const bossLength = requirePositive$6(options.bossLength ?? Math.max(screwDiameter * 1.1, 8), "bossLength");
|
|
57830
|
-
const bossDiameter = requirePositive$6(options.bossDiameter ?? Math.max(threadEnvelopeDiameter + 5, screwDiameter * 2.5), "bossDiameter");
|
|
57831
|
-
const exposedScrewLength = requirePositive$6(
|
|
57832
|
-
options.exposedScrewLength ?? Math.max(pressurePadDiameter * 0.45, screwDiameter * 2.2),
|
|
57833
|
-
"exposedScrewLength"
|
|
57834
|
-
);
|
|
57835
|
-
const screwCenterZ = baseThickness + Math.max(workpieceHeight * 0.52, pressurePadDiameter * 0.68);
|
|
57836
|
-
const frameHeight = requirePositive$6(
|
|
57837
|
-
options.frameHeight ?? screwCenterZ - baseThickness + pressurePadDiameter / 2 + Math.max(baseThickness, 7),
|
|
57838
|
-
"frameHeight"
|
|
57839
|
-
);
|
|
57840
|
-
if (workpieceDepth > frameDepth - 6) {
|
|
57841
|
-
throw new Error("thumbScrewClampAssembly: frameDepth must leave side material around the clamped workpiece");
|
|
57842
|
-
}
|
|
57843
|
-
if (pressurePadDiameter > frameDepth - 4) {
|
|
57844
|
-
throw new Error("thumbScrewClampAssembly: pressurePadDiameter is too large for the frame depth");
|
|
57845
|
-
}
|
|
57846
|
-
if (bossDiameter > frameDepth - 4) {
|
|
57847
|
-
throw new Error("thumbScrewClampAssembly: bossDiameter is too large for the frame depth");
|
|
57848
|
-
}
|
|
57849
|
-
if (screwCenterZ - pressurePadDiameter / 2 <= baseThickness + 0.5) {
|
|
57850
|
-
throw new Error("thumbScrewClampAssembly: pressure pad collides with the base bridge");
|
|
57851
|
-
}
|
|
57852
|
-
if (baseThickness + frameHeight - screwCenterZ <= pressurePadDiameter / 2 + 2) {
|
|
57853
|
-
throw new Error("thumbScrewClampAssembly: frameHeight leaves too little material above the screw axis");
|
|
57854
|
-
}
|
|
57855
|
-
if (threadEnvelopeDiameter + 4 > Math.min(frameDepth, frameHeight)) {
|
|
57856
|
-
throw new Error("thumbScrewClampAssembly: threaded boss bore leaves too little surrounding frame material");
|
|
57857
|
-
}
|
|
57858
|
-
const workpieceLeftFaceX = -workpieceThickness / 2;
|
|
57859
|
-
const workpieceRightFaceX = workpieceThickness / 2;
|
|
57860
|
-
const anvilOverlap = Math.min(0.35, pressurePadThickness * 0.18);
|
|
57861
|
-
const anvilPadCenterX = workpieceLeftFaceX - faceClearance - pressurePadThickness / 2;
|
|
57862
|
-
const pressurePadCenterX = workpieceRightFaceX + faceClearance + pressurePadThickness / 2;
|
|
57863
|
-
const fixedJawRightFaceX = anvilPadCenterX - pressurePadThickness / 2 + anvilOverlap;
|
|
57864
|
-
const fixedJawCenterX = fixedJawRightFaceX - jawThickness / 2;
|
|
57865
|
-
const pressurePadRightFaceX = pressurePadCenterX + pressurePadThickness / 2;
|
|
57866
|
-
const supportInnerFaceX = pressurePadRightFaceX + exposedScrewLength;
|
|
57867
|
-
const supportCenterX = supportInnerFaceX + supportThickness / 2;
|
|
57868
|
-
const supportOuterFaceX = supportInnerFaceX + supportThickness;
|
|
57869
|
-
const frameLeftFaceX = fixedJawCenterX - jawThickness / 2;
|
|
57870
|
-
const frameRightFaceX = supportOuterFaceX;
|
|
57871
|
-
const baseLength = frameRightFaceX - frameLeftFaceX;
|
|
57872
|
-
if (baseLength <= 0 || !Number.isFinite(baseLength)) {
|
|
57873
|
-
throw new Error("thumbScrewClampAssembly: generated clamp frame length is invalid");
|
|
57874
|
-
}
|
|
57875
|
-
const bossCenterX = supportInnerFaceX + (supportThickness + bossLength) / 2;
|
|
57876
|
-
const threadedBossBore = cylinderAlongX(supportThickness + bossLength + 1, threadEnvelopeDiameter / 2, bossCenterX, segments).translate(
|
|
57877
|
-
0,
|
|
57878
|
-
0,
|
|
57879
|
-
screwCenterZ
|
|
57880
|
-
);
|
|
57881
|
-
const frameOverlap = Math.min(0.12, baseThickness * 0.04);
|
|
57882
|
-
const base = box(baseLength, frameDepth, baseThickness).translate((frameLeftFaceX + frameRightFaceX) / 2, 0, 0);
|
|
57883
|
-
const fixedJaw = box(jawThickness, frameDepth, frameHeight + frameOverlap).translate(fixedJawCenterX, 0, baseThickness - frameOverlap);
|
|
57884
|
-
const support = box(supportThickness, frameDepth, frameHeight + frameOverlap).translate(supportCenterX, 0, baseThickness - frameOverlap);
|
|
57885
|
-
const boss2 = cylinderAlongX(supportThickness + bossLength, bossDiameter / 2, bossCenterX, segments).translate(0, 0, screwCenterZ);
|
|
57886
|
-
const anvilPad = cylinderAlongX(pressurePadThickness, pressurePadDiameter / 2, anvilPadCenterX, segments).translate(0, 0, screwCenterZ);
|
|
57887
|
-
const frame = union(base, fixedJaw, support, boss2, anvilPad).subtract(threadedBossBore).color("#475569");
|
|
57888
|
-
const workpieceBottomZ = screwCenterZ - workpieceHeight / 2;
|
|
57889
|
-
const workpiece = box(workpieceThickness, workpieceDepth, workpieceHeight).translate(0, 0, workpieceBottomZ).color("#a16207");
|
|
57890
|
-
const pressurePad = cylinderAlongX(pressurePadThickness, pressurePadDiameter / 2, pressurePadCenterX, segments).translate(
|
|
57891
|
-
0,
|
|
57892
|
-
0,
|
|
57893
|
-
screwCenterZ
|
|
57894
|
-
);
|
|
57895
|
-
const knobCenterX = supportOuterFaceX + bossLength + runningClearance + knobThickness / 2;
|
|
57896
|
-
const knob = cylinderAlongX(knobThickness, knobDiameter / 2, knobCenterX, segments).translate(0, 0, screwCenterZ);
|
|
57897
|
-
const shaftLeftX = pressurePadRightFaceX - Math.min(pressurePadThickness * 0.45, screwDiameter * 0.45);
|
|
57898
|
-
const shaftRightX = knobCenterX + knobThickness / 2;
|
|
57899
|
-
const shaftLength = shaftRightX - shaftLeftX;
|
|
57900
|
-
if (shaftLength <= supportThickness + bossLength) {
|
|
57901
|
-
throw new Error("thumbScrewClampAssembly: generated screw length is too short for the threaded support");
|
|
57902
|
-
}
|
|
57903
|
-
const shaft = cylinderAlongX(shaftLength, screwDiameter / 2, (shaftLeftX + shaftRightX) / 2, segments).translate(0, 0, screwCenterZ);
|
|
57904
|
-
const clampScrew = union(shaft, pressurePad, knob).color("#cbd5e1");
|
|
57905
|
-
const workpieceEnvelope = box(workpieceThickness, workpieceDepth, workpieceHeight).translate(0, 0, workpieceBottomZ);
|
|
57906
|
-
return {
|
|
57907
|
-
parts: [
|
|
57908
|
-
{ name: "thumb-screw clamp frame with fixed anvil and threaded boss", shape: frame },
|
|
57909
|
-
{ name: "representative clamped workpiece between pads", shape: workpiece },
|
|
57910
|
-
{ name: "installed thumb screw with captive pressure pad and hand knob", shape: clampScrew }
|
|
57911
|
-
],
|
|
57912
|
-
frame,
|
|
57913
|
-
workpiece,
|
|
57914
|
-
clampScrew,
|
|
57915
|
-
cutters: {
|
|
57916
|
-
threadedBossBore,
|
|
57917
|
-
workpieceEnvelope
|
|
57918
|
-
},
|
|
57919
|
-
dims: {
|
|
57920
|
-
screwSize,
|
|
57921
|
-
screwDiameter,
|
|
57922
|
-
threadEnvelopeDiameter,
|
|
57923
|
-
workpieceThickness,
|
|
57924
|
-
workpieceDepth,
|
|
57925
|
-
workpieceHeight,
|
|
57926
|
-
frameDepth,
|
|
57927
|
-
frameHeight,
|
|
57928
|
-
baseThickness,
|
|
57929
|
-
jawThickness,
|
|
57930
|
-
supportThickness,
|
|
57931
|
-
bossLength,
|
|
57932
|
-
bossDiameter,
|
|
57933
|
-
exposedScrewLength,
|
|
57934
|
-
pressurePadDiameter,
|
|
57935
|
-
pressurePadThickness,
|
|
57936
|
-
knobDiameter,
|
|
57937
|
-
knobThickness,
|
|
57938
|
-
screwCenterZ,
|
|
57939
|
-
fixedAnvilFaceX: workpieceLeftFaceX - faceClearance,
|
|
57940
|
-
pressurePadFaceX: workpieceRightFaceX + faceClearance,
|
|
57941
|
-
supportInnerFaceX,
|
|
57942
|
-
runningClearance,
|
|
57943
|
-
faceClearance
|
|
57944
|
-
}
|
|
57945
|
-
};
|
|
57946
|
-
}
|
|
57947
57169
|
function fastenerSet(size, boltLength, options) {
|
|
57948
57170
|
const sizeData = METRIC_HOLE_TABLE[size];
|
|
57949
57171
|
if (!sizeData) throw new Error(`fastenerSet: unsupported size "${size}"`);
|
|
@@ -58004,22 +57226,6 @@ const partLibrary = {
|
|
|
58004
57226
|
nut,
|
|
58005
57227
|
washer,
|
|
58006
57228
|
fastenerSet,
|
|
58007
|
-
boltedServiceCover,
|
|
58008
|
-
datumEnclosureAssembly,
|
|
58009
|
-
snapLatchCoverAssembly,
|
|
58010
|
-
pinnedLeverAssembly,
|
|
58011
|
-
retainedShaftAssembly,
|
|
58012
|
-
capturedLinearSlide,
|
|
58013
|
-
capturedCartridgeGuideAssembly,
|
|
58014
|
-
livingHingeCoverAssembly,
|
|
58015
|
-
knuckledHingeAssembly,
|
|
58016
|
-
clevisPinJointAssembly,
|
|
58017
|
-
seatedBearingAssembly,
|
|
58018
|
-
cableGlandAnchorAssembly,
|
|
58019
|
-
hoseBarbPortAssembly,
|
|
58020
|
-
routedTubeClipAssembly,
|
|
58021
|
-
pcbTerminalBlockAssembly,
|
|
58022
|
-
thumbScrewClampAssembly,
|
|
58023
57229
|
pipeRoute,
|
|
58024
57230
|
elbow,
|
|
58025
57231
|
beltDrive,
|
|
@@ -304893,7 +304099,8 @@ function describeKinematicConvergenceError(kinematics) {
|
|
|
304893
304099
|
const edgeResidual = edge ? Math.abs(edge.residual) : 0;
|
|
304894
304100
|
const angleResidual = angle ? Math.abs(angle.residual) : 0;
|
|
304895
304101
|
const worst = edgeResidual >= angleResidual && edge ? `edge "${edge.name}"` : angle ? `angle "${angle.name}"` : "constraint";
|
|
304896
|
-
|
|
304102
|
+
const diagnostic = kinematics.diagnostics.length > 0 ? ` Diagnostic: ${kinematics.diagnostics[0]}` : "";
|
|
304103
|
+
return `Assembly kinematic solve did not converge (max residual ${kinematics.maxResidual.toFixed(6)}, worst ${worst}).${diagnostic} The requested control state is outside the selected mechanism mode or valid range; rejecting best-effort stretched geometry.`;
|
|
304897
304104
|
}
|
|
304898
304105
|
function mapScriptResultToScene(args) {
|
|
304899
304106
|
var _a3;
|