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
|
@@ -1220,7 +1220,7 @@ function requireFiniteVec2(value, label) {
|
|
|
1220
1220
|
}
|
|
1221
1221
|
return [requireFiniteNumber$1(value[0], `${label}[0]`), requireFiniteNumber$1(value[1], `${label}[1]`)];
|
|
1222
1222
|
}
|
|
1223
|
-
function requireFiniteVec3$
|
|
1223
|
+
function requireFiniteVec3$4(value, label) {
|
|
1224
1224
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
1225
1225
|
throw new Error(`${label} must be [x, y, z] with finite numbers, got ${formatValidationValue(value)}`);
|
|
1226
1226
|
}
|
|
@@ -1236,7 +1236,7 @@ function requireNonZeroFiniteVec2(value, label) {
|
|
|
1236
1236
|
return v;
|
|
1237
1237
|
}
|
|
1238
1238
|
function requireNonZeroFiniteVec3(value, label) {
|
|
1239
|
-
const v = requireFiniteVec3$
|
|
1239
|
+
const v = requireFiniteVec3$4(value, label);
|
|
1240
1240
|
if (v[0] === 0 && v[1] === 0 && v[2] === 0) throw new Error(`${label} must not be [0, 0, 0]`);
|
|
1241
1241
|
return v;
|
|
1242
1242
|
}
|
|
@@ -1254,7 +1254,7 @@ function requireNonZeroFiniteScale2(value, label) {
|
|
|
1254
1254
|
return scale2;
|
|
1255
1255
|
}
|
|
1256
1256
|
function requireNonZeroFiniteScale3(value, label) {
|
|
1257
|
-
const scale2 = typeof value === "number" ? [requireFiniteNumber$1(value, label), requireFiniteNumber$1(value, label), requireFiniteNumber$1(value, label)] : requireFiniteVec3$
|
|
1257
|
+
const scale2 = typeof value === "number" ? [requireFiniteNumber$1(value, label), requireFiniteNumber$1(value, label), requireFiniteNumber$1(value, label)] : requireFiniteVec3$4(value, label);
|
|
1258
1258
|
if (Math.abs(scale2[0]) < 1e-12 || Math.abs(scale2[1]) < 1e-12 || Math.abs(scale2[2]) < 1e-12) {
|
|
1259
1259
|
throw new Error(`${label} must have finite non-zero components, got ${formatValidationValue(value)}`);
|
|
1260
1260
|
}
|
|
@@ -1449,7 +1449,7 @@ class Transform {
|
|
|
1449
1449
|
static rotationAxis(axis, angleDeg, pivot = [0, 0, 0]) {
|
|
1450
1450
|
const [ux, uy, uz] = normalizeVec3$4(requireNonZeroFiniteVec3(axis, "Transform.rotationAxis() axis"));
|
|
1451
1451
|
const degrees2 = requireFiniteNumber$1(angleDeg, "Transform.rotationAxis() angleDeg");
|
|
1452
|
-
const [px, py, pz] = requireFiniteVec3$
|
|
1452
|
+
const [px, py, pz] = requireFiniteVec3$4(pivot, "Transform.rotationAxis() pivot");
|
|
1453
1453
|
const rad = degrees2 * Math.PI / 180;
|
|
1454
1454
|
const cos2 = Math.cos(rad);
|
|
1455
1455
|
const sin2 = Math.sin(rad);
|
|
@@ -1470,9 +1470,9 @@ class Transform {
|
|
|
1470
1470
|
/** Solve the rotation needed to move one point onto a target line or plane. */
|
|
1471
1471
|
static rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
|
|
1472
1472
|
const rotateAxis = requireNonZeroFiniteVec3(axis, "Transform.rotateAroundTo() axis");
|
|
1473
|
-
const rotatePivot = requireFiniteVec3$
|
|
1474
|
-
const moving = requireFiniteVec3$
|
|
1475
|
-
const target = requireFiniteVec3$
|
|
1473
|
+
const rotatePivot = requireFiniteVec3$4(pivot, "Transform.rotateAroundTo() pivot");
|
|
1474
|
+
const moving = requireFiniteVec3$4(movingPoint, "Transform.rotateAroundTo() movingPoint");
|
|
1475
|
+
const target = requireFiniteVec3$4(targetPoint, "Transform.rotateAroundTo() targetPoint");
|
|
1476
1476
|
const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
|
|
1477
1477
|
return Transform.rotationAxis(rotateAxis, angleDeg, rotatePivot);
|
|
1478
1478
|
}
|
|
@@ -1502,6 +1502,7 @@ class Transform {
|
|
|
1502
1502
|
return this.rotateAxis([0, 0, 1], angleDeg, pivot);
|
|
1503
1503
|
}
|
|
1504
1504
|
/** Scale after the current transform. */
|
|
1505
|
+
// biome-ignore lint/suspicious/useAdjacentOverloadSignatures: Static Transform.scale() and chainable instance scale() intentionally share the CAD API name.
|
|
1505
1506
|
scale(v) {
|
|
1506
1507
|
return this.mul(Transform.scale(v));
|
|
1507
1508
|
}
|
|
@@ -1511,11 +1512,11 @@ class Transform {
|
|
|
1511
1512
|
}
|
|
1512
1513
|
/** Transform a point using homogeneous coordinates. */
|
|
1513
1514
|
point(p2) {
|
|
1514
|
-
return transformPoint$1(this.m, requireFiniteVec3$
|
|
1515
|
+
return transformPoint$1(this.m, requireFiniteVec3$4(p2, "Transform.point() point"), 1);
|
|
1515
1516
|
}
|
|
1516
1517
|
/** Transform a direction vector without translation. */
|
|
1517
1518
|
vector(v) {
|
|
1518
|
-
return transformPoint$1(this.m, requireFiniteVec3$
|
|
1519
|
+
return transformPoint$1(this.m, requireFiniteVec3$4(v, "Transform.vector() vector"), 0);
|
|
1519
1520
|
}
|
|
1520
1521
|
/** Return the transform as a raw 4x4 matrix array. */
|
|
1521
1522
|
toArray() {
|
|
@@ -3245,7 +3246,7 @@ const EPSILON$4 = 1e-9;
|
|
|
3245
3246
|
function add$6(a2, b) {
|
|
3246
3247
|
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
3247
3248
|
}
|
|
3248
|
-
function scale$
|
|
3249
|
+
function scale$7(v, factor) {
|
|
3249
3250
|
return [v[0] * factor, v[1] * factor, v[2] * factor];
|
|
3250
3251
|
}
|
|
3251
3252
|
function sub$8(a2, b) {
|
|
@@ -3260,9 +3261,9 @@ function cross$8(a2, b) {
|
|
|
3260
3261
|
function rotateAroundAxis(v, axis, angleRad) {
|
|
3261
3262
|
const c2 = Math.cos(angleRad);
|
|
3262
3263
|
const s = Math.sin(angleRad);
|
|
3263
|
-
const term1 = scale$
|
|
3264
|
-
const term2 = scale$
|
|
3265
|
-
const term3 = scale$
|
|
3264
|
+
const term1 = scale$7(v, c2);
|
|
3265
|
+
const term2 = scale$7(cross$8(axis, v), s);
|
|
3266
|
+
const term3 = scale$7(axis, dot$8(axis, v) * (1 - c2));
|
|
3266
3267
|
return add$6(add$6(term1, term2), term3);
|
|
3267
3268
|
}
|
|
3268
3269
|
function arcPointAt(segment, t) {
|
|
@@ -3290,7 +3291,7 @@ function routePointAt(plan, t) {
|
|
|
3290
3291
|
const next = station + segment.length;
|
|
3291
3292
|
if (target <= next || segment === plan.segments[plan.segments.length - 1]) {
|
|
3292
3293
|
const localT = segment.length <= EPSILON$4 ? 1 : Math.max(0, Math.min(1, (target - station) / segment.length));
|
|
3293
|
-
if (segment.kind === "line") return add$6(segment.from, scale$
|
|
3294
|
+
if (segment.kind === "line") return add$6(segment.from, scale$7(sub$8(segment.to, segment.from), localT));
|
|
3294
3295
|
return arcPointAt(segment, localT);
|
|
3295
3296
|
}
|
|
3296
3297
|
station = next;
|
|
@@ -3310,7 +3311,7 @@ function sampleRoute3DCompilePlan(plan, options) {
|
|
|
3310
3311
|
pushUnique(points, start);
|
|
3311
3312
|
for (let i = 1; i <= intervals; i += 1) {
|
|
3312
3313
|
if (segment.kind === "line") {
|
|
3313
|
-
pushUnique(points, add$6(segment.from, scale$
|
|
3314
|
+
pushUnique(points, add$6(segment.from, scale$7(sub$8(segment.to, segment.from), i / intervals)));
|
|
3314
3315
|
} else {
|
|
3315
3316
|
pushUnique(points, arcPointAt(segment, i / intervals));
|
|
3316
3317
|
}
|
|
@@ -5637,11 +5638,11 @@ function distSq$1(a2, b) {
|
|
|
5637
5638
|
const dz = a2[2] - b[2];
|
|
5638
5639
|
return dx * dx + dy * dy + dz * dz;
|
|
5639
5640
|
}
|
|
5640
|
-
function vecLength$
|
|
5641
|
+
function vecLength$4(v) {
|
|
5641
5642
|
return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
5642
5643
|
}
|
|
5643
5644
|
function normalize$6(v) {
|
|
5644
|
-
const len2 = vecLength$
|
|
5645
|
+
const len2 = vecLength$4(v);
|
|
5645
5646
|
if (len2 < 1e-12) return [0, 0, 0];
|
|
5646
5647
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
5647
5648
|
}
|
|
@@ -6371,14 +6372,14 @@ function resolveSdfMeshingSettings(tree, bounds, options = {}) {
|
|
|
6371
6372
|
const quality = options.quality ?? "preview";
|
|
6372
6373
|
const minEdgeLength = positiveOrDefault(options.minEdgeLength, DEFAULT_MIN_EDGE_LENGTH);
|
|
6373
6374
|
const maxGridPoints = positiveOrDefault(options.maxGridPoints, DEFAULT_MAX_GRID_POINTS);
|
|
6374
|
-
const tolerance = options.tolerance !== void 0 ? requirePositiveFinite$
|
|
6375
|
-
const minFeatureSize = options.minFeatureSize !== void 0 ? requirePositiveFinite$
|
|
6376
|
-
const maxTriangles = options.maxTriangles !== void 0 ? Math.floor(requirePositiveFinite$
|
|
6375
|
+
const tolerance = options.tolerance !== void 0 ? requirePositiveFinite$4(options.tolerance, "SDF tolerance") : void 0;
|
|
6376
|
+
const minFeatureSize = options.minFeatureSize !== void 0 ? requirePositiveFinite$4(options.minFeatureSize, "SDF minFeatureSize") : void 0;
|
|
6377
|
+
const maxTriangles = options.maxTriangles !== void 0 ? Math.floor(requirePositiveFinite$4(options.maxTriangles, "SDF maxTriangles")) : void 0;
|
|
6377
6378
|
const analysis = analyzeSdfTree(tree);
|
|
6378
6379
|
const warnings = [];
|
|
6379
6380
|
let edgeLength;
|
|
6380
6381
|
if (options.edgeLength !== void 0) {
|
|
6381
|
-
edgeLength = requirePositiveFinite$
|
|
6382
|
+
edgeLength = requirePositiveFinite$4(options.edgeLength, "SDF edgeLength");
|
|
6382
6383
|
if (edgeLength < minEdgeLength) {
|
|
6383
6384
|
warnings.push(`edgeLength ${formatMm(edgeLength)} was clamped to minimum ${formatMm(minEdgeLength)}.`);
|
|
6384
6385
|
edgeLength = minEdgeLength;
|
|
@@ -6471,8 +6472,8 @@ function resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, opti
|
|
|
6471
6472
|
const maxDim = Math.max(dx, dy, dz, minEdgeLength);
|
|
6472
6473
|
const divisor = quality === "draft" ? 60 : quality === "export" ? 160 : 100;
|
|
6473
6474
|
const candidates = [maxDim / divisor];
|
|
6474
|
-
if (options.tolerance !== void 0) candidates.push(requirePositiveFinite$
|
|
6475
|
-
if (options.minFeatureSize !== void 0) candidates.push(requirePositiveFinite$
|
|
6475
|
+
if (options.tolerance !== void 0) candidates.push(requirePositiveFinite$4(options.tolerance, "SDF tolerance") * 2);
|
|
6476
|
+
if (options.minFeatureSize !== void 0) candidates.push(requirePositiveFinite$4(options.minFeatureSize, "SDF minFeatureSize") / 2.5);
|
|
6476
6477
|
if (analysis.minMetricTpmsThickness < Infinity) candidates.push(analysis.minMetricTpmsThickness / 2);
|
|
6477
6478
|
if (analysis.minTpmsCellSize < Infinity) candidates.push(analysis.minTpmsCellSize / 10);
|
|
6478
6479
|
if (analysis.minRepeatSpacing < Infinity) candidates.push(analysis.minRepeatSpacing / 8);
|
|
@@ -6612,9 +6613,9 @@ function visitSdfNode(node, analysis) {
|
|
|
6612
6613
|
}
|
|
6613
6614
|
function positiveOrDefault(value, fallback) {
|
|
6614
6615
|
if (value === void 0) return fallback;
|
|
6615
|
-
return requirePositiveFinite$
|
|
6616
|
+
return requirePositiveFinite$4(value, "SDF meshing option");
|
|
6616
6617
|
}
|
|
6617
|
-
function requirePositiveFinite$
|
|
6618
|
+
function requirePositiveFinite$4(value, name) {
|
|
6618
6619
|
if (!Number.isFinite(value) || value <= 0) {
|
|
6619
6620
|
throw new Error(`${name} must be a positive finite number.`);
|
|
6620
6621
|
}
|
|
@@ -8598,7 +8599,7 @@ function midpoint$3(a2, b) {
|
|
|
8598
8599
|
function add$5(a2, b) {
|
|
8599
8600
|
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
8600
8601
|
}
|
|
8601
|
-
function scale$
|
|
8602
|
+
function scale$6(v, s) {
|
|
8602
8603
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
8603
8604
|
}
|
|
8604
8605
|
function sub$7(a2, b) {
|
|
@@ -8627,7 +8628,7 @@ function average$1(points) {
|
|
|
8627
8628
|
for (const point2 of points) {
|
|
8628
8629
|
acc = add$5(acc, point2);
|
|
8629
8630
|
}
|
|
8630
|
-
return scale$
|
|
8631
|
+
return scale$6(acc, 1 / points.length);
|
|
8631
8632
|
}
|
|
8632
8633
|
function buildSurfaceSheetTopology(boundaries, options = {}) {
|
|
8633
8634
|
var _a3, _b3, _c2, _d2;
|
|
@@ -8773,7 +8774,7 @@ function edgeCurveFaceName(curve) {
|
|
|
8773
8774
|
function dotVec3$3(a2, b) {
|
|
8774
8775
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
8775
8776
|
}
|
|
8776
|
-
function vecLength$
|
|
8777
|
+
function vecLength$3(v) {
|
|
8777
8778
|
return Math.hypot(v[0], v[1], v[2]);
|
|
8778
8779
|
}
|
|
8779
8780
|
function sameScalar$2(a2, b) {
|
|
@@ -8783,9 +8784,9 @@ function uniformDistanceScale(matrix) {
|
|
|
8783
8784
|
const xAxis = [matrix[0], matrix[1], matrix[2]];
|
|
8784
8785
|
const yAxis = [matrix[4], matrix[5], matrix[6]];
|
|
8785
8786
|
const zAxis = [matrix[8], matrix[9], matrix[10]];
|
|
8786
|
-
const sx = vecLength$
|
|
8787
|
-
const sy = vecLength$
|
|
8788
|
-
const sz = vecLength$
|
|
8787
|
+
const sx = vecLength$3(xAxis);
|
|
8788
|
+
const sy = vecLength$3(yAxis);
|
|
8789
|
+
const sz = vecLength$3(zAxis);
|
|
8789
8790
|
if (sx <= 1e-9 || sy <= 1e-9 || sz <= 1e-9) return null;
|
|
8790
8791
|
if (!sameScalar$2(sx, sy) || !sameScalar$2(sx, sz)) return null;
|
|
8791
8792
|
const orthoTolerance = 1e-9 * Math.max(1, sx * sx);
|
|
@@ -8796,7 +8797,7 @@ function uniformDistanceScale(matrix) {
|
|
|
8796
8797
|
}
|
|
8797
8798
|
function transformSurfaceAxis(tx, axis) {
|
|
8798
8799
|
const transformed = tx.vector(axis);
|
|
8799
|
-
const length4 = vecLength$
|
|
8800
|
+
const length4 = vecLength$3(transformed);
|
|
8800
8801
|
return length4 > 1e-9 ? [transformed[0] / length4, transformed[1] / length4, transformed[2] / length4] : null;
|
|
8801
8802
|
}
|
|
8802
8803
|
function transformFaceSurface(surface, tx) {
|
|
@@ -9755,7 +9756,7 @@ let _wasm$1 = null;
|
|
|
9755
9756
|
async function initManifoldWasm() {
|
|
9756
9757
|
if (_wasm$1) return _wasm$1;
|
|
9757
9758
|
performance.mark("manifold:start");
|
|
9758
|
-
const Module = (await import("./manifold-
|
|
9759
|
+
const Module = (await import("./manifold-rZexZI0G.js")).default;
|
|
9759
9760
|
performance.mark("manifold:imported");
|
|
9760
9761
|
const wasm = await Module();
|
|
9761
9762
|
wasm.setup();
|
|
@@ -18408,7 +18409,7 @@ function add$4(a2, b) {
|
|
|
18408
18409
|
function sub$6(a2, b) {
|
|
18409
18410
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
18410
18411
|
}
|
|
18411
|
-
function scale$
|
|
18412
|
+
function scale$5(v, scalar) {
|
|
18412
18413
|
return [v[0] * scalar, v[1] * scalar, v[2] * scalar];
|
|
18413
18414
|
}
|
|
18414
18415
|
function cross$6(a2, b) {
|
|
@@ -18657,7 +18658,7 @@ function createBoundedHalfSpace(bounds, normal, originOffset) {
|
|
|
18657
18658
|
const n = [normal[0] / normalLength, normal[1] / normalLength, normal[2] / normalLength];
|
|
18658
18659
|
const signedOffset = originOffset / normalLength;
|
|
18659
18660
|
const corners = boundsCorners(bounds);
|
|
18660
|
-
const planeOrigin = scale$
|
|
18661
|
+
const planeOrigin = scale$5(n, signedOffset);
|
|
18661
18662
|
const { uAxis, vAxis } = perpendicularAxes(n);
|
|
18662
18663
|
const diagonal = Math.hypot(bounds.max[0] - bounds.min[0], bounds.max[1] - bounds.min[1], bounds.max[2] - bounds.min[2]);
|
|
18663
18664
|
const margin = Math.max(diagonal * 0.05, 1e-3);
|
|
@@ -18682,7 +18683,7 @@ function createBoundedHalfSpace(bounds, normal, originOffset) {
|
|
|
18682
18683
|
const height = Math.max(maxDistance + margin, margin);
|
|
18683
18684
|
const centerU = (minU + maxU) / 2;
|
|
18684
18685
|
const centerV = (minV + maxV) / 2;
|
|
18685
|
-
const translation = add$4(add$4(planeOrigin, scale$
|
|
18686
|
+
const translation = add$4(add$4(planeOrigin, scale$5(uAxis, centerU)), scale$5(vAxis, centerV));
|
|
18686
18687
|
const matrix = [
|
|
18687
18688
|
uAxis[0],
|
|
18688
18689
|
uAxis[1],
|
|
@@ -27870,7 +27871,7 @@ function placementReferenceNames(refs, kind) {
|
|
|
27870
27871
|
(entryKind) => Object.keys(refs[entryKind]).sort().map((name) => `${entryKind}.${name}`)
|
|
27871
27872
|
);
|
|
27872
27873
|
}
|
|
27873
|
-
function requireFiniteVec3$
|
|
27874
|
+
function requireFiniteVec3$3(v, label) {
|
|
27874
27875
|
const [x2, y2, z2] = v;
|
|
27875
27876
|
if (!Number.isFinite(x2) || !Number.isFinite(y2) || !Number.isFinite(z2)) {
|
|
27876
27877
|
throw new Error(`${label} must contain finite numbers, got [${x2}, ${y2}, ${z2}]`);
|
|
@@ -27910,8 +27911,8 @@ function normalizePortInput(input) {
|
|
|
27910
27911
|
const hasStartEnd = input.start != null && input.end != null;
|
|
27911
27912
|
const hasOriginAxis = input.origin != null && input.axis != null;
|
|
27912
27913
|
if (hasStartEnd) {
|
|
27913
|
-
const start = requireFiniteVec3$
|
|
27914
|
-
const end = requireFiniteVec3$
|
|
27914
|
+
const start = requireFiniteVec3$3(input.start, "port start");
|
|
27915
|
+
const end = requireFiniteVec3$3(input.end, "port end");
|
|
27915
27916
|
origin = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2, (start[2] + end[2]) / 2];
|
|
27916
27917
|
const dir = [end[0] - start[0], end[1] - start[1], end[2] - start[2]];
|
|
27917
27918
|
const dirLen = len3$2(dir);
|
|
@@ -27920,11 +27921,11 @@ function normalizePortInput(input) {
|
|
|
27920
27921
|
}
|
|
27921
27922
|
axis = normalize3$2(dir);
|
|
27922
27923
|
extent = dirLen / 2;
|
|
27923
|
-
if (input.origin != null) origin = requireFiniteVec3$
|
|
27924
|
-
if (input.axis != null) axis = normalize3$2(requireFiniteVec3$
|
|
27924
|
+
if (input.origin != null) origin = requireFiniteVec3$3(input.origin, "port origin");
|
|
27925
|
+
if (input.axis != null) axis = normalize3$2(requireFiniteVec3$3(input.axis, "port axis"));
|
|
27925
27926
|
} else if (hasOriginAxis) {
|
|
27926
|
-
origin = requireFiniteVec3$
|
|
27927
|
-
const rawAxis = requireFiniteVec3$
|
|
27927
|
+
origin = requireFiniteVec3$3(input.origin, "port origin");
|
|
27928
|
+
const rawAxis = requireFiniteVec3$3(input.axis, "port axis");
|
|
27928
27929
|
if (len3$2(rawAxis) < 1e-10) {
|
|
27929
27930
|
throw new Error("Port axis must be non-zero");
|
|
27930
27931
|
}
|
|
@@ -27933,14 +27934,14 @@ function normalizePortInput(input) {
|
|
|
27933
27934
|
extent = input.extent;
|
|
27934
27935
|
}
|
|
27935
27936
|
} else if (input.origin != null) {
|
|
27936
|
-
origin = requireFiniteVec3$
|
|
27937
|
+
origin = requireFiniteVec3$3(input.origin, "port origin");
|
|
27937
27938
|
axis = [0, 0, 1];
|
|
27938
27939
|
} else {
|
|
27939
27940
|
throw new Error("Port requires either { origin, axis } or { start, end }");
|
|
27940
27941
|
}
|
|
27941
27942
|
let up;
|
|
27942
27943
|
if (input.up != null) {
|
|
27943
|
-
const rawUp = requireFiniteVec3$
|
|
27944
|
+
const rawUp = requireFiniteVec3$3(input.up, "port up");
|
|
27944
27945
|
if (len3$2(rawUp) < 1e-10) {
|
|
27945
27946
|
throw new Error("Port up vector must be non-zero");
|
|
27946
27947
|
}
|
|
@@ -29315,7 +29316,7 @@ class ShapeGroup {
|
|
|
29315
29316
|
return { min: bb.min, max: bb.max };
|
|
29316
29317
|
}
|
|
29317
29318
|
resolveRotatePoint(point2) {
|
|
29318
|
-
if (Array.isArray(point2)) return requireFiniteVec3$
|
|
29319
|
+
if (Array.isArray(point2)) return requireFiniteVec3$4(point2, "ShapeGroup.rotateAroundTo() point");
|
|
29319
29320
|
const bb = this._bbox();
|
|
29320
29321
|
return resolveAnchor3D(bb.min, bb.max, point2);
|
|
29321
29322
|
}
|
|
@@ -29368,7 +29369,7 @@ class ShapeGroup {
|
|
|
29368
29369
|
const sp = resolveAnchor3D(sbb.min, sbb.max, selfAnchor);
|
|
29369
29370
|
let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
|
|
29370
29371
|
if (offset2) {
|
|
29371
|
-
const offsetPoint = requireFiniteVec3$
|
|
29372
|
+
const offsetPoint = requireFiniteVec3$4(offset2, "ShapeGroup.attachTo() offset");
|
|
29372
29373
|
dx += offsetPoint[0];
|
|
29373
29374
|
dy += offsetPoint[1];
|
|
29374
29375
|
dz += offsetPoint[2];
|
|
@@ -29423,7 +29424,7 @@ class ShapeGroup {
|
|
|
29423
29424
|
rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
|
|
29424
29425
|
const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundAxis() axis");
|
|
29425
29426
|
const degrees2 = requireFiniteNumber$1(angleDeg, "ShapeGroup.rotateAroundAxis() angleDeg");
|
|
29426
|
-
const rotatePivot = requireFiniteVec3$
|
|
29427
|
+
const rotatePivot = requireFiniteVec3$4(pivot, "ShapeGroup.rotateAroundAxis() pivot");
|
|
29427
29428
|
return this.transform(Transform.rotationAxis(rotateAxis, degrees2, rotatePivot));
|
|
29428
29429
|
}
|
|
29429
29430
|
/**
|
|
@@ -29432,7 +29433,7 @@ class ShapeGroup {
|
|
|
29432
29433
|
*/
|
|
29433
29434
|
rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
|
|
29434
29435
|
const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundTo() axis");
|
|
29435
|
-
const rotatePivot = requireFiniteVec3$
|
|
29436
|
+
const rotatePivot = requireFiniteVec3$4(pivot, "ShapeGroup.rotateAroundTo() pivot");
|
|
29436
29437
|
return this.transform(
|
|
29437
29438
|
Transform.rotateAroundTo(
|
|
29438
29439
|
rotateAxis,
|
|
@@ -29483,7 +29484,7 @@ class ShapeGroup {
|
|
|
29483
29484
|
/** Scale uniformly or per-axis from an explicit pivot point. */
|
|
29484
29485
|
scaleAround(pivot, v) {
|
|
29485
29486
|
const scale2 = requireNonZeroFiniteScale3(v, "ShapeGroup.scaleAround() scale");
|
|
29486
|
-
const scalePivot = requireFiniteVec3$
|
|
29487
|
+
const scalePivot = requireFiniteVec3$4(pivot, "ShapeGroup.scaleAround() pivot");
|
|
29487
29488
|
const matrix = Transform.scale(scale2).toArray();
|
|
29488
29489
|
if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
|
|
29489
29490
|
return this.mapChildrenTransform((c2) => {
|
|
@@ -29506,7 +29507,7 @@ class ShapeGroup {
|
|
|
29506
29507
|
}
|
|
29507
29508
|
/** Mirror across a plane through an explicit point. */
|
|
29508
29509
|
mirrorThrough(point2, normal) {
|
|
29509
|
-
const mirrorPoint = requireFiniteVec3$
|
|
29510
|
+
const mirrorPoint = requireFiniteVec3$4(point2, "ShapeGroup.mirrorThrough() point");
|
|
29510
29511
|
const mirrorNormal = requireNonZeroFiniteVec3(normal, "ShapeGroup.mirrorThrough() normal");
|
|
29511
29512
|
const matrix = mirrorPlaneMatrix(mirrorNormal);
|
|
29512
29513
|
if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
|
|
@@ -29594,8 +29595,8 @@ class ShapeGroup {
|
|
|
29594
29595
|
* ```
|
|
29595
29596
|
*/
|
|
29596
29597
|
placeReference(ref, target, offset2) {
|
|
29597
|
-
const targetPoint = requireFiniteVec3$
|
|
29598
|
-
const offsetPoint = offset2 === void 0 ? void 0 : requireFiniteVec3$
|
|
29598
|
+
const targetPoint = requireFiniteVec3$4(target, "ShapeGroup.placeReference() target");
|
|
29599
|
+
const offsetPoint = offset2 === void 0 ? void 0 : requireFiniteVec3$4(offset2, "ShapeGroup.placeReference() offset");
|
|
29599
29600
|
const sourcePoint = this.referencePoint(ref);
|
|
29600
29601
|
let dx = targetPoint[0] - sourcePoint[0];
|
|
29601
29602
|
let dy = targetPoint[1] - sourcePoint[1];
|
|
@@ -31785,7 +31786,7 @@ function buildSdfFunctionDefinition(source, options) {
|
|
|
31785
31786
|
jsExpression: expression,
|
|
31786
31787
|
...shader.ok ? { shaderExpression: shader.expression } : { shaderUnsupportedReason: shader.reason },
|
|
31787
31788
|
raymarchStepLimit: resolveRaymarchStepLimit(options.bounds, options.maxStep),
|
|
31788
|
-
...options.lipschitz !== void 0 ? { raymarchLipschitz: requirePositiveFinite$
|
|
31789
|
+
...options.lipschitz !== void 0 ? { raymarchLipschitz: requirePositiveFinite$3(options.lipschitz, "sdf.fromFunction() lipschitz") } : {}
|
|
31789
31790
|
};
|
|
31790
31791
|
}
|
|
31791
31792
|
function extractSdfExpression(source) {
|
|
@@ -31953,7 +31954,7 @@ function formatNumericLiteralsForGlsl(source) {
|
|
|
31953
31954
|
return result;
|
|
31954
31955
|
}
|
|
31955
31956
|
function resolveRaymarchStepLimit(bounds, maxStep) {
|
|
31956
|
-
if (maxStep !== void 0) return requirePositiveFinite$
|
|
31957
|
+
if (maxStep !== void 0) return requirePositiveFinite$3(maxStep, "sdf.fromFunction() maxStep");
|
|
31957
31958
|
const dx = bounds.max[0] - bounds.min[0];
|
|
31958
31959
|
const dy = bounds.max[1] - bounds.min[1];
|
|
31959
31960
|
const dz = bounds.max[2] - bounds.min[2];
|
|
@@ -31961,7 +31962,7 @@ function resolveRaymarchStepLimit(bounds, maxStep) {
|
|
|
31961
31962
|
if (!Number.isFinite(diagonal) || diagonal <= 0) return 0.1;
|
|
31962
31963
|
return Math.max(0.025, Math.min(0.5, diagonal / 240));
|
|
31963
31964
|
}
|
|
31964
|
-
function requirePositiveFinite$
|
|
31965
|
+
function requirePositiveFinite$3(value, label) {
|
|
31965
31966
|
if (!Number.isFinite(value) || value <= 0) throw new Error(`${label} must be a positive finite number.`);
|
|
31966
31967
|
return value;
|
|
31967
31968
|
}
|
|
@@ -32074,7 +32075,7 @@ class Pattern2DBuilder {
|
|
|
32074
32075
|
return new Pattern2DImpl({
|
|
32075
32076
|
kind: "surfacePattern:sineWave",
|
|
32076
32077
|
direction: normalizeDirection$1(options.direction ?? [1, 0], "sdf.pattern2d().sineWave() direction"),
|
|
32077
|
-
wavelength: requirePositiveFinite$
|
|
32078
|
+
wavelength: requirePositiveFinite$2(options.wavelength, "sdf.pattern2d().sineWave() wavelength"),
|
|
32078
32079
|
amplitude: requireFinite$a(options.amplitude ?? 1, "sdf.pattern2d().sineWave() amplitude"),
|
|
32079
32080
|
phase: requireFinite$a(options.phase ?? 0, "sdf.pattern2d().sineWave() phase"),
|
|
32080
32081
|
bias: requireFinite$a(options.bias ?? 0, "sdf.pattern2d().sineWave() bias")
|
|
@@ -32085,19 +32086,19 @@ class Pattern2DBuilder {
|
|
|
32085
32086
|
return new Pattern2DImpl({
|
|
32086
32087
|
kind: "surfacePattern:stripes",
|
|
32087
32088
|
direction: normalizeDirection$1(options.direction ?? [1, 0], "sdf.pattern2d().stripes() direction"),
|
|
32088
|
-
spacing: requirePositiveFinite$
|
|
32089
|
-
width: requirePositiveFinite$
|
|
32090
|
-
depth: requireNonNegativeFinite$
|
|
32089
|
+
spacing: requirePositiveFinite$2(options.spacing, "sdf.pattern2d().stripes() spacing"),
|
|
32090
|
+
width: requirePositiveFinite$2(options.width, "sdf.pattern2d().stripes() width"),
|
|
32091
|
+
depth: requireNonNegativeFinite$2(options.depth ?? 1, "sdf.pattern2d().stripes() depth")
|
|
32091
32092
|
});
|
|
32092
32093
|
}
|
|
32093
32094
|
/** Create an over-under woven relief pattern in UV space. */
|
|
32094
32095
|
overUnderWeave(options) {
|
|
32095
32096
|
return new Pattern2DImpl({
|
|
32096
32097
|
kind: "surfacePattern:overUnderWeave",
|
|
32097
|
-
spacing: normalizeVec2(options.spacing, "sdf.pattern2d().overUnderWeave() spacing", requirePositiveFinite$
|
|
32098
|
-
threadWidth: normalizeVec2(options.threadWidth, "sdf.pattern2d().overUnderWeave() threadWidth", requirePositiveFinite$
|
|
32099
|
-
depth: requireNonNegativeFinite$
|
|
32100
|
-
underScale: requireNonNegativeFinite$
|
|
32098
|
+
spacing: normalizeVec2(options.spacing, "sdf.pattern2d().overUnderWeave() spacing", requirePositiveFinite$2),
|
|
32099
|
+
threadWidth: normalizeVec2(options.threadWidth, "sdf.pattern2d().overUnderWeave() threadWidth", requirePositiveFinite$2),
|
|
32100
|
+
depth: requireNonNegativeFinite$2(options.depth ?? 0.8, "sdf.pattern2d().overUnderWeave() depth"),
|
|
32101
|
+
underScale: requireNonNegativeFinite$2(options.underScale ?? 0.15, "sdf.pattern2d().overUnderWeave() underScale")
|
|
32101
32102
|
});
|
|
32102
32103
|
}
|
|
32103
32104
|
}
|
|
@@ -32120,13 +32121,13 @@ function requireFinite$a(value, label) {
|
|
|
32120
32121
|
}
|
|
32121
32122
|
return value;
|
|
32122
32123
|
}
|
|
32123
|
-
function requirePositiveFinite$
|
|
32124
|
+
function requirePositiveFinite$2(value, label) {
|
|
32124
32125
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
32125
32126
|
throw new Error(`${label} must be a positive finite number. Received: ${String(value)}`);
|
|
32126
32127
|
}
|
|
32127
32128
|
return value;
|
|
32128
32129
|
}
|
|
32129
|
-
function requireNonNegativeFinite$
|
|
32130
|
+
function requireNonNegativeFinite$2(value, label) {
|
|
32130
32131
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
32131
32132
|
throw new Error(`${label} must be a non-negative finite number. Received: ${String(value)}`);
|
|
32132
32133
|
}
|
|
@@ -32244,7 +32245,7 @@ Fix: use a known preset or pass material props directly.`
|
|
|
32244
32245
|
material: validateMaterialProps(material)
|
|
32245
32246
|
};
|
|
32246
32247
|
}
|
|
32247
|
-
function requirePositiveFinite(value, label) {
|
|
32248
|
+
function requirePositiveFinite$1(value, label) {
|
|
32248
32249
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
32249
32250
|
throw new Error(`${label} must be a positive finite number. Received: ${String(value)}`);
|
|
32250
32251
|
}
|
|
@@ -32256,15 +32257,15 @@ function requirePositiveInteger(value, label) {
|
|
|
32256
32257
|
}
|
|
32257
32258
|
return value;
|
|
32258
32259
|
}
|
|
32259
|
-
function requireNonNegativeFinite(value, label) {
|
|
32260
|
+
function requireNonNegativeFinite$1(value, label) {
|
|
32260
32261
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
32261
32262
|
throw new Error(`${label} must be a non-negative finite number. Received: ${String(value)}`);
|
|
32262
32263
|
}
|
|
32263
32264
|
return value;
|
|
32264
32265
|
}
|
|
32265
32266
|
function resolveBlendRadius(input, label, fallback = 4) {
|
|
32266
|
-
if (typeof input === "number") return requirePositiveFinite(input, `${label} radius`);
|
|
32267
|
-
if ((input == null ? void 0 : input.radius) !== void 0) return requirePositiveFinite(input.radius, `${label} radius`);
|
|
32267
|
+
if (typeof input === "number") return requirePositiveFinite$1(input, `${label} radius`);
|
|
32268
|
+
if ((input == null ? void 0 : input.radius) !== void 0) return requirePositiveFinite$1(input.radius, `${label} radius`);
|
|
32268
32269
|
return fallback;
|
|
32269
32270
|
}
|
|
32270
32271
|
function formatExpressionNumber(value) {
|
|
@@ -32274,7 +32275,7 @@ function formatExpressionNumber(value) {
|
|
|
32274
32275
|
}
|
|
32275
32276
|
function roundedBoxNode(halfExtents, radius) {
|
|
32276
32277
|
const minHalf = Math.min(halfExtents[0], halfExtents[1], halfExtents[2]);
|
|
32277
|
-
const r = Math.min(requirePositiveFinite(radius, "SdfShape.round() radius"), Math.max(1e-3, minHalf - 1e-3));
|
|
32278
|
+
const r = Math.min(requirePositiveFinite$1(radius, "SdfShape.round() radius"), Math.max(1e-3, minHalf - 1e-3));
|
|
32278
32279
|
const inner = [halfExtents[0] - r, halfExtents[1] - r, halfExtents[2] - r];
|
|
32279
32280
|
const [ix, iy, iz] = inner.map(formatExpressionNumber);
|
|
32280
32281
|
const rr = formatExpressionNumber(r);
|
|
@@ -32517,9 +32518,9 @@ class SdfShape {
|
|
|
32517
32518
|
clipBox(x2, y2, z2) {
|
|
32518
32519
|
return this.intersect(
|
|
32519
32520
|
box$1(
|
|
32520
|
-
requirePositiveFinite(x2, "SdfShape.clipBox() x"),
|
|
32521
|
-
requirePositiveFinite(y2, "SdfShape.clipBox() y"),
|
|
32522
|
-
requirePositiveFinite(z2, "SdfShape.clipBox() z")
|
|
32521
|
+
requirePositiveFinite$1(x2, "SdfShape.clipBox() x"),
|
|
32522
|
+
requirePositiveFinite$1(y2, "SdfShape.clipBox() y"),
|
|
32523
|
+
requirePositiveFinite$1(z2, "SdfShape.clipBox() z")
|
|
32523
32524
|
)
|
|
32524
32525
|
);
|
|
32525
32526
|
}
|
|
@@ -32551,7 +32552,7 @@ class SdfShape {
|
|
|
32551
32552
|
return this.withNode({
|
|
32552
32553
|
kind: "sdf:smoothUnion",
|
|
32553
32554
|
children: [this._node, other._node],
|
|
32554
|
-
radius: requirePositiveFinite(radius, "SdfShape.smoothUnion() radius")
|
|
32555
|
+
radius: requirePositiveFinite$1(radius, "SdfShape.smoothUnion() radius")
|
|
32555
32556
|
});
|
|
32556
32557
|
}
|
|
32557
32558
|
/** Smooth difference — smoothly carves other from this. */
|
|
@@ -32559,7 +32560,7 @@ class SdfShape {
|
|
|
32559
32560
|
return this.withNode({
|
|
32560
32561
|
kind: "sdf:smoothDifference",
|
|
32561
32562
|
children: [this._node, other._node],
|
|
32562
|
-
radius: requirePositiveFinite(radius, "SdfShape.smoothSubtract() radius")
|
|
32563
|
+
radius: requirePositiveFinite$1(radius, "SdfShape.smoothSubtract() radius")
|
|
32563
32564
|
});
|
|
32564
32565
|
}
|
|
32565
32566
|
/** Smooth intersection — smoothly intersects. */
|
|
@@ -32567,7 +32568,7 @@ class SdfShape {
|
|
|
32567
32568
|
return this.withNode({
|
|
32568
32569
|
kind: "sdf:smoothIntersection",
|
|
32569
32570
|
children: [this._node, other._node],
|
|
32570
|
-
radius: requirePositiveFinite(radius, "SdfShape.smoothIntersect() radius")
|
|
32571
|
+
radius: requirePositiveFinite$1(radius, "SdfShape.smoothIntersect() radius")
|
|
32571
32572
|
});
|
|
32572
32573
|
}
|
|
32573
32574
|
/** Morph between this shape and another. t=0 → this, t=1 → other. */
|
|
@@ -32625,7 +32626,7 @@ class SdfShape {
|
|
|
32625
32626
|
}
|
|
32626
32627
|
/** Uniformly scale this SDF around the origin. */
|
|
32627
32628
|
scale(factor) {
|
|
32628
|
-
return this.withNode({ kind: "sdf:scale", child: this._node, factor: requirePositiveFinite(factor, "SdfShape.scale() factor") });
|
|
32629
|
+
return this.withNode({ kind: "sdf:scale", child: this._node, factor: requirePositiveFinite$1(factor, "SdfShape.scale() factor") });
|
|
32629
32630
|
}
|
|
32630
32631
|
// ── Domain operations ──
|
|
32631
32632
|
/** Twist around the Z axis. */
|
|
@@ -32638,15 +32639,15 @@ class SdfShape {
|
|
|
32638
32639
|
}
|
|
32639
32640
|
/** Bend around the Z axis with given radius. */
|
|
32640
32641
|
bend(radius) {
|
|
32641
|
-
return this.withNode({ kind: "sdf:bend", child: this._node, radius: requirePositiveFinite(radius, "SdfShape.bend() radius") });
|
|
32642
|
+
return this.withNode({ kind: "sdf:bend", child: this._node, radius: requirePositiveFinite$1(radius, "SdfShape.bend() radius") });
|
|
32642
32643
|
}
|
|
32643
32644
|
/** Repeat in space. Spacing of 0 on an axis means no repetition. Count of 0 = infinite. */
|
|
32644
32645
|
repeat(spacing, count) {
|
|
32645
32646
|
return this.withNode({
|
|
32646
32647
|
kind: "sdf:repeat",
|
|
32647
32648
|
child: this._node,
|
|
32648
|
-
spacing: requireFiniteVec3$
|
|
32649
|
-
count: count === void 0 ? [0, 0, 0] : requireFiniteVec3$
|
|
32649
|
+
spacing: requireFiniteVec3$4(spacing, "SdfShape.repeat() spacing"),
|
|
32650
|
+
count: count === void 0 ? [0, 0, 0] : requireFiniteVec3$4(count, "SdfShape.repeat() count")
|
|
32650
32651
|
});
|
|
32651
32652
|
}
|
|
32652
32653
|
/**
|
|
@@ -32661,7 +32662,7 @@ class SdfShape {
|
|
|
32661
32662
|
kind: "sdf:circularArray",
|
|
32662
32663
|
child: this._node,
|
|
32663
32664
|
count: requirePositiveInteger(count, "SdfShape.circularArray() count"),
|
|
32664
|
-
offset: requireNonNegativeFinite(offset2, "SdfShape.circularArray() offset")
|
|
32665
|
+
offset: requireNonNegativeFinite$1(offset2, "SdfShape.circularArray() offset")
|
|
32665
32666
|
});
|
|
32666
32667
|
}
|
|
32667
32668
|
/** Hollow out, keeping only a shell of given thickness. */
|
|
@@ -32669,7 +32670,7 @@ class SdfShape {
|
|
|
32669
32670
|
return this.withNode({
|
|
32670
32671
|
kind: "sdf:shell",
|
|
32671
32672
|
child: this._node,
|
|
32672
|
-
thickness: requirePositiveFinite(thickness, "SdfShape.shell() thickness")
|
|
32673
|
+
thickness: requirePositiveFinite$1(thickness, "SdfShape.shell() thickness")
|
|
32673
32674
|
});
|
|
32674
32675
|
}
|
|
32675
32676
|
/**
|
|
@@ -32749,70 +32750,70 @@ class SdfShape {
|
|
|
32749
32750
|
kind: "sdf:onion",
|
|
32750
32751
|
child: this._node,
|
|
32751
32752
|
layers: requirePositiveInteger(layers, "SdfShape.onion() layers"),
|
|
32752
|
-
thickness: requirePositiveFinite(thickness, "SdfShape.onion() thickness")
|
|
32753
|
+
thickness: requirePositiveFinite$1(thickness, "SdfShape.onion() thickness")
|
|
32753
32754
|
});
|
|
32754
32755
|
}
|
|
32755
32756
|
}
|
|
32756
32757
|
function sphere$1(radius) {
|
|
32757
|
-
return new SdfShape({ kind: "sdf:sphere", radius: requirePositiveFinite(radius, "sdf.sphere() radius") });
|
|
32758
|
+
return new SdfShape({ kind: "sdf:sphere", radius: requirePositiveFinite$1(radius, "sdf.sphere() radius") });
|
|
32758
32759
|
}
|
|
32759
32760
|
function box$1(x2, y2, z2) {
|
|
32760
32761
|
return new SdfShape({
|
|
32761
32762
|
kind: "sdf:box",
|
|
32762
32763
|
halfExtents: [
|
|
32763
|
-
requirePositiveFinite(x2, "sdf.box() x") / 2,
|
|
32764
|
-
requirePositiveFinite(y2, "sdf.box() y") / 2,
|
|
32765
|
-
requirePositiveFinite(z2, "sdf.box() z") / 2
|
|
32764
|
+
requirePositiveFinite$1(x2, "sdf.box() x") / 2,
|
|
32765
|
+
requirePositiveFinite$1(y2, "sdf.box() y") / 2,
|
|
32766
|
+
requirePositiveFinite$1(z2, "sdf.box() z") / 2
|
|
32766
32767
|
]
|
|
32767
32768
|
});
|
|
32768
32769
|
}
|
|
32769
32770
|
function cylinder$1(height, radius) {
|
|
32770
32771
|
return new SdfShape({
|
|
32771
32772
|
kind: "sdf:cylinder",
|
|
32772
|
-
height: requirePositiveFinite(height, "sdf.cylinder() height"),
|
|
32773
|
-
radius: requirePositiveFinite(radius, "sdf.cylinder() radius")
|
|
32773
|
+
height: requirePositiveFinite$1(height, "sdf.cylinder() height"),
|
|
32774
|
+
radius: requirePositiveFinite$1(radius, "sdf.cylinder() radius")
|
|
32774
32775
|
});
|
|
32775
32776
|
}
|
|
32776
32777
|
function torus$1(majorRadius, minorRadius) {
|
|
32777
32778
|
return new SdfShape({
|
|
32778
32779
|
kind: "sdf:torus",
|
|
32779
|
-
majorRadius: requirePositiveFinite(majorRadius, "sdf.torus() majorRadius"),
|
|
32780
|
-
minorRadius: requirePositiveFinite(minorRadius, "sdf.torus() minorRadius")
|
|
32780
|
+
majorRadius: requirePositiveFinite$1(majorRadius, "sdf.torus() majorRadius"),
|
|
32781
|
+
minorRadius: requirePositiveFinite$1(minorRadius, "sdf.torus() minorRadius")
|
|
32781
32782
|
});
|
|
32782
32783
|
}
|
|
32783
32784
|
function capsule(height, radius) {
|
|
32784
32785
|
return new SdfShape({
|
|
32785
32786
|
kind: "sdf:capsule",
|
|
32786
|
-
height: requirePositiveFinite(height, "sdf.capsule() height"),
|
|
32787
|
-
radius: requirePositiveFinite(radius, "sdf.capsule() radius")
|
|
32787
|
+
height: requirePositiveFinite$1(height, "sdf.capsule() height"),
|
|
32788
|
+
radius: requirePositiveFinite$1(radius, "sdf.capsule() radius")
|
|
32788
32789
|
});
|
|
32789
32790
|
}
|
|
32790
32791
|
function cone(height, radius) {
|
|
32791
32792
|
return new SdfShape({
|
|
32792
32793
|
kind: "sdf:cone",
|
|
32793
|
-
height: requirePositiveFinite(height, "sdf.cone() height"),
|
|
32794
|
-
radius: requirePositiveFinite(radius, "sdf.cone() radius")
|
|
32794
|
+
height: requirePositiveFinite$1(height, "sdf.cone() height"),
|
|
32795
|
+
radius: requirePositiveFinite$1(radius, "sdf.cone() radius")
|
|
32795
32796
|
});
|
|
32796
32797
|
}
|
|
32797
32798
|
function smoothUnion(a2, b, options) {
|
|
32798
32799
|
return new SdfShape({
|
|
32799
32800
|
kind: "sdf:smoothUnion",
|
|
32800
32801
|
children: [a2._node, b._node],
|
|
32801
|
-
radius: requirePositiveFinite(options.radius, "sdf.smoothUnion() radius")
|
|
32802
|
+
radius: requirePositiveFinite$1(options.radius, "sdf.smoothUnion() radius")
|
|
32802
32803
|
});
|
|
32803
32804
|
}
|
|
32804
32805
|
function smoothDifference(a2, b, options) {
|
|
32805
32806
|
return new SdfShape({
|
|
32806
32807
|
kind: "sdf:smoothDifference",
|
|
32807
32808
|
children: [a2._node, b._node],
|
|
32808
|
-
radius: requirePositiveFinite(options.radius, "sdf.smoothDifference() radius")
|
|
32809
|
+
radius: requirePositiveFinite$1(options.radius, "sdf.smoothDifference() radius")
|
|
32809
32810
|
});
|
|
32810
32811
|
}
|
|
32811
32812
|
function smoothIntersection(a2, b, options) {
|
|
32812
32813
|
return new SdfShape({
|
|
32813
32814
|
kind: "sdf:smoothIntersection",
|
|
32814
32815
|
children: [a2._node, b._node],
|
|
32815
|
-
radius: requirePositiveFinite(options.radius, "sdf.smoothIntersection() radius")
|
|
32816
|
+
radius: requirePositiveFinite$1(options.radius, "sdf.smoothIntersection() radius")
|
|
32816
32817
|
});
|
|
32817
32818
|
}
|
|
32818
32819
|
function morph(a2, b, t) {
|
|
@@ -32995,9 +32996,9 @@ function weave(options) {
|
|
|
32995
32996
|
});
|
|
32996
32997
|
}
|
|
32997
32998
|
function basketWeave(options) {
|
|
32998
|
-
const SP = requirePositiveFinite((options == null ? void 0 : options.spacing) ?? 3, "sdf.basketWeave() spacing");
|
|
32999
|
-
const TW = requirePositiveFinite((options == null ? void 0 : options.threadWidth) ?? 1.5, "sdf.basketWeave() threadWidth");
|
|
33000
|
-
const D2 = requireNonNegativeFinite((options == null ? void 0 : options.depth) ?? 0.8, "sdf.basketWeave() depth");
|
|
32999
|
+
const SP = requirePositiveFinite$1((options == null ? void 0 : options.spacing) ?? 3, "sdf.basketWeave() spacing");
|
|
33000
|
+
const TW = requirePositiveFinite$1((options == null ? void 0 : options.threadWidth) ?? 1.5, "sdf.basketWeave() threadWidth");
|
|
33001
|
+
const D2 = requireNonNegativeFinite$1((options == null ? void 0 : options.depth) ?? 0.8, "sdf.basketWeave() depth");
|
|
33001
33002
|
return pattern2d().overUnderWeave({ spacing: SP, threadWidth: TW, depth: D2 });
|
|
33002
33003
|
}
|
|
33003
33004
|
function fromFunction(fn, options) {
|
|
@@ -33026,18 +33027,18 @@ function twist(shape, degreesPerUnit) {
|
|
|
33026
33027
|
return shape.twist(requireFiniteNumber$1(degreesPerUnit, "sdf.twist() degreesPerUnit"));
|
|
33027
33028
|
}
|
|
33028
33029
|
function bend(shape, radius) {
|
|
33029
|
-
return shape.bend(requirePositiveFinite(radius, "sdf.bend() radius"));
|
|
33030
|
+
return shape.bend(requirePositiveFinite$1(radius, "sdf.bend() radius"));
|
|
33030
33031
|
}
|
|
33031
33032
|
function repeat(shape, spacing, count) {
|
|
33032
33033
|
return shape.repeat(
|
|
33033
|
-
requireFiniteVec3$
|
|
33034
|
-
count === void 0 ? void 0 : requireFiniteVec3$
|
|
33034
|
+
requireFiniteVec3$4(spacing, "sdf.repeat() spacing"),
|
|
33035
|
+
count === void 0 ? void 0 : requireFiniteVec3$4(count, "sdf.repeat() count")
|
|
33035
33036
|
);
|
|
33036
33037
|
}
|
|
33037
33038
|
function circularArray(shape, count, offset2 = 0) {
|
|
33038
33039
|
return shape.circularArray(
|
|
33039
33040
|
requirePositiveInteger(count, "sdf.circularArray() count"),
|
|
33040
|
-
requireNonNegativeFinite(offset2, "sdf.circularArray() offset")
|
|
33041
|
+
requireNonNegativeFinite$1(offset2, "sdf.circularArray() offset")
|
|
33041
33042
|
);
|
|
33042
33043
|
}
|
|
33043
33044
|
function resolveTpmsOptions(options) {
|
|
@@ -33818,11 +33819,11 @@ function mergeTopology(base, overlay) {
|
|
|
33818
33819
|
return merged;
|
|
33819
33820
|
}
|
|
33820
33821
|
const ANALYTIC_SURFACE_EPS = 1e-9;
|
|
33821
|
-
function vecLength$
|
|
33822
|
+
function vecLength$2(v) {
|
|
33822
33823
|
return Math.hypot(v[0], v[1], v[2]);
|
|
33823
33824
|
}
|
|
33824
33825
|
function normalizeVec3OrNull(v) {
|
|
33825
|
-
const length4 = vecLength$
|
|
33826
|
+
const length4 = vecLength$2(v);
|
|
33826
33827
|
return length4 > ANALYTIC_SURFACE_EPS ? [v[0] / length4, v[1] / length4, v[2] / length4] : null;
|
|
33827
33828
|
}
|
|
33828
33829
|
function dotVec3$1(a2, b) {
|
|
@@ -33874,9 +33875,9 @@ function matrixUniformDistanceScale(matrix) {
|
|
|
33874
33875
|
const xAxis = [matrix[0], matrix[1], matrix[2]];
|
|
33875
33876
|
const yAxis = [matrix[4], matrix[5], matrix[6]];
|
|
33876
33877
|
const zAxis = [matrix[8], matrix[9], matrix[10]];
|
|
33877
|
-
const sx = vecLength$
|
|
33878
|
-
const sy = vecLength$
|
|
33879
|
-
const sz = vecLength$
|
|
33878
|
+
const sx = vecLength$2(xAxis);
|
|
33879
|
+
const sy = vecLength$2(yAxis);
|
|
33880
|
+
const sz = vecLength$2(zAxis);
|
|
33880
33881
|
if (sx <= ANALYTIC_SURFACE_EPS || sy <= ANALYTIC_SURFACE_EPS || sz <= ANALYTIC_SURFACE_EPS) return null;
|
|
33881
33882
|
if (!sameScalar(sx, sy) || !sameScalar(sx, sz)) return null;
|
|
33882
33883
|
const orthoTolerance = ANALYTIC_SURFACE_EPS * Math.max(1, sx * sx);
|
|
@@ -34564,7 +34565,7 @@ function withBaseDimensionsAndMergedSourceSpans(base, sources, out, preserveOutp
|
|
|
34564
34565
|
return result;
|
|
34565
34566
|
}
|
|
34566
34567
|
function resolveRotationPoint(shape, point2) {
|
|
34567
|
-
if (Array.isArray(point2)) return requireFiniteVec3$
|
|
34568
|
+
if (Array.isArray(point2)) return requireFiniteVec3$4(point2, "rotateAroundTo(): point");
|
|
34568
34569
|
return shape.referencePoint(point2);
|
|
34569
34570
|
}
|
|
34570
34571
|
function setShapeDimensions(shape, dims, options = {}) {
|
|
@@ -35456,8 +35457,8 @@ class Shape {
|
|
|
35456
35457
|
* ```
|
|
35457
35458
|
*/
|
|
35458
35459
|
placeReference(ref, target, offset2) {
|
|
35459
|
-
const targetPoint = requireFiniteVec3$
|
|
35460
|
-
const offsetPoint = offset2 === void 0 ? void 0 : requireFiniteVec3$
|
|
35460
|
+
const targetPoint = requireFiniteVec3$4(target, "Shape.placeReference() target");
|
|
35461
|
+
const offsetPoint = offset2 === void 0 ? void 0 : requireFiniteVec3$4(offset2, "Shape.placeReference() offset");
|
|
35461
35462
|
const sourcePoint = this.referencePoint(ref);
|
|
35462
35463
|
let dx = targetPoint[0] - sourcePoint[0];
|
|
35463
35464
|
let dy = targetPoint[1] - sourcePoint[1];
|
|
@@ -35562,7 +35563,7 @@ class Shape {
|
|
|
35562
35563
|
/** Scale the shape uniformly or per-axis from an explicit pivot point. */
|
|
35563
35564
|
scaleAround(pivot, v) {
|
|
35564
35565
|
const scale2 = requireNonZeroFiniteScale3(v, "Shape.scaleAround() scale");
|
|
35565
|
-
const scalePivot = requireFiniteVec3$
|
|
35566
|
+
const scalePivot = requireFiniteVec3$4(pivot, "Shape.scaleAround() pivot");
|
|
35566
35567
|
if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
|
|
35567
35568
|
const nextPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
|
|
35568
35569
|
kind: "scale",
|
|
@@ -35600,7 +35601,7 @@ class Shape {
|
|
|
35600
35601
|
}
|
|
35601
35602
|
/** Mirror across a plane through an explicit point, defined by its normal vector. */
|
|
35602
35603
|
mirrorThrough(point2, normal) {
|
|
35603
|
-
const mirrorPoint = requireFiniteVec3$
|
|
35604
|
+
const mirrorPoint = requireFiniteVec3$4(point2, "Shape.mirrorThrough() point");
|
|
35604
35605
|
const mirrorNormal = requireNonZeroFiniteVec3(normal, "Shape.mirrorThrough() normal");
|
|
35605
35606
|
if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
|
|
35606
35607
|
const transformedPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
|
|
@@ -35658,7 +35659,7 @@ class Shape {
|
|
|
35658
35659
|
rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
|
|
35659
35660
|
const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundAxis() axis");
|
|
35660
35661
|
const degrees2 = requireFiniteNumber$1(angleDeg, "Shape.rotateAroundAxis() angleDeg");
|
|
35661
|
-
const rotatePivot = requireFiniteVec3$
|
|
35662
|
+
const rotatePivot = requireFiniteVec3$4(pivot, "Shape.rotateAroundAxis() pivot");
|
|
35662
35663
|
const len2 = Math.sqrt(rotateAxis[0] ** 2 + rotateAxis[1] ** 2 + rotateAxis[2] ** 2) || 1;
|
|
35663
35664
|
const normalizedAxis = [rotateAxis[0] / len2, rotateAxis[1] / len2, rotateAxis[2] / len2];
|
|
35664
35665
|
const matrix = rotationAroundAxisMatrix(normalizedAxis, degrees2, rotatePivot);
|
|
@@ -35683,7 +35684,7 @@ class Shape {
|
|
|
35683
35684
|
*/
|
|
35684
35685
|
rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
|
|
35685
35686
|
const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundTo() axis");
|
|
35686
|
-
const rotatePivot = requireFiniteVec3$
|
|
35687
|
+
const rotatePivot = requireFiniteVec3$4(pivot, "Shape.rotateAroundTo() pivot");
|
|
35687
35688
|
const moving = resolveRotationPoint(this, movingPoint);
|
|
35688
35689
|
const target = resolveRotationPoint(this, targetPoint);
|
|
35689
35690
|
const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
|
|
@@ -36006,7 +36007,7 @@ class Shape {
|
|
|
36006
36007
|
const sp = this.referencePoint(selfAnchor);
|
|
36007
36008
|
let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
|
|
36008
36009
|
if (offset2) {
|
|
36009
|
-
const offsetPoint = requireFiniteVec3$
|
|
36010
|
+
const offsetPoint = requireFiniteVec3$4(offset2, "Shape.attachTo() offset");
|
|
36010
36011
|
dx += offsetPoint[0];
|
|
36011
36012
|
dy += offsetPoint[1];
|
|
36012
36013
|
dz += offsetPoint[2];
|
|
@@ -38244,6 +38245,107 @@ function collectJointsView(options = {}, base = null, motionSource) {
|
|
|
38244
38245
|
function jointsView(options = {}) {
|
|
38245
38246
|
_collected$8 = collectJointsView(options, _collected$8);
|
|
38246
38247
|
}
|
|
38248
|
+
function requireFiniteVec3$2(input, label) {
|
|
38249
|
+
if (!Array.isArray(input) || input.length !== 3) throw new Error(`${label} must be a [x, y, z] tuple`);
|
|
38250
|
+
const out = [input[0], input[1], input[2]];
|
|
38251
|
+
if (!Number.isFinite(out[0]) || !Number.isFinite(out[1]) || !Number.isFinite(out[2])) {
|
|
38252
|
+
throw new Error(`${label} must contain finite numbers`);
|
|
38253
|
+
}
|
|
38254
|
+
return out;
|
|
38255
|
+
}
|
|
38256
|
+
function vecDot$1(a2, b) {
|
|
38257
|
+
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
38258
|
+
}
|
|
38259
|
+
function vecCross$1(a2, b) {
|
|
38260
|
+
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]];
|
|
38261
|
+
}
|
|
38262
|
+
function vecLength$1(v) {
|
|
38263
|
+
return Math.hypot(v[0], v[1], v[2]);
|
|
38264
|
+
}
|
|
38265
|
+
function vecNormalize$1(v, label) {
|
|
38266
|
+
const length4 = vecLength$1(v);
|
|
38267
|
+
if (length4 < 1e-10) throw new Error(`${label} must be non-zero`);
|
|
38268
|
+
return [v[0] / length4, v[1] / length4, v[2] / length4];
|
|
38269
|
+
}
|
|
38270
|
+
function orthonormalizeFrame(axisInput, upInput) {
|
|
38271
|
+
const axis = vecNormalize$1(axisInput, "frame axis");
|
|
38272
|
+
if (vecLength$1(upInput) < 1e-10) throw new Error("frame up vector must be non-zero");
|
|
38273
|
+
const upProjection = vecDot$1(upInput, axis);
|
|
38274
|
+
const upOrtho = [upInput[0] - upProjection * axis[0], upInput[1] - upProjection * axis[1], upInput[2] - upProjection * axis[2]];
|
|
38275
|
+
if (vecLength$1(upOrtho) < 1e-10) throw new Error("frame axis and up must not be parallel");
|
|
38276
|
+
const up = vecNormalize$1(upOrtho, "frame up vector");
|
|
38277
|
+
const right = vecNormalize$1(vecCross$1(up, axis), "frame right vector");
|
|
38278
|
+
return { axis, up, right };
|
|
38279
|
+
}
|
|
38280
|
+
function frameTransform(origin, axisInput, upInput) {
|
|
38281
|
+
const { axis, up, right } = orthonormalizeFrame(axisInput, upInput);
|
|
38282
|
+
return Transform.from([
|
|
38283
|
+
right[0],
|
|
38284
|
+
right[1],
|
|
38285
|
+
right[2],
|
|
38286
|
+
0,
|
|
38287
|
+
up[0],
|
|
38288
|
+
up[1],
|
|
38289
|
+
up[2],
|
|
38290
|
+
0,
|
|
38291
|
+
axis[0],
|
|
38292
|
+
axis[1],
|
|
38293
|
+
axis[2],
|
|
38294
|
+
0,
|
|
38295
|
+
origin[0],
|
|
38296
|
+
origin[1],
|
|
38297
|
+
origin[2],
|
|
38298
|
+
1
|
|
38299
|
+
]);
|
|
38300
|
+
}
|
|
38301
|
+
function normalizeAssemblyFrame(name, input) {
|
|
38302
|
+
if (!input || typeof input !== "object") throw new Error(`frame("${name}") options are required`);
|
|
38303
|
+
const origin = requireFiniteVec3$2(input.origin, `frame("${name}") origin`);
|
|
38304
|
+
const rawAxis = requireFiniteVec3$2(input.axis, `frame("${name}") axis`);
|
|
38305
|
+
const rawUp = requireFiniteVec3$2(input.up, `frame("${name}") up`);
|
|
38306
|
+
const { axis, up } = orthonormalizeFrame(rawAxis, rawUp);
|
|
38307
|
+
return {
|
|
38308
|
+
name,
|
|
38309
|
+
origin,
|
|
38310
|
+
axis,
|
|
38311
|
+
up,
|
|
38312
|
+
fixed: input.fixed ?? false,
|
|
38313
|
+
metadata: input.metadata ? { ...input.metadata } : void 0
|
|
38314
|
+
};
|
|
38315
|
+
}
|
|
38316
|
+
function cloneAssemblyFrame(frame) {
|
|
38317
|
+
return {
|
|
38318
|
+
name: frame.name,
|
|
38319
|
+
origin: [...frame.origin],
|
|
38320
|
+
axis: [...frame.axis],
|
|
38321
|
+
up: [...frame.up],
|
|
38322
|
+
fixed: frame.fixed,
|
|
38323
|
+
metadata: frame.metadata ? { ...frame.metadata } : void 0
|
|
38324
|
+
};
|
|
38325
|
+
}
|
|
38326
|
+
function cloneAssemblyFrameEdge(edge) {
|
|
38327
|
+
return {
|
|
38328
|
+
name: edge.name,
|
|
38329
|
+
a: edge.a,
|
|
38330
|
+
b: edge.b,
|
|
38331
|
+
metadata: edge.metadata ? { ...edge.metadata } : void 0
|
|
38332
|
+
};
|
|
38333
|
+
}
|
|
38334
|
+
function frameDefTransform(frame) {
|
|
38335
|
+
return frameTransform(frame.origin, frame.axis, frame.up);
|
|
38336
|
+
}
|
|
38337
|
+
function frameFromTransform(frame, transform) {
|
|
38338
|
+
return {
|
|
38339
|
+
...cloneAssemblyFrame(frame),
|
|
38340
|
+
origin: transform.point([0, 0, 0]),
|
|
38341
|
+
up: vecNormalize$1(transform.vector([0, 1, 0]), `solved frame "${frame.name}" up`),
|
|
38342
|
+
axis: vecNormalize$1(transform.vector([0, 0, 1]), `solved frame "${frame.name}" axis`),
|
|
38343
|
+
transform
|
|
38344
|
+
};
|
|
38345
|
+
}
|
|
38346
|
+
function relativeFrameTransform(parent, child) {
|
|
38347
|
+
return composeChain(frameDefTransform(child), frameDefTransform(parent).inverse());
|
|
38348
|
+
}
|
|
38247
38349
|
var define_process_env_default$1 = {};
|
|
38248
38350
|
let collectedAssemblies = [];
|
|
38249
38351
|
function resetCollectedAssemblies() {
|
|
@@ -38377,12 +38479,22 @@ function motionTransform(joint2, value) {
|
|
|
38377
38479
|
const dz = joint2.axis[2] * value;
|
|
38378
38480
|
return Transform.identity().translate(dx, dy, dz);
|
|
38379
38481
|
}
|
|
38482
|
+
function frameJointMotionTransform(joint2, value) {
|
|
38483
|
+
if (joint2.type === "fixed") return Transform.identity();
|
|
38484
|
+
if (joint2.type === "revolute") return Transform.rotationAxis([0, 0, 1], value);
|
|
38485
|
+
return Transform.translation(0, 0, value);
|
|
38486
|
+
}
|
|
38380
38487
|
function clampJointValue(joint2, value) {
|
|
38381
38488
|
let clamped = Number.isFinite(value) ? value : joint2.defaultValue;
|
|
38382
38489
|
if (joint2.min != null) clamped = Math.max(joint2.min, clamped);
|
|
38383
38490
|
if (joint2.max != null) clamped = Math.min(joint2.max, clamped);
|
|
38384
38491
|
return { value: clamped, wasClamped: clamped !== value };
|
|
38385
38492
|
}
|
|
38493
|
+
function clampFrameJointDefault(jointName, value, min2, max2) {
|
|
38494
|
+
if (min2 !== void 0 && value < min2) throw new Error(`Frame joint "${jointName}" default must be >= min`);
|
|
38495
|
+
if (max2 !== void 0 && value > max2) throw new Error(`Frame joint "${jointName}" default must be <= max`);
|
|
38496
|
+
return value;
|
|
38497
|
+
}
|
|
38386
38498
|
function finiteVec3(input, label) {
|
|
38387
38499
|
if (!Array.isArray(input) || input.length !== 3) throw new Error(`${label} must be a [x, y, z] tuple`);
|
|
38388
38500
|
const out = [input[0], input[1], input[2]];
|
|
@@ -38394,6 +38506,9 @@ function finiteVec3(input, label) {
|
|
|
38394
38506
|
function cloneVec3$3(v) {
|
|
38395
38507
|
return [v[0], v[1], v[2]];
|
|
38396
38508
|
}
|
|
38509
|
+
function cloneAngleReference(reference) {
|
|
38510
|
+
return reference ? { kind: "worldDirection", direction: cloneVec3$3(reference.direction) } : void 0;
|
|
38511
|
+
}
|
|
38397
38512
|
function vecDistance(a2, b) {
|
|
38398
38513
|
return Math.hypot(b[0] - a2[0], b[1] - a2[1], b[2] - a2[2]);
|
|
38399
38514
|
}
|
|
@@ -38413,12 +38528,10 @@ function vecNormalize(a2) {
|
|
|
38413
38528
|
const len2 = vecLength(a2);
|
|
38414
38529
|
return len2 < 1e-12 ? [0, 0, 0] : [a2[0] / len2, a2[1] / len2, a2[2] / len2];
|
|
38415
38530
|
}
|
|
38416
|
-
function
|
|
38417
|
-
const
|
|
38418
|
-
const bc = vecSub(c2, b);
|
|
38419
|
-
const cross2 = vecCross(ba, bc);
|
|
38531
|
+
function signedAngleBetweenVectorsAboutAxis(reference, target, axisUnit) {
|
|
38532
|
+
const cross2 = vecCross(reference, target);
|
|
38420
38533
|
const sin2 = vecDot(cross2, axisUnit);
|
|
38421
|
-
const cos2 = vecDot(
|
|
38534
|
+
const cos2 = vecDot(reference, target);
|
|
38422
38535
|
return Math.atan2(sin2, cos2) * 180 / Math.PI;
|
|
38423
38536
|
}
|
|
38424
38537
|
function normalizeAngleDeltaDeg(value) {
|
|
@@ -38539,7 +38652,7 @@ function deriveExplodeHintsFromMates(constraints, result, bodies, ctx) {
|
|
|
38539
38652
|
return hints;
|
|
38540
38653
|
}
|
|
38541
38654
|
class SolvedAssembly {
|
|
38542
|
-
constructor(name, parts, transforms, jointValues, solveWarnings, _mateMetadata = null, _kinematics = null, usedPortRefs) {
|
|
38655
|
+
constructor(name, parts, transforms, jointValues, solveWarnings, _mateMetadata = null, _kinematics = null, _frames = /* @__PURE__ */ new Map(), usedPortRefs) {
|
|
38543
38656
|
__publicField(this, "_usedPortRefs");
|
|
38544
38657
|
this.name = name;
|
|
38545
38658
|
this.parts = parts;
|
|
@@ -38548,6 +38661,7 @@ class SolvedAssembly {
|
|
|
38548
38661
|
this.solveWarnings = solveWarnings;
|
|
38549
38662
|
this._mateMetadata = _mateMetadata;
|
|
38550
38663
|
this._kinematics = _kinematics;
|
|
38664
|
+
this._frames = _frames;
|
|
38551
38665
|
this._usedPortRefs = usedPortRefs ?? /* @__PURE__ */ new Set();
|
|
38552
38666
|
}
|
|
38553
38667
|
/** Return any warnings generated during solve (clamped joints, unconverged mates, etc.). */
|
|
@@ -38573,7 +38687,7 @@ class SolvedAssembly {
|
|
|
38573
38687
|
var _a3;
|
|
38574
38688
|
return ((_a3 = this._mateMetadata) == null ? void 0 : _a3.converged) ?? null;
|
|
38575
38689
|
}
|
|
38576
|
-
/** Solved assembly-native kinematic
|
|
38690
|
+
/** Solved assembly-native kinematic or frame-edge overlay data, or null when no rig overlay data was declared. */
|
|
38577
38691
|
get kinematics() {
|
|
38578
38692
|
return this._kinematics;
|
|
38579
38693
|
}
|
|
@@ -38584,6 +38698,19 @@ class SolvedAssembly {
|
|
|
38584
38698
|
if (!link) throw new Error(`Unknown kinematic link "${linkName}"`);
|
|
38585
38699
|
return cloneVec3$3(link.position);
|
|
38586
38700
|
}
|
|
38701
|
+
/** Return the solved world transform for a named rig frame. */
|
|
38702
|
+
getFrame(frameName) {
|
|
38703
|
+
const frame = this._frames.get(frameName);
|
|
38704
|
+
if (!frame) throw new Error(`Unknown frame "${frameName}"`);
|
|
38705
|
+
return frame.transform;
|
|
38706
|
+
}
|
|
38707
|
+
/** Return solved rig frames, including origin, axis, up, and transform. */
|
|
38708
|
+
get frames() {
|
|
38709
|
+
return [...this._frames.values()].map((frame) => ({
|
|
38710
|
+
...cloneAssemblyFrame(frame),
|
|
38711
|
+
transform: frame.transform
|
|
38712
|
+
}));
|
|
38713
|
+
}
|
|
38587
38714
|
/**
|
|
38588
38715
|
* Return the world-space `Transform` for the named part at the solved pose.
|
|
38589
38716
|
*
|
|
@@ -38836,14 +38963,19 @@ class Assembly {
|
|
|
38836
38963
|
__publicField(this, "parts", /* @__PURE__ */ new Map());
|
|
38837
38964
|
__publicField(this, "joints", /* @__PURE__ */ new Map());
|
|
38838
38965
|
__publicField(this, "jointCouplings", /* @__PURE__ */ new Map());
|
|
38966
|
+
__publicField(this, "frames", /* @__PURE__ */ new Map());
|
|
38967
|
+
__publicField(this, "frameJoints", /* @__PURE__ */ new Map());
|
|
38968
|
+
__publicField(this, "frameEdges", /* @__PURE__ */ new Map());
|
|
38839
38969
|
__publicField(this, "links", /* @__PURE__ */ new Map());
|
|
38840
38970
|
__publicField(this, "linkEdges", /* @__PURE__ */ new Map());
|
|
38841
38971
|
__publicField(this, "linkAngles", /* @__PURE__ */ new Map());
|
|
38972
|
+
__publicField(this, "derivedLinks", /* @__PURE__ */ new Map());
|
|
38842
38973
|
__publicField(this, "_mateFns", []);
|
|
38843
38974
|
__publicField(this, "_refs", createPlacementReferences());
|
|
38844
38975
|
__publicField(this, "_portsByPart", /* @__PURE__ */ new Map());
|
|
38845
38976
|
__publicField(this, "_usedPortRefs", /* @__PURE__ */ new Set());
|
|
38846
38977
|
__publicField(this, "_connectCounter", 0);
|
|
38978
|
+
__publicField(this, "_frameEdgeCounter", 0);
|
|
38847
38979
|
__publicField(this, "_linkEdgeCounter", 0);
|
|
38848
38980
|
__publicField(this, "_linkAngleCounter", 0);
|
|
38849
38981
|
this.name = name;
|
|
@@ -38853,6 +38985,7 @@ class Assembly {
|
|
|
38853
38985
|
const next = new Assembly(this.name);
|
|
38854
38986
|
next._refs = clonePlacementReferences(this._refs);
|
|
38855
38987
|
next._connectCounter = this._connectCounter;
|
|
38988
|
+
next._frameEdgeCounter = this._frameEdgeCounter;
|
|
38856
38989
|
next._linkEdgeCounter = this._linkEdgeCounter;
|
|
38857
38990
|
next._linkAngleCounter = this._linkAngleCounter;
|
|
38858
38991
|
next._mateFns.push(...this._mateFns);
|
|
@@ -38862,7 +38995,8 @@ class Assembly {
|
|
|
38862
38995
|
part: record.part,
|
|
38863
38996
|
base: record.base,
|
|
38864
38997
|
metadata: record.metadata ? { ...record.metadata } : void 0,
|
|
38865
|
-
mates: record.mates.map((mate) => ({ ...mate }))
|
|
38998
|
+
mates: record.mates.map((mate) => ({ ...mate })),
|
|
38999
|
+
bindToFrame: record.bindToFrame
|
|
38866
39000
|
});
|
|
38867
39001
|
}
|
|
38868
39002
|
for (const [name, joint2] of this.joints) {
|
|
@@ -38891,6 +39025,27 @@ class Assembly {
|
|
|
38891
39025
|
offset: coupling.offset
|
|
38892
39026
|
});
|
|
38893
39027
|
}
|
|
39028
|
+
for (const [name, frame] of this.frames) {
|
|
39029
|
+
next.frames.set(name, cloneAssemblyFrame(frame));
|
|
39030
|
+
}
|
|
39031
|
+
for (const [name, joint2] of this.frameJoints) {
|
|
39032
|
+
next.frameJoints.set(name, {
|
|
39033
|
+
name: joint2.name,
|
|
39034
|
+
type: joint2.type,
|
|
39035
|
+
parent: joint2.parent,
|
|
39036
|
+
child: joint2.child,
|
|
39037
|
+
rest: joint2.rest,
|
|
39038
|
+
min: joint2.min,
|
|
39039
|
+
max: joint2.max,
|
|
39040
|
+
defaultValue: joint2.defaultValue,
|
|
39041
|
+
unit: joint2.unit,
|
|
39042
|
+
control: joint2.control,
|
|
39043
|
+
metadata: joint2.metadata ? { ...joint2.metadata } : void 0
|
|
39044
|
+
});
|
|
39045
|
+
}
|
|
39046
|
+
for (const [name, edge] of this.frameEdges) {
|
|
39047
|
+
next.frameEdges.set(name, cloneAssemblyFrameEdge(edge));
|
|
39048
|
+
}
|
|
38894
39049
|
for (const [name, link] of this.links) {
|
|
38895
39050
|
next.links.set(name, {
|
|
38896
39051
|
name: link.name,
|
|
@@ -38918,6 +39073,7 @@ class Assembly {
|
|
|
38918
39073
|
a: angle.a,
|
|
38919
39074
|
b: angle.b,
|
|
38920
39075
|
c: angle.c,
|
|
39076
|
+
reference: cloneAngleReference(angle.reference),
|
|
38921
39077
|
target: angle.target,
|
|
38922
39078
|
min: angle.min,
|
|
38923
39079
|
max: angle.max,
|
|
@@ -38925,6 +39081,9 @@ class Assembly {
|
|
|
38925
39081
|
metadata: angle.metadata ? { ...angle.metadata } : void 0
|
|
38926
39082
|
});
|
|
38927
39083
|
}
|
|
39084
|
+
for (const [name, derived] of this.derivedLinks) {
|
|
39085
|
+
next.derivedLinks.set(name, { ...derived });
|
|
39086
|
+
}
|
|
38928
39087
|
for (const [partName, ports] of this._portsByPart) {
|
|
38929
39088
|
next._portsByPart.set(partName, clonePortMap(ports));
|
|
38930
39089
|
}
|
|
@@ -39062,6 +39221,129 @@ class Assembly {
|
|
|
39062
39221
|
port: resolved.connector
|
|
39063
39222
|
};
|
|
39064
39223
|
}
|
|
39224
|
+
/**
|
|
39225
|
+
* Add a named rig frame to the assembly.
|
|
39226
|
+
*
|
|
39227
|
+
* A frame is a solved pose: `origin` plus orientation. `axis` is the frame's
|
|
39228
|
+
* primary direction and `up` fixes roll around that axis. Use frames for
|
|
39229
|
+
* robot links, joint axes, and parts that must carry orientation. Use
|
|
39230
|
+
* `link()` for solved points in distance/angle graphs.
|
|
39231
|
+
*
|
|
39232
|
+
* @category Assembly
|
|
39233
|
+
*/
|
|
39234
|
+
frame(name, options) {
|
|
39235
|
+
const id = typeof name === "string" ? name.trim() : "";
|
|
39236
|
+
if (!id) throw new Error("frame() name must be non-empty");
|
|
39237
|
+
if (this.frames.has(id)) throw new Error(`Frame "${id}" already exists`);
|
|
39238
|
+
this.frames.set(id, normalizeAssemblyFrame(id, options));
|
|
39239
|
+
return this;
|
|
39240
|
+
}
|
|
39241
|
+
resolveFrameJointEndpoint(jointName, value, role) {
|
|
39242
|
+
const frameName = typeof value === "string" ? value.trim() : "";
|
|
39243
|
+
if (!frameName) throw new Error(`Frame joint "${jointName}" ${role} must be non-empty`);
|
|
39244
|
+
const frame = this.frames.get(frameName);
|
|
39245
|
+
if (!frame) throw new Error(`Frame joint "${jointName}" unknown ${role} frame "${frameName}"`);
|
|
39246
|
+
return frame;
|
|
39247
|
+
}
|
|
39248
|
+
addFrameJoint(type, name, options) {
|
|
39249
|
+
const id = typeof name === "string" ? name.trim() : "";
|
|
39250
|
+
if (!id) throw new Error(`${type}Joint() name must be non-empty`);
|
|
39251
|
+
if (!options || typeof options !== "object") throw new Error(`${type}Joint("${id}") options are required`);
|
|
39252
|
+
if (this.frameJoints.has(id) || this.joints.has(id)) throw new Error(`Joint "${id}" already exists`);
|
|
39253
|
+
const parent = this.resolveFrameJointEndpoint(id, options.parent, "parent");
|
|
39254
|
+
const child = this.resolveFrameJointEndpoint(id, options.child, "child");
|
|
39255
|
+
if (parent.name === child.name) throw new Error(`Frame joint "${id}" cannot connect a frame to itself`);
|
|
39256
|
+
if (child.fixed) throw new Error(`Frame joint "${id}" cannot drive fixed frame "${child.name}"`);
|
|
39257
|
+
for (const joint2 of this.frameJoints.values()) {
|
|
39258
|
+
if (joint2.child === child.name) throw new Error(`Frame "${child.name}" already has parent joint "${joint2.name}"`);
|
|
39259
|
+
}
|
|
39260
|
+
const movingOptions = options;
|
|
39261
|
+
const min2 = movingOptions.min;
|
|
39262
|
+
const max2 = movingOptions.max;
|
|
39263
|
+
if (min2 !== void 0 && !Number.isFinite(min2)) throw new Error(`Frame joint "${id}" min must be finite`);
|
|
39264
|
+
if (max2 !== void 0 && !Number.isFinite(max2)) throw new Error(`Frame joint "${id}" max must be finite`);
|
|
39265
|
+
if (min2 !== void 0 && max2 !== void 0 && min2 > max2) throw new Error(`Frame joint "${id}" min must be <= max`);
|
|
39266
|
+
const defaultValue = type === "fixed" ? 0 : movingOptions.default ?? 0;
|
|
39267
|
+
if (!Number.isFinite(defaultValue)) throw new Error(`Frame joint "${id}" default must be finite`);
|
|
39268
|
+
this.frameJoints.set(id, {
|
|
39269
|
+
name: id,
|
|
39270
|
+
type,
|
|
39271
|
+
parent: parent.name,
|
|
39272
|
+
child: child.name,
|
|
39273
|
+
rest: relativeFrameTransform(parent, child),
|
|
39274
|
+
min: min2,
|
|
39275
|
+
max: max2,
|
|
39276
|
+
defaultValue: clampFrameJointDefault(id, defaultValue, min2, max2),
|
|
39277
|
+
unit: movingOptions.unit,
|
|
39278
|
+
control: type !== "fixed" && movingOptions.control !== false,
|
|
39279
|
+
metadata: options.metadata ? { ...options.metadata } : void 0
|
|
39280
|
+
});
|
|
39281
|
+
return this;
|
|
39282
|
+
}
|
|
39283
|
+
/**
|
|
39284
|
+
* Rigidly attach a child rig frame to a parent rig frame.
|
|
39285
|
+
*
|
|
39286
|
+
* Fixed joints carry frame hierarchy but do not expose a Motion control.
|
|
39287
|
+
*
|
|
39288
|
+
* @category Assembly
|
|
39289
|
+
*/
|
|
39290
|
+
fixedJoint(name, options) {
|
|
39291
|
+
return this.addFrameJoint("fixed", name, options);
|
|
39292
|
+
}
|
|
39293
|
+
/**
|
|
39294
|
+
* Add a revolute rig-frame joint.
|
|
39295
|
+
*
|
|
39296
|
+
* The child frame rotates around the parent frame's `axis` direction. Moving
|
|
39297
|
+
* frame joints appear in Motion by default; pass `control: false` to keep the
|
|
39298
|
+
* joint solved at its default value without showing a Motion control.
|
|
39299
|
+
*
|
|
39300
|
+
* @category Assembly
|
|
39301
|
+
*/
|
|
39302
|
+
revoluteJoint(name, options) {
|
|
39303
|
+
return this.addFrameJoint("revolute", name, options);
|
|
39304
|
+
}
|
|
39305
|
+
/**
|
|
39306
|
+
* Add a prismatic rig-frame joint.
|
|
39307
|
+
*
|
|
39308
|
+
* The child frame translates along the parent frame's `axis` direction. Moving
|
|
39309
|
+
* frame joints appear in Motion by default; pass `control: false` to keep the
|
|
39310
|
+
* joint solved at its default value without showing a Motion control.
|
|
39311
|
+
*
|
|
39312
|
+
* @category Assembly
|
|
39313
|
+
*/
|
|
39314
|
+
prismaticJoint(name, options) {
|
|
39315
|
+
return this.addFrameJoint("prismatic", name, options);
|
|
39316
|
+
}
|
|
39317
|
+
/**
|
|
39318
|
+
* Add a visual skeleton edge between two rig frame origins.
|
|
39319
|
+
*
|
|
39320
|
+
* Frame edges follow the solved frame poses produced by `fixedJoint()`,
|
|
39321
|
+
* `revoluteJoint()`, and `prismaticJoint()`. They do not add constraints,
|
|
39322
|
+
* degrees of freedom, parts, or geometry; use them to make a frame-only rig
|
|
39323
|
+
* readable in the Motion/rig inspection overlay.
|
|
39324
|
+
*
|
|
39325
|
+
* @category Assembly
|
|
39326
|
+
*/
|
|
39327
|
+
edgeBetweenFrames(a2, b, options = {}) {
|
|
39328
|
+
const frameA = typeof a2 === "string" ? a2.trim() : "";
|
|
39329
|
+
const frameB = typeof b === "string" ? b.trim() : "";
|
|
39330
|
+
if (!frameA) throw new Error("edgeBetweenFrames() first frame must be non-empty");
|
|
39331
|
+
if (!frameB) throw new Error("edgeBetweenFrames() second frame must be non-empty");
|
|
39332
|
+
if (!this.frames.has(frameA)) throw new Error(`edgeBetweenFrames() unknown frame "${frameA}"`);
|
|
39333
|
+
if (!this.frames.has(frameB)) throw new Error(`edgeBetweenFrames() unknown frame "${frameB}"`);
|
|
39334
|
+
if (frameA === frameB) throw new Error("edgeBetweenFrames() requires two different frames");
|
|
39335
|
+
if (options.name !== void 0 && typeof options.name !== "string") throw new Error("edgeBetweenFrames() name must be a string");
|
|
39336
|
+
const name = (options.name ?? `${frameA}_${frameB}_${this._frameEdgeCounter++}`).trim();
|
|
39337
|
+
if (!name) throw new Error("edgeBetweenFrames() name must be non-empty");
|
|
39338
|
+
if (this.frameEdges.has(name)) throw new Error(`Frame edge "${name}" already exists`);
|
|
39339
|
+
this.frameEdges.set(name, {
|
|
39340
|
+
name,
|
|
39341
|
+
a: frameA,
|
|
39342
|
+
b: frameB,
|
|
39343
|
+
metadata: options.metadata ? { ...options.metadata } : void 0
|
|
39344
|
+
});
|
|
39345
|
+
return this;
|
|
39346
|
+
}
|
|
39065
39347
|
/**
|
|
39066
39348
|
* Add a named kinematic link to the assembly graph.
|
|
39067
39349
|
*
|
|
@@ -39109,6 +39391,11 @@ class Assembly {
|
|
|
39109
39391
|
if (options.min !== void 0 && options.max !== void 0 && options.min > options.max) {
|
|
39110
39392
|
throw new Error(`Link edge "${name}" min must be <= max`);
|
|
39111
39393
|
}
|
|
39394
|
+
const isStructural = !options.visualOnly && options.length !== "free";
|
|
39395
|
+
if (isStructural) {
|
|
39396
|
+
this.assertNotDerivedStructuralLink(a2, "edgeBetweenLinks()");
|
|
39397
|
+
this.assertNotDerivedStructuralLink(b, "edgeBetweenLinks()");
|
|
39398
|
+
}
|
|
39112
39399
|
let length4;
|
|
39113
39400
|
if (options.visualOnly || options.length === "free") {
|
|
39114
39401
|
length4 = null;
|
|
@@ -39117,6 +39404,11 @@ class Assembly {
|
|
|
39117
39404
|
length4 = options.length;
|
|
39118
39405
|
} else {
|
|
39119
39406
|
length4 = vecDistance(linkA.at, linkB.at);
|
|
39407
|
+
if (length4 <= 1e-10) {
|
|
39408
|
+
throw new Error(
|
|
39409
|
+
`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 }.`
|
|
39410
|
+
);
|
|
39411
|
+
}
|
|
39120
39412
|
}
|
|
39121
39413
|
this.linkEdges.set(name, {
|
|
39122
39414
|
name,
|
|
@@ -39141,13 +39433,135 @@ class Assembly {
|
|
|
39141
39433
|
* @category Assembly
|
|
39142
39434
|
*/
|
|
39143
39435
|
addAngleBetweenLinks(a2, b, c2, options = {}) {
|
|
39144
|
-
var _a3, _b3;
|
|
39145
39436
|
for (const id of [a2, b, c2]) {
|
|
39146
39437
|
if (!this.links.has(id)) throw new Error(`addAngleBetweenLinks() unknown link "${id}"`);
|
|
39438
|
+
this.assertNotDerivedStructuralLink(id, "addAngleBetweenLinks()");
|
|
39147
39439
|
}
|
|
39148
39440
|
if (a2 === b || b === c2 || a2 === c2) throw new Error("addAngleBetweenLinks() requires three different links");
|
|
39149
39441
|
const name = options.name ?? `${a2}_${b}_${c2}_${this._linkAngleCounter++}`;
|
|
39150
39442
|
if (this.linkAngles.has(name)) throw new Error(`Link angle "${name}" already exists`);
|
|
39443
|
+
const normalized = this.normalizeAngleOptions(name, options);
|
|
39444
|
+
this.linkAngles.set(name, {
|
|
39445
|
+
name,
|
|
39446
|
+
a: a2,
|
|
39447
|
+
b,
|
|
39448
|
+
c: c2,
|
|
39449
|
+
...normalized
|
|
39450
|
+
});
|
|
39451
|
+
return this;
|
|
39452
|
+
}
|
|
39453
|
+
/**
|
|
39454
|
+
* Add an absolute angle relationship from a world direction to a link segment.
|
|
39455
|
+
*
|
|
39456
|
+
* The first link is the vertex/pivot and the second link is the moving point.
|
|
39457
|
+
* A value of `0` places `fromLink -> toLink` along `direction` in the
|
|
39458
|
+
* mechanism plane; positive angles rotate counter-clockwise in that plane.
|
|
39459
|
+
*
|
|
39460
|
+
* Use `Points.polar(1, angleDeg)` when the reference direction is planar and
|
|
39461
|
+
* angle-based instead of axis-aligned.
|
|
39462
|
+
*
|
|
39463
|
+
* @category Assembly
|
|
39464
|
+
*/
|
|
39465
|
+
addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, direction2, options = {}) {
|
|
39466
|
+
const caller = "addAngleBetweenLinkSegmentAndWorldDirection()";
|
|
39467
|
+
for (const id of [fromLink, toLink]) {
|
|
39468
|
+
if (!this.links.has(id)) throw new Error(`${caller} unknown link "${id}"`);
|
|
39469
|
+
this.assertNotDerivedStructuralLink(id, caller);
|
|
39470
|
+
}
|
|
39471
|
+
if (fromLink === toLink) throw new Error(`${caller} requires two different links`);
|
|
39472
|
+
const referenceDirection = finiteVec3(direction2, `${caller} direction`);
|
|
39473
|
+
if (vecLength(referenceDirection) <= 1e-10) throw new Error(`${caller} direction must be non-zero`);
|
|
39474
|
+
const name = options.name ?? `${fromLink}_${toLink}_from_direction_${this._linkAngleCounter++}`;
|
|
39475
|
+
if (this.linkAngles.has(name)) throw new Error(`Link angle "${name}" already exists`);
|
|
39476
|
+
this.linkAngles.set(name, {
|
|
39477
|
+
name,
|
|
39478
|
+
b: fromLink,
|
|
39479
|
+
c: toLink,
|
|
39480
|
+
reference: { kind: "worldDirection", direction: vecNormalize(referenceDirection) },
|
|
39481
|
+
...this.normalizeAngleOptions(name, options)
|
|
39482
|
+
});
|
|
39483
|
+
return this;
|
|
39484
|
+
}
|
|
39485
|
+
/**
|
|
39486
|
+
* @deprecated Use `addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [1, 0, 0], options)`.
|
|
39487
|
+
* @skillSuppress Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead.
|
|
39488
|
+
*/
|
|
39489
|
+
addAngleOfLinkSegmentFromXAxis(fromLink, toLink, options = {}) {
|
|
39490
|
+
throw new Error(
|
|
39491
|
+
"addAngleOfLinkSegmentFromXAxis() has been replaced by addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [1, 0, 0], options). Update your script."
|
|
39492
|
+
);
|
|
39493
|
+
}
|
|
39494
|
+
/**
|
|
39495
|
+
* @deprecated Use `addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [0, 1, 0], options)`.
|
|
39496
|
+
* @skillSuppress Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead.
|
|
39497
|
+
*/
|
|
39498
|
+
addAngleOfLinkSegmentFromYAxis(fromLink, toLink, options = {}) {
|
|
39499
|
+
throw new Error(
|
|
39500
|
+
"addAngleOfLinkSegmentFromYAxis() has been replaced by addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [0, 1, 0], options). Update your script."
|
|
39501
|
+
);
|
|
39502
|
+
}
|
|
39503
|
+
assertNotDerivedStructuralLink(name, caller) {
|
|
39504
|
+
if (!this.derivedLinks.has(name)) return;
|
|
39505
|
+
throw new Error(
|
|
39506
|
+
`${caller} cannot structurally constrain derived link "${name}". Derived links are recomputed after the primary link solve.`
|
|
39507
|
+
);
|
|
39508
|
+
}
|
|
39509
|
+
assertCanBecomeDerivedLink(name) {
|
|
39510
|
+
const link = this.links.get(name);
|
|
39511
|
+
if (link == null ? void 0 : link.fixed) throw new Error(`Derived link "${name}" cannot be fixed`);
|
|
39512
|
+
for (const edge of this.linkEdges.values()) {
|
|
39513
|
+
if (edge.visualOnly || edge.length == null) continue;
|
|
39514
|
+
if (edge.a === name || edge.b === name) throw new Error(`Derived link "${name}" cannot already have structural edge "${edge.name}"`);
|
|
39515
|
+
}
|
|
39516
|
+
for (const angle of this.linkAngles.values()) {
|
|
39517
|
+
if (angle.a === name || angle.b === name || angle.c === name) {
|
|
39518
|
+
throw new Error(`Derived link "${name}" cannot already participate in angle "${angle.name}"`);
|
|
39519
|
+
}
|
|
39520
|
+
}
|
|
39521
|
+
}
|
|
39522
|
+
addDerivedLink(direction2, name, fromLink, referenceLink, distance2) {
|
|
39523
|
+
const id = typeof name === "string" ? name.trim() : "";
|
|
39524
|
+
if (!id) throw new Error(`${direction2 === "toward" ? "linkToward" : "linkAwayFrom"}() name must be non-empty`);
|
|
39525
|
+
if (!this.links.has(fromLink)) throw new Error(`Derived link "${id}" unknown fromLink "${fromLink}"`);
|
|
39526
|
+
if (!this.links.has(referenceLink)) throw new Error(`Derived link "${id}" unknown reference link "${referenceLink}"`);
|
|
39527
|
+
if (id === fromLink || id === referenceLink || fromLink === referenceLink) {
|
|
39528
|
+
throw new Error(`Derived link "${id}" requires three different links`);
|
|
39529
|
+
}
|
|
39530
|
+
if (!Number.isFinite(distance2) || distance2 < 0) {
|
|
39531
|
+
throw new Error(`Derived link "${id}" distance must be a finite value >= 0`);
|
|
39532
|
+
}
|
|
39533
|
+
if (!this.links.has(id)) {
|
|
39534
|
+
this.link(id);
|
|
39535
|
+
}
|
|
39536
|
+
this.assertCanBecomeDerivedLink(id);
|
|
39537
|
+
this.derivedLinks.set(id, { name: id, fromLink, referenceLink, distance: distance2, direction: direction2 });
|
|
39538
|
+
return this;
|
|
39539
|
+
}
|
|
39540
|
+
/**
|
|
39541
|
+
* Create a derived link at a fixed distance from `fromLink` toward `towardLink`.
|
|
39542
|
+
*
|
|
39543
|
+
* Derived links are trace/reference points. They are recomputed after the
|
|
39544
|
+
* primary link solve and cannot participate in structural edges or angle
|
|
39545
|
+
* constraints.
|
|
39546
|
+
*
|
|
39547
|
+
* @category Assembly
|
|
39548
|
+
*/
|
|
39549
|
+
linkToward(name, fromLink, towardLink, distance2) {
|
|
39550
|
+
return this.addDerivedLink("toward", name, fromLink, towardLink, distance2);
|
|
39551
|
+
}
|
|
39552
|
+
/**
|
|
39553
|
+
* Create a derived link at a fixed distance from `fromLink` away from `awayFromLink`.
|
|
39554
|
+
*
|
|
39555
|
+
* Use this for coupler trace/extension points such as the Chebyshev lambda
|
|
39556
|
+
* linkage's point beyond the rocker joint.
|
|
39557
|
+
*
|
|
39558
|
+
* @category Assembly
|
|
39559
|
+
*/
|
|
39560
|
+
linkAwayFrom(name, fromLink, awayFromLink, distance2) {
|
|
39561
|
+
return this.addDerivedLink("awayFrom", name, fromLink, awayFromLink, distance2);
|
|
39562
|
+
}
|
|
39563
|
+
normalizeAngleOptions(name, options) {
|
|
39564
|
+
var _a3, _b3;
|
|
39151
39565
|
const control = options.control === true ? { min: options.min, max: options.max, default: options.value } : options.control ? { ...options.control } : void 0;
|
|
39152
39566
|
const min2 = ((_a3 = options.limit) == null ? void 0 : _a3.min) ?? options.min;
|
|
39153
39567
|
const max2 = ((_b3 = options.limit) == null ? void 0 : _b3.max) ?? options.max;
|
|
@@ -39156,18 +39570,7 @@ class Assembly {
|
|
|
39156
39570
|
if (min2 !== void 0 && max2 !== void 0 && min2 > max2) throw new Error(`Link angle "${name}" min must be <= max`);
|
|
39157
39571
|
const target = options.value ?? (control == null ? void 0 : control.default);
|
|
39158
39572
|
if (target !== void 0 && !Number.isFinite(target)) throw new Error(`Link angle "${name}" value must be finite`);
|
|
39159
|
-
|
|
39160
|
-
name,
|
|
39161
|
-
a: a2,
|
|
39162
|
-
b,
|
|
39163
|
-
c: c2,
|
|
39164
|
-
target,
|
|
39165
|
-
min: min2,
|
|
39166
|
-
max: max2,
|
|
39167
|
-
control,
|
|
39168
|
-
metadata: options.metadata ? { ...options.metadata } : void 0
|
|
39169
|
-
});
|
|
39170
|
-
return this;
|
|
39573
|
+
return { target, min: min2, max: max2, control, metadata: options.metadata ? { ...options.metadata } : void 0 };
|
|
39171
39574
|
}
|
|
39172
39575
|
/** Return the assembly-native kinematic graph definition. */
|
|
39173
39576
|
describeKinematics() {
|
|
@@ -39194,30 +39597,23 @@ class Assembly {
|
|
|
39194
39597
|
a: angle.a,
|
|
39195
39598
|
b: angle.b,
|
|
39196
39599
|
c: angle.c,
|
|
39600
|
+
reference: cloneAngleReference(angle.reference),
|
|
39197
39601
|
target: angle.target,
|
|
39198
39602
|
min: angle.min,
|
|
39199
39603
|
max: angle.max,
|
|
39200
39604
|
control: angle.control ? { ...angle.control } : void 0,
|
|
39201
39605
|
metadata: angle.metadata ? { ...angle.metadata } : void 0
|
|
39202
|
-
}))
|
|
39606
|
+
})),
|
|
39607
|
+
derivedLinks: [...this.derivedLinks.values()].map((derived) => ({ ...derived }))
|
|
39203
39608
|
};
|
|
39204
39609
|
}
|
|
39205
39610
|
/**
|
|
39206
|
-
*
|
|
39207
|
-
*
|
|
39208
|
-
*
|
|
39209
|
-
*
|
|
39210
|
-
* Useful when you need a named pivot point or coordinate frame that has no
|
|
39211
|
-
* visual geometry. Acts like a zero-volume part and can be connected to
|
|
39212
|
-
* other parts via joints.
|
|
39213
|
-
*
|
|
39214
|
-
* @param name - Unique part name for the frame in the assembly graph
|
|
39215
|
-
* @param options - Optional transform and metadata
|
|
39216
|
-
* @returns `this` for chaining
|
|
39217
|
-
* @category Assembly
|
|
39611
|
+
* @deprecated `addFrame()` has been removed. Use `frame()` for rig frames, or
|
|
39612
|
+
* `addPart(name, group())` for an empty placeholder part.
|
|
39613
|
+
* @internal
|
|
39218
39614
|
*/
|
|
39219
|
-
addFrame(
|
|
39220
|
-
|
|
39615
|
+
addFrame(_name2, _options = {}) {
|
|
39616
|
+
throw new Error("addFrame() has been removed. Use frame() for rig frames, or addPart(name, group()) for an empty placeholder part.");
|
|
39221
39617
|
}
|
|
39222
39618
|
/**
|
|
39223
39619
|
* Add a named part to the assembly.
|
|
@@ -39257,6 +39653,16 @@ class Assembly {
|
|
|
39257
39653
|
addPart(name, part, options = {}) {
|
|
39258
39654
|
if (this.parts.has(name)) throw new Error(`Part "${name}" already exists`);
|
|
39259
39655
|
const mates = options.mate == null ? [] : Array.isArray(options.mate) ? options.mate : [options.mate];
|
|
39656
|
+
if (options.bindToFrame != null && typeof options.bindToFrame !== "string") {
|
|
39657
|
+
throw new Error(`addPart("${name}") bindToFrame must be a frame name string`);
|
|
39658
|
+
}
|
|
39659
|
+
const bindToFrame = typeof options.bindToFrame === "string" ? options.bindToFrame.trim() : "";
|
|
39660
|
+
if (bindToFrame && mates.length > 0) {
|
|
39661
|
+
throw new Error(`addPart("${name}") cannot use both bindToFrame and mate`);
|
|
39662
|
+
}
|
|
39663
|
+
if (bindToFrame && !this.frames.has(bindToFrame)) {
|
|
39664
|
+
throw new Error(`addPart("${name}") bindToFrame references unknown frame "${bindToFrame}"`);
|
|
39665
|
+
}
|
|
39260
39666
|
this.parts.set(name, {
|
|
39261
39667
|
name,
|
|
39262
39668
|
part,
|
|
@@ -39274,7 +39680,8 @@ class Assembly {
|
|
|
39274
39680
|
if (aimLink === toLink) throw new Error(`addPart("${name}") mate[${index2}] aimLink must differ from toLink`);
|
|
39275
39681
|
}
|
|
39276
39682
|
return aimLink ? { connector, toLink, aimLink } : { connector, toLink };
|
|
39277
|
-
})
|
|
39683
|
+
}),
|
|
39684
|
+
bindToFrame: bindToFrame || void 0
|
|
39278
39685
|
});
|
|
39279
39686
|
let ports = {};
|
|
39280
39687
|
if (part instanceof Shape) {
|
|
@@ -39320,10 +39727,15 @@ class Assembly {
|
|
|
39320
39727
|
* @internal
|
|
39321
39728
|
*/
|
|
39322
39729
|
addJoint(name, type, parent, child, options = {}) {
|
|
39323
|
-
if (this.joints.has(name)) throw new Error(`Joint "${name}" already exists`);
|
|
39730
|
+
if (this.joints.has(name) || this.frameJoints.has(name)) throw new Error(`Joint "${name}" already exists`);
|
|
39324
39731
|
if (!this.parts.has(parent)) throw new Error(`Unknown parent part "${parent}"`);
|
|
39325
39732
|
if (!this.parts.has(child)) throw new Error(`Unknown child part "${child}"`);
|
|
39326
39733
|
if (parent === child) throw new Error(`Joint "${name}" cannot connect a part to itself`);
|
|
39734
|
+
const parentRecord = this.parts.get(parent);
|
|
39735
|
+
const childRecord = this.parts.get(child);
|
|
39736
|
+
if (parentRecord.bindToFrame || childRecord.bindToFrame) {
|
|
39737
|
+
throw new Error(`Joint "${name}" cannot connect frame-bound parts. Use frame joints for parts added with bindToFrame.`);
|
|
39738
|
+
}
|
|
39327
39739
|
if (options.frame && options.origin) {
|
|
39328
39740
|
throw new Error(`Joint "${name}" cannot have both frame and origin`);
|
|
39329
39741
|
}
|
|
@@ -39600,7 +40012,578 @@ class Assembly {
|
|
|
39600
40012
|
"addGearCoupling() has been removed from the modeling API. Express gear behavior through assembly kinematic constraints instead."
|
|
39601
40013
|
);
|
|
39602
40014
|
}
|
|
40015
|
+
solveFrames(state) {
|
|
40016
|
+
const transforms = /* @__PURE__ */ new Map();
|
|
40017
|
+
const frames = /* @__PURE__ */ new Map();
|
|
40018
|
+
const jointValues = {};
|
|
40019
|
+
const warnings = [];
|
|
40020
|
+
if (this.frames.size === 0) return { frames, transforms, jointValues, warnings };
|
|
40021
|
+
const incoming = /* @__PURE__ */ new Map();
|
|
40022
|
+
const jointsByParent = /* @__PURE__ */ new Map();
|
|
40023
|
+
for (const joint2 of this.frameJoints.values()) {
|
|
40024
|
+
if (incoming.has(joint2.child)) {
|
|
40025
|
+
throw new Error(`Frame "${joint2.child}" has multiple parent joints`);
|
|
40026
|
+
}
|
|
40027
|
+
incoming.set(joint2.child, joint2.name);
|
|
40028
|
+
const list = jointsByParent.get(joint2.parent) ?? [];
|
|
40029
|
+
list.push(joint2);
|
|
40030
|
+
jointsByParent.set(joint2.parent, list);
|
|
40031
|
+
}
|
|
40032
|
+
const roots = [...this.frames.keys()].filter((name) => !incoming.has(name));
|
|
40033
|
+
if (roots.length === 0) throw new Error("Frame graph has no root frame");
|
|
40034
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
40035
|
+
const visited = /* @__PURE__ */ new Set();
|
|
40036
|
+
const resolveJointValue = (joint2) => {
|
|
40037
|
+
if (joint2.type === "fixed") {
|
|
40038
|
+
jointValues[joint2.name] = 0;
|
|
40039
|
+
return 0;
|
|
40040
|
+
}
|
|
40041
|
+
const hasStateValue = Object.prototype.hasOwnProperty.call(state, joint2.name);
|
|
40042
|
+
let raw = joint2.defaultValue;
|
|
40043
|
+
if (joint2.control === false) {
|
|
40044
|
+
if (hasStateValue) {
|
|
40045
|
+
warnings.push(`Frame joint "${joint2.name}" state override ignored because control is false`);
|
|
40046
|
+
}
|
|
40047
|
+
} else {
|
|
40048
|
+
raw = state[joint2.name] ?? joint2.defaultValue;
|
|
40049
|
+
}
|
|
40050
|
+
const finite = Number.isFinite(raw) ? raw : joint2.defaultValue;
|
|
40051
|
+
let clamped = finite;
|
|
40052
|
+
if (joint2.min != null) clamped = Math.max(joint2.min, clamped);
|
|
40053
|
+
if (joint2.max != null) clamped = Math.min(joint2.max, clamped);
|
|
40054
|
+
if (clamped !== raw) {
|
|
40055
|
+
warnings.push(`Frame joint "${joint2.name}" clamped from ${raw} to ${clamped}${joint2.unit ?? ""}`);
|
|
40056
|
+
}
|
|
40057
|
+
jointValues[joint2.name] = clamped;
|
|
40058
|
+
return clamped;
|
|
40059
|
+
};
|
|
40060
|
+
const visit = (frameName, worldTransform) => {
|
|
40061
|
+
if (visiting.has(frameName)) throw new Error(`Frame joint cycle detected at "${frameName}"`);
|
|
40062
|
+
const frame = this.frames.get(frameName);
|
|
40063
|
+
if (!frame) throw new Error(`Unknown frame "${frameName}"`);
|
|
40064
|
+
visiting.add(frameName);
|
|
40065
|
+
transforms.set(frameName, worldTransform);
|
|
40066
|
+
frames.set(frameName, frameFromTransform(frame, worldTransform));
|
|
40067
|
+
visited.add(frameName);
|
|
40068
|
+
for (const joint2 of jointsByParent.get(frameName) ?? []) {
|
|
40069
|
+
const value = resolveJointValue(joint2);
|
|
40070
|
+
const childWorld = composeChain(joint2.rest, frameJointMotionTransform(joint2, value), worldTransform);
|
|
40071
|
+
visit(joint2.child, childWorld);
|
|
40072
|
+
}
|
|
40073
|
+
visiting.delete(frameName);
|
|
40074
|
+
};
|
|
40075
|
+
for (const rootName of roots) {
|
|
40076
|
+
const root = this.frames.get(rootName);
|
|
40077
|
+
visit(rootName, frameDefTransform(root));
|
|
40078
|
+
}
|
|
40079
|
+
if (visited.size !== this.frames.size) {
|
|
40080
|
+
const missing = [...this.frames.keys()].filter((name) => !visited.has(name));
|
|
40081
|
+
throw new Error(`Frame graph unresolved for frames: ${missing.join(", ")}`);
|
|
40082
|
+
}
|
|
40083
|
+
return { frames, transforms, jointValues, warnings };
|
|
40084
|
+
}
|
|
40085
|
+
solveFrameEdges(frames) {
|
|
40086
|
+
const solved = [];
|
|
40087
|
+
for (const edge of this.frameEdges.values()) {
|
|
40088
|
+
const a2 = frames.get(edge.a);
|
|
40089
|
+
const b = frames.get(edge.b);
|
|
40090
|
+
if (!a2) throw new Error(`Frame edge "${edge.name}" references unresolved frame "${edge.a}"`);
|
|
40091
|
+
if (!b) throw new Error(`Frame edge "${edge.name}" references unresolved frame "${edge.b}"`);
|
|
40092
|
+
const start = cloneVec3$3(a2.origin);
|
|
40093
|
+
const end = cloneVec3$3(b.origin);
|
|
40094
|
+
solved.push({
|
|
40095
|
+
...cloneAssemblyFrameEdge(edge),
|
|
40096
|
+
start,
|
|
40097
|
+
end,
|
|
40098
|
+
solvedLength: vecDistance(start, end)
|
|
40099
|
+
});
|
|
40100
|
+
}
|
|
40101
|
+
return solved;
|
|
40102
|
+
}
|
|
40103
|
+
attachFrameEdgesToKinematics(metadata, frameEdges) {
|
|
40104
|
+
if (frameEdges.length === 0) return metadata;
|
|
40105
|
+
if (metadata) return { ...metadata, frameEdges };
|
|
40106
|
+
return {
|
|
40107
|
+
links: [],
|
|
40108
|
+
edges: [],
|
|
40109
|
+
angles: [],
|
|
40110
|
+
derivedLinks: [],
|
|
40111
|
+
frameEdges,
|
|
40112
|
+
controls: {},
|
|
40113
|
+
floatingComponents: [],
|
|
40114
|
+
diagnostics: [],
|
|
40115
|
+
maxResidual: 0,
|
|
40116
|
+
converged: true
|
|
40117
|
+
};
|
|
40118
|
+
}
|
|
39603
40119
|
solveKinematicLinks(state) {
|
|
40120
|
+
if (this.links.size === 0) {
|
|
40121
|
+
return { metadata: null, linkPositions: /* @__PURE__ */ new Map(), warnings: [] };
|
|
40122
|
+
}
|
|
40123
|
+
const plane = this.buildKinematicWorkplane();
|
|
40124
|
+
if (!plane) {
|
|
40125
|
+
return this.solveKinematicLinksLegacy(state);
|
|
40126
|
+
}
|
|
40127
|
+
return this.solveKinematicLinksPlanar(state, plane);
|
|
40128
|
+
}
|
|
40129
|
+
buildKinematicWorkplane() {
|
|
40130
|
+
const derivedNames = new Set(this.derivedLinks.keys());
|
|
40131
|
+
const primaryLinks = [...this.links.values()].filter((link) => !derivedNames.has(link.name));
|
|
40132
|
+
const originLink = primaryLinks.find((link) => link.fixed) ?? primaryLinks[0];
|
|
40133
|
+
const origin = originLink ? cloneVec3$3(originLink.at) : [0, 0, 0];
|
|
40134
|
+
let normal = null;
|
|
40135
|
+
for (const angle of this.linkAngles.values()) {
|
|
40136
|
+
const b = this.links.get(angle.b).at;
|
|
40137
|
+
const c2 = this.links.get(angle.c).at;
|
|
40138
|
+
const reference = angle.reference ? angle.reference.direction : angle.a ? vecSub(this.links.get(angle.a).at, b) : null;
|
|
40139
|
+
if (!reference) continue;
|
|
40140
|
+
const n = vecCross(reference, vecSub(c2, b));
|
|
40141
|
+
if (vecLength(n) < 1e-9) continue;
|
|
40142
|
+
const unit = vecNormalize(n);
|
|
40143
|
+
if (!normal) {
|
|
40144
|
+
normal = unit;
|
|
40145
|
+
} else if (Math.abs(vecDot(normal, unit)) < 0.999) {
|
|
40146
|
+
return null;
|
|
40147
|
+
}
|
|
40148
|
+
}
|
|
40149
|
+
normal ?? (normal = [0, 0, 1]);
|
|
40150
|
+
const projectDir = (v) => {
|
|
40151
|
+
const d2 = vecDot(v, normal);
|
|
40152
|
+
const projected = [v[0] - normal[0] * d2, v[1] - normal[1] * d2, v[2] - normal[2] * d2];
|
|
40153
|
+
const len2 = vecLength(projected);
|
|
40154
|
+
return len2 > 1e-9 ? [projected[0] / len2, projected[1] / len2, projected[2] / len2] : null;
|
|
40155
|
+
};
|
|
40156
|
+
let xAxis = null;
|
|
40157
|
+
const fixed = primaryLinks.filter((link) => link.fixed);
|
|
40158
|
+
for (let i = 0; i < fixed.length && !xAxis; i++) {
|
|
40159
|
+
for (let j = i + 1; j < fixed.length && !xAxis; j++) {
|
|
40160
|
+
xAxis = projectDir(vecSub(fixed[j].at, fixed[i].at));
|
|
40161
|
+
}
|
|
40162
|
+
}
|
|
40163
|
+
for (const edge of this.linkEdges.values()) {
|
|
40164
|
+
if (xAxis) break;
|
|
40165
|
+
if (derivedNames.has(edge.a) || derivedNames.has(edge.b)) continue;
|
|
40166
|
+
const a2 = this.links.get(edge.a);
|
|
40167
|
+
const b = this.links.get(edge.b);
|
|
40168
|
+
if (a2 && b) xAxis = projectDir(vecSub(b.at, a2.at));
|
|
40169
|
+
}
|
|
40170
|
+
for (const link of primaryLinks) {
|
|
40171
|
+
if (xAxis) break;
|
|
40172
|
+
xAxis = projectDir(vecSub(link.at, origin));
|
|
40173
|
+
}
|
|
40174
|
+
xAxis ?? (xAxis = projectDir(Math.abs(normal[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0]) ?? [1, 0, 0]);
|
|
40175
|
+
const yAxis = vecNormalize(vecCross(normal, xAxis));
|
|
40176
|
+
for (const link of primaryLinks) {
|
|
40177
|
+
const offPlane = Math.abs(vecDot(vecSub(link.at, origin), normal));
|
|
40178
|
+
if (offPlane > 1e-6) return null;
|
|
40179
|
+
}
|
|
40180
|
+
return { origin, xAxis, yAxis, normal };
|
|
40181
|
+
}
|
|
40182
|
+
solveKinematicLinksPlanar(state, plane) {
|
|
40183
|
+
var _a3, _b3, _c2, _d2, _e2, _f, _g;
|
|
40184
|
+
const derivedNames = new Set(this.derivedLinks.keys());
|
|
40185
|
+
const primaryLinks = [...this.links.values()].filter((link) => !derivedNames.has(link.name));
|
|
40186
|
+
const controls = {};
|
|
40187
|
+
const diagnosticSet = /* @__PURE__ */ new Set();
|
|
40188
|
+
const diagnostics = [];
|
|
40189
|
+
const addDiagnostic = (message) => {
|
|
40190
|
+
if (diagnosticSet.has(message)) return;
|
|
40191
|
+
diagnosticSet.add(message);
|
|
40192
|
+
diagnostics.push(message);
|
|
40193
|
+
};
|
|
40194
|
+
const project = (p2) => {
|
|
40195
|
+
const rel = vecSub(p2, plane.origin);
|
|
40196
|
+
return [vecDot(rel, plane.xAxis), vecDot(rel, plane.yAxis)];
|
|
40197
|
+
};
|
|
40198
|
+
const unproject = (p2) => [
|
|
40199
|
+
plane.origin[0] + plane.xAxis[0] * p2[0] + plane.yAxis[0] * p2[1],
|
|
40200
|
+
plane.origin[1] + plane.xAxis[1] * p2[0] + plane.yAxis[1] * p2[1],
|
|
40201
|
+
plane.origin[2] + plane.xAxis[2] * p2[0] + plane.yAxis[2] * p2[1]
|
|
40202
|
+
];
|
|
40203
|
+
const unprojectVector = (v) => [
|
|
40204
|
+
plane.xAxis[0] * v[0] + plane.yAxis[0] * v[1],
|
|
40205
|
+
plane.xAxis[1] * v[0] + plane.yAxis[1] * v[1],
|
|
40206
|
+
plane.xAxis[2] * v[0] + plane.yAxis[2] * v[1]
|
|
40207
|
+
];
|
|
40208
|
+
const projectVector = (v) => [vecDot(v, plane.xAxis), vecDot(v, plane.yAxis)];
|
|
40209
|
+
const initialPositions2d = /* @__PURE__ */ new Map();
|
|
40210
|
+
for (const link of primaryLinks) initialPositions2d.set(link.name, project(link.at));
|
|
40211
|
+
const neighbors = /* @__PURE__ */ new Map();
|
|
40212
|
+
for (const link of primaryLinks) neighbors.set(link.name, /* @__PURE__ */ new Set());
|
|
40213
|
+
for (const edge of this.linkEdges.values()) {
|
|
40214
|
+
if (derivedNames.has(edge.a) || derivedNames.has(edge.b)) continue;
|
|
40215
|
+
(_a3 = neighbors.get(edge.a)) == null ? void 0 : _a3.add(edge.b);
|
|
40216
|
+
(_b3 = neighbors.get(edge.b)) == null ? void 0 : _b3.add(edge.a);
|
|
40217
|
+
}
|
|
40218
|
+
for (const angle of this.linkAngles.values()) {
|
|
40219
|
+
if (angle.a) {
|
|
40220
|
+
(_c2 = neighbors.get(angle.a)) == null ? void 0 : _c2.add(angle.b);
|
|
40221
|
+
(_d2 = neighbors.get(angle.b)) == null ? void 0 : _d2.add(angle.a);
|
|
40222
|
+
}
|
|
40223
|
+
(_e2 = neighbors.get(angle.b)) == null ? void 0 : _e2.add(angle.c);
|
|
40224
|
+
(_f = neighbors.get(angle.c)) == null ? void 0 : _f.add(angle.b);
|
|
40225
|
+
}
|
|
40226
|
+
const floatingComponents = [];
|
|
40227
|
+
const gaugeFixed = /* @__PURE__ */ new Set();
|
|
40228
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40229
|
+
for (const link of primaryLinks.map((link2) => link2.name)) {
|
|
40230
|
+
if (seen.has(link)) continue;
|
|
40231
|
+
const stack = [link];
|
|
40232
|
+
const component = [];
|
|
40233
|
+
seen.add(link);
|
|
40234
|
+
while (stack.length > 0) {
|
|
40235
|
+
const next = stack.pop();
|
|
40236
|
+
component.push(next);
|
|
40237
|
+
for (const neighbor of neighbors.get(next) ?? []) {
|
|
40238
|
+
if (seen.has(neighbor)) continue;
|
|
40239
|
+
seen.add(neighbor);
|
|
40240
|
+
stack.push(neighbor);
|
|
40241
|
+
}
|
|
40242
|
+
}
|
|
40243
|
+
if (!component.some((name) => {
|
|
40244
|
+
var _a4;
|
|
40245
|
+
return (_a4 = this.links.get(name)) == null ? void 0 : _a4.fixed;
|
|
40246
|
+
})) {
|
|
40247
|
+
const sorted = [...component].sort();
|
|
40248
|
+
const gaugeLink = sorted[0];
|
|
40249
|
+
gaugeFixed.add(gaugeLink);
|
|
40250
|
+
floatingComponents.push({ links: sorted, gaugeLink });
|
|
40251
|
+
}
|
|
40252
|
+
}
|
|
40253
|
+
const angleValueForState = (angle, inputState, recordControl) => {
|
|
40254
|
+
var _a4, _b4, _c3, _d3;
|
|
40255
|
+
if (!angle.control && angle.target === void 0) return null;
|
|
40256
|
+
const raw = inputState[angle.name] ?? angle.target ?? ((_a4 = angle.control) == null ? void 0 : _a4.default) ?? 0;
|
|
40257
|
+
let value = Number.isFinite(raw) ? raw : ((_b4 = angle.control) == null ? void 0 : _b4.default) ?? angle.target ?? 0;
|
|
40258
|
+
const min2 = ((_c3 = angle.control) == null ? void 0 : _c3.min) ?? angle.min;
|
|
40259
|
+
const max2 = ((_d3 = angle.control) == null ? void 0 : _d3.max) ?? angle.max;
|
|
40260
|
+
if (min2 != null) value = Math.max(min2, value);
|
|
40261
|
+
if (max2 != null) value = Math.min(max2, value);
|
|
40262
|
+
if (recordControl) controls[angle.name] = value;
|
|
40263
|
+
return value;
|
|
40264
|
+
};
|
|
40265
|
+
const controlledAngleValue = (angle) => angleValueForState(angle, state, true);
|
|
40266
|
+
const edgeLengthBetween = (x2, y2) => {
|
|
40267
|
+
for (const edge of this.linkEdges.values()) {
|
|
40268
|
+
if (edge.visualOnly || edge.length == null) continue;
|
|
40269
|
+
if (edge.a === x2 && edge.b === y2 || edge.a === y2 && edge.b === x2) return edge.length;
|
|
40270
|
+
}
|
|
40271
|
+
return null;
|
|
40272
|
+
};
|
|
40273
|
+
const seedDirection = (index2) => {
|
|
40274
|
+
const angle = index2 * Math.PI * (3 - Math.sqrt(5));
|
|
40275
|
+
return [Math.cos(angle), Math.sin(angle)];
|
|
40276
|
+
};
|
|
40277
|
+
const rotate2d = (v, angleDeg) => {
|
|
40278
|
+
const rad = angleDeg * Math.PI / 180;
|
|
40279
|
+
const c2 = Math.cos(rad);
|
|
40280
|
+
const s = Math.sin(rad);
|
|
40281
|
+
return [v[0] * c2 - v[1] * s, v[0] * s + v[1] * c2];
|
|
40282
|
+
};
|
|
40283
|
+
const angleReferenceVector2d = (angle, seedPositions) => {
|
|
40284
|
+
if (angle.reference) return projectVector(angle.reference.direction);
|
|
40285
|
+
if (!angle.a) return null;
|
|
40286
|
+
const a2 = seedPositions.get(angle.a);
|
|
40287
|
+
const b = seedPositions.get(angle.b);
|
|
40288
|
+
if (!a2 || !b) return null;
|
|
40289
|
+
return [a2[0] - b[0], a2[1] - b[1]];
|
|
40290
|
+
};
|
|
40291
|
+
const angleReferenceLabel = (angle) => angle.reference ? `world direction [${angle.reference.direction.join(", ")}]` : `reference links "${angle.a}" and "${angle.b}"`;
|
|
40292
|
+
const signedArea2d2 = (a2, b, p2) => (b[0] - a2[0]) * (p2[1] - a2[1]) - (b[1] - a2[1]) * (p2[0] - a2[0]);
|
|
40293
|
+
const circleCircleCandidates = (a2, radiusA, b, radiusB) => {
|
|
40294
|
+
const dx = b[0] - a2[0];
|
|
40295
|
+
const dy = b[1] - a2[1];
|
|
40296
|
+
const d2 = Math.hypot(dx, dy);
|
|
40297
|
+
if (d2 <= 1e-10) return [];
|
|
40298
|
+
const along = (radiusA * radiusA - radiusB * radiusB + d2 * d2) / (2 * d2);
|
|
40299
|
+
const h2 = radiusA * radiusA - along * along;
|
|
40300
|
+
if (h2 < -1e-7) return [];
|
|
40301
|
+
const h = Math.sqrt(Math.max(0, h2));
|
|
40302
|
+
const ux = dx / d2;
|
|
40303
|
+
const uy = dy / d2;
|
|
40304
|
+
const base = [a2[0] + ux * along, a2[1] + uy * along];
|
|
40305
|
+
const offset2 = [-uy * h, ux * h];
|
|
40306
|
+
return [
|
|
40307
|
+
[base[0] + offset2[0], base[1] + offset2[1]],
|
|
40308
|
+
[base[0] - offset2[0], base[1] - offset2[1]]
|
|
40309
|
+
];
|
|
40310
|
+
};
|
|
40311
|
+
const chooseCandidate = (targetName, anchors, candidates, seedPositions, branchReference2) => {
|
|
40312
|
+
if (candidates.length === 0) return null;
|
|
40313
|
+
const anchorAName = anchors[0].name;
|
|
40314
|
+
const anchorBName = anchors[1].name;
|
|
40315
|
+
const anchorA = seedPositions.get(anchorAName);
|
|
40316
|
+
const anchorB = seedPositions.get(anchorBName);
|
|
40317
|
+
let referenceSign = 0;
|
|
40318
|
+
if (branchReference2) {
|
|
40319
|
+
const refA = branchReference2.get(anchorAName);
|
|
40320
|
+
const refB = branchReference2.get(anchorBName);
|
|
40321
|
+
const refTarget = branchReference2.get(targetName);
|
|
40322
|
+
if (refA && refB && refTarget) {
|
|
40323
|
+
const area2 = signedArea2d2(refA, refB, refTarget);
|
|
40324
|
+
if (Math.abs(area2) > 1e-7) referenceSign = Math.sign(area2);
|
|
40325
|
+
}
|
|
40326
|
+
}
|
|
40327
|
+
const authored = initialPositions2d.get(targetName) ?? [0, 0];
|
|
40328
|
+
let best = null;
|
|
40329
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
40330
|
+
for (const candidate of candidates) {
|
|
40331
|
+
const candidateSign = Math.sign(signedArea2d2(anchorA, anchorB, candidate));
|
|
40332
|
+
const branchPenalty = referenceSign !== 0 && candidateSign !== referenceSign ? 1e12 : 0;
|
|
40333
|
+
const authoredDistance = Math.hypot(candidate[0] - authored[0], candidate[1] - authored[1]);
|
|
40334
|
+
const constraintResidual = anchors.reduce((sum2, anchor) => {
|
|
40335
|
+
const p2 = seedPositions.get(anchor.name);
|
|
40336
|
+
return sum2 + Math.abs(Math.hypot(candidate[0] - p2[0], candidate[1] - p2[1]) - anchor.length);
|
|
40337
|
+
}, 0);
|
|
40338
|
+
const score = branchPenalty + constraintResidual * 1e6 + authoredDistance;
|
|
40339
|
+
if (score < bestScore) {
|
|
40340
|
+
bestScore = score;
|
|
40341
|
+
best = candidate;
|
|
40342
|
+
}
|
|
40343
|
+
}
|
|
40344
|
+
return best;
|
|
40345
|
+
};
|
|
40346
|
+
const buildSeedPositions = (inputState, branchReference2, diagnosticPrefix = "") => {
|
|
40347
|
+
const seedPositions = new Map(initialPositions2d);
|
|
40348
|
+
const seeded = /* @__PURE__ */ new Set();
|
|
40349
|
+
const angleDriven = /* @__PURE__ */ new Set();
|
|
40350
|
+
const isLocked = (name) => {
|
|
40351
|
+
var _a4;
|
|
40352
|
+
return ((_a4 = this.links.get(name)) == null ? void 0 : _a4.fixed) === true || gaugeFixed.has(name);
|
|
40353
|
+
};
|
|
40354
|
+
for (const link of primaryLinks) {
|
|
40355
|
+
const p2 = seedPositions.get(link.name);
|
|
40356
|
+
if (link.fixed || gaugeFixed.has(link.name) || Math.hypot(p2[0], p2[1]) > 1e-9) seeded.add(link.name);
|
|
40357
|
+
}
|
|
40358
|
+
if (seeded.size === 0 && primaryLinks.length > 0) seeded.add(primaryLinks[0].name);
|
|
40359
|
+
let seedIndex = 1;
|
|
40360
|
+
let changed = true;
|
|
40361
|
+
for (let pass = 0; pass < primaryLinks.length * 3 && changed; pass++) {
|
|
40362
|
+
changed = false;
|
|
40363
|
+
for (const angle of this.linkAngles.values()) {
|
|
40364
|
+
const target = angleValueForState(angle, inputState, false);
|
|
40365
|
+
const radius = edgeLengthBetween(angle.b, angle.c);
|
|
40366
|
+
const hasReference = !!angle.reference || angle.a !== void 0 && seeded.has(angle.a);
|
|
40367
|
+
if (target == null || radius == null || isLocked(angle.c) || !hasReference || !seeded.has(angle.b)) continue;
|
|
40368
|
+
const b = seedPositions.get(angle.b);
|
|
40369
|
+
const ref = angleReferenceVector2d(angle, seedPositions);
|
|
40370
|
+
if (!ref) continue;
|
|
40371
|
+
const refLen = Math.hypot(ref[0], ref[1]);
|
|
40372
|
+
if (refLen <= 1e-10) {
|
|
40373
|
+
addDiagnostic(
|
|
40374
|
+
`${diagnosticPrefix}Angle "${angle.name}" cannot drive link "${angle.c}" because ${angleReferenceLabel(angle)} has no usable direction in the mechanism plane.`
|
|
40375
|
+
);
|
|
40376
|
+
continue;
|
|
40377
|
+
}
|
|
40378
|
+
const dir = rotate2d([ref[0] / refLen, ref[1] / refLen], target);
|
|
40379
|
+
seedPositions.set(angle.c, [b[0] + dir[0] * radius, b[1] + dir[1] * radius]);
|
|
40380
|
+
seeded.add(angle.c);
|
|
40381
|
+
angleDriven.add(angle.c);
|
|
40382
|
+
changed = true;
|
|
40383
|
+
}
|
|
40384
|
+
for (const link of primaryLinks) {
|
|
40385
|
+
if (isLocked(link.name) || angleDriven.has(link.name)) continue;
|
|
40386
|
+
const anchors = [];
|
|
40387
|
+
for (const edge of this.linkEdges.values()) {
|
|
40388
|
+
if (edge.visualOnly || edge.length == null || derivedNames.has(edge.a) || derivedNames.has(edge.b)) continue;
|
|
40389
|
+
if (edge.a === link.name && seeded.has(edge.b)) anchors.push({ name: edge.b, length: edge.length });
|
|
40390
|
+
if (edge.b === link.name && seeded.has(edge.a)) anchors.push({ name: edge.a, length: edge.length });
|
|
40391
|
+
}
|
|
40392
|
+
if (anchors.length < 2) continue;
|
|
40393
|
+
const a2 = anchors[0];
|
|
40394
|
+
const b = anchors[1];
|
|
40395
|
+
const candidates = circleCircleCandidates(seedPositions.get(a2.name), a2.length, seedPositions.get(b.name), b.length);
|
|
40396
|
+
const chosen = chooseCandidate(link.name, anchors, candidates, seedPositions, branchReference2);
|
|
40397
|
+
if (!chosen) {
|
|
40398
|
+
const pa = seedPositions.get(a2.name);
|
|
40399
|
+
const pb = seedPositions.get(b.name);
|
|
40400
|
+
const centerDistance = Math.hypot(pb[0] - pa[0], pb[1] - pa[1]);
|
|
40401
|
+
const minDistance = Math.abs(a2.length - b.length);
|
|
40402
|
+
const maxDistance = a2.length + b.length;
|
|
40403
|
+
addDiagnostic(
|
|
40404
|
+
`${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.`
|
|
40405
|
+
);
|
|
40406
|
+
continue;
|
|
40407
|
+
}
|
|
40408
|
+
seedPositions.set(link.name, chosen);
|
|
40409
|
+
seeded.add(link.name);
|
|
40410
|
+
changed = true;
|
|
40411
|
+
}
|
|
40412
|
+
for (const edge of this.linkEdges.values()) {
|
|
40413
|
+
if (edge.visualOnly || edge.length == null || derivedNames.has(edge.a) || derivedNames.has(edge.b)) continue;
|
|
40414
|
+
const aSeeded = seeded.has(edge.a);
|
|
40415
|
+
const bSeeded = seeded.has(edge.b);
|
|
40416
|
+
if (aSeeded === bSeeded) continue;
|
|
40417
|
+
const anchorName = aSeeded ? edge.a : edge.b;
|
|
40418
|
+
const targetName = aSeeded ? edge.b : edge.a;
|
|
40419
|
+
if (isLocked(targetName) || angleDriven.has(targetName)) continue;
|
|
40420
|
+
const anchor = seedPositions.get(anchorName);
|
|
40421
|
+
const current = seedPositions.get(targetName);
|
|
40422
|
+
const delta = [current[0] - anchor[0], current[1] - anchor[1]];
|
|
40423
|
+
const deltaLen = Math.hypot(delta[0], delta[1]);
|
|
40424
|
+
const dir = deltaLen > 1e-9 ? [delta[0] / deltaLen, delta[1] / deltaLen] : seedDirection(seedIndex++);
|
|
40425
|
+
seedPositions.set(targetName, [anchor[0] + dir[0] * edge.length, anchor[1] + dir[1] * edge.length]);
|
|
40426
|
+
seeded.add(targetName);
|
|
40427
|
+
changed = true;
|
|
40428
|
+
}
|
|
40429
|
+
}
|
|
40430
|
+
const refLength = Math.max(1, ...[...this.linkEdges.values()].map((edge) => edge.length ?? 0));
|
|
40431
|
+
for (const link of primaryLinks) {
|
|
40432
|
+
if (seeded.has(link.name)) continue;
|
|
40433
|
+
if (this.linkEdges.size > 0 || this.linkAngles.size > 0) {
|
|
40434
|
+
addDiagnostic(
|
|
40435
|
+
`${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.`
|
|
40436
|
+
);
|
|
40437
|
+
} else {
|
|
40438
|
+
const dir = seedDirection(seedIndex++);
|
|
40439
|
+
seedPositions.set(link.name, [dir[0] * refLength, dir[1] * refLength]);
|
|
40440
|
+
seeded.add(link.name);
|
|
40441
|
+
}
|
|
40442
|
+
}
|
|
40443
|
+
return seedPositions;
|
|
40444
|
+
};
|
|
40445
|
+
const defaultControlState = {};
|
|
40446
|
+
let hasDefaultControlState = false;
|
|
40447
|
+
for (const angle of this.linkAngles.values()) {
|
|
40448
|
+
if (!angle.control && angle.target === void 0) continue;
|
|
40449
|
+
const value = ((_g = angle.control) == null ? void 0 : _g.default) ?? angle.target;
|
|
40450
|
+
if (Number.isFinite(value)) {
|
|
40451
|
+
defaultControlState[angle.name] = value;
|
|
40452
|
+
hasDefaultControlState = true;
|
|
40453
|
+
}
|
|
40454
|
+
}
|
|
40455
|
+
const branchReference = hasDefaultControlState ? buildSeedPositions(defaultControlState, void 0, "Default assembly mode: ") : void 0;
|
|
40456
|
+
const positions2d = buildSeedPositions(state, branchReference);
|
|
40457
|
+
const positions = /* @__PURE__ */ new Map();
|
|
40458
|
+
for (const link of primaryLinks) {
|
|
40459
|
+
const point2 = positions2d.get(link.name);
|
|
40460
|
+
if (!point2) {
|
|
40461
|
+
addDiagnostic(`Link "${link.name}" has no solved planar position after kinematic construction.`);
|
|
40462
|
+
continue;
|
|
40463
|
+
}
|
|
40464
|
+
positions.set(link.name, unproject(point2));
|
|
40465
|
+
}
|
|
40466
|
+
const solvedDerivedLinks = this.evaluateDerivedLinks(positions);
|
|
40467
|
+
for (const derived of solvedDerivedLinks) positions.set(derived.name, cloneVec3$3(derived.position));
|
|
40468
|
+
const solvedEdges = [];
|
|
40469
|
+
const solvedAngles = [];
|
|
40470
|
+
let maxResidual = diagnostics.length > 0 ? Number.POSITIVE_INFINITY : 0;
|
|
40471
|
+
for (const edge of this.linkEdges.values()) {
|
|
40472
|
+
const edgeA = positions.get(edge.a);
|
|
40473
|
+
const edgeB = positions.get(edge.b);
|
|
40474
|
+
const solvedLength = edgeA && edgeB ? vecDistance(edgeA, edgeB) : Number.NaN;
|
|
40475
|
+
let residual = 0;
|
|
40476
|
+
if (!edgeA || !edgeB) {
|
|
40477
|
+
residual = Number.POSITIVE_INFINITY;
|
|
40478
|
+
addDiagnostic(`Edge "${edge.name}" could not be evaluated because one of its links has no solved position.`);
|
|
40479
|
+
} else {
|
|
40480
|
+
if (edge.length != null) residual = solvedLength - edge.length;
|
|
40481
|
+
if (edge.min != null && solvedLength < edge.min) residual = Math.min(residual, solvedLength - edge.min);
|
|
40482
|
+
if (edge.max != null && solvedLength > edge.max) residual = Math.max(residual, solvedLength - edge.max);
|
|
40483
|
+
}
|
|
40484
|
+
maxResidual = Math.max(maxResidual, Math.abs(residual));
|
|
40485
|
+
solvedEdges.push({
|
|
40486
|
+
name: edge.name,
|
|
40487
|
+
a: edge.a,
|
|
40488
|
+
b: edge.b,
|
|
40489
|
+
length: edge.length,
|
|
40490
|
+
solvedLength,
|
|
40491
|
+
residual,
|
|
40492
|
+
visualOnly: edge.visualOnly
|
|
40493
|
+
});
|
|
40494
|
+
}
|
|
40495
|
+
for (const angle of this.linkAngles.values()) {
|
|
40496
|
+
const angleB = positions.get(angle.b);
|
|
40497
|
+
const angleC = positions.get(angle.c);
|
|
40498
|
+
const reference = angleReferenceVector2d(angle, positions2d);
|
|
40499
|
+
const solvedValue = reference && angleB && angleC ? signedAngleBetweenVectorsAboutAxis(unprojectVector(reference), vecSub(angleC, angleB), plane.normal) : Number.NaN;
|
|
40500
|
+
const target = controlledAngleValue(angle) ?? angle.target;
|
|
40501
|
+
let residual = target == null ? 0 : normalizeAngleDeltaDeg(solvedValue - target);
|
|
40502
|
+
if (!reference || !angleB || !angleC) {
|
|
40503
|
+
residual = Number.POSITIVE_INFINITY;
|
|
40504
|
+
addDiagnostic(`Angle "${angle.name}" could not be evaluated because its reference or one of its links has no solved position.`);
|
|
40505
|
+
} else {
|
|
40506
|
+
if (angle.min != null && solvedValue < angle.min) residual = Math.min(residual, solvedValue - angle.min);
|
|
40507
|
+
if (angle.max != null && solvedValue > angle.max) residual = Math.max(residual, solvedValue - angle.max);
|
|
40508
|
+
}
|
|
40509
|
+
maxResidual = Math.max(maxResidual, Math.abs(residual));
|
|
40510
|
+
solvedAngles.push({
|
|
40511
|
+
name: angle.name,
|
|
40512
|
+
a: angle.a,
|
|
40513
|
+
b: angle.b,
|
|
40514
|
+
c: angle.c,
|
|
40515
|
+
reference: cloneAngleReference(angle.reference),
|
|
40516
|
+
target,
|
|
40517
|
+
solvedValue,
|
|
40518
|
+
residual,
|
|
40519
|
+
control: angle.control ? { ...angle.control } : void 0
|
|
40520
|
+
});
|
|
40521
|
+
}
|
|
40522
|
+
const warnings = [];
|
|
40523
|
+
for (const component of floatingComponents) {
|
|
40524
|
+
warnings.push(`Kinematic component is floating; using link "${component.gaugeLink}" as a numerical display gauge`);
|
|
40525
|
+
}
|
|
40526
|
+
if (maxResidual > 1e-3) {
|
|
40527
|
+
warnings.push(`Kinematic graph residual ${maxResidual.toFixed(6)} exceeds tolerance`);
|
|
40528
|
+
}
|
|
40529
|
+
return {
|
|
40530
|
+
metadata: {
|
|
40531
|
+
links: [...this.links.values()].map((link) => ({
|
|
40532
|
+
name: link.name,
|
|
40533
|
+
position: cloneVec3$3(positions.get(link.name) ?? link.at),
|
|
40534
|
+
fixed: link.fixed,
|
|
40535
|
+
gaugeFixed: gaugeFixed.has(link.name) || void 0
|
|
40536
|
+
})),
|
|
40537
|
+
edges: solvedEdges,
|
|
40538
|
+
angles: solvedAngles,
|
|
40539
|
+
derivedLinks: solvedDerivedLinks,
|
|
40540
|
+
controls,
|
|
40541
|
+
floatingComponents,
|
|
40542
|
+
diagnostics,
|
|
40543
|
+
maxResidual,
|
|
40544
|
+
converged: diagnostics.length === 0 && maxResidual <= 1e-3
|
|
40545
|
+
},
|
|
40546
|
+
linkPositions: positions,
|
|
40547
|
+
warnings
|
|
40548
|
+
};
|
|
40549
|
+
}
|
|
40550
|
+
evaluateDerivedLinks(basePositions) {
|
|
40551
|
+
const output = /* @__PURE__ */ new Map();
|
|
40552
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
40553
|
+
const resolve = (name) => {
|
|
40554
|
+
const existing = basePositions.get(name);
|
|
40555
|
+
if (existing && !this.derivedLinks.has(name)) return existing;
|
|
40556
|
+
const cached = output.get(name);
|
|
40557
|
+
if (cached) return cached.position;
|
|
40558
|
+
const derived = this.derivedLinks.get(name);
|
|
40559
|
+
if (!derived) {
|
|
40560
|
+
const position2 = basePositions.get(name);
|
|
40561
|
+
if (!position2) throw new Error(`Derived link references unknown solved link "${name}"`);
|
|
40562
|
+
return position2;
|
|
40563
|
+
}
|
|
40564
|
+
if (visiting.has(name)) throw new Error(`Derived link cycle detected at "${name}"`);
|
|
40565
|
+
visiting.add(name);
|
|
40566
|
+
const from = resolve(derived.fromLink);
|
|
40567
|
+
const reference = resolve(derived.referenceLink);
|
|
40568
|
+
const rawDir = derived.direction === "toward" ? vecSub(reference, from) : vecSub(from, reference);
|
|
40569
|
+
const dir = vecNormalize(rawDir);
|
|
40570
|
+
if (vecLength(dir) < 1e-10) {
|
|
40571
|
+
throw new Error(`Derived link "${name}" cannot be solved because "${derived.fromLink}" and "${derived.referenceLink}" coincide`);
|
|
40572
|
+
}
|
|
40573
|
+
const position = [
|
|
40574
|
+
from[0] + dir[0] * derived.distance,
|
|
40575
|
+
from[1] + dir[1] * derived.distance,
|
|
40576
|
+
from[2] + dir[2] * derived.distance
|
|
40577
|
+
];
|
|
40578
|
+
visiting.delete(name);
|
|
40579
|
+
const solved = { ...derived, position };
|
|
40580
|
+
output.set(name, solved);
|
|
40581
|
+
return position;
|
|
40582
|
+
};
|
|
40583
|
+
for (const name of this.derivedLinks.keys()) resolve(name);
|
|
40584
|
+
return [...output.values()];
|
|
40585
|
+
}
|
|
40586
|
+
solveKinematicLinksLegacy(state) {
|
|
39604
40587
|
var _a3, _b3, _c2, _d2, _e2, _f;
|
|
39605
40588
|
if (this.links.size === 0) {
|
|
39606
40589
|
return { metadata: null, linkPositions: /* @__PURE__ */ new Map(), warnings: [] };
|
|
@@ -39616,8 +40599,10 @@ class Assembly {
|
|
|
39616
40599
|
(_b3 = neighbors.get(edge.b)) == null ? void 0 : _b3.add(edge.a);
|
|
39617
40600
|
}
|
|
39618
40601
|
for (const angle of this.linkAngles.values()) {
|
|
39619
|
-
|
|
39620
|
-
|
|
40602
|
+
if (angle.a) {
|
|
40603
|
+
(_c2 = neighbors.get(angle.a)) == null ? void 0 : _c2.add(angle.b);
|
|
40604
|
+
(_d2 = neighbors.get(angle.b)) == null ? void 0 : _d2.add(angle.a);
|
|
40605
|
+
}
|
|
39621
40606
|
(_e2 = neighbors.get(angle.b)) == null ? void 0 : _e2.add(angle.c);
|
|
39622
40607
|
(_f = neighbors.get(angle.c)) == null ? void 0 : _f.add(angle.b);
|
|
39623
40608
|
}
|
|
@@ -39672,27 +40657,36 @@ class Assembly {
|
|
|
39672
40657
|
const link = this.links.get(name);
|
|
39673
40658
|
return !link.fixed && !gaugeFixed.has(name) && !controlledLinks.has(name);
|
|
39674
40659
|
};
|
|
40660
|
+
const angleReferenceVector = (angle, source) => {
|
|
40661
|
+
if (angle.reference) return cloneVec3$3(angle.reference.direction);
|
|
40662
|
+
if (!angle.a) return null;
|
|
40663
|
+
const a2 = source.get(angle.a);
|
|
40664
|
+
const b = source.get(angle.b);
|
|
40665
|
+
if (!a2 || !b) return null;
|
|
40666
|
+
return vecSub(a2, b);
|
|
40667
|
+
};
|
|
39675
40668
|
const anglePlaneNormal = (angle) => {
|
|
39676
|
-
const a2 = this.links.get(angle.a).at;
|
|
39677
40669
|
const b = this.links.get(angle.b).at;
|
|
39678
40670
|
const c2 = this.links.get(angle.c).at;
|
|
39679
|
-
const
|
|
40671
|
+
const reference = angle.reference ? angle.reference.direction : angle.a ? vecSub(this.links.get(angle.a).at, b) : null;
|
|
40672
|
+
if (!reference) return [0, 0, 1];
|
|
40673
|
+
const n = vecCross(reference, vecSub(c2, b));
|
|
39680
40674
|
return vecLength(n) < 1e-9 ? [0, 0, 1] : vecNormalize(n);
|
|
39681
40675
|
};
|
|
39682
40676
|
const applyAngleControls = () => {
|
|
39683
40677
|
for (const angle of this.linkAngles.values()) {
|
|
39684
40678
|
const target = controlledAngleValue(angle);
|
|
39685
40679
|
if (target == null) continue;
|
|
39686
|
-
const a2 = positions.get(angle.a);
|
|
39687
40680
|
const b = positions.get(angle.b);
|
|
39688
40681
|
const c2 = positions.get(angle.c);
|
|
39689
40682
|
if (!isMovable(angle.c) && !controlledLinks.has(angle.c)) continue;
|
|
39690
40683
|
const radius = edgeLengthBetween(angle.b, angle.c) ?? vecDistance(b, c2);
|
|
39691
40684
|
if (radius <= 1e-10) continue;
|
|
39692
|
-
const
|
|
39693
|
-
|
|
40685
|
+
const reference = angleReferenceVector(angle, positions);
|
|
40686
|
+
if (!reference) continue;
|
|
40687
|
+
const len2 = vecLength(reference);
|
|
39694
40688
|
if (len2 <= 1e-10) continue;
|
|
39695
|
-
const baUnit = [
|
|
40689
|
+
const baUnit = [reference[0] / len2, reference[1] / len2, reference[2] / len2];
|
|
39696
40690
|
const rotated = rotateAboutAxis(baUnit, anglePlaneNormal(angle), target);
|
|
39697
40691
|
positions.set(angle.c, [b[0] + rotated[0] * radius, b[1] + rotated[1] * radius, b[2] + rotated[2] * radius]);
|
|
39698
40692
|
controlledLinks.add(angle.c);
|
|
@@ -39722,6 +40716,8 @@ class Assembly {
|
|
|
39722
40716
|
}
|
|
39723
40717
|
controlledLinks.clear();
|
|
39724
40718
|
applyAngleControls();
|
|
40719
|
+
const solvedDerivedLinks = this.evaluateDerivedLinks(positions);
|
|
40720
|
+
for (const derived of solvedDerivedLinks) positions.set(derived.name, cloneVec3$3(derived.position));
|
|
39725
40721
|
const solvedEdges = [];
|
|
39726
40722
|
const solvedAngles = [];
|
|
39727
40723
|
let maxResidual = 0;
|
|
@@ -39743,14 +40739,13 @@ class Assembly {
|
|
|
39743
40739
|
});
|
|
39744
40740
|
}
|
|
39745
40741
|
for (const angle of this.linkAngles.values()) {
|
|
39746
|
-
const
|
|
39747
|
-
|
|
39748
|
-
|
|
39749
|
-
|
|
39750
|
-
anglePlaneNormal(angle)
|
|
39751
|
-
);
|
|
40742
|
+
const reference = angleReferenceVector(angle, positions);
|
|
40743
|
+
const b = positions.get(angle.b);
|
|
40744
|
+
const c2 = positions.get(angle.c);
|
|
40745
|
+
const solvedValue = reference ? signedAngleBetweenVectorsAboutAxis(reference, vecSub(c2, b), anglePlaneNormal(angle)) : Number.NaN;
|
|
39752
40746
|
const target = controlledAngleValue(angle) ?? angle.target;
|
|
39753
40747
|
let residual = target == null ? 0 : normalizeAngleDeltaDeg(solvedValue - target);
|
|
40748
|
+
if (!reference) residual = Number.POSITIVE_INFINITY;
|
|
39754
40749
|
if (angle.min != null && solvedValue < angle.min) residual = Math.min(residual, solvedValue - angle.min);
|
|
39755
40750
|
if (angle.max != null && solvedValue > angle.max) residual = Math.max(residual, solvedValue - angle.max);
|
|
39756
40751
|
maxResidual = Math.max(maxResidual, Math.abs(residual));
|
|
@@ -39759,6 +40754,7 @@ class Assembly {
|
|
|
39759
40754
|
a: angle.a,
|
|
39760
40755
|
b: angle.b,
|
|
39761
40756
|
c: angle.c,
|
|
40757
|
+
reference: cloneAngleReference(angle.reference),
|
|
39762
40758
|
target,
|
|
39763
40759
|
solvedValue,
|
|
39764
40760
|
residual,
|
|
@@ -39782,8 +40778,10 @@ class Assembly {
|
|
|
39782
40778
|
})),
|
|
39783
40779
|
edges: solvedEdges,
|
|
39784
40780
|
angles: solvedAngles,
|
|
40781
|
+
derivedLinks: solvedDerivedLinks,
|
|
39785
40782
|
controls,
|
|
39786
40783
|
floatingComponents,
|
|
40784
|
+
diagnostics: [],
|
|
39787
40785
|
maxResidual,
|
|
39788
40786
|
converged: maxResidual <= 1e-3
|
|
39789
40787
|
},
|
|
@@ -39831,8 +40829,12 @@ class Assembly {
|
|
|
39831
40829
|
const incoming = /* @__PURE__ */ new Map();
|
|
39832
40830
|
const jointsByParent = /* @__PURE__ */ new Map();
|
|
39833
40831
|
const warnings = [];
|
|
40832
|
+
const frameSolve = this.solveFrames(state);
|
|
40833
|
+
warnings.push(...frameSolve.warnings);
|
|
39834
40834
|
const kinematicSolve = this.solveKinematicLinks(state);
|
|
39835
40835
|
warnings.push(...kinematicSolve.warnings);
|
|
40836
|
+
const solvedFrameEdges = this.solveFrameEdges(frameSolve.frames);
|
|
40837
|
+
const kinematicMetadata = this.attachFrameEdgesToKinematics(kinematicSolve.metadata, solvedFrameEdges);
|
|
39836
40838
|
for (const joint2 of this.joints.values()) {
|
|
39837
40839
|
if (incoming.has(joint2.child)) {
|
|
39838
40840
|
throw new Error(`Part "${joint2.child}" has multiple parent joints`);
|
|
@@ -39842,8 +40844,9 @@ class Assembly {
|
|
|
39842
40844
|
list.push(joint2);
|
|
39843
40845
|
jointsByParent.set(joint2.parent, list);
|
|
39844
40846
|
}
|
|
39845
|
-
const
|
|
39846
|
-
|
|
40847
|
+
const unboundPartNames = [...this.parts.values()].filter((part) => !part.bindToFrame).map((part) => part.name);
|
|
40848
|
+
const roots = unboundPartNames.filter((name) => !incoming.has(name));
|
|
40849
|
+
if (roots.length === 0 && unboundPartNames.length > 0) {
|
|
39847
40850
|
throw new Error("Assembly has no root part (cyclic joint graph)");
|
|
39848
40851
|
}
|
|
39849
40852
|
const mateBaseOverrides = /* @__PURE__ */ new Map();
|
|
@@ -39946,7 +40949,7 @@ class Assembly {
|
|
|
39946
40949
|
const world = /* @__PURE__ */ new Map();
|
|
39947
40950
|
const visiting = /* @__PURE__ */ new Set();
|
|
39948
40951
|
const visited = /* @__PURE__ */ new Set();
|
|
39949
|
-
const jointValues = {};
|
|
40952
|
+
const jointValues = { ...frameSolve.jointValues };
|
|
39950
40953
|
const resolvingJointValues = /* @__PURE__ */ new Set();
|
|
39951
40954
|
const resolveJointValue = (jointName) => {
|
|
39952
40955
|
const cached = jointValues[jointName];
|
|
@@ -39996,6 +40999,13 @@ class Assembly {
|
|
|
39996
40999
|
const rootBase = mateBaseOverrides.get(rootName) ?? kinematicBaseForPart(root) ?? root.base;
|
|
39997
41000
|
dfs(rootName, rootBase);
|
|
39998
41001
|
}
|
|
41002
|
+
for (const rec of this.parts.values()) {
|
|
41003
|
+
if (!rec.bindToFrame) continue;
|
|
41004
|
+
const frameTransform2 = frameSolve.transforms.get(rec.bindToFrame);
|
|
41005
|
+
if (!frameTransform2) throw new Error(`Part "${rec.name}" references unresolved frame "${rec.bindToFrame}"`);
|
|
41006
|
+
world.set(rec.name, composeChain(rec.base, frameTransform2));
|
|
41007
|
+
visited.add(rec.name);
|
|
41008
|
+
}
|
|
39999
41009
|
if (visited.size !== this.parts.size) {
|
|
40000
41010
|
const missing = [...this.parts.keys()].filter((name) => !visited.has(name));
|
|
40001
41011
|
throw new Error(`Assembly graph unresolved for parts: ${missing.join(", ")}`);
|
|
@@ -40051,7 +41061,8 @@ class Assembly {
|
|
|
40051
41061
|
jointValues,
|
|
40052
41062
|
warnings,
|
|
40053
41063
|
mateMetadata,
|
|
40054
|
-
|
|
41064
|
+
kinematicMetadata,
|
|
41065
|
+
frameSolve.frames,
|
|
40055
41066
|
this._usedPortRefs
|
|
40056
41067
|
);
|
|
40057
41068
|
}
|
|
@@ -40164,7 +41175,7 @@ class Assembly {
|
|
|
40164
41175
|
return collectJointsView(this.buildJointsViewOptions(options, state, false), null, "assembly");
|
|
40165
41176
|
}
|
|
40166
41177
|
buildJointsViewOptions(options, state, relativeLinkControls) {
|
|
40167
|
-
var _a3, _b3, _c2, _d2;
|
|
41178
|
+
var _a3, _b3, _c2, _d2, _e2, _f;
|
|
40168
41179
|
const solved = this.solve(state);
|
|
40169
41180
|
const def = this.describe();
|
|
40170
41181
|
const joints = [];
|
|
@@ -40208,7 +41219,31 @@ class Assembly {
|
|
|
40208
41219
|
}
|
|
40209
41220
|
joints.push(entry);
|
|
40210
41221
|
}
|
|
40211
|
-
|
|
41222
|
+
for (const j of def.frameJoints) {
|
|
41223
|
+
if (j.type === "fixed" || j.control === false) continue;
|
|
41224
|
+
const parentWorld = solved.getFrame(j.parent);
|
|
41225
|
+
const pivot = parentWorld.point([0, 0, 0]);
|
|
41226
|
+
const axisWorld = parentWorld.vector([0, 0, 1]);
|
|
41227
|
+
const axisLen = Math.hypot(axisWorld[0], axisWorld[1], axisWorld[2]);
|
|
41228
|
+
const normalizedAxis = axisLen > 1e-10 ? [axisWorld[0] / axisLen, axisWorld[1] / axisLen, axisWorld[2] / axisLen] : [0, 0, 1];
|
|
41229
|
+
const entry = {
|
|
41230
|
+
name: j.name,
|
|
41231
|
+
child: j.child,
|
|
41232
|
+
parent: j.parent,
|
|
41233
|
+
type: j.type,
|
|
41234
|
+
axis: normalizedAxis,
|
|
41235
|
+
pivot,
|
|
41236
|
+
min: j.min,
|
|
41237
|
+
max: j.max,
|
|
41238
|
+
default: ((_c2 = options.defaults) == null ? void 0 : _c2[j.name]) ?? j.defaultValue,
|
|
41239
|
+
unit: j.unit
|
|
41240
|
+
};
|
|
41241
|
+
if ((_d2 = options.overrides) == null ? void 0 : _d2[j.name]) {
|
|
41242
|
+
Object.assign(entry, options.overrides[j.name]);
|
|
41243
|
+
}
|
|
41244
|
+
joints.push(entry);
|
|
41245
|
+
}
|
|
41246
|
+
if (this.linkAngles.size > 0) {
|
|
40212
41247
|
const mateLinksOf = /* @__PURE__ */ new Map();
|
|
40213
41248
|
for (const part of this.parts.values()) {
|
|
40214
41249
|
if (part.mates.length > 0) mateLinksOf.set(part.name, new Set(part.mates.map((m2) => m2.toLink)));
|
|
@@ -40229,18 +41264,20 @@ class Assembly {
|
|
|
40229
41264
|
for (const angle of this.linkAngles.values()) {
|
|
40230
41265
|
if (angle.control) controlMoving.set(angle.c, angle);
|
|
40231
41266
|
}
|
|
41267
|
+
const controlledAngleChild = (angle) => spanPart(angle.b, angle.c) ?? firstPartMatedTo(angle.c) ?? angle.c;
|
|
40232
41268
|
const normalAtSolvedPose = (angle) => {
|
|
40233
|
-
const a2 = solved.getLinkPosition(angle.a);
|
|
40234
41269
|
const b = solved.getLinkPosition(angle.b);
|
|
40235
41270
|
const c2 = solved.getLinkPosition(angle.c);
|
|
40236
|
-
const
|
|
41271
|
+
const reference = angle.reference ? angle.reference.direction : angle.a ? vecSub(solved.getLinkPosition(angle.a), b) : null;
|
|
41272
|
+
if (!reference) return [0, 0, 1];
|
|
41273
|
+
const n = vecCross(reference, vecSub(c2, b));
|
|
40237
41274
|
return vecLength(n) < 1e-9 ? [0, 0, 1] : vecNormalize(n);
|
|
40238
41275
|
};
|
|
40239
41276
|
for (const angle of this.linkAngles.values()) {
|
|
40240
41277
|
if (!angle.control) continue;
|
|
40241
|
-
const child =
|
|
41278
|
+
const child = controlledAngleChild(angle);
|
|
40242
41279
|
const parentAngle = controlMoving.get(angle.b);
|
|
40243
|
-
const parent = parentAngle ?
|
|
41280
|
+
const parent = parentAngle ? controlledAngleChild(parentAngle) : firstPartMatedTo(angle.b);
|
|
40244
41281
|
const defValue = angle.control.default ?? angle.target ?? 0;
|
|
40245
41282
|
const absMin = angle.control.min ?? angle.min;
|
|
40246
41283
|
const absMax = angle.control.max ?? angle.max;
|
|
@@ -40253,10 +41290,10 @@ class Assembly {
|
|
|
40253
41290
|
pivot: solved.getLinkPosition(angle.b),
|
|
40254
41291
|
min: absMin !== void 0 && relativeLinkControls ? absMin - defValue : absMin,
|
|
40255
41292
|
max: absMax !== void 0 && relativeLinkControls ? absMax - defValue : absMax,
|
|
40256
|
-
default: relativeLinkControls ? 0 : ((
|
|
41293
|
+
default: relativeLinkControls ? 0 : ((_e2 = options.defaults) == null ? void 0 : _e2[angle.name]) ?? defValue,
|
|
40257
41294
|
unit: angle.control.unit
|
|
40258
41295
|
};
|
|
40259
|
-
if ((
|
|
41296
|
+
if ((_f = options.overrides) == null ? void 0 : _f[angle.name]) {
|
|
40260
41297
|
Object.assign(entry, options.overrides[angle.name]);
|
|
40261
41298
|
}
|
|
40262
41299
|
joints.push(entry);
|
|
@@ -40318,7 +41355,8 @@ class Assembly {
|
|
|
40318
41355
|
part: part.part,
|
|
40319
41356
|
base: part.base,
|
|
40320
41357
|
metadata: part.metadata ? { ...part.metadata } : void 0,
|
|
40321
|
-
mates: part.mates.map((mate) => ({ ...mate }))
|
|
41358
|
+
mates: part.mates.map((mate) => ({ ...mate })),
|
|
41359
|
+
bindToFrame: part.bindToFrame
|
|
40322
41360
|
})),
|
|
40323
41361
|
joints: [...this.joints.values()].map((joint2) => ({
|
|
40324
41362
|
name: joint2.name,
|
|
@@ -40342,7 +41380,22 @@ class Assembly {
|
|
|
40342
41380
|
terms: coupling.terms.map((term) => ({ joint: term.joint, ratio: term.ratio })),
|
|
40343
41381
|
offset: coupling.offset
|
|
40344
41382
|
})),
|
|
40345
|
-
kinematics: this.describeKinematics()
|
|
41383
|
+
kinematics: this.describeKinematics(),
|
|
41384
|
+
frames: [...this.frames.values()].map((frame) => cloneAssemblyFrame(frame)),
|
|
41385
|
+
frameJoints: [...this.frameJoints.values()].map((joint2) => ({
|
|
41386
|
+
name: joint2.name,
|
|
41387
|
+
type: joint2.type,
|
|
41388
|
+
parent: joint2.parent,
|
|
41389
|
+
child: joint2.child,
|
|
41390
|
+
rest: joint2.rest,
|
|
41391
|
+
min: joint2.min,
|
|
41392
|
+
max: joint2.max,
|
|
41393
|
+
defaultValue: joint2.defaultValue,
|
|
41394
|
+
unit: joint2.unit,
|
|
41395
|
+
control: joint2.control,
|
|
41396
|
+
metadata: joint2.metadata ? { ...joint2.metadata } : void 0
|
|
41397
|
+
})),
|
|
41398
|
+
frameEdges: [...this.frameEdges.values()].map((edge) => cloneAssemblyFrameEdge(edge))
|
|
40346
41399
|
};
|
|
40347
41400
|
}
|
|
40348
41401
|
}
|
|
@@ -40569,6 +41622,9 @@ class ImportedAssembly {
|
|
|
40569
41622
|
mergeInto(parent, options) {
|
|
40570
41623
|
const def = this._assembly.describe();
|
|
40571
41624
|
const pfx = options.prefix ? `${options.prefix}.` : "";
|
|
41625
|
+
if (def.frames.length > 0 || def.frameJoints.length > 0 || def.frameEdges.length > 0) {
|
|
41626
|
+
throw new Error("mergeInto() does not yet support rig frames. Import and solve the frame-based sub-assembly directly for now.");
|
|
41627
|
+
}
|
|
40572
41628
|
const childSet = new Set(def.joints.map((j) => j.child));
|
|
40573
41629
|
const roots = def.parts.filter((p2) => !childSet.has(p2.name));
|
|
40574
41630
|
if (roots.length === 0) {
|
|
@@ -40599,14 +41655,26 @@ class ImportedAssembly {
|
|
|
40599
41655
|
});
|
|
40600
41656
|
}
|
|
40601
41657
|
for (const angle of def.kinematics.angles) {
|
|
40602
|
-
|
|
41658
|
+
const options2 = {
|
|
40603
41659
|
name: `${pfx}${angle.name}`,
|
|
40604
41660
|
value: angle.target,
|
|
40605
41661
|
min: angle.min,
|
|
40606
41662
|
max: angle.max,
|
|
40607
41663
|
control: angle.control,
|
|
40608
41664
|
metadata: angle.metadata
|
|
40609
|
-
}
|
|
41665
|
+
};
|
|
41666
|
+
if (angle.reference) {
|
|
41667
|
+
parent.addAngleBetweenLinkSegmentAndWorldDirection(`${pfx}${angle.b}`, `${pfx}${angle.c}`, angle.reference.direction, options2);
|
|
41668
|
+
} else if (angle.a) {
|
|
41669
|
+
parent.addAngleBetweenLinks(`${pfx}${angle.a}`, `${pfx}${angle.b}`, `${pfx}${angle.c}`, options2);
|
|
41670
|
+
}
|
|
41671
|
+
}
|
|
41672
|
+
for (const derived of def.kinematics.derivedLinks) {
|
|
41673
|
+
if (derived.direction === "toward") {
|
|
41674
|
+
parent.linkToward(`${pfx}${derived.name}`, `${pfx}${derived.fromLink}`, `${pfx}${derived.referenceLink}`, derived.distance);
|
|
41675
|
+
} else {
|
|
41676
|
+
parent.linkAwayFrom(`${pfx}${derived.name}`, `${pfx}${derived.fromLink}`, `${pfx}${derived.referenceLink}`, derived.distance);
|
|
41677
|
+
}
|
|
40610
41678
|
}
|
|
40611
41679
|
for (const p2 of def.parts) {
|
|
40612
41680
|
parent.addPart(`${pfx}${p2.name}`, p2.part, {
|
|
@@ -45122,7 +46190,7 @@ const EPSILON$2 = 1e-9;
|
|
|
45122
46190
|
function add$3(a2, b) {
|
|
45123
46191
|
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
45124
46192
|
}
|
|
45125
|
-
function scale$
|
|
46193
|
+
function scale$4(v, factor) {
|
|
45126
46194
|
return [v[0] * factor, v[1] * factor, v[2] * factor];
|
|
45127
46195
|
}
|
|
45128
46196
|
function sub$5(a2, b) {
|
|
@@ -45197,9 +46265,9 @@ function buildBend(points, directions, cornerIndex, radius) {
|
|
|
45197
46265
|
const angle = Math.acos(turnDot);
|
|
45198
46266
|
const trim = radius * Math.tan(angle / 2);
|
|
45199
46267
|
const axis = normalize$4(cross$4(dIn, dOut), `corner ${cornerIndex} bend axis`);
|
|
45200
|
-
const start = add$3(points[cornerIndex], scale$
|
|
45201
|
-
const end = add$3(points[cornerIndex], scale$
|
|
45202
|
-
const center = add$3(start, scale$
|
|
46268
|
+
const start = add$3(points[cornerIndex], scale$4(dIn, -trim));
|
|
46269
|
+
const end = add$3(points[cornerIndex], scale$4(dOut, trim));
|
|
46270
|
+
const center = add$3(start, scale$4(normalize$4(cross$4(axis, dIn), `corner ${cornerIndex} bend normal`), radius));
|
|
45203
46271
|
return { cornerIndex, trim, start, end, center, axis, sweepDeg: angle * 180 / Math.PI, length: radius * angle };
|
|
45204
46272
|
}
|
|
45205
46273
|
function assertBendFits(bend2, radius, segmentLengths, bends) {
|
|
@@ -45230,7 +46298,7 @@ function buildRoute3DPlanFromPolyline(pointsInput, options = {}) {
|
|
|
45230
46298
|
const segmentLengths = points.slice(1).map((point2, index2) => length$1(sub$5(point2, points[index2])));
|
|
45231
46299
|
const directions = segmentLengths.map((segmentLength, index2) => {
|
|
45232
46300
|
if (segmentLength <= EPSILON$2) throw new Error(`Curve.Route.fromPolyline: segment ${index2} is zero length.`);
|
|
45233
|
-
return scale$
|
|
46301
|
+
return scale$4(sub$5(points[index2 + 1], points[index2]), 1 / segmentLength);
|
|
45234
46302
|
});
|
|
45235
46303
|
const bends = new Array(points.length).fill(null);
|
|
45236
46304
|
if (radius > EPSILON$2) {
|
|
@@ -45263,7 +46331,7 @@ function buildRoute3DPlanFromPolyline(pointsInput, options = {}) {
|
|
|
45263
46331
|
segments,
|
|
45264
46332
|
length: routeLength,
|
|
45265
46333
|
ports: {
|
|
45266
|
-
[startPort]: portFrame(startPort, points[0], scale$
|
|
46334
|
+
[startPort]: portFrame(startPort, points[0], scale$4(directions[0], -1), 0, up),
|
|
45267
46335
|
[endPort]: portFrame(endPort, points[points.length - 1], directions[directions.length - 1], routeLength, up)
|
|
45268
46336
|
}
|
|
45269
46337
|
};
|
|
@@ -45766,7 +46834,7 @@ function variableSweep(spine, sections, options = {}) {
|
|
|
45766
46834
|
sources: ["sweep"]
|
|
45767
46835
|
});
|
|
45768
46836
|
}
|
|
45769
|
-
function requirePositive$
|
|
46837
|
+
function requirePositive$8(value, label) {
|
|
45770
46838
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
45771
46839
|
throw new Error(`${label} must be a finite number > 0, got ${JSON.stringify(value)}`);
|
|
45772
46840
|
}
|
|
@@ -45785,10 +46853,10 @@ function requireIntegerAtLeast(value, label, min2) {
|
|
|
45785
46853
|
return value;
|
|
45786
46854
|
}
|
|
45787
46855
|
function normalizeHelixOptions(options, apiName) {
|
|
45788
|
-
const radius = requirePositive$
|
|
45789
|
-
const pitch = options.pitch == null ? void 0 : requirePositive$
|
|
45790
|
-
const turns = options.turns == null ? void 0 : requirePositive$
|
|
45791
|
-
const height = options.height == null ? void 0 : requirePositive$
|
|
46856
|
+
const radius = requirePositive$8(options.radius, `${apiName}.radius`);
|
|
46857
|
+
const pitch = options.pitch == null ? void 0 : requirePositive$8(options.pitch, `${apiName}.pitch`);
|
|
46858
|
+
const turns = options.turns == null ? void 0 : requirePositive$8(options.turns, `${apiName}.turns`);
|
|
46859
|
+
const height = options.height == null ? void 0 : requirePositive$8(options.height, `${apiName}.height`);
|
|
45792
46860
|
const provided = [pitch != null, turns != null, height != null].filter(Boolean).length;
|
|
45793
46861
|
if (provided < 2) {
|
|
45794
46862
|
throw new Error(`${apiName}: provide any two of pitch, turns, and height so the third can be derived.`);
|
|
@@ -45874,7 +46942,7 @@ function buildHelixCoil(profileOrOptions, maybeOptions) {
|
|
|
45874
46942
|
if (!options) throw new Error("Helix.coil: options are required.");
|
|
45875
46943
|
const spec2 = normalizeHelixOptions(options, "Helix.coil");
|
|
45876
46944
|
const profile = hasCustomProfile ? profileOrOptions : circle2d(
|
|
45877
|
-
requirePositive$
|
|
46945
|
+
requirePositive$8(options.wireRadius, "Helix.coil.wireRadius"),
|
|
45878
46946
|
requireIntegerAtLeast(options.profileSegments ?? 24, "Helix.coil.profileSegments", 8)
|
|
45879
46947
|
);
|
|
45880
46948
|
if (profile.isEmpty()) throw new Error("Helix.coil: profile must not be empty.");
|
|
@@ -46457,6 +47525,12 @@ const Curve = {
|
|
|
46457
47525
|
function sub$4(a2, b) {
|
|
46458
47526
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
46459
47527
|
}
|
|
47528
|
+
function addVec(a2, b) {
|
|
47529
|
+
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
47530
|
+
}
|
|
47531
|
+
function scale$3(v, s) {
|
|
47532
|
+
return [v[0] * s, v[1] * s, v[2] * s];
|
|
47533
|
+
}
|
|
46460
47534
|
function dot$4(a2, b) {
|
|
46461
47535
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
46462
47536
|
}
|
|
@@ -46473,6 +47547,56 @@ function normalize$3(v) {
|
|
|
46473
47547
|
function clampDot(d2) {
|
|
46474
47548
|
return Math.max(-1, Math.min(1, d2));
|
|
46475
47549
|
}
|
|
47550
|
+
function requirePositiveFinite(value, label) {
|
|
47551
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(`${label} must be a finite number > 0`);
|
|
47552
|
+
return value;
|
|
47553
|
+
}
|
|
47554
|
+
function requireNonNegativeFinite(value, label) {
|
|
47555
|
+
if (!Number.isFinite(value) || value < 0) throw new Error(`${label} must be a finite number >= 0`);
|
|
47556
|
+
return value;
|
|
47557
|
+
}
|
|
47558
|
+
function requireFiniteVec3$1(value, label) {
|
|
47559
|
+
if (value == null) return void 0;
|
|
47560
|
+
if (!Array.isArray(value) || value.length !== 3) throw new Error(`${label} must be [x, y, z]`);
|
|
47561
|
+
const result = [Number(value[0]), Number(value[1]), Number(value[2])];
|
|
47562
|
+
if (!Number.isFinite(result[0]) || !Number.isFinite(result[1]) || !Number.isFinite(result[2])) {
|
|
47563
|
+
throw new Error(`${label} must contain finite coordinates`);
|
|
47564
|
+
}
|
|
47565
|
+
return result;
|
|
47566
|
+
}
|
|
47567
|
+
function normalizedDirection(value, label) {
|
|
47568
|
+
const len2 = vecLen(value);
|
|
47569
|
+
if (len2 < 1e-12) throw new Error(`${label} must not be the zero vector`);
|
|
47570
|
+
return [value[0] / len2, value[1] / len2, value[2] / len2];
|
|
47571
|
+
}
|
|
47572
|
+
function stablePerpendicularAxis$1(direction2) {
|
|
47573
|
+
const seed = Math.abs(direction2[1]) < 0.9 ? [0, 1, 0] : [1, 0, 0];
|
|
47574
|
+
return normalizedDirection(sub$4(seed, scale$3(direction2, dot$4(direction2, seed))), "elbow: turn plane");
|
|
47575
|
+
}
|
|
47576
|
+
function rotateDirection(v, axis, angleRad) {
|
|
47577
|
+
const c2 = Math.cos(angleRad);
|
|
47578
|
+
const s = Math.sin(angleRad);
|
|
47579
|
+
const axisDot = dot$4(axis, v);
|
|
47580
|
+
const axisCross = cross$3(axis, v);
|
|
47581
|
+
return [
|
|
47582
|
+
v[0] * c2 + axisCross[0] * s + axis[0] * axisDot * (1 - c2),
|
|
47583
|
+
v[1] * c2 + axisCross[1] * s + axis[1] * axisDot * (1 - c2),
|
|
47584
|
+
v[2] * c2 + axisCross[2] * s + axis[2] * axisDot * (1 - c2)
|
|
47585
|
+
];
|
|
47586
|
+
}
|
|
47587
|
+
function angleBetweenDirections(from, to) {
|
|
47588
|
+
return Math.acos(clampDot(dot$4(from, to)));
|
|
47589
|
+
}
|
|
47590
|
+
function turnAxisForDirections(from, to) {
|
|
47591
|
+
const axis = cross$3(from, to);
|
|
47592
|
+
if (vecLen(axis) >= 1e-12) return normalizedDirection(axis, "elbow: turn axis");
|
|
47593
|
+
if (dot$4(from, to) > 0) throw new Error("elbow: from and to directions are collinear; angle too small");
|
|
47594
|
+
return stablePerpendicularAxis$1(from);
|
|
47595
|
+
}
|
|
47596
|
+
function profileForPipeRadius(pipeRadius, wall, segments) {
|
|
47597
|
+
const outer = circle2d(pipeRadius, segments);
|
|
47598
|
+
return wall != null && wall > 0 ? difference2d(outer, circle2d(pipeRadius - wall, segments)) : outer;
|
|
47599
|
+
}
|
|
46476
47600
|
function pipeRoute(points, radius, options) {
|
|
46477
47601
|
if (points.length < 2) throw new Error("pipeRoute needs at least 2 points");
|
|
46478
47602
|
const bendR = (options == null ? void 0 : options.bendRadius) ?? radius * 4;
|
|
@@ -46504,88 +47628,40 @@ function simplifyStraightPipeRoutePoints(points) {
|
|
|
46504
47628
|
return simplified;
|
|
46505
47629
|
}
|
|
46506
47630
|
function elbow(pipeRadius, bendRadius, angle, options) {
|
|
46507
|
-
|
|
46508
|
-
|
|
46509
|
-
|
|
46510
|
-
|
|
46511
|
-
|
|
46512
|
-
if (
|
|
46513
|
-
|
|
46514
|
-
wall
|
|
46515
|
-
|
|
46516
|
-
|
|
46517
|
-
|
|
47631
|
+
requirePositiveFinite(pipeRadius, "elbow: pipeRadius");
|
|
47632
|
+
requirePositiveFinite(bendRadius, "elbow: bendRadius");
|
|
47633
|
+
const opts = typeof angle === "object" && angle !== null ? angle : options;
|
|
47634
|
+
const wall = opts == null ? void 0 : opts.wall;
|
|
47635
|
+
const segments = (opts == null ? void 0 : opts.segments) ?? 32;
|
|
47636
|
+
if (!Number.isFinite(segments) || segments < 3) throw new Error("elbow: segments must be a finite number >= 3");
|
|
47637
|
+
if (wall != null && requireNonNegativeFinite(wall, "elbow: wall") >= pipeRadius) {
|
|
47638
|
+
throw new Error("elbow: wall must be smaller than pipeRadius");
|
|
47639
|
+
}
|
|
47640
|
+
const from = normalizedDirection(requireFiniteVec3$1(opts == null ? void 0 : opts.from, "elbow: from") ?? [0, 0, 1], "elbow: from");
|
|
47641
|
+
const toInput = requireFiniteVec3$1(opts == null ? void 0 : opts.to, "elbow: to");
|
|
47642
|
+
const angleDegInput = typeof angle === "number" ? angle : void 0;
|
|
47643
|
+
const angleRadInput = (angleDegInput ?? 90) * Math.PI / 180;
|
|
47644
|
+
let angleRad;
|
|
47645
|
+
let turnAxis;
|
|
47646
|
+
if (toInput) {
|
|
47647
|
+
const to = normalizedDirection(toInput, "elbow: to");
|
|
47648
|
+
angleRad = angleBetweenDirections(from, to);
|
|
47649
|
+
turnAxis = turnAxisForDirections(from, to);
|
|
46518
47650
|
} else {
|
|
46519
|
-
|
|
46520
|
-
|
|
46521
|
-
|
|
46522
|
-
|
|
46523
|
-
|
|
46524
|
-
}
|
|
46525
|
-
if (fromDir && toDir) {
|
|
46526
|
-
const nFrom = normalize$3(fromDir);
|
|
46527
|
-
const nTo = normalize$3(toDir);
|
|
46528
|
-
const d2 = clampDot(dot$4(nFrom, nTo));
|
|
46529
|
-
angleDeg = Math.acos(d2) * 180 / Math.PI;
|
|
46530
|
-
}
|
|
46531
|
-
if (angleDeg < 0.01) throw new Error("elbow: angle too small");
|
|
46532
|
-
const circlePts = [];
|
|
46533
|
-
for (let i = 0; i < segs; i++) {
|
|
46534
|
-
const a2 = i / segs * Math.PI * 2;
|
|
46535
|
-
circlePts.push([bendRadius + pipeRadius * Math.cos(a2), pipeRadius * Math.sin(a2)]);
|
|
46536
|
-
}
|
|
46537
|
-
const bendSegs = Math.max(4, Math.ceil(segs * angleDeg / 360));
|
|
46538
|
-
const outerPlan = {
|
|
46539
|
-
kind: "revolve",
|
|
46540
|
-
profile: { kind: "polygon", points: circlePts, transforms: [] },
|
|
46541
|
-
degrees: angleDeg,
|
|
46542
|
-
segments: bendSegs
|
|
46543
|
-
};
|
|
46544
|
-
let bendShape = buildShapeFromCompilePlan(outerPlan);
|
|
46545
|
-
if (wall != null && wall > 0) {
|
|
46546
|
-
const innerPts = [];
|
|
46547
|
-
const innerR = pipeRadius - wall;
|
|
46548
|
-
for (let i = 0; i < segs; i++) {
|
|
46549
|
-
const a2 = i / segs * Math.PI * 2;
|
|
46550
|
-
innerPts.push([bendRadius + innerR * Math.cos(a2), innerR * Math.sin(a2)]);
|
|
46551
|
-
}
|
|
46552
|
-
const innerPlan = {
|
|
46553
|
-
kind: "revolve",
|
|
46554
|
-
profile: { kind: "polygon", points: innerPts, transforms: [] },
|
|
46555
|
-
degrees: angleDeg,
|
|
46556
|
-
segments: bendSegs
|
|
46557
|
-
};
|
|
46558
|
-
const innerBend = buildShapeFromCompilePlan(innerPlan);
|
|
46559
|
-
bendShape = bendShape.subtract(innerBend);
|
|
46560
|
-
}
|
|
46561
|
-
if (fromDir && toDir) {
|
|
46562
|
-
const nFrom = normalize$3(fromDir);
|
|
46563
|
-
const nTo = normalize$3(toDir);
|
|
46564
|
-
const crossVec = cross$3(nFrom, nTo);
|
|
46565
|
-
const crossLen = vecLen(crossVec);
|
|
46566
|
-
if (crossLen < 1e-10) return bendShape;
|
|
46567
|
-
const axis = normalize$3(crossVec);
|
|
46568
|
-
const perpDir = cross$3(axis, nFrom);
|
|
46569
|
-
bendShape = bendShape.transform([
|
|
46570
|
-
perpDir[0],
|
|
46571
|
-
perpDir[1],
|
|
46572
|
-
perpDir[2],
|
|
46573
|
-
0,
|
|
46574
|
-
nFrom[0],
|
|
46575
|
-
nFrom[1],
|
|
46576
|
-
nFrom[2],
|
|
46577
|
-
0,
|
|
46578
|
-
axis[0],
|
|
46579
|
-
axis[1],
|
|
46580
|
-
axis[2],
|
|
46581
|
-
0,
|
|
46582
|
-
0,
|
|
46583
|
-
0,
|
|
46584
|
-
0,
|
|
46585
|
-
1
|
|
46586
|
-
]);
|
|
47651
|
+
if (!Number.isFinite(angleRadInput) || angleRadInput <= 1e-6 || angleRadInput > Math.PI) {
|
|
47652
|
+
throw new Error("elbow: angle must be finite and in the range (0, 180] degrees");
|
|
47653
|
+
}
|
|
47654
|
+
turnAxis = stablePerpendicularAxis$1(from);
|
|
47655
|
+
angleRad = angleRadInput;
|
|
46587
47656
|
}
|
|
46588
|
-
|
|
47657
|
+
if (angleRad <= 1e-6) throw new Error("elbow: angle too small");
|
|
47658
|
+
const inward = normalizedDirection(cross$3(turnAxis, from), "elbow: bend normal");
|
|
47659
|
+
const center = scale$3(inward, bendRadius);
|
|
47660
|
+
const endRadial = rotateDirection(scale$3(inward, -bendRadius), turnAxis, angleRad);
|
|
47661
|
+
const end = addVec(center, endRadial);
|
|
47662
|
+
const profile = profileForPipeRadius(pipeRadius, wall, Math.floor(segments));
|
|
47663
|
+
const pathSamples = Math.max(6, Math.ceil(segments * (angleRad * 180 / Math.PI) / 360) + 1);
|
|
47664
|
+
return sweep(profile, Curve.Arc({ start: [0, 0, 0], end, tangent: from }), { samples: pathSamples, up: turnAxis });
|
|
46589
47665
|
}
|
|
46590
47666
|
const EPS$7 = 1e-9;
|
|
46591
47667
|
function assertFinitePositive(apiName, name, value) {
|
|
@@ -47084,7 +48160,7 @@ function spurGear(options) {
|
|
|
47084
48160
|
});
|
|
47085
48161
|
return attachGearMeta(shapeWithConnectors, meta2);
|
|
47086
48162
|
}
|
|
47087
|
-
function requirePositive$
|
|
48163
|
+
function requirePositive$7(scope, name, value) {
|
|
47088
48164
|
if (!isFinitePositive(value)) throw new Error(`${scope}: "${name}" must be > 0`);
|
|
47089
48165
|
}
|
|
47090
48166
|
function requireOptionalBore(scope, boreDiameter, maxDiameter) {
|
|
@@ -47106,8 +48182,8 @@ function cutBore$1(shape, boreDiameter) {
|
|
|
47106
48182
|
return shape.subtract(cutter);
|
|
47107
48183
|
}
|
|
47108
48184
|
function gearBodyDisk(options) {
|
|
47109
|
-
requirePositive$
|
|
47110
|
-
requirePositive$
|
|
48185
|
+
requirePositive$7("gearBodyDisk", "outerRadius", options.outerRadius);
|
|
48186
|
+
requirePositive$7("gearBodyDisk", "faceWidth", options.faceWidth);
|
|
47111
48187
|
const bore = requireOptionalBore("gearBodyDisk", options.boreDiameter, options.outerRadius * 2);
|
|
47112
48188
|
const segments = resolveSegments(options.segments);
|
|
47113
48189
|
const outer = circle2d(options.outerRadius, segments);
|
|
@@ -47115,14 +48191,14 @@ function gearBodyDisk(options) {
|
|
|
47115
48191
|
return sketchExtrude(profile, options.faceWidth);
|
|
47116
48192
|
}
|
|
47117
48193
|
function gearBodyDiskWithHub(options) {
|
|
47118
|
-
requirePositive$
|
|
48194
|
+
requirePositive$7("gearBodyDiskWithHub", "hubDiameter", options.hubDiameter);
|
|
47119
48195
|
if (options.hubDiameter >= options.outerRadius * 2) {
|
|
47120
48196
|
throw new Error('gearBodyDiskWithHub: "hubDiameter" must be smaller than the outer diameter');
|
|
47121
48197
|
}
|
|
47122
48198
|
const bore = requireOptionalBore("gearBodyDiskWithHub", options.boreDiameter, options.hubDiameter);
|
|
47123
48199
|
const base = gearBodyDisk({ ...options, boreDiameter: 0 });
|
|
47124
48200
|
const hubFaceWidth = options.hubFaceWidth ?? options.faceWidth * 1.5;
|
|
47125
|
-
requirePositive$
|
|
48201
|
+
requirePositive$7("gearBodyDiskWithHub", "hubFaceWidth", hubFaceWidth);
|
|
47126
48202
|
const hub = cylinder(hubFaceWidth, options.hubDiameter * 0.5, void 0, options.segments).translate(
|
|
47127
48203
|
0,
|
|
47128
48204
|
0,
|
|
@@ -47131,11 +48207,11 @@ function gearBodyDiskWithHub(options) {
|
|
|
47131
48207
|
return cutBore$1(base.add(hub), bore);
|
|
47132
48208
|
}
|
|
47133
48209
|
function gearBodySpoked(options) {
|
|
47134
|
-
requirePositive$
|
|
47135
|
-
requirePositive$
|
|
47136
|
-
requirePositive$
|
|
47137
|
-
requirePositive$
|
|
47138
|
-
requirePositive$
|
|
48210
|
+
requirePositive$7("gearBodySpoked", "outerRadius", options.outerRadius);
|
|
48211
|
+
requirePositive$7("gearBodySpoked", "faceWidth", options.faceWidth);
|
|
48212
|
+
requirePositive$7("gearBodySpoked", "rimWidth", options.rimWidth);
|
|
48213
|
+
requirePositive$7("gearBodySpoked", "hubDiameter", options.hubDiameter);
|
|
48214
|
+
requirePositive$7("gearBodySpoked", "spokeWidth", options.spokeWidth);
|
|
47139
48215
|
if (!Number.isInteger(options.spokeCount) || options.spokeCount < 2) {
|
|
47140
48216
|
throw new Error('gearBodySpoked: "spokeCount" must be an integer >= 2');
|
|
47141
48217
|
}
|
|
@@ -47158,12 +48234,12 @@ function gearBodySpoked(options) {
|
|
|
47158
48234
|
}
|
|
47159
48235
|
function gearBodyFromProfile(profile, options) {
|
|
47160
48236
|
if (!(profile instanceof Sketch)) throw new Error('gearBodyFromProfile: "profile" must be a Sketch');
|
|
47161
|
-
requirePositive$
|
|
48237
|
+
requirePositive$7("gearBodyFromProfile", "faceWidth", options.faceWidth);
|
|
47162
48238
|
const bore = options.boreDiameter ?? 0;
|
|
47163
48239
|
if (!Number.isFinite(bore) || bore < 0) throw new Error('gearBodyFromProfile: "boreDiameter" must be >= 0');
|
|
47164
48240
|
return cutBore$1(sketchExtrude(profile, options.faceWidth), bore);
|
|
47165
48241
|
}
|
|
47166
|
-
function requirePositive$
|
|
48242
|
+
function requirePositive$6(scope, name, value) {
|
|
47167
48243
|
if (!isFinitePositive(value)) throw new Error(`${scope}: "${name}" must be > 0`);
|
|
47168
48244
|
}
|
|
47169
48245
|
function requireFiniteAngle(scope, name, value) {
|
|
@@ -47225,7 +48301,7 @@ function buildSpurTeethRegion(options, name, faceWidth) {
|
|
|
47225
48301
|
}
|
|
47226
48302
|
function buildSolidArcRegion(options, name, faceWidth) {
|
|
47227
48303
|
const scope = "driveWheel.addSolidArcBetween";
|
|
47228
|
-
requirePositive$
|
|
48304
|
+
requirePositive$6(scope, "outerRadius", options.outerRadius);
|
|
47229
48305
|
const innerRadius = options.innerRadius ?? 0;
|
|
47230
48306
|
if (!Number.isFinite(innerRadius) || innerRadius < 0) throw new Error(`${scope}: "innerRadius" must be >= 0`);
|
|
47231
48307
|
if (innerRadius >= options.outerRadius) throw new Error(`${scope}: "innerRadius" must be smaller than "outerRadius"`);
|
|
@@ -47291,7 +48367,7 @@ class DriveWheelBuilder {
|
|
|
47291
48367
|
__publicField(this, "boreDiameter");
|
|
47292
48368
|
__publicField(this, "regions", []);
|
|
47293
48369
|
if (options.body !== void 0 && !(options.body instanceof Shape)) throw new Error('driveWheel: "body" must be a Shape');
|
|
47294
|
-
if (options.faceWidth !== void 0) requirePositive$
|
|
48370
|
+
if (options.faceWidth !== void 0) requirePositive$6("driveWheel", "faceWidth", options.faceWidth);
|
|
47295
48371
|
const boreDiameter = options.boreDiameter ?? 0;
|
|
47296
48372
|
if (!Number.isFinite(boreDiameter) || boreDiameter < 0) throw new Error('driveWheel: "boreDiameter" must be >= 0');
|
|
47297
48373
|
this.body = options.body;
|
|
@@ -47326,7 +48402,7 @@ class DriveWheelBuilder {
|
|
|
47326
48402
|
if (options.innerRadius !== void 0 && (!Number.isFinite(options.innerRadius) || options.innerRadius < 0)) {
|
|
47327
48403
|
throw new Error(`${scope}: "innerRadius" must be >= 0`);
|
|
47328
48404
|
}
|
|
47329
|
-
if (options.outerRadius !== void 0) requirePositive$
|
|
48405
|
+
if (options.outerRadius !== void 0) requirePositive$6(scope, "outerRadius", options.outerRadius);
|
|
47330
48406
|
this.regions.push({
|
|
47331
48407
|
shape: shape.clone(),
|
|
47332
48408
|
meta: {
|
|
@@ -47392,7 +48468,7 @@ class DriveWheelBuilder {
|
|
|
47392
48468
|
resolveFaceWidth(scope, localFaceWidth) {
|
|
47393
48469
|
const faceWidth = localFaceWidth ?? this.faceWidth;
|
|
47394
48470
|
if (faceWidth === void 0) throw new Error(`${scope}: "faceWidth" is required unless driveWheel({ faceWidth }) was set`);
|
|
47395
|
-
requirePositive$
|
|
48471
|
+
requirePositive$6(scope, "faceWidth", faceWidth);
|
|
47396
48472
|
if (this.faceWidth !== void 0 && localFaceWidth !== void 0 && Math.abs(this.faceWidth - localFaceWidth) > EPSILON$1) {
|
|
47397
48473
|
throw new Error(`${scope}: region faceWidth must match driveWheel faceWidth`);
|
|
47398
48474
|
}
|
|
@@ -48545,1860 +49621,6 @@ function washer(size, options) {
|
|
|
48545
49621
|
const bore = cylinder(dims.t + 1, dims.id / 2, void 0, segs);
|
|
48546
49622
|
return outer.subtract(bore);
|
|
48547
49623
|
}
|
|
48548
|
-
function requirePositive$6(value, name) {
|
|
48549
|
-
if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive finite number`);
|
|
48550
|
-
return value;
|
|
48551
|
-
}
|
|
48552
|
-
function requireNonNegative(value, name) {
|
|
48553
|
-
if (!Number.isFinite(value) || value < 0) throw new Error(`${name} must be a non-negative finite number`);
|
|
48554
|
-
return value;
|
|
48555
|
-
}
|
|
48556
|
-
function metricWasherSizeForPin(pinDiameter) {
|
|
48557
|
-
if (pinDiameter <= 2) return "M2";
|
|
48558
|
-
if (pinDiameter <= 2.5) return "M2.5";
|
|
48559
|
-
if (pinDiameter <= 3) return "M3";
|
|
48560
|
-
if (pinDiameter <= 4) return "M4";
|
|
48561
|
-
if (pinDiameter <= 5) return "M5";
|
|
48562
|
-
if (pinDiameter <= 6) return "M6";
|
|
48563
|
-
if (pinDiameter <= 8) return "M8";
|
|
48564
|
-
return "M10";
|
|
48565
|
-
}
|
|
48566
|
-
function cylinderAlongX(length4, radius, xCenter, segments) {
|
|
48567
|
-
return cylinder(length4, radius, void 0, segments).pointAlong([1, 0, 0]).translate(xCenter - length4 / 2, 0, 0);
|
|
48568
|
-
}
|
|
48569
|
-
function tubeAlongX(length4, outerRadius, innerRadius, xCenter, segments) {
|
|
48570
|
-
return cylinderAlongX(length4, outerRadius, xCenter, segments).subtract(cylinderAlongX(length4 + 0.4, innerRadius, xCenter, segments));
|
|
48571
|
-
}
|
|
48572
|
-
function cylinderAlongY(length4, radius, yCenter, segments) {
|
|
48573
|
-
return cylinder(length4, radius, void 0, segments).pointAlong([0, 1, 0]).translate(0, yCenter - length4 / 2, 0);
|
|
48574
|
-
}
|
|
48575
|
-
function tubeAlongY(length4, outerRadius, innerRadius, yCenter, segments) {
|
|
48576
|
-
return cylinderAlongY(length4, outerRadius, yCenter, segments).subtract(cylinderAlongY(length4 + 0.4, innerRadius, yCenter, segments));
|
|
48577
|
-
}
|
|
48578
|
-
function tubeAlongZ(height, outerRadius, innerRadius, segments) {
|
|
48579
|
-
return cylinder(height, outerRadius, void 0, segments).subtract(
|
|
48580
|
-
cylinder(height + 0.4, innerRadius, void 0, segments).translate(0, 0, -0.2)
|
|
48581
|
-
);
|
|
48582
|
-
}
|
|
48583
|
-
function washerAlongX(size, xCenter, segments) {
|
|
48584
|
-
const dims = WASHER_TABLE[size];
|
|
48585
|
-
return washer(size, { segments }).pointAlong([1, 0, 0]).translate(xCenter - dims.t / 2, 0, 0);
|
|
48586
|
-
}
|
|
48587
|
-
function resolveBoltInset(raw, fallback) {
|
|
48588
|
-
if (raw === void 0) return [fallback, fallback];
|
|
48589
|
-
if (typeof raw === "number") return [requirePositive$6(raw, "boltInset"), requirePositive$6(raw, "boltInset")];
|
|
48590
|
-
if (raw.length !== 2) throw new Error("boltInset tuple must be [x, y]");
|
|
48591
|
-
return [requirePositive$6(raw[0], "boltInset[0]"), requirePositive$6(raw[1], "boltInset[1]")];
|
|
48592
|
-
}
|
|
48593
|
-
function validateBoltPositionsForServiceCover(args) {
|
|
48594
|
-
args.positions.forEach(([x2, y2], index2) => {
|
|
48595
|
-
if (!Number.isFinite(x2) || !Number.isFinite(y2)) {
|
|
48596
|
-
throw new Error(`boltedServiceCover: boltPositions[${index2}] must contain finite numbers`);
|
|
48597
|
-
}
|
|
48598
|
-
if (Math.abs(x2) + args.holeRadius >= args.coverWidth / 2 || Math.abs(y2) + args.holeRadius >= args.coverDepth / 2) {
|
|
48599
|
-
throw new Error(`boltedServiceCover: boltPositions[${index2}] is too close to the cover edge`);
|
|
48600
|
-
}
|
|
48601
|
-
const overlapsOpening = Math.abs(x2) - args.holeRadius <= args.openingWidth / 2 && Math.abs(y2) - args.holeRadius <= args.openingDepth / 2;
|
|
48602
|
-
if (overlapsOpening) {
|
|
48603
|
-
throw new Error(
|
|
48604
|
-
`boltedServiceCover: boltPositions[${index2}] lands over the service opening; decrease boltInset, increase ledgeWidth, or provide a smaller opening`
|
|
48605
|
-
);
|
|
48606
|
-
}
|
|
48607
|
-
});
|
|
48608
|
-
}
|
|
48609
|
-
function placeCutterAtPositions(cutter, positions, z2) {
|
|
48610
|
-
return union(...positions.map(([x2, y2]) => cutter.translate(x2, y2, z2)));
|
|
48611
|
-
}
|
|
48612
|
-
function boltedServiceCover(options) {
|
|
48613
|
-
const width = requirePositive$6(options.width, "width");
|
|
48614
|
-
const depth = requirePositive$6(options.depth, "depth");
|
|
48615
|
-
const coverThickness = requirePositive$6(options.coverThickness ?? 3, "coverThickness");
|
|
48616
|
-
const parentThickness = requirePositive$6(options.parentThickness ?? 8, "parentThickness");
|
|
48617
|
-
const ledgeWidth = requirePositive$6(options.ledgeWidth ?? 8, "ledgeWidth");
|
|
48618
|
-
const gasketThickness = Math.max(0, options.gasketThickness ?? 0.8);
|
|
48619
|
-
const gasketInset = Math.max(0, options.gasketInset ?? 2);
|
|
48620
|
-
const screwSize = options.screwSize ?? "M4";
|
|
48621
|
-
const segments = options.segments ?? 36;
|
|
48622
|
-
const sizeData = METRIC_HOLE_TABLE[screwSize];
|
|
48623
|
-
if (!sizeData) throw new Error(`boltedServiceCover: unsupported screwSize "${screwSize}"`);
|
|
48624
|
-
const screwLength = requirePositive$6(options.screwLength ?? parentThickness + gasketThickness + coverThickness + 4, "screwLength");
|
|
48625
|
-
const coverFit = options.coverFit ?? "normal";
|
|
48626
|
-
const counterboreEnabled = options.counterbore ?? true;
|
|
48627
|
-
const [insetX, insetY] = resolveBoltInset(options.boltInset, Math.max(ledgeWidth * 0.65, sizeData.head * 0.75));
|
|
48628
|
-
if (insetX * 2 >= width || insetY * 2 >= depth) {
|
|
48629
|
-
throw new Error("boltedServiceCover: boltInset leaves no room for a four-corner bolt pattern");
|
|
48630
|
-
}
|
|
48631
|
-
const boltPositions = options.boltPositions ?? [
|
|
48632
|
-
[-width / 2 + insetX, -depth / 2 + insetY],
|
|
48633
|
-
[width / 2 - insetX, -depth / 2 + insetY],
|
|
48634
|
-
[-width / 2 + insetX, depth / 2 - insetY],
|
|
48635
|
-
[width / 2 - insetX, depth / 2 - insetY]
|
|
48636
|
-
];
|
|
48637
|
-
if (boltPositions.length === 0) throw new Error("boltedServiceCover: boltPositions must contain at least one point");
|
|
48638
|
-
const parentWidth = width + ledgeWidth * 2;
|
|
48639
|
-
const parentDepth = depth + ledgeWidth * 2;
|
|
48640
|
-
const openingWidth = Math.max(1, width - ledgeWidth * 2);
|
|
48641
|
-
const openingDepth = Math.max(1, depth - ledgeWidth * 2);
|
|
48642
|
-
validateBoltPositionsForServiceCover({
|
|
48643
|
-
positions: boltPositions,
|
|
48644
|
-
coverWidth: width,
|
|
48645
|
-
coverDepth: depth,
|
|
48646
|
-
openingWidth,
|
|
48647
|
-
openingDepth,
|
|
48648
|
-
holeRadius: sizeData[coverFit] / 2
|
|
48649
|
-
});
|
|
48650
|
-
const coverHole = fastenerHole({
|
|
48651
|
-
size: screwSize,
|
|
48652
|
-
fit: coverFit,
|
|
48653
|
-
depth: coverThickness + 0.6,
|
|
48654
|
-
center: true,
|
|
48655
|
-
segments,
|
|
48656
|
-
...counterboreEnabled ? { counterbore: { depth: Math.min(coverThickness * 0.6, Math.max(0.6, coverThickness - 0.4)) } } : {}
|
|
48657
|
-
});
|
|
48658
|
-
const parentTap = fastenerHole({ size: screwSize, fit: "tap", depth: parentThickness + 0.6, center: true, segments });
|
|
48659
|
-
const parentThreadEnvelope = fastenerHole({
|
|
48660
|
-
size: screwSize,
|
|
48661
|
-
fit: "close",
|
|
48662
|
-
depth: parentThickness + 0.6,
|
|
48663
|
-
center: true,
|
|
48664
|
-
segments
|
|
48665
|
-
});
|
|
48666
|
-
const openingCutter = box(openingWidth, openingDepth, parentThickness + 1).translate(0, 0, -0.5);
|
|
48667
|
-
const parentTappedPattern = placeCutterAtPositions(parentTap, boltPositions, parentThickness / 2);
|
|
48668
|
-
const parentThreadEnvelopePattern = placeCutterAtPositions(parentThreadEnvelope, boltPositions, parentThickness / 2);
|
|
48669
|
-
const parent = box(parentWidth, parentDepth, parentThickness).subtract(openingCutter).subtract(parentThreadEnvelopePattern).color("#4b5563");
|
|
48670
|
-
let coverBlank = box(width, depth, coverThickness);
|
|
48671
|
-
if (options.pullTabs ?? true) {
|
|
48672
|
-
const tabWidth = Math.min(width * 0.18, Math.max(sizeData.head * 1.6, 12));
|
|
48673
|
-
const tabDepth = Math.max(4, coverThickness * 1.4);
|
|
48674
|
-
const tabOverlap = Math.min(0.5, tabDepth * 0.25);
|
|
48675
|
-
const tabY = -depth / 2 - tabDepth / 2 + tabOverlap;
|
|
48676
|
-
const tabX = width * 0.23;
|
|
48677
|
-
coverBlank = union(
|
|
48678
|
-
coverBlank,
|
|
48679
|
-
box(tabWidth, tabDepth, coverThickness).translate(-tabX, tabY, 0),
|
|
48680
|
-
box(tabWidth, tabDepth, coverThickness).translate(tabX, tabY, 0)
|
|
48681
|
-
);
|
|
48682
|
-
}
|
|
48683
|
-
const coverClearancePattern = placeCutterAtPositions(coverHole, boltPositions, coverThickness / 2);
|
|
48684
|
-
const cover = coverBlank.subtract(coverClearancePattern).translate(0, 0, parentThickness + gasketThickness).color("#334155");
|
|
48685
|
-
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;
|
|
48686
|
-
const hardware = fastenerSet(screwSize, screwLength, { washerUnderHead: false, washerUnderNut: false, fit: coverFit, segments });
|
|
48687
|
-
const screwOriginZ = parentThickness + gasketThickness + coverThickness;
|
|
48688
|
-
const screws = boltPositions.map(([x2, y2]) => hardware.bolt.translate(x2, y2, screwOriginZ).color("#94a3b8"));
|
|
48689
|
-
const parts = [
|
|
48690
|
-
{ name: "service cover parent ledge with threaded hole envelopes", shape: parent },
|
|
48691
|
-
...gasket ? [{ name: "service cover gasket seated on ledge", shape: gasket }] : [],
|
|
48692
|
-
{ name: "bolted service cover plate with fused pull tabs", shape: cover },
|
|
48693
|
-
...screws.map((shape, index2) => ({ name: `installed ${screwSize} cover screw ${index2 + 1}`, shape }))
|
|
48694
|
-
];
|
|
48695
|
-
return {
|
|
48696
|
-
parts,
|
|
48697
|
-
parent,
|
|
48698
|
-
cover,
|
|
48699
|
-
gasket,
|
|
48700
|
-
screws,
|
|
48701
|
-
boltPositions,
|
|
48702
|
-
cutters: {
|
|
48703
|
-
coverClearance: coverClearancePattern,
|
|
48704
|
-
parentTapped: parentTappedPattern,
|
|
48705
|
-
parentThreadEnvelope: parentThreadEnvelopePattern
|
|
48706
|
-
},
|
|
48707
|
-
dims: {
|
|
48708
|
-
width,
|
|
48709
|
-
depth,
|
|
48710
|
-
coverThickness,
|
|
48711
|
-
parentThickness,
|
|
48712
|
-
ledgeWidth,
|
|
48713
|
-
gasketThickness,
|
|
48714
|
-
screwSize,
|
|
48715
|
-
screwLength,
|
|
48716
|
-
clearanceDia: sizeData[coverFit],
|
|
48717
|
-
tapDia: sizeData.tap,
|
|
48718
|
-
threadEnvelopeDia: sizeData.close
|
|
48719
|
-
}
|
|
48720
|
-
};
|
|
48721
|
-
}
|
|
48722
|
-
function datumEnclosureAssembly(options) {
|
|
48723
|
-
const width = requirePositive$6(options.width, "width");
|
|
48724
|
-
const depth = requirePositive$6(options.depth, "depth");
|
|
48725
|
-
const height = requirePositive$6(options.height, "height");
|
|
48726
|
-
const wallThickness = requirePositive$6(options.wallThickness ?? 2.4, "wallThickness");
|
|
48727
|
-
const baseThickness = requirePositive$6(options.baseThickness ?? wallThickness, "baseThickness");
|
|
48728
|
-
const coverThickness = requirePositive$6(options.coverThickness ?? 2.4, "coverThickness");
|
|
48729
|
-
const ledgeWidth = requirePositive$6(options.ledgeWidth ?? Math.max(3.6, wallThickness * 1.35), "ledgeWidth");
|
|
48730
|
-
const gasketThickness = requireNonNegative(options.gasketThickness ?? 0.8, "gasketThickness");
|
|
48731
|
-
const faceClearance = requirePositive$6(options.faceClearance ?? 0.04, "faceClearance");
|
|
48732
|
-
const screwSize = options.screwSize ?? "M3";
|
|
48733
|
-
const coverFit = options.coverFit ?? "normal";
|
|
48734
|
-
const segments = options.segments ?? 32;
|
|
48735
|
-
const sizeData = METRIC_HOLE_TABLE[screwSize];
|
|
48736
|
-
if (!sizeData) throw new Error(`datumEnclosureAssembly: unsupported screwSize "${screwSize}"`);
|
|
48737
|
-
const innerWidth = width - wallThickness * 2;
|
|
48738
|
-
const innerDepth = depth - wallThickness * 2;
|
|
48739
|
-
if (innerWidth <= ledgeWidth * 2 + 8 || innerDepth <= ledgeWidth * 2 + 8) {
|
|
48740
|
-
throw new Error("datumEnclosureAssembly: wallThickness and ledgeWidth leave too little internal opening");
|
|
48741
|
-
}
|
|
48742
|
-
if (height <= baseThickness + coverThickness + 4) {
|
|
48743
|
-
throw new Error("datumEnclosureAssembly: height must leave room for internal ribs and standoffs");
|
|
48744
|
-
}
|
|
48745
|
-
const standoffDiameter = requirePositive$6(
|
|
48746
|
-
options.standoffDiameter ?? Math.max(sizeData.head * 1.65, sizeData.close * 2.2),
|
|
48747
|
-
"standoffDiameter"
|
|
48748
|
-
);
|
|
48749
|
-
const minInset = wallThickness + Math.max(ledgeWidth, standoffDiameter / 2 + 1.2);
|
|
48750
|
-
const [insetX, insetY] = resolveBoltInset(options.screwInset, minInset);
|
|
48751
|
-
if (insetX * 2 >= width || insetY * 2 >= depth) {
|
|
48752
|
-
throw new Error("datumEnclosureAssembly: screwInset leaves no room for the standoff datum");
|
|
48753
|
-
}
|
|
48754
|
-
const screwPositions = options.screwPositions ?? [
|
|
48755
|
-
[-width / 2 + insetX, -depth / 2 + insetY],
|
|
48756
|
-
[width / 2 - insetX, -depth / 2 + insetY],
|
|
48757
|
-
[-width / 2 + insetX, depth / 2 - insetY],
|
|
48758
|
-
[width / 2 - insetX, depth / 2 - insetY]
|
|
48759
|
-
];
|
|
48760
|
-
if (screwPositions.length === 0) throw new Error("datumEnclosureAssembly: screwPositions must contain at least one point");
|
|
48761
|
-
for (const [index2, [x2, y2]] of screwPositions.entries()) {
|
|
48762
|
-
if (!Number.isFinite(x2) || !Number.isFinite(y2)) {
|
|
48763
|
-
throw new Error(`datumEnclosureAssembly: screwPositions[${index2}] must contain finite numbers`);
|
|
48764
|
-
}
|
|
48765
|
-
if (Math.abs(x2) + standoffDiameter / 2 > innerWidth / 2 || Math.abs(y2) + standoffDiameter / 2 > innerDepth / 2) {
|
|
48766
|
-
throw new Error(`datumEnclosureAssembly: screwPositions[${index2}] does not fit inside the enclosure walls`);
|
|
48767
|
-
}
|
|
48768
|
-
}
|
|
48769
|
-
const ribHeight = requirePositive$6(options.ribHeight ?? Math.min(height * 0.24, Math.max(2.4, baseThickness * 1.4)), "ribHeight");
|
|
48770
|
-
const ribThickness = requirePositive$6(options.ribThickness ?? Math.max(1.2, wallThickness * 0.75), "ribThickness");
|
|
48771
|
-
const portWidth = requirePositive$6(options.portWidth ?? Math.min(innerWidth * 0.28, Math.max(12, width * 0.16)), "portWidth");
|
|
48772
|
-
const portHeight = requirePositive$6(options.portHeight ?? Math.min(height * 0.42, Math.max(5, height * 0.28)), "portHeight");
|
|
48773
|
-
if (portWidth >= innerWidth - ledgeWidth * 2) {
|
|
48774
|
-
throw new Error("datumEnclosureAssembly: portWidth must fit between internal ledges and standoffs");
|
|
48775
|
-
}
|
|
48776
|
-
if (portHeight >= height - baseThickness - 1) {
|
|
48777
|
-
throw new Error("datumEnclosureAssembly: portHeight must leave material above and below the service port");
|
|
48778
|
-
}
|
|
48779
|
-
const screwLength = requirePositive$6(options.screwLength ?? coverThickness + gasketThickness + Math.max(6, height * 0.45), "screwLength");
|
|
48780
|
-
const coverHole = fastenerHole({
|
|
48781
|
-
size: screwSize,
|
|
48782
|
-
fit: coverFit,
|
|
48783
|
-
depth: coverThickness + 0.6,
|
|
48784
|
-
center: true,
|
|
48785
|
-
segments,
|
|
48786
|
-
counterbore: { depth: Math.min(coverThickness * 0.6, Math.max(0.6, coverThickness - 0.35)) }
|
|
48787
|
-
});
|
|
48788
|
-
const standoffTap = fastenerHole({ size: screwSize, fit: "tap", depth: height + 0.8, center: true, segments });
|
|
48789
|
-
const standoffThreadEnvelope = fastenerHole({ size: screwSize, fit: "close", depth: height + 0.8, center: true, segments });
|
|
48790
|
-
const coverClearance = placeCutterAtPositions(coverHole, screwPositions, coverThickness / 2);
|
|
48791
|
-
const standoffTappedPattern = placeCutterAtPositions(standoffTap, screwPositions, height / 2);
|
|
48792
|
-
const standoffThreadEnvelopePattern = placeCutterAtPositions(standoffThreadEnvelope, screwPositions, height / 2);
|
|
48793
|
-
const fuseOverlap = Math.min(0.06, Math.max(0.02, wallThickness * 0.02));
|
|
48794
|
-
const ledgeThickness = Math.min(Math.max(1.1, coverThickness * 0.45), height * 0.2);
|
|
48795
|
-
const sideX = width / 2 - wallThickness / 2;
|
|
48796
|
-
const sideY = depth / 2 - wallThickness / 2;
|
|
48797
|
-
const ledgeZ = height - ledgeThickness;
|
|
48798
|
-
const baseSolids = [
|
|
48799
|
-
box(width, depth, baseThickness),
|
|
48800
|
-
box(wallThickness, depth, height).translate(sideX, 0, 0),
|
|
48801
|
-
box(wallThickness, depth, height).translate(-sideX, 0, 0),
|
|
48802
|
-
box(width, wallThickness, height).translate(0, sideY, 0),
|
|
48803
|
-
box(width, wallThickness, height).translate(0, -sideY, 0),
|
|
48804
|
-
box(ledgeWidth, innerDepth, ledgeThickness).translate(-width / 2 + wallThickness + ledgeWidth / 2, 0, ledgeZ),
|
|
48805
|
-
box(ledgeWidth, innerDepth, ledgeThickness).translate(width / 2 - wallThickness - ledgeWidth / 2, 0, ledgeZ),
|
|
48806
|
-
box(innerWidth, ledgeWidth, ledgeThickness).translate(0, -depth / 2 + wallThickness + ledgeWidth / 2, ledgeZ),
|
|
48807
|
-
box(innerWidth, ledgeWidth, ledgeThickness).translate(0, depth / 2 - wallThickness - ledgeWidth / 2, ledgeZ),
|
|
48808
|
-
box(Math.max(1, innerWidth - standoffDiameter * 1.8), ribThickness, ribHeight + fuseOverlap).translate(
|
|
48809
|
-
0,
|
|
48810
|
-
0,
|
|
48811
|
-
baseThickness - fuseOverlap
|
|
48812
|
-
),
|
|
48813
|
-
box(ribThickness, Math.max(1, innerDepth - standoffDiameter * 1.8), ribHeight + fuseOverlap).translate(
|
|
48814
|
-
0,
|
|
48815
|
-
0,
|
|
48816
|
-
baseThickness - fuseOverlap
|
|
48817
|
-
),
|
|
48818
|
-
...screwPositions.map(
|
|
48819
|
-
([x2, y2]) => cylinder(height - baseThickness + fuseOverlap, standoffDiameter / 2, void 0, segments).translate(
|
|
48820
|
-
x2,
|
|
48821
|
-
y2,
|
|
48822
|
-
baseThickness - fuseOverlap
|
|
48823
|
-
)
|
|
48824
|
-
)
|
|
48825
|
-
];
|
|
48826
|
-
const servicePort = box(portWidth, wallThickness + 1, portHeight).translate(
|
|
48827
|
-
0,
|
|
48828
|
-
-depth / 2 + wallThickness / 2,
|
|
48829
|
-
baseThickness + Math.max(0.8, (height - baseThickness - portHeight) * 0.35)
|
|
48830
|
-
);
|
|
48831
|
-
const base = union(...baseSolids).subtract(standoffThreadEnvelopePattern).subtract(servicePort).color("#475569");
|
|
48832
|
-
const gasketFrameCutter = box(Math.max(1, width - ledgeWidth * 2), Math.max(1, depth - ledgeWidth * 2), gasketThickness + 0.6).translate(
|
|
48833
|
-
0,
|
|
48834
|
-
0,
|
|
48835
|
-
-0.3
|
|
48836
|
-
);
|
|
48837
|
-
const gasket = gasketThickness > 0 ? box(width, depth, gasketThickness).subtract(gasketFrameCutter).subtract(placeCutterAtPositions(coverHole, screwPositions, gasketThickness / 2)).translate(0, 0, height + faceClearance).color("#111827") : null;
|
|
48838
|
-
const coverZ = height + faceClearance + (gasket ? gasketThickness + faceClearance : 0);
|
|
48839
|
-
const cover = box(width, depth, coverThickness).subtract(coverClearance).translate(0, 0, coverZ).color("#334155");
|
|
48840
|
-
const hardware = fastenerSet(screwSize, screwLength, { washerUnderHead: false, washerUnderNut: false, fit: coverFit, segments });
|
|
48841
|
-
const screwOriginZ = coverZ + coverThickness;
|
|
48842
|
-
const screws = screwPositions.map(([x2, y2]) => hardware.bolt.translate(x2, y2, screwOriginZ).color("#94a3b8"));
|
|
48843
|
-
const parts = [
|
|
48844
|
-
{ name: "datum enclosure base tray with walls ribs standoffs and service port", shape: base },
|
|
48845
|
-
...gasket ? [{ name: "datum enclosure gasket seated on continuous ledge", shape: gasket }] : [],
|
|
48846
|
-
{ name: "datum enclosure cover plate with matched screw pattern", shape: cover },
|
|
48847
|
-
...screws.map((shape, index2) => ({ name: `installed ${screwSize} enclosure screw ${index2 + 1}`, shape }))
|
|
48848
|
-
];
|
|
48849
|
-
return {
|
|
48850
|
-
parts,
|
|
48851
|
-
base,
|
|
48852
|
-
cover,
|
|
48853
|
-
gasket,
|
|
48854
|
-
screws,
|
|
48855
|
-
screwPositions,
|
|
48856
|
-
cutters: {
|
|
48857
|
-
coverClearance,
|
|
48858
|
-
standoffTapped: standoffTappedPattern,
|
|
48859
|
-
standoffThreadEnvelope: standoffThreadEnvelopePattern,
|
|
48860
|
-
servicePort
|
|
48861
|
-
},
|
|
48862
|
-
dims: {
|
|
48863
|
-
width,
|
|
48864
|
-
depth,
|
|
48865
|
-
height,
|
|
48866
|
-
innerWidth,
|
|
48867
|
-
innerDepth,
|
|
48868
|
-
wallThickness,
|
|
48869
|
-
baseThickness,
|
|
48870
|
-
coverThickness,
|
|
48871
|
-
ledgeWidth,
|
|
48872
|
-
gasketThickness,
|
|
48873
|
-
faceClearance,
|
|
48874
|
-
screwSize,
|
|
48875
|
-
screwLength,
|
|
48876
|
-
standoffDiameter,
|
|
48877
|
-
ribHeight,
|
|
48878
|
-
ribThickness,
|
|
48879
|
-
portWidth,
|
|
48880
|
-
portHeight,
|
|
48881
|
-
clearanceDia: sizeData[coverFit],
|
|
48882
|
-
tapDia: sizeData.tap,
|
|
48883
|
-
threadEnvelopeDia: sizeData.close
|
|
48884
|
-
}
|
|
48885
|
-
};
|
|
48886
|
-
}
|
|
48887
|
-
function snapLatchCoverAssembly(options) {
|
|
48888
|
-
const width = requirePositive$6(options.width, "width");
|
|
48889
|
-
const depth = requirePositive$6(options.depth, "depth");
|
|
48890
|
-
const coverThickness = requirePositive$6(options.coverThickness ?? 2.4, "coverThickness");
|
|
48891
|
-
const parentThickness = requirePositive$6(options.parentThickness ?? 6, "parentThickness");
|
|
48892
|
-
const ledgeWidth = requirePositive$6(options.ledgeWidth ?? 8, "ledgeWidth");
|
|
48893
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.25, "runningClearance");
|
|
48894
|
-
const faceClearance = requirePositive$6(options.faceClearance ?? 0.04, "faceClearance");
|
|
48895
|
-
const latchWidth = requirePositive$6(options.latchWidth ?? Math.min(width * 0.22, Math.max(12, width * 0.16)), "latchWidth");
|
|
48896
|
-
const latchThickness = requirePositive$6(options.latchThickness ?? 1.6, "latchThickness");
|
|
48897
|
-
const hookThrow = requirePositive$6(options.hookThrow ?? 3.2, "hookThrow");
|
|
48898
|
-
const hookThickness = requirePositive$6(options.hookThickness ?? 1.6, "hookThickness");
|
|
48899
|
-
const openingWidth = width - ledgeWidth * 2;
|
|
48900
|
-
const openingDepth = depth - ledgeWidth * 2;
|
|
48901
|
-
if (openingWidth <= Math.max(8, latchWidth * 0.8) || openingDepth <= 8) {
|
|
48902
|
-
throw new Error("snapLatchCoverAssembly: ledgeWidth leaves too little service opening under the cover");
|
|
48903
|
-
}
|
|
48904
|
-
if (latchWidth >= openingWidth) {
|
|
48905
|
-
throw new Error("snapLatchCoverAssembly: latchWidth must fit along the receiver opening");
|
|
48906
|
-
}
|
|
48907
|
-
if (latchThickness + runningClearance * 2 >= ledgeWidth) {
|
|
48908
|
-
throw new Error("snapLatchCoverAssembly: latchThickness and clearance must fit inside the receiver ledge");
|
|
48909
|
-
}
|
|
48910
|
-
if (hookThrow + latchThickness / 2 + runningClearance >= ledgeWidth * 1.5) {
|
|
48911
|
-
throw new Error("snapLatchCoverAssembly: hookThrow is too large for the available underside catch land");
|
|
48912
|
-
}
|
|
48913
|
-
const parentWidth = width + ledgeWidth * 2;
|
|
48914
|
-
const parentDepth = depth + ledgeWidth * 2;
|
|
48915
|
-
const fuseOverlap = Math.min(0.04, faceClearance * 0.7);
|
|
48916
|
-
const hookClearance = Math.min(0.08, runningClearance * 0.32);
|
|
48917
|
-
const coverMinZ = parentThickness + faceClearance;
|
|
48918
|
-
const stemMinZ = -hookClearance - hookThickness;
|
|
48919
|
-
const stemHeight = coverMinZ + fuseOverlap - stemMinZ;
|
|
48920
|
-
const slotY = openingDepth / 2 + ledgeWidth / 2;
|
|
48921
|
-
const latchWindow = (sign2) => box(latchWidth + runningClearance * 2, latchThickness + runningClearance * 2, parentThickness + 0.8).translate(0, sign2 * slotY, -0.4);
|
|
48922
|
-
const latchWindows = union(latchWindow(1), latchWindow(-1));
|
|
48923
|
-
const serviceOpening = box(openingWidth, openingDepth, parentThickness + 1).translate(0, 0, -0.5);
|
|
48924
|
-
const parent = box(parentWidth, parentDepth, parentThickness).subtract(serviceOpening).subtract(latchWindows).color("#475569");
|
|
48925
|
-
const coverPlate = box(width, depth, coverThickness).translate(0, 0, coverMinZ);
|
|
48926
|
-
const snapHook = (sign2) => {
|
|
48927
|
-
const y2 = sign2 * slotY;
|
|
48928
|
-
const stem = box(latchWidth, latchThickness, stemHeight).translate(0, y2, stemMinZ);
|
|
48929
|
-
const barb = box(latchWidth, latchThickness + hookThrow, hookThickness).translate(0, y2 + sign2 * (hookThrow / 2), stemMinZ);
|
|
48930
|
-
const rootRib = box(latchWidth, Math.max(latchThickness, hookThrow * 0.55), coverThickness * 0.65).translate(
|
|
48931
|
-
0,
|
|
48932
|
-
y2 - sign2 * (ledgeWidth * 0.18),
|
|
48933
|
-
coverMinZ
|
|
48934
|
-
);
|
|
48935
|
-
return union(stem, barb, rootRib);
|
|
48936
|
-
};
|
|
48937
|
-
const cover = union(coverPlate, snapHook(1), snapHook(-1)).color("#111827");
|
|
48938
|
-
const parts = [
|
|
48939
|
-
{ name: "snap cover receiver frame with latch windows and catch lands", shape: parent },
|
|
48940
|
-
{ name: "one-piece snap cover with fused hooks and underside barbs", shape: cover }
|
|
48941
|
-
];
|
|
48942
|
-
return {
|
|
48943
|
-
parts,
|
|
48944
|
-
parent,
|
|
48945
|
-
cover,
|
|
48946
|
-
cutters: {
|
|
48947
|
-
serviceOpening,
|
|
48948
|
-
latchWindows
|
|
48949
|
-
},
|
|
48950
|
-
dims: {
|
|
48951
|
-
width,
|
|
48952
|
-
depth,
|
|
48953
|
-
parentWidth,
|
|
48954
|
-
parentDepth,
|
|
48955
|
-
openingWidth,
|
|
48956
|
-
openingDepth,
|
|
48957
|
-
coverThickness,
|
|
48958
|
-
parentThickness,
|
|
48959
|
-
ledgeWidth,
|
|
48960
|
-
latchWidth,
|
|
48961
|
-
latchThickness,
|
|
48962
|
-
hookThrow,
|
|
48963
|
-
hookThickness,
|
|
48964
|
-
runningClearance,
|
|
48965
|
-
faceClearance
|
|
48966
|
-
}
|
|
48967
|
-
};
|
|
48968
|
-
}
|
|
48969
|
-
function pinnedLeverAssembly(options) {
|
|
48970
|
-
const armLength = requirePositive$6(options.armLength, "armLength");
|
|
48971
|
-
const armWidth = requirePositive$6(options.armWidth ?? 10, "armWidth");
|
|
48972
|
-
const leverThickness = requirePositive$6(options.leverThickness ?? 5, "leverThickness");
|
|
48973
|
-
const pinDiameter = requirePositive$6(options.pinDiameter ?? 5, "pinDiameter");
|
|
48974
|
-
const pinClearance = requireNonNegative(options.pinClearance ?? 0.25, "pinClearance");
|
|
48975
|
-
const boreDiameter = pinDiameter + pinClearance;
|
|
48976
|
-
const hubRadius = requirePositive$6(options.hubRadius ?? Math.max(armWidth * 0.85, pinDiameter * 1.8), "hubRadius");
|
|
48977
|
-
const supportThickness = requirePositive$6(options.supportThickness ?? Math.max(6, pinDiameter * 1.4), "supportThickness");
|
|
48978
|
-
const supportWidth = requirePositive$6(options.supportWidth ?? hubRadius * 2 + 18, "supportWidth");
|
|
48979
|
-
const supportDepth = requirePositive$6(options.supportDepth ?? Math.max(armWidth + 18, hubRadius * 2 + 10), "supportDepth");
|
|
48980
|
-
const washerSize = options.washerSize ?? metricWasherSizeForPin(pinDiameter);
|
|
48981
|
-
const washerDims = WASHER_TABLE[washerSize];
|
|
48982
|
-
if (!washerDims) throw new Error(`pinnedLeverAssembly: unsupported washerSize "${washerSize}"`);
|
|
48983
|
-
if (washerDims.id <= pinDiameter) {
|
|
48984
|
-
throw new Error(`pinnedLeverAssembly: ${washerSize} washer inner diameter is too small for a ${pinDiameter} mm pin`);
|
|
48985
|
-
}
|
|
48986
|
-
if (hubRadius <= boreDiameter / 2 + Math.max(1, pinDiameter * 0.25)) {
|
|
48987
|
-
throw new Error("pinnedLeverAssembly: hubRadius leaves too little material around the pivot bore");
|
|
48988
|
-
}
|
|
48989
|
-
if (supportWidth <= boreDiameter + 4 || supportDepth <= boreDiameter + 4) {
|
|
48990
|
-
throw new Error("pinnedLeverAssembly: support dimensions leave too little material around the pivot bore");
|
|
48991
|
-
}
|
|
48992
|
-
const segments = options.segments ?? 40;
|
|
48993
|
-
const gripLength = requirePositive$6(options.gripLength ?? Math.min(armLength * 0.32, Math.max(16, armWidth * 2.4)), "gripLength");
|
|
48994
|
-
const gripWidth = requirePositive$6(options.gripWidth ?? armWidth * 1.55, "gripWidth");
|
|
48995
|
-
if (gripLength >= armLength) throw new Error("pinnedLeverAssembly: gripLength must be shorter than armLength");
|
|
48996
|
-
const armOverlap = Math.min(hubRadius * 0.65, armLength * 0.25);
|
|
48997
|
-
const armStartX = hubRadius - armOverlap;
|
|
48998
|
-
const armCenterX = armStartX + armLength / 2;
|
|
48999
|
-
const gripCenterX = armStartX + armLength - gripLength / 2;
|
|
49000
|
-
const runningClearance = 0.03;
|
|
49001
|
-
const lowerWasherZ = supportThickness + runningClearance;
|
|
49002
|
-
const leverZ = lowerWasherZ + washerDims.t + runningClearance;
|
|
49003
|
-
const upperWasherZ = leverZ + leverThickness + runningClearance;
|
|
49004
|
-
const stackHeight = upperWasherZ + washerDims.t;
|
|
49005
|
-
const pinHeadThickness = Math.max(washerDims.t, pinDiameter * 0.35);
|
|
49006
|
-
const pinHeadRadius = Math.max(washerDims.od * 0.42, pinDiameter * 0.8);
|
|
49007
|
-
const supportBore = cylinder(supportThickness + 1, boreDiameter / 2, void 0, segments).translate(0, 0, -0.5);
|
|
49008
|
-
let supportBlank = box(supportWidth, supportDepth, supportThickness);
|
|
49009
|
-
if (options.stopBlock ?? true) {
|
|
49010
|
-
const stopLength = Math.min(armLength * 0.22, Math.max(10, armWidth * 1.4));
|
|
49011
|
-
const stopWidth = Math.max(4, pinDiameter * 0.7);
|
|
49012
|
-
const stopHeight = supportThickness;
|
|
49013
|
-
const stopX = hubRadius + stopLength / 2;
|
|
49014
|
-
const stopY = armWidth / 2 + stopWidth / 2 + runningClearance;
|
|
49015
|
-
supportBlank = union(supportBlank, box(stopLength, stopWidth, stopHeight).translate(stopX, stopY, 0));
|
|
49016
|
-
}
|
|
49017
|
-
const support = supportBlank.subtract(supportBore).color("#475569");
|
|
49018
|
-
const hub = cylinder(leverThickness, hubRadius, void 0, segments);
|
|
49019
|
-
const arm = box(armLength, armWidth, leverThickness).translate(armCenterX, 0, 0);
|
|
49020
|
-
const grip = box(gripLength, gripWidth, leverThickness).translate(gripCenterX, 0, 0);
|
|
49021
|
-
const leverSolids = [hub, arm, grip];
|
|
49022
|
-
if (options.detentBoss ?? true) {
|
|
49023
|
-
const bossRadius = Math.min(armWidth * 0.42, hubRadius * 0.42);
|
|
49024
|
-
const bossX = hubRadius + Math.min(armLength * 0.22, armWidth * 2);
|
|
49025
|
-
const bossY = -armWidth / 2 - bossRadius * 0.45;
|
|
49026
|
-
leverSolids.push(cylinder(leverThickness, bossRadius, void 0, segments).translate(bossX, bossY, 0));
|
|
49027
|
-
}
|
|
49028
|
-
const leverBore = cylinder(leverThickness + 1, boreDiameter / 2, void 0, segments).translate(0, 0, -0.5);
|
|
49029
|
-
const lever = union(...leverSolids).subtract(leverBore).translate(0, 0, leverZ).color("#7f1d1d");
|
|
49030
|
-
const lowerWasher = washer(washerSize, { segments }).translate(0, 0, lowerWasherZ).color("#94a3b8");
|
|
49031
|
-
const upperWasher = washer(washerSize, { segments }).translate(0, 0, upperWasherZ).color("#94a3b8");
|
|
49032
|
-
const shaft = cylinder(stackHeight, pinDiameter / 2, void 0, segments);
|
|
49033
|
-
const lowerRetainer = cylinder(pinHeadThickness, pinHeadRadius, void 0, segments).translate(
|
|
49034
|
-
0,
|
|
49035
|
-
0,
|
|
49036
|
-
-pinHeadThickness - runningClearance
|
|
49037
|
-
);
|
|
49038
|
-
const upperHead = cylinder(pinHeadThickness, pinHeadRadius, void 0, segments).translate(0, 0, stackHeight + runningClearance);
|
|
49039
|
-
const pin = union(shaft, lowerRetainer, upperHead).color("#cbd5e1");
|
|
49040
|
-
const pivotBore = cylinder(stackHeight + 1, boreDiameter / 2, void 0, segments).translate(0, 0, -0.5);
|
|
49041
|
-
const parts = [
|
|
49042
|
-
{ name: "pivot support block with bearing bore and low stop land", shape: support },
|
|
49043
|
-
{ name: "lower thrust washer under pinned lever", shape: lowerWasher },
|
|
49044
|
-
{ name: "fused pinned lever with hub arm grip and detent boss", shape: lever },
|
|
49045
|
-
{ name: "upper thrust washer over pinned lever", shape: upperWasher },
|
|
49046
|
-
{ name: "retained pivot pin through lever stack", shape: pin }
|
|
49047
|
-
];
|
|
49048
|
-
return {
|
|
49049
|
-
parts,
|
|
49050
|
-
support,
|
|
49051
|
-
lever,
|
|
49052
|
-
pin,
|
|
49053
|
-
washers: {
|
|
49054
|
-
lower: lowerWasher,
|
|
49055
|
-
upper: upperWasher
|
|
49056
|
-
},
|
|
49057
|
-
cutters: {
|
|
49058
|
-
pivotBore
|
|
49059
|
-
},
|
|
49060
|
-
dims: {
|
|
49061
|
-
armLength,
|
|
49062
|
-
armWidth,
|
|
49063
|
-
leverThickness,
|
|
49064
|
-
hubRadius,
|
|
49065
|
-
pinDiameter,
|
|
49066
|
-
boreDiameter,
|
|
49067
|
-
supportWidth,
|
|
49068
|
-
supportDepth,
|
|
49069
|
-
supportThickness,
|
|
49070
|
-
washerSize,
|
|
49071
|
-
washerThickness: washerDims.t,
|
|
49072
|
-
stackHeight
|
|
49073
|
-
}
|
|
49074
|
-
};
|
|
49075
|
-
}
|
|
49076
|
-
function retainedShaftAssembly(options) {
|
|
49077
|
-
const supportSpacing = requirePositive$6(options.supportSpacing, "supportSpacing");
|
|
49078
|
-
const shaftDiameter = requirePositive$6(options.shaftDiameter ?? 8, "shaftDiameter");
|
|
49079
|
-
const boreClearance = requireNonNegative(options.boreClearance ?? 0.35, "boreClearance");
|
|
49080
|
-
const boreDiameter = shaftDiameter + boreClearance;
|
|
49081
|
-
const supportThickness = requirePositive$6(options.supportThickness ?? Math.max(5, shaftDiameter * 0.75), "supportThickness");
|
|
49082
|
-
const washerSize = options.washerSize ?? metricWasherSizeForPin(shaftDiameter);
|
|
49083
|
-
const washerDims = WASHER_TABLE[washerSize];
|
|
49084
|
-
if (!washerDims) throw new Error(`retainedShaftAssembly: unsupported washerSize "${washerSize}"`);
|
|
49085
|
-
if (washerDims.id <= shaftDiameter) {
|
|
49086
|
-
throw new Error(`retainedShaftAssembly: ${washerSize} washer inner diameter is too small for a ${shaftDiameter} mm shaft`);
|
|
49087
|
-
}
|
|
49088
|
-
const knobDiameter = requirePositive$6(options.knobDiameter ?? shaftDiameter * 3, "knobDiameter");
|
|
49089
|
-
const knobThickness = requirePositive$6(options.knobThickness ?? Math.max(8, shaftDiameter), "knobThickness");
|
|
49090
|
-
const retainerThickness = requirePositive$6(options.retainerThickness ?? Math.max(washerDims.t, shaftDiameter * 0.35), "retainerThickness");
|
|
49091
|
-
const runningClearance = requireNonNegative(options.runningClearance ?? 0.05, "runningClearance");
|
|
49092
|
-
const supportWidth = requirePositive$6(options.supportWidth ?? Math.max(28, knobDiameter * 1.25), "supportWidth");
|
|
49093
|
-
const supportHeight = requirePositive$6(options.supportHeight ?? Math.max(34, knobDiameter * 1.45), "supportHeight");
|
|
49094
|
-
const segments = options.segments ?? 40;
|
|
49095
|
-
if (supportSpacing <= supportThickness) {
|
|
49096
|
-
throw new Error("retainedShaftAssembly: supportSpacing must leave a gap between support cheeks");
|
|
49097
|
-
}
|
|
49098
|
-
if (supportWidth <= boreDiameter + 4 || supportHeight <= boreDiameter + 4) {
|
|
49099
|
-
throw new Error("retainedShaftAssembly: support dimensions leave too little material around the shaft bore");
|
|
49100
|
-
}
|
|
49101
|
-
const leftSupportX = -supportSpacing / 2;
|
|
49102
|
-
const rightSupportX = supportSpacing / 2;
|
|
49103
|
-
const leftOuterFaceX = leftSupportX - supportThickness / 2;
|
|
49104
|
-
const rightOuterFaceX = rightSupportX + supportThickness / 2;
|
|
49105
|
-
const leftWasherX = leftOuterFaceX - runningClearance - washerDims.t / 2;
|
|
49106
|
-
const rightWasherX = rightOuterFaceX + runningClearance + washerDims.t / 2;
|
|
49107
|
-
const leftKnobX = leftOuterFaceX - runningClearance * 2 - washerDims.t - knobThickness / 2;
|
|
49108
|
-
const rightKnobX = rightOuterFaceX + runningClearance * 2 + washerDims.t + knobThickness / 2;
|
|
49109
|
-
const leftStackOuterX = leftKnobX - knobThickness / 2;
|
|
49110
|
-
const rightStackOuterX = rightKnobX + knobThickness / 2;
|
|
49111
|
-
const minimumShaftLength = rightStackOuterX - leftStackOuterX + retainerThickness * 2 + runningClearance * 2;
|
|
49112
|
-
const shaftLength = requirePositive$6(options.shaftLength ?? minimumShaftLength, "shaftLength");
|
|
49113
|
-
if (shaftLength < minimumShaftLength) {
|
|
49114
|
-
throw new Error("retainedShaftAssembly: shaftLength is too short to retain both supports, washers, and knobs");
|
|
49115
|
-
}
|
|
49116
|
-
const supportBore = cylinderAlongX(supportThickness + 1, boreDiameter / 2, 0, segments);
|
|
49117
|
-
const makeSupport = (x2) => box(supportThickness, supportWidth, supportHeight).translate(x2, 0, -supportHeight / 2).subtract(supportBore.translate(x2, 0, 0)).color("#334155");
|
|
49118
|
-
const knobBore = cylinder(knobThickness + 1, boreDiameter / 2, void 0, segments).translate(0, 0, -0.5);
|
|
49119
|
-
const makeKnob = (x2) => cylinder(knobThickness, knobDiameter / 2, void 0, 18).subtract(knobBore).pointAlong([1, 0, 0]).translate(x2 - knobThickness / 2, 0, 0).color("#111827");
|
|
49120
|
-
const retainerRadius = Math.max(shaftDiameter * 0.85, knobDiameter * 0.36);
|
|
49121
|
-
const shaftCore = cylinderAlongX(shaftLength, shaftDiameter / 2, 0, segments);
|
|
49122
|
-
const leftRetainer = cylinderAlongX(retainerThickness, retainerRadius, -shaftLength / 2 + retainerThickness / 2, segments);
|
|
49123
|
-
const rightRetainer = cylinderAlongX(retainerThickness, retainerRadius, shaftLength / 2 - retainerThickness / 2, segments);
|
|
49124
|
-
const shaft = union(shaftCore, leftRetainer, rightRetainer).color("#cbd5e1");
|
|
49125
|
-
const leftSupport = makeSupport(leftSupportX);
|
|
49126
|
-
const rightSupport = makeSupport(rightSupportX);
|
|
49127
|
-
const leftWasher = washerAlongX(washerSize, leftWasherX, segments).color("#94a3b8");
|
|
49128
|
-
const rightWasher = washerAlongX(washerSize, rightWasherX, segments).color("#94a3b8");
|
|
49129
|
-
const leftKnob = makeKnob(leftKnobX);
|
|
49130
|
-
const rightKnob = makeKnob(rightKnobX);
|
|
49131
|
-
const shaftBore = cylinderAlongX(supportThickness + knobThickness + 2, boreDiameter / 2, 0, segments);
|
|
49132
|
-
const parts = [
|
|
49133
|
-
{ name: "left bored support cheek for retained shaft", shape: leftSupport },
|
|
49134
|
-
{ name: "right bored support cheek for retained shaft", shape: rightSupport },
|
|
49135
|
-
{ name: "retained through shaft with end heads", shape: shaft },
|
|
49136
|
-
{ name: `left ${washerSize} thrust washer on shaft`, shape: leftWasher },
|
|
49137
|
-
{ name: `right ${washerSize} thrust washer on shaft`, shape: rightWasher },
|
|
49138
|
-
{ name: "left retained hand knob with shaft bore", shape: leftKnob },
|
|
49139
|
-
{ name: "right retained hand knob with shaft bore", shape: rightKnob }
|
|
49140
|
-
];
|
|
49141
|
-
return {
|
|
49142
|
-
parts,
|
|
49143
|
-
supports: {
|
|
49144
|
-
left: leftSupport,
|
|
49145
|
-
right: rightSupport
|
|
49146
|
-
},
|
|
49147
|
-
shaft,
|
|
49148
|
-
washers: {
|
|
49149
|
-
left: leftWasher,
|
|
49150
|
-
right: rightWasher
|
|
49151
|
-
},
|
|
49152
|
-
knobs: {
|
|
49153
|
-
left: leftKnob,
|
|
49154
|
-
right: rightKnob
|
|
49155
|
-
},
|
|
49156
|
-
cutters: {
|
|
49157
|
-
shaftBore
|
|
49158
|
-
},
|
|
49159
|
-
dims: {
|
|
49160
|
-
supportSpacing,
|
|
49161
|
-
supportThickness,
|
|
49162
|
-
supportWidth,
|
|
49163
|
-
supportHeight,
|
|
49164
|
-
shaftDiameter,
|
|
49165
|
-
shaftLength,
|
|
49166
|
-
boreDiameter,
|
|
49167
|
-
washerSize,
|
|
49168
|
-
washerThickness: washerDims.t,
|
|
49169
|
-
knobDiameter,
|
|
49170
|
-
knobThickness,
|
|
49171
|
-
retainerThickness,
|
|
49172
|
-
runningClearance
|
|
49173
|
-
}
|
|
49174
|
-
};
|
|
49175
|
-
}
|
|
49176
|
-
function capturedLinearSlide(options) {
|
|
49177
|
-
const length4 = requirePositive$6(options.length, "length");
|
|
49178
|
-
const railWidth = requirePositive$6(options.railWidth ?? 38, "railWidth");
|
|
49179
|
-
const baseThickness = requirePositive$6(options.baseThickness ?? 2.4, "baseThickness");
|
|
49180
|
-
const wallThickness = requirePositive$6(options.wallThickness ?? 2, "wallThickness");
|
|
49181
|
-
const wallHeight = requirePositive$6(options.wallHeight ?? 9, "wallHeight");
|
|
49182
|
-
const lipWidth = requirePositive$6(options.lipWidth ?? 4, "lipWidth");
|
|
49183
|
-
const lipThickness = requirePositive$6(options.lipThickness ?? 1.8, "lipThickness");
|
|
49184
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.35, "runningClearance");
|
|
49185
|
-
const endStopLength = requirePositive$6(options.endStopLength ?? 6, "endStopLength");
|
|
49186
|
-
const carriageLength = requirePositive$6(options.carriageLength ?? length4 * 0.32, "carriageLength");
|
|
49187
|
-
const innerWidth = railWidth - wallThickness * 2;
|
|
49188
|
-
const throatWidth = innerWidth - lipWidth * 2;
|
|
49189
|
-
if (innerWidth <= 0) throw new Error("capturedLinearSlide: wallThickness leaves no inner rail width");
|
|
49190
|
-
if (throatWidth <= 0) throw new Error("capturedLinearSlide: lipWidth closes the rail throat");
|
|
49191
|
-
const carriageWidth = requirePositive$6(options.carriageWidth ?? innerWidth - runningClearance * 2, "carriageWidth");
|
|
49192
|
-
const carriageThickness = requirePositive$6(options.carriageThickness ?? 4, "carriageThickness");
|
|
49193
|
-
if (carriageWidth >= innerWidth - runningClearance) {
|
|
49194
|
-
throw new Error("capturedLinearSlide: carriageWidth leaves too little side clearance inside the rail");
|
|
49195
|
-
}
|
|
49196
|
-
if (carriageWidth <= throatWidth + runningClearance) {
|
|
49197
|
-
throw new Error("capturedLinearSlide: carriageWidth must be wider than the lip throat so the rail actually captures it");
|
|
49198
|
-
}
|
|
49199
|
-
if (carriageThickness + runningClearance * 2 >= wallHeight) {
|
|
49200
|
-
throw new Error("capturedLinearSlide: carriage is too tall to clear the return lips");
|
|
49201
|
-
}
|
|
49202
|
-
const maxTravel = length4 - endStopLength * 2 - carriageLength;
|
|
49203
|
-
if (maxTravel <= 0) {
|
|
49204
|
-
throw new Error("capturedLinearSlide: rail length, end stops, and carriage length leave no travel");
|
|
49205
|
-
}
|
|
49206
|
-
const travel = options.travel ?? maxTravel / 2;
|
|
49207
|
-
if (!Number.isFinite(travel) || travel < 0 || travel > maxTravel) {
|
|
49208
|
-
throw new Error(`capturedLinearSlide: travel must be between 0 and ${maxTravel}`);
|
|
49209
|
-
}
|
|
49210
|
-
const carriageCenterX = -maxTravel / 2 + travel;
|
|
49211
|
-
const fuseOverlap = Math.min(0.04, runningClearance * 0.1);
|
|
49212
|
-
const sideY = railWidth / 2 - wallThickness / 2;
|
|
49213
|
-
const lipY = railWidth / 2 - wallThickness - lipWidth / 2 + fuseOverlap / 2;
|
|
49214
|
-
const stopZ = baseThickness - fuseOverlap;
|
|
49215
|
-
const rail2 = union(
|
|
49216
|
-
box(length4, railWidth, baseThickness),
|
|
49217
|
-
box(length4, wallThickness, wallHeight + fuseOverlap).translate(0, sideY, baseThickness - fuseOverlap),
|
|
49218
|
-
box(length4, wallThickness, wallHeight + fuseOverlap).translate(0, -sideY, baseThickness - fuseOverlap),
|
|
49219
|
-
box(length4, lipWidth, lipThickness + fuseOverlap).translate(0, lipY, baseThickness + wallHeight - fuseOverlap),
|
|
49220
|
-
box(length4, lipWidth, lipThickness + fuseOverlap).translate(0, -lipY, baseThickness + wallHeight - fuseOverlap),
|
|
49221
|
-
box(endStopLength, throatWidth, carriageThickness + fuseOverlap).translate(-length4 / 2 + endStopLength / 2, 0, stopZ),
|
|
49222
|
-
box(endStopLength, throatWidth, carriageThickness + fuseOverlap).translate(length4 / 2 - endStopLength / 2, 0, stopZ)
|
|
49223
|
-
).color("#475569");
|
|
49224
|
-
const carriage = union(
|
|
49225
|
-
box(carriageLength, carriageWidth, carriageThickness),
|
|
49226
|
-
box(carriageLength * 0.78, throatWidth - runningClearance * 2, Math.max(1, carriageThickness * 0.38)).translate(
|
|
49227
|
-
0,
|
|
49228
|
-
0,
|
|
49229
|
-
carriageThickness
|
|
49230
|
-
)
|
|
49231
|
-
).translate(carriageCenterX, 0, baseThickness + runningClearance).color("#111827");
|
|
49232
|
-
const parts = [
|
|
49233
|
-
{ name: "captured linear rail with return lips and end stops", shape: rail2 },
|
|
49234
|
-
{ name: "sliding carriage captured under rail lips", shape: carriage }
|
|
49235
|
-
];
|
|
49236
|
-
return {
|
|
49237
|
-
parts,
|
|
49238
|
-
rail: rail2,
|
|
49239
|
-
carriage,
|
|
49240
|
-
dims: {
|
|
49241
|
-
length: length4,
|
|
49242
|
-
railWidth,
|
|
49243
|
-
innerWidth,
|
|
49244
|
-
throatWidth,
|
|
49245
|
-
baseThickness,
|
|
49246
|
-
wallThickness,
|
|
49247
|
-
wallHeight,
|
|
49248
|
-
lipWidth,
|
|
49249
|
-
lipThickness,
|
|
49250
|
-
carriageLength,
|
|
49251
|
-
carriageWidth,
|
|
49252
|
-
carriageThickness,
|
|
49253
|
-
endStopLength,
|
|
49254
|
-
runningClearance,
|
|
49255
|
-
maxTravel,
|
|
49256
|
-
travel,
|
|
49257
|
-
carriageCenterX
|
|
49258
|
-
}
|
|
49259
|
-
};
|
|
49260
|
-
}
|
|
49261
|
-
function capturedCartridgeGuideAssembly(options) {
|
|
49262
|
-
const length4 = requirePositive$6(options.length, "length");
|
|
49263
|
-
const guideWidth = requirePositive$6(options.guideWidth ?? 42, "guideWidth");
|
|
49264
|
-
const baseThickness = requirePositive$6(options.baseThickness ?? 3, "baseThickness");
|
|
49265
|
-
const wallThickness = requirePositive$6(options.wallThickness ?? 2.5, "wallThickness");
|
|
49266
|
-
const wallHeight = requirePositive$6(options.wallHeight ?? 12, "wallHeight");
|
|
49267
|
-
const lipWidth = requirePositive$6(options.lipWidth ?? 4, "lipWidth");
|
|
49268
|
-
const lipThickness = requirePositive$6(options.lipThickness ?? 2, "lipThickness");
|
|
49269
|
-
const rearStopLength = requirePositive$6(options.rearStopLength ?? 7, "rearStopLength");
|
|
49270
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.35, "runningClearance");
|
|
49271
|
-
const cartridgeLength = requirePositive$6(options.cartridgeLength ?? length4 * 0.58, "cartridgeLength");
|
|
49272
|
-
const cartridgeHeight = requirePositive$6(options.cartridgeHeight ?? 10, "cartridgeHeight");
|
|
49273
|
-
const flangeThickness = requirePositive$6(options.flangeThickness ?? 3, "flangeThickness");
|
|
49274
|
-
const pullTabLength = requirePositive$6(options.pullTabLength ?? 10, "pullTabLength");
|
|
49275
|
-
const innerWidth = guideWidth - wallThickness * 2;
|
|
49276
|
-
const throatWidth = innerWidth - lipWidth * 2;
|
|
49277
|
-
if (innerWidth <= 0) throw new Error("capturedCartridgeGuideAssembly: wallThickness leaves no inner guide width");
|
|
49278
|
-
if (throatWidth <= 0) throw new Error("capturedCartridgeGuideAssembly: lipWidth closes the guide throat");
|
|
49279
|
-
if (wallHeight <= lipThickness + flangeThickness + runningClearance * 2) {
|
|
49280
|
-
throw new Error("capturedCartridgeGuideAssembly: wallHeight leaves too little vertical capture clearance");
|
|
49281
|
-
}
|
|
49282
|
-
const cartridgeWidth = requirePositive$6(options.cartridgeWidth ?? innerWidth - runningClearance * 2, "cartridgeWidth");
|
|
49283
|
-
const cartridgeBodyWidth = throatWidth - runningClearance * 2;
|
|
49284
|
-
if (cartridgeBodyWidth <= 0) {
|
|
49285
|
-
throw new Error("capturedCartridgeGuideAssembly: throatWidth and runningClearance leave no cartridge body width");
|
|
49286
|
-
}
|
|
49287
|
-
if (cartridgeWidth >= innerWidth - runningClearance) {
|
|
49288
|
-
throw new Error("capturedCartridgeGuideAssembly: cartridgeWidth leaves too little side clearance inside the guide");
|
|
49289
|
-
}
|
|
49290
|
-
if (cartridgeWidth <= throatWidth + runningClearance) {
|
|
49291
|
-
throw new Error("capturedCartridgeGuideAssembly: cartridge flange must be wider than the guide throat so the cartridge is captured");
|
|
49292
|
-
}
|
|
49293
|
-
const maxInsertion = length4 - rearStopLength - cartridgeLength;
|
|
49294
|
-
if (maxInsertion <= 0) {
|
|
49295
|
-
throw new Error("capturedCartridgeGuideAssembly: length, rearStopLength, and cartridgeLength leave no insertion travel");
|
|
49296
|
-
}
|
|
49297
|
-
const insertion = options.insertion ?? maxInsertion * 0.4;
|
|
49298
|
-
if (!Number.isFinite(insertion) || insertion < 0 || insertion > maxInsertion) {
|
|
49299
|
-
throw new Error(`capturedCartridgeGuideAssembly: insertion must be between 0 and ${maxInsertion}`);
|
|
49300
|
-
}
|
|
49301
|
-
const cartridgeCenterX = -length4 / 2 + cartridgeLength / 2 + insertion;
|
|
49302
|
-
const fuseOverlap = Math.min(0.04, runningClearance * 0.1);
|
|
49303
|
-
const sideY = guideWidth / 2 - wallThickness / 2;
|
|
49304
|
-
const lipY = guideWidth / 2 - wallThickness - lipWidth / 2 + fuseOverlap / 2;
|
|
49305
|
-
const guide = union(
|
|
49306
|
-
box(length4, guideWidth, baseThickness),
|
|
49307
|
-
box(length4, wallThickness, wallHeight + fuseOverlap).translate(0, sideY, baseThickness - fuseOverlap),
|
|
49308
|
-
box(length4, wallThickness, wallHeight + fuseOverlap).translate(0, -sideY, baseThickness - fuseOverlap),
|
|
49309
|
-
box(length4, lipWidth, lipThickness + fuseOverlap).translate(0, lipY, baseThickness + wallHeight - fuseOverlap),
|
|
49310
|
-
box(length4, lipWidth, lipThickness + fuseOverlap).translate(0, -lipY, baseThickness + wallHeight - fuseOverlap),
|
|
49311
|
-
box(rearStopLength, throatWidth, Math.max(flangeThickness + runningClearance, 4)).translate(
|
|
49312
|
-
length4 / 2 - rearStopLength / 2,
|
|
49313
|
-
0,
|
|
49314
|
-
baseThickness - fuseOverlap
|
|
49315
|
-
)
|
|
49316
|
-
).color("#475569");
|
|
49317
|
-
const flangeZ = baseThickness + runningClearance;
|
|
49318
|
-
const bodyHeight = Math.max(1, cartridgeHeight - flangeThickness);
|
|
49319
|
-
const bodyZ = flangeZ + flangeThickness;
|
|
49320
|
-
const tabOverlap = Math.min(0.6, pullTabLength * 0.15);
|
|
49321
|
-
const pullTabX = cartridgeCenterX - cartridgeLength / 2 - pullTabLength / 2 + tabOverlap;
|
|
49322
|
-
const pullTabWidth = Math.max(cartridgeBodyWidth * 0.55, 12);
|
|
49323
|
-
const cartridge = union(
|
|
49324
|
-
box(cartridgeLength, cartridgeWidth, flangeThickness).translate(cartridgeCenterX, 0, flangeZ),
|
|
49325
|
-
box(cartridgeLength * 0.88, cartridgeBodyWidth, bodyHeight).translate(cartridgeCenterX, 0, bodyZ),
|
|
49326
|
-
box(pullTabLength, pullTabWidth, Math.max(flangeThickness, 3)).translate(pullTabX, 0, flangeZ)
|
|
49327
|
-
).color("#111827");
|
|
49328
|
-
const parts = [
|
|
49329
|
-
{ name: "captured cartridge guide with return lips and rear stop", shape: guide },
|
|
49330
|
-
{ name: "removable cartridge with captured flange and pull tab", shape: cartridge }
|
|
49331
|
-
];
|
|
49332
|
-
return {
|
|
49333
|
-
parts,
|
|
49334
|
-
guide,
|
|
49335
|
-
cartridge,
|
|
49336
|
-
dims: {
|
|
49337
|
-
length: length4,
|
|
49338
|
-
guideWidth,
|
|
49339
|
-
innerWidth,
|
|
49340
|
-
throatWidth,
|
|
49341
|
-
baseThickness,
|
|
49342
|
-
wallThickness,
|
|
49343
|
-
wallHeight,
|
|
49344
|
-
lipWidth,
|
|
49345
|
-
lipThickness,
|
|
49346
|
-
rearStopLength,
|
|
49347
|
-
cartridgeLength,
|
|
49348
|
-
cartridgeWidth,
|
|
49349
|
-
cartridgeBodyWidth,
|
|
49350
|
-
cartridgeHeight,
|
|
49351
|
-
flangeThickness,
|
|
49352
|
-
pullTabLength,
|
|
49353
|
-
runningClearance,
|
|
49354
|
-
maxInsertion,
|
|
49355
|
-
insertion,
|
|
49356
|
-
cartridgeCenterX
|
|
49357
|
-
}
|
|
49358
|
-
};
|
|
49359
|
-
}
|
|
49360
|
-
function livingHingeCoverAssembly(options) {
|
|
49361
|
-
const width = requirePositive$6(options.width, "width");
|
|
49362
|
-
const coverDepth = requirePositive$6(options.coverDepth ?? 42, "coverDepth");
|
|
49363
|
-
const fixedLeafDepth = requirePositive$6(options.fixedLeafDepth ?? 18, "fixedLeafDepth");
|
|
49364
|
-
const leafThickness = requirePositive$6(options.leafThickness ?? 2, "leafThickness");
|
|
49365
|
-
const hingeWebWidth = requirePositive$6(options.hingeWebWidth ?? 3.2, "hingeWebWidth");
|
|
49366
|
-
const hingeWebThickness = requirePositive$6(options.hingeWebThickness ?? 0.45, "hingeWebThickness");
|
|
49367
|
-
const pullLipDepth = requirePositive$6(options.pullLipDepth ?? 5, "pullLipDepth");
|
|
49368
|
-
const snapBarbWidth = requirePositive$6(options.snapBarbWidth ?? width * 0.35, "snapBarbWidth");
|
|
49369
|
-
const snapBarbDepth = requirePositive$6(options.snapBarbDepth ?? 2.4, "snapBarbDepth");
|
|
49370
|
-
const snapBarbHeight = requirePositive$6(options.snapBarbHeight ?? 1.4, "snapBarbHeight");
|
|
49371
|
-
const catchLandDepth = requirePositive$6(options.catchLandDepth ?? 2.4, "catchLandDepth");
|
|
49372
|
-
if (hingeWebThickness >= leafThickness * 0.55) {
|
|
49373
|
-
throw new Error("livingHingeCoverAssembly: hingeWebThickness must be much thinner than the rigid leaves");
|
|
49374
|
-
}
|
|
49375
|
-
if (hingeWebWidth >= Math.min(coverDepth, fixedLeafDepth) * 0.45) {
|
|
49376
|
-
throw new Error("livingHingeCoverAssembly: hingeWebWidth is too wide for the selected leaves");
|
|
49377
|
-
}
|
|
49378
|
-
if (snapBarbWidth >= width - 2) {
|
|
49379
|
-
throw new Error("livingHingeCoverAssembly: snapBarbWidth must leave side material on the cover leaf");
|
|
49380
|
-
}
|
|
49381
|
-
const fuseOverlap = Math.min(0.04, hingeWebWidth * 0.02);
|
|
49382
|
-
const fixedCenterY = -hingeWebWidth / 2 - fixedLeafDepth / 2 + fuseOverlap / 2;
|
|
49383
|
-
const coverCenterY = hingeWebWidth / 2 + coverDepth / 2 - fuseOverlap / 2;
|
|
49384
|
-
const fixedLeaf = box(width, fixedLeafDepth + fuseOverlap, leafThickness).translate(0, fixedCenterY, 0);
|
|
49385
|
-
const movingLeaf = box(width, coverDepth + fuseOverlap, leafThickness).translate(0, coverCenterY, 0);
|
|
49386
|
-
const hingeWeb = box(width, hingeWebWidth + fuseOverlap * 2, hingeWebThickness).translate(0, 0, 0);
|
|
49387
|
-
const pullLip = box(width * 0.92, pullLipDepth, leafThickness).translate(
|
|
49388
|
-
0,
|
|
49389
|
-
coverCenterY + coverDepth / 2 + pullLipDepth / 2 - fuseOverlap,
|
|
49390
|
-
0
|
|
49391
|
-
);
|
|
49392
|
-
const snapBarb = box(snapBarbWidth, snapBarbDepth, snapBarbHeight).translate(
|
|
49393
|
-
0,
|
|
49394
|
-
coverCenterY + coverDepth / 2 - snapBarbDepth / 2,
|
|
49395
|
-
leafThickness
|
|
49396
|
-
);
|
|
49397
|
-
const catchLand = box(width * 0.55, catchLandDepth, Math.max(0.8, leafThickness * 0.45)).translate(
|
|
49398
|
-
0,
|
|
49399
|
-
fixedCenterY - fixedLeafDepth / 2 + catchLandDepth / 2,
|
|
49400
|
-
leafThickness
|
|
49401
|
-
);
|
|
49402
|
-
const cover = union(fixedLeaf, movingLeaf, hingeWeb, pullLip, snapBarb, catchLand).color("#0f766e");
|
|
49403
|
-
const overallDepth = fixedLeafDepth + hingeWebWidth + coverDepth + pullLipDepth;
|
|
49404
|
-
const flexRatio = leafThickness / hingeWebThickness;
|
|
49405
|
-
return {
|
|
49406
|
-
parts: [{ name: "one-piece molded living hinge cover with snap barb", shape: cover }],
|
|
49407
|
-
cover,
|
|
49408
|
-
fixedLeaf,
|
|
49409
|
-
movingLeaf,
|
|
49410
|
-
hingeWeb,
|
|
49411
|
-
snapBarb,
|
|
49412
|
-
catchLand,
|
|
49413
|
-
dims: {
|
|
49414
|
-
width,
|
|
49415
|
-
coverDepth,
|
|
49416
|
-
fixedLeafDepth,
|
|
49417
|
-
leafThickness,
|
|
49418
|
-
hingeWebWidth,
|
|
49419
|
-
hingeWebThickness,
|
|
49420
|
-
pullLipDepth,
|
|
49421
|
-
snapBarbWidth,
|
|
49422
|
-
snapBarbDepth,
|
|
49423
|
-
snapBarbHeight,
|
|
49424
|
-
catchLandDepth,
|
|
49425
|
-
flexRatio,
|
|
49426
|
-
overallDepth
|
|
49427
|
-
}
|
|
49428
|
-
};
|
|
49429
|
-
}
|
|
49430
|
-
function knuckledHingeAssembly(options) {
|
|
49431
|
-
const length4 = requirePositive$6(options.length, "length");
|
|
49432
|
-
const leafLength = requirePositive$6(options.leafLength ?? 36, "leafLength");
|
|
49433
|
-
const leafThickness = requirePositive$6(options.leafThickness ?? 1.6, "leafThickness");
|
|
49434
|
-
const barrelOuterRadius = requirePositive$6(options.barrelOuterRadius ?? 3, "barrelOuterRadius");
|
|
49435
|
-
const pinDiameter = requirePositive$6(options.pinDiameter ?? 2, "pinDiameter");
|
|
49436
|
-
const pinClearance = requireNonNegative(options.pinClearance ?? 0.25, "pinClearance");
|
|
49437
|
-
const boreDiameter = pinDiameter + pinClearance;
|
|
49438
|
-
const knuckleGap = requireNonNegative(options.knuckleGap ?? 0.45, "knuckleGap");
|
|
49439
|
-
const openAngleDeg = Number.isFinite(options.openAngleDeg ?? 35) ? options.openAngleDeg ?? 35 : 35;
|
|
49440
|
-
const retainerThickness = requirePositive$6(options.retainerThickness ?? Math.max(leafThickness, pinDiameter * 0.7), "retainerThickness");
|
|
49441
|
-
const segments = options.segments ?? 36;
|
|
49442
|
-
const knuckleCount = options.knuckleCount ?? 5;
|
|
49443
|
-
if (!Number.isInteger(knuckleCount) || knuckleCount < 3 || knuckleCount % 2 === 0) {
|
|
49444
|
-
throw new Error("knuckledHingeAssembly: knuckleCount must be an odd integer >= 3");
|
|
49445
|
-
}
|
|
49446
|
-
if (barrelOuterRadius <= boreDiameter / 2 + Math.max(0.35, pinDiameter * 0.18)) {
|
|
49447
|
-
throw new Error("knuckledHingeAssembly: barrelOuterRadius leaves too little wall around the pin bore");
|
|
49448
|
-
}
|
|
49449
|
-
const knuckleLength = (length4 - knuckleGap * (knuckleCount - 1)) / knuckleCount;
|
|
49450
|
-
if (knuckleLength <= pinDiameter * 1.4) {
|
|
49451
|
-
throw new Error("knuckledHingeAssembly: length, knuckleCount, and knuckleGap make knuckles too short");
|
|
49452
|
-
}
|
|
49453
|
-
const leafRootClearance = Math.max(0.12, Math.min(knuckleGap * 0.35, 0.35));
|
|
49454
|
-
const barrelLeafOverlap = Math.min(barrelOuterRadius * 0.18, leafThickness * 0.35);
|
|
49455
|
-
const bridgeDepth = leafRootClearance + barrelLeafOverlap + 0.2;
|
|
49456
|
-
const fixedLeafPlate = box(length4, leafLength, leafThickness).translate(
|
|
49457
|
-
0,
|
|
49458
|
-
barrelOuterRadius + leafRootClearance + leafLength / 2,
|
|
49459
|
-
-leafThickness / 2
|
|
49460
|
-
);
|
|
49461
|
-
const movingLeafPlate = box(length4, leafLength, leafThickness).translate(
|
|
49462
|
-
0,
|
|
49463
|
-
-barrelOuterRadius - leafRootClearance - leafLength / 2,
|
|
49464
|
-
-leafThickness / 2
|
|
49465
|
-
);
|
|
49466
|
-
const fixedKnuckles = [];
|
|
49467
|
-
const movingKnuckles = [];
|
|
49468
|
-
const fixedBridges = [];
|
|
49469
|
-
const movingBridges = [];
|
|
49470
|
-
for (let index2 = 0; index2 < knuckleCount; index2 += 1) {
|
|
49471
|
-
const xStart = -length4 / 2 + index2 * (knuckleLength + knuckleGap);
|
|
49472
|
-
const xCenter = xStart + knuckleLength / 2;
|
|
49473
|
-
const knuckle = tubeAlongX(knuckleLength, barrelOuterRadius, boreDiameter / 2, xCenter, segments);
|
|
49474
|
-
if (index2 % 2 === 0) {
|
|
49475
|
-
fixedKnuckles.push(knuckle);
|
|
49476
|
-
fixedBridges.push(
|
|
49477
|
-
box(knuckleLength, bridgeDepth, leafThickness).translate(
|
|
49478
|
-
xCenter,
|
|
49479
|
-
barrelOuterRadius - barrelLeafOverlap + bridgeDepth / 2,
|
|
49480
|
-
-leafThickness / 2
|
|
49481
|
-
)
|
|
49482
|
-
);
|
|
49483
|
-
} else {
|
|
49484
|
-
movingKnuckles.push(knuckle);
|
|
49485
|
-
movingBridges.push(
|
|
49486
|
-
box(knuckleLength, bridgeDepth, leafThickness).translate(
|
|
49487
|
-
xCenter,
|
|
49488
|
-
-barrelOuterRadius + barrelLeafOverlap - bridgeDepth / 2,
|
|
49489
|
-
-leafThickness / 2
|
|
49490
|
-
)
|
|
49491
|
-
);
|
|
49492
|
-
}
|
|
49493
|
-
}
|
|
49494
|
-
const fixedLeaf = union(fixedLeafPlate, ...fixedKnuckles, ...fixedBridges).color("#475569");
|
|
49495
|
-
const movingLeaf = union(movingLeafPlate, ...movingKnuckles, ...movingBridges).rotateX(openAngleDeg).color("#111827");
|
|
49496
|
-
const pinCore = cylinderAlongX(length4 + retainerThickness * 2, pinDiameter / 2, 0, segments);
|
|
49497
|
-
const retainerRadius = Math.max(barrelOuterRadius * 0.85, pinDiameter);
|
|
49498
|
-
const leftHead = cylinderAlongX(retainerThickness, retainerRadius, -length4 / 2 - retainerThickness / 2, segments);
|
|
49499
|
-
const rightHead = cylinderAlongX(retainerThickness, retainerRadius, length4 / 2 + retainerThickness / 2, segments);
|
|
49500
|
-
const pin = union(pinCore, leftHead, rightHead).color("#cbd5e1");
|
|
49501
|
-
const pinBore = cylinderAlongX(length4 + retainerThickness * 2, boreDiameter / 2, 0, segments);
|
|
49502
|
-
const parts = [
|
|
49503
|
-
{ name: "fixed hinge leaf with alternating knuckles", shape: fixedLeaf },
|
|
49504
|
-
{ name: "moving hinge leaf with alternating knuckles", shape: movingLeaf },
|
|
49505
|
-
{ name: "retained hinge pin through knuckle stack", shape: pin }
|
|
49506
|
-
];
|
|
49507
|
-
return {
|
|
49508
|
-
parts,
|
|
49509
|
-
fixedLeaf,
|
|
49510
|
-
movingLeaf,
|
|
49511
|
-
pin,
|
|
49512
|
-
cutters: {
|
|
49513
|
-
pinBore
|
|
49514
|
-
},
|
|
49515
|
-
dims: {
|
|
49516
|
-
length: length4,
|
|
49517
|
-
leafLength,
|
|
49518
|
-
leafThickness,
|
|
49519
|
-
barrelOuterRadius,
|
|
49520
|
-
pinDiameter,
|
|
49521
|
-
boreDiameter,
|
|
49522
|
-
knuckleGap,
|
|
49523
|
-
knuckleCount,
|
|
49524
|
-
knuckleLength,
|
|
49525
|
-
openAngleDeg,
|
|
49526
|
-
retainerThickness
|
|
49527
|
-
}
|
|
49528
|
-
};
|
|
49529
|
-
}
|
|
49530
|
-
function clevisPinJointAssembly(options = {}) {
|
|
49531
|
-
const pinDiameter = requirePositive$6(options.pinDiameter ?? 4, "pinDiameter");
|
|
49532
|
-
const pinClearance = requireNonNegative(options.pinClearance ?? 0.3, "pinClearance");
|
|
49533
|
-
const boreDiameter = pinDiameter + pinClearance;
|
|
49534
|
-
const linkThickness = requirePositive$6(options.linkThickness ?? Math.max(5, pinDiameter * 1.5), "linkThickness");
|
|
49535
|
-
const earThickness = requirePositive$6(options.earThickness ?? Math.max(3.5, pinDiameter), "earThickness");
|
|
49536
|
-
const runningClearance = requireNonNegative(options.runningClearance ?? 0.25, "runningClearance");
|
|
49537
|
-
const linkArmWidth = requirePositive$6(options.linkArmWidth ?? pinDiameter * 2.4, "linkArmWidth");
|
|
49538
|
-
const eyeOuterRadius = requirePositive$6(options.eyeOuterRadius ?? Math.max(pinDiameter * 1.8, linkArmWidth / 2 + 1.4), "eyeOuterRadius");
|
|
49539
|
-
const earLength = requirePositive$6(options.earLength ?? Math.max(eyeOuterRadius * 2.55, pinDiameter * 4.2), "earLength");
|
|
49540
|
-
const earHeight = requirePositive$6(options.earHeight ?? Math.max(eyeOuterRadius * 2.25, pinDiameter * 4.4), "earHeight");
|
|
49541
|
-
const linkArmLength = requirePositive$6(options.linkArmLength ?? 34, "linkArmLength");
|
|
49542
|
-
const retainerThickness = requirePositive$6(options.retainerThickness ?? Math.max(1.2, pinDiameter * 0.35), "retainerThickness");
|
|
49543
|
-
const segments = options.segments ?? 40;
|
|
49544
|
-
if (eyeOuterRadius <= boreDiameter / 2 + Math.max(0.8, pinDiameter * 0.25)) {
|
|
49545
|
-
throw new Error("clevisPinJointAssembly: eyeOuterRadius leaves too little material around the pin bore");
|
|
49546
|
-
}
|
|
49547
|
-
if (earHeight <= boreDiameter + Math.max(3, pinDiameter)) {
|
|
49548
|
-
throw new Error("clevisPinJointAssembly: earHeight leaves too little material around the pin bore");
|
|
49549
|
-
}
|
|
49550
|
-
if (earLength / 2 <= eyeOuterRadius + runningClearance) {
|
|
49551
|
-
throw new Error("clevisPinJointAssembly: earLength must extend behind the link eye for a rear clevis bridge");
|
|
49552
|
-
}
|
|
49553
|
-
const clevisGap = linkThickness + runningClearance * 2;
|
|
49554
|
-
const earCenterY = clevisGap / 2 + earThickness / 2;
|
|
49555
|
-
const totalStackY = clevisGap + earThickness * 2;
|
|
49556
|
-
const pinLength = totalStackY + retainerThickness * 2 + runningClearance * 2;
|
|
49557
|
-
const bridgeClearX = -eyeOuterRadius - runningClearance;
|
|
49558
|
-
const bridgeLength = Math.max(pinDiameter * 2.2, 4);
|
|
49559
|
-
const bridgeHeight = Math.min(earHeight * 0.48, Math.max(pinDiameter * 1.4, eyeOuterRadius * 0.75));
|
|
49560
|
-
const bridgeCenterX = bridgeClearX - bridgeLength / 2;
|
|
49561
|
-
const bridgeCenterZ = -earHeight / 2 + bridgeHeight / 2;
|
|
49562
|
-
const pinBore = cylinderAlongY(totalStackY + 0.8, boreDiameter / 2, 0, segments);
|
|
49563
|
-
const clevisBlank = union(
|
|
49564
|
-
box(earLength, earThickness, earHeight).translate(0, earCenterY, -earHeight / 2),
|
|
49565
|
-
box(earLength, earThickness, earHeight).translate(0, -earCenterY, -earHeight / 2),
|
|
49566
|
-
box(bridgeLength, totalStackY, bridgeHeight).translate(bridgeCenterX, 0, bridgeCenterZ)
|
|
49567
|
-
);
|
|
49568
|
-
const clevis = clevisBlank.subtract(pinBore).color("#475569");
|
|
49569
|
-
const eye = tubeAlongY(linkThickness, eyeOuterRadius, boreDiameter / 2, 0, segments);
|
|
49570
|
-
const armOverlap = Math.min(eyeOuterRadius * 0.65, linkArmLength * 0.25);
|
|
49571
|
-
const armCenterX = eyeOuterRadius - armOverlap + linkArmLength / 2;
|
|
49572
|
-
const linkArm = box(linkArmLength, linkThickness, linkArmWidth).translate(armCenterX, 0, -linkArmWidth / 2);
|
|
49573
|
-
const link = union(eye, linkArm).color("#111827");
|
|
49574
|
-
const pinCore = cylinderAlongY(pinLength, pinDiameter / 2, 0, segments);
|
|
49575
|
-
const headRadius = Math.max(pinDiameter * 0.9, boreDiameter / 2 + 0.8);
|
|
49576
|
-
const headY = totalStackY / 2 + runningClearance + retainerThickness / 2;
|
|
49577
|
-
const headA = cylinderAlongY(retainerThickness, headRadius, headY, segments);
|
|
49578
|
-
const headB = cylinderAlongY(retainerThickness, headRadius, -headY, segments);
|
|
49579
|
-
const pin = union(pinCore, headA, headB).color("#cbd5e1");
|
|
49580
|
-
const cutter = cylinderAlongY(pinLength + 1, boreDiameter / 2, 0, segments);
|
|
49581
|
-
const parts = [
|
|
49582
|
-
{ name: "bored clevis yoke with rear bridge", shape: clevis },
|
|
49583
|
-
{ name: "center link eye captured in clevis", shape: link },
|
|
49584
|
-
{ name: "retained clevis pin through link eye", shape: pin }
|
|
49585
|
-
];
|
|
49586
|
-
return {
|
|
49587
|
-
parts,
|
|
49588
|
-
clevis,
|
|
49589
|
-
link,
|
|
49590
|
-
pin,
|
|
49591
|
-
cutters: {
|
|
49592
|
-
pinBore: cutter
|
|
49593
|
-
},
|
|
49594
|
-
dims: {
|
|
49595
|
-
pinDiameter,
|
|
49596
|
-
boreDiameter,
|
|
49597
|
-
linkThickness,
|
|
49598
|
-
earThickness,
|
|
49599
|
-
runningClearance,
|
|
49600
|
-
earLength,
|
|
49601
|
-
earHeight,
|
|
49602
|
-
linkArmLength,
|
|
49603
|
-
linkArmWidth,
|
|
49604
|
-
eyeOuterRadius,
|
|
49605
|
-
retainerThickness,
|
|
49606
|
-
pinLength,
|
|
49607
|
-
clevisGap
|
|
49608
|
-
}
|
|
49609
|
-
};
|
|
49610
|
-
}
|
|
49611
|
-
function seatedBearingAssembly(options) {
|
|
49612
|
-
const bearingOuterDiameter = requirePositive$6(options.bearingOuterDiameter, "bearingOuterDiameter");
|
|
49613
|
-
const bearingInnerDiameter = requirePositive$6(options.bearingInnerDiameter, "bearingInnerDiameter");
|
|
49614
|
-
const bearingWidth = requirePositive$6(options.bearingWidth, "bearingWidth");
|
|
49615
|
-
const shaftDiameter = requirePositive$6(options.shaftDiameter ?? Math.max(1, bearingInnerDiameter - 0.4), "shaftDiameter");
|
|
49616
|
-
const pocketClearance = requireNonNegative(options.pocketClearance ?? 0.2, "pocketClearance");
|
|
49617
|
-
const shaftClearance = requireNonNegative(options.shaftClearance ?? 0.35, "shaftClearance");
|
|
49618
|
-
const runningClearance = requireNonNegative(options.runningClearance ?? 0.05, "runningClearance");
|
|
49619
|
-
const housingThickness = requirePositive$6(options.housingThickness ?? bearingWidth + 5, "housingThickness");
|
|
49620
|
-
const bossHeight = requirePositive$6(options.bossHeight ?? Math.max(2, bearingWidth * 0.45), "bossHeight");
|
|
49621
|
-
const bossOuterDiameter = requirePositive$6(
|
|
49622
|
-
options.bossOuterDiameter ?? bearingOuterDiameter + Math.max(8, bearingOuterDiameter * 0.36),
|
|
49623
|
-
"bossOuterDiameter"
|
|
49624
|
-
);
|
|
49625
|
-
const housingWidth = requirePositive$6(
|
|
49626
|
-
options.housingWidth ?? Math.max(bossOuterDiameter + 12, bearingOuterDiameter * 2.1),
|
|
49627
|
-
"housingWidth"
|
|
49628
|
-
);
|
|
49629
|
-
const housingDepth = requirePositive$6(
|
|
49630
|
-
options.housingDepth ?? Math.max(bossOuterDiameter + 12, bearingOuterDiameter * 1.8),
|
|
49631
|
-
"housingDepth"
|
|
49632
|
-
);
|
|
49633
|
-
const shaftOverhang = requirePositive$6(options.shaftOverhang ?? Math.max(8, bearingOuterDiameter * 0.45), "shaftOverhang");
|
|
49634
|
-
const shoulderDiameter = requirePositive$6(
|
|
49635
|
-
options.shoulderDiameter ?? Math.max(shaftDiameter * 1.65, bearingInnerDiameter + 2),
|
|
49636
|
-
"shoulderDiameter"
|
|
49637
|
-
);
|
|
49638
|
-
const shoulderThickness = requirePositive$6(options.shoulderThickness ?? Math.max(1.5, shaftDiameter * 0.32), "shoulderThickness");
|
|
49639
|
-
const segments = options.segments ?? 48;
|
|
49640
|
-
if (bearingOuterDiameter <= bearingInnerDiameter + Math.max(1, bearingOuterDiameter * 0.08)) {
|
|
49641
|
-
throw new Error("seatedBearingAssembly: bearingOuterDiameter leaves too little bearing wall around the bore");
|
|
49642
|
-
}
|
|
49643
|
-
if (shaftDiameter + shaftClearance >= bearingInnerDiameter) {
|
|
49644
|
-
throw new Error("seatedBearingAssembly: shaftDiameter plus shaftClearance must fit inside the bearing bore");
|
|
49645
|
-
}
|
|
49646
|
-
if (shoulderDiameter >= bearingOuterDiameter - runningClearance * 2) {
|
|
49647
|
-
throw new Error("seatedBearingAssembly: shoulderDiameter must stay smaller than the bearing outer race");
|
|
49648
|
-
}
|
|
49649
|
-
const pocketDiameter = bearingOuterDiameter + pocketClearance;
|
|
49650
|
-
const shaftBoreDiameter = shaftDiameter + shaftClearance;
|
|
49651
|
-
const totalHousingHeight = housingThickness + bossHeight;
|
|
49652
|
-
const pocketDepth = bearingWidth + runningClearance * 2;
|
|
49653
|
-
if (pocketDepth >= totalHousingHeight - runningClearance) {
|
|
49654
|
-
throw new Error("seatedBearingAssembly: housingThickness and bossHeight must leave a shoulder below the bearing pocket");
|
|
49655
|
-
}
|
|
49656
|
-
if (bossOuterDiameter <= pocketDiameter + Math.max(2, bearingOuterDiameter * 0.12)) {
|
|
49657
|
-
throw new Error("seatedBearingAssembly: bossOuterDiameter leaves too little wall around the bearing pocket");
|
|
49658
|
-
}
|
|
49659
|
-
if (housingWidth <= pocketDiameter + 6 || housingDepth <= pocketDiameter + 6) {
|
|
49660
|
-
throw new Error("seatedBearingAssembly: housing dimensions leave too little material around the bearing pocket");
|
|
49661
|
-
}
|
|
49662
|
-
if (shoulderThickness * 2 + runningClearance * 2 >= shaftOverhang) {
|
|
49663
|
-
throw new Error("seatedBearingAssembly: shaftOverhang must leave room for retaining collars outside the housing");
|
|
49664
|
-
}
|
|
49665
|
-
const pocketBottomZ = totalHousingHeight - pocketDepth;
|
|
49666
|
-
const bearingZ = pocketBottomZ + runningClearance;
|
|
49667
|
-
const lowerShoulderZ = -runningClearance - shoulderThickness;
|
|
49668
|
-
const upperShoulderZ = totalHousingHeight + runningClearance;
|
|
49669
|
-
const shaftLength = totalHousingHeight + shaftOverhang * 2;
|
|
49670
|
-
const bossFuseOverlap = Math.min(0.08, Math.max(0.02, bossHeight * 0.03));
|
|
49671
|
-
const bearingPocket = cylinder(pocketDepth + 0.4, pocketDiameter / 2, void 0, segments).translate(0, 0, pocketBottomZ - 0.2);
|
|
49672
|
-
const shaftBore = cylinder(totalHousingHeight + 1, shaftBoreDiameter / 2, void 0, segments).translate(0, 0, -0.5);
|
|
49673
|
-
const housingBase = box(housingWidth, housingDepth, housingThickness).subtract(bearingPocket).subtract(shaftBore);
|
|
49674
|
-
const housingBoss = cylinder(bossHeight + bossFuseOverlap, bossOuterDiameter / 2, void 0, segments).translate(0, 0, housingThickness - bossFuseOverlap).subtract(bearingPocket);
|
|
49675
|
-
const housing = union(housingBase, housingBoss).color("#475569");
|
|
49676
|
-
const bearingRing = tubeAlongZ(bearingWidth, bearingOuterDiameter / 2, bearingInnerDiameter / 2, segments);
|
|
49677
|
-
const shieldInset = Math.min(bearingWidth * 0.18, 0.7);
|
|
49678
|
-
const shieldOuterRadius = bearingOuterDiameter / 2 - Math.max(0.45, (bearingOuterDiameter - bearingInnerDiameter) * 0.08);
|
|
49679
|
-
const shieldInnerRadius = bearingInnerDiameter / 2 + Math.max(0.2, (bearingOuterDiameter - bearingInnerDiameter) * 0.035);
|
|
49680
|
-
const bearingShield = shieldOuterRadius > shieldInnerRadius + 0.2 ? union(
|
|
49681
|
-
tubeAlongZ(Math.min(0.35, bearingWidth * 0.08), shieldOuterRadius, shieldInnerRadius, segments).translate(0, 0, shieldInset),
|
|
49682
|
-
tubeAlongZ(Math.min(0.35, bearingWidth * 0.08), shieldOuterRadius, shieldInnerRadius, segments).translate(
|
|
49683
|
-
0,
|
|
49684
|
-
0,
|
|
49685
|
-
bearingWidth - shieldInset - Math.min(0.35, bearingWidth * 0.08)
|
|
49686
|
-
)
|
|
49687
|
-
) : null;
|
|
49688
|
-
const bearing = (bearingShield ? union(bearingRing, bearingShield) : bearingRing).translate(0, 0, bearingZ).color("#111827");
|
|
49689
|
-
const shaftCore = cylinder(shaftLength, shaftDiameter / 2, void 0, segments).translate(0, 0, -shaftOverhang);
|
|
49690
|
-
const lowerShoulder = cylinder(shoulderThickness, shoulderDiameter / 2, void 0, segments).translate(0, 0, lowerShoulderZ);
|
|
49691
|
-
const upperShoulder = cylinder(shoulderThickness, shoulderDiameter / 2, void 0, segments).translate(0, 0, upperShoulderZ);
|
|
49692
|
-
const shaft = union(shaftCore, lowerShoulder, upperShoulder).color("#cbd5e1");
|
|
49693
|
-
const parts = [
|
|
49694
|
-
{ name: "bearing housing with counterbore pocket and shoulder", shape: housing },
|
|
49695
|
-
{ name: "purchased radial bearing seated in counterbore", shape: bearing },
|
|
49696
|
-
{ name: "shaft through bearing bore with retaining collars", shape: shaft }
|
|
49697
|
-
];
|
|
49698
|
-
return {
|
|
49699
|
-
parts,
|
|
49700
|
-
housing,
|
|
49701
|
-
bearing,
|
|
49702
|
-
shaft,
|
|
49703
|
-
cutters: {
|
|
49704
|
-
bearingPocket,
|
|
49705
|
-
shaftBore
|
|
49706
|
-
},
|
|
49707
|
-
dims: {
|
|
49708
|
-
bearingOuterDiameter,
|
|
49709
|
-
bearingInnerDiameter,
|
|
49710
|
-
bearingWidth,
|
|
49711
|
-
shaftDiameter,
|
|
49712
|
-
housingWidth,
|
|
49713
|
-
housingDepth,
|
|
49714
|
-
housingThickness,
|
|
49715
|
-
bossOuterDiameter,
|
|
49716
|
-
bossHeight,
|
|
49717
|
-
totalHousingHeight,
|
|
49718
|
-
pocketDiameter,
|
|
49719
|
-
pocketDepth,
|
|
49720
|
-
shaftBoreDiameter,
|
|
49721
|
-
runningClearance,
|
|
49722
|
-
shaftLength,
|
|
49723
|
-
shoulderDiameter,
|
|
49724
|
-
shoulderThickness
|
|
49725
|
-
}
|
|
49726
|
-
};
|
|
49727
|
-
}
|
|
49728
|
-
function cableGlandAnchorAssembly(options) {
|
|
49729
|
-
const cableDiameter = requirePositive$6(options.cableDiameter, "cableDiameter");
|
|
49730
|
-
const panelThickness = requirePositive$6(options.panelThickness ?? 3, "panelThickness");
|
|
49731
|
-
const panelWidth = requirePositive$6(options.panelWidth ?? Math.max(54, cableDiameter * 7), "panelWidth");
|
|
49732
|
-
const panelHeight = requirePositive$6(options.panelHeight ?? Math.max(38, cableDiameter * 5), "panelHeight");
|
|
49733
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.35, "runningClearance");
|
|
49734
|
-
const panelHoleClearance = requirePositive$6(options.panelHoleClearance ?? 0.25, "panelHoleClearance");
|
|
49735
|
-
const cableBoreDiameter = cableDiameter + runningClearance * 2;
|
|
49736
|
-
const glandOuterDiameter = requirePositive$6(
|
|
49737
|
-
options.glandOuterDiameter ?? cableDiameter + Math.max(6, cableDiameter * 0.9),
|
|
49738
|
-
"glandOuterDiameter"
|
|
49739
|
-
);
|
|
49740
|
-
const nutOuterDiameter = requirePositive$6(
|
|
49741
|
-
options.nutOuterDiameter ?? glandOuterDiameter + Math.max(6, cableDiameter * 0.8),
|
|
49742
|
-
"nutOuterDiameter"
|
|
49743
|
-
);
|
|
49744
|
-
const nutThickness = requirePositive$6(options.nutThickness ?? Math.max(4, cableDiameter * 0.8), "nutThickness");
|
|
49745
|
-
const flangeDiameter = requirePositive$6(options.flangeDiameter ?? glandOuterDiameter + Math.max(5, cableDiameter * 0.7), "flangeDiameter");
|
|
49746
|
-
const flangeThickness = requirePositive$6(options.flangeThickness ?? Math.max(2, panelThickness * 0.45), "flangeThickness");
|
|
49747
|
-
const minGlandLength = panelThickness + nutThickness + flangeThickness + runningClearance * 4;
|
|
49748
|
-
const glandLength = requirePositive$6(options.glandLength ?? minGlandLength + Math.max(8, cableDiameter), "glandLength");
|
|
49749
|
-
const cableLength = requirePositive$6(options.cableLength ?? glandLength + Math.max(36, cableDiameter * 5), "cableLength");
|
|
49750
|
-
const segments = options.segments ?? 40;
|
|
49751
|
-
if (glandOuterDiameter <= cableBoreDiameter + Math.max(1.2, cableDiameter * 0.18)) {
|
|
49752
|
-
throw new Error("cableGlandAnchorAssembly: glandOuterDiameter leaves too little wall around the cable bore");
|
|
49753
|
-
}
|
|
49754
|
-
if (nutOuterDiameter <= glandOuterDiameter + Math.max(1.5, cableDiameter * 0.2)) {
|
|
49755
|
-
throw new Error("cableGlandAnchorAssembly: nutOuterDiameter must leave material around the gland body");
|
|
49756
|
-
}
|
|
49757
|
-
if (flangeDiameter <= glandOuterDiameter + Math.max(1.2, cableDiameter * 0.16)) {
|
|
49758
|
-
throw new Error("cableGlandAnchorAssembly: flangeDiameter must be larger than the gland body");
|
|
49759
|
-
}
|
|
49760
|
-
if (panelWidth <= flangeDiameter + 8 || panelHeight <= flangeDiameter + 8) {
|
|
49761
|
-
throw new Error("cableGlandAnchorAssembly: panel dimensions leave too little material around the gland hole");
|
|
49762
|
-
}
|
|
49763
|
-
if (glandLength <= minGlandLength) {
|
|
49764
|
-
throw new Error("cableGlandAnchorAssembly: glandLength must span the panel, flange, compression nut, and clearances");
|
|
49765
|
-
}
|
|
49766
|
-
if (cableLength <= glandLength + runningClearance * 2) {
|
|
49767
|
-
throw new Error("cableGlandAnchorAssembly: cableLength must extend beyond the gland body");
|
|
49768
|
-
}
|
|
49769
|
-
const panelHoleDiameter = glandOuterDiameter + panelHoleClearance * 2;
|
|
49770
|
-
const glandOuterRadius = glandOuterDiameter / 2;
|
|
49771
|
-
const cableBoreRadius = cableBoreDiameter / 2;
|
|
49772
|
-
const faceClearance = Math.min(0.05, runningClearance * 0.15);
|
|
49773
|
-
const flangePocketDepth = Math.min(Math.max(0.35, panelThickness * 0.18), panelThickness * 0.4, flangeThickness * 0.55);
|
|
49774
|
-
const panelHole = cylinderAlongX(panelThickness + 0.8, panelHoleDiameter / 2, 0, segments);
|
|
49775
|
-
const flangeSeatPocket = cylinderAlongX(
|
|
49776
|
-
flangePocketDepth + 0.2,
|
|
49777
|
-
flangeDiameter / 2 + panelHoleClearance,
|
|
49778
|
-
panelThickness / 2 - flangePocketDepth / 2,
|
|
49779
|
-
segments
|
|
49780
|
-
);
|
|
49781
|
-
const cableBore = cylinderAlongX(glandLength + 0.8, cableBoreRadius, 0, segments);
|
|
49782
|
-
const panel = box(panelThickness, panelWidth, panelHeight).translate(0, 0, -panelHeight / 2).subtract(panelHole).subtract(flangeSeatPocket).color("#475569");
|
|
49783
|
-
const glandBody = tubeAlongX(glandLength, glandOuterRadius, cableBoreRadius, 0, segments);
|
|
49784
|
-
const flangeCenterX = panelThickness / 2 - flangePocketDepth + faceClearance + flangeThickness / 2;
|
|
49785
|
-
const flange = tubeAlongX(flangeThickness, flangeDiameter / 2, cableBoreRadius, flangeCenterX, segments);
|
|
49786
|
-
const gland = union(glandBody, flange).color("#94a3b8");
|
|
49787
|
-
const nutInnerRadius = glandOuterRadius + Math.min(0.12, runningClearance * 0.4);
|
|
49788
|
-
const nutCenterX = -panelThickness / 2 - faceClearance - nutThickness / 2;
|
|
49789
|
-
const compressionNut = tubeAlongX(nutThickness, nutOuterDiameter / 2, nutInnerRadius, nutCenterX, segments).color("#cbd5e1");
|
|
49790
|
-
const cable = cylinderAlongX(cableLength, cableDiameter / 2, 0, segments).color("#111827");
|
|
49791
|
-
const parts = [
|
|
49792
|
-
{ name: "panel with gland clearance hole", shape: panel },
|
|
49793
|
-
{ name: "hollow cable gland body with panel flange", shape: gland },
|
|
49794
|
-
{ name: "compression nut around gland body", shape: compressionNut },
|
|
49795
|
-
{ name: "routed cable through gland bore", shape: cable }
|
|
49796
|
-
];
|
|
49797
|
-
return {
|
|
49798
|
-
parts,
|
|
49799
|
-
panel,
|
|
49800
|
-
gland,
|
|
49801
|
-
compressionNut,
|
|
49802
|
-
cable,
|
|
49803
|
-
cutters: {
|
|
49804
|
-
panelHole,
|
|
49805
|
-
flangeSeatPocket,
|
|
49806
|
-
cableBore
|
|
49807
|
-
},
|
|
49808
|
-
dims: {
|
|
49809
|
-
cableDiameter,
|
|
49810
|
-
cableBoreDiameter,
|
|
49811
|
-
panelThickness,
|
|
49812
|
-
panelWidth,
|
|
49813
|
-
panelHeight,
|
|
49814
|
-
glandOuterDiameter,
|
|
49815
|
-
glandLength,
|
|
49816
|
-
nutOuterDiameter,
|
|
49817
|
-
nutThickness,
|
|
49818
|
-
flangeDiameter,
|
|
49819
|
-
flangeThickness,
|
|
49820
|
-
runningClearance,
|
|
49821
|
-
faceClearance,
|
|
49822
|
-
flangePocketDepth,
|
|
49823
|
-
panelHoleDiameter,
|
|
49824
|
-
cableLength
|
|
49825
|
-
}
|
|
49826
|
-
};
|
|
49827
|
-
}
|
|
49828
|
-
function hoseBarbPortAssembly(options) {
|
|
49829
|
-
const hoseInnerDiameter = requirePositive$6(options.hoseInnerDiameter, "hoseInnerDiameter");
|
|
49830
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.18, "runningClearance");
|
|
49831
|
-
const faceClearance = requirePositive$6(options.faceClearance ?? 0.04, "faceClearance");
|
|
49832
|
-
const barbRootDiameter = requirePositive$6(
|
|
49833
|
-
options.barbRootDiameter ?? Math.max(1, hoseInnerDiameter - Math.max(0.25, hoseInnerDiameter * 0.06)),
|
|
49834
|
-
"barbRootDiameter"
|
|
49835
|
-
);
|
|
49836
|
-
const barbPeakDiameter = requirePositive$6(
|
|
49837
|
-
options.barbPeakDiameter ?? hoseInnerDiameter + Math.max(0.65, hoseInnerDiameter * 0.12),
|
|
49838
|
-
"barbPeakDiameter"
|
|
49839
|
-
);
|
|
49840
|
-
const installedHoseBoreDiameter = barbPeakDiameter + runningClearance * 2;
|
|
49841
|
-
const hoseOuterDiameter = requirePositive$6(
|
|
49842
|
-
options.hoseOuterDiameter ?? Math.max(installedHoseBoreDiameter + 2.4, hoseInnerDiameter + Math.max(3, hoseInnerDiameter * 0.55)),
|
|
49843
|
-
"hoseOuterDiameter"
|
|
49844
|
-
);
|
|
49845
|
-
const fluidBoreDiameter = requirePositive$6(options.fluidBoreDiameter ?? hoseInnerDiameter * 0.65, "fluidBoreDiameter");
|
|
49846
|
-
const blockThickness = requirePositive$6(options.blockThickness ?? Math.max(7, hoseInnerDiameter * 1.2), "blockThickness");
|
|
49847
|
-
const barbCount = options.barbCount ?? 3;
|
|
49848
|
-
const barbLength = requirePositive$6(options.barbLength ?? Math.max(2.6, hoseInnerDiameter * 0.55), "barbLength");
|
|
49849
|
-
const barbStackLength = barbCount * barbLength;
|
|
49850
|
-
const shoulderDiameter = requirePositive$6(
|
|
49851
|
-
options.shoulderDiameter ?? barbPeakDiameter + Math.max(4, hoseInnerDiameter * 0.65),
|
|
49852
|
-
"shoulderDiameter"
|
|
49853
|
-
);
|
|
49854
|
-
const shoulderThickness = requirePositive$6(options.shoulderThickness ?? Math.max(2, hoseInnerDiameter * 0.35), "shoulderThickness");
|
|
49855
|
-
const bossDiameter = requirePositive$6(options.bossDiameter ?? shoulderDiameter + Math.max(4, hoseInnerDiameter * 0.6), "bossDiameter");
|
|
49856
|
-
const bossHeight = requirePositive$6(options.bossHeight ?? Math.max(2.4, hoseInnerDiameter * 0.45), "bossHeight");
|
|
49857
|
-
const blockWidth = requirePositive$6(options.blockWidth ?? bossDiameter + Math.max(14, hoseInnerDiameter * 2.4), "blockWidth");
|
|
49858
|
-
const blockHeight = requirePositive$6(options.blockHeight ?? bossDiameter + Math.max(12, hoseInnerDiameter * 2.1), "blockHeight");
|
|
49859
|
-
const hoseLength = requirePositive$6(options.hoseLength ?? barbStackLength + Math.max(32, hoseInnerDiameter * 5), "hoseLength");
|
|
49860
|
-
const clampWidth = requirePositive$6(options.clampWidth ?? Math.max(4, hoseOuterDiameter * 0.45), "clampWidth");
|
|
49861
|
-
const clampThickness = requirePositive$6(options.clampThickness ?? 0.9, "clampThickness");
|
|
49862
|
-
const segments = options.segments ?? 40;
|
|
49863
|
-
if (!Number.isInteger(barbCount) || barbCount < 1 || barbCount > 8) {
|
|
49864
|
-
throw new Error("hoseBarbPortAssembly: barbCount must be an integer from 1 to 8");
|
|
49865
|
-
}
|
|
49866
|
-
if (barbPeakDiameter <= hoseInnerDiameter) {
|
|
49867
|
-
throw new Error("hoseBarbPortAssembly: barbPeakDiameter must exceed hoseInnerDiameter so the barb retains the hose");
|
|
49868
|
-
}
|
|
49869
|
-
if (barbRootDiameter >= barbPeakDiameter - Math.max(0.25, hoseInnerDiameter * 0.04)) {
|
|
49870
|
-
throw new Error("hoseBarbPortAssembly: barbRootDiameter must leave a visible barb rise");
|
|
49871
|
-
}
|
|
49872
|
-
if (fluidBoreDiameter >= barbRootDiameter - Math.max(0.8, hoseInnerDiameter * 0.12)) {
|
|
49873
|
-
throw new Error("hoseBarbPortAssembly: fluidBoreDiameter leaves too little wall in the barb fitting");
|
|
49874
|
-
}
|
|
49875
|
-
if (hoseOuterDiameter <= installedHoseBoreDiameter + Math.max(1.2, hoseInnerDiameter * 0.16)) {
|
|
49876
|
-
throw new Error("hoseBarbPortAssembly: hoseOuterDiameter leaves too little hose wall around the installed barb envelope");
|
|
49877
|
-
}
|
|
49878
|
-
if (shoulderDiameter <= barbPeakDiameter + Math.max(1.5, hoseInnerDiameter * 0.2)) {
|
|
49879
|
-
throw new Error("hoseBarbPortAssembly: shoulderDiameter must be larger than the barb peaks");
|
|
49880
|
-
}
|
|
49881
|
-
if (bossDiameter <= shoulderDiameter + Math.max(1.5, hoseInnerDiameter * 0.2)) {
|
|
49882
|
-
throw new Error("hoseBarbPortAssembly: bossDiameter must leave material around the shoulder seat");
|
|
49883
|
-
}
|
|
49884
|
-
if (blockWidth <= bossDiameter + 8 || blockHeight <= bossDiameter + 8) {
|
|
49885
|
-
throw new Error("hoseBarbPortAssembly: receiver block dimensions leave too little material around the port boss");
|
|
49886
|
-
}
|
|
49887
|
-
const portBoreDiameter = barbRootDiameter + runningClearance * 2;
|
|
49888
|
-
const portBore = cylinderAlongX(blockThickness + bossHeight + 0.8, portBoreDiameter / 2, bossHeight / 2, segments);
|
|
49889
|
-
const fuseOverlap = Math.min(0.04, faceClearance * 0.7);
|
|
49890
|
-
const bossCenterX = blockThickness / 2 + bossHeight / 2 - fuseOverlap;
|
|
49891
|
-
const receiver = union(
|
|
49892
|
-
box(blockThickness, blockWidth, blockHeight).translate(0, 0, -blockHeight / 2),
|
|
49893
|
-
cylinderAlongX(bossHeight + fuseOverlap, bossDiameter / 2, bossCenterX, segments)
|
|
49894
|
-
).subtract(portBore).color("#475569");
|
|
49895
|
-
const bossFaceX = blockThickness / 2 + bossHeight;
|
|
49896
|
-
const shoulderCenterX = bossFaceX + faceClearance + shoulderThickness / 2;
|
|
49897
|
-
const barbStartX = shoulderCenterX + shoulderThickness / 2;
|
|
49898
|
-
const fittingStartX = -blockThickness / 2 - runningClearance;
|
|
49899
|
-
const fittingEndX = barbStartX + barbStackLength;
|
|
49900
|
-
const fittingCore = tubeAlongX(
|
|
49901
|
-
fittingEndX - fittingStartX,
|
|
49902
|
-
barbRootDiameter / 2,
|
|
49903
|
-
fluidBoreDiameter / 2,
|
|
49904
|
-
(fittingStartX + fittingEndX) / 2,
|
|
49905
|
-
segments
|
|
49906
|
-
);
|
|
49907
|
-
const shoulder = tubeAlongX(shoulderThickness, shoulderDiameter / 2, fluidBoreDiameter / 2, shoulderCenterX, segments);
|
|
49908
|
-
const barbSolids = [];
|
|
49909
|
-
const ridgeLength = Math.max(0.8, Math.min(barbLength * 0.45, hoseInnerDiameter * 0.28));
|
|
49910
|
-
for (let index2 = 0; index2 < barbCount; index2 += 1) {
|
|
49911
|
-
const startX = barbStartX + index2 * barbLength;
|
|
49912
|
-
const ridgeCenterX = startX + barbLength - ridgeLength / 2;
|
|
49913
|
-
barbSolids.push(tubeAlongX(ridgeLength, barbPeakDiameter / 2, fluidBoreDiameter / 2, ridgeCenterX, segments));
|
|
49914
|
-
}
|
|
49915
|
-
const fitting = union(fittingCore, shoulder, ...barbSolids).color("#94a3b8");
|
|
49916
|
-
const hoseStartX = barbStartX + faceClearance;
|
|
49917
|
-
const hoseCenterX = hoseStartX + hoseLength / 2;
|
|
49918
|
-
const installedHoseBore = cylinderAlongX(hoseLength + 0.8, installedHoseBoreDiameter / 2, hoseCenterX, segments);
|
|
49919
|
-
const hose = tubeAlongX(hoseLength, hoseOuterDiameter / 2, installedHoseBoreDiameter / 2, hoseCenterX, segments).color("#111827");
|
|
49920
|
-
const clampCenterX = barbStartX + Math.min(barbStackLength * 0.55, Math.max(barbLength, clampWidth));
|
|
49921
|
-
const clamp2 = tubeAlongX(
|
|
49922
|
-
clampWidth,
|
|
49923
|
-
hoseOuterDiameter / 2 + clampThickness,
|
|
49924
|
-
hoseOuterDiameter / 2 + Math.min(0.08, runningClearance * 0.45),
|
|
49925
|
-
clampCenterX,
|
|
49926
|
-
segments
|
|
49927
|
-
).color("#cbd5e1");
|
|
49928
|
-
const parts = [
|
|
49929
|
-
{ name: "bored pump or filter body with raised hose-port boss", shape: receiver },
|
|
49930
|
-
{ name: "hollow hose barb fitting with shoulder and retention ridges", shape: fitting },
|
|
49931
|
-
{ name: "installed flexible hose over barb tail", shape: hose },
|
|
49932
|
-
{ name: "clamp band over hose and barb ridges", shape: clamp2 }
|
|
49933
|
-
];
|
|
49934
|
-
return {
|
|
49935
|
-
parts,
|
|
49936
|
-
receiver,
|
|
49937
|
-
fitting,
|
|
49938
|
-
hose,
|
|
49939
|
-
clamp: clamp2,
|
|
49940
|
-
cutters: {
|
|
49941
|
-
portBore,
|
|
49942
|
-
installedHoseBore
|
|
49943
|
-
},
|
|
49944
|
-
dims: {
|
|
49945
|
-
hoseInnerDiameter,
|
|
49946
|
-
hoseOuterDiameter,
|
|
49947
|
-
installedHoseBoreDiameter,
|
|
49948
|
-
blockThickness,
|
|
49949
|
-
blockWidth,
|
|
49950
|
-
blockHeight,
|
|
49951
|
-
bossDiameter,
|
|
49952
|
-
bossHeight,
|
|
49953
|
-
fluidBoreDiameter,
|
|
49954
|
-
barbRootDiameter,
|
|
49955
|
-
barbPeakDiameter,
|
|
49956
|
-
barbCount,
|
|
49957
|
-
barbLength,
|
|
49958
|
-
barbStackLength,
|
|
49959
|
-
shoulderDiameter,
|
|
49960
|
-
shoulderThickness,
|
|
49961
|
-
hoseLength,
|
|
49962
|
-
clampWidth,
|
|
49963
|
-
clampThickness,
|
|
49964
|
-
runningClearance,
|
|
49965
|
-
faceClearance
|
|
49966
|
-
}
|
|
49967
|
-
};
|
|
49968
|
-
}
|
|
49969
|
-
function routedTubeClipAssembly(options) {
|
|
49970
|
-
const tubeDiameter = requirePositive$6(options.tubeDiameter, "tubeDiameter");
|
|
49971
|
-
const tubeLength = requirePositive$6(options.tubeLength ?? 120, "tubeLength");
|
|
49972
|
-
const panelThickness = requirePositive$6(options.panelThickness ?? 3, "panelThickness");
|
|
49973
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.35, "runningClearance");
|
|
49974
|
-
const screwSize = options.screwSize ?? "M3";
|
|
49975
|
-
const segments = options.segments ?? 32;
|
|
49976
|
-
const sizeData = METRIC_HOLE_TABLE[screwSize];
|
|
49977
|
-
if (!sizeData) throw new Error(`routedTubeClipAssembly: unsupported screwSize "${screwSize}"`);
|
|
49978
|
-
const clipCount = options.clipCount ?? 3;
|
|
49979
|
-
if (!Number.isInteger(clipCount) || clipCount < 1 || clipCount > 8) {
|
|
49980
|
-
throw new Error("routedTubeClipAssembly: clipCount must be an integer from 1 to 8");
|
|
49981
|
-
}
|
|
49982
|
-
const screwDiameter = parseFloat(screwSize.replace("M", ""));
|
|
49983
|
-
const screwHeadDiameter = sizeData.head;
|
|
49984
|
-
const tubeBoreDiameter = tubeDiameter + runningClearance * 2;
|
|
49985
|
-
const clipWallThickness = requirePositive$6(
|
|
49986
|
-
options.clipWallThickness ?? Math.max(screwHeadDiameter + 1.2, tubeDiameter * 0.45, 5),
|
|
49987
|
-
"clipWallThickness"
|
|
49988
|
-
);
|
|
49989
|
-
const clipWidth = requirePositive$6(options.clipWidth ?? Math.max(screwHeadDiameter + 3, tubeDiameter * 1.4, 10), "clipWidth");
|
|
49990
|
-
const clipDepth = tubeBoreDiameter + clipWallThickness * 2;
|
|
49991
|
-
const bottomWall = Math.max(1.2, clipWallThickness * 0.35);
|
|
49992
|
-
const topWall = Math.max(2, clipWallThickness * 0.45);
|
|
49993
|
-
const clipHeight = bottomWall + tubeBoreDiameter + topWall;
|
|
49994
|
-
const tubeCenterZ = panelThickness + bottomWall + tubeBoreDiameter / 2;
|
|
49995
|
-
const panelLength = requirePositive$6(options.panelLength ?? tubeLength + 24, "panelLength");
|
|
49996
|
-
const panelWidth = requirePositive$6(options.panelWidth ?? clipDepth + Math.max(14, screwHeadDiameter * 2), "panelWidth");
|
|
49997
|
-
if (tubeLength <= clipWidth + 8) {
|
|
49998
|
-
throw new Error("routedTubeClipAssembly: tubeLength must leave visible tube beyond the clip body");
|
|
49999
|
-
}
|
|
50000
|
-
const defaultSpacing = clipCount === 1 ? 0 : Math.max(clipWidth + 8, (tubeLength - clipWidth * 2) / (clipCount - 1));
|
|
50001
|
-
const clipSpacing = options.clipSpacing === void 0 ? defaultSpacing : requirePositive$6(options.clipSpacing, "clipSpacing");
|
|
50002
|
-
const clipCenters = Array.from({ length: clipCount }, (_2, index2) => (index2 - (clipCount - 1) / 2) * clipSpacing);
|
|
50003
|
-
const maxClipExtent = Math.max(...clipCenters.map((x2) => Math.abs(x2) + clipWidth / 2));
|
|
50004
|
-
if (maxClipExtent > tubeLength / 2 - 2) {
|
|
50005
|
-
throw new Error("routedTubeClipAssembly: clipSpacing places a clip beyond the routed tube length");
|
|
50006
|
-
}
|
|
50007
|
-
if (maxClipExtent > panelLength / 2 - 2) {
|
|
50008
|
-
throw new Error("routedTubeClipAssembly: panelLength is too short for the clip pattern");
|
|
50009
|
-
}
|
|
50010
|
-
const boreRadius = tubeBoreDiameter / 2;
|
|
50011
|
-
const screwY = boreRadius + clipWallThickness / 2;
|
|
50012
|
-
if (screwY + screwHeadDiameter / 2 > clipDepth / 2 - 0.2) {
|
|
50013
|
-
throw new Error("routedTubeClipAssembly: clipWallThickness leaves too little land for screw heads");
|
|
50014
|
-
}
|
|
50015
|
-
if (clipDepth > panelWidth - Math.max(4, screwHeadDiameter * 0.5)) {
|
|
50016
|
-
throw new Error("routedTubeClipAssembly: panelWidth leaves too little material beside the clips");
|
|
50017
|
-
}
|
|
50018
|
-
const screwPositions = clipCenters.flatMap((x2) => [[x2, -screwY], [x2, screwY]]);
|
|
50019
|
-
const screwClearanceDiameter = Math.max(sizeData.loose, screwDiameter + 0.8);
|
|
50020
|
-
const panelThreadEnvelopeDiameter = screwClearanceDiameter;
|
|
50021
|
-
const clipTopZ = panelThickness + clipHeight;
|
|
50022
|
-
const clipTubeBores = union(
|
|
50023
|
-
...clipCenters.map((x2) => cylinderAlongX(clipWidth + 0.8, boreRadius, x2, segments).translate(0, 0, tubeCenterZ))
|
|
50024
|
-
);
|
|
50025
|
-
const clipScrewClearances = union(
|
|
50026
|
-
...screwPositions.map(
|
|
50027
|
-
([x2, y2]) => cylinder(clipHeight + 0.8, screwClearanceDiameter / 2, void 0, segments).translate(x2, y2, panelThickness - 0.4)
|
|
50028
|
-
)
|
|
50029
|
-
);
|
|
50030
|
-
const panelThreadEnvelopes = union(
|
|
50031
|
-
...screwPositions.map(
|
|
50032
|
-
([x2, y2]) => cylinder(panelThickness + 0.8, panelThreadEnvelopeDiameter / 2, void 0, segments).translate(x2, y2, -0.4)
|
|
50033
|
-
)
|
|
50034
|
-
);
|
|
50035
|
-
const panel = box(panelLength, panelWidth, panelThickness).subtract(panelThreadEnvelopes).color("#475569");
|
|
50036
|
-
const tube2 = cylinderAlongX(tubeLength, tubeDiameter / 2, 0, segments).translate(0, 0, tubeCenterZ).color("#0f172a");
|
|
50037
|
-
const clips = clipCenters.map((x2) => {
|
|
50038
|
-
const body = box(clipWidth, clipDepth, clipHeight).translate(x2, 0, panelThickness);
|
|
50039
|
-
const tubeBore = cylinderAlongX(clipWidth + 0.8, boreRadius, x2, segments).translate(0, 0, tubeCenterZ);
|
|
50040
|
-
const screwHoles = union(
|
|
50041
|
-
cylinder(clipHeight + 0.8, screwClearanceDiameter / 2, void 0, segments).translate(x2, -screwY, panelThickness - 0.4),
|
|
50042
|
-
cylinder(clipHeight + 0.8, screwClearanceDiameter / 2, void 0, segments).translate(x2, screwY, panelThickness - 0.4)
|
|
50043
|
-
);
|
|
50044
|
-
return body.subtract(tubeBore).subtract(screwHoles).color("#94a3b8");
|
|
50045
|
-
});
|
|
50046
|
-
const screwLength = clipHeight + panelThickness * 0.65;
|
|
50047
|
-
const screwHeadHeight = Math.max(1.2, screwDiameter * 0.55);
|
|
50048
|
-
const screwBlank = union(
|
|
50049
|
-
cylinder(screwLength, screwDiameter / 2, void 0, segments).translate(0, 0, clipTopZ - screwLength),
|
|
50050
|
-
cylinder(screwHeadHeight, screwHeadDiameter / 2, void 0, segments).translate(0, 0, clipTopZ)
|
|
50051
|
-
).color("#cbd5e1");
|
|
50052
|
-
const screws = screwPositions.map(([x2, y2]) => screwBlank.translate(x2, y2, 0));
|
|
50053
|
-
const parts = [
|
|
50054
|
-
{ name: "panel with tube-clip screw receiving holes", shape: panel },
|
|
50055
|
-
{ name: "routed flexible tube through retained clip bores", shape: tube2 },
|
|
50056
|
-
...clips.map((shape, index2) => ({ name: `saddle tube clip ${index2 + 1} with through-bore`, shape })),
|
|
50057
|
-
...screws.map((shape, index2) => ({ name: `installed ${screwSize} tube clip screw ${index2 + 1}`, shape }))
|
|
50058
|
-
];
|
|
50059
|
-
return {
|
|
50060
|
-
parts,
|
|
50061
|
-
panel,
|
|
50062
|
-
tube: tube2,
|
|
50063
|
-
clips,
|
|
50064
|
-
screws,
|
|
50065
|
-
clipCenters,
|
|
50066
|
-
screwPositions,
|
|
50067
|
-
cutters: {
|
|
50068
|
-
clipTubeBores,
|
|
50069
|
-
clipScrewClearances,
|
|
50070
|
-
panelThreadEnvelopes
|
|
50071
|
-
},
|
|
50072
|
-
dims: {
|
|
50073
|
-
tubeDiameter,
|
|
50074
|
-
tubeLength,
|
|
50075
|
-
tubeBoreDiameter,
|
|
50076
|
-
panelLength,
|
|
50077
|
-
panelWidth,
|
|
50078
|
-
panelThickness,
|
|
50079
|
-
clipCount,
|
|
50080
|
-
clipWidth,
|
|
50081
|
-
clipDepth,
|
|
50082
|
-
clipHeight,
|
|
50083
|
-
clipWallThickness,
|
|
50084
|
-
tubeCenterZ,
|
|
50085
|
-
screwSize,
|
|
50086
|
-
screwDiameter,
|
|
50087
|
-
screwHeadDiameter,
|
|
50088
|
-
screwLength,
|
|
50089
|
-
screwClearanceDiameter,
|
|
50090
|
-
panelThreadEnvelopeDiameter,
|
|
50091
|
-
runningClearance
|
|
50092
|
-
}
|
|
50093
|
-
};
|
|
50094
|
-
}
|
|
50095
|
-
function pcbTerminalBlockAssembly(options = {}) {
|
|
50096
|
-
const terminalCount = options.terminalCount ?? 4;
|
|
50097
|
-
if (!Number.isInteger(terminalCount) || terminalCount < 1 || terminalCount > 24) {
|
|
50098
|
-
throw new Error("pcbTerminalBlockAssembly: terminalCount must be an integer from 1 to 24");
|
|
50099
|
-
}
|
|
50100
|
-
const terminalPitch = requirePositive$6(options.terminalPitch ?? 5.08, "terminalPitch");
|
|
50101
|
-
const terminalBlockWidth = terminalPitch * terminalCount + 3;
|
|
50102
|
-
const boardWidth = requirePositive$6(options.boardWidth ?? Math.max(50, terminalBlockWidth + 28), "boardWidth");
|
|
50103
|
-
const boardDepth = requirePositive$6(options.boardDepth ?? 38, "boardDepth");
|
|
50104
|
-
const boardThickness = requirePositive$6(options.boardThickness ?? 1.6, "boardThickness");
|
|
50105
|
-
const backplateThickness = requirePositive$6(options.backplateThickness ?? 3, "backplateThickness");
|
|
50106
|
-
const backplateMargin = requirePositive$6(options.backplateMargin ?? 5, "backplateMargin");
|
|
50107
|
-
const standoffHeight = requirePositive$6(options.standoffHeight ?? 6, "standoffHeight");
|
|
50108
|
-
const screwSize = options.screwSize ?? "M3";
|
|
50109
|
-
const segments = options.segments ?? 28;
|
|
50110
|
-
const sizeData = METRIC_HOLE_TABLE[screwSize];
|
|
50111
|
-
if (!sizeData) throw new Error(`pcbTerminalBlockAssembly: unsupported screwSize "${screwSize}"`);
|
|
50112
|
-
const screwDiameter = parseFloat(screwSize.replace("M", ""));
|
|
50113
|
-
const screwHeadDiameter = sizeData.head;
|
|
50114
|
-
const screwHeadHeight = Math.max(1.2, screwDiameter * 0.55);
|
|
50115
|
-
const standoffDiameter = requirePositive$6(
|
|
50116
|
-
options.standoffDiameter ?? Math.max(screwHeadDiameter * 1.45, sizeData.normal + 3),
|
|
50117
|
-
"standoffDiameter"
|
|
50118
|
-
);
|
|
50119
|
-
const [mountInsetX, mountInsetY] = resolveBoltInset(
|
|
50120
|
-
options.mountingInset,
|
|
50121
|
-
Math.max(standoffDiameter / 2 + 1.2, screwHeadDiameter * 0.75)
|
|
50122
|
-
);
|
|
50123
|
-
if (mountInsetX * 2 >= boardWidth || mountInsetY * 2 >= boardDepth) {
|
|
50124
|
-
throw new Error("pcbTerminalBlockAssembly: mountingInset leaves no room for the PCB mounting pattern");
|
|
50125
|
-
}
|
|
50126
|
-
const terminalBlockDepth = requirePositive$6(options.terminalBlockDepth ?? 10, "terminalBlockDepth");
|
|
50127
|
-
const terminalBlockHeight = requirePositive$6(options.terminalBlockHeight ?? 9, "terminalBlockHeight");
|
|
50128
|
-
const terminalEdgeInset = requirePositive$6(options.terminalEdgeInset ?? 5, "terminalEdgeInset");
|
|
50129
|
-
const pinDiameter = requirePositive$6(options.pinDiameter ?? 0.9, "pinDiameter");
|
|
50130
|
-
const pinClearance = requirePositive$6(options.pinClearance ?? 0.25, "pinClearance");
|
|
50131
|
-
const pinTailLength = requireNonNegative(options.pinTailLength ?? 0, "pinTailLength");
|
|
50132
|
-
const wirePortDiameter = requirePositive$6(options.wirePortDiameter ?? 2.6, "wirePortDiameter");
|
|
50133
|
-
const pinHoleDiameter = pinDiameter + pinClearance;
|
|
50134
|
-
const terminalCenterY = -boardDepth / 2 + terminalEdgeInset + terminalBlockDepth / 2;
|
|
50135
|
-
const pinY = terminalCenterY + terminalBlockDepth * 0.24;
|
|
50136
|
-
const firstPinX = -((terminalCount - 1) * terminalPitch) / 2;
|
|
50137
|
-
const pinPositions = Array.from({ length: terminalCount }, (_2, index2) => [firstPinX + index2 * terminalPitch, pinY]);
|
|
50138
|
-
const mountingPositions = [
|
|
50139
|
-
[-boardWidth / 2 + mountInsetX, -boardDepth / 2 + mountInsetY],
|
|
50140
|
-
[boardWidth / 2 - mountInsetX, -boardDepth / 2 + mountInsetY],
|
|
50141
|
-
[-boardWidth / 2 + mountInsetX, boardDepth / 2 - mountInsetY],
|
|
50142
|
-
[boardWidth / 2 - mountInsetX, boardDepth / 2 - mountInsetY]
|
|
50143
|
-
];
|
|
50144
|
-
if (terminalBlockWidth >= boardWidth - mountInsetX * 2) {
|
|
50145
|
-
throw new Error("pcbTerminalBlockAssembly: terminal block is too wide for the PCB mounting pattern");
|
|
50146
|
-
}
|
|
50147
|
-
if (terminalEdgeInset + terminalBlockDepth >= boardDepth - mountInsetY * 2) {
|
|
50148
|
-
throw new Error("pcbTerminalBlockAssembly: terminal block depth collides with the rear mounting datum");
|
|
50149
|
-
}
|
|
50150
|
-
if (pinHoleDiameter >= terminalPitch * 0.55) {
|
|
50151
|
-
throw new Error("pcbTerminalBlockAssembly: pinDiameter and pinClearance leave too little PCB web between terminal holes");
|
|
50152
|
-
}
|
|
50153
|
-
if (wirePortDiameter >= Math.min(terminalPitch * 0.72, terminalBlockHeight * 0.65)) {
|
|
50154
|
-
throw new Error("pcbTerminalBlockAssembly: wirePortDiameter is too large for the terminal pitch or body height");
|
|
50155
|
-
}
|
|
50156
|
-
for (const [index2, [x2, y2]] of [...mountingPositions, ...pinPositions].entries()) {
|
|
50157
|
-
if (!Number.isFinite(x2) || !Number.isFinite(y2)) {
|
|
50158
|
-
throw new Error(`pcbTerminalBlockAssembly: generated datum position ${index2} is not finite`);
|
|
50159
|
-
}
|
|
50160
|
-
}
|
|
50161
|
-
const backplateWidth = boardWidth + backplateMargin * 2;
|
|
50162
|
-
const backplateDepth = boardDepth + backplateMargin * 2;
|
|
50163
|
-
const boardBottomZ = backplateThickness + standoffHeight;
|
|
50164
|
-
const boardTopZ = boardBottomZ + boardThickness;
|
|
50165
|
-
const standoffOverlap = Math.min(0.08, standoffHeight * 0.03);
|
|
50166
|
-
const standoffThreadEnvelopeDiameter = Math.max(sizeData.loose, screwDiameter + 1);
|
|
50167
|
-
const standoffThreadEnvelope = cylinder(standoffHeight + 0.8, standoffThreadEnvelopeDiameter / 2, void 0, segments).translate(
|
|
50168
|
-
0,
|
|
50169
|
-
0,
|
|
50170
|
-
backplateThickness - 0.4
|
|
50171
|
-
);
|
|
50172
|
-
const standoffThreadEnvelopes = union(...mountingPositions.map(([x2, y2]) => standoffThreadEnvelope.translate(x2, y2, 0)));
|
|
50173
|
-
const standoff = cylinder(standoffHeight + standoffOverlap, standoffDiameter / 2, void 0, segments).translate(0, 0, backplateThickness - standoffOverlap).subtract(standoffThreadEnvelope);
|
|
50174
|
-
const standoffs = union(...mountingPositions.map(([x2, y2]) => standoff.translate(x2, y2, 0)));
|
|
50175
|
-
const backplate = union(box(backplateWidth, backplateDepth, backplateThickness), standoffs).color("#475569");
|
|
50176
|
-
const boardMountingHoleDiameter = sizeData.normal;
|
|
50177
|
-
const boardMountHole = cylinder(boardThickness + 0.8, boardMountingHoleDiameter / 2, void 0, segments).translate(
|
|
50178
|
-
0,
|
|
50179
|
-
0,
|
|
50180
|
-
boardBottomZ - 0.4
|
|
50181
|
-
);
|
|
50182
|
-
const pcbMountingHoles = union(...mountingPositions.map(([x2, y2]) => boardMountHole.translate(x2, y2, 0)));
|
|
50183
|
-
const pinHole = cylinder(boardThickness + 0.8, pinHoleDiameter / 2, void 0, segments).translate(0, 0, boardBottomZ - 0.4);
|
|
50184
|
-
const pcbPinHoles = union(...pinPositions.map(([x2, y2]) => pinHole.translate(x2, y2, 0)));
|
|
50185
|
-
const pcb = box(boardWidth, boardDepth, boardThickness).translate(0, 0, boardBottomZ).subtract(pcbMountingHoles).subtract(pcbPinHoles).color("#166534");
|
|
50186
|
-
const terminalBodyBlank = box(terminalBlockWidth, terminalBlockDepth, terminalBlockHeight).translate(0, terminalCenterY, boardTopZ);
|
|
50187
|
-
const wirePort = cylinderAlongY(terminalBlockDepth + 0.8, wirePortDiameter / 2, terminalCenterY, segments).translate(
|
|
50188
|
-
0,
|
|
50189
|
-
0,
|
|
50190
|
-
boardTopZ + terminalBlockHeight * 0.42
|
|
50191
|
-
);
|
|
50192
|
-
const wirePorts = union(...pinPositions.map(([x2]) => wirePort.translate(x2, 0, 0)));
|
|
50193
|
-
const clampScrewPockets = union(
|
|
50194
|
-
...pinPositions.map(
|
|
50195
|
-
([x2]) => cylinder(
|
|
50196
|
-
Math.max(0.6, terminalBlockHeight * 0.22),
|
|
50197
|
-
Math.min(terminalPitch * 0.22, wirePortDiameter * 0.42),
|
|
50198
|
-
void 0,
|
|
50199
|
-
segments
|
|
50200
|
-
).translate(x2, terminalCenterY + terminalBlockDepth * 0.12, boardTopZ + terminalBlockHeight * 0.76)
|
|
50201
|
-
)
|
|
50202
|
-
);
|
|
50203
|
-
const pinLength = boardThickness + pinTailLength + Math.min(0.6, terminalBlockHeight * 0.08);
|
|
50204
|
-
const pinStartZ = boardBottomZ - pinTailLength;
|
|
50205
|
-
const pins = union(...pinPositions.map(([x2, y2]) => cylinder(pinLength, pinDiameter / 2, void 0, segments).translate(x2, y2, pinStartZ)));
|
|
50206
|
-
const terminalBlock = union(terminalBodyBlank.subtract(wirePorts).subtract(clampScrewPockets), pins).color("#16a34a");
|
|
50207
|
-
const screwShaftLength = boardThickness + standoffHeight * 0.85;
|
|
50208
|
-
const mountingHardware = fastenerSet(screwSize, screwShaftLength, {
|
|
50209
|
-
washerUnderHead: false,
|
|
50210
|
-
washerUnderNut: false,
|
|
50211
|
-
fit: "normal",
|
|
50212
|
-
segments
|
|
50213
|
-
});
|
|
50214
|
-
const screws = mountingPositions.map(([x2, y2]) => mountingHardware.bolt.translate(x2, y2, boardTopZ).color("#cbd5e1"));
|
|
50215
|
-
const parts = [
|
|
50216
|
-
{ name: "electronics backplate with fused PCB standoffs", shape: backplate },
|
|
50217
|
-
{ name: "PCB with mounting holes and terminal pin clearances", shape: pcb },
|
|
50218
|
-
{ name: "seated purchased terminal block with through-board pins", shape: terminalBlock },
|
|
50219
|
-
...screws.map((shape, index2) => ({ name: `installed ${screwSize} PCB mounting screw ${index2 + 1}`, shape }))
|
|
50220
|
-
];
|
|
50221
|
-
return {
|
|
50222
|
-
parts,
|
|
50223
|
-
backplate,
|
|
50224
|
-
pcb,
|
|
50225
|
-
terminalBlock,
|
|
50226
|
-
screws,
|
|
50227
|
-
mountingPositions,
|
|
50228
|
-
pinPositions,
|
|
50229
|
-
cutters: {
|
|
50230
|
-
pcbMountingHoles,
|
|
50231
|
-
pcbPinHoles,
|
|
50232
|
-
standoffThreadEnvelopes
|
|
50233
|
-
},
|
|
50234
|
-
dims: {
|
|
50235
|
-
terminalCount,
|
|
50236
|
-
terminalPitch,
|
|
50237
|
-
boardWidth,
|
|
50238
|
-
boardDepth,
|
|
50239
|
-
boardThickness,
|
|
50240
|
-
backplateWidth,
|
|
50241
|
-
backplateDepth,
|
|
50242
|
-
backplateThickness,
|
|
50243
|
-
standoffHeight,
|
|
50244
|
-
standoffDiameter,
|
|
50245
|
-
screwSize,
|
|
50246
|
-
screwDiameter,
|
|
50247
|
-
screwHeadDiameter,
|
|
50248
|
-
screwHeadHeight,
|
|
50249
|
-
screwShaftLength,
|
|
50250
|
-
boardMountingHoleDiameter,
|
|
50251
|
-
standoffThreadEnvelopeDiameter,
|
|
50252
|
-
terminalBlockWidth,
|
|
50253
|
-
terminalBlockDepth,
|
|
50254
|
-
terminalBlockHeight,
|
|
50255
|
-
terminalEdgeInset,
|
|
50256
|
-
pinDiameter,
|
|
50257
|
-
pinClearance,
|
|
50258
|
-
pinHoleDiameter,
|
|
50259
|
-
pinTailLength,
|
|
50260
|
-
wirePortDiameter
|
|
50261
|
-
}
|
|
50262
|
-
};
|
|
50263
|
-
}
|
|
50264
|
-
function thumbScrewClampAssembly(options = {}) {
|
|
50265
|
-
const screwSize = options.screwSize ?? "M6";
|
|
50266
|
-
const segments = options.segments ?? 36;
|
|
50267
|
-
const sizeData = METRIC_HOLE_TABLE[screwSize];
|
|
50268
|
-
if (!sizeData) throw new Error(`thumbScrewClampAssembly: unsupported screwSize "${screwSize}"`);
|
|
50269
|
-
const screwDiameter = parseFloat(screwSize.replace("M", ""));
|
|
50270
|
-
const runningClearance = requirePositive$6(options.runningClearance ?? 0.35, "runningClearance");
|
|
50271
|
-
const faceClearance = requireNonNegative(options.faceClearance ?? 0, "faceClearance");
|
|
50272
|
-
const threadEnvelopeDiameter = Math.max(sizeData.normal, screwDiameter + runningClearance * 2);
|
|
50273
|
-
const pressurePadDiameter = requirePositive$6(options.pressurePadDiameter ?? Math.max(screwDiameter * 3.2, 18), "pressurePadDiameter");
|
|
50274
|
-
const pressurePadThickness = requirePositive$6(options.pressurePadThickness ?? Math.max(screwDiameter * 0.72, 4), "pressurePadThickness");
|
|
50275
|
-
const knobDiameter = requirePositive$6(options.knobDiameter ?? Math.max(screwDiameter * 4.2, 24), "knobDiameter");
|
|
50276
|
-
const knobThickness = requirePositive$6(options.knobThickness ?? Math.max(screwDiameter * 0.9, 7), "knobThickness");
|
|
50277
|
-
const workpieceThickness = requirePositive$6(options.workpieceThickness ?? 18, "workpieceThickness");
|
|
50278
|
-
const workpieceDepth = requirePositive$6(options.workpieceDepth ?? Math.max(46, pressurePadDiameter * 1.5), "workpieceDepth");
|
|
50279
|
-
const workpieceHeight = requirePositive$6(options.workpieceHeight ?? Math.max(pressurePadDiameter * 1.35, 24), "workpieceHeight");
|
|
50280
|
-
const frameDepth = requirePositive$6(options.frameDepth ?? Math.max(workpieceDepth + 12, pressurePadDiameter + 16), "frameDepth");
|
|
50281
|
-
const baseThickness = requirePositive$6(options.baseThickness ?? Math.max(screwDiameter, 6), "baseThickness");
|
|
50282
|
-
const jawThickness = requirePositive$6(options.jawThickness ?? Math.max(screwDiameter * 1.35, 9), "jawThickness");
|
|
50283
|
-
const supportThickness = requirePositive$6(options.supportThickness ?? Math.max(screwDiameter * 1.8, 12), "supportThickness");
|
|
50284
|
-
const bossLength = requirePositive$6(options.bossLength ?? Math.max(screwDiameter * 1.1, 8), "bossLength");
|
|
50285
|
-
const bossDiameter = requirePositive$6(options.bossDiameter ?? Math.max(threadEnvelopeDiameter + 5, screwDiameter * 2.5), "bossDiameter");
|
|
50286
|
-
const exposedScrewLength = requirePositive$6(
|
|
50287
|
-
options.exposedScrewLength ?? Math.max(pressurePadDiameter * 0.45, screwDiameter * 2.2),
|
|
50288
|
-
"exposedScrewLength"
|
|
50289
|
-
);
|
|
50290
|
-
const screwCenterZ = baseThickness + Math.max(workpieceHeight * 0.52, pressurePadDiameter * 0.68);
|
|
50291
|
-
const frameHeight = requirePositive$6(
|
|
50292
|
-
options.frameHeight ?? screwCenterZ - baseThickness + pressurePadDiameter / 2 + Math.max(baseThickness, 7),
|
|
50293
|
-
"frameHeight"
|
|
50294
|
-
);
|
|
50295
|
-
if (workpieceDepth > frameDepth - 6) {
|
|
50296
|
-
throw new Error("thumbScrewClampAssembly: frameDepth must leave side material around the clamped workpiece");
|
|
50297
|
-
}
|
|
50298
|
-
if (pressurePadDiameter > frameDepth - 4) {
|
|
50299
|
-
throw new Error("thumbScrewClampAssembly: pressurePadDiameter is too large for the frame depth");
|
|
50300
|
-
}
|
|
50301
|
-
if (bossDiameter > frameDepth - 4) {
|
|
50302
|
-
throw new Error("thumbScrewClampAssembly: bossDiameter is too large for the frame depth");
|
|
50303
|
-
}
|
|
50304
|
-
if (screwCenterZ - pressurePadDiameter / 2 <= baseThickness + 0.5) {
|
|
50305
|
-
throw new Error("thumbScrewClampAssembly: pressure pad collides with the base bridge");
|
|
50306
|
-
}
|
|
50307
|
-
if (baseThickness + frameHeight - screwCenterZ <= pressurePadDiameter / 2 + 2) {
|
|
50308
|
-
throw new Error("thumbScrewClampAssembly: frameHeight leaves too little material above the screw axis");
|
|
50309
|
-
}
|
|
50310
|
-
if (threadEnvelopeDiameter + 4 > Math.min(frameDepth, frameHeight)) {
|
|
50311
|
-
throw new Error("thumbScrewClampAssembly: threaded boss bore leaves too little surrounding frame material");
|
|
50312
|
-
}
|
|
50313
|
-
const workpieceLeftFaceX = -workpieceThickness / 2;
|
|
50314
|
-
const workpieceRightFaceX = workpieceThickness / 2;
|
|
50315
|
-
const anvilOverlap = Math.min(0.35, pressurePadThickness * 0.18);
|
|
50316
|
-
const anvilPadCenterX = workpieceLeftFaceX - faceClearance - pressurePadThickness / 2;
|
|
50317
|
-
const pressurePadCenterX = workpieceRightFaceX + faceClearance + pressurePadThickness / 2;
|
|
50318
|
-
const fixedJawRightFaceX = anvilPadCenterX - pressurePadThickness / 2 + anvilOverlap;
|
|
50319
|
-
const fixedJawCenterX = fixedJawRightFaceX - jawThickness / 2;
|
|
50320
|
-
const pressurePadRightFaceX = pressurePadCenterX + pressurePadThickness / 2;
|
|
50321
|
-
const supportInnerFaceX = pressurePadRightFaceX + exposedScrewLength;
|
|
50322
|
-
const supportCenterX = supportInnerFaceX + supportThickness / 2;
|
|
50323
|
-
const supportOuterFaceX = supportInnerFaceX + supportThickness;
|
|
50324
|
-
const frameLeftFaceX = fixedJawCenterX - jawThickness / 2;
|
|
50325
|
-
const frameRightFaceX = supportOuterFaceX;
|
|
50326
|
-
const baseLength = frameRightFaceX - frameLeftFaceX;
|
|
50327
|
-
if (baseLength <= 0 || !Number.isFinite(baseLength)) {
|
|
50328
|
-
throw new Error("thumbScrewClampAssembly: generated clamp frame length is invalid");
|
|
50329
|
-
}
|
|
50330
|
-
const bossCenterX = supportInnerFaceX + (supportThickness + bossLength) / 2;
|
|
50331
|
-
const threadedBossBore = cylinderAlongX(supportThickness + bossLength + 1, threadEnvelopeDiameter / 2, bossCenterX, segments).translate(
|
|
50332
|
-
0,
|
|
50333
|
-
0,
|
|
50334
|
-
screwCenterZ
|
|
50335
|
-
);
|
|
50336
|
-
const frameOverlap = Math.min(0.12, baseThickness * 0.04);
|
|
50337
|
-
const base = box(baseLength, frameDepth, baseThickness).translate((frameLeftFaceX + frameRightFaceX) / 2, 0, 0);
|
|
50338
|
-
const fixedJaw = box(jawThickness, frameDepth, frameHeight + frameOverlap).translate(fixedJawCenterX, 0, baseThickness - frameOverlap);
|
|
50339
|
-
const support = box(supportThickness, frameDepth, frameHeight + frameOverlap).translate(supportCenterX, 0, baseThickness - frameOverlap);
|
|
50340
|
-
const boss2 = cylinderAlongX(supportThickness + bossLength, bossDiameter / 2, bossCenterX, segments).translate(0, 0, screwCenterZ);
|
|
50341
|
-
const anvilPad = cylinderAlongX(pressurePadThickness, pressurePadDiameter / 2, anvilPadCenterX, segments).translate(0, 0, screwCenterZ);
|
|
50342
|
-
const frame = union(base, fixedJaw, support, boss2, anvilPad).subtract(threadedBossBore).color("#475569");
|
|
50343
|
-
const workpieceBottomZ = screwCenterZ - workpieceHeight / 2;
|
|
50344
|
-
const workpiece = box(workpieceThickness, workpieceDepth, workpieceHeight).translate(0, 0, workpieceBottomZ).color("#a16207");
|
|
50345
|
-
const pressurePad = cylinderAlongX(pressurePadThickness, pressurePadDiameter / 2, pressurePadCenterX, segments).translate(
|
|
50346
|
-
0,
|
|
50347
|
-
0,
|
|
50348
|
-
screwCenterZ
|
|
50349
|
-
);
|
|
50350
|
-
const knobCenterX = supportOuterFaceX + bossLength + runningClearance + knobThickness / 2;
|
|
50351
|
-
const knob = cylinderAlongX(knobThickness, knobDiameter / 2, knobCenterX, segments).translate(0, 0, screwCenterZ);
|
|
50352
|
-
const shaftLeftX = pressurePadRightFaceX - Math.min(pressurePadThickness * 0.45, screwDiameter * 0.45);
|
|
50353
|
-
const shaftRightX = knobCenterX + knobThickness / 2;
|
|
50354
|
-
const shaftLength = shaftRightX - shaftLeftX;
|
|
50355
|
-
if (shaftLength <= supportThickness + bossLength) {
|
|
50356
|
-
throw new Error("thumbScrewClampAssembly: generated screw length is too short for the threaded support");
|
|
50357
|
-
}
|
|
50358
|
-
const shaft = cylinderAlongX(shaftLength, screwDiameter / 2, (shaftLeftX + shaftRightX) / 2, segments).translate(0, 0, screwCenterZ);
|
|
50359
|
-
const clampScrew = union(shaft, pressurePad, knob).color("#cbd5e1");
|
|
50360
|
-
const workpieceEnvelope = box(workpieceThickness, workpieceDepth, workpieceHeight).translate(0, 0, workpieceBottomZ);
|
|
50361
|
-
return {
|
|
50362
|
-
parts: [
|
|
50363
|
-
{ name: "thumb-screw clamp frame with fixed anvil and threaded boss", shape: frame },
|
|
50364
|
-
{ name: "representative clamped workpiece between pads", shape: workpiece },
|
|
50365
|
-
{ name: "installed thumb screw with captive pressure pad and hand knob", shape: clampScrew }
|
|
50366
|
-
],
|
|
50367
|
-
frame,
|
|
50368
|
-
workpiece,
|
|
50369
|
-
clampScrew,
|
|
50370
|
-
cutters: {
|
|
50371
|
-
threadedBossBore,
|
|
50372
|
-
workpieceEnvelope
|
|
50373
|
-
},
|
|
50374
|
-
dims: {
|
|
50375
|
-
screwSize,
|
|
50376
|
-
screwDiameter,
|
|
50377
|
-
threadEnvelopeDiameter,
|
|
50378
|
-
workpieceThickness,
|
|
50379
|
-
workpieceDepth,
|
|
50380
|
-
workpieceHeight,
|
|
50381
|
-
frameDepth,
|
|
50382
|
-
frameHeight,
|
|
50383
|
-
baseThickness,
|
|
50384
|
-
jawThickness,
|
|
50385
|
-
supportThickness,
|
|
50386
|
-
bossLength,
|
|
50387
|
-
bossDiameter,
|
|
50388
|
-
exposedScrewLength,
|
|
50389
|
-
pressurePadDiameter,
|
|
50390
|
-
pressurePadThickness,
|
|
50391
|
-
knobDiameter,
|
|
50392
|
-
knobThickness,
|
|
50393
|
-
screwCenterZ,
|
|
50394
|
-
fixedAnvilFaceX: workpieceLeftFaceX - faceClearance,
|
|
50395
|
-
pressurePadFaceX: workpieceRightFaceX + faceClearance,
|
|
50396
|
-
supportInnerFaceX,
|
|
50397
|
-
runningClearance,
|
|
50398
|
-
faceClearance
|
|
50399
|
-
}
|
|
50400
|
-
};
|
|
50401
|
-
}
|
|
50402
49624
|
function fastenerSet(size, boltLength, options) {
|
|
50403
49625
|
const sizeData = METRIC_HOLE_TABLE[size];
|
|
50404
49626
|
if (!sizeData) throw new Error(`fastenerSet: unsupported size "${size}"`);
|
|
@@ -50459,22 +49681,6 @@ const partLibrary = {
|
|
|
50459
49681
|
nut,
|
|
50460
49682
|
washer,
|
|
50461
49683
|
fastenerSet,
|
|
50462
|
-
boltedServiceCover,
|
|
50463
|
-
datumEnclosureAssembly,
|
|
50464
|
-
snapLatchCoverAssembly,
|
|
50465
|
-
pinnedLeverAssembly,
|
|
50466
|
-
retainedShaftAssembly,
|
|
50467
|
-
capturedLinearSlide,
|
|
50468
|
-
capturedCartridgeGuideAssembly,
|
|
50469
|
-
livingHingeCoverAssembly,
|
|
50470
|
-
knuckledHingeAssembly,
|
|
50471
|
-
clevisPinJointAssembly,
|
|
50472
|
-
seatedBearingAssembly,
|
|
50473
|
-
cableGlandAnchorAssembly,
|
|
50474
|
-
hoseBarbPortAssembly,
|
|
50475
|
-
routedTubeClipAssembly,
|
|
50476
|
-
pcbTerminalBlockAssembly,
|
|
50477
|
-
thumbScrewClampAssembly,
|
|
50478
49684
|
pipeRoute,
|
|
50479
49685
|
elbow,
|
|
50480
49686
|
beltDrive,
|
|
@@ -306313,7 +305519,8 @@ function describeKinematicConvergenceError(kinematics) {
|
|
|
306313
305519
|
const edgeResidual = edge ? Math.abs(edge.residual) : 0;
|
|
306314
305520
|
const angleResidual = angle ? Math.abs(angle.residual) : 0;
|
|
306315
305521
|
const worst = edgeResidual >= angleResidual && edge ? `edge "${edge.name}"` : angle ? `angle "${angle.name}"` : "constraint";
|
|
306316
|
-
|
|
305522
|
+
const diagnostic = kinematics.diagnostics.length > 0 ? ` Diagnostic: ${kinematics.diagnostics[0]}` : "";
|
|
305523
|
+
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.`;
|
|
306317
305524
|
}
|
|
306318
305525
|
function mapScriptResultToScene(args) {
|
|
306319
305526
|
var _a3;
|