forgecad 0.6.3 → 0.8.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 (234) hide show
  1. package/README.md +3 -12
  2. package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-D4bocK4E.js} +250 -151
  3. package/dist/assets/{BlogPage-P_AJP0v9.js → BlogPage-CJEXL_zJ.js} +94 -70
  4. package/dist/assets/{DocsPage-CKRV2iq2.js → DocsPage-D3A_g8V3.js} +329 -163
  5. package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-BWYUSpUN.css} +590 -136
  6. package/dist/assets/EditorApp-Cihhqcsq.js +11692 -0
  7. package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-kWjKaC_t.js} +2 -4
  8. package/dist/assets/LandingPageProofDriven-Bg2IUc3l.css +856 -0
  9. package/dist/assets/LandingPageProofDriven-DXkKlyhI.js +601 -0
  10. package/dist/assets/PricingPage-BsU5vzEx.js +232 -0
  11. package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-PqvpAKIs.js} +129 -5
  12. package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-C-hzNUMy.js} +8949 -3161
  13. package/dist/assets/{Viewport-CoB46f5R.js → index-Pz321YAt.js} +38382 -7501
  14. package/dist/assets/{index-2hfs_ub0.css → index-ay13WNfa.css} +726 -53
  15. package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
  16. package/dist/assets/{manifold-CqNMHHKO.js → manifold-BcbjWLIo.js} +4 -3
  17. package/dist/assets/{manifold-Cce9wRFz.js → manifold-DBckbFgx.js} +1 -1
  18. package/dist/assets/{manifold-D6BeHIOo.js → manifold-O2AAGXyj.js} +1 -1
  19. package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-Dxr-5A7w.js} +8760 -3559
  20. package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
  21. package/dist/docs/index.html +2 -2
  22. package/dist/docs-raw/CLI.md +341 -718
  23. package/dist/docs-raw/generated/assembly.md +699 -112
  24. package/dist/docs-raw/generated/concepts.md +1834 -1346
  25. package/dist/docs-raw/generated/core.md +1012 -1059
  26. package/dist/docs-raw/generated/curves.md +759 -116
  27. package/dist/docs-raw/generated/lib.md +43 -748
  28. package/dist/docs-raw/generated/output.md +139 -245
  29. package/dist/docs-raw/generated/sdf.md +208 -0
  30. package/dist/docs-raw/generated/sheet-metal.md +473 -21
  31. package/dist/docs-raw/generated/sketch.md +1518 -362
  32. package/dist/docs-raw/generated/viewport.md +368 -299
  33. package/dist/docs-raw/generated/wood.md +104 -0
  34. package/dist/index.html +2 -2
  35. package/dist/landing/proof-ams-adapter.png +0 -0
  36. package/dist/landing/proof-bolt-and-nut.png +0 -0
  37. package/dist/landing/proof-fillet-enclosure.png +0 -0
  38. package/dist/landing/proof-glasses.png +0 -0
  39. package/dist/landing/proof-gyroid.png +0 -0
  40. package/dist/sitemap.xml +6 -6
  41. package/dist-cli/forgecad.js +12321 -5700
  42. package/dist-cli/forgecad.js.map +1 -0
  43. package/dist-cli/solver-46FFSK2U.js +363 -0
  44. package/dist-cli/solver-46FFSK2U.js.map +1 -0
  45. package/dist-skill/CONTEXT.md +4890 -6302
  46. package/dist-skill/SKILL-dev.md +22 -66
  47. package/dist-skill/SKILL.md +20 -59
  48. package/dist-skill/docs/API/core/concepts.md +37 -92
  49. package/dist-skill/docs/CLI.md +341 -718
  50. package/dist-skill/docs/generated/assembly.md +699 -112
  51. package/dist-skill/docs/generated/core.md +1012 -1059
  52. package/dist-skill/docs/generated/curves.md +759 -116
  53. package/dist-skill/docs/generated/lib.md +43 -748
  54. package/dist-skill/docs/generated/output.md +139 -245
  55. package/dist-skill/docs/generated/sdf.md +208 -0
  56. package/dist-skill/docs/generated/sheet-metal.md +473 -21
  57. package/dist-skill/docs/generated/sketch.md +1518 -362
  58. package/dist-skill/docs/generated/viewport.md +368 -299
  59. package/dist-skill/docs/generated/wood.md +104 -0
  60. package/dist-skill/docs/guides/coordinate-system.md +11 -17
  61. package/dist-skill/docs/guides/geometry-conventions.md +13 -70
  62. package/dist-skill/docs/guides/joint-design.md +78 -0
  63. package/dist-skill/docs/guides/modeling-recipes.md +22 -195
  64. package/dist-skill/docs/guides/positioning.md +88 -147
  65. package/dist-skill/docs-dev/API/core/concepts.md +78 -0
  66. package/dist-skill/docs-dev/CLI.md +488 -0
  67. package/dist-skill/{docs → docs-dev}/blueprint-first.md +5 -0
  68. package/dist-skill/{docs → docs-dev}/coding-best-practices.md +6 -8
  69. package/dist-skill/{docs → docs-dev}/coding.md +2 -4
  70. package/dist-skill/docs-dev/component-model.md +164 -0
  71. package/dist-skill/docs-dev/generated/assembly.md +779 -0
  72. package/dist-skill/docs-dev/generated/core.md +1676 -0
  73. package/dist-skill/docs-dev/generated/curves.md +855 -0
  74. package/dist-skill/docs-dev/generated/lib.md +55 -0
  75. package/dist-skill/docs-dev/generated/output.md +234 -0
  76. package/dist-skill/docs-dev/generated/sdf.md +208 -0
  77. package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
  78. package/dist-skill/docs-dev/generated/sketch.md +1753 -0
  79. package/dist-skill/docs-dev/generated/viewport.md +513 -0
  80. package/dist-skill/docs-dev/generated/wood.md +104 -0
  81. package/dist-skill/docs-dev/guides/coordinate-system.md +46 -0
  82. package/dist-skill/docs-dev/guides/geometry-conventions.md +52 -0
  83. package/dist-skill/docs-dev/guides/joint-design.md +78 -0
  84. package/dist-skill/docs-dev/guides/modeling-recipes.md +77 -0
  85. package/dist-skill/docs-dev/guides/positioning.md +151 -0
  86. package/dist-skill/{docs → docs-dev}/guides/skill-maintenance.md +21 -10
  87. package/dist-skill/{docs → docs-dev}/internals/compiler.md +5 -6
  88. package/dist-skill/{docs → docs-dev}/internals/constraint-solver-quality.md +0 -1
  89. package/dist-skill/{docs → docs-dev}/internals/constraint-solver.md +0 -1
  90. package/dist-skill/{docs → docs-dev}/internals/sketch-2d-pipeline.md +2 -3
  91. package/examples/api/attachTo-basics.forge.js +8 -8
  92. package/examples/api/bill-of-materials.forge.js +9 -9
  93. package/examples/api/bolt-pattern.forge.js +5 -5
  94. package/examples/api/boolean-operations.forge.js +5 -5
  95. package/examples/api/bounding-box-visualizer.forge.js +3 -3
  96. package/examples/api/clone-duplicate.forge.js +2 -2
  97. package/examples/api/colors-union-vs-array.forge.js +6 -6
  98. package/examples/api/connector-assembly.forge.js +8 -6
  99. package/examples/api/connector-basics.forge.js +7 -7
  100. package/examples/api/constrained-sketch-mechanical.forge.js +4 -4
  101. package/examples/api/elbow-test.forge.js +3 -3
  102. package/examples/api/extrude-options.forge.js +8 -14
  103. package/examples/api/feature-created-faces.forge.js +6 -10
  104. package/examples/api/fillet-showcase.forge.js +2 -2
  105. package/examples/api/folded-service-panel-cover.forge.js +2 -2
  106. package/examples/api/gears-tier1.forge.js +5 -5
  107. package/examples/api/group-test.forge.js +3 -3
  108. package/examples/api/group-vs-union.forge.js +1 -1
  109. package/examples/api/highlight-debug.forge.js +4 -0
  110. package/examples/api/js-module-pillars.js +1 -1
  111. package/examples/api/js-module-scene.js +2 -2
  112. package/examples/api/mesh-import-slats.forge.js +4 -4
  113. package/examples/api/patterns.forge.js +3 -3
  114. package/examples/api/pointAlong-orientation.forge.js +3 -3
  115. package/examples/api/profile-2020-b-slot6.forge.js +4 -5
  116. package/examples/api/route-perimeter-flange.forge.js +1 -1
  117. package/examples/api/sdf-rover-demo.forge.js +10 -10
  118. package/examples/api/sketch-on-face-demo.forge.js +2 -2
  119. package/examples/api/sketch-regions.forge.js +4 -4
  120. package/examples/api/sketch-rounding-strategies.forge.js +1 -1
  121. package/examples/api/smooth-curve-connections.forge.js +1 -1
  122. package/examples/api/transition-curves.forge.js +4 -4
  123. package/examples/api/variable-sweep-pure-sdf-test.forge.js +162 -0
  124. package/examples/api/variable-sweep-test.forge.js +2 -2
  125. package/examples/api/wood-joinery.forge.js +60 -0
  126. package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +3 -3
  127. package/examples/compiler-corpus/fastener-plate-variants.forge.js +2 -2
  128. package/examples/constraints/01-fully-constrained-rect.forge.js +2 -2
  129. package/examples/constraints/02-underconstrained-triangle.forge.js +1 -1
  130. package/examples/constraints/03-redundant-constraints.forge.js +2 -2
  131. package/examples/constraints/05-parallel-with-linedistance.forge.js +2 -2
  132. package/examples/constraints/06-complex-spectrogram.forge.js +1 -1
  133. package/examples/constraints/07-perpendicular-chain.forge.js +4 -4
  134. package/examples/constraints/08-symmetric-bracket.forge.js +4 -4
  135. package/examples/constraints/09-stress-spiral.forge.js +1 -1
  136. package/examples/constraints/10-stress-honeycomb.forge.js +1 -1
  137. package/examples/constraints/11-surface-grid.forge.js +2 -2
  138. package/examples/constraints/12-surface-nested.forge.js +4 -4
  139. package/examples/constraints/13-surface-complex.forge.js +1 -1
  140. package/examples/exact-arc-housing.forge.js +12 -0
  141. package/examples/experiments/drone-arm.forge.js +53 -0
  142. package/examples/furniture/adjustable-table.forge.js +15 -15
  143. package/examples/furniture/bathroom.forge.js +26 -26
  144. package/examples/furniture/chair.forge.js +13 -13
  145. package/examples/furniture/picture-frame.forge.js +6 -6
  146. package/examples/furniture/shoe-rack-doors.forge.js +8 -8
  147. package/examples/furniture/shoe-rack.forge.js +7 -7
  148. package/examples/furniture/table-lamp.forge.js +8 -8
  149. package/examples/gcode/lissajous-vase.forge.js +4 -4
  150. package/examples/gcode/math-surface.forge.js +3 -3
  151. package/examples/gcode/parametric-vase.forge.js +4 -4
  152. package/examples/gcode/spiral-tower.forge.js +4 -4
  153. package/examples/generative/crystal-growth.forge.js +9 -9
  154. package/examples/generative/frost-spires.forge.js +9 -9
  155. package/examples/generative/golden-spiral-tower.forge.js +11 -11
  156. package/examples/generative/molten-forge.forge.js +6 -6
  157. package/examples/generative/neon-coral.forge.js +7 -7
  158. package/examples/mechanical/3d-printer.forge.js +37 -37
  159. package/examples/mechanical/5-finger-robot-hand.forge.js +19 -19
  160. package/examples/mechanical/airplane-propeller.forge.js +9 -9
  161. package/examples/mechanical/bolt-and-nut.forge.js +10 -10
  162. package/examples/mechanical/door-with-hinges.forge.js +7 -7
  163. package/examples/mechanical/fillet-enclosure.forge.js +15 -11
  164. package/examples/mechanical/headphone-hanger-v2.forge.js +11 -11
  165. package/examples/mechanical/robot_hand.forge.js +24 -24
  166. package/examples/mechanical/robot_hand_2.forge.js +26 -26
  167. package/examples/nurbs-surface.forge.js +8 -0
  168. package/examples/nurbs-tube.forge.js +7 -0
  169. package/examples/products/bottle.forge.js +8 -8
  170. package/examples/products/chess-set.forge.js +25 -25
  171. package/examples/products/classical-piano.forge.js +20 -20
  172. package/examples/products/clock.forge.js +33 -33
  173. package/examples/products/cup.forge.js +5 -5
  174. package/examples/products/iphone.forge.js +20 -20
  175. package/examples/products/laptop.forge.js +24 -24
  176. package/examples/products/laser-cut-box.forge.js +6 -6
  177. package/examples/products/laser-cut-tray.forge.js +6 -6
  178. package/examples/products/liquid-soap-dispenser.forge.js +23 -23
  179. package/examples/products/origami-fish.forge.js +14 -12
  180. package/examples/products/spiderman-cake.forge.js +6 -6
  181. package/examples/shelf/container.forge.js +5 -5
  182. package/examples/shelf/shelf-unit.forge.js +6 -6
  183. package/examples/toolbox/bolted-joint.forge.js +7 -7
  184. package/package.json +9 -4
  185. package/dist/assets/EditorApp-B-vQvgam.js +0 -9888
  186. package/dist/assets/LandingPage-C5n9hDXI.js +0 -322
  187. package/dist/assets/PublishedModelPage-Dt7PCVBj.js +0 -146
  188. package/dist/assets/__vite-browser-external-CURh0WXD.js +0 -8
  189. package/dist/assets/deserializeRunResult-BLAFoiE0.js +0 -19365
  190. package/dist/assets/index-1CYp3zUp.js +0 -1455
  191. package/dist-skill/docs/API/API.md +0 -1666
  192. package/dist-skill/docs/API/README.md +0 -37
  193. package/dist-skill/docs/API/assembly/assembly.md +0 -617
  194. package/dist-skill/docs/API/core/edge-queries.md +0 -130
  195. package/dist-skill/docs/API/core/parameters.md +0 -122
  196. package/dist-skill/docs/API/core/reserved-terms.md +0 -137
  197. package/dist-skill/docs/API/core/sdf.md +0 -326
  198. package/dist-skill/docs/API/core/skill-cli.md +0 -194
  199. package/dist-skill/docs/API/core/skill-guide.md +0 -205
  200. package/dist-skill/docs/API/core/specs.md +0 -186
  201. package/dist-skill/docs/API/core/topology.md +0 -372
  202. package/dist-skill/docs/API/entities.md +0 -268
  203. package/dist-skill/docs/API/output/bom.md +0 -58
  204. package/dist-skill/docs/API/output/brep-export.md +0 -87
  205. package/dist-skill/docs/API/output/dimensions.md +0 -67
  206. package/dist-skill/docs/API/output/export.md +0 -110
  207. package/dist-skill/docs/API/output/gcode.md +0 -195
  208. package/dist-skill/docs/API/runtime/viewport.md +0 -420
  209. package/dist-skill/docs/API/sheet-metal/sheet-metal.md +0 -185
  210. package/dist-skill/docs/API/sketch/anchor.md +0 -37
  211. package/dist-skill/docs/API/sketch/booleans.md +0 -91
  212. package/dist-skill/docs/API/sketch/core.md +0 -73
  213. package/dist-skill/docs/API/sketch/extrude.md +0 -62
  214. package/dist-skill/docs/API/sketch/on-face.md +0 -104
  215. package/dist-skill/docs/API/sketch/operations.md +0 -78
  216. package/dist-skill/docs/API/sketch/path.md +0 -75
  217. package/dist-skill/docs/API/sketch/primitives.md +0 -146
  218. package/dist-skill/docs/API/sketch/regions.md +0 -80
  219. package/dist-skill/docs/API/sketch/text.md +0 -108
  220. package/dist-skill/docs/API/sketch/transforms.md +0 -65
  221. package/dist-skill/docs/API/toolbox/fasteners.md +0 -129
  222. package/dist-skill/docs/INDEX.md +0 -94
  223. package/dist-skill/docs/RELEASING.md +0 -55
  224. package/dist-skill/docs/cli-monetization.md +0 -111
  225. package/dist-skill/docs/deployment.md +0 -281
  226. package/dist-skill/docs/generated/concepts.md +0 -2112
  227. package/dist-skill/docs/internals/shape-from-slices.md +0 -152
  228. package/dist-skill/docs/platform/admin.md +0 -45
  229. package/dist-skill/docs/platform/architecture.md +0 -79
  230. package/dist-skill/docs/platform/auth.md +0 -110
  231. package/dist-skill/docs/platform/email.md +0 -67
  232. package/dist-skill/docs/platform/projects.md +0 -111
  233. package/dist-skill/docs/platform/sharing.md +0 -90
  234. package/dist-skill/docs/runbook.md +0 -345
