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,88 @@
1
+ # Compiler Regression Corpus
2
+
3
+ These parts are the curated multi-feature corpus behind `forgecad check compiler`,
4
+ the focused `forgecad check query-propagation` snapshots, and `forgecad check brep`.
5
+ They are intentionally ordinary mechanical parts instead of isolated geometry
6
+ tricks, so lowerer regressions show up in workflows that look like real
7
+ product-design code.
8
+
9
+ Each file is deterministic: no randomness, no params, fixed named solid result(s).
10
+
11
+ ## Coverage Map
12
+
13
+ | Part | Main workflow families |
14
+ | --- | --- |
15
+ | `enclosure-shell-cuts.forge.js` | `shell()`, face-driven cuts, mirrored feet, boolean chain |
16
+ | `motor-mount-plate.forge.js` | circular pattern, mirrored ears, deterministic boolean pockets |
17
+ | `sensor-bracket.forge.js` | mirrored ribs, upright `onFace()` cuts, repeated detail holes |
18
+ | `edge-finished-mount.forge.js` | tracked-edge fillet/chamfer, downstream hole/cut edits, boolean chain |
19
+ | `fastener-plate-variants.forge.js` | counterbores, countersinks, planar `upToFace`, created-face propagation |
20
+ | `folded-service-panel-cover.forge.js` | compiler-owned sheet metal, named panel/flange/bend descendants, folded + flat outputs |
21
+ | `projection-relay-cover.forge.js` | `projectToPlane()` replay from repeated union descendants |
22
+ | `service-panel-cover.forge.js` | repeated bosses plus richer hole/cut details plus projection replay |
23
+ | `trimmed-access-cover.forge.js` | `trimByPlane()`, plane-cap ownership, later union edits |
24
+
25
+ ## Parts
26
+
27
+ ### `enclosure-shell-cuts.forge.js`
28
+
29
+ Guards:
30
+ - shell lowering inside an enclosure-style boolean workflow
31
+ - `onFace()`-driven subtractive features that need semantic workplane placement in the compiler graph
32
+ - mirrored mounting feet that stay exact-exportable after later subtracts
33
+
34
+ ### `motor-mount-plate.forge.js`
35
+
36
+ Guards:
37
+ - `circularPattern()` around a real bolt circle instead of a toy transform demo
38
+ - stacked analytic bore cutters inside an exact-exportable boolean workflow, not compiler-owned counterbore features yet
39
+ - mirrored tabs and center pocket booleans that must remain deterministic
40
+
41
+ ### `sensor-bracket.forge.js`
42
+
43
+ Guards:
44
+ - mirrored reinforcement ribs inside a larger bracket union
45
+ - front/side face cuts placed with `onFace()` on an upright wall
46
+ - repeated indicator-hole cutters plus raw mounting bores in the same boolean tree
47
+
48
+ ### `edge-finished-mount.forge.js`
49
+
50
+ Guards:
51
+ - tracked-edge `filletEdge()` plus later propagated-edge `chamferEdge()` lowering on the defended vertical-edge subset
52
+ - mirrored additive features staying exact-exportable while the selected preserved edge keeps one unique propagated lineage
53
+ - downstream `hole()` / `cutout()` edits that still target the original tracked body owner after the broadened edge-finish flow
54
+
55
+ ### `fastener-plate-variants.forge.js`
56
+
57
+ Guards:
58
+ - compiler-owned counterbore and countersink holes inside a normal fastener-plate workflow
59
+ - `upToFace` hole/cut extents lowering through both Manifold and CadQuery/OCCT from the shared feature family
60
+ - defended created-face/query semantics staying inspectable after multiple richer hole rewrites
61
+
62
+ ### `folded-service-panel-cover.forge.js`
63
+
64
+ Guards:
65
+ - compiler-owned `sheetMetal()` intent lowering to both the folded cover and the flat pattern from one semantic model
66
+ - named `panel`, `flange-*`, and `bend-*` descendants staying inspectable after panel/flange cutouts split those regions
67
+ - exact export and query-propagation coverage for a manufacturable-looking sheet-metal cover instead of an isolated toy bend
68
+
69
+ ### `projection-relay-cover.forge.js`
70
+
71
+ Guards:
72
+ - `projectToPlane()` replay after a repeated top-edge boss chain has already been merged through a supported union
73
+ - projection-driven downstream lips staying exact-exportable instead of collapsing back to runtime-only geometry
74
+ - downstream placement still using defended face-query lineage from the base plate instead of anonymous heuristics
75
+
76
+ ### `service-panel-cover.forge.js`
77
+
78
+ Guards:
79
+ - repeated top-side bosses staying compiler-owned before later richer hole/cut rewrites hit the same part
80
+ - counterbores, countersinks, and a face-driven display pocket remaining exact-exportable in one ordinary service-cover workflow
81
+ - projection-driven gasket geometry replaying from a hole/cut/union source instead of dropping back to runtime-only silhouette logic
82
+
83
+ ### `trimmed-access-cover.forge.js`
84
+
85
+ Guards:
86
+ - `trimByPlane()` exposing the defended `plane-cap` created-face query inside a normal cover-style workflow
87
+ - earlier `hole()` / `cutout()` rewrites still surfacing explicit split-face ambiguity and unsupported created-edge diagnostics
88
+ - later union edits staying reviewable after the trim-created face lands
@@ -0,0 +1,18 @@
1
+ const base = box(84, 54, 24, true);
2
+
3
+ const pocket = roundedRect(20, 10, 2, true)
4
+ .onFace(base, 'top', { u: 12, v: -8, selfAnchor: 'center' });
5
+
6
+ const bossSeed = roundedRect(14, 8, 1.5, true)
7
+ .onFace(base, 'top', { u: -18, v: 8, protrude: 0.25, selfAnchor: 'center' })
8
+ .extrude(8);
9
+
10
+ const once = filletEdge(base.toShape(), base.edge('vert-br'), 6, [-1, -1]);
11
+ const bosses = mirrorCopy(bossSeed, [1, 0, 0]);
12
+
13
+ const body = chamferEdge(once.add(bosses), base.edge('vert-bl'), 4, [1, -1])
14
+ .hole(base.face('top'), { diameter: 6, u: -18, v: 10, depth: 9 })
15
+ .cutout(pocket, { depth: 5 })
16
+ .subtract(cylinder(28, 4, undefined, undefined, true).translate(0, -20, 0));
17
+
18
+ return [{ name: 'Edge Finished Mount', shape: body }];
@@ -0,0 +1,24 @@
1
+ // Guard part: shell + face-driven cuts + mirrored feet in one ordinary enclosure workflow.
2
+
3
+ const base = roundedRect(120, 80, 10, true).extrude(36);
4
+ const shell = base.shell(3, { openFaces: ['top'] });
5
+
6
+ const displayCut = roundedRect(34, 18, 3, true)
7
+ .onFace(base, 'front', { u: 0, v: 8, protrude: 0.25, selfAnchor: 'center' })
8
+ .extrude(10);
9
+
10
+ const cableCut = circle2d(7)
11
+ .onFace(base, 'right', { u: -12, v: -8, protrude: 0.25, selfAnchor: 'center' })
12
+ .extrude(10);
13
+
14
+ const foot = roundedRect(18, 18, 4, true)
15
+ .onFace(base, 'bottom', { u: 36, v: 20, protrude: 0, selfAnchor: 'center' })
16
+ .extrude(6);
17
+
18
+ const feet = mirrorCopy(mirrorCopy(foot, [1, 0, 0]), [0, 1, 0]);
19
+
20
+ const enclosure = union(shell, feet)
21
+ .subtract(displayCut)
22
+ .subtract(cableCut);
23
+
24
+ return [{ name: 'Enclosure Shell Cuts', shape: enclosure }];
@@ -0,0 +1,42 @@
1
+ // Guard part: counterbore/countersink hole variants plus an up-to-face service pocket in one ordinary plate workflow.
2
+
3
+ const plate = roundedRect(118, 76, 8, true).extrude(18);
4
+ const entryFace = plate.face('top');
5
+ const exitFace = plate.face('bottom');
6
+ const pocketExitFace = plate.toShape().face('side-top');
7
+
8
+ const servicePocket = roundedRect(34, 18, 4, true)
9
+ .onFace(plate, 'front', { u: 0, v: -2, selfAnchor: 'center' });
10
+
11
+ const fastenerPlate = plate
12
+ .hole(entryFace, {
13
+ diameter: 6.2,
14
+ u: -34,
15
+ v: 20,
16
+ upToFace: exitFace,
17
+ counterbore: { diameter: 11.5, depth: 4 },
18
+ })
19
+ .hole(entryFace, {
20
+ diameter: 6.2,
21
+ u: 34,
22
+ v: 20,
23
+ upToFace: exitFace,
24
+ counterbore: { diameter: 11.5, depth: 4 },
25
+ })
26
+ .hole(entryFace, {
27
+ diameter: 4.3,
28
+ u: -34,
29
+ v: -20,
30
+ upToFace: exitFace,
31
+ countersink: { diameter: 9, angleDeg: 90 },
32
+ })
33
+ .hole(entryFace, {
34
+ diameter: 4.3,
35
+ u: 34,
36
+ v: -20,
37
+ upToFace: exitFace,
38
+ countersink: { diameter: 9, angleDeg: 90 },
39
+ })
40
+ .cutout(servicePocket, { upToFace: pocketExitFace });
41
+
42
+ return [{ name: 'Fastener Plate Variants', shape: fastenerPlate }];
@@ -0,0 +1,5 @@
1
+ // Guard part: compiler-owned sheet-metal semantics drive both the folded cover and its flat pattern.
2
+
3
+ import { buildFoldedServicePanelCoverScene } from '../api/folded-service-panel-cover.js';
4
+
5
+ return buildFoldedServicePanelCoverScene();
@@ -0,0 +1,32 @@
1
+ // Guard part: patterned fastener holes, mirrored ears, and deterministic boolean pockets.
2
+
3
+ const plate = roundedRect(132, 96, 12, true).extrude(10);
4
+ const ear = roundedRect(28, 42, 8, true)
5
+ .extrude(10)
6
+ .translate(0, 62, 0);
7
+ const body = union(plate, mirrorCopy(ear, [0, 1, 0]));
8
+
9
+ const counterboredHole = union(
10
+ cylinder(14, 2.25, undefined, undefined, true),
11
+ cylinder(4, 4.25, undefined, undefined, true).translate(0, 0, 5),
12
+ );
13
+ const centerBore = cylinder(14, 24, undefined, undefined, true);
14
+ const bolt = counterboredHole.translate(32, 0, 0);
15
+ const boltCircle = circularPattern(bolt, 4);
16
+
17
+ const earSlot = roundedRect(16, 8, 3, true)
18
+ .extrude(14, { center: true })
19
+ .translate(0, 62, 5);
20
+ const earSlots = mirrorCopy(earSlot, [0, 1, 0]);
21
+
22
+ const lighteningPocket = roundedRect(54, 22, 5, true)
23
+ .extrude(12, { center: true })
24
+ .translate(0, 0, 5);
25
+
26
+ const motorMount = body
27
+ .subtract(centerBore)
28
+ .subtract(boltCircle)
29
+ .subtract(earSlots)
30
+ .subtract(lighteningPocket);
31
+
32
+ return [{ name: 'Motor Mount Plate', shape: motorMount }];
@@ -0,0 +1,16 @@
1
+ const plate = roundedRect(96, 56, 6, true).extrude(12);
2
+
3
+ const bossSeed = roundedRect(18, 12, 2.5, true)
4
+ .onFace(plate, 'top', { u: -28, v: 24, protrude: 0.5, selfAnchor: 'center' })
5
+ .extrude(7);
6
+
7
+ const bosses = linearPattern(bossSeed, 3, 28, 0, 0);
8
+ const cover = union(plate, bosses);
9
+
10
+ const projected = projectToPlane(cover, { plane: 'XY' });
11
+ const lip = projected
12
+ .offset(2)
13
+ .onFace(plate.face('top'), { protrude: 0.5, selfAnchor: 'center' })
14
+ .extrude(1.2);
15
+
16
+ return [{ name: 'Projection Relay Cover', shape: union(cover, lip) }];
@@ -0,0 +1,35 @@
1
+ // Guard part: mirrored ribs, face-mounted cuts, and repeated wall details in one bracket.
2
+
3
+ const base = roundedRect(100, 54, 4, true).extrude(10);
4
+ const upright = roundedRect(100, 10, 4, true)
5
+ .extrude(56)
6
+ .translate(0, 22, 10);
7
+ const rib = box(10, 24, 36, true).translate(36, 10, 28);
8
+ const ribs = mirrorCopy(rib, [1, 0, 0]);
9
+
10
+ const mountHole = union(
11
+ cylinder(14, 2.25, undefined, undefined, true),
12
+ cylinder(3.5, 4.25, undefined, undefined, true).translate(0, 0, 5.25),
13
+ ).translate(28, 0, 5);
14
+ const mountHoles = mirrorCopy(mountHole, [1, 0, 0]);
15
+
16
+ const sensorWindow = roundedRect(36, 16, 3, true)
17
+ .onFace(upright, 'front', { u: 0, v: 6, protrude: 0.25, selfAnchor: 'center' })
18
+ .extrude(12);
19
+
20
+ const cablePort = roundedRect(18, 8, 2, true)
21
+ .onFace(upright, 'right', { u: 10, v: -8, protrude: 0.25, selfAnchor: 'center' })
22
+ .extrude(12);
23
+
24
+ const indicatorHole = circle2d(2.5)
25
+ .onFace(upright, 'front', { u: -16, v: -12, protrude: 0.25, selfAnchor: 'center' })
26
+ .extrude(12);
27
+ const indicatorArray = linearPattern(indicatorHole, 3, 16, 0);
28
+
29
+ const bracket = union(base, upright, ribs)
30
+ .subtract(mountHoles)
31
+ .subtract(sensorWindow)
32
+ .subtract(cablePort)
33
+ .subtract(indicatorArray);
34
+
35
+ return [{ name: 'Sensor Bracket', shape: bracket }];
@@ -0,0 +1,53 @@
1
+ // Guard part: repeated bosses, richer hole variants, projection replay, and a face-driven cut in one service-cover workflow.
2
+
3
+ const plate = roundedRect(124, 78, 8, true).extrude(12);
4
+
5
+ const bossSeed = roundedRect(18, 10, 2, true)
6
+ .onFace(plate, 'top', { u: -30, v: 22, protrude: 0.5, selfAnchor: 'center' })
7
+ .extrude(7);
8
+ const bosses = linearPattern(bossSeed, 3, 30, 0, 0);
9
+
10
+ const displayPocket = roundedRect(30, 14, 3, true)
11
+ .onFace(plate, 'top', { u: 0, v: 4, protrude: 0.25, selfAnchor: 'center' });
12
+
13
+ const topFace = plate.face('top');
14
+ const bottomFace = plate.face('bottom');
15
+
16
+ const cover = union(plate, bosses)
17
+ .hole(topFace, {
18
+ diameter: 5.2,
19
+ u: -46,
20
+ v: -22,
21
+ upToFace: bottomFace,
22
+ countersink: { diameter: 10.5, angleDeg: 90 },
23
+ })
24
+ .hole(topFace, {
25
+ diameter: 5.2,
26
+ u: 46,
27
+ v: -22,
28
+ upToFace: bottomFace,
29
+ countersink: { diameter: 10.5, angleDeg: 90 },
30
+ })
31
+ .hole(topFace, {
32
+ diameter: 4.5,
33
+ u: -30,
34
+ v: 22,
35
+ upToFace: bottomFace,
36
+ counterbore: { diameter: 9, depth: 3 },
37
+ })
38
+ .hole(topFace, {
39
+ diameter: 4.5,
40
+ u: 30,
41
+ v: 22,
42
+ upToFace: bottomFace,
43
+ counterbore: { diameter: 9, depth: 3 },
44
+ })
45
+ .cutout(displayPocket, { depth: 4 });
46
+
47
+ const projected = projectToPlane(cover, { plane: 'XY' });
48
+ const gasket = projected
49
+ .offset(1.8)
50
+ .onFace(topFace, { protrude: 0.5, selfAnchor: 'center' })
51
+ .extrude(1.2);
52
+
53
+ return [{ name: 'Service Panel Cover', shape: union(cover, gasket) }];
@@ -0,0 +1,26 @@
1
+ // Guard part: trim-created plane cap plus upstream hole/cut rewrites in one ordinary cover workflow.
2
+
3
+ const base = roundedRect(104, 68, 8, true).extrude(18);
4
+
5
+ const gusset = roundedRect(20, 8, 2, true)
6
+ .onFace(base, 'front', { u: -22, v: 4, protrude: 0.25, selfAnchor: 'center' })
7
+ .extrude(10);
8
+
9
+ const merged = union(base, gusset);
10
+
11
+ const servicePocket = roundedRect(22, 12, 2, true)
12
+ .onFace(base, 'top', { u: 18, v: -10, protrude: 0.25, selfAnchor: 'center' });
13
+
14
+ const preTrim = merged
15
+ .hole(base.face('top'), { diameter: 6, u: -24, v: 12, depth: 9 })
16
+ .cutout(servicePocket, { depth: 6 });
17
+
18
+ const trimmed = preTrim.trimByPlane([0, 1, 0], 6);
19
+
20
+ const latch = roundedRect(16, 10, 2, true)
21
+ .extrude(8, { center: true })
22
+ .translate(0, 28, 8);
23
+
24
+ const cover = union(trimmed, latch);
25
+
26
+ return [{ name: 'Trimmed Access Cover', shape: cover }];
@@ -0,0 +1,25 @@
1
+ // Cup with larger top than bottom
2
+
3
+ // Parameters
4
+ const height = param("Height", 80, { min: 40, max: 150, unit: "mm" });
5
+ const topRadius = param("Top Radius", 35, { min: 20, max: 60, unit: "mm" });
6
+ const bottomRadius = param("Bottom Radius", 25, { min: 15, max: 50, unit: "mm" });
7
+ const wallThickness = param("Wall Thickness", 3, { min: 1, max: 8, unit: "mm" });
8
+ const baseThickness = param("Base Thickness", 5, { min: 2, max: 10, unit: "mm" });
9
+
10
+ // Outer cup shape - tapered cylinder (truncated cone)
11
+ const outerCup = cylinder(height, bottomRadius, topRadius);
12
+
13
+ // Inner hollow shape - slightly smaller, shorter to leave a base
14
+ const innerHeight = height - baseThickness;
15
+ const innerTopRadius = topRadius - wallThickness;
16
+ const innerBottomRadius = bottomRadius - wallThickness;
17
+
18
+ // Create hollow interior by subtracting inner cone
19
+ const innerCup = cylinder(innerHeight, innerBottomRadius, innerTopRadius)
20
+ .translate(0, 0, baseThickness);
21
+
22
+ // Final cup with hollow interior
23
+ const cup = outerCup.subtract(innerCup);
24
+
25
+ return cup;
@@ -0,0 +1,28 @@
1
+ // Cut Plane Demo — toggle section views in the View Panel
2
+ //
3
+ // cutPlane(name, normal, offsetOrOptions?, options?)
4
+ // normal: direction pointing toward the side that gets removed
5
+ // offset: distance from origin along the normal where the cut happens
6
+ // exclude: object name(s) to keep uncut for this plane
7
+
8
+ const size = param("Size", 60, { min: 20, max: 120, unit: "mm" });
9
+ const holeR = param("Hole Radius", 12, { min: 4, max: 25, unit: "mm" });
10
+ const cutZ = param("Cut Z", 10, { min: -60, max: 60, unit: "mm" });
11
+ const cutY = param("Cut Y", 0, { min: -60, max: 60, unit: "mm" });
12
+
13
+ // Define cut planes — they appear as toggles in the View Panel
14
+ cutPlane("Horizontal Section", [0, 0, 1], cutZ, { exclude: "Probe" }); // removes top half except Probe
15
+ cutPlane("Front Section", [0, -1, 0], cutY, { exclude: "Probe" }); // removes front half except Probe
16
+
17
+ // Build a box with a cylindrical hole
18
+ const body = box(size, size, size, true);
19
+ const hole = cylinder(size + 2, holeR, holeR, 32, true);
20
+ const part = body.subtract(hole).color("#8aa7c8");
21
+ const probe = cylinder(size + 14, Math.max(2, holeR * 0.22), undefined, 32, true)
22
+ .translate(size * 0.3, size * 0.18, 0)
23
+ .color("#efaa6d");
24
+
25
+ return [
26
+ { name: "Body", shape: part },
27
+ { name: "Probe", shape: probe },
28
+ ];
@@ -0,0 +1,54 @@
1
+ // Door with hinges — the door pivots around the hinge axis using joint().
2
+ // Drag the "Door Angle" slider to open/close the door.
3
+
4
+ const doorW = param("Door Width", 80, { min: 40, max: 120, unit: "mm" });
5
+ const doorH = param("Door Height", 200, { min: 100, max: 300, unit: "mm" });
6
+ const doorT = param("Door Thickness", 4, { min: 2, max: 10, unit: "mm" });
7
+
8
+ const frameW = param("Frame Width", 8, { min: 4, max: 15, unit: "mm" });
9
+ const frameD = param("Frame Depth", 6, { min: 3, max: 12, unit: "mm" });
10
+
11
+ const hingeR = param("Hinge Radius", 3, { min: 1.5, max: 6, unit: "mm" });
12
+ const hingeH = param("Hinge Height", 16, { min: 8, max: 30, unit: "mm" });
13
+
14
+ // ── Door frame (stationary U-shape) ──
15
+ const frame = union(
16
+ box(frameW, frameD, doorH), // left
17
+ box(frameW, frameD, doorH).translate(doorW + frameW, 0, 0), // right
18
+ box(doorW + 2 * frameW, frameD, frameW).translate(0, 0, doorH), // top
19
+ ).color("#8B7355");
20
+
21
+ // ── Hinge barrels ──
22
+ const hingePivot = [frameW, 0, 0]; // pivot axis runs along Z at this XY
23
+ const hingeZ1 = doorH * 0.15;
24
+ const hingeZ2 = doorH * 0.85;
25
+ const hinge1 = cylinder(hingeH, hingeR).translate(frameW, 0, hingeZ1 - hingeH / 2).color("#888888");
26
+ const hinge2 = cylinder(hingeH, hingeR).translate(frameW, 0, hingeZ2 - hingeH / 2).color("#888888");
27
+
28
+ // ── Door panel ──
29
+ // Position at frame edge, then use joint() to rotate around hinge axis
30
+ const doorPanel = box(doorW, doorT, doorH)
31
+ .translate(frameW, -doorT / 2, 0)
32
+ .color("#C4A46C");
33
+
34
+ // joint() auto-creates the "Door Angle" slider and rotates around the pivot
35
+ const rotatedDoor = joint("Door Angle", doorPanel, [frameW, 0, 0], {
36
+ axis: [0, 0, 1],
37
+ min: -170,
38
+ max: 0,
39
+ default: -45,
40
+ reverse: true
41
+ });
42
+
43
+ // ── Phase 1 demo: diagonal intersection to find door center ──
44
+ const doorRect = rectangle(0, 0, doorW, doorH);
45
+ const [d1, d2] = doorRect.diagonals();
46
+ const doorCenter = d1.intersect(d2);
47
+ console.log("Door center (from diagonals):", doorCenter.x.toFixed(1), doorCenter.y.toFixed(1));
48
+
49
+ return [
50
+ { name: "Frame", shape: frame },
51
+ { name: "Door", shape: rotatedDoor },
52
+ { name: "Hinge Bottom", shape: hinge1 },
53
+ { name: "Hinge Top", shape: hinge2 },
54
+ ];
@@ -0,0 +1,4 @@
1
+ // 2D Sketch
2
+ const size = param("size", 50, {unit: "mm"});
3
+ let result = rect(size, size, true);
4
+ return result;
@@ -0,0 +1,18 @@
1
+ // Headphone Hanger — stroke approach
2
+ // Draw centerline, thicken it. Done.
3
+
4
+ const plateW = param("Plate Width", 50, { min: 30, max: 80, unit: "mm" });
5
+ const t = param("Thickness", 4, { min: 2, max: 8, unit: "mm" });
6
+ const armLen = param("Arm Length", 70, { min: 40, max: 120, unit: "mm" });
7
+ const hookLen = param("Hook Length", 20, { min: 10, max: 40, unit: "mm" });
8
+ const hookAngle = param("Hook Angle", 47, { min: 15, max: 60, unit: "°" });
9
+ const hookP2Length = param("Hook p2 length", 20, { min: 10, max: 40, unit: "mm" });
10
+
11
+ return path()
12
+ .moveTo(0, 0)
13
+ .lineH(plateW)
14
+ .lineV(-armLen)
15
+ .lineAngled(hookLen, 270 - hookAngle)
16
+ .lineH(-hookP2Length)
17
+ .lineAngled(hookLen, 90+hookAngle)
18
+ .stroke(t);
@@ -0,0 +1,88 @@
1
+ // Headphone Hanger — under-desk clamp mount
2
+ // Clamps to the bottom edge of a desk, hook hangs below.
3
+ // Designed for actual 3D printing / building.
4
+
5
+ const deskThick = param("Desk Thickness", 25, { min: 15, max: 50, unit: "mm" });
6
+ const clampDepth = param("Clamp Depth", 40, { min: 25, max: 70, unit: "mm" });
7
+ const width = param("Width", 50, { min: 30, max: 80, unit: "mm" });
8
+ const thick = param("Material Thick", 5, { min: 3, max: 10, unit: "mm" });
9
+ const hookDrop = param("Hook Drop", 60, { min: 30, max: 100, unit: "mm" });
10
+ const hookLen = param("Hook Length", 35, { min: 15, max: 60, unit: "mm" });
11
+ const hookCurveR = param("Hook Curve R", 15, { min: 5, max: 30, unit: "mm" });
12
+ const padThick = param("Pad Thickness", 2, { min: 1, max: 5, unit: "mm" });
13
+ const boltHoleD = param("Bolt Hole", 5, { min: 0, max: 8, unit: "mm" });
14
+
15
+ // The clamp is a C-shape that grips the desk edge.
16
+ // Top jaw sits on top of desk, bottom jaw presses from below.
17
+ // A vertical arm drops down, then curves into the hook.
18
+
19
+ // --- Top jaw (sits on desk surface) ---
20
+ const topJaw = box(width, clampDepth, thick);
21
+
22
+ // --- Vertical back (connects top to bottom jaw) ---
23
+ const backWall = box(width, thick, deskThick + thick * 2)
24
+ .translate(0, 0, -deskThick - thick);
25
+
26
+ // --- Bottom jaw (presses up against desk bottom) ---
27
+ const bottomJaw = box(width, clampDepth * 0.6, thick)
28
+ .translate(0, 0, -deskThick - thick);
29
+
30
+ // --- Pad on bottom jaw (rubber contact, slightly inset) ---
31
+ const pad = box(width - 10, clampDepth * 0.5, padThick)
32
+ .translate(5, 5, -deskThick);
33
+
34
+ // --- Vertical arm dropping down from bottom jaw ---
35
+ const armTop = -deskThick - thick;
36
+ const arm = box(width, thick, hookDrop)
37
+ .translate(0, clampDepth * 0.6 - thick, armTop - hookDrop);
38
+
39
+ // --- Hook (curved part at bottom) ---
40
+ // Use revolve to make a clean quarter-torus for the curve
41
+ const hookZ = armTop - hookDrop;
42
+ const armY = clampDepth * 0.6 - thick;
43
+
44
+ // Quarter-torus: revolve a rect(width x thick) around an axis at hookCurveR distance
45
+ // The profile sits at X = hookCurveR (distance from revolution axis = Y)
46
+ // revolve() goes around Y axis, so profile X = radial, profile Y = height
47
+ const curveProfile = rect(thick, width).translate(hookCurveR, 0);
48
+ const curvePiece = curveProfile.revolve(90)
49
+ // revolve produces shape around Y axis; rotate to align:
50
+ // we need the arc to go from -Z (down) to +Y (forward)
51
+ .rotate(90, 0, 90) // align width along X
52
+ .rotate(180, 0, 0) // flip so arc opens downward-to-forward
53
+ .translate(0, clampDepth * 0.6 + hookCurveR, hookZ);
54
+
55
+ // Straight hook tip extending forward
56
+ const tipY = armY + thick + hookCurveR;
57
+ const tipZ = hookZ - hookCurveR - thick;
58
+ const hookTip = box(width, hookLen - hookCurveR, thick)
59
+ .translate(0, tipY, tipZ);
60
+
61
+ // Small upward lip at the end to prevent headphones from sliding off
62
+ const lipHeight = 8;
63
+ const hookLip = box(width, thick, lipHeight)
64
+ .translate(0, tipY + hookLen - hookCurveR - thick, tipZ + thick);
65
+
66
+ // --- Bolt holes for tightening clamp (optional) ---
67
+ const boltHoles = [];
68
+ if (boltHoleD > 0) {
69
+ // Two holes through the top jaw
70
+ const holeSpacing = width * 0.6;
71
+ for (let i = -1; i <= 1; i += 2) {
72
+ boltHoles.push(
73
+ cylinder(thick + 2, boltHoleD / 2)
74
+ .translate(width / 2 + i * holeSpacing / 2, clampDepth * 0.3, -1)
75
+ );
76
+ }
77
+ }
78
+
79
+ // --- Assembly ---
80
+ let clamp = union(topJaw, backWall, bottomJaw, arm, curvePiece, hookTip, hookLip);
81
+ if (boltHoles.length > 0) {
82
+ clamp = clamp.subtract(union(...boltHoles));
83
+ }
84
+
85
+ return [
86
+ { name: "Clamp + Hook", shape: clamp, color: "#445566" },
87
+ { name: "Desk Pad", shape: pad, color: "#338855" },
88
+ ];
@@ -0,0 +1,5 @@
1
+ const sketch = importSketch("headphone-hanger-profile.sketch.js");
2
+
3
+ console.log(sketch);
4
+
5
+ return sketch.extrude(40);
@@ -0,0 +1,72 @@
1
+ // Foldable iPhone Stand — two-piece with hinge
2
+ // A base plate and a back support that folds flat for travel.
3
+
4
+ const standW = param("Stand Width", 85, { min: 70, max: 120, unit: "mm" });
5
+ const thick = param("Thickness", 4, { min: 2, max: 8, unit: "mm" });
6
+ const baseLen = param("Base Length", 80, { min: 50, max: 120, unit: "mm" });
7
+ const backH = param("Back Height", 70, { min: 40, max: 120, unit: "mm" });
8
+ const lipH = param("Lip Height", 12, { min: 5, max: 25, unit: "mm" });
9
+ const lipAngle = param("Lip Angle", 80, { min: 60, max: 90, unit: "°" });
10
+ const foldAngle = param("Fold Angle", 65, { min: 0, max: 90, unit: "°" });
11
+ const hingeR = param("Hinge Radius", 3, { min: 2, max: 6, unit: "mm" });
12
+ const cableHoleD = param("Cable Hole", 12, { min: 0, max: 20, unit: "mm" });
13
+ const gripSlots = param("Grip Slots", 3, { min: 0, max: 5 });
14
+
15
+ // --- Base plate (centered on XY, bottom at Z=0) ---
16
+ const base = roundedRect(standW, baseLen, 3, true).extrude(thick);
17
+
18
+ // --- Front lip (holds the phone) ---
19
+ // Build at origin: sits on XZ plane, thick in Y, lipH tall in Z
20
+ // Then tilt backward, then move to front edge
21
+ const lipRaw = box(standW - 10, thick, lipH, true)
22
+ .translate(0, 0, lipH / 2); // lift so bottom edge is at Z=0
23
+ const lipTilted = lipRaw.rotate(-(90 - lipAngle), 0, 0); // tilt back
24
+ const lip = lipTilted.translate(0, -baseLen / 2, thick); // move to front edge, on top of base
25
+
26
+ // --- Grip slots (cross-wise along X to prevent phone sliding forward) ---
27
+ const slotParts = [];
28
+ if (gripSlots > 0) {
29
+ const slotSpacing = (baseLen * 0.5) / (gripSlots + 1);
30
+ for (let i = 0; i < gripSlots; i++) {
31
+ const sy = -baseLen / 4 + slotSpacing * (i + 1);
32
+ slotParts.push(
33
+ box(standW * 0.6, 2, thick + 2, true)
34
+ .translate(0, sy, thick / 2)
35
+ );
36
+ }
37
+ }
38
+
39
+ // --- Cable hole (near front lip where phone bottom rests) ---
40
+ let baseFinal = union(base, lip);
41
+ if (slotParts.length > 0) {
42
+ baseFinal = baseFinal.subtract(union(...slotParts));
43
+ }
44
+ if (cableHoleD > 0) {
45
+ const cableHole = cylinder(thick + 2, cableHoleD / 2)
46
+ .translate(0, -baseLen / 4, -1);
47
+ baseFinal = baseFinal.subtract(cableHole);
48
+ }
49
+
50
+ // --- Back support (foldable) ---
51
+ // Build panel at origin standing up in Z, then rotate around its bottom edge, then translate to hinge
52
+ const backPanel = box(standW - 6, thick, backH, true)
53
+ .translate(0, 0, backH / 2); // bottom edge at Z=0
54
+ const backRotated = backPanel
55
+ .rotate(-foldAngle, 0, 0) // fold: 0°=vertical, 90°=flat
56
+ .translate(0, baseLen / 2, thick); // move to back edge of base
57
+
58
+ // --- Hinge cylinders (decorative pivots along X axis) ---
59
+ const hingeLen = 10;
60
+ const hingeLeft = cylinder(hingeLen, hingeR, hingeR)
61
+ .rotate(0, 90, 0)
62
+ .translate(-standW / 2 + 8, baseLen / 2, thick);
63
+ const hingeRight = cylinder(hingeLen, hingeR, hingeR)
64
+ .rotate(0, 90, 0)
65
+ .translate(standW / 2 - 8 - hingeLen, baseLen / 2, thick);
66
+
67
+ return [
68
+ { name: "Base", shape: baseFinal, color: "#556677" },
69
+ { name: "Back Support", shape: backRotated, color: "#778899" },
70
+ { name: "Hinge L", shape: hingeLeft, color: "#aabbcc" },
71
+ { name: "Hinge R", shape: hingeRight, color: "#aabbcc" },
72
+ ];