forgecad 0.6.3 → 0.7.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 (193) hide show
  1. package/README.md +2 -11
  2. package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-DAu1C1ST.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-Gc_BCdqC.js} +269 -143
  5. package/dist/assets/EditorApp-D9bJvtf7.js +11338 -0
  6. package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-DG1-oUSV.css} +459 -87
  7. package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-CEO8XbV8.js} +2 -4
  8. package/dist/assets/LandingPage-CdCuEOdC.js +451 -0
  9. package/dist/assets/PricingPage-BSrxu6d7.js +232 -0
  10. package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-FUCSIRq6.js} +129 -5
  11. package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-KoR0SNKq.js} +6770 -2914
  12. package/dist/assets/{index-2hfs_ub0.css → index-CyVd1D4D.css} +227 -53
  13. package/dist/assets/{Viewport-CoB46f5R.js → index-wTEK39at.js} +31385 -6439
  14. package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
  15. package/dist/assets/{manifold-CqNMHHKO.js → manifold-B1sGWdYk.js} +4 -3
  16. package/dist/assets/{manifold-Cce9wRFz.js → manifold-D7o0N50J.js} +1 -1
  17. package/dist/assets/{manifold-D6BeHIOo.js → manifold-G5sBaXzi.js} +1 -1
  18. package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-DYcRHhv9.js} +6798 -3341
  19. package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
  20. package/dist/docs-raw/generated/assembly.md +691 -112
  21. package/dist/docs-raw/generated/concepts.md +1225 -1400
  22. package/dist/docs-raw/generated/core.md +464 -1412
  23. package/dist/docs-raw/generated/curves.md +593 -117
  24. package/dist/docs-raw/generated/lib.md +38 -748
  25. package/dist/docs-raw/generated/output.md +139 -245
  26. package/dist/docs-raw/generated/sheet-metal.md +473 -21
  27. package/dist/docs-raw/generated/sketch.md +553 -349
  28. package/dist/docs-raw/generated/viewport.md +345 -303
  29. package/dist/docs-raw/generated/wood.md +104 -0
  30. package/dist/index.html +2 -2
  31. package/dist/sitemap.xml +6 -6
  32. package/dist-cli/chunk-PZ5AY32C.js +10 -0
  33. package/dist-cli/chunk-PZ5AY32C.js.map +1 -0
  34. package/dist-cli/forgecad.js +9435 -5407
  35. package/dist-cli/forgecad.js.map +1 -0
  36. package/dist-cli/solver-FV7TJZGI.js +365 -0
  37. package/dist-cli/solver-FV7TJZGI.js.map +1 -0
  38. package/dist-skill/CONTEXT.md +3186 -7145
  39. package/dist-skill/SKILL-dev.md +21 -63
  40. package/dist-skill/SKILL.md +12 -56
  41. package/dist-skill/docs/API/core/concepts.md +16 -98
  42. package/dist-skill/docs/CLI/export.md +91 -0
  43. package/dist-skill/docs/CLI/projects.md +107 -0
  44. package/dist-skill/docs/CLI/studio_publishing.md +52 -0
  45. package/dist-skill/docs/CLI/validation.md +66 -0
  46. package/dist-skill/docs/generated/assembly.md +691 -112
  47. package/dist-skill/docs/generated/core.md +464 -1412
  48. package/dist-skill/docs/generated/curves.md +593 -117
  49. package/dist-skill/docs/generated/lib.md +38 -748
  50. package/dist-skill/docs/generated/output.md +139 -245
  51. package/dist-skill/docs/generated/sheet-metal.md +473 -21
  52. package/dist-skill/docs/generated/sketch.md +553 -349
  53. package/dist-skill/docs/generated/viewport.md +345 -303
  54. package/dist-skill/docs/generated/wood.md +104 -0
  55. package/dist-skill/docs/guides/coordinate-system.md +11 -17
  56. package/dist-skill/docs/guides/geometry-conventions.md +13 -70
  57. package/dist-skill/docs/guides/modeling-recipes.md +22 -195
  58. package/dist-skill/docs/guides/positioning.md +88 -147
  59. package/dist-skill/docs-dev/API/core/concepts.md +51 -0
  60. package/dist-skill/docs-dev/API/core/sdf-advanced.md +92 -0
  61. package/dist-skill/docs-dev/API/core/sdf-primitives.md +58 -0
  62. package/dist-skill/docs-dev/API/core/sdf-workflow.md +42 -0
  63. package/dist-skill/docs-dev/CLI/export.md +91 -0
  64. package/dist-skill/docs-dev/CLI/projects.md +107 -0
  65. package/dist-skill/docs-dev/CLI/studio_publishing.md +52 -0
  66. package/dist-skill/docs-dev/CLI/validation.md +66 -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 +1 -3
  70. package/dist-skill/docs-dev/generated/assembly.md +771 -0
  71. package/dist-skill/docs-dev/generated/core.md +775 -0
  72. package/dist-skill/docs-dev/generated/curves.md +688 -0
  73. package/dist-skill/docs-dev/generated/lib.md +50 -0
  74. package/dist-skill/docs-dev/generated/output.md +234 -0
  75. package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
  76. package/dist-skill/docs-dev/generated/sketch.md +801 -0
  77. package/dist-skill/docs-dev/generated/viewport.md +486 -0
  78. package/dist-skill/docs-dev/generated/wood.md +104 -0
  79. package/dist-skill/docs-dev/guides/coordinate-system.md +46 -0
  80. package/dist-skill/docs-dev/guides/geometry-conventions.md +52 -0
  81. package/dist-skill/docs-dev/guides/modeling-recipes.md +77 -0
  82. package/dist-skill/docs-dev/guides/positioning.md +151 -0
  83. package/dist-skill/{docs → docs-dev}/guides/skill-maintenance.md +21 -10
  84. package/dist-skill/{docs → docs-dev}/internals/compiler.md +5 -6
  85. package/dist-skill/{docs → docs-dev}/internals/constraint-solver-quality.md +0 -1
  86. package/dist-skill/{docs → docs-dev}/internals/constraint-solver.md +0 -1
  87. package/dist-skill/{docs → docs-dev}/internals/sketch-2d-pipeline.md +2 -3
  88. package/examples/api/attachTo-basics.forge.js +5 -5
  89. package/examples/api/boolean-operations.forge.js +3 -3
  90. package/examples/api/bounding-box-visualizer.forge.js +2 -2
  91. package/examples/api/clone-duplicate.forge.js +1 -1
  92. package/examples/api/colors-union-vs-array.forge.js +6 -6
  93. package/examples/api/connector-assembly.forge.js +4 -4
  94. package/examples/api/connector-basics.forge.js +2 -2
  95. package/examples/api/extrude-options.forge.js +4 -10
  96. package/examples/api/feature-created-faces.forge.js +6 -10
  97. package/examples/api/fillet-showcase.forge.js +1 -1
  98. package/examples/api/folded-service-panel-cover.forge.js +2 -2
  99. package/examples/api/group-test.forge.js +1 -1
  100. package/examples/api/group-vs-union.forge.js +1 -1
  101. package/examples/api/highlight-debug.forge.js +4 -0
  102. package/examples/api/js-module-pillars.js +1 -1
  103. package/examples/api/js-module-scene.js +2 -2
  104. package/examples/api/mesh-import-slats.forge.js +1 -1
  105. package/examples/api/pointAlong-orientation.forge.js +1 -1
  106. package/examples/api/profile-2020-b-slot6.forge.js +0 -1
  107. package/examples/api/route-perimeter-flange.forge.js +1 -1
  108. package/examples/api/sdf-rover-demo.forge.js +10 -10
  109. package/examples/api/sketch-on-face-demo.forge.js +2 -2
  110. package/examples/api/sketch-regions.forge.js +4 -4
  111. package/examples/api/transition-curves.forge.js +1 -1
  112. package/examples/api/variable-sweep-pure-sdf-test.forge.js +162 -0
  113. package/examples/api/variable-sweep-test.forge.js +2 -2
  114. package/examples/api/wood-joinery.forge.js +60 -0
  115. package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +3 -3
  116. package/examples/compiler-corpus/fastener-plate-variants.forge.js +2 -2
  117. package/examples/experiments/drone-arm.forge.js +53 -0
  118. package/examples/furniture/adjustable-table.forge.js +2 -2
  119. package/examples/furniture/bathroom.forge.js +11 -11
  120. package/examples/furniture/chair.forge.js +1 -1
  121. package/examples/generative/crystal-growth.forge.js +2 -2
  122. package/examples/generative/frost-spires.forge.js +3 -3
  123. package/examples/generative/golden-spiral-tower.forge.js +3 -3
  124. package/examples/mechanical/3d-printer.forge.js +28 -28
  125. package/examples/mechanical/5-finger-robot-hand.forge.js +15 -15
  126. package/examples/mechanical/airplane-propeller.forge.js +2 -2
  127. package/examples/mechanical/fillet-enclosure.forge.js +1 -1
  128. package/examples/mechanical/headphone-hanger-v2.forge.js +2 -2
  129. package/examples/mechanical/robot_hand.forge.js +15 -15
  130. package/examples/mechanical/robot_hand_2.forge.js +9 -9
  131. package/examples/products/bottle.forge.js +1 -1
  132. package/examples/products/chess-set.forge.js +19 -19
  133. package/examples/products/classical-piano.forge.js +11 -11
  134. package/examples/products/clock.forge.js +12 -12
  135. package/examples/products/iphone.forge.js +8 -8
  136. package/examples/products/laptop.forge.js +15 -15
  137. package/examples/products/liquid-soap-dispenser.forge.js +18 -18
  138. package/examples/products/origami-fish.forge.js +8 -6
  139. package/examples/products/spiderman-cake.forge.js +4 -4
  140. package/examples/toolbox/bolted-joint.forge.js +2 -2
  141. package/package.json +7 -4
  142. package/dist/assets/EditorApp-B-vQvgam.js +0 -9888
  143. package/dist/assets/LandingPage-C5n9hDXI.js +0 -322
  144. package/dist/assets/PublishedModelPage-Dt7PCVBj.js +0 -146
  145. package/dist/assets/__vite-browser-external-CURh0WXD.js +0 -8
  146. package/dist/assets/deserializeRunResult-BLAFoiE0.js +0 -19365
  147. package/dist/assets/index-1CYp3zUp.js +0 -1455
  148. package/dist/docs-raw/CLI.md +0 -865
  149. package/dist-skill/docs/API/API.md +0 -1666
  150. package/dist-skill/docs/API/README.md +0 -37
  151. package/dist-skill/docs/API/assembly/assembly.md +0 -617
  152. package/dist-skill/docs/API/core/edge-queries.md +0 -130
  153. package/dist-skill/docs/API/core/parameters.md +0 -122
  154. package/dist-skill/docs/API/core/reserved-terms.md +0 -137
  155. package/dist-skill/docs/API/core/sdf.md +0 -326
  156. package/dist-skill/docs/API/core/skill-cli.md +0 -194
  157. package/dist-skill/docs/API/core/skill-guide.md +0 -205
  158. package/dist-skill/docs/API/core/specs.md +0 -186
  159. package/dist-skill/docs/API/core/topology.md +0 -372
  160. package/dist-skill/docs/API/entities.md +0 -268
  161. package/dist-skill/docs/API/output/bom.md +0 -58
  162. package/dist-skill/docs/API/output/brep-export.md +0 -87
  163. package/dist-skill/docs/API/output/dimensions.md +0 -67
  164. package/dist-skill/docs/API/output/export.md +0 -110
  165. package/dist-skill/docs/API/output/gcode.md +0 -195
  166. package/dist-skill/docs/API/runtime/viewport.md +0 -420
  167. package/dist-skill/docs/API/sheet-metal/sheet-metal.md +0 -185
  168. package/dist-skill/docs/API/sketch/anchor.md +0 -37
  169. package/dist-skill/docs/API/sketch/booleans.md +0 -91
  170. package/dist-skill/docs/API/sketch/core.md +0 -73
  171. package/dist-skill/docs/API/sketch/extrude.md +0 -62
  172. package/dist-skill/docs/API/sketch/on-face.md +0 -104
  173. package/dist-skill/docs/API/sketch/operations.md +0 -78
  174. package/dist-skill/docs/API/sketch/path.md +0 -75
  175. package/dist-skill/docs/API/sketch/primitives.md +0 -146
  176. package/dist-skill/docs/API/sketch/regions.md +0 -80
  177. package/dist-skill/docs/API/sketch/text.md +0 -108
  178. package/dist-skill/docs/API/sketch/transforms.md +0 -65
  179. package/dist-skill/docs/API/toolbox/fasteners.md +0 -129
  180. package/dist-skill/docs/CLI.md +0 -865
  181. package/dist-skill/docs/INDEX.md +0 -94
  182. package/dist-skill/docs/RELEASING.md +0 -55
  183. package/dist-skill/docs/cli-monetization.md +0 -111
  184. package/dist-skill/docs/deployment.md +0 -281
  185. package/dist-skill/docs/generated/concepts.md +0 -2112
  186. package/dist-skill/docs/internals/shape-from-slices.md +0 -152
  187. package/dist-skill/docs/platform/admin.md +0 -45
  188. package/dist-skill/docs/platform/architecture.md +0 -79
  189. package/dist-skill/docs/platform/auth.md +0 -110
  190. package/dist-skill/docs/platform/email.md +0 -67
  191. package/dist-skill/docs/platform/projects.md +0 -111
  192. package/dist-skill/docs/platform/sharing.md +0 -90
  193. package/dist-skill/docs/runbook.md +0 -345