@@ -7,17 +7,17 @@ const size = 25;
7
7
  const gap = 5;
8
8
 
9
9
  // --- Three colored boxes ---
10
- const red = box(size, size, size, true).color('#cc4444');
11
- const green = box(size, size, size, true).color('#44cc44').translate(size + gap, 0, 0);
12
- const blue = box(size, size, size, true).color('#4444cc').translate(2 * (size + gap), 0, 0);
10
+ const red = box(size, size, size).color('#cc4444');
11
+ const green = box(size, size, size).color('#44cc44').translate(size + gap, 0, 0);
12
+ const blue = box(size, size, size).color('#4444cc').translate(2 * (size + gap), 0, 0);
13
13
 
14
14
  // BAD: union kills individual colors — result is all red (first shape's color)
15
15
  const merged = union(red, green, blue).translate(-80, 0, 0);
16
16
 
17
17
  // GOOD: separate objects keep their colors
18
- const redSep = box(size, size, size, true).color('#cc4444').translate(80, 0, 0);
19
- const greenSep = box(size, size, size, true).color('#44cc44').translate(80 + size + gap, 0, 0);
20
- const blueSep = box(size, size, size, true).color('#4444cc').translate(80 + 2 * (size + gap), 0, 0);
18
+ const redSep = box(size, size, size).color('#cc4444').translate(80, 0, 0);
19
+ const greenSep = box(size, size, size).color('#44cc44').translate(80 + size + gap, 0, 0);
20
+ const blueSep = box(size, size, size).color('#4444cc').translate(80 + 2 * (size + gap), 0, 0);
21
21
 
