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.
Files changed (119) hide show
  1. package/LICENSE +97 -0
  2. package/README.md +354 -0
  3. package/dist/assets/evalWorker-BYHXxh15.js +461 -0
  4. package/dist/assets/index--CYbOPKS.js +5797 -0
  5. package/dist/assets/manifold-65fIQlgQ.js +20 -0
  6. package/dist/assets/manifold-B85M7kop.js +20 -0
  7. package/dist/assets/manifold-B8h_vZ5O.js +16 -0
  8. package/dist/assets/manifold-D9yvTBHx.wasm +0 -0
  9. package/dist/assets/manifold-d1UpyLJ8.js +20 -0
  10. package/dist/assets/reportWorker-B1Zdrz9l.js +494 -0
  11. package/dist/index.html +16 -0
  12. package/dist-cli/forgecad.js +44464 -0
  13. package/dist-skill/SKILL.md +4635 -0
  14. package/examples/3d-printer.forge.js +328 -0
  15. package/examples/5-figen-robot-hand.forge.js +283 -0
  16. package/examples/ac-unit-glm47.forge.js +108 -0
  17. package/examples/ac-unit-glm5.forge.js +174 -0
  18. package/examples/ac-unit-kimi25.forge.js +236 -0
  19. package/examples/ac-unit-minimax.forge.js +123 -0
  20. package/examples/ac-unit.forge.js +126 -0
  21. package/examples/adjustable-table.forge.js +191 -0
  22. package/examples/api/assembly-gear-coupling.forge.js +32 -0
  23. package/examples/api/assembly-mechanism.forge.js +111 -0
  24. package/examples/api/attachTo-basics.forge.js +45 -0
  25. package/examples/api/benchy-style-hull.forge.js +89 -0
  26. package/examples/api/bill-of-materials.forge.js +46 -0
  27. package/examples/api/boolean-operations.forge.js +48 -0
  28. package/examples/api/bounding-box-visualizer.forge.js +58 -0
  29. package/examples/api/brep-exportable.forge.js +19 -0
  30. package/examples/api/center-true-vs-false.forge.js +40 -0
  31. package/examples/api/clone-duplicate.forge.js +41 -0
  32. package/examples/api/colors-union-vs-array.forge.js +27 -0
  33. package/examples/api/coordinate-system.forge.js +54 -0
  34. package/examples/api/curves-surfacing-basics.forge.js +91 -0
  35. package/examples/api/dimensioned-bracket.forge.js +19 -0
  36. package/examples/api/elbow-test.forge.js +23 -0
  37. package/examples/api/exploded-view.forge.js +60 -0
  38. package/examples/api/extrude-options.forge.js +44 -0
  39. package/examples/api/face-gears.forge.js +44 -0
  40. package/examples/api/face-transformation-history.forge.js +45 -0
  41. package/examples/api/feature-created-faces.forge.js +47 -0
  42. package/examples/api/folded-service-panel-cover.forge.js +3 -0
  43. package/examples/api/folded-service-panel-cover.js +117 -0
  44. package/examples/api/gears-bevel-face-joints.forge.js +157 -0
  45. package/examples/api/gears-tier1.forge.js +57 -0
  46. package/examples/api/geometry-info.forge.js +49 -0
  47. package/examples/api/group-test.forge.js +34 -0
  48. package/examples/api/group-vs-union.forge.js +25 -0
  49. package/examples/api/import-args-unit.forge.js +5 -0
  50. package/examples/api/import-args.forge.js +16 -0
  51. package/examples/api/import-dimensions-follow.forge.js +18 -0
  52. package/examples/api/import-placement-references.forge.js +18 -0
  53. package/examples/api/import-placement-widget-source.forge.js +30 -0
  54. package/examples/api/import-relative-paths.forge.js +18 -0
  55. package/examples/api/import-svg-sketch-shape.svg +15 -0
  56. package/examples/api/import-svg-sketch.forge.js +28 -0
  57. package/examples/api/js-module-imports.forge.js +9 -0
  58. package/examples/api/js-module-pillars.js +25 -0
  59. package/examples/api/js-module-scene.js +9 -0
  60. package/examples/api/notebook-assembly-debug.forge-notebook.json +90 -0
  61. package/examples/api/notebook-iteration.forge-notebook.json +75 -0
  62. package/examples/api/patterns.forge.js +32 -0
  63. package/examples/api/pointAlong-orientation.forge.js +52 -0
  64. package/examples/api/profile-2020-b-slot6.forge.js +36 -0
  65. package/examples/api/rotate-around-to.forge.js +31 -0
  66. package/examples/api/runtime-joints-view.forge.js +116 -0
  67. package/examples/api/sdf-rover-demo.forge.js +159 -0
  68. package/examples/api/section-plane-visualization.forge.js +38 -0
  69. package/examples/api/sketch-basics.forge.js +48 -0
  70. package/examples/api/sketch-on-face.forge.js +56 -0
  71. package/examples/api/sketch-rounding-strategies.forge.js +56 -0
  72. package/examples/api/spatial-recipes.forge.js +129 -0
  73. package/examples/bathroom.forge.js +197 -0
  74. package/examples/bolt-and-nut.forge.js +39 -0
  75. package/examples/bolt-pattern.forge.js +18 -0
  76. package/examples/bottle.forge.js +101 -0
  77. package/examples/chair.forge.js +62 -0
  78. package/examples/chess-set.forge.js +232 -0
  79. package/examples/classical-piano.forge.js +203 -0
  80. package/examples/clock.forge.js +169 -0
  81. package/examples/compiler-corpus/README.md +88 -0
  82. package/examples/compiler-corpus/edge-finished-mount.forge.js +18 -0
  83. package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +24 -0
  84. package/examples/compiler-corpus/fastener-plate-variants.forge.js +42 -0
  85. package/examples/compiler-corpus/folded-service-panel-cover.forge.js +5 -0
  86. package/examples/compiler-corpus/motor-mount-plate.forge.js +32 -0
  87. package/examples/compiler-corpus/projection-relay-cover.forge.js +16 -0
  88. package/examples/compiler-corpus/sensor-bracket.forge.js +35 -0
  89. package/examples/compiler-corpus/service-panel-cover.forge.js +53 -0
  90. package/examples/compiler-corpus/trimmed-access-cover.forge.js +26 -0
  91. package/examples/cup.forge.js +25 -0
  92. package/examples/cut-plane-demo.forge.js +28 -0
  93. package/examples/door-with-hinges.forge.js +54 -0
  94. package/examples/frame.sketch.js +4 -0
  95. package/examples/headphone-hanger-profile.sketch.js +18 -0
  96. package/examples/headphone-hanger-v2.forge.js +88 -0
  97. package/examples/headphone-hanger.forge.js +5 -0
  98. package/examples/iphone-stand.forge.js +72 -0
  99. package/examples/iphone.forge.js +114 -0
  100. package/examples/ironman-helmet.js +79 -0
  101. package/examples/kitchen.forge.js +231 -0
  102. package/examples/lamp-shade.sketch.js +17 -0
  103. package/examples/laptop.forge.js +144 -0
  104. package/examples/liquid-soap-dispenser.forge.js +159 -0
  105. package/examples/modern-tv.forge.js +86 -0
  106. package/examples/picture-frame.forge.js +34 -0
  107. package/examples/robot_hand.forge.js +393 -0
  108. package/examples/robot_hand_2.forge.js +622 -0
  109. package/examples/sandbox.forge.js +3 -0
  110. package/examples/shelf/container.forge.js +30 -0
  111. package/examples/shelf/shelf-unit.forge.js +62 -0
  112. package/examples/shoe-rack-doors.forge.js +107 -0
  113. package/examples/shoe-rack.forge.js +65 -0
  114. package/examples/spiderman-cake.forge.js +92 -0
  115. package/examples/table-lamp.forge.js +33 -0
  116. package/examples/table.forge.js +44 -0
  117. package/examples/test-colors.forge.js +19 -0
  118. package/examples/tv-stand.forge.js +21 -0
  119. package/package.json +69 -0