@@ -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
  }
@@ -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,7 +1,7 @@
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.
@@ -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.
@@ -9,7 +9,7 @@ const radius = param("Tube Radius", 1.5, { min: 0.3, max: 5, unit: "mm" });
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 ──────────────────────────────
@@ -0,0 +1,162 @@
1
+ // Pure-SDF reference for the variableSweep taper test.
2
+ // If this renders with clean sidewalls while variable-sweep-test.forge.js does not,
3
+ // the artifact is in variableSweep()'s spine field construction rather than the
4
+ // shared SDF meshing pipeline.
5
+
6
+ const controlPoints = [
7
+ [0, 0, 0],
8
+ [20, 0, 10],
9
+ [40, 10, 20],
10
+ [60, 10, 30],
11
+ ];
12
+
13
+ const spine = spline3d(controlPoints, { tension: 0.4 });
14
+ const pathPoints = spine.sample(160);
15
+
16
+ const cumulativeLengths = [0];
17
+ for (let index = 1; index < pathPoints.length; index += 1) {
18
+ const [ax, ay, az] = pathPoints[index - 1];
19
+ const [bx, by, bz] = pathPoints[index];
20
+ cumulativeLengths.push(
21
+ cumulativeLengths[cumulativeLengths.length - 1] + Math.hypot(bx - ax, by - ay, bz - az),
22
+ );
23
+ }
24
+ const totalLength = cumulativeLengths[cumulativeLengths.length - 1];
25
+
26
+ const smallRadius = 3;
27
+ const largeRadius = 8;
28
+ const maxRadius = largeRadius;
29
+ const pad = maxRadius + 4;
30
+
31
+ let minX = Infinity;
32
+ let minY = Infinity;
33
+ let minZ = Infinity;
34
+ let maxX = -Infinity;
35
+ let maxY = -Infinity;
36
+ let maxZ = -Infinity;
37
+ for (const [x, y, z] of pathPoints) {
38
+ minX = Math.min(minX, x);
39
+ minY = Math.min(minY, y);
40
+ minZ = Math.min(minZ, z);
41
+ maxX = Math.max(maxX, x);
42
+ maxY = Math.max(maxY, y);
43
+ maxZ = Math.max(maxZ, z);
44
+ }
45
+
46
+ const embeddedPath = JSON.stringify(pathPoints);
47
+ const embeddedArc = JSON.stringify(cumulativeLengths);
48
+
49
+ const pureSweepSdf = new Function(`
50
+ return (x, y, z) => (() => {
51
+ const pts = ${embeddedPath};
52
+ const arc = ${embeddedArc};
53
+ const totalLength = ${totalLength};
54
+ const smallRadius = ${smallRadius};
55
+ const largeRadius = ${largeRadius};
56
+
57
+ function clamp(v, lo, hi) {
58
+ return Math.max(lo, Math.min(hi, v));
59
+ }
60
+
61
+ function catmullScalar(p0, p1, p2, p3, t0, t1, t2, t3, t) {
62
+ const dt = Math.max(t2 - t1, 1e-9);
63
+ const local = clamp((t - t1) / dt, 0, 1);
64
+ const tt = local * local;
65
+ const ttt = tt * local;
66
+ const m1 = (p2 - p0) / Math.max(t2 - t0, 1e-9);
67
+ const m2 = (p3 - p1) / Math.max(t3 - t1, 1e-9);
68
+ const h00 = 2 * ttt - 3 * tt + 1;
69
+ const h10 = ttt - 2 * tt + local;
70
+ const h01 = -2 * ttt + 3 * tt;
71
+ const h11 = ttt - tt;
72
+ return h00 * p1 + h10 * dt * m1 + h01 * p2 + h11 * dt * m2;
73
+ }
74
+
75
+ function radiusAt(t) {
76
+ const tc = clamp(t, 0, 1);
77
+ if (tc <= 0) return smallRadius;
78
+ if (tc >= 1) return smallRadius;
79
+ if (tc <= 0.5) return catmullScalar(smallRadius, smallRadius, largeRadius, smallRadius, -0.5, 0, 0.5, 1, tc);
80
+ return catmullScalar(smallRadius, largeRadius, smallRadius, smallRadius, 0, 0.5, 1, 1.5, tc);
81
+ }
82
+
83
+ let bestDist2 = Infinity;
84
+ let bestIndex = 0;
85
+ let bestSegmentT = 0;
86
+ let bestPoint = pts[0];
87
+
88
+ for (let index = 0; index < pts.length - 1; index += 1) {
89
+ const a = pts[index];
90
+ const b = pts[index + 1];
91
+ const dx = b[0] - a[0];
92
+ const dy = b[1] - a[1];
93
+ const dz = b[2] - a[2];
94
+ const len2 = dx * dx + dy * dy + dz * dz;
95
+ if (len2 < 1e-12) continue;
96
+
97
+ const px = x - a[0];
98
+ const py = y - a[1];
99
+ const pz = z - a[2];
100
+ const segT = clamp((px * dx + py * dy + pz * dz) / len2, 0, 1);
101
+ const qx = a[0] + dx * segT;
102
+ const qy = a[1] + dy * segT;
103
+ const qz = a[2] + dz * segT;
104
+ const rx = x - qx;
105
+ const ry = y - qy;
106
+ const rz = z - qz;
107
+ const dist2 = rx * rx + ry * ry + rz * rz;
108
+
109
+ if (dist2 < bestDist2) {
110
+ bestDist2 = dist2;
111
+ bestIndex = index;
112
+ bestSegmentT = segT;
113
+ bestPoint = [qx, qy, qz];
114
+ }
115
+ }
116
+
117
+ const radiusT = (arc[bestIndex] + (arc[bestIndex + 1] - arc[bestIndex]) * bestSegmentT) / Math.max(totalLength, 1e-9);
118
+ let sdfValue = Math.sqrt(bestDist2) - radiusAt(radiusT);
119
+
120
+ if (bestIndex === 0 && bestSegmentT <= 1e-6) {
121
+ const a = pts[0];
122
+ const b = pts[1];
123
+ const dx = b[0] - a[0];
124
+ const dy = b[1] - a[1];
125
+ const dz = b[2] - a[2];
126
+ const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
127
+ if (len > 1e-9) {
128
+ const tx = dx / len;
129
+ const ty = dy / len;
130
+ const tz = dz / len;
131
+ const startCap = -((x - a[0]) * tx + (y - a[1]) * ty + (z - a[2]) * tz);
132
+ sdfValue = Math.max(sdfValue, startCap);
133
+ }
134
+ } else if (bestIndex === pts.length - 2 && bestSegmentT >= 1 - 1e-6) {
135
+ const a = pts[pts.length - 2];
136
+ const b = pts[pts.length - 1];
137
+ const dx = b[0] - a[0];
138
+ const dy = b[1] - a[1];
139
+ const dz = b[2] - a[2];
140
+ const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
141
+ if (len > 1e-9) {
142
+ const tx = dx / len;
143
+ const ty = dy / len;
144
+ const tz = dz / len;
145
+ const endCap = (x - b[0]) * tx + (y - b[1]) * ty + (z - b[2]) * tz;
146
+ sdfValue = Math.max(sdfValue, endCap);
147
+ }
148
+ }
149
+
150
+ return sdfValue;
151
+ })();
152
+ `)();
153
+
154
+ const tapered = sdf.fromFunction(
155
+ pureSweepSdf,
156
+ {
157
+ min: [minX - pad, minY - pad, minZ - pad],
158
+ max: [maxX + pad, maxY + pad, maxZ + pad],
159
+ },
160
+ ).toShape({ edgeLength: 0.8 });
161
+
162
+ return tapered.color('#8899aa');
@@ -8,8 +8,8 @@ const spine = spline3d([
8
8
  [60, 10, 30],
9
9
  ], { tension: 0.4 });
