forgecad 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +97 -0
- package/README.md +354 -0
- package/dist/assets/evalWorker-BYHXxh15.js +461 -0
- package/dist/assets/index--CYbOPKS.js +5797 -0
- package/dist/assets/manifold-65fIQlgQ.js +20 -0
- package/dist/assets/manifold-B85M7kop.js +20 -0
- package/dist/assets/manifold-B8h_vZ5O.js +16 -0
- package/dist/assets/manifold-D9yvTBHx.wasm +0 -0
- package/dist/assets/manifold-d1UpyLJ8.js +20 -0
- package/dist/assets/reportWorker-B1Zdrz9l.js +494 -0
- package/dist/index.html +16 -0
- package/dist-cli/forgecad.js +44464 -0
- package/dist-skill/SKILL.md +4635 -0
- package/examples/3d-printer.forge.js +328 -0
- package/examples/5-figen-robot-hand.forge.js +283 -0
- package/examples/ac-unit-glm47.forge.js +108 -0
- package/examples/ac-unit-glm5.forge.js +174 -0
- package/examples/ac-unit-kimi25.forge.js +236 -0
- package/examples/ac-unit-minimax.forge.js +123 -0
- package/examples/ac-unit.forge.js +126 -0
- package/examples/adjustable-table.forge.js +191 -0
- package/examples/api/assembly-gear-coupling.forge.js +32 -0
- package/examples/api/assembly-mechanism.forge.js +111 -0
- package/examples/api/attachTo-basics.forge.js +45 -0
- package/examples/api/benchy-style-hull.forge.js +89 -0
- package/examples/api/bill-of-materials.forge.js +46 -0
- package/examples/api/boolean-operations.forge.js +48 -0
- package/examples/api/bounding-box-visualizer.forge.js +58 -0
- package/examples/api/brep-exportable.forge.js +19 -0
- package/examples/api/center-true-vs-false.forge.js +40 -0
- package/examples/api/clone-duplicate.forge.js +41 -0
- package/examples/api/colors-union-vs-array.forge.js +27 -0
- package/examples/api/coordinate-system.forge.js +54 -0
- package/examples/api/curves-surfacing-basics.forge.js +91 -0
- package/examples/api/dimensioned-bracket.forge.js +19 -0
- package/examples/api/elbow-test.forge.js +23 -0
- package/examples/api/exploded-view.forge.js +60 -0
- package/examples/api/extrude-options.forge.js +44 -0
- package/examples/api/face-gears.forge.js +44 -0
- package/examples/api/face-transformation-history.forge.js +45 -0
- package/examples/api/feature-created-faces.forge.js +47 -0
- package/examples/api/folded-service-panel-cover.forge.js +3 -0
- package/examples/api/folded-service-panel-cover.js +117 -0
- package/examples/api/gears-bevel-face-joints.forge.js +157 -0
- package/examples/api/gears-tier1.forge.js +57 -0
- package/examples/api/geometry-info.forge.js +49 -0
- package/examples/api/group-test.forge.js +34 -0
- package/examples/api/group-vs-union.forge.js +25 -0
- package/examples/api/import-args-unit.forge.js +5 -0
- package/examples/api/import-args.forge.js +16 -0
- package/examples/api/import-dimensions-follow.forge.js +18 -0
- package/examples/api/import-placement-references.forge.js +18 -0
- package/examples/api/import-placement-widget-source.forge.js +30 -0
- package/examples/api/import-relative-paths.forge.js +18 -0
- package/examples/api/import-svg-sketch-shape.svg +15 -0
- package/examples/api/import-svg-sketch.forge.js +28 -0
- package/examples/api/js-module-imports.forge.js +9 -0
- package/examples/api/js-module-pillars.js +25 -0
- package/examples/api/js-module-scene.js +9 -0
- package/examples/api/notebook-assembly-debug.forge-notebook.json +90 -0
- package/examples/api/notebook-iteration.forge-notebook.json +75 -0
- package/examples/api/patterns.forge.js +32 -0
- package/examples/api/pointAlong-orientation.forge.js +52 -0
- package/examples/api/profile-2020-b-slot6.forge.js +36 -0
- package/examples/api/rotate-around-to.forge.js +31 -0
- package/examples/api/runtime-joints-view.forge.js +116 -0
- package/examples/api/sdf-rover-demo.forge.js +159 -0
- package/examples/api/section-plane-visualization.forge.js +38 -0
- package/examples/api/sketch-basics.forge.js +48 -0
- package/examples/api/sketch-on-face.forge.js +56 -0
- package/examples/api/sketch-rounding-strategies.forge.js +56 -0
- package/examples/api/spatial-recipes.forge.js +129 -0
- package/examples/bathroom.forge.js +197 -0
- package/examples/bolt-and-nut.forge.js +39 -0
- package/examples/bolt-pattern.forge.js +18 -0
- package/examples/bottle.forge.js +101 -0
- package/examples/chair.forge.js +62 -0
- package/examples/chess-set.forge.js +232 -0
- package/examples/classical-piano.forge.js +203 -0
- package/examples/clock.forge.js +169 -0
- package/examples/compiler-corpus/README.md +88 -0
- package/examples/compiler-corpus/edge-finished-mount.forge.js +18 -0
- package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +24 -0
- package/examples/compiler-corpus/fastener-plate-variants.forge.js +42 -0
- package/examples/compiler-corpus/folded-service-panel-cover.forge.js +5 -0
- package/examples/compiler-corpus/motor-mount-plate.forge.js +32 -0
- package/examples/compiler-corpus/projection-relay-cover.forge.js +16 -0
- package/examples/compiler-corpus/sensor-bracket.forge.js +35 -0
- package/examples/compiler-corpus/service-panel-cover.forge.js +53 -0
- package/examples/compiler-corpus/trimmed-access-cover.forge.js +26 -0
- package/examples/cup.forge.js +25 -0
- package/examples/cut-plane-demo.forge.js +28 -0
- package/examples/door-with-hinges.forge.js +54 -0
- package/examples/frame.sketch.js +4 -0
- package/examples/headphone-hanger-profile.sketch.js +18 -0
- package/examples/headphone-hanger-v2.forge.js +88 -0
- package/examples/headphone-hanger.forge.js +5 -0
- package/examples/iphone-stand.forge.js +72 -0
- package/examples/iphone.forge.js +114 -0
- package/examples/ironman-helmet.js +79 -0
- package/examples/kitchen.forge.js +231 -0
- package/examples/lamp-shade.sketch.js +17 -0
- package/examples/laptop.forge.js +144 -0
- package/examples/liquid-soap-dispenser.forge.js +159 -0
- package/examples/modern-tv.forge.js +86 -0
- package/examples/picture-frame.forge.js +34 -0
- package/examples/robot_hand.forge.js +393 -0
- package/examples/robot_hand_2.forge.js +622 -0
- package/examples/sandbox.forge.js +3 -0
- package/examples/shelf/container.forge.js +30 -0
- package/examples/shelf/shelf-unit.forge.js +62 -0
- package/examples/shoe-rack-doors.forge.js +107 -0
- package/examples/shoe-rack.forge.js +65 -0
- package/examples/spiderman-cake.forge.js +92 -0
- package/examples/table-lamp.forge.js +33 -0
- package/examples/table.forge.js +44 -0
- package/examples/test-colors.forge.js +19 -0
- package/examples/tv-stand.forge.js +21 -0
- package/package.json +69 -0
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
// Robot Hand 2
|
|
2
|
+
// Goal: printable, robust, budget-friendly (~500-1000 EUR) desktop robot hand + long reach arm.
|
|
3
|
+
// Strategy: build small reliable modules, then combine with parametric kinematics.
|
|
4
|
+
|
|
5
|
+
// ---- 1) Control params (motion + fabrication) ----
|
|
6
|
+
const baseYaw = param("Base Yaw", 20, { min: -170, max: 170, unit: "°" });
|
|
7
|
+
const shoulderPitch = param("Shoulder Pitch", 35, { min: -25, max: 105, unit: "°" });
|
|
8
|
+
const elbowPitch = param("Elbow Pitch", 55, { min: -15, max: 135, unit: "°" });
|
|
9
|
+
const wristPitch = param("Wrist Pitch", -20, { min: -100, max: 100, unit: "°" });
|
|
10
|
+
const wristRoll = param("Wrist Roll", 10, { min: -180, max: 180, unit: "°" });
|
|
11
|
+
|
|
12
|
+
const upperLen = param("Upper Arm Len", 210, { min: 140, max: 320, unit: "mm" });
|
|
13
|
+
const foreLen = param("Forearm Len", 220, { min: 150, max: 340, unit: "mm" });
|
|
14
|
+
const foreExtension = param("Forearm Ext", 70, { min: 0, max: 150, unit: "mm" });
|
|
15
|
+
|
|
16
|
+
const gripperOpen = param("Gripper Open", 55, { min: 0, max: 90, unit: "mm" });
|
|
17
|
+
const fingerCurl = param("Finger Curl", 72, { min: 0, max: 100, unit: "°" });
|
|
18
|
+
const payloadType = param("Payload Type", 2, { min: 1, max: 4, integer: true });
|
|
19
|
+
const payloadSize = param("Payload Size", 36, { min: 20, max: 65, unit: "mm" });
|
|
20
|
+
const carryPayload = param("Carry Payload", 1, { min: 0, max: 1, integer: true });
|
|
21
|
+
|
|
22
|
+
const exploded = param("Exploded", 0, { min: 0, max: 140, unit: "mm" });
|
|
23
|
+
const sectionEnabled = param("Section Enabled", 0, { min: 0, max: 1, integer: true });
|
|
24
|
+
|
|
25
|
+
// ---- 2) Math + transform helpers ----
|
|
26
|
+
function rad(deg) {
|
|
27
|
+
return (deg * Math.PI) / 180;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function clamp(v, lo, hi) {
|
|
31
|
+
return Math.max(lo, Math.min(hi, v));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function add3(a, b) {
|
|
35
|
+
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function rotateXPoint(p, deg) {
|
|
39
|
+
const r = rad(deg);
|
|
40
|
+
const c = Math.cos(r);
|
|
41
|
+
const s = Math.sin(r);
|
|
42
|
+
return [p[0], p[1] * c - p[2] * s, p[1] * s + p[2] * c];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function explodeShape(shape, v, stage) {
|
|
46
|
+
const k = exploded * stage;
|
|
47
|
+
return shape.translate(v[0] * k, v[1] * k, v[2] * k);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function mergeBounds(a, b) {
|
|
51
|
+
return {
|
|
52
|
+
min: [
|
|
53
|
+
Math.min(a.min[0], b.min[0]),
|
|
54
|
+
Math.min(a.min[1], b.min[1]),
|
|
55
|
+
Math.min(a.min[2], b.min[2]),
|
|
56
|
+
],
|
|
57
|
+
max: [
|
|
58
|
+
Math.max(a.max[0], b.max[0]),
|
|
59
|
+
Math.max(a.max[1], b.max[1]),
|
|
60
|
+
Math.max(a.max[2], b.max[2]),
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function collectItemBounds(item) {
|
|
66
|
+
if (item.shape) {
|
|
67
|
+
return item.shape.boundingBox();
|
|
68
|
+
}
|
|
69
|
+
if (item.group && item.group.length > 0) {
|
|
70
|
+
let acc = null;
|
|
71
|
+
for (let i = 0; i < item.group.length; i++) {
|
|
72
|
+
const b = collectItemBounds(item.group[i]);
|
|
73
|
+
if (!b) continue;
|
|
74
|
+
acc = acc ? mergeBounds(acc, b) : b;
|
|
75
|
+
}
|
|
76
|
+
return acc;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function collectSceneBounds(items) {
|
|
82
|
+
let acc = null;
|
|
83
|
+
for (let i = 0; i < items.length; i++) {
|
|
84
|
+
const b = collectItemBounds(items[i]);
|
|
85
|
+
if (!b) continue;
|
|
86
|
+
acc = acc ? mergeBounds(acc, b) : b;
|
|
87
|
+
}
|
|
88
|
+
return acc || { min: [-100, -100, -100], max: [100, 100, 100] };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function gearDisc(radius, thickness, teeth) {
|
|
92
|
+
return cylinder(thickness, radius, radius, teeth, true).pointAlong([0, 1, 0]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function axle(radius, length) {
|
|
96
|
+
return cylinder(length, radius).pointAlong([0, 1, 0]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function makeHollowBeam(length, width, height, wall, ribs) {
|
|
100
|
+
const safeWall = clamp(wall, 2, Math.min(width, height) * 0.35);
|
|
101
|
+
const innerLen = Math.max(8, length - 2 * safeWall);
|
|
102
|
+
const innerW = Math.max(6, width - 2 * safeWall);
|
|
103
|
+
const innerH = Math.max(6, height - 2 * safeWall);
|
|
104
|
+
|
|
105
|
+
const outer = box(length, width, height).translate(0, -width / 2, -height / 2);
|
|
106
|
+
const inner = box(innerLen, innerW, innerH).translate(safeWall, -innerW / 2, -innerH / 2);
|
|
107
|
+
|
|
108
|
+
let beam = outer.subtract(inner);
|
|
109
|
+
|
|
110
|
+
const ribList = [];
|
|
111
|
+
for (let i = 1; i <= ribs; i++) {
|
|
112
|
+
const x = (i * length) / (ribs + 1) - safeWall * 0.5;
|
|
113
|
+
ribList.push(box(safeWall, innerW, innerH).translate(x, -innerW / 2, -innerH / 2));
|
|
114
|
+
}
|
|
115
|
+
if (ribList.length > 0) {
|
|
116
|
+
beam = union(beam, ...ribList);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const endBossR = Math.min(width, height) * 0.22;
|
|
120
|
+
const bossA = axle(endBossR, width + 6).translate(0, 0, 0);
|
|
121
|
+
const bossB = axle(endBossR, width + 6).translate(length, 0, 0);
|
|
122
|
+
|
|
123
|
+
const windowCuts = [];
|
|
124
|
+
const windowCount = 4;
|
|
125
|
+
for (let i = 0; i < windowCount; i++) {
|
|
126
|
+
const x = length * 0.15 + (i * length * 0.68) / (windowCount - 1);
|
|
127
|
+
windowCuts.push(cylinder(width + 8, Math.max(3, height * 0.12)).pointAlong([0, 1, 0]).translate(x, 0, 0));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return difference(union(beam, bossA, bossB), ...windowCuts);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function makeServoLocal(bodyL, bodyW, bodyH, hornR) {
|
|
134
|
+
const shell = box(bodyL, bodyW, bodyH).translate(-bodyL * 0.62, -bodyW / 2, -bodyH / 2);
|
|
135
|
+
const mountingEar = box(bodyL * 0.28, bodyW + 8, 3).translate(-bodyL * 0.62, -(bodyW + 8) / 2, -bodyH / 2 - 3);
|
|
136
|
+
const horn = gearDisc(hornR, 4, 20);
|
|
137
|
+
const shaft = axle(2.6, bodyW + 10);
|
|
138
|
+
return union(shell, mountingEar, horn, shaft);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function makeFingerLocal(jointAngles, segLens, fingerW, fingerH) {
|
|
142
|
+
let x = 0;
|
|
143
|
+
let z = 0;
|
|
144
|
+
let angle = 0;
|
|
145
|
+
|
|
146
|
+
const segs = [];
|
|
147
|
+
const pins = [];
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < segLens.length; i++) {
|
|
150
|
+
const len = segLens[i];
|
|
151
|
+
const core = box(len, fingerW, fingerH).translate(0, -fingerW / 2, -fingerH / 2);
|
|
152
|
+
const cutW = Math.max(3, fingerW - 3.2);
|
|
153
|
+
const cutH = Math.max(3, fingerH - 3.2);
|
|
154
|
+
const pocket = box(len * 0.56, cutW, cutH).translate(len * 0.24, -cutW / 2, -cutH / 2);
|
|
155
|
+
|
|
156
|
+
const seg = core.subtract(pocket)
|
|
157
|
+
.rotateAround([0, -1, 0], angle, [0, 0, 0])
|
|
158
|
+
.translate(x, 0, z);
|
|
159
|
+
segs.push(seg);
|
|
160
|
+
|
|
161
|
+
const pin = axle(1.9, fingerW + 2).translate(x, 0, z);
|
|
162
|
+
pins.push(pin);
|
|
163
|
+
|
|
164
|
+
x += len * Math.cos(rad(angle));
|
|
165
|
+
z += len * Math.sin(rad(angle));
|
|
166
|
+
angle += jointAngles[i];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const tipPos = [x, 0, z - fingerH * 0.15];
|
|
170
|
+
const tipPad = sphere(fingerH * 0.65).scale([1.35, 0.9, 0.65]).translate(tipPos[0], tipPos[1], tipPos[2]);
|
|
171
|
+
const tendonTunnel = cylinder(segLens.reduce((a, b) => a + b, 0) + 10, 1.2)
|
|
172
|
+
.pointAlong([1, 0, 0])
|
|
173
|
+
.translate(-4, 0, 0);
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
shell: union(...segs).subtract(tendonTunnel),
|
|
177
|
+
pins: union(...pins),
|
|
178
|
+
tip: tipPad,
|
|
179
|
+
tipPos,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function makePayloadLocal(kind, size) {
|
|
184
|
+
if (kind === 1) {
|
|
185
|
+
return sphere(size * 0.5);
|
|
186
|
+
}
|
|
187
|
+
if (kind === 2) {
|
|
188
|
+
return box(size, size * 0.78, size * 0.58, true);
|
|
189
|
+
}
|
|
190
|
+
if (kind === 3) {
|
|
191
|
+
return cylinder(size * 0.9, size * 0.32, undefined, undefined, true);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const lobeA = sphere(size * 0.35).translate(-size * 0.2, 0, 0);
|
|
195
|
+
const lobeB = sphere(size * 0.28).translate(size * 0.2, size * 0.1, size * 0.05);
|
|
196
|
+
const bridge = box(size * 0.48, size * 0.22, size * 0.28, true);
|
|
197
|
+
return union(lobeA, lobeB, bridge);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---- 3) Base + mount (manufacturable fixed module) ----
|
|
201
|
+
const mountW = 180;
|
|
202
|
+
const mountD = 140;
|
|
203
|
+
const mountT = 8;
|
|
204
|
+
|
|
205
|
+
let mountPlate = box(mountW, mountD, mountT, true).translate(0, 0, mountT * 0.5);
|
|
206
|
+
const mountHoles = [];
|
|
207
|
+
for (const sx of [-1, 1]) {
|
|
208
|
+
for (const sy of [-1, 1]) {
|
|
209
|
+
mountHoles.push(
|
|
210
|
+
cylinder(mountT + 2, 4.2).translate(sx * (mountW * 0.34), sy * (mountD * 0.34), -1)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
mountPlate = difference(mountPlate, ...mountHoles);
|
|
215
|
+
|
|
216
|
+
const clampGap = 38;
|
|
217
|
+
const clampNeck = box(44, 28, clampGap + mountT)
|
|
218
|
+
.translate(-22, -mountD / 2 - 14, -clampGap + mountT * 0.5);
|
|
219
|
+
const clampJaw = box(130, 28, 10, true)
|
|
220
|
+
.translate(0, -mountD / 2 - 14, -clampGap - 5);
|
|
221
|
+
const clampScrew = cylinder(clampGap + 7, 4.8)
|
|
222
|
+
.translate(0, -mountD / 2 - 14, -clampGap - 1);
|
|
223
|
+
const clampKnob = cylinder(8, 14, 14, 18, true)
|
|
224
|
+
.translate(0, -mountD / 2 - 14, -clampGap - 11);
|
|
225
|
+
|
|
226
|
+
const statorH = 44;
|
|
227
|
+
const statorR = 56;
|
|
228
|
+
let baseStator = cylinder(statorH, statorR).translate(0, 0, mountT);
|
|
229
|
+
baseStator = baseStator.subtract(cylinder(statorH + 2, statorR - 10).translate(0, 0, mountT + 4));
|
|
230
|
+
|
|
231
|
+
const rotorH = 20;
|
|
232
|
+
let baseRotor = cylinder(rotorH, 46).translate(0, 0, mountT + statorH - 4);
|
|
233
|
+
baseRotor = baseRotor.subtract(cylinder(rotorH + 2, 30).translate(0, 0, mountT + statorH - 3));
|
|
234
|
+
|
|
235
|
+
const towerH = 40;
|
|
236
|
+
const shoulderTower = box(42, 58, towerH, true)
|
|
237
|
+
.translate(0, 0, mountT + statorH + rotorH + towerH * 0.5 - 4);
|
|
238
|
+
|
|
239
|
+
const yawServoLocal = makeServoLocal(44, 21, 39, 11)
|
|
240
|
+
.translate(-34, 0, mountT + statorH + 8);
|
|
241
|
+
|
|
242
|
+
// ---- 4) Arm kinematics (assembly graph solve) ----
|
|
243
|
+
const shoulderPivotZ = mountT + statorH + rotorH + towerH * 0.62 - 4;
|
|
244
|
+
const forearmReach = foreLen + foreExtension;
|
|
245
|
+
|
|
246
|
+
const kinematics = assembly("Robot Hand 2 Kinematics")
|
|
247
|
+
.addFrame("Base Frame")
|
|
248
|
+
.addFrame("Yaw Frame")
|
|
249
|
+
.addFrame("Shoulder Frame")
|
|
250
|
+
.addFrame("Elbow Frame")
|
|
251
|
+
.addFrame("Wrist Pitch Frame")
|
|
252
|
+
.addFrame("Wrist Roll Frame")
|
|
253
|
+
.addRevolute("Base Yaw", "Base Frame", "Yaw Frame", {
|
|
254
|
+
axis: [0, 0, 1],
|
|
255
|
+
min: -170,
|
|
256
|
+
max: 170,
|
|
257
|
+
})
|
|
258
|
+
.addRevolute("Shoulder Pitch", "Yaw Frame", "Shoulder Frame", {
|
|
259
|
+
axis: [0, -1, 0],
|
|
260
|
+
min: -25,
|
|
261
|
+
max: 105,
|
|
262
|
+
frame: Transform.identity().translate(0, 0, shoulderPivotZ),
|
|
263
|
+
})
|
|
264
|
+
.addRevolute("Elbow Pitch", "Shoulder Frame", "Elbow Frame", {
|
|
265
|
+
axis: [0, -1, 0],
|
|
266
|
+
min: -15,
|
|
267
|
+
max: 135,
|
|
268
|
+
frame: Transform.identity().translate(upperLen, 0, 0),
|
|
269
|
+
})
|
|
270
|
+
.addRevolute("Wrist Pitch", "Elbow Frame", "Wrist Pitch Frame", {
|
|
271
|
+
axis: [0, -1, 0],
|
|
272
|
+
min: -100,
|
|
273
|
+
max: 100,
|
|
274
|
+
frame: Transform.identity().translate(forearmReach, 0, 0),
|
|
275
|
+
})
|
|
276
|
+
.addRevolute("Wrist Roll", "Wrist Pitch Frame", "Wrist Roll Frame", {
|
|
277
|
+
axis: [1, 0, 0],
|
|
278
|
+
min: -180,
|
|
279
|
+
max: 180,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const solvedKinematics = kinematics.solve({
|
|
283
|
+
"Base Yaw": baseYaw,
|
|
284
|
+
"Shoulder Pitch": shoulderPitch,
|
|
285
|
+
"Elbow Pitch": elbowPitch,
|
|
286
|
+
"Wrist Pitch": wristPitch,
|
|
287
|
+
"Wrist Roll": wristRoll,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const yawFrameT = solvedKinematics.getTransform("Yaw Frame");
|
|
291
|
+
const shoulderFrameT = solvedKinematics.getTransform("Shoulder Frame");
|
|
292
|
+
const elbowFrameT = solvedKinematics.getTransform("Elbow Frame");
|
|
293
|
+
const handFrameT = solvedKinematics.getTransform("Wrist Roll Frame");
|
|
294
|
+
|
|
295
|
+
const shoulderPivot = shoulderFrameT.point([0, 0, 0]);
|
|
296
|
+
const elbowPivot = elbowFrameT.point([0, 0, 0]);
|
|
297
|
+
const wristPivot = handFrameT.point([0, 0, 0]);
|
|
298
|
+
|
|
299
|
+
function placeAtYaw(shape) {
|
|
300
|
+
return shape.transform(yawFrameT);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function placeAtShoulder(shape) {
|
|
304
|
+
return shape.transform(shoulderFrameT);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function placeAtElbow(shape) {
|
|
308
|
+
return shape.transform(elbowFrameT);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function placeInHandFrame(shape) {
|
|
312
|
+
return shape.transform(handFrameT);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ---- 5) Arm mechanics modules ----
|
|
316
|
+
const upperBeam = makeHollowBeam(upperLen, 44, 36, 3.4, 5);
|
|
317
|
+
const shoulderHub = gearDisc(16, 14, 28);
|
|
318
|
+
const shoulderDrivenGear = gearDisc(26, 8, 42);
|
|
319
|
+
const shoulderServo = makeServoLocal(40, 20, 38, 10).translate(-22, 18, -8);
|
|
320
|
+
const shoulderAxle = axle(3.6, 70);
|
|
321
|
+
|
|
322
|
+
const foreOuter = makeHollowBeam(foreLen, 40, 32, 3.2, 5);
|
|
323
|
+
const sliderLen = 120 + foreExtension;
|
|
324
|
+
const sliderInsert = 70;
|
|
325
|
+
let foreSlider = makeHollowBeam(sliderLen, 28, 24, 2.8, 3).translate(foreLen - sliderInsert, 0, 0);
|
|
326
|
+
|
|
327
|
+
const lockCuts = [];
|
|
328
|
+
for (let i = 0; i < 5; i++) {
|
|
329
|
+
const x = foreLen - sliderInsert + 18 + i * 24;
|
|
330
|
+
lockCuts.push(axle(2.2, 36).translate(x, 0, 0));
|
|
331
|
+
}
|
|
332
|
+
foreSlider = difference(foreSlider, ...lockCuts);
|
|
333
|
+
|
|
334
|
+
const leadScrew = cylinder(sliderLen + 8, 2.4)
|
|
335
|
+
.pointAlong([1, 0, 0])
|
|
336
|
+
.translate(foreLen - sliderInsert - 4, 0, 0);
|
|
337
|
+
|
|
338
|
+
const elbowHub = gearDisc(15, 12, 24);
|
|
339
|
+
const elbowGear = gearDisc(24, 8, 36);
|
|
340
|
+
const elbowServo = makeServoLocal(40, 20, 38, 9).translate(-20, -20, 6);
|
|
341
|
+
const elbowAxle = axle(3.4, 62);
|
|
342
|
+
|
|
343
|
+
const wristCarrierLocal = box(54, 28, 24).translate(-8, -14, -12);
|
|
344
|
+
const wristPitchGear = gearDisc(14, 10, 22);
|
|
345
|
+
const wristPitchServo = makeServoLocal(34, 18, 32, 8).translate(-18, 15, 0);
|
|
346
|
+
const rollTubeLen = 86;
|
|
347
|
+
const rollTube = cylinder(rollTubeLen, 11).pointAlong([1, 0, 0]).translate(12, 0, 0);
|
|
348
|
+
const rollShaft = cylinder(rollTubeLen + 8, 3).pointAlong([1, 0, 0]).translate(10, 0, 0);
|
|
349
|
+
|
|
350
|
+
// ---- 6) Hand mechanics (iterative finger generation) ----
|
|
351
|
+
const palmLen = 76;
|
|
352
|
+
const palmW = 56;
|
|
353
|
+
const palmH = 40;
|
|
354
|
+
const palmWall = 3;
|
|
355
|
+
|
|
356
|
+
let palmOuter = box(palmLen, palmW, palmH).translate(0, -palmW / 2, -palmH / 2);
|
|
357
|
+
const palmInner = box(palmLen - 2 * palmWall, palmW - 2 * palmWall, palmH - 2 * palmWall)
|
|
358
|
+
.translate(palmWall, -(palmW - 2 * palmWall) / 2, -(palmH - 2 * palmWall) / 2);
|
|
359
|
+
|
|
360
|
+
const palmAccess = box(palmLen * 0.58, palmW * 0.35, palmH * 0.65)
|
|
361
|
+
.translate(palmLen * 0.18, -palmW * 0.17, -palmH * 0.32);
|
|
362
|
+
|
|
363
|
+
const knuckleRing = gearDisc(12, 12, 22).translate(palmLen - 8, 0, 0);
|
|
364
|
+
const wristRollGear = gearDisc(18, 8, 30).translate(6, 0, 0);
|
|
365
|
+
const handServoCore = makeServoLocal(32, 17, 29, 7).translate(16, 0, -2);
|
|
366
|
+
|
|
367
|
+
let palmShell = difference(palmOuter, palmInner, palmAccess);
|
|
368
|
+
|
|
369
|
+
const closeRatio = clamp(1 - gripperOpen / 90, 0, 1);
|
|
370
|
+
const bend = fingerCurl * closeRatio;
|
|
371
|
+
const fingerJointAngles = [bend * 0.5, bend * 0.32, bend * 0.18];
|
|
372
|
+
const fingerSegs = [38, 28, 22];
|
|
373
|
+
const fingerW = 11;
|
|
374
|
+
const fingerH = 9;
|
|
375
|
+
const fingerBaseRadius = 12 + gripperOpen * 0.24;
|
|
376
|
+
const fingerSpreadAngles = [-110, 10, 130];
|
|
377
|
+
|
|
378
|
+
const fingerShells = [];
|
|
379
|
+
const fingerPins = [];
|
|
380
|
+
const fingerTips = [];
|
|
381
|
+
const knuckleGears = [];
|
|
382
|
+
const fingerTipPoints = [];
|
|
383
|
+
|
|
384
|
+
for (let i = 0; i < fingerSpreadAngles.length; i++) {
|
|
385
|
+
const phi = fingerSpreadAngles[i];
|
|
386
|
+
const finger = makeFingerLocal(fingerJointAngles, fingerSegs, fingerW, fingerH);
|
|
387
|
+
|
|
388
|
+
const rootX = palmLen - 8;
|
|
389
|
+
const shellPlaced = finger.shell
|
|
390
|
+
.translate(rootX, 0, fingerBaseRadius)
|
|
391
|
+
.rotateAround([1, 0, 0], phi, [0, 0, 0]);
|
|
392
|
+
|
|
393
|
+
const pinsPlaced = finger.pins
|
|
394
|
+
.translate(rootX, 0, fingerBaseRadius)
|
|
395
|
+
.rotateAround([1, 0, 0], phi, [0, 0, 0]);
|
|
396
|
+
|
|
397
|
+
const tipPlaced = finger.tip
|
|
398
|
+
.translate(rootX, 0, fingerBaseRadius)
|
|
399
|
+
.rotateAround([1, 0, 0], phi, [0, 0, 0]);
|
|
400
|
+
const tipPoint = rotateXPoint(
|
|
401
|
+
[finger.tipPos[0] + rootX, finger.tipPos[1], finger.tipPos[2] + fingerBaseRadius],
|
|
402
|
+
phi
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
const knuckle = gearDisc(7.5, 6, 16)
|
|
406
|
+
.translate(rootX - 2, 0, fingerBaseRadius)
|
|
407
|
+
.rotateAround([1, 0, 0], phi, [0, 0, 0]);
|
|
408
|
+
|
|
409
|
+
fingerShells.push(shellPlaced);
|
|
410
|
+
fingerPins.push(pinsPlaced);
|
|
411
|
+
fingerTips.push(tipPlaced);
|
|
412
|
+
fingerTipPoints.push(tipPoint);
|
|
413
|
+
knuckleGears.push(knuckle);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let tipSum = [0, 0, 0];
|
|
417
|
+
for (let i = 0; i < fingerTipPoints.length; i++) {
|
|
418
|
+
tipSum = add3(tipSum, fingerTipPoints[i]);
|
|
419
|
+
}
|
|
420
|
+
const gripCenter = [
|
|
421
|
+
tipSum[0] / fingerTipPoints.length - payloadSize * 0.07,
|
|
422
|
+
tipSum[1] / fingerTipPoints.length,
|
|
423
|
+
tipSum[2] / fingerTipPoints.length,
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
// ---- 7) Payload in hand frame (shows arbitrary-object handling + object rotation) ----
|
|
427
|
+
const payloadLocal = makePayloadLocal(payloadType, payloadSize)
|
|
428
|
+
.translate(gripCenter[0], gripCenter[1], gripCenter[2]);
|
|
429
|
+
|
|
430
|
+
// ---- 8) Transform modules to world ----
|
|
431
|
+
const mountGroup = [
|
|
432
|
+
{ name: "Mount Plate", shape: explodeShape(mountPlate.color("#7e8a96"), [0, 0, 0], 0.2) },
|
|
433
|
+
{ name: "Clamp Neck", shape: explodeShape(clampNeck.color("#5c6770"), [0, -0.12, -1], 1.0) },
|
|
434
|
+
{ name: "Clamp Jaw", shape: explodeShape(clampJaw.color("#3e454b"), [0, -0.2, -1], 1.3) },
|
|
435
|
+
{ name: "Clamp Screw", shape: explodeShape(clampScrew.color("#c6ccd2"), [0, -0.45, -1], 1.5) },
|
|
436
|
+
{ name: "Clamp Knob", shape: explodeShape(clampKnob.color("#2f3439"), [0, -0.7, -1], 1.8) },
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
const yawGroup = [
|
|
440
|
+
{ name: "Yaw Stator", shape: explodeShape(baseStator.color("#4a5259"), [0, 0, 0.1], 0.4) },
|
|
441
|
+
{ name: "Yaw Rotor", shape: explodeShape(placeAtYaw(baseRotor).color("#768391"), [0, 0, 1], 0.9) },
|
|
442
|
+
{ name: "Shoulder Tower", shape: explodeShape(placeAtYaw(shoulderTower).color("#8e9bab"), [0, 0.15, 1], 1.2) },
|
|
443
|
+
{ name: "Yaw Servo", shape: explodeShape(placeAtYaw(yawServoLocal).color("#23272b"), [-0.6, 0.6, 0.5], 1.4) },
|
|
444
|
+
];
|
|
445
|
+
|
|
446
|
+
const armGroup = [
|
|
447
|
+
{
|
|
448
|
+
name: "Upper Arm Beam",
|
|
449
|
+
shape: explodeShape(placeAtShoulder(upperBeam).color("#5f87c6"), [0.9, 0, 0.22], 1.0),
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: "Shoulder Hub",
|
|
453
|
+
shape: explodeShape(placeAtShoulder(shoulderHub).color("#d7dde4"), [0.5, 0.5, 0.2], 1.4),
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
name: "Shoulder Gear",
|
|
457
|
+
shape: explodeShape(placeAtShoulder(shoulderDrivenGear).color("#b8c5d3"), [0.5, -0.5, 0.2], 1.5),
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: "Shoulder Servo",
|
|
461
|
+
shape: explodeShape(placeAtShoulder(shoulderServo).color("#2b3137"), [0.3, 0.9, 0.2], 1.8),
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: "Shoulder Axle",
|
|
465
|
+
shape: explodeShape(placeAtShoulder(shoulderAxle).color("#d8dde3"), [0.2, -0.9, 0.1], 1.6),
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: "Forearm Beam",
|
|
469
|
+
shape: explodeShape(placeAtElbow(foreOuter).color("#66a2d8"), [1.2, 0, 0.3], 1.2),
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "Forearm Slider",
|
|
473
|
+
shape: explodeShape(placeAtElbow(foreSlider).color("#7fb3e0"), [1.2, 0, 0.9], 1.5),
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: "Lead Screw",
|
|
477
|
+
shape: explodeShape(placeAtElbow(leadScrew).color("#d8dde3"), [1.2, -0.4, 0.6], 1.8),
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: "Elbow Hub",
|
|
481
|
+
shape: explodeShape(placeAtElbow(elbowHub).color("#d7dde4"), [0.8, 0.7, 0.3], 1.8),
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: "Elbow Gear",
|
|
485
|
+
shape: explodeShape(placeAtElbow(elbowGear).color("#b8c5d3"), [0.8, -0.7, 0.3], 1.9),
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
name: "Elbow Servo",
|
|
489
|
+
shape: explodeShape(placeAtElbow(elbowServo).color("#2b3137"), [0.8, 1, 0.2], 2.0),
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "Elbow Axle",
|
|
493
|
+
shape: explodeShape(placeAtElbow(elbowAxle).color("#d8dde3"), [0.8, -1, 0.2], 1.9),
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: "Wrist Carrier",
|
|
497
|
+
shape: explodeShape(placeAtElbow(wristCarrierLocal.translate(foreLen + foreExtension, 0, 0)).color("#6fb3b3"), [1.5, 0, 0.3], 1.6),
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: "Wrist Pitch Gear",
|
|
501
|
+
shape: explodeShape(placeAtElbow(wristPitchGear.translate(foreLen + foreExtension, 0, 0)).color("#c7d0d8"), [1.5, 0.75, 0.4], 1.9),
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
name: "Wrist Pitch Servo",
|
|
505
|
+
shape: explodeShape(placeAtElbow(wristPitchServo.translate(foreLen + foreExtension, 0, 0)).color("#2b3137"), [1.5, -0.75, 0.4], 1.9),
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
name: "Roll Tube",
|
|
509
|
+
shape: explodeShape(placeAtElbow(rollTube.translate(foreLen + foreExtension, 0, 0)).color("#87bfc2"), [1.8, 0, 0.6], 2.0),
|
|
510
|
+
},
|
|
511
|
+
];
|
|
512
|
+
|
|
513
|
+
const handRootX = rollTubeLen + 12;
|
|
514
|
+
function placeHandLocal(shape) {
|
|
515
|
+
return placeInHandFrame(shape.translate(handRootX, 0, 0));
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const rotatingRollShaftLocal = rollShaft;
|
|
519
|
+
const wristCouplerLocal = union(
|
|
520
|
+
cylinder(20, 8.5).pointAlong([1, 0, 0]).translate(handRootX - 20, 0, 0),
|
|
521
|
+
box(14, 6, 18, true).translate(handRootX - 8, 0, 9)
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
const handGroup = [
|
|
525
|
+
{
|
|
526
|
+
name: "Rotating Wrist Shaft",
|
|
527
|
+
shape: explodeShape(placeInHandFrame(rotatingRollShaftLocal).color("#d8dde3"), [1.95, 0.3, 0.72], 2.2),
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: "Wrist Coupler",
|
|
531
|
+
shape: explodeShape(placeInHandFrame(wristCouplerLocal).color("#9fb3c6"), [2.05, -0.35, 0.76], 2.25),
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: "Palm Shell",
|
|
535
|
+
shape: explodeShape(placeHandLocal(palmShell).color("#c9a35f"), [2.1, 0, 0.4], 1.6),
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: "Knuckle Ring",
|
|
539
|
+
shape: explodeShape(placeHandLocal(knuckleRing).color("#d3b67d"), [2.2, 0.7, 0.45], 1.9),
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
name: "Wrist Roll Gear",
|
|
543
|
+
shape: explodeShape(placeHandLocal(wristRollGear).color("#c7d0d8"), [2.2, -0.7, 0.45], 1.9),
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "Palm Servo",
|
|
547
|
+
shape: explodeShape(placeHandLocal(handServoCore).color("#2b3137"), [2.1, 0.95, 0.4], 2.0),
|
|
548
|
+
},
|
|
549
|
+
];
|
|
550
|
+
|
|
551
|
+
for (let i = 0; i < knuckleGears.length; i++) {
|
|
552
|
+
const dirY = i === 0 ? -1 : i === 1 ? 0.1 : 1;
|
|
553
|
+
handGroup.push({
|
|
554
|
+
name: `Knuckle Gear ${i + 1}`,
|
|
555
|
+
shape: explodeShape(placeHandLocal(knuckleGears[i]).color("#d8dde3"), [2.5, dirY, 0.55], 2.1),
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
for (let i = 0; i < fingerShells.length; i++) {
|
|
560
|
+
const dirY = i === 0 ? -1 : i === 1 ? 0 : 1;
|
|
561
|
+
handGroup.push({
|
|
562
|
+
name: `Finger Shell ${i + 1}`,
|
|
563
|
+
shape: explodeShape(placeHandLocal(fingerShells[i]).color("#c6c2bc"), [2.7, dirY, 0.65], 2.2),
|
|
564
|
+
});
|
|
565
|
+
handGroup.push({
|
|
566
|
+
name: `Finger Pins ${i + 1}`,
|
|
567
|
+
shape: explodeShape(placeHandLocal(fingerPins[i]).color("#d8dde3"), [2.8, dirY * 1.1, 0.8], 2.35),
|
|
568
|
+
});
|
|
569
|
+
handGroup.push({
|
|
570
|
+
name: `Grip Pad ${i + 1}`,
|
|
571
|
+
shape: explodeShape(placeHandLocal(fingerTips[i]).color("#40464d"), [2.95, dirY * 1.15, 0.95], 2.5),
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const wristPivotMarker = sphere(4).translate(wristPivot[0], wristPivot[1], wristPivot[2]).color("#e34f4f");
|
|
576
|
+
const elbowPivotMarker = sphere(4).translate(elbowPivot[0], elbowPivot[1], elbowPivot[2]).color("#e3944f");
|
|
577
|
+
const shoulderPivotMarker = sphere(4).translate(shoulderPivot[0], shoulderPivot[1], shoulderPivot[2]).color("#f0d04f");
|
|
578
|
+
|
|
579
|
+
const kinematicsMarkers = [
|
|
580
|
+
{ name: "Shoulder Pivot", shape: shoulderPivotMarker },
|
|
581
|
+
{ name: "Elbow Pivot", shape: elbowPivotMarker },
|
|
582
|
+
{ name: "Wrist Pivot", shape: wristPivotMarker },
|
|
583
|
+
];
|
|
584
|
+
|
|
585
|
+
const payloadShape = placeInHandFrame(payloadLocal.translate(rollTubeLen + 12, 0, 0)).color("#d98bc7");
|
|
586
|
+
|
|
587
|
+
const scene = [
|
|
588
|
+
{ name: "Mount System", group: mountGroup },
|
|
589
|
+
{ name: "Yaw Base", group: yawGroup },
|
|
590
|
+
{ name: "Arm Mechanics", group: armGroup },
|
|
591
|
+
{ name: "Hand Mechanics", group: handGroup },
|
|
592
|
+
{ name: "Kinematic Pivots", group: kinematicsMarkers },
|
|
593
|
+
];
|
|
594
|
+
|
|
595
|
+
if (carryPayload === 1) {
|
|
596
|
+
scene.push({ name: "Payload (Rotates With Hand)", shape: explodeShape(payloadShape, [3.2, 0, 1.05], 2.6) });
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const sceneBounds = collectSceneBounds(scene);
|
|
600
|
+
const sectionMarginX = Math.max(30, (sceneBounds.max[0] - sceneBounds.min[0]) * 0.25);
|
|
601
|
+
const sectionMarginZ = Math.max(30, (sceneBounds.max[2] - sceneBounds.min[2]) * 0.25);
|
|
602
|
+
const sectionDefaultX = (sceneBounds.min[0] + sceneBounds.max[0]) * 0.5;
|
|
603
|
+
const sectionDefaultZ = (sceneBounds.min[2] + sceneBounds.max[2]) * 0.5;
|
|
604
|
+
|
|
605
|
+
const sectionX = param("Section X", sectionDefaultX, {
|
|
606
|
+
min: sceneBounds.min[0] - sectionMarginX,
|
|
607
|
+
max: sceneBounds.max[0] + sectionMarginX,
|
|
608
|
+
unit: "mm",
|
|
609
|
+
});
|
|
610
|
+
const sectionZ = param("Section Z", sectionDefaultZ, {
|
|
611
|
+
min: sceneBounds.min[2] - sectionMarginZ,
|
|
612
|
+
max: sceneBounds.max[2] + sectionMarginZ,
|
|
613
|
+
unit: "mm",
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// Renderer-side section planes (no model subtraction / no geometry pollution).
|
|
617
|
+
if (sectionEnabled === 1) {
|
|
618
|
+
cutPlane("Internal X", [1, 0, 0], sectionX);
|
|
619
|
+
cutPlane("Internal Z", [0, 0, 1], sectionZ);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return scene;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Storage Container — parametric open-top bin
|
|
2
|
+
// Used by shelf-unit.forge.js via importPart()
|
|
3
|
+
|
|
4
|
+
const w = param("Container Width", 120, { min: 60, max: 200, unit: "mm" });
|
|
5
|
+
const d = param("Container Depth", 180, { min: 100, max: 300, unit: "mm" });
|
|
6
|
+
const h = param("Container Height", 100, { min: 50, max: 200, unit: "mm" });
|
|
7
|
+
const wall = param("Wall", 3, { min: 1.5, max: 6, unit: "mm" });
|
|
8
|
+
const lipH = param("Lip Height", 5, { min: 2, max: 10, unit: "mm" });
|
|
9
|
+
|
|
10
|
+
// Outer shell
|
|
11
|
+
const outer = box(w, d, h);
|
|
12
|
+
|
|
13
|
+
// Inner cavity (open top)
|
|
14
|
+
const inner = box(w - wall * 2, d - wall * 2, h - wall + 1)
|
|
15
|
+
.translate(wall, wall, wall);
|
|
16
|
+
|
|
17
|
+
// Lip — slight outward flange at top for grip
|
|
18
|
+
const lip = box(w + lipH * 2, d + lipH * 2, wall)
|
|
19
|
+
.translate(-lipH, -lipH, h - wall);
|
|
20
|
+
|
|
21
|
+
const container = union(outer, lip).subtract(inner);
|
|
22
|
+
|
|
23
|
+
// Label area — shallow recess on front face
|
|
24
|
+
const labelW = w * 0.6;
|
|
25
|
+
const labelH = h * 0.3;
|
|
26
|
+
const labelDepth = 0.8;
|
|
27
|
+
const label = box(labelW, labelDepth + 1, labelH)
|
|
28
|
+
.translate(w / 2 - labelW / 2, -0.5, h * 0.35);
|
|
29
|
+
|
|
30
|
+
return container.subtract(label);
|