22
22
  return [
23
23
  { name: "❌ Union (all one color)", shape: merged },
@@ -9,20 +9,22 @@ const doorW = 60, doorH = 100, doorT = 4;
9
9
  const frameT = 8, frameD = 10;
10
10
 
11
11
  // ── Door with hinge connectors ─────────────────────────────────────────────
12
+ // Hinge axes point "outward" from each part. When connectors mate,
13
+ // they meet face-to-face (anti-parallel), producing the shared rotation axis.
12
14
 
13
- const door = box(doorW, doorT, doorH, true)
15
+ const door = box(doorW, doorT, doorH)
14
16
  .translate(doorW / 2, 0, 0)
15
17
  .withConnectors({
16
18
  hinge_top: connector.male("hinge_pin", {
17
19
  origin: [0, 0, doorH * 0.3],
18
- axis: [0, 0, 1],
20
+ axis: [0, 0, -1],
19
21
  kind: "revolute",
20
22
  min: 0,
21
23
  max: 110,
22
24
  }),
23
25
  hinge_bottom: connector.male("hinge_pin", {
24
26
  origin: [0, 0, -doorH * 0.3],
25
- axis: [0, 0, 1],
27
+ axis: [0, 0, -1],
26
28
  kind: "revolute",
27
29
  min: 0,
28
30
  max: 110,
@@ -32,11 +34,11 @@ const door = box(doorW, doorT, doorH, true)
32
34
 
33
35
  // ── Frame with matching hinge connectors ───────────────────────────────────
34
36
 
35
- const leftPost = box(frameT, frameD, doorH + frameT, true)
37
+ const leftPost = box(frameT, frameD, doorH + frameT)
36
38
  .translate(-(doorW / 2 + frameT / 2), 0, frameT / 4);
37
- const rightPost = box(frameT, frameD, doorH + frameT, true)
39
+ const rightPost = box(frameT, frameD, doorH + frameT)
38
40
  .translate(doorW / 2 + frameT / 2, 0, frameT / 4);
39
- const lintel = box(doorW + frameT * 2, frameD, frameT, true)
41
+ const lintel = box(doorW + frameT * 2, frameD, frameT)
40
42
  .translate(0, 0, doorH / 2 + frameT / 2);
41
43
 
42
44
  const frame = union(leftPost, rightPost, lintel)
@@ -6,16 +6,16 @@
6
6
  // - matchTo() single-pair connector matching
7
7
  // - Auto-bubbling: connectors on named group children are accessible via "ChildName.connectorName"
8
8
 
9
- const panelH = param("Panel Height", 200, { min: 100, max: 400, unit: "mm" });
10
- const panelD = param("Panel Depth", 120, { min: 60, max: 200, unit: "mm" });
11
- const panelT = param("Panel Thickness", 12, { unit: "mm" });
12
- const shelfCount = param("Shelves", 3, { min: 1, max: 6 });
13
- const cabinetW = param("Cabinet Width", 180, { min: 100, max: 400, unit: "mm" });
9
+ const panelH = Param.number("Panel Height", 200, { min: 100, max: 400, unit: "mm" });
10
+ const panelD = Param.number("Panel Depth", 120, { min: 60, max: 200, unit: "mm" });
11
+ const panelT = Param.number("Panel Thickness", 12, { unit: "mm" });
12
+ const shelfCount = Param.number("Shelves", 3, { min: 1, max: 6 });
13
+ const cabinetW = Param.number("Cabinet Width", 180, { min: 100, max: 400, unit: "mm" });
14
14
 
15
15
  // ── Side Panel with shelf slots ────────────────────────────────────────────
16
16
 
17
17
  function makeSidePanel(facingDir) {
18
- const panel = box(panelT, panelD, panelH, true);
18
+ const panel = box(panelT, panelD, panelH);
19
19
  const connectors = {};
20
20
 
21
21
  for (let i = 0; i < shelfCount; i++) {
@@ -47,7 +47,7 @@ console.log("Cabinet connectors:", cabinet.connectorNames());
47
47
 
48
48
  const shelfW = cabinetW - panelT;
49
49
  const shelfT = 10;
50
- const shelf = box(shelfW, panelD - 10, shelfT, true)
50
+ const shelf = box(shelfW, panelD - 10, shelfT)
51
51
  .withConnectors({
52
52
  left_tab: connector.male("dovetail", {
53
53
  origin: [-shelfW / 2, 0, 0],
@@ -11,10 +11,10 @@
11
11
  * 4. Connecting rod — constrained trapezoid with symmetric sides
12
12
  */
13
13
 
14
- const W = param('width', 60, { min: 30, max: 120, unit: 'mm' });
15
- const H = param('height', 40, { min: 20, max: 80, unit: 'mm' });
16
- const T = param('thick', 8, { min: 3, max: 20, unit: 'mm' });
17
- const R = param('radius', 10, { min: 5, max: 25, unit: 'mm' });
14
+ const W = Param.number('width', 60, { min: 30, max: 120, unit: 'mm' });
15
+ const H = Param.number('height', 40, { min: 20, max: 80, unit: 'mm' });
16
+ const T = Param.number('thick', 8, { min: 3, max: 20, unit: 'mm' });
17
+ const R = Param.number('radius', 10, { min: 5, max: 25, unit: 'mm' });
18
18
 
19
19
  // ─── 1. Slotted plate ─────────────────────────────────────────────────────────
20
20
  const slottedPlate = (() => {
@@ -1,7 +1,7 @@
1
1
  // Test lib.elbow() — pipe bend primitive
2
- const pipeR = param("Pipe Radius", 5, { min: 2, max: 15, unit: "mm" });
3
- const bendR = param("Bend Radius", 25, { min: 10, max: 60, unit: "mm" });
4
- const angle = param("Angle", 90, { min: 15, max: 180, unit: "°" });
2
+ const pipeR = Param.number("Pipe Radius", 5, { min: 2, max: 15, unit: "mm" });
3
+ const bendR = Param.number("Bend Radius", 25, { min: 10, max: 60, unit: "mm" });
4
+ const angle = Param.number("Angle", 90, { min: 15, max: 180, unit: "°" });
5
5
 
6
6
  // Basic elbow at default orientation
7
7
  const basic = lib.elbow(pipeR, bendR, angle).color('#B87333');
@@ -1,12 +1,12 @@
1
- // Extrude options — twist, taper, center.
1
+ // Extrude options — twist, taper, scaleTop.
2
2
  //
3
3
  // .extrude(height) is the basic form.
4
- // Options: { twist, divisions, scaleTop, center }
4
+ // Options: { twist, divisions, scaleTop }
5
5
 
6
- const r = param("Radius", 20, { min: 10, max: 40, unit: "mm" });
7
- const h = param("Height", 60, { min: 20, max: 120, unit: "mm" });
8
- const twist = param("Twist", 90, { min: 0, max: 360, unit: "°" });
9
- const taper = param("Taper", 0.5, { min: 0.1, max: 1.0 });
6
+ const r = Param.number("Radius", 20, { min: 10, max: 40, unit: "mm" });
7
+ const h = Param.number("Height", 60, { min: 20, max: 120, unit: "mm" });
8
+ const twist = Param.number("Twist", 90, { min: 0, max: 360, unit: "°" });
9
+ const taper = Param.number("Taper", 0.5, { min: 0.1, max: 1.0 });
10
10
  const spacing = 60;
11
11
 
12
12
  // 1. Plain extrude
@@ -23,22 +23,16 @@ const tapered = circle2d(r).extrude(h, { scaleTop: taper })
23
23
  .translate(2 * spacing, 0, 0)
24
24
  .color('#44cc88');
25
25
 
26
- // 4. Centered extrude shape is centered along Z instead of starting at Z=0
27
- const centered = rect(r * 1.5, r, true).extrude(h, { center: true })
28
- .translate(3 * spacing, 0, 0)
29
- .color('#cc44cc');
30
-
31
- // 5. Combined: twist + taper
26
+ // 4. Combined: twist + taper
32
27
  const combo = star(5, r, r * 0.5).extrude(h, {
33
28
  twist: twist,
34
29
  scaleTop: taper,
35
30
  divisions: 32,
36
- }).translate(4 * spacing, 0, 0).color('#cccc44');
31
+ }).translate(3 * spacing, 0, 0).color('#cccc44');
37
32
 
38
33
  return [
39
34
  { name: "Plain", shape: plain },
40
35
  { name: "Twisted", shape: twisted },
41
36
  { name: "Tapered", shape: tapered },
42
- { name: "Centered (Z)", shape: centered },
43
37
  { name: "Twist + Taper", shape: combo },
44
38
  ];
@@ -1,20 +1,18 @@
1
- const shellBase = roundedRect(90, 56, 6, true).extrude(28);
1
+ const shellBase = roundedRect(90, 56, 6).extrude(28);
2
2
  const cup = shellBase.shell(2.5, { openFaces: ['top'] }).color('#6f7b86');
3
- const innerWallPad = roundedRect(16, 9, 1.8, true)
3
+ const innerWallPad = roundedRect(16, 9, 1.8)
4
4
  .onFace(cup, 'inner-side-right', { u: 0, v: -3, protrude: 0.05, selfAnchor: 'center' })
5
5
  .extrude(1.4)
6
- // .toShape()
7
6
  .color('#f2b16a');
8
7
 
9
- const holeBase = roundedRect(72, 44, 5, true).extrude(20);
8
+ const holeBase = roundedRect(72, 44, 5).extrude(20);
10
9
  const drilled = holeBase.hole('top', { diameter: 8, u: 16, v: -8, depth: 10 }).color('#7a8792');
11
10
  const floorBoss = circle2d(3)
12
11
  .onFace(drilled, 'floor', { u: 0, v: 0, protrude: 0.05, selfAnchor: 'center' })
13
12
  .extrude(1.2)
14
- // .toShape()
15
13
  .color('#d46452');
16
14
 
17
- const counterboreBase = roundedRect(72, 44, 5, true).extrude(20);
15
+ const counterboreBase = roundedRect(72, 44, 5).extrude(20);
18
16
  const counterboreExitFace = counterboreBase.face('bottom');
19
17
  const counterbored = counterboreBase.hole('top', {
20
18
  diameter: 6,
@@ -26,17 +24,15 @@ const counterbored = counterboreBase.hole('top', {
26
24
  const shoulderPad = rect(4, 3)
27
25
  .onFace(counterbored, 'counterbore-floor', { u: 0, v: 0, protrude: 0.05, selfAnchor: 'center' })
28
26
  .extrude(0.9)
29
- // .toShape()
30
27
  .color('#f0c36c');
31
28
 
32
- const cutBase = roundedRect(78, 46, 5, true).extrude(22);
33
- const pocket = roundedRect(20, 12, 2, true)
29
+ const cutBase = roundedRect(78, 46, 5).extrude(22);
30
+ const pocket = roundedRect(20, 12, 2)
34
31
  .onFace(cutBase, 'front', { u: 0, v: 4, selfAnchor: 'center' });
35
32
  const cut = cutBase.cutout(pocket, { depth: 8 }).color('#64707d');
36
33
  const wallTab = rect(5, 4)
37
34
  .onFace(cut, 'wall-right', { u: 0, v: 0, protrude: 0.05, selfAnchor: 'center' })
38
35
  .extrude(1)
39
- // .toShape()
40
36
  .color('#5ba6d6');
41
37
 
42
38
  return [
@@ -1,7 +1,7 @@
1
1
  // Fillet Showcase — the new fillet() API
2
2
  // Demonstrates: fillet(), edge queries, multi-edge, curved edges
3
3
 
4
- const r = param("Fillet Radius", 3, { min: 0.5, max: 8, unit: "mm" });
4
+ const r = Param.number("Fillet Radius", 3, { min: 0.5, max: 8, unit: "mm" });
5
5
 
6
6
  // ── 1. Basic: fillet all edges of a box ─────────────────────────────────────
7
7
  const simpleBox = box(30, 20, 15);
@@ -16,7 +16,7 @@ const vertFilleted = fillet(tallBox, r, { parallel: [0, 0, 1], convex: true });
16
16
 
17
17
  // ── 4. Boolean result: fillet the sharp edges after a cut ────────────────────
18
18
  const base = box(40, 30, 20);
19
- const cutter = cylinder(25, 10, 10, 32, true).translate(20, 15, 10);
19
+ const cutter = cylinder(25, 10, 10, 32).translate(20, 15, 10);
20
20
  const cutPart = difference(base, cutter);
21
21
  const cutFilleted = fillet(cutPart, 2, { convex: true, minLength: 3 });
22
22
 
@@ -40,8 +40,8 @@ export function buildFoldedServicePanelCoverPart() {
40
40
  .flange('right', { length: 18, angleDeg: 90 })
41
41
  .flange('bottom', { length: 18, angleDeg: 90 })
42
42
  .flange('left', { length: 18, angleDeg: 90 })
43
- .cutout('panel', rect(72, 36, true), { selfAnchor: 'center' })
44
- .cutout('flange-right', roundedRect(26, 10, 5, true), { selfAnchor: 'center' });
43
+ .cutout('panel', rect(72, 36), { selfAnchor: 'center' })
44
+ .cutout('flange-right', roundedRect(26, 10, 5), { selfAnchor: 'center' });
45
45
 
46
46
  const holes = [
47
47
  mountingHole(2.2, -68, -37),
@@ -1,10 +1,10 @@
1
1
  // Tier 1 gears demo: spur pair + ring gear + rack gear
2
2
 
3
- const moduleSize = param("Module", 1.25, { min: 0.6, max: 3.0, step: 0.05 });
4
- const pinionTeeth = param("Pinion Teeth", 14, { min: 8, max: 28, integer: true });
5
- const drivenTeeth = param("Driven Teeth", 42, { min: 16, max: 90, integer: true });
6
- const backlash = param("Backlash", 0.05, { min: 0, max: 0.2, step: 0.01, unit: "mm" });
7
- const faceWidth = param("Face Width", 10, { min: 4, max: 18, unit: "mm" });
3
+ const moduleSize = Param.number("Module", 1.25, { min: 0.6, max: 3.0, step: 0.05 });
4
+ const pinionTeeth = Param.number("Pinion Teeth", 14, { min: 8, max: 28, integer: true });
5
+ const drivenTeeth = Param.number("Driven Teeth", 42, { min: 16, max: 90, integer: true });
6
+ const backlash = Param.number("Backlash", 0.05, { min: 0, max: 0.2, step: 0.01, unit: "mm" });
7
+ const faceWidth = Param.number("Face Width", 10, { min: 4, max: 18, unit: "mm" });
8
8
 
9
9
  const pair = lib.gearPair({
10
10
  pinion: {
@@ -1,6 +1,6 @@
1
1
  // Test assembly grouping — nested group format
2
- const baseW = param("Base Width", 100, { min: 60, max: 200, unit: "mm" });
3
- const baseD = param("Base Depth", 80, { min: 40, max: 150, unit: "mm" });
2
+ const baseW = Param.number("Base Width", 100, { min: 60, max: 200, unit: "mm" });
3
+ const baseD = Param.number("Base Depth", 80, { min: 40, max: 150, unit: "mm" });
4
4
 
5
5
  // Bed assembly
6
6
  const bedPlate = box(baseW, baseD, 5).color('#666666');
@@ -14,7 +14,7 @@ const crossBar = box(baseW + 20, 5, 5).translate(-10, baseD / 2, 63).color('#aaa
14
14
 
15
15
  // Extruder (intentionally overlaps crossbar — intra-group collision)
16
16
  const nozzle = cylinder(15, 4).translate(baseW / 2, baseD / 2, 48).color('#ff8800');
17
- const heatsink = box(20, 20, 10, true).translate(baseW / 2, baseD / 2, 60).color('#cccccc');
17
+ const heatsink = box(20, 20, 10).translate(baseW / 2, baseD / 2, 60).color('#cccccc');
18
18
 
19
19
  return [
20
20
  { name: "Bed Assembly", group: [
@@ -6,7 +6,7 @@
6
6
  // Use union when you need a single solid (e.g., to subtract from something).
7
7
  // Use group when you want parts to move together but stay visually distinct.
8
8
 
9
- const base = box(60, 60, 5, true).color('#888888');
9
+ const base = box(60, 60, 5).color('#888888');
10
10
  const col = cylinder(30, 5).color('#cc4444')
11
11
  .attachTo(base, 'top', 'bottom');
12
12
 
@@ -29,4 +29,8 @@ highlight(b.edge('top-right'), { color: 'blue', label: 'top-right edge' });
29
29
  // Shape highlight — transparent colored overlay on the entire shape
30
30
  highlight(b, { color: '#ff00ff' });
31
31
 
32
+ // Intermediary shape highlight — snapshot a temporary shape that is not returned
33
+ const temporary = b.translate(40, 0, 0);
34
+ highlight(temporary, { color: '#00ffff', label: 'temporary snapshot' });
35
+
32
36
  return b;
@@ -16,7 +16,7 @@ export default class PillarPair {
16
16
  }
17
17
 
18
18
  build() {
19
- const pillar = box(6, 6, this.height, true);
19
+ const pillar = box(6, 6, this.height);
20
20
  return union(
21
21
  pillar.translate(-this.spacing / 2, 0, this.height / 2),
22
22
  pillar.translate(this.spacing / 2, 0, this.height / 2),
@@ -2,8 +2,8 @@ import { box, union } from "forgecad";
2
2
  import PillarPair, { capHeight } from "./js-module-pillars.js";
3
3
 
4
4
  export function buildAssembly() {
5
- const base = box(40, 18, 4, true);
5
+ const base = box(40, 18, 4);
6
6
  const pillars = new PillarPair(24, 12).build().translate(0, 0, 2);
7
- const cap = box(14, 18, capHeight, true).translate(0, 0, 17);
7
+ const cap = box(14, 18, capHeight).translate(0, 0, 17);
8
8
  return union(base, pillars, cap).color("#d6a86a");
9
9
  }
@@ -3,9 +3,9 @@
3
3
  // Imports an external STL mesh and intersects it with a parametric
4
4
  // slat pattern to create a layered sculpture effect.
5
5
 
6
- const slatCount = param("Slat count", 16, { min: 4, max: 40 });
7
- const slatThickness = param("Slat thickness", 1.2, { min: 0.4, max: 3, unit: "mm" });
8
- const gap = param("Gap", 1.5, { min: 0.5, max: 5, unit: "mm" });
6
+ const slatCount = Param.number("Slat count", 16, { min: 4, max: 40 });
7
+ const slatThickness = Param.number("Slat thickness", 1.2, { min: 0.4, max: 3, unit: "mm" });
8
+ const gap = Param.number("Gap", 1.5, { min: 0.5, max: 5, unit: "mm" });
9
9
 
10
10
  // Import an external mesh file
11
11
  const mesh = importMesh("assets/sphere.stl");
@@ -19,7 +19,7 @@ const slats = [];
19
19
  for (let i = 0; i < slatCount; i++) {
20
20
  const y = center[1] - (slatCount - 1) * pitch / 2 + i * pitch;
21
21
  slats.push(
22
- box(size[0] + 10, slatThickness, size[2] + 10, true)
22
+ box(size[0] + 10, slatThickness, size[2] + 10)
23
23
  .translate(center[0], y, center[2])
24
24
  );
25
25
  }
@@ -1,8 +1,8 @@
1
1
  // Patterns — linearPattern and circularPattern for repeating shapes.
2
2
 
3
- const count = param("Count", 6, { min: 2, max: 12, integer: true });
4
- const spacing = param("Spacing", 20, { min: 10, max: 40, unit: "mm" });
5
- const radius = param("Ring Radius", 40, { min: 20, max: 80, unit: "mm" });
3
+ const count = Param.number("Count", 6, { min: 2, max: 12, integer: true });
4
+ const spacing = Param.number("Spacing", 20, { min: 10, max: 40, unit: "mm" });
5
+ const radius = Param.number("Ring Radius", 40, { min: 20, max: 80, unit: "mm" });
6
6
 
7
7
  // --- linearPattern: repeat along a direction ---
8
8
  const peg = cylinder(15, 4).color('#4488cc');
@@ -1,14 +1,14 @@
1
1
  // pointAlong() — orient a cylinder's axis without thinking about Euler angles.
2
2
  //
3
3
  // Cylinders default to Z-up. To lay one along X or Y:
4
- // ❌ cylinder(80, 5).rotate(90, 0, 0) — which axis? confusing
4
+ // ❌ cylinder(80, 5).rotateX(90) — works, but less readable
5
5
  // ✅ cylinder(80, 5).pointAlong([0, 1, 0]) — "point along Y"
6
6
  //
7
7
  // After pointAlong, the cylinder starts at origin and extends in that direction.
8
8
  // Always call pointAlong BEFORE translate/attachTo.
9
9
 
10
- const len = param("Length", 80, { min: 30, max: 150, unit: "mm" });
11
- const r = param("Radius", 5, { min: 2, max: 15, unit: "mm" });
10
+ const len = Param.number("Length", 80, { min: 30, max: 150, unit: "mm" });
11
+ const r = Param.number("Radius", 5, { min: 2, max: 15, unit: "mm" });
12
12
  const spacing = 40;
13
13
 
14
14
  // Default: along +Z (up)
@@ -4,10 +4,10 @@
4
4
  // - direct 3D helper (`lib.profile2020BSlot6`)
5
5
  // - parameterized technical dimensions
6
6
 
7
- const length = param("Length", 220, { min: 40, max: 800, unit: "mm" });
8
- const slotDepth = param("Slot Depth", 5.5, { min: 4.6, max: 6.6, step: 0.1, unit: "mm" });
9
- const slotInner = param("Slot Inner Width", 8.2, { min: 7, max: 10.5, step: 0.1, unit: "mm" });
10
- const centerBore = param("Center Bore", 5.5, { min: 0, max: 6.5, step: 0.1, unit: "mm" });
7
+ const length = Param.number("Length", 220, { min: 40, max: 800, unit: "mm" });
8
+ const slotDepth = Param.number("Slot Depth", 5.5, { min: 4.6, max: 6.6, step: 0.1, unit: "mm" });
9
+ const slotInner = Param.number("Slot Inner Width", 8.2, { min: 7, max: 10.5, step: 0.1, unit: "mm" });
10
+ const centerBore = Param.number("Center Bore", 5.5, { min: 0, max: 6.5, step: 0.1, unit: "mm" });
11
11
 
12
12
  const profile2d = lib.profile2020BSlot6Profile({
13
13
  slotInnerWidth: slotInner,
@@ -16,7 +16,6 @@ const profile2d = lib.profile2020BSlot6Profile({
16
16
  });
17
17
 
18
18
  const extrusion = lib.profile2020BSlot6(length, {
19
- center: true,
20
19
  slotDepth,
21
20
  slotInnerWidth: slotInner,
22
21
  centerBoreDia: centerBore,
@@ -29,7 +29,7 @@ let body = constrainedSketch()
29
29
  // Subtract center bore
30
30
  body = body.subtract(circle2d(R_CORE_IN))
31
31
 
32
- const solidCutRectangle = rect(200, 8, true);
32
+ const solidCutRectangle = rect(200, 8);
33
33
  const solidRing = circle2d(R_CORE)
34
34
  .subtract(circle2d(R_CORE_IN))
35
35
  .subtract(solidCutRectangle)
@@ -26,33 +26,33 @@ const wheelbase = 250;
26
26
  const groundClearance = 26;
27
27
  const bodyZ = wheelRadius + groundClearance + chassisHeight * 0.5;
28
28
 
29
- const baseDeck = box(chassisLength, chassisWidth, chassisHeight, true)
29
+ const baseDeck = box(chassisLength, chassisWidth, chassisHeight)
30
30
  .translate(0, 0, bodyZ);
31
31
 
32
- const roofPod = box(roofLength, roofWidth, roofHeight, true)
32
+ const roofPod = box(roofLength, roofWidth, roofHeight)
33
33
  .translate(20, 0, bodyZ + 40);
34
34
 
35
35
  const bumper = union(
36
- box(54, bumperWidth, bumperDepth, true).translate(chassisLength * 0.5 - 18, 0, wheelRadius + 6),
37
- box(bumperLength, bumperWidth - 42, bumperDepth * 0.7, true).translate(chassisLength * 0.5 + 46, 0, wheelRadius - 10),
36
+ box(54, bumperWidth, bumperDepth).translate(chassisLength * 0.5 - 18, 0, wheelRadius + 6),
37
+ box(bumperLength, bumperWidth - 42, bumperDepth * 0.7).translate(chassisLength * 0.5 + 46, 0, wheelRadius - 10),
38
38
  ).color('#c8742b');
39
39
 
40
40
  const sensorMast = union(
41
- cylinder(92, 10, undefined, 40, true).translate(58, 0, bodyZ + 78),
42
- box(78, 34, 26, true).translate(88, 0, bodyZ + 126),
41
+ cylinder(92, 10, undefined, 40).translate(58, 0, bodyZ + 78),
42
+ box(78, 34, 26).translate(88, 0, bodyZ + 126),
43
43
  ).color('#d7dee8');
44
44
 
45
45
  const chassis = union(baseDeck, roofPod)
46
46
  .color('#60707d');
47
47
 
48
48
  const wheelTire = difference(
49
- cylinder(wheelWidth, wheelRadius, undefined, 64, true).pointAlong([0, 1, 0]),
50
- cylinder(wheelWidth + 2, wheelRadius * 0.56, undefined, 48, true).pointAlong([0, 1, 0]),
49
+ cylinder(wheelWidth, wheelRadius, undefined, 64).pointAlong([0, 1, 0]),
50
+ cylinder(wheelWidth + 2, wheelRadius * 0.56, undefined, 48).pointAlong([0, 1, 0]),
51
51
  ).color('#1d2329');
52
52
 
53
53
  const wheelRim = union(
54
- cylinder(wheelWidth * 0.86, wheelRadius * 0.52, undefined, 40, true).pointAlong([0, 1, 0]),
55
- cylinder(wheelWidth * 1.02, wheelRadius * 0.16, undefined, 28, true).pointAlong([0, 1, 0]),
54
+ cylinder(wheelWidth * 0.86, wheelRadius * 0.52, undefined, 40).pointAlong([0, 1, 0]),
55
+ cylinder(wheelWidth * 1.02, wheelRadius * 0.16, undefined, 28).pointAlong([0, 1, 0]),
56
56
  ).color('#b8c5d3');
57
57
 
58
58
  const wheel = group(
@@ -8,7 +8,7 @@
8
8
  const W = 80, D = 50, H = 30;
9
9
 
10
10
  // ── Base enclosure ────────────────────────────────────────────────────────────
11
- const body = roundedRect(W, D, 4, true)
11
+ const body = roundedRect(W, D, 4)
12
12
  .extrude(H)
13
13
  .color('#c8cdd6');
14
14
 
@@ -41,7 +41,7 @@ const vents = ribSketch
41
41
  .color('#1a1a2e');
42
42
 
43
43
  // ── Badge recess on the front face ────────────────────────────────────────────
44
- const badge = roundedRect(36, 12, 2, true)
44
+ const badge = roundedRect(36, 12, 2)
45
45
  .onFace(body, 'front', { v: 4, protrude: 0.05 })
46
46
  .extrude(1.5)
47
47
  .color('#2563eb');
@@ -22,8 +22,8 @@
22
22
  // A "T" cross-section: wide base + narrow stem. The boolean creates one
23
23
  // connected region; we pick it with the regions() API.
24
24
 
25
- const base = rect(120, 20, true); // centered at origin
26
- const stem = rect(30, 50, true).translate(0, 35); // stem centered above base
25
+ const base = rect(120, 20); // centered at origin
26
+ const stem = rect(30, 50).translate(0, 35); // stem centered above base
27
27
  const tShape = base.add(stem);
28
28
  const [tRegion] = tShape.regions(); // one connected T region
29
29
  const tPart = tRegion.extrude(6); // extrude the T profile
@@ -32,8 +32,8 @@ const tPart = tRegion.extrude(6); // extrude the T profile
32
32
  // A 100×40 plate with a 60×20 hole punched in the center, leaving a frame.
33
33
  // Pick the frame ring as one region using a corner seed.
34
34
 
35
- const plate = rect(100, 40, true); // centered at origin
36
- const hole = rect(60, 20, true); // centered cutout
35
+ const plate = rect(100, 40); // centered at origin
36
+ const hole = rect(60, 20); // centered cutout
37
37
  const frame = plate.subtract(hole);
38
38
 
39
39
  // The seed [45, 15] is in the top-right corner — inside the frame wall, outside the hole.
@@ -1,7 +1,7 @@
1
1
  // Compare common sketch-rounding strategies on the same roof profile.
2
2
  // Only the selective fillet keeps the lower roof corners sharp.
3
3
 
4
- const radius = param("Radius", 14, { min: 4, max: 24, unit: "mm" });
4
+ const radius = Param.number("Radius", 14, { min: 4, max: 24, unit: "mm" });
5
5
  const gap = 120;
6
6
  const bodyWidth = 90;
7
7
  const bodyHeight = 44;
@@ -10,7 +10,7 @@
10
10
  * Drag the Blend Weight slider to shift the blend shape!
11
11
  */
12
12
 
13
- const blendWeight = param('Blend Weight', 0.5, { min: 0.1, max: 0.9, step: 0.05 });
13
+ const blendWeight = Param.number('Blend Weight', 0.5, { min: 0.1, max: 0.9, step: 0.05 });
14
14
 
15
15
  // ─── 1. Gothic arch — two arcs meeting at a point ──────────────────────────
16
16
  // Two arcs sharing a crown point — smooth by construction since both
@@ -2,14 +2,14 @@
2
2
  // Demonstrates transitionCurve(), transitionSurface(), connectEdges(),
3
3
  // and weighted blending (G1 + G2 continuity).
4
4
 
5
- const weight = param("Weight A", 1.0, { min: 0.1, max: 5.0, step: 0.1 });
6
- const weightB = param("Weight B", 1.0, { min: 0.1, max: 5.0, step: 0.1 });
7
- const radius = param("Tube Radius", 1.5, { min: 0.3, max: 5, unit: "mm" });
5
+ const weight = Param.number("Weight A", 1.0, { min: 0.1, max: 5.0, step: 0.1 });
6
+ const weightB = Param.number("Weight B", 1.0, { min: 0.1, max: 5.0, step: 0.1 });
7
+ const radius = Param.number("Tube Radius", 1.5, { min: 0.3, max: 5, unit: "mm" });
8
8
 
9
9
  // Small anchor block at a point (visual reference for where the tube connects)
10
10
  function anchor(pos) {
11
11
  const s = 3;
12
- return box(s, s, s, true).translate(pos[0], pos[1], pos[2]);
12
+ return box(s, s, s).translate(pos[0], pos[1], pos[2]);
13
13
  }
14
14
 
15
15
  // ── 1. L-bend: 90-degree pipe turn ──────────────────────────────