10
10
 
11
- const smallCircle = circle2d(3, 24);
12
- const largeCircle = circle2d(8, 24);
11
+ const smallCircle = circle2d(3);
12
+ const largeCircle = circle2d(8);
13
13
 
14
14
  const tapered = variableSweep(spine, [
15
15
  { t: 0.0, profile: smallCircle },
@@ -0,0 +1,60 @@
1
+ // Wood Joinery — dado, rabbet, mortise & tenon
2
+ // Demonstrates joint cuts and assembled positioning.
3
+
4
+ const t = 18; // stock thickness
5
+
6
+ // ─── BOOKCASE CORNER ───────────────────────────────────────────
7
+ // Side panel lies flat: width=250 (X, depth), height=400 (Y, vertical), thickness=18 (Z, face)
8
+ const side = Wood.board(250, 400, t, { species: 'birch' });
9
+ const shelf = Wood.board(250, 300, t, { species: 'birch' });
10
+
11
+ // Dado: channel across the face of side for shelf to sit in
12
+ Wood.dado(side, shelf, { fromBottom: 150 });
13
+
14
+ // Rabbet: step on the back face for a back panel
15
+ Wood.rabbet(side, { edge: 'back', width: 6, depth: 9 });
16
+
17
+ // Assemble the bookcase corner:
18
+ // Side panel stands upright — rotate so Y (400mm) becomes vertical (Z)
19
+ const sideAssembled = side.shape.rotateX(90).color('#c4956a');
20
+
21
+ // Shelf sits in the dado channel
22
+ // After side.rotateX(90): side is in XZ plane, dado channel is at Z = -200 + 150 + 9 = -41
23
+ // Shelf needs its 18mm thickness edge in the dado
24
+ const shelfAssembled = shelf.shape
25
+ .rotateX(90) // stand upright
26
+ .rotateZ(90) // perpendicular to side
27
+ .translate(0, t, -200 + 150 + t / 2)
28
+ .color('#b8864e');
29
+
30
+ // ─── TABLE JOINT: MORTISE & TENON ──────────────────────────────
31
+ // Leg: width=45 (X), height=400 (Y, vertical), thickness=45 (Z, face for mortise)
32
+ // The mortise cuts into Z (45mm face), positioned along Y (400mm height)
33
+ const leg = Wood.board(45, 400, 45, { species: 'oak' });
34
+ const apron = Wood.board(300, 80, 20, { species: 'oak' });
35
+
36
+ Wood.mortiseAndTenon(leg, apron, {
37
+ style: 'blind',
38
+ position: { fromTop: 25 },
39
+ cornerRadius: 4,
40
+ });
41
+
42
+ // Tenon defaults: thickness = 20/3 ≈ 6.7mm, width = min(48, 320) = 48mm, length = 45*2/3 = 30mm
43
+
44
+ // Assemble: leg stands on its Y-axis (already vertical), apron connects to face
45
+ const legAssembled = leg.shape
46
+ .translate(500, 0, 0)
47
+ .color('#a07040');
48
+
49
+ // Apron: tenon protrudes from +X end. Rotate so it points into the leg's Z-face.
50
+ const apronAssembled = apron.shape
51
+ .rotateY(90) // tenon now points +Z (into leg face)
52
+ .translate(500, 200 - 25 - 24, 45) // align with mortise position on leg
53
+ .color('#8b6030');
54
+
55
+ return [
56
+ { name: 'Side (dado + rabbet)', shape: sideAssembled },
57
+ { name: 'Shelf', shape: shelfAssembled },
58
+ { name: 'Leg (mortise)', shape: legAssembled },
59
+ { name: 'Apron (tenon)', shape: apronAssembled },
60
+ ];
@@ -1,9 +1,9 @@
1
1
  // Guard part: shell + face-driven cuts + mirrored feet in one ordinary enclosure workflow.
2
2
 
3
- const base = roundedRect(120, 80, 10, true).extrude(36);
3
+ const base = roundedRect(120, 80, 10).extrude(36);
4
4
  const shell = base.shell(3, { openFaces: ['top'] });
5
5
 
6
- const displayCut = roundedRect(34, 18, 3, true)
6
+ const displayCut = roundedRect(34, 18, 3)
7
7
  .onFace(base, 'front', { u: 0, v: 8, protrude: 0.25, selfAnchor: 'center' })
8
8
  .extrude(10);
9
9
 
@@ -11,7 +11,7 @@ const cableCut = circle2d(7)
11
11
  .onFace(base, 'right', { u: -12, v: -8, protrude: 0.25, selfAnchor: 'center' })
12
12
  .extrude(10);
13
13
 
14
- const foot = roundedRect(18, 18, 4, true)
14
+ const foot = roundedRect(18, 18, 4)
15
15
  .onFace(base, 'bottom', { u: 36, v: 20, protrude: 0, selfAnchor: 'center' })
16
16
  .extrude(6);
17
17
 
@@ -1,11 +1,11 @@
1
1
  // Guard part: counterbore/countersink hole variants plus an up-to-face service pocket in one ordinary plate workflow.
2
2
 
3
- const plate = roundedRect(118, 76, 8, true).extrude(18);
3
+ const plate = roundedRect(118, 76, 8).extrude(18);
4
4
  const entryFace = plate.face('top');
5
5
  const exitFace = plate.face('bottom');
6
6
  const pocketExitFace = plate.face('side-top');
7
7
 
8
- const servicePocket = roundedRect(34, 18, 4, true)
8
+ const servicePocket = roundedRect(34, 18, 4)
9
9
  .onFace(plate, 'front', { u: 0, v: -2, selfAnchor: 'center' });
10
10
 
11
11
  const fastenerPlate = plate
@@ -0,0 +1,53 @@
1
+ // Smooth drone arm: flat rectangular mount → organic taper → circular motor mount
2
+ // Tests: cross-section morphing, S-curve sweep, shell, G2 transitions
3
+
4
+ // --- Spine: gentle S-curve from origin to motor mount ---
5
+ const armLength = 120;
6
+ const spine = spline3d([
7
+ [0, 0, 0],
8
+ [armLength * 0.3, 0, 8],
9
+ [armLength * 0.7, 0, -5],
10
+ [armLength, 0, 3],
11
+ ], { tension: 0.3 });
12
+
13
+ // --- Cross-sections ---
14
+
15
+ // Mount end: flat rectangle with rounded corners
16
+ const mountProfile = roundedRect(20, 6, 1.5);
17
+
18
+ // Mid-section: transitional rounded rectangle
19
+ const midProfile = roundedRect(14, 10, 4);
20
+
21
+ // Motor end: circle
22
+ const motorProfile = circle2d(8);
23
+
24
+ // --- Variable sweep: morph between profiles along spine ---
25
+ const solidArm = variableSweep(spine, [
26
+ { t: 0, profile: mountProfile },
27
+ { t: 0.4, profile: midProfile },
28
+ { t: 1, profile: motorProfile },
29
+ ], { edgeLength: 1.5 });
30
+
31
+ // --- Hollow it out ---
32
+ const arm = solidArm.shell(1.5);
33
+
34
+ // --- Mounting holes at the flat end ---
35
+ const mountHole = cylinder(10, 2).translate(0, 0, -5);
36
+ const mountHoles = union(
37
+ mountHole.translate(-6, 0, 0),
38
+ mountHole.translate(6, 0, 0),
39
+ );
40
+
41
+ // --- Motor mount ring at the far end ---
42
+ // Place at the end of the spine
43
+ const motorRing = difference(
44
+ cylinder(4, 10),
45
+ cylinder(4, 7),
46
+ ).translate(armLength, 0, 3);
47
+
48
+ const result = difference(
49
+ union(arm, motorRing),
50
+ mountHoles,
51
+ );
52
+
53
+ return result;
@@ -75,7 +75,7 @@ for (const [lx, ly] of legPositions) {
75
75
  // Through-hole in Y direction
76
76
  pinHoles.push(
77
77
  cylinder(outerLeg + 2, pinHoleR)
78
- .rotateAround([0, 0, 0], 90, 0, 0)
78
+ .rotateX(90)
79
79
  .translate(legCenterX, ly - 1, pz)
80
80
  );
81
81
  }
@@ -105,7 +105,7 @@ const shaftY = inset + outerLeg / 2;
105
105
  const shaftZ = legH - outerLegH + 25;
106
106
  const shaftLen = topW - 2 * inset;
107
107
  const shaft = cylinder(shaftLen, 0.8)
108
- .rotateAround([0, 0, 0], 0, 90, 0)
108
+ .rotateY(90)
109
109
  .translate(inset, shaftY, shaftZ);
110
110
 
111
111
  // Crank handle (on the right side)
@@ -51,7 +51,7 @@ const armLen = 250;
51
51
  const showerArmX = tubX + tubL * 0.6;
52
52
  const showerArmZ = wallT + glassH - 150;
53
53
  const showerArm = cylinder(armLen, 12)
54
- .rotateAround([0, 0, 0], 90, 0, 0)
54
+ .rotateX(90)
55
55
  .translate(showerArmX, roomD - wallT, showerArmZ);
56
56
 
57
57
  // Shower head — disc at end of arm
@@ -83,11 +83,11 @@ const bowl = bowlOuter.subtract(bowlInner)
83
83
  const tankW = toiletW * 0.85;
84
84
  const tankD = 150;
85
85
  const tankH = 320;
86
- const tank = box(tankW, tankD, tankH, true)
86
+ const tank = box(tankW, tankD, tankH)
87
87
  .translate(toiletCX, toiletY + toiletD - tankD / 2 - 10, wallT + toiletH / 2 + tankH / 2 - 80);
88
88
 
89
89
  // Lid — flat slab on top of tank
90
- const lid = box(tankW + 10, tankD + 10, 15, true)
90
+ const lid = box(tankW + 10, tankD + 10, 15)
91
91
  .translate(toiletCX, toiletY + toiletD - tankD / 2 - 10, wallT + toiletH / 2 + tankH - 80 + 7);
92
92
 
93
93
  // Seat — flat ring on top of bowl
@@ -109,14 +109,14 @@ const sinkZ = 850;
109
109
  const sinkCX = roomW - wallT - sinkD / 2 - 30;
110
110
  const sinkCY = roomD * 0.35;
111
111
 
112
- const basinOuter = box(sinkD, sinkW, sinkH, true);
113
- const basinInner = box(sinkD - 50, sinkW - 50, sinkH - 25, true).translate(0, 0, 12);
112
+ const basinOuter = box(sinkD, sinkW, sinkH);
113
+ const basinInner = box(sinkD - 50, sinkW - 50, sinkH - 25).translate(0, 0, 12);
114
114
  const drain = cylinder(27, 18).translate(0, 0, -sinkH / 2);
115
115
  const basin = basinOuter.subtract(basinInner).subtract(drain)
116
116
  .translate(sinkCX, sinkCY, wallT + sinkZ + sinkH / 2);
117
117
 
118
118
  // Pedestal
119
- const pedestal = box(80, 80, sinkZ, true)
119
+ const pedestal = box(80, 80, sinkZ)
120
120
  .translate(sinkCX, sinkCY, wallT + sinkZ / 2);
121
121
 
122
122
  // Faucet — vertical stem + curved spout
@@ -124,12 +124,12 @@ const faucetX = sinkCX + sinkD / 2 - 40;
124
124
  const faucetZ = wallT + sinkZ + sinkH;
125
125
  const faucetStem = cylinder(100, 10)
126
126
  .translate(faucetX, sinkCY, faucetZ);
127
- const faucetSpout = box(80, 16, 16, true)
127
+ const faucetSpout = box(80, 16, 16)
128
128
  .translate(faucetX - 40, sinkCY, faucetZ + 100);
129
129
  // Handles
130
- const handleL = box(30, 8, 8, true)
130
+ const handleL = box(30, 8, 8)
131
131
  .translate(faucetX, sinkCY - 30, faucetZ + 50);
132
- const handleR = box(30, 8, 8, true)
132
+ const handleR = box(30, 8, 8)
133
133
  .translate(faucetX, sinkCY + 30, faucetZ + 50);
134
134
 
135
135
  const sink = union(basin, pedestal, faucetStem, faucetSpout, handleL, handleR);
@@ -167,7 +167,7 @@ const bracketL = box(70, bracketSize, bracketSize)
167
167
  const bracketR = box(70, bracketSize, bracketSize)
168
168
  .translate(wallT, rackY + rackLen / 2 - bracketSize, wallT + rackZ);
169
169
  const towelBar = cylinder(rackLen - bracketSize * 2, barR)
170
- .rotateAround([0, 0, 0], -90, 0, 0)
170
+ .rotateX(-90)
171
171
  .translate(wallT + 50, rackY - rackLen / 2 + bracketSize, wallT + rackZ + bracketSize / 2);
172
172
 
173
173
  const towelRack = union(bracketL, bracketR, towelBar);
@@ -177,7 +177,7 @@ const towelRack = union(bracketL, bracketR, towelBar);
177
177
  const matW = 700;
178
178
  const matD = 450;
179
179
  const matH = 8;
180
- const bathMat = box(matW, matD, matH, true)
180
+ const bathMat = box(matW, matD, matH)
181
181
  .translate(tubX + tubL / 2, tubY - matD / 2 - 30, wallT + matH / 2);
182
182
 
183
183
  // ─── Assemble ───
@@ -37,7 +37,7 @@ const legs = union(
37
37
  // Tilted slightly backward — rotate around X then position at back edge
38
38
  const backPanel = box(seatW, backT, backH)
39
39
  .translate(-seatW / 2, -backT / 2, 0) // center for rotation
40
- .rotateAround([0, 0, 0], -backTilt, 0, 0)
40
+ .rotateX(-backTilt)
41
41
  .translate(seatW / 2, seatD - legInset - legW / 2, seatH + seatT);
42
42
 
43
43
  // --- Stretchers (side rails between legs) ---
@@ -103,7 +103,7 @@ const mainW = rand(14, 20);
103
103
  crystals.push({
104
104
  name: 'Main Crystal',
105
105
  shape: crystal(mainH, mainW)
106
- .rotate(0, 0, rand(0, 360))
106
+ .rotateZ(rand(0, 360))
107
107
  .translate(rand(-5, 5), rand(-5, 5), 0),
108
108
  color: '#a855f7',
109
109
  });
@@ -118,7 +118,7 @@ for (let i = 0; i < crystalCount - 1; i++) {
118
118
  crystals.push({
119
119
  name: `Crystal ${i + 2}`,
120
120
  shape: crystal(h, w)
121
- .rotate(rand(-8, 8), rand(-12, 12), rand(0, 360))
121
+ .rotateX(rand(-8, 8)).rotateY(rand(-12, 12)).rotateZ(rand(0, 360))
122
122
  .translate(Math.cos(angle) * dist, Math.sin(angle) * dist, 0),
123
123
  color: pickColor(),
124
124
  });
@@ -127,7 +127,7 @@ for (let i = 0; i < spireCount; i++) {
127
127
  shape: ngon(6, r)
128
128
  .extrude(h)
129
129
  .translate(px, py, 0)
130
- .rotate(0, 0, rand(0, 60))
130
+ .rotateZ(rand(0, 60))
131
131
  .material({
132
132
  opacity: rand(0.4, 0.75),
133
133
  metalness: 0.0,
@@ -145,7 +145,7 @@ for (let i = 0; i < spireCount; i++) {
145
145
  shape: ngon(6, r * 0.4)
146
146
  .extrude(h * 0.7)
147
147
  .translate(px, py, h * 0.1)
148
- .rotate(0, 0, rand(0, 60))
148
+ .rotateZ(rand(0, 60))
149
149
  .material({
150
150
  emissive: pick(glowColors),
151
151
  emissiveIntensity: rand(1, 3),
@@ -183,7 +183,7 @@ for (let i = 0; i < 10; i++) {
183
183
  shape: ngon(6, size)
184
184
  .extrude(size * rand(0.3, 0.8))
185
185
  .translate(Math.cos(angle) * dist, Math.sin(angle) * dist, 0)
186
- .rotate(rand(-15, 15), rand(-15, 15), rand(0, 60))
186
+ .rotateX(rand(-15, 15)).rotateY(rand(-15, 15)).rotateZ(rand(0, 60))
187
187
  .material({
188
188
  opacity: rand(0.4, 0.7),
189
189
  metalness: 0.0,
@@ -91,15 +91,15 @@ for (let i = 0; i < count; i++) {
91
91
  const lum = 45 + t * 15; // slightly brighter at top
92
92
  const r255 = hslToHex(hue, sat, lum);
93
93
 
94
- const element = box(s, s * 0.6, s * 1.2, true)
95
- .rotate(0, 0, facingDeg)
94
+ const element = box(s, s * 0.6, s * 1.2)
95
+ .rotateZ(facingDeg)
96
96
  .translate(Math.cos(angle) * r, Math.sin(angle) * r, z);
97
97
 
98
98
  shapes.push({ name: `Petal ${i + 1}`, shape: element, color: r255 });
99
99
  }
100
100
 
101
101
  // Central spine
102
- const spine = box(6, 6, towerHeight * 0.9, true)
102
+ const spine = box(6, 6, towerHeight * 0.9)
103
103
  .translate(0, 0, towerHeight * 0.45);
104
104
  shapes.push({ name: 'Spine', shape: spine, color: '#1a1a2e' });
105
105