@@ -0,0 +1,328 @@
1
+ // CoreXY 3D Printer — Bambu-style with proper clearances
2
+ // Z-up, Y = depth (front/back), X = width (left/right)
3
+ //
4
+ // Printing model: bed starts near nozzle, lowers as layers build up.
5
+ // Bed Z param represents current print progress (high = start, low = done).
6
+
7
+ // ─── Parameters ───
8
+ const bedW = param("Bed Width", 220, { min: 180, max: 300, unit: "mm" });
9
+ const bedD = param("Bed Depth", 220, { min: 180, max: 300, unit: "mm" });
10
+ const bedThick = param("Bed Thickness", 4, { min: 3, max: 8, unit: "mm" });
11
+ const frameMargin = param("Frame Margin", 40, { min: 20, max: 80, unit: "mm" });
12
+ const beam = param("Beam Size", 20, { min: 15, max: 30, unit: "mm" });
13
+ const frameH = param("Frame Height", 400, { min: 320, max: 520, unit: "mm" });
14
+
15
+ // ─── Derived frame ───
16
+ const frameW = bedW + 2 * frameMargin;
17
+ const frameD = bedD + 2 * frameMargin;
18
+ const innerW = frameW - 2 * beam;
19
+ const innerD = frameD - 2 * beam;
20
+ const railR = 4;
21
+
22
+ // Gantry sits near the top of the frame
23
+ const gantryZ = frameH - 80;
24
+ const nozzleToRail = 55;
25
+ const nozzleZ = gantryZ - nozzleToRail;
26
+
27
+ // Bed starts near nozzle tip and lowers during printing.
28
+ const bedTopMax = nozzleZ - 1;
29
+ const bedTopMin = beam + bedThick + 80;
30
+ const bedZ = param("Bed Z (print progress)", bedTopMax, {
31
+ min: bedTopMin, max: bedTopMax, unit: "mm",
32
+ });
33
+
34
+ // Gantry XY travel
35
+ const gantryTravel = innerD / 2 - 30;
36
+ const gantryY = param("Gantry Y", 0, { min: -gantryTravel, max: gantryTravel, unit: "mm" });
37
+ const headTravel = innerW / 2 - 50;
38
+ const headX = param("Nozzle X", 0, { min: -headTravel, max: headTravel, unit: "mm" });
39
+
40
+ // ─── Colors ───
41
+ const C = {
42
+ frame: "#555555", bed: "#c45c1a", glass: "#6bb6ff",
43
+ carriage: "#888888", rail: "#c0c0c0", lead: "#999999",
44
+ belt: "#222222", motor: "#444444", electronics: "#666666",
45
+ screen: "#00ccff", nozzle: "#b87333", hotend: "#999999",
46
+ fan: "#333333", spool: "#dddddd", filament: "#cc4444",
47
+ buildVol: "#33ccff",
48
+ };
49
+
50
+ const parts = [];
51
+ const add = (name, shape, color) =>
52
+ parts.push({ name, shape: color ? shape.color(color) : shape });
53
+
54
+ // ─── Frame ───
55
+ const fp = [];
56
+ const bz = beam / 2;
57
+ const tz = frameH - beam / 2;
58
+ const pH = frameH - 2 * beam;
59
+ const pZ = beam + pH / 2;
60
+ const hw = frameW / 2 - beam / 2;
61
+ const hd = frameD / 2 - beam / 2;
62
+
63
+ for (const z of [bz, tz]) {
64
+ for (const sy of [-1, 1])
65
+ fp.push(box(frameW, beam, beam, true).translate(0, sy * hd, z));
66
+ for (const sx of [-1, 1])
67
+ fp.push(box(beam, frameD - 2 * beam, beam, true).translate(sx * hw, 0, z));
68
+ }
69
+ for (const sx of [-1, 1])
70
+ for (const sy of [-1, 1])
71
+ fp.push(box(beam, beam, pH, true).translate(sx * hw, sy * hd, pZ));
72
+
73
+ add("Frame", union(...fp), C.frame);
74
+
75
+ // ─── Z-Axis: rails + lead screws at rear corners ───
76
+ // Z rails only extend from bottom beam to just below gantry zone
77
+ const zRailInsetX = 25;
78
+ const zRailX = innerW / 2 - zRailInsetX;
79
+ const zRailY = innerD / 2 - 5; // flush with rear inner wall
80
+ const zTopClearance = gantryZ - 40; // stop well below gantry
81
+ const zLen = zTopClearance - beam - 10;
82
+ const zBase = beam + 10;
83
+
84
+ add("Z Rails", union(
85
+ cylinder(zLen, railR).translate(-zRailX, zRailY, zBase),
86
+ cylinder(zLen, railR).translate(zRailX, zRailY, zBase),
87
+ ), C.rail);
88
+
89
+ const leadY = zRailY - 10; // lead screws just forward of rails, but behind bed edge
90
+ add("Lead Screws", union(
91
+ cylinder(zLen - 10, 4).translate(-zRailX, leadY, zBase),
92
+ cylinder(zLen - 10, 4).translate(zRailX, leadY, zBase),
93
+ ), C.lead);
94
+
95
+ add("Z Motors", union(
96
+ box(42, 42, 40, true).translate(-zRailX, leadY, beam + 20),
97
+ box(42, 42, 40, true).translate(zRailX, leadY, beam + 20),
98
+ ), C.motor);
99
+
100
+ // ─── Bed Assembly (moves in Z) ───
101
+ const glassThick = 2;
102
+ const glassTopZ = bedZ;
103
+ const bedPlateTopZ = glassTopZ - glassThick;
104
+ const bedPlateCenterZ = bedPlateTopZ - bedThick / 2;
105
+
106
+ const bedPlate = box(bedW, bedD, bedThick, true).translate(0, 0, bedPlateCenterZ);
107
+ const glass = box(bedW - 6, bedD - 6, glassThick, true)
108
+ .translate(0, 0, bedPlateTopZ + glassThick / 2);
109
+
110
+ const carriageThick = 8;
111
+ const springH = 12;
112
+ const carriageZ = bedPlateCenterZ - bedThick / 2 - springH - carriageThick / 2;
113
+
114
+ // Carriage narrower in Y so it doesn't extend past the Z rail Y position
115
+ const bedCarriage = box(bedW + 20, bedD - 40, carriageThick, true)
116
+ .translate(0, 0, carriageZ);
117
+
118
+ add("Z Bearings", union(
119
+ box(24, 24, 16, true).translate(-zRailX, zRailY, carriageZ),
120
+ box(24, 24, 16, true).translate(zRailX, zRailY, carriageZ),
121
+ ), C.carriage);
122
+
123
+ add("Lead Nuts", union(
124
+ cylinder(8, 6).translate(-zRailX, leadY, carriageZ),
125
+ cylinder(8, 6).translate(zRailX, leadY, carriageZ),
126
+ ), C.lead);
127
+
128
+ const spX = bedW / 2 - 25;
129
+ const spY = bedD / 2 - 25;
130
+ const springs = [];
131
+ for (const sx of [-1, 1])
132
+ for (const sy of [-1, 1])
133
+ springs.push(cylinder(springH, 3).translate(
134
+ sx * spX, sy * spY, bedPlateCenterZ - bedThick / 2 - springH));
135
+
136
+ add("Bed Plate", bedPlate, C.bed);
137
+ add("Glass Surface", glass, C.glass);
138
+ add("Bed Carriage", bedCarriage, C.carriage);
139
+ add("Leveling Springs", union(...springs), C.lead);
140
+
141
+ // ─── XY Gantry ───
142
+ const yRailLen = innerD - 30;
143
+ const yRailX = innerW / 2 - 15;
144
+
145
+ add("Y Rails", union(
146
+ cylinder(yRailLen, railR).pointAlong([0, 1, 0]).translate(-yRailX, -yRailLen / 2, gantryZ),
147
+ cylinder(yRailLen, railR).pointAlong([0, 1, 0]).translate(yRailX, -yRailLen / 2, gantryZ),
148
+ ), C.rail);
149
+
150
+ add("Y Carriages", union(
151
+ box(28, 32, 18, true).translate(-yRailX, gantryY, gantryZ),
152
+ box(28, 32, 18, true).translate(yRailX, gantryY, gantryZ),
153
+ ), C.carriage);
154
+
155
+ const xRailLen = yRailX * 2 - 30;
156
+ add("X Rail", cylinder(xRailLen, railR).pointAlong([1, 0, 0])
157
+ .translate(-xRailLen / 2, gantryY, gantryZ), C.rail);
158
+
159
+ // X beam at gantry level — supports the X rail from behind
160
+ add("X Beam", box(xRailLen, beam, beam, true)
161
+ .translate(0, gantryY, gantryZ), C.frame);
162
+
163
+ // ─── CoreXY Belts ───
164
+ const beltZ = gantryZ + 22;
165
+ add("XY Belts", union(
166
+ box(xRailLen, 2, 3, true).translate(0, gantryY + 8, beltZ),
167
+ box(xRailLen, 2, 3, true).translate(0, gantryY - 8, beltZ),
168
+ box(2, yRailLen, 3, true).translate(-yRailX, 0, beltZ),
169
+ box(2, yRailLen, 3, true).translate(yRailX, 0, beltZ),
170
+ ), C.belt);
171
+
172
+ // XY motors inside frame at top-rear
173
+ const motorSize = 42;
174
+ const motorY = innerD / 2 - motorSize / 2 - 5;
175
+ add("XY Motors", union(
176
+ box(motorSize, motorSize, motorSize, true).translate(-yRailX, motorY, gantryZ + 30),
177
+ box(motorSize, motorSize, motorSize, true).translate(yRailX, motorY, gantryZ + 30),
178
+ ), C.motor);
179
+
180
+ add("XY Idlers", union(
181
+ cylinder(8, 6).pointAlong([0, 1, 0]).translate(-yRailX, -yRailLen / 2, beltZ),
182
+ cylinder(8, 6).pointAlong([0, 1, 0]).translate(yRailX, -yRailLen / 2, beltZ),
183
+ cylinder(8, 6).pointAlong([0, 1, 0]).translate(-yRailX, yRailLen / 2, beltZ),
184
+ cylinder(8, 6).pointAlong([0, 1, 0]).translate(yRailX, yRailLen / 2, beltZ),
185
+ ), C.lead);
186
+
187
+ // ─── Print Head / Extruder ───
188
+ const hx = headX;
189
+ const hy = gantryY;
190
+
191
+ add("Nozzle", cylinder(6, 0.4, 2).translate(hx, hy, nozzleZ), C.nozzle);
192
+ add("Heater Block", box(12, 12, 8, true).translate(hx, hy, nozzleZ + 10), C.hotend);
193
+ add("Heatbreak", cylinder(6, 1.5).translate(hx, hy, nozzleZ + 14), C.lead);
194
+ add("Heatsink", cylinder(12, 6).translate(hx, hy, nozzleZ + 22), C.hotend);
195
+
196
+ const headCarriage = box(46, 28, 36, true).translate(hx, hy, gantryZ - 18);
197
+ add("Extruder Carriage", headCarriage, C.carriage);
198
+
199
+ add("Part Fan", box(18, 8, 16, true)
200
+ .onFace(headCarriage, 'front', { v: -5, protrude: 5 }), C.fan);
201
+
202
+ // Extruder motor — compact, sits just above gantry plane
203
+ const extruderMotorZ = gantryZ + 5;
204
+ add("Extruder Motor", box(30, 30, 20, true)
205
+ .translate(hx, hy, extruderMotorZ), C.motor);
206
+
207
+ // ─── Spool Holder (behind frame, on top) ───
208
+ const spoolW = 60;
209
+ const spoolR = 35;
210
+ const spoolY = frameD / 2 + spoolR + 15;
211
+ const spoolZ = frameH;
212
+
213
+ const spoolRod = cylinder(spoolW + 20, 3).pointAlong([1, 0, 0])
214
+ .translate(-(spoolW + 20) / 2, spoolY, spoolZ);
215
+ add("Spool Rod", spoolRod, C.lead);
216
+
217
+ const spoolShell = cylinder(spoolW, spoolR)
218
+ .subtract(cylinder(spoolW + 2, spoolR - 4).translate(0, 0, -1))
219
+ .pointAlong([1, 0, 0])
220
+ .translate(-spoolW / 2, spoolY, spoolZ);
221
+ add("Spool Shell", spoolShell, C.spool);
222
+ add("Filament Roll", cylinder(spoolW - 4, spoolR - 6).pointAlong([1, 0, 0])
223
+ .translate(-spoolW / 2 + 2, spoolY, spoolZ), C.filament);
224
+ add("Spool Hub", cylinder(spoolW, 8).pointAlong([1, 0, 0])
225
+ .translate(-spoolW / 2, spoolY, spoolZ), C.lead);
226
+
227
+ add("Spool Supports", union(
228
+ box(10, 20, 50, true).translate(-spoolW / 2 - 15, frameD / 2 - beam / 2, frameH - 25),
229
+ box(10, 20, 50, true).translate(spoolW / 2 + 15, frameD / 2 - beam / 2, frameH - 25),
230
+ ), C.frame);
231
+
232
+ // ─── Filament Path ───
233
+ // Simple path: spool → over rear frame beam → down into extruder
234
+ const filR = 1.5;
235
+ const filBendR = 30;
236
+
237
+ // Guide tube: spool to top of frame (fixed portion)
238
+ const filFixedPath = lib.pipeRoute(
239
+ [
240
+ [0, spoolY, spoolZ], // spool center
241
+ [0, frameD / 2 - beam, frameH + 20], // up and forward over rear beam
242
+ [0, 0, frameH + 20], // across top to center
243
+ [hx, hy, extruderMotorZ + 12], // straight to extruder motor top
244
+ ],
245
+ filR,
246
+ { bendRadius: 25, wall: 0.4, segments: 16 }
247
+ );
248
+ add("Filament Guide Tube", filFixedPath, C.bowden);
249
+
250
+ // ─── Electronics ───
251
+ add("PSU", box(100, 60, 40, true)
252
+ .translate(frameW / 2 - 55, -frameD / 2 + 35, 25), C.electronics);
253
+
254
+ add("Control Board", box(70, 40, 25, true)
255
+ .translate(-frameW / 2 + 40, -frameD / 2 + 25, 18), C.electronics);
256
+
257
+ add("Display", box(50, 3, 25, true)
258
+ .translate(-frameW / 2 + 40, -frameD / 2 + 1, frameH / 3), C.screen);
259
+
260
+ // ─── Build Volume wireframe ───
261
+ const buildH = Math.max(20, nozzleZ - bedTopMin - 5);
262
+ const bvr = 0.6;
263
+ const bvW = bedW - 10, bvD = bedD - 10;
264
+ const bvEdges = [];
265
+ const x0 = -bvW / 2, x1 = bvW / 2;
266
+ const y0 = -bvD / 2, y1 = bvD / 2;
267
+ const z0b = bedTopMin + 5, z1b = z0b + buildH;
268
+ for (const y of [y0, y1]) for (const z of [z0b, z1b])
269
+ bvEdges.push(cylinder(bvW, bvr).pointAlong([1, 0, 0]).translate(x0, y, z));
270
+ for (const x of [x0, x1]) for (const z of [z0b, z1b])
271
+ bvEdges.push(cylinder(bvD, bvr).pointAlong([0, 1, 0]).translate(x, y0, z));
272
+ for (const x of [x0, x1]) for (const y of [y0, y1])
273
+ bvEdges.push(cylinder(buildH, bvr).translate(x, y, z0b));
274
+
275
+ add("Build Volume", union(...bvEdges), C.buildVol);
276
+
277
+ // ─── Cut Planes ───
278
+ cutPlane("Front Section", [0, -1, 0], 0);
279
+ cutPlane("Side Section", [1, 0, 0], 0);
280
+ cutPlane("Top Section", [0, 0, 1], gantryZ);
281
+
282
+ // Assembly groups — intentional overlaps within groups are not flagged
283
+ return [
284
+ { name: "Structure", group: [
285
+ parts.find(p => p.name === "Frame"),
286
+ parts.find(p => p.name === "Spool Supports"),
287
+ parts.find(p => p.name === "PSU"),
288
+ parts.find(p => p.name === "Control Board"),
289
+ parts.find(p => p.name === "Display"),
290
+ ]},
291
+ { name: "Z Axis", group: [
292
+ parts.find(p => p.name === "Z Rails"),
293
+ parts.find(p => p.name === "Lead Screws"),
294
+ parts.find(p => p.name === "Z Motors"),
295
+ parts.find(p => p.name === "Z Bearings"),
296
+ parts.find(p => p.name === "Lead Nuts"),
297
+ ]},
298
+ { name: "Bed Assembly", group: [
299
+ parts.find(p => p.name === "Bed Plate"),
300
+ parts.find(p => p.name === "Glass Surface"),
301
+ parts.find(p => p.name === "Bed Carriage"),
302
+ parts.find(p => p.name === "Leveling Springs"),
303
+ ]},
304
+ { name: "XY Gantry + Head", group: [
305
+ parts.find(p => p.name === "Y Rails"),
306
+ parts.find(p => p.name === "Y Carriages"),
307
+ parts.find(p => p.name === "X Rail"),
308
+ parts.find(p => p.name === "X Beam"),
309
+ parts.find(p => p.name === "XY Belts"),
310
+ parts.find(p => p.name === "XY Motors"),
311
+ parts.find(p => p.name === "XY Idlers"),
312
+ parts.find(p => p.name === "Nozzle"),
313
+ parts.find(p => p.name === "Heater Block"),
314
+ parts.find(p => p.name === "Heatbreak"),
315
+ parts.find(p => p.name === "Heatsink"),
316
+ parts.find(p => p.name === "Extruder Carriage"),
317
+ parts.find(p => p.name === "Part Fan"),
318
+ parts.find(p => p.name === "Extruder Motor"),
319
+ ]},
320
+ { name: "Spool + Filament", group: [
321
+ parts.find(p => p.name === "Spool Rod"),
322
+ parts.find(p => p.name === "Spool Shell"),
323
+ parts.find(p => p.name === "Filament Roll"),
324
+ parts.find(p => p.name === "Spool Hub"),
325
+ parts.find(p => p.name === "Filament Guide Tube"),
326
+ ]},
327
+ parts.find(p => p.name === "Build Volume"),
328
+ ];
@@ -0,0 +1,283 @@
1
+ // Robot Hand — functional, buildable concept
2
+ // Z-up, Y depth (front = -Y)
3
+
4
+ const scale = param("Scale", 1.0, { min: 0.7, max: 1.3, step: 0.05 });
5
+ const curl = param("Finger Curl", 40, { min: 0, max: 70, unit: "°" });
6
+ const thumbCurl = param("Thumb Curl", 35, { min: 0, max: 70, unit: "°" });
7
+ const spread = param("Finger Spread", 8, { min: 0, max: 20, unit: "°" });
8
+
9
+ // --- Dimensions ---
10
+ const palmW = 90 * scale;
11
+ const palmD = 70 * scale;
12
+ const palmH = 18 * scale;
13
+ const fingerT = 12 * scale;
14
+ const gap = 2 * scale;
15
+
16
+ const baseY = -palmD / 2 - gap;
17
+ const baseZ = palmH / 2 + fingerT / 2 + 2 * scale;
18
+
19
+ // --- Colors ---
20
+ const colors = {
21
+ palm: '#d8cbb2',
22
+ palmPad: '#2a2a2a',
23
+ knuckle: '#aaaaaa',
24
+ wrist: '#666666',
25
+ motor: '#444444',
26
+ spool: '#cc9933',
27
+ segment: '#e6e6e6',
28
+ pad: '#222222',
29
+ pin: '#9aa1a8',
30
+ tendon: '#c9a227',
31
+ cable: '#999999',
32
+ };
33
+
34
+ // --- Helper functions ---
35
+ function rotX(p, pivot, deg) {
36
+ const rad = deg * Math.PI / 180;
37
+ const cos = Math.cos(rad);
38
+ const sin = Math.sin(rad);
39
+ const x = p[0] - pivot[0];
40
+ const y = p[1] - pivot[1];
41
+ const z = p[2] - pivot[2];
42
+ const y2 = y * cos - z * sin;
43
+ const z2 = y * sin + z * cos;
44
+ return [pivot[0] + x, pivot[1] + y2, pivot[2] + z2];
45
+ }
46
+
47
+ function centerOf(shape) {
48
+ const bb = shape.boundingBox();
49
+ return [
50
+ (bb.min[0] + bb.max[0]) / 2,
51
+ (bb.min[1] + bb.max[1]) / 2,
52
+ (bb.min[2] + bb.max[2]) / 2,
53
+ ];
54
+ }
55
+
56
+ // --- Finger builder ---
57
+ function makeFinger(opts) {
58
+ const {
59
+ name,
60
+ base,
61
+ lengths,
62
+ widths,
63
+ thickness,
64
+ angles,
65
+ yaw = 0,
66
+ colors,
67
+ } = opts;
68
+
69
+ const [L1, L2, L3] = lengths;
70
+ const [W1, W2, W3] = widths;
71
+ const t = thickness;
72
+ const [a1, a2, a3] = angles;
73
+
74
+ const basePivot = base;
75
+ const midPivot0 = [basePivot[0], basePivot[1] - L1, basePivot[2]];
76
+ const tipPivot0 = [basePivot[0], basePivot[1] - L1 - L2, basePivot[2]];
77
+
78
+ // Straight segments in world space
79
+ let s1 = box(W1, L1, t, true)
80
+ .translate(basePivot[0], basePivot[1] - L1 / 2, basePivot[2])
81
+ .color(colors.segment);
82
+ let s2 = box(W2, L2, t, true)
83
+ .translate(basePivot[0], basePivot[1] - L1 - L2 / 2, basePivot[2])
84
+ .color(colors.segment);
85
+ let s3 = box(W3, L3, t, true)
86
+ .translate(basePivot[0], basePivot[1] - L1 - L2 - L3 / 2, basePivot[2])
87
+ .color(colors.segment);
88
+
89
+ // Pads (rubber contact)
90
+ const padT = t * 0.18;
91
+ let pad1 = box(W1 * 0.7, L1 * 0.45, padT, true)
92
+ .color(colors.pad)
93
+ .onFace(s1, 'bottom', { v: -L1 * 0.15, protrude: padT / 2 });
94
+ let pad2 = box(W2 * 0.65, L2 * 0.45, padT, true)
95
+ .color(colors.pad)
96
+ .onFace(s2, 'bottom', { v: -L2 * 0.15, protrude: padT / 2 });
97
+ let pad3 = box(W3 * 0.8, L3 * 0.6, padT, true)
98
+ .color(colors.pad)
99
+ .onFace(s3, 'bottom', { v: -L3 * 0.1, protrude: padT / 2 });
100
+
101
+ // Tendon guides (raised top rails)
102
+ const rodT = t * 0.12;
103
+ let rod1 = box(W1 * 0.2, L1 * 0.9, rodT, true)
104
+ .color(colors.tendon)
105
+ .onFace(s1, 'top', { v: -L1 * 0.05, protrude: rodT / 2 });
106
+ let rod2 = box(W2 * 0.2, L2 * 0.9, rodT, true)
107
+ .color(colors.tendon)
108
+ .onFace(s2, 'top', { v: -L2 * 0.05, protrude: rodT / 2 });
109
+ let rod3 = box(W3 * 0.2, L3 * 0.85, rodT, true)
110
+ .color(colors.tendon)
111
+ .onFace(s3, 'top', { v: -L3 * 0.05, protrude: rodT / 2 });
112
+
113
+ // Pivots after rotation
114
+ const midPivot1 = rotX(midPivot0, basePivot, a1);
115
+ const tipPivot1 = rotX(tipPivot0, basePivot, a1);
116
+ const tipPivot2 = rotX(tipPivot1, midPivot1, a2);
117
+
118
+ const seg1 = (shape) => shape.rotateAround([1, 0, 0], a1, basePivot);
119
+ const seg2 = (shape) => shape
120
+ .rotateAround([1, 0, 0], a1, basePivot)
121
+ .rotateAround([1, 0, 0], a2, midPivot1);
122
+ const seg3 = (shape) => shape
123
+ .rotateAround([1, 0, 0], a1, basePivot)
124
+ .rotateAround([1, 0, 0], a2, midPivot1)
125
+ .rotateAround([1, 0, 0], a3, tipPivot2);
126
+
127
+ s1 = seg1(s1); s2 = seg2(s2); s3 = seg3(s3);
128
+ pad1 = seg1(pad1); pad2 = seg2(pad2); pad3 = seg3(pad3);
129
+ rod1 = seg1(rod1); rod2 = seg2(rod2); rod3 = seg3(rod3);
130
+
131
+ // Joint pins
132
+ const pinLen = Math.max(W1, W2, W3) + 8 * scale;
133
+ const pinR = t * 0.15;
134
+ const pinBase = cylinder(pinLen, pinR).pointAlong([1, 0, 0]).color(colors.pin);
135
+ let pin1 = pinBase.translate(basePivot[0] - pinLen / 2, basePivot[1], basePivot[2]);
136
+ let pin2 = pinBase.translate(midPivot1[0] - pinLen / 2, midPivot1[1], midPivot1[2]);
137
+ let pin3 = pinBase.translate(tipPivot2[0] - pinLen / 2, tipPivot2[1], tipPivot2[2]);
138
+
139
+ // Yoke block at base
140
+ let yoke = box(W1 * 1.1, 6 * scale, t * 0.8, true)
141
+ .translate(basePivot[0], basePivot[1] + 3 * scale, basePivot[2])
142
+ .color(colors.knuckle);
143
+ yoke = seg1(yoke);
144
+
145
+ // Apply yaw (splay)
146
+ if (yaw !== 0) {
147
+ const yawRot = (shape) => shape.rotateAround([0, 0, 1], yaw, basePivot);
148
+ s1 = yawRot(s1); s2 = yawRot(s2); s3 = yawRot(s3);
149
+ pad1 = yawRot(pad1); pad2 = yawRot(pad2); pad3 = yawRot(pad3);
150
+ rod1 = yawRot(rod1); rod2 = yawRot(rod2); rod3 = yawRot(rod3);
151
+ pin1 = yawRot(pin1); pin2 = yawRot(pin2); pin3 = yawRot(pin3);
152
+ yoke = yawRot(yoke);
153
+ }
154
+
155
+ return {
156
+ name,
157
+ group: [
158
+ { name: `${name} Prox`, shape: s1 },
159
+ { name: `${name} Mid`, shape: s2 },
160
+ { name: `${name} Tip`, shape: s3 },
161
+ { name: `${name} Pad 1`, shape: pad1 },
162
+ { name: `${name} Pad 2`, shape: pad2 },
163
+ { name: `${name} Pad 3`, shape: pad3 },
164
+ { name: `${name} Tendon 1`, shape: rod1 },
165
+ { name: `${name} Tendon 2`, shape: rod2 },
166
+ { name: `${name} Tendon 3`, shape: rod3 },
167
+ { name: `${name} Pin Base`, shape: pin1 },
168
+ { name: `${name} Pin Mid`, shape: pin2 },
169
+ { name: `${name} Pin Tip`, shape: pin3 },
170
+ { name: `${name} Yoke`, shape: yoke },
171
+ ],
172
+ };
173
+ }
174
+
175
+ // --- Palm assembly ---
176
+ const palm = box(palmW, palmD, palmH, true).color(colors.palm);
177
+ const palmPad = box(palmW * 0.6, 3 * scale, palmH * 0.5, true)
178
+ .color(colors.palmPad)
179
+ .onFace(palm, 'front', { v: -palmH * 0.05, protrude: 1 * scale });
180
+
181
+ const knuckleBar = box(palmW * 0.9, 10 * scale, 6 * scale, true)
182
+ .color(colors.knuckle)
183
+ .onFace(palm, 'top', { v: -palmD / 2 + 8 * scale, protrude: 3 * scale });
184
+
185
+ const wristLen = 60 * scale;
186
+ const wristR = 14 * scale;
187
+ const wrist = cylinder(wristLen, wristR)
188
+ .pointAlong([0, 1, 0])
189
+ .color(colors.wrist)
190
+ .attachTo(palm, 'back', 'front', [0, 0, -palmH * 0.2]);
191
+
192
+ const motorW = 40 * scale;
193
+ const motorD = 28 * scale;
194
+ const motorH = 20 * scale;
195
+ const motor = box(motorW, motorD, motorH, true)
196
+ .color(colors.motor)
197
+ .attachTo(palm, 'bottom', 'top', [0, palmD / 2 - motorD / 2 - 4 * scale, -2 * scale]);
198
+
199
+ const spoolLen = palmW * 0.7;
200
+ const spoolR = 6 * scale;
201
+ const spool = cylinder(spoolLen, spoolR)
202
+ .pointAlong([1, 0, 0])
203
+ .color(colors.spool)
204
+ .attachTo(motor, 'top', 'bottom', [0, -motorD * 0.25, spoolR * 0.2]);
205
+
206
+ // --- Fingers ---
207
+ const fingerDefs = [
208
+ { name: 'Index', x: -palmW * 0.28, lengths: [32, 24, 18], widths: [16, 14, 13], yaw: -spread * 0.4 },
209
+ { name: 'Middle', x: -palmW * 0.08, lengths: [35, 26, 20], widths: [17, 15, 14], yaw: -spread * 0.15 },
210
+ { name: 'Ring', x: palmW * 0.12, lengths: [33, 24, 19], widths: [16, 14, 13], yaw: spread * 0.2 },
211
+ { name: 'Pinky', x: palmW * 0.30, lengths: [28, 20, 16], widths: [14, 12, 11], yaw: spread * 0.45 },
212
+ ];
213
+
214
+ const fingerGroups = fingerDefs.map(def => makeFinger({
215
+ name: def.name,
216
+ base: [def.x, baseY, baseZ],
217
+ lengths: def.lengths.map(v => v * scale),
218
+ widths: def.widths.map(v => v * scale),
219
+ thickness: fingerT,
220
+ angles: [curl * 0.6, curl * 0.9, curl * 1.1],
221
+ yaw: def.yaw,
222
+ colors,
223
+ }));
224
+
225
+ // Thumb (angled, shorter)
226
+ const thumb = makeFinger({
227
+ name: 'Thumb',
228
+ base: [-palmW * 0.45, -palmD * 0.1, baseZ - fingerT * 0.2],
229
+ lengths: [26, 18, 14].map(v => v * scale),
230
+ widths: [18, 16, 14].map(v => v * scale),
231
+ thickness: fingerT * 0.9,
232
+ angles: [thumbCurl * 0.6, thumbCurl * 0.8, thumbCurl * 0.9],
233
+ yaw: -50,
234
+ colors,
235
+ });
236
+
237
+ // --- Tendon cables from spool to finger bases ---
238
+ const spoolCenter = centerOf(spool);
239
+ const spoolBB = spool.boundingBox();
240
+ const cableR = 1.2 * scale;
241
+ const cableTopZ = palmH / 2 + fingerT + 20 * scale;
242
+
243
+ const fingerBases = [
244
+ ...fingerDefs.map(def => [def.x, baseY, baseZ]),
245
+ [-palmW * 0.45, -palmD * 0.1, baseZ - fingerT * 0.2],
246
+ ];
247
+
248
+ const cableCount = fingerBases.length;
249
+ const cables = fingerBases.map((end, i) => {
250
+ const t = (i + 1) / (cableCount + 1);
251
+ const startX = spoolBB.min[0] + t * (spoolBB.max[0] - spoolBB.min[0]);
252
+ const start = [startX, spoolCenter[1], spoolCenter[2] + spoolR * 0.8];
253
+ const midBack = [startX, palmD / 2 + 6 * scale, cableTopZ];
254
+ const midPalm = [end[0], 0, cableTopZ];
255
+ const endPt = [end[0], end[1] + 6 * scale, end[2] + fingerT * 0.6];
256
+
257
+ return lib.pipeRoute([start, midBack, midPalm, endPt], cableR, { bendRadius: 10 * scale })
258
+ .color(colors.cable);
259
+ });
260
+
261
+ // --- Grasp target object ---
262
+ const graspObject = hull3d(
263
+ sphere(18 * scale).translate(0, baseY - 45 * scale, baseZ + 6 * scale),
264
+ box(32 * scale, 20 * scale, 26 * scale, true).translate(12 * scale, baseY - 65 * scale, baseZ + 2 * scale)
265
+ ).color('#88ccee');
266
+
267
+ // --- Return scene ---
268
+ return [
269
+ {
270
+ name: 'Palm Assembly', group: [
271
+ { name: 'Palm', shape: palm },
272
+ { name: 'Palm Grip', shape: palmPad },
273
+ { name: 'Knuckle Bar', shape: knuckleBar },
274
+ { name: 'Wrist', shape: wrist },
275
+ { name: 'Motor', shape: motor },
276
+ { name: 'Spool', shape: spool },
277
+ ...cables.map((c, i) => ({ name: `Cable ${i + 1}`, shape: c })),
278
+ ]
279
+ },
280
+ ...fingerGroups,
281
+ thumb,
282
+ // { name: 'Grasp Object', shape: graspObject },
283
+ ];