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,126 @@
1
+ // Home AC Unit — Indoor and Outdoor on opposite sides of a wall
2
+ // Strategy: build each part at origin, then attachTo() its parent.
3
+
4
+ // === Parameters ===
5
+ const wallW = param("Wall Width", 400, { min: 200, max: 600, unit: "mm" });
6
+ const wallH = param("Wall Height", 300, { min: 200, max: 500, unit: "mm" });
7
+ const wallT = param("Wall Thickness", 20, { min: 10, max: 50, unit: "mm" });
8
+
9
+ const inW = param("Indoor Width", 200, { min: 100, max: 400, unit: "mm" });
10
+ const inH = param("Indoor Height", 80, { min: 40, max: 150, unit: "mm" });
11
+ const inD = param("Indoor Depth", 25, { min: 15, max: 60, unit: "mm" });
12
+
13
+ const outW = param("Outdoor Width", 180, { min: 100, max: 400, unit: "mm" });
14
+ const outH = param("Outdoor Height", 150, { min: 80, max: 300, unit: "mm" });
15
+ const outD = param("Outdoor Depth", 50, { min: 30, max: 100, unit: "mm" });
16
+
17
+ // === Colors ===
18
+ const C_WALL = '#D4C4A8';
19
+ const C_INDOOR = '#FFFFFF';
20
+ const C_ACCENT = '#E8E8E8';
21
+ const C_OUTDOOR = '#F5F5F5';
22
+ const C_GRILL = '#404040';
23
+ const C_PIPE = '#A0A0A0';
24
+
25
+ // === Wall (centered at origin) ===
26
+ const wall = box(wallW, wallT, wallH, true).color(C_WALL);
27
+
28
+ // === Indoor Unit ===
29
+ // Sits on the front face of the wall (-Y side), near the ceiling.
30
+ // Build at origin, then attach: indoor's back face → wall's front face, then offset up and out.
31
+ const indoorBody = box(inW, inD, inH, true).color(C_INDOOR)
32
+ .attachTo(wall, 'top-front', 'top-back', [0, -5, -20]);
33
+ // back of indoor touches front of wall, shifted 5mm away from wall, 20mm below ceiling
34
+
35
+ // Vent slats — protrude from the FRONT face of the indoor unit
36
+ const slatCount = 4;
37
+ const slats = [];
38
+ for (let i = 0; i < slatCount; i++) {
39
+ const slat = box(inW - 30, 8, 3, true).color(C_ACCENT)
40
+ .attachTo(indoorBody, 'bottom-front', 'bottom-back', [0, -1, 8 + i * 12]);
41
+ // back of slat on front of indoor, 1mm gap, stacked upward from bottom
42
+ slats.push(slat);
43
+ }
44
+
45
+ // Control panel — sticks out from front-right area
46
+ const panel = box(35, 4, 18, true).color('#1A1A1A')
47
+ .attachTo(indoorBody, 'top-front-right', 'top-back-right', [5, -1, -15]);
48
+
49
+ // LED
50
+ const led = sphere(2.5).color('#00FF00')
51
+ .attachTo(panel, 'front', 'center', [5, -2, -5]);
52
+
53
+ // === Outdoor Unit ===
54
+ // Sits on the back face of the wall (+Y side), near the ground.
55
+ const outdoorBody = box(outW, outD, outH, true).color(C_OUTDOOR)
56
+ .attachTo(wall, 'bottom-back', 'bottom-front', [0, 10, 30]);
57
+ // front of outdoor touches back of wall, 10mm standoff, 30mm above ground
58
+
59
+ // Fan grille — on the back face (+Y) of outdoor unit, centered
60
+ const grilleR = Math.min(outW, outH) * 0.35;
61
+ const fanRim = cylinder(4, grilleR).color(C_GRILL)
62
+ .pointAlong([0, 1, 0]) // lay along +Y
63
+ .attachTo(outdoorBody, 'back', 'front', [0, 2, 0]);
64
+
65
+ const fanCenter = cylinder(2, grilleR * 0.4).color('#505050')
66
+ .pointAlong([0, 1, 0])
67
+ .attachTo(outdoorBody, 'back', 'front', [0, 3, 0]);
68
+
69
+ // Cooling fins — protrude from left and right sides
70
+ const finCount = 8;
71
+ const fins = [];
72
+ const outdoorBB = outdoorBody.boundingBox();
73
+ const finSpacing = (outH - 40) / finCount;
74
+ for (let i = 0; i < finCount; i++) {
75
+ const zOff = -outH / 2 + 20 + i * finSpacing;
76
+ // Left fin: right face of fin on left face of outdoor
77
+ const leftFin = box(5, 12, 3, true).color(C_GRILL)
78
+ .attachTo(outdoorBody, 'left', 'right', [0, -10, zOff]);
79
+ // Right fin: left face of fin on right face of outdoor
80
+ const rightFin = box(5, 12, 3, true).color(C_GRILL)
81
+ .attachTo(outdoorBody, 'right', 'left', [0, -10, zOff]);
82
+ fins.push(leftFin, rightFin);
83
+ }
84
+
85
+ // === Refrigerant Pipes ===
86
+ // Connect from back of indoor unit through wall to front of outdoor unit.
87
+ // Pipe runs along Y axis.
88
+ const indoorBB = indoorBody.boundingBox();
89
+ const pipeStartY = indoorBB.max[1]; // back of indoor (wall side)
90
+ const pipeEndY = outdoorBB.min[1]; // front of outdoor (wall side)
91
+ const pipeLen = pipeEndY - pipeStartY;
92
+ const pipeZ = indoorBB.min[2] + 15; // near bottom of indoor unit
93
+ const pipeX = 40;
94
+
95
+ // cylinder after pointAlong([0,1,0]) spans Y=0..pipeLen, so translate to pipeStartY
96
+ const liquidPipe = cylinder(pipeLen, 3.5).color(C_PIPE)
97
+ .pointAlong([0, 1, 0])
98
+ .translate(pipeX, pipeStartY, pipeZ);
99
+
100
+ const gasPipe = cylinder(pipeLen, 5).color(C_PIPE)
101
+ .pointAlong([0, 1, 0])
102
+ .translate(pipeX + 15, pipeStartY, pipeZ);
103
+
104
+ // Insulation at outdoor connection
105
+ const insLiquid = cylinder(12, 7).color('#333333')
106
+ .pointAlong([0, 1, 0])
107
+ .translate(pipeX, pipeEndY - 12, pipeZ);
108
+
109
+ const insGas = cylinder(12, 10).color('#333333')
110
+ .pointAlong([0, 1, 0])
111
+ .translate(pipeX + 15, pipeEndY - 12, pipeZ);
112
+
113
+ // === Return ===
114
+ return [
115
+ { name: "Wall", shape: wall },
116
+ { name: "Indoor Unit", shape: indoorBody },
117
+ { name: "Vent Slats", shape: union(...slats) },
118
+ { name: "Control Panel", shape: panel },
119
+ { name: "LED", shape: led },
120
+ { name: "Outdoor Unit", shape: outdoorBody },
121
+ { name: "Fan Grille", shape: union(fanRim, fanCenter) },
122
+ { name: "Cooling Fins", shape: union(...fins) },
123
+ { name: "Liquid Pipe", shape: liquidPipe },
124
+ { name: "Gas Pipe", shape: gasPipe },
125
+ { name: "Pipe Insulation", shape: union(insLiquid, insGas) },
126
+ ];
@@ -0,0 +1,191 @@
1
+ // Adjustable Height Table — manual crank mechanism
2
+ // Shows real construction details: telescoping legs, cross braces,
3
+ // crank shaft, and tabletop.
4
+ //
5
+ // BUILD NOTES (at bottom of file):
6
+ // Materials, dimensions, and assembly instructions for actually building this.
7
+
8
+ const topW = param("Top Width", 120, { min: 80, max: 200, unit: "cm" });
9
+ const topD = param("Top Depth", 60, { min: 40, max: 100, unit: "cm" });
10
+ const topThick = param("Top Thickness", 3, { min: 2, max: 5, unit: "cm" });
11
+ const minH = param("Min Height", 72, { min: 60, max: 80, unit: "cm" });
12
+ const maxH = param("Max Height", 120, { min: 100, max: 140, unit: "cm" });
13
+ const heightPct = param("Height %", 30, { min: 0, max: 100, unit: "%" });
14
+ const outerLeg = param("Outer Leg", 6, { min: 4, max: 10, unit: "cm" });
15
+ const innerLeg = param("Inner Leg", 4, { min: 3, max: 8, unit: "cm" });
16
+ const legWall = param("Leg Wall", 0.3, { min: 0.2, max: 0.5, unit: "cm" });
17
+ const inset = param("Leg Inset", 5, { min: 2, max: 15, unit: "cm" });
18
+ const braceW = param("Brace Width", 3, { min: 2, max: 5, unit: "cm" });
19
+ const braceH = param("Brace Height", 1.5, { min: 1, max: 3, unit: "cm" });
20
+ const crankR = param("Crank Radius", 8, { min: 5, max: 15, unit: "cm" });
21
+
22
+ // Current height based on slider
23
+ const currentH = minH + (maxH - minH) * heightPct / 100;
24
+ const legH = currentH - topThick;
25
+ const extensionH = legH - minH + topThick; // how much inner leg extends
26
+
27
+ // --- Tabletop ---
28
+ const top = box(topW, topD, topThick).translate(0, 0, legH);
29
+
30
+ // --- Outer legs (fixed, bolted to frame under tabletop) ---
31
+ // These are square tubes — outer shell minus inner hollow
32
+ const outerLegH = minH - topThick; // fixed portion height
33
+
34
+ const makeOuterLeg = (x, y) => {
35
+ const outer = box(outerLeg, outerLeg, outerLegH).translate(x, y, legH - outerLegH);
36
+ const inner = box(outerLeg - legWall * 2, outerLeg - legWall * 2, outerLegH + 1)
37
+ .translate(x + legWall, y + legWall, legH - outerLegH - 0.5);
38
+ return outer.subtract(inner);
39
+ };
40
+
41
+ // --- Inner legs (slide inside outer, extend downward) ---
42
+ const makeInnerLeg = (x, y) => {
43
+ const legOffset = (outerLeg - innerLeg) / 2;
44
+ const inner = box(innerLeg, innerLeg, legH)
45
+ .translate(x + legOffset, y + legOffset, 0);
46
+ const hollow = box(innerLeg - legWall * 2, innerLeg - legWall * 2, legH + 1)
47
+ .translate(x + legOffset + legWall, y + legOffset + legWall, -0.5);
48
+ return inner.subtract(hollow);
49
+ };
50
+
51
+ // Leg positions
52
+ const legPositions = [
53
+ [inset, inset],
54
+ [topW - inset - outerLeg, inset],
55
+ [inset, topD - inset - outerLeg],
56
+ [topW - inset - outerLeg, topD - inset - outerLeg],
57
+ ];
58
+
59
+ const outerLegs = union(...legPositions.map(([x, y]) => makeOuterLeg(x, y)));
60
+ const innerLegs = union(...legPositions.map(([x, y]) => makeInnerLeg(x, y)));
61
+
62
+ // --- Locking pin holes (show where pins go through both tubes) ---
63
+ // Holes at multiple heights for discrete height adjustment
64
+ const pinHoleR = 0.4;
65
+ const pinSpacing = 5; // every 5cm
66
+ const pinHoles = [];
67
+ const numPins = Math.floor((maxH - minH) / pinSpacing);
68
+
69
+ for (const [lx, ly] of legPositions) {
70
+ const legCenterX = lx + outerLeg / 2;
71
+ const legCenterY = ly + outerLeg / 2;
72
+ for (let i = 0; i <= numPins; i++) {
73
+ const pz = legH - outerLegH + 5 + i * pinSpacing;
74
+ if (pz > 0 && pz < legH) {
75
+ // Through-hole in Y direction
76
+ pinHoles.push(
77
+ cylinder(outerLeg + 2, pinHoleR)
78
+ .rotate(90, 0, 0)
79
+ .translate(legCenterX, ly - 1, pz)
80
+ );
81
+ }
82
+ }
83
+ }
84
+
85
+ // --- Cross braces (connect legs for rigidity) ---
86
+ // Front brace
87
+ const frontBraceLen = topW - 2 * inset - 2 * outerLeg;
88
+ const frontBrace = box(frontBraceLen, braceW, braceH)
89
+ .translate(inset + outerLeg, inset + outerLeg / 2 - braceW / 2, legH - outerLegH + 10);
90
+
91
+ // Back brace
92
+ const backBrace = box(frontBraceLen, braceW, braceH)
93
+ .translate(inset + outerLeg, topD - inset - outerLeg / 2 - braceW / 2, legH - outerLegH + 10);
94
+
95
+ // Side braces
96
+ const sideBraceLen = topD - 2 * inset - 2 * outerLeg;
97
+ const leftBrace = box(braceW, sideBraceLen, braceH)
98
+ .translate(inset + outerLeg / 2 - braceW / 2, inset + outerLeg, legH - outerLegH + 10);
99
+ const rightBrace = box(braceW, sideBraceLen, braceH)
100
+ .translate(topW - inset - outerLeg / 2 - braceW / 2, inset + outerLeg, legH - outerLegH + 10);
101
+
102
+ // --- Crank mechanism (side-mounted, visual representation) ---
103
+ // Crank shaft runs between front legs
104
+ const shaftY = inset + outerLeg / 2;
105
+ const shaftZ = legH - outerLegH + 25;
106
+ const shaftLen = topW - 2 * inset;
107
+ const shaft = cylinder(shaftLen, 0.8)
108
+ .rotate(0, 90, 0)
109
+ .translate(inset, shaftY, shaftZ);
110
+
111
+ // Crank handle (on the right side)
112
+ const handleX = topW - inset + 2;
113
+ const crankArm = cylinder(crankR, 0.5)
114
+ .translate(handleX, shaftY, shaftZ);
115
+ const crankKnob = sphere(1.2)
116
+ .translate(handleX, shaftY, shaftZ + crankR);
117
+
118
+ // --- Foot pads (rubber feet at bottom of inner legs) ---
119
+ const footPads = union(
120
+ ...legPositions.map(([lx, ly]) => {
121
+ const cx = lx + outerLeg / 2;
122
+ const cy = ly + outerLeg / 2;
123
+ return cylinder(0.5, innerLeg / 2 + 0.5)
124
+ .translate(cx, cy, 0);
125
+ })
126
+ );
127
+
128
+ // --- Assembly ---
129
+ let structure = union(outerLegs, innerLegs, frontBrace, backBrace, leftBrace, rightBrace);
130
+ if (pinHoles.length > 0) {
131
+ structure = structure.subtract(union(...pinHoles));
132
+ }
133
+
134
+ return [
135
+ { name: "Tabletop", shape: top, color: "#8B7355" },
136
+ { name: "Leg Structure", shape: structure, color: "#888888" },
137
+ { name: "Crank Shaft", shape: shaft, color: "#666666" },
138
+ { name: "Crank Arm", shape: crankArm, color: "#555555" },
139
+ { name: "Crank Knob", shape: crankKnob, color: "#444444" },
140
+ { name: "Foot Pads", shape: footPads, color: "#333333" },
141
+ ];
142
+
143
+ // ============================================================
144
+ // BUILD NOTES — How to actually build this table
145
+ // ============================================================
146
+ //
147
+ // MATERIALS:
148
+ // - Tabletop: 18mm plywood or solid wood panel (120×60cm)
149
+ // - Outer legs: 60×60mm square steel tube, 2mm wall (×4, cut to ~69cm)
150
+ // - Inner legs: 40×40mm square steel tube, 2mm wall (×4, cut to ~120cm)
151
+ // - Cross braces: 30×15mm steel flat bar (×4)
152
+ // - Crank shaft: 16mm steel rod, threaded ends
153
+ // - Locking pins: 8mm spring pins or clevis pins
154
+ // - Foot pads: rubber furniture feet (40mm)
155
+ // - Hardware: M8 bolts for frame, M6 for braces
156
+ //
157
+ // ASSEMBLY:
158
+ // 1. Cut all steel tubes to length. Deburr edges.
159
+ // 2. Weld or bolt cross braces to outer leg tubes at 10cm from top.
160
+ // 3. Slide inner legs into outer legs from below.
161
+ // 4. Drill pin holes through both tubes at 5cm intervals.
162
+ // Use a drill press for alignment. Start from bottom.
163
+ // 5. Mount outer leg assembly to underside of tabletop with
164
+ // L-brackets and M8 bolts (4 per leg).
165
+ // 6. Thread crank shaft through front legs. Add bearing blocks
166
+ // or bushings at each leg pass-through.
167
+ // 7. Attach crank handle to shaft end.
168
+ // 8. For the crank-to-leg connection: weld a small gear or
169
+ // threaded collar to the shaft at each inner leg position.
170
+ // The inner leg needs a matching rack or threaded insert.
171
+ // SIMPLER ALTERNATIVE: Skip the crank, use spring pins only
172
+ // for discrete height positions (every 5cm).
173
+ // 9. Press-fit rubber feet onto inner leg bottoms.
174
+ // 10. Sand and paint/powder-coat all steel parts.
175
+ //
176
+ // TOOLS NEEDED:
177
+ // - Angle grinder or chop saw (cutting tubes)
178
+ // - Drill press (pin holes — alignment critical)
179
+ // - Welder (MIG preferred) OR bolt-together with brackets
180
+ // - Socket set, Allen keys
181
+ // - Level, tape measure, square
182
+ //
183
+ // COST ESTIMATE (EU prices, 2026):
184
+ // - Steel tubes: ~€40-60
185
+ // - Plywood top: ~€25-40
186
+ // - Hardware + feet: ~€15-20
187
+ // - Paint/finish: ~€10-15
188
+ // - Total: ~€90-135
189
+ //
190
+ // WEIGHT CAPACITY: ~80kg evenly distributed (depends on pin strength)
191
+ // Upgrade: use 2 pins per leg for more stability.
@@ -0,0 +1,32 @@
1
+ // Assembly + gear coupling demo
2
+ // Uses addGearCoupling(...) so the driven joint follows pinion motion automatically.
3
+
4
+ const pinionDeg = param("Pinion Angle", 20, { min: -180, max: 180, step: 1, unit: "°" });
5
+
6
+ const pair = lib.gearPair({
7
+ pinion: { module: 1.25, teeth: 14, faceWidth: 8, boreDiameter: 5 },
8
+ gear: { module: 1.25, teeth: 42, faceWidth: 8, boreDiameter: 8 },
9
+ backlash: 0.05,
10
+ place: false,
11
+ });
12
+
13
+ const mech = assembly("Gear Coupling Demo")
14
+ .addFrame("Base")
15
+ .addPart("Pinion", pair.pinion.color("#d5a15f"))
16
+ .addPart("Driven", pair.gear.color("#9ab3ca"), {
17
+ transform: Transform.identity().translate(pair.centerDistance, 0, 0),
18
+ })
19
+ .addRevolute("Pinion", "Base", "Pinion", {
20
+ axis: [0, 0, 1],
21
+ min: -720,
22
+ max: 720,
23
+ })
24
+ .addRevolute("Driven", "Base", "Driven", {
25
+ axis: [0, 0, 1],
26
+ min: -720,
27
+ max: 720,
28
+ })
29
+ .addGearCoupling("Driven", "Pinion", { pair });
30
+
31
+ const solved = mech.solve({ Pinion: pinionDeg });
32
+ return solved.toScene();
@@ -0,0 +1,111 @@
1
+ // Assembly + mechanism demo
2
+ // Shows Transform composition, assembly joints, BOM metadata, and collision checks.
3
+
4
+ const baseYaw = param("Base Yaw", 20, { min: -170, max: 170, unit: "°" });
5
+ const shoulder = param("Shoulder", 30, { min: -30, max: 110, unit: "°" });
6
+ const elbow = param("Elbow", 45, { min: -20, max: 135, unit: "°" });
7
+ const open = param("Gripper Open", 28, { min: 0, max: 55, unit: "mm" });
8
+
9
+ const upperLen = 180;
10
+ const foreLen = 160;
11
+
12
+ const basePlate = box(180, 140, 10, true).translate(0, 0, 5);
13
+ const tower = cylinder(20, 36).translate(0, 0, 10);
14
+
15
+ const m4 = lib.fastenerHole({ size: "M4", fit: "normal", depth: 14, counterbore: { depth: 4 } });
16
+ const mountHoles = [
17
+ m4.translate(55, 40, 7),
18
+ m4.translate(-55, 40, 7),
19
+ m4.translate(55, -40, 7),
20
+ m4.translate(-55, -40, 7),
21
+ ];
22
+
23
+ const base = difference(union(basePlate, tower), ...mountHoles).color("#6e7b88");
24
+
25
+ const upperArm = box(upperLen, 28, 28)
26
+ .translate(0, -14, -14)
27
+ .subtract(cylinder(32, 8).pointAlong([0, 1, 0]).translate(0, 0, 0))
28
+ .color("#5f87c6");
29
+
30
+ const forearm = box(foreLen, 24, 24)
31
+ .translate(0, -12, -12)
32
+ .subtract(cylinder(28, 7).pointAlong([0, 1, 0]).translate(0, 0, 0))
33
+ .color("#6fa2d6");
34
+
35
+ const wristHub = cylinder(26, 10).pointAlong([1, 0, 0]).translate(0, 0, 0);
36
+ const palm = box(34, 44, 16, true).translate(16, 0, 0);
37
+ const toolBody = union(wristHub, palm).color("#b8c5d3");
38
+
39
+ const fingerLen = 50;
40
+ const finger = box(fingerLen, 8, 10).translate(8, -4, -5).color("#414952");
41
+ const fingerLeft = finger.translate(18, 8 + open * 0.5, 0);
42
+ const fingerRight = finger.translate(18, -8 - open * 0.5, 0);
43
+ const gripper = group(
44
+ { name: "Tool Body", shape: toolBody },
45
+ { name: "Left Finger", shape: fingerLeft },
46
+ { name: "Right Finger", shape: fingerRight },
47
+ );
48
+
49
+ const mech = assembly("Robot Arm Demo")
50
+ .addPart("Base", base, {
51
+ metadata: { material: "PETG", process: "FDM", tolerance: "+/-0.2mm", qty: 1 },
52
+ })
53
+ .addPart("Upper Arm", upperArm, {
54
+ metadata: { material: "PETG-CF", process: "FDM", qty: 1 },
55
+ })
56
+ .addPart("Forearm", forearm, {
57
+ metadata: { material: "PETG-CF", process: "FDM", qty: 1 },
58
+ })
59
+ .addPart("Gripper", gripper, {
60
+ metadata: { material: "PETG", process: "FDM", notes: "Print fingers in TPU for compliance", qty: 1 },
61
+ })
62
+ .addJoint("baseYaw", "revolute", "Base", "Upper Arm", {
63
+ axis: [0, 0, 1],
64
+ min: -170,
65
+ max: 170,
66
+ frame: Transform.identity().translate(0, 0, 46),
67
+ })
68
+ .addJoint("shoulder", "revolute", "Upper Arm", "Forearm", {
69
+ axis: [0, -1, 0],
70
+ min: -30,
71
+ max: 110,
72
+ frame: Transform.identity().translate(upperLen + 8, 0, 0),
73
+ })
74
+ .addJoint("elbow", "revolute", "Forearm", "Gripper", {
75
+ axis: [0, -1, 0],
76
+ min: -20,
77
+ max: 135,
78
+ frame: Transform.identity().translate(foreLen + 12, 0, 0),
79
+ });
80
+
81
+ const solved = mech.solve({
82
+ baseYaw,
83
+ shoulder,
84
+ elbow,
85
+ });
86
+
87
+ const collisions = solved.collisionReport({
88
+ minOverlapVolume: 0.5,
89
+ ignorePairs: [
90
+ ["Upper Arm", "Forearm"],
91
+ ["Forearm", "Gripper"],
92
+ ],
93
+ });
94
+
95
+ if (collisions.length > 0) {
96
+ console.warn("Assembly collisions:", collisions);
97
+ }
98
+
99
+ const elbowSweep = mech.sweepJoint("elbow", -20, 135, 16, {
100
+ baseYaw,
101
+ shoulder,
102
+ });
103
+ const sweptCollisions = elbowSweep.filter(step => step.collisions.length > 0).length;
104
+ if (sweptCollisions > 0) {
105
+ console.info(`Elbow sweep has collisions in ${sweptCollisions}/${elbowSweep.length} steps`);
106
+ }
107
+
108
+ console.log("BOM", solved.bom());
109
+ console.log("BOM CSV\n" + solved.bomCsv());
110
+
111
+ return solved.toScene();
@@ -0,0 +1,45 @@
1
+ // attachTo() — the primary way to position parts relative to each other.
2
+ //
3
+ // Mental model: child.attachTo(parent, parentAnchor, selfAnchor, offset)
4
+ // "Put my [selfAnchor] at the parent's [parentAnchor], then shift by [offset]"
5
+ //
6
+ // Anchor names:
7
+ // 1 word = face center: 'top', 'bottom', 'front', 'back', 'left', 'right'
8
+ // 2 words = edge midpoint: 'top-front', 'back-left', etc.
9
+ // 3 words = corner: 'top-front-left', 'bottom-back-right', etc.
10
+
11
+ const baseW = param("Base Width", 100, { min: 50, max: 200, unit: "mm" });
12
+ const baseD = param("Base Depth", 80, { min: 40, max: 150, unit: "mm" });
13
+ const baseH = param("Base Height", 10, { min: 5, max: 30, unit: "mm" });
14
+
15
+ const base = box(baseW, baseD, baseH, true).color('#888888');
16
+
17
+ // Stack on top: column's bottom face meets base's top face
18
+ const column = cylinder(40, 8).color('#4488cc')
19
+ .attachTo(base, 'top', 'bottom');
20
+
21
+ // Protrude from front: button's back face meets base's front face
22
+ const button = box(20, 6, 10, true).color('#cc4444')
23
+ .attachTo(base, 'front', 'back');
24
+
25
+ // Hang below: bracket's top face meets base's bottom face
26
+ const bracket = box(30, 30, 5, true).color('#44cc44')
27
+ .attachTo(base, 'bottom', 'top');
28
+
29
+ // Attach to side with offset: panel's left face meets base's right face,
30
+ // then shift 0mm on X, 0mm on Y, 10mm up on Z
31
+ const sidePanel = box(4, 40, 25, true).color('#cc8844')
32
+ .attachTo(base, 'right', 'left', [0, 0, 10]);
33
+
34
+ // Corner alignment: small cube at top-front-right corner of base
35
+ const corner = box(8, 8, 8, true).color('#8844cc')
36
+ .attachTo(base, 'top-front-right', 'bottom-back-left');
37
+
38
+ return [
39
+ { name: "Base", shape: base },
40
+ { name: "Column (top→bottom)", shape: column },
41
+ { name: "Button (front→back)", shape: button },
42
+ { name: "Bracket (bottom→top)", shape: bracket },
43
+ { name: "Side Panel (right→left, +10Z)", shape: sidePanel },
44
+ { name: "Corner Cube", shape: corner },
45
+ ];
@@ -0,0 +1,89 @@
1
+ // Benchy-style hull concept using reusable curve/surface APIs.
2
+ // Not an exact #3DBenchy clone; this shows the modeling workflow:
3
+ // sections -> loft hull, sweep rails/chimney, simple superstructure.
4
+
5
+ const length = param("Length", 92, { min: 60, max: 150, unit: "mm" });
6
+ const beam = param("Beam", 42, { min: 24, max: 70, unit: "mm" });
7
+ const hullH = param("Hull Height", 34, { min: 18, max: 60, unit: "mm" });
8
+ const deckDrop = param("Deck Drop", 6, { min: 2, max: 12, unit: "mm" });
9
+
10
+ const mkSection = (w, h, keel = 0, chine = 0) => spline2d([
11
+ [w * 0.5, 0],
12
+ [w * 0.45, h * 0.28 + chine],
13
+ [w * 0.25, h * 0.5 + chine],
14
+ [0, h * 0.58 + keel],
15
+ [-w * 0.25, h * 0.5 + chine],
16
+ [-w * 0.45, h * 0.28 + chine],
17
+ [-w * 0.5, 0],
18
+ [-w * 0.45, -h * 0.18],
19
+ [-w * 0.23, -h * 0.32],
20
+ [0, -h * 0.36 - deckDrop],
21
+ [w * 0.23, -h * 0.32],
22
+ [w * 0.45, -h * 0.18],
23
+ ], {
24
+ closed: true,
25
+ samplesPerSegment: 10,
26
+ tension: 0.45,
27
+ });
28
+
29
+ const z0 = 0;
30
+ const z1 = length * 0.22;
31
+ const z2 = length * 0.56;
32
+ const z3 = length * 0.88;
33
+ const z4 = length;
34
+
35
+ let hull = loft(
36
+ [
37
+ mkSection(beam * 0.52, hullH * 0.72, 2, 1), // stern
38
+ mkSection(beam * 0.94, hullH * 0.95, 3, 1.5),
39
+ mkSection(beam, hullH, 3.5, 1.2), // max beam
40
+ mkSection(beam * 0.58, hullH * 0.82, 1.5, 0.5),
41
+ mkSection(beam * 0.18, hullH * 0.35, 0, 0), // bow tip
42
+ ],
43
+ [z0, z1, z2, z3, z4],
44
+ { edgeLength: 0.95 },
45
+ );
46
+ hull = hull.smoothOut(72, 0.28).refine(2);
47
+
48
+ // Orient hull so length goes along X, beam along Y, height along Z.
49
+ hull = hull
50
+ .rotate(0, 90, 0) // Z (loft stations) -> X
51
+ .rotate(90, 0, 0) // Y (section height) -> Z
52
+ .translate(-length * 0.5, 0, hullH * 0.58);
53
+
54
+ // Deckhouse and cabin
55
+ const houseW = beam * 0.48;
56
+ const houseD = length * 0.26;
57
+ const houseH = hullH * 0.62;
58
+ const house = roundedRect(houseW, houseD, 4, true).extrude(houseH)
59
+ .translate(length * 0.04, 0, hullH * 0.82);
60
+
61
+ const cabinCut = roundedRect(houseW * 0.68, houseD * 0.56, 2.2, true).extrude(houseH * 0.7)
62
+ .translate(length * 0.04, 0, hullH * 1.08);
63
+
64
+ // Chimney via sweep
65
+ const stackPath = spline3d(
66
+ [
67
+ [length * 0.02, 0, hullH * 1.45],
68
+ [length * 0.02, 0, hullH * 1.72],
69
+ [length * 0.08, 0, hullH * 1.84],
70
+ ],
71
+ { tension: 0.5 },
72
+ );
73
+ const stack = sweep(circle2d(3.8), stackPath, {
74
+ samples: 28,
75
+ edgeLength: 0.55,
76
+ });
77
+ const stackInner = sweep(circle2d(2.2), stackPath, {
78
+ samples: 28,
79
+ edgeLength: 0.55,
80
+ });
81
+
82
+ const cabin = house.subtract(cabinCut);
83
+ const chimney = stack.subtract(stackInner);
84
+
85
+ return [
86
+ { name: "Hull", shape: hull.color('#ce6f4e') },
87
+ { name: "Cabin", shape: cabin.color('#f0eee9') },
88
+ { name: "Chimney", shape: chimney.color('#3d4854') },
89
+ ];
@@ -0,0 +1,46 @@
1
+ // API demo: script-declared bill of materials that gets auto-summed in report export
2
+
3
+ const frameWidth = param('Frame Width', 900, { min: 300, max: 1800, unit: 'mm' });
4
+ const frameDepth = param('Frame Depth', 500, { min: 200, max: 1200, unit: 'mm' });
5
+ const legHeight = param('Leg Height', 720, { min: 300, max: 1200, unit: 'mm' });
6
+ const tubeW = param('Tube Width', 30, { min: 15, max: 80, unit: 'mm' });
7
+ const tubeH = param('Tube Height', 20, { min: 10, max: 80, unit: 'mm' });
8
+
9
+ const frontBolts = param('Front Bolts', 8, { min: 0, max: 64, integer: true });
10
+ const rearBolts = param('Rear Bolts', 8, { min: 0, max: 64, integer: true });
11
+ const boltLength = param('Bolt Length', 16, { min: 6, max: 60, unit: 'mm' });
12
+
13
+ const wall = 2;
14
+ const longTubeMm = frameWidth * 2;
15
+ const shortTubeMm = frameDepth * 2;
16
+ const legTubeMm = legHeight * 4;
17
+ const totalTubeMm = longTubeMm + shortTubeMm + legTubeMm;
18
+
19
+ // Physical materials are authored by code, not inferred from mesh primitives.
20
+ bom(totalTubeMm, `iron tube with dimensions ${tubeW} x ${tubeH}`, { unit: 'mm' });
21
+
22
+ // These two lines intentionally share the same descriptor so report export sums them.
23
+ bom(frontBolts, `M4 bolt of ${boltLength} mm length`, { unit: 'pieces' });
24
+ bom(rearBolts, `M4 bolt of ${boltLength} mm length`, { unit: 'pieces' });
25
+
26
+ const railFront = box(frameWidth, tubeW, tubeH).color('#778da9');
27
+ const railBack = box(frameWidth, tubeW, tubeH).translate(0, frameDepth - tubeW, 0).color('#778da9');
28
+ const railLeft = box(tubeW, frameDepth, tubeH).color('#778da9');
29
+ const railRight = box(tubeW, frameDepth, tubeH).translate(frameWidth - tubeW, 0, 0).color('#778da9');
30
+
31
+ const legSize = Math.min(tubeW, tubeH);
32
+ const legA = box(legSize, legSize, legHeight).translate(0, 0, tubeH).color('#415a77');
33
+ const legB = box(legSize, legSize, legHeight).translate(frameWidth - legSize, 0, tubeH).color('#415a77');
34
+ const legC = box(legSize, legSize, legHeight).translate(0, frameDepth - legSize, tubeH).color('#415a77');
35
+ const legD = box(legSize, legSize, legHeight).translate(frameWidth - legSize, frameDepth - legSize, tubeH).color('#415a77');
36
+
37
+ return [
38
+ { name: 'Front Rail', shape: railFront },
39
+ { name: 'Back Rail', shape: railBack },
40
+ { name: 'Left Rail', shape: railLeft },
41
+ { name: 'Right Rail', shape: railRight },
42
+ { name: 'Leg A', shape: legA },
43
+ { name: 'Leg B', shape: legB },
44
+ { name: 'Leg C', shape: legC },
45
+ { name: 'Leg D', shape: legD },
46
+ ];