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,56 @@
1
+ // Sketch on face — place 2D profiles onto canonical or tracked planar faces,
2
+ // then extrude along that face normal.
3
+
4
+ const body = box(140, 70, 44, true).color('#d5dbe3');
5
+
6
+ const frontBadge = roundedRect(30, 12, 2.5, true)
7
+ .subtract(circle2d(2.5).translate(-8, 0))
8
+ .subtract(circle2d(2.5).translate(8, 0))
9
+ .onFace(body, 'front', { v: 10, protrude: 0.05 })
10
+ .extrude(2.4)
11
+ .color('#1d2733');
12
+
13
+ const topVent = union2d(
14
+ rect(56, 6, true),
15
+ rect(56, 6, true).translate(0, 10),
16
+ rect(56, 6, true).translate(0, -10),
17
+ )
18
+ .onFace(body, 'top', { v: 8, protrude: 0.05 })
19
+ .extrude(1.5)
20
+ .color('#55697e');
21
+
22
+ const sidePort = roundedRect(22, 10, 3, true)
23
+ .onFace(body, 'right', { u: -8, v: 0, protrude: 0.05 })
24
+ .extrude(3)
25
+ .color('#20262e');
26
+
27
+ const trackedPanel = Rectangle2D.from3Points(
28
+ point(-34, -18),
29
+ point(30, -6),
30
+ point(18, 26),
31
+ )
32
+ .extrude(18)
33
+ .translate(0, 92, 0)
34
+ .color('#c4ccd6');
35
+
36
+ const trackedSideBadge = roundedRect(22, 8, 2, true)
37
+ .onFace(trackedPanel, 'side-right', { v: -2, protrude: 0.05 })
38
+ .extrude(1.4)
39
+ .color('#27313c');
40
+
41
+ const trackedTopCap = circle2d(5)
42
+ .onFace(trackedPanel.face('top'), { u: 12, protrude: 0.05 })
43
+ .extrude(1.2)
44
+ .color('#5a6c7c');
45
+
46
+ cutPlane('Center X', [1, 0, 0], 0);
47
+
48
+ return [
49
+ { name: 'Body', shape: body },
50
+ { name: 'Front Badge', shape: frontBadge },
51
+ { name: 'Top Vent', shape: topVent },
52
+ { name: 'Side Port', shape: sidePort },
53
+ { name: 'Tracked Panel', shape: trackedPanel },
54
+ { name: 'Tracked Side Badge', shape: trackedSideBadge },
55
+ { name: 'Tracked Top Cap', shape: trackedTopCap },
56
+ ];
@@ -0,0 +1,56 @@
1
+ // Compare common sketch-rounding strategies on the same roof profile.
2
+ // Only the selective fillet keeps the lower roof corners sharp.
3
+
4
+ const radius = param("Radius", 14, { min: 4, max: 24, unit: "mm" });
5
+ const gap = 120;
6
+ const bodyWidth = 90;
7
+ const bodyHeight = 44;
8
+ const shoulderInset = 24;
9
+ const shoulderRise = 30;
10
+ const peakRise = 42;
11
+
12
+ const roofPoints = [
13
+ [0, 0],
14
+ [bodyWidth, 0],
15
+ [bodyWidth, bodyHeight],
16
+ [bodyWidth - shoulderInset, bodyHeight + shoulderRise],
17
+ [bodyWidth / 2, bodyHeight + peakRise],
18
+ [shoulderInset, bodyHeight + shoulderRise],
19
+ [0, bodyHeight],
20
+ ];
21
+
22
+ const roofRidge = [
23
+ [0, bodyHeight],
24
+ [shoulderInset, bodyHeight + shoulderRise],
25
+ [bodyWidth / 2, bodyHeight + peakRise],
26
+ [bodyWidth - shoulderInset, bodyHeight + shoulderRise],
27
+ [bodyWidth, bodyHeight],
28
+ ];
29
+
30
+ const rawProfile = polygon(roofPoints).color('#7b858c');
31
+ const roundedAllCorners = rawProfile.offset(-radius, 'Round').offset(radius, 'Round').color('#d4862d');
32
+ const strokedCenterline = union2d(
33
+ rect(bodyWidth, bodyHeight),
34
+ stroke(roofRidge, radius * 2, 'Round'),
35
+ ).color('#2a9d8f');
36
+ const hulledCircles = union2d(
37
+ rect(bodyWidth, bodyHeight),
38
+ hull2d(
39
+ circle2d(radius).translate(shoulderInset, bodyHeight + shoulderRise),
40
+ circle2d(radius).translate(bodyWidth / 2, bodyHeight + peakRise),
41
+ circle2d(radius).translate(bodyWidth - shoulderInset, bodyHeight + shoulderRise),
42
+ ),
43
+ ).color('#7f5af0');
44
+ const selectiveFillet = filletCorners(roofPoints, [
45
+ { index: 3, radius },
46
+ { index: 4, radius },
47
+ { index: 5, radius },
48
+ ]).color('#e63946');
49
+
50
+ return [
51
+ { name: "Raw polygon", sketch: rawProfile },
52
+ { name: "offset(-r).offset(+r)", sketch: roundedAllCorners.translate(gap, 0) },
53
+ { name: "stroke(..., 'Round')", sketch: strokedCenterline.translate(gap * 2, 0) },
54
+ { name: "hull2d() of circles", sketch: hulledCircles.translate(gap * 3, 0) },
55
+ { name: "filletCorners()", sketch: selectiveFillet.translate(gap * 4, 0) },
56
+ ];
@@ -0,0 +1,129 @@
1
+ // Spatial Recipes — common arrangements for multi-part assemblies.
2
+ //
3
+ // ForgeCAD coordinate system:
4
+ // X = left/right (+X = right)
5
+ // Y = forward/back (+Y = forward, −Y = back toward camera)
6
+ // Z = up/down (+Z = up)
7
+ //
8
+ // "front" anchor = −Y face (faces the camera in default view)
9
+ // "back" anchor = +Y face
10
+ //
11
+ // These recipes show how to position parts relative to each other
12
+ // using attachTo() and onFace() so you never need manual coordinate math.
13
+
14
+ const recipe = param("Recipe", 1, { min: 1, max: 3, integer: true });
15
+
16
+ if (recipe === 1) {
17
+ // ─── Recipe 1: Wall separating two spaces ───
18
+ // Wall is thin along Y. Indoor side = −Y. Outdoor side = +Y.
19
+
20
+ const wallThick = 15;
21
+ const wall = box(200, wallThick, 150, true).color('#C4A77D');
22
+
23
+ // Indoor unit: its back face meets the wall's front face
24
+ const indoor = box(120, 30, 50, true).color('#F5F5F5')
25
+ .attachTo(wall, 'front', 'back', [0, -5, 0]);
26
+
27
+ // Outdoor unit: its front face meets the wall's back face
28
+ const outdoor = box(140, 40, 60, true).color('#888888')
29
+ .attachTo(wall, 'back', 'front', [0, 5, -10]);
30
+
31
+ // Pipe hole through wall — orient along Y (same as wall thickness)
32
+ const hole = cylinder(wallThick + 2, 10).pointAlong([0, 1, 0]);
33
+ const wallWithHole = wall.subtract(hole);
34
+
35
+ // Pipe spanning both sides — also along Y, centered at same XZ as hole
36
+ const pipe = cylinder(100, 4).pointAlong([0, 1, 0]).color('#B87333');
37
+
38
+ return [
39
+ { name: "Wall", shape: wallWithHole },
40
+ { name: "Indoor Unit", shape: indoor },
41
+ { name: "Outdoor Unit", shape: outdoor },
42
+ { name: "Pipe", shape: pipe },
43
+ ];
44
+ }
45
+
46
+ if (recipe === 2) {
47
+ // ─── Recipe 2: Surface details using onFace() ───
48
+ // onFace(parent, face, {u, v, protrude}) places a child on a parent's face.
49
+ // u, v = position within the face (from center)
50
+ // protrude = how far it sticks out (positive = outward)
51
+ //
52
+ // Face coordinate mapping:
53
+ // front/back: u = left/right (X), v = up/down (Z)
54
+ // left/right: u = forward/back (Y), v = up/down (Z)
55
+ // top/bottom: u = left/right (X), v = forward/back (Y)
56
+
57
+ const body = box(100, 40, 60, true).color('#F5F5F5');
58
+
59
+ // Vent slits on front face, near bottom
60
+ const vent = box(80, 2, 12, true).color('#333333')
61
+ .onFace(body, 'front', { v: -15, protrude: 2 });
62
+
63
+ // Display panel on front face, near top-right
64
+ const display = box(35, 1.5, 8, true).color('#00ddee')
65
+ .onFace(body, 'front', { u: 20, v: 15, protrude: 1 });
66
+
67
+ // Button on front face, top-left area
68
+ const button = box(6, 2, 6, true).color('#44cc44')
69
+ .onFace(body, 'front', { u: -30, v: 18, protrude: 2 });
70
+
71
+ // Side vent on left face
72
+ const sideVent = box(2, 30, 40, true).color('#666666')
73
+ .onFace(body, 'left', { protrude: 1 });
74
+
75
+ // Fan on top, protruding 5mm
76
+ const fan = cylinder(10, 40).color('#333333')
77
+ .onFace(body, 'top', { protrude: 5 });
78
+
79
+ return [
80
+ { name: "Body", shape: body },
81
+ { name: "Front Vent", shape: vent },
82
+ { name: "Display", shape: display },
83
+ { name: "Button", shape: button },
84
+ { name: "Side Vent", shape: sideVent },
85
+ { name: "Top Fan", shape: fan },
86
+ ];
87
+ }
88
+
89
+ if (recipe === 3) {
90
+ // ─── Recipe 3: Full AC outdoor condenser ───
91
+ // Combines attachTo() for stacking and onFace() for surface details
92
+
93
+ const body = box(140, 50, 70, true).color('#888888');
94
+
95
+ // Fan housing on top — cylinder defaults to Z-up, correct for top placement
96
+ const fan = cylinder(10, 50).color('#333333')
97
+ .onFace(body, 'top', { protrude: 2 });
98
+
99
+ // Fan grill (flat disc on top of fan)
100
+ const grill = cylinder(2, 52).color('#777777')
101
+ .attachTo(fan, 'top', 'bottom');
102
+
103
+ // Pipe ports on front face — orient along Y (pointing outward from front)
104
+ const pipe1 = cylinder(20, 5).pointAlong([0, -1, 0]).color('#B87333')
105
+ .onFace(body, 'front', { u: -15, v: -10, protrude: 2 });
106
+
107
+ const pipe2 = cylinder(20, 3).pointAlong([0, -1, 0]).color('#B87333')
108
+ .onFace(body, 'front', { u: 15, v: -10, protrude: 2 });
109
+
110
+ // Side louvers on right face
111
+ const louver = box(2, 3, 50, true).color('#666666')
112
+ .onFace(body, 'right', { protrude: 1 });
113
+
114
+ // Feet on bottom
115
+ const foot = box(20, 15, 5, true).color('#222222');
116
+ const footL = foot.attachTo(body, 'bottom-left', 'top-left', [10, 5, -1]);
117
+ const footR = foot.attachTo(body, 'bottom-right', 'top-right', [-10, 5, -1]);
118
+
119
+ return [
120
+ { name: "Body", shape: body },
121
+ { name: "Fan", shape: fan },
122
+ { name: "Grill", shape: grill },
123
+ { name: "Pipe 1", shape: pipe1 },
124
+ { name: "Pipe 2", shape: pipe2 },
125
+ { name: "Louver", shape: louver },
126
+ { name: "Foot L", shape: footL },
127
+ { name: "Foot R", shape: footR },
128
+ ];
129
+ }
@@ -0,0 +1,197 @@
1
+ // Full Bathroom — parametric room with fixtures
2
+ // Bathtub, sink, toilet, mirror, shower glass panel, towel rack
3
+
4
+ const roomW = param("Room Width", 2400, { min: 1800, max: 3500, unit: "mm" });
5
+ const roomD = param("Room Depth", 2000, { min: 1500, max: 3000, unit: "mm" });
6
+ const roomH = param("Room Height", 2500, { min: 2200, max: 3000, unit: "mm" });
7
+ const wallT = param("Wall Thickness", 80, { min: 50, max: 150, unit: "mm" });
8
+ const tileH = param("Tile Height", 1200, { min: 800, max: 1800, unit: "mm" });
9
+
10
+ // ─── Room shell (floor + 3 walls, front open) ───
11
+
12
+ const floor = box(roomW, roomD, wallT);
13
+ const wallBack = box(roomW, wallT, roomH).translate(0, roomD - wallT, wallT);
14
+ const wallLeft = box(wallT, roomD, roomH).translate(0, 0, wallT);
15
+ const wallRight = box(wallT, roomD, roomH).translate(roomW - wallT, 0, wallT);
16
+ const room = union(floor, wallBack, wallLeft, wallRight);
17
+
18
+ // Tile strip on back wall
19
+ const tileStrip = box(roomW - 2 * wallT, 2, tileH)
20
+ .translate(wallT, roomD - wallT - 1, wallT);
21
+
22
+ // ─── Bathtub (left-back corner) ───
23
+
24
+ const tubL = param("Tub Length", 1500, { min: 1200, max: 1800, unit: "mm" });
25
+ const tubW = param("Tub Width", 700, { min: 550, max: 800, unit: "mm" });
26
+ const tubH = param("Tub Height", 500, { min: 400, max: 600, unit: "mm" });
27
+ const tubWall = param("Tub Wall", 40, { min: 25, max: 60, unit: "mm" });
28
+
29
+ const tubX = wallT + 20;
30
+ const tubY = roomD - wallT - tubW - 20;
31
+
32
+ const tubOuter = box(tubL, tubW, tubH);
33
+ const tubInner = box(tubL - 2 * tubWall, tubW - 2 * tubWall, tubH - tubWall)
34
+ .translate(tubWall, tubWall, tubWall);
35
+ const tubDrain = cylinder(tubWall + 2, 25)
36
+ .translate(tubL / 2, tubW / 2, 0);
37
+ const bathtub = tubOuter.subtract(tubInner).subtract(tubDrain)
38
+ .translate(tubX, tubY, wallT);
39
+
40
+ // ─── Shower (above tub) ───
41
+
42
+ const glassH = param("Glass Height", 1800, { min: 1500, max: 2200, unit: "mm" });
43
+ const glassT = 8;
44
+
45
+ // Glass panel at tub edge
46
+ const showerGlass = box(glassT, tubW, glassH)
47
+ .translate(tubX + tubL, tubY, wallT);
48
+
49
+ // Shower arm — horizontal pipe from back wall
50
+ const armLen = 250;
51
+ const showerArmX = tubX + tubL * 0.6;
52
+ const showerArmZ = wallT + glassH - 150;
53
+ const showerArm = cylinder(armLen, 12)
54
+ .rotate(90, 0, 0)
55
+ .translate(showerArmX, roomD - wallT, showerArmZ);
56
+
57
+ // Shower head — disc at end of arm
58
+ const showerHead = cylinder(15, 55, 55)
59
+ .translate(showerArmX, roomD - wallT - armLen, showerArmZ - 7);
60
+
61
+ const shower = union(showerArm, showerHead);
62
+
63
+ // ─── Toilet (right side, against back wall) ───
64
+
65
+ const toiletW = param("Toilet Width", 380, { min: 340, max: 420, unit: "mm" });
66
+ const toiletD = param("Toilet Depth", 650, { min: 550, max: 750, unit: "mm" });
67
+ const toiletH = 400;
68
+
69
+ const toiletX = roomW - wallT - toiletW - 150;
70
+ const toiletY = roomD - wallT - toiletD - 20;
71
+ const toiletCX = toiletX + toiletW / 2;
72
+ const toiletCY = toiletY + toiletD * 0.4; // bowl center forward of midpoint
73
+
74
+ // Bowl — tapered cylinder, hollowed
75
+ const bowlR = toiletW / 2;
76
+ const bowlOuter = cylinder(toiletH, bowlR, bowlR * 0.85);
77
+ const bowlInner = cylinder(toiletH - 40, bowlR - 30, bowlR * 0.85 - 30)
78
+ .translate(0, 0, 40);
79
+ const bowl = bowlOuter.subtract(bowlInner)
80
+ .translate(toiletCX, toiletCY, wallT);
81
+
82
+ // Tank — box behind the bowl
83
+ const tankW = toiletW * 0.85;
84
+ const tankD = 150;
85
+ const tankH = 320;
86
+ const tank = box(tankW, tankD, tankH, true)
87
+ .translate(toiletCX, toiletY + toiletD - tankD / 2 - 10, wallT + toiletH / 2 + tankH / 2 - 80);
88
+
89
+ // Lid — flat slab on top of tank
90
+ const lid = box(tankW + 10, tankD + 10, 15, true)
91
+ .translate(toiletCX, toiletY + toiletD - tankD / 2 - 10, wallT + toiletH / 2 + tankH - 80 + 7);
92
+
93
+ // Seat — flat ring on top of bowl
94
+ const seatOuter = cylinder(18, bowlR - 5, bowlR * 0.85 - 5);
95
+ const seatInner = cylinder(20, bowlR - 30, bowlR * 0.85 - 30).translate(0, 0, -1);
96
+ const seat = seatOuter.subtract(seatInner)
97
+ .translate(toiletCX, toiletCY, wallT + toiletH - 18);
98
+
99
+ const toilet = union(bowl, tank, lid, seat);
100
+
101
+ // ─── Sink (right wall, facing into room) ───
102
+
103
+ const sinkW = param("Sink Width", 550, { min: 400, max: 700, unit: "mm" });
104
+ const sinkD = 420;
105
+ const sinkH = 160;
106
+ const sinkZ = 850;
107
+
108
+ // Sink centered on right wall, facing left (into room)
109
+ const sinkCX = roomW - wallT - sinkD / 2 - 30;
110
+ const sinkCY = roomD * 0.35;
111
+
112
+ const basinOuter = box(sinkD, sinkW, sinkH, true);
113
+ const basinInner = box(sinkD - 50, sinkW - 50, sinkH - 25, true).translate(0, 0, 12);
114
+ const drain = cylinder(27, 18).translate(0, 0, -sinkH / 2);
115
+ const basin = basinOuter.subtract(basinInner).subtract(drain)
116
+ .translate(sinkCX, sinkCY, wallT + sinkZ + sinkH / 2);
117
+
118
+ // Pedestal
119
+ const pedestal = box(80, 80, sinkZ, true)
120
+ .translate(sinkCX, sinkCY, wallT + sinkZ / 2);
121
+
122
+ // Faucet — vertical stem + curved spout
123
+ const faucetX = sinkCX + sinkD / 2 - 40;
124
+ const faucetZ = wallT + sinkZ + sinkH;
125
+ const faucetStem = cylinder(100, 10)
126
+ .translate(faucetX, sinkCY, faucetZ);
127
+ const faucetSpout = box(80, 16, 16, true)
128
+ .translate(faucetX - 40, sinkCY, faucetZ + 100);
129
+ // Handles
130
+ const handleL = box(30, 8, 8, true)
131
+ .translate(faucetX, sinkCY - 30, faucetZ + 50);
132
+ const handleR = box(30, 8, 8, true)
133
+ .translate(faucetX, sinkCY + 30, faucetZ + 50);
134
+
135
+ const sink = union(basin, pedestal, faucetStem, faucetSpout, handleL, handleR);
136
+
137
+ // ─── Mirror (on right wall, above sink) ───
138
+
139
+ const mirrorW = param("Mirror Width", 600, { min: 400, max: 900, unit: "mm" });
140
+ const mirrorH = param("Mirror Height", 800, { min: 500, max: 1200, unit: "mm" });
141
+ const mirrorT = 5;
142
+ const frameW = 15;
143
+
144
+ const mirrorZ = wallT + sinkZ + sinkH + 150;
145
+ const mirrorCY = sinkCY;
146
+
147
+ // Frame — flat rectangle with cutout
148
+ const frameOuter = box(frameW, mirrorW + 2 * frameW, mirrorH + 2 * frameW);
149
+ const frameInner = box(frameW + 2, mirrorW, mirrorH).translate(-1, frameW, frameW);
150
+ const frame = frameOuter.subtract(frameInner)
151
+ .translate(roomW - wallT - frameW, mirrorCY - mirrorW / 2 - frameW, mirrorZ);
152
+
153
+ // Mirror surface
154
+ const mirrorSurface = box(mirrorT, mirrorW, mirrorH)
155
+ .translate(roomW - wallT - mirrorT - 1, mirrorCY - mirrorW / 2, mirrorZ);
156
+
157
+ // ─── Towel rack (left wall) ───
158
+
159
+ const rackZ = 1100;
160
+ const rackLen = 600;
161
+ const barR = 8;
162
+ const bracketSize = 25;
163
+ const rackY = roomD * 0.4;
164
+
165
+ const bracketL = box(70, bracketSize, bracketSize)
166
+ .translate(wallT, rackY - rackLen / 2, wallT + rackZ);
167
+ const bracketR = box(70, bracketSize, bracketSize)
168
+ .translate(wallT, rackY + rackLen / 2 - bracketSize, wallT + rackZ);
169
+ const towelBar = cylinder(rackLen - bracketSize * 2, barR)
170
+ .rotate(-90, 0, 0)
171
+ .translate(wallT + 50, rackY - rackLen / 2 + bracketSize, wallT + rackZ + bracketSize / 2);
172
+
173
+ const towelRack = union(bracketL, bracketR, towelBar);
174
+
175
+ // ─── Bath mat (in front of tub) ───
176
+
177
+ const matW = 700;
178
+ const matD = 450;
179
+ const matH = 8;
180
+ const bathMat = box(matW, matD, matH, true)
181
+ .translate(tubX + tubL / 2, tubY - matD / 2 - 30, wallT + matH / 2);
182
+
183
+ // ─── Assemble ───
184
+
185
+ return [
186
+ { name: "Room", shape: room, color: "#e8e0d4" },
187
+ { name: "Tile Strip", shape: tileStrip, color: "#5a8a9a" },
188
+ { name: "Bathtub", shape: bathtub, color: "#f0f0f0" },
189
+ { name: "Shower Glass", shape: showerGlass, color: "#aaddee" },
190
+ { name: "Shower Head", shape: shower, color: "#b0b0b0" },
191
+ { name: "Toilet", shape: toilet, color: "#f5f5f0" },
192
+ { name: "Sink", shape: sink, color: "#f0f0f0" },
193
+ { name: "Mirror Frame", shape: frame, color: "#2a2a2a" },
194
+ { name: "Mirror", shape: mirrorSurface, color: "#b8ccd8" },
195
+ { name: "Towel Rack", shape: towelRack, color: "#707070" },
196
+ { name: "Bath Mat", shape: bathMat, color: "#6b9e7a" },
197
+ ];
@@ -0,0 +1,39 @@
1
+ // Bolt and Nut — helical threads via twisted extrusion
2
+ //
3
+ // lib.bolt() and lib.nut() use Manifold's native extrude+twist
4
+ // to sweep a thread tooth profile helically — clean geometry, no SDF grid.
5
+
6
+ const diameter = param("Diameter", 8, { min: 4, max: 20, unit: "mm" });
7
+ const length = param("Length", 30, { min: 10, max: 60, unit: "mm" });
8
+ const pitch = param("Pitch", 1.25, { min: 0.5, max: 3, step: 0.25, unit: "mm" });
9
+ const headH = param("Head Height", 5, { min: 3, max: 12, unit: "mm" });
10
+ const headAF = param("Head AF", 13, { min: 7, max: 30, unit: "mm" });
11
+ const nutHeight = param("Nut Height", 6.5, { min: 3, max: 12, unit: "mm" });
12
+ const nutAF = param("Nut AF", 13, { min: 7, max: 30, unit: "mm" });
13
+ const showNut = param("Show Nut", 1, { min: 0, max: 1, step: 1 });
14
+ const nutPos = param("Nut Position", 5, { min: 0, max: 30, unit: "mm" });
15
+ const segments = param("Segments", 36, { min: 12, max: 72, step: 4, integer: true });
16
+
17
+ const boltShape = lib.bolt(diameter, length, {
18
+ pitch,
19
+ headHeight: headH,
20
+ headAcrossFlats: headAF,
21
+ segments,
22
+ });
23
+
24
+ const result = [
25
+ { name: "Bolt", shape: boltShape, color: "#aaaaaa" },
26
+ ];
27
+
28
+ if (showNut >= 1) {
29
+ const nutShape = lib.nut(diameter, {
30
+ pitch,
31
+ height: nutHeight,
32
+ acrossFlats: nutAF,
33
+ segments,
34
+ }).translate(0, 0, -length + nutPos + nutHeight / 2);
35
+
36
+ result.push({ name: "Nut", shape: nutShape, color: "#999999" });
37
+ }
38
+
39
+ return result;
@@ -0,0 +1,18 @@
1
+ // Bolt Pattern — circularPattern + linearPattern demo
2
+
3
+ const baseR = param("Base Radius", 40, { min: 20, max: 80, unit: "mm" });
4
+ const baseH = param("Base Height", 10, { min: 5, max: 20, unit: "mm" });
5
+ const boltR = param("Bolt Radius", 3, { min: 1, max: 6, unit: "mm" });
6
+ const boltCount = param("Bolt Count", 6, { min: 3, max: 12 });
7
+ const boltCircleR = param("Bolt Circle", 30, { min: 15, max: 70, unit: "mm" });
8
+
9
+ // Base plate
10
+ const base = circle2d(baseR).extrude(baseH);
11
+
12
+ // Single bolt hole
13
+ const hole = circle2d(boltR).extrude(baseH + 2).translate(boltCircleR, 0, -1);
14
+
15
+ // Circular pattern of holes
16
+ const holes = circularPattern(hole, boltCount);
17
+
18
+ return base.subtract(holes);
@@ -0,0 +1,101 @@
1
+ // Water Bottle — revolve profile with cap
2
+ // Demonstrates: revolve, polygon, difference, smoothing, multi-object
3
+
4
+ const bodyH = param("Body Height", 180, { min: 120, max: 250, unit: "mm" });
5
+ const bodyR = param("Body Radius", 35, { min: 25, max: 50, unit: "mm" });
6
+ const neckH = param("Neck Height", 30, { min: 15, max: 50, unit: "mm" });
7
+ const neckR = param("Neck Radius", 14, { min: 10, max: 25, unit: "mm" });
8
+ const wall = param("Wall Thickness", 2, { min: 1, max: 5, unit: "mm" });
9
+ const capH = param("Cap Height", 18, { min: 10, max: 30, unit: "mm" });
10
+ const shoulderR = param("Shoulder Curve", 20, { min: 5, max: 40, unit: "mm" });
11
+
12
+ // Outer profile — polygon traced from bottom-center up and around
13
+ // Bottom flat → body → shoulder curve → neck
14
+ const steps = 12;
15
+ const outerPts = [];
16
+
17
+ // Bottom center
18
+ outerPts.push([0, 0]);
19
+ // Bottom edge
20
+ outerPts.push([bodyR, 0]);
21
+ // Body straight up
22
+ outerPts.push([bodyR, bodyH - shoulderR]);
23
+
24
+ // Shoulder curve (quarter circle from body to neck)
25
+ for (let i = 0; i <= steps; i++) {
26
+ const t = (i / steps) * Math.PI / 2;
27
+ const x = neckR + (bodyR - neckR) * Math.cos(t);
28
+ const y = bodyH - shoulderR + shoulderR * Math.sin(t);
29
+ outerPts.push([x, y]);
30
+ }
31
+
32
+ // Neck top
33
+ outerPts.push([neckR, bodyH + neckH]);
34
+ // Back to center at top
35
+ outerPts.push([0, bodyH + neckH]);
36
+
37
+ const outerProfile = polygon(outerPts);
38
+ const outerBody = outerProfile.revolve();
39
+
40
+ // Inner profile — offset inward by wall thickness
41
+ const innerPts = [];
42
+ const innerBodyR = bodyR - wall;
43
+ const innerNeckR = neckR - wall;
44
+
45
+ innerPts.push([0, wall]); // bottom floor
46
+ innerPts.push([innerBodyR, wall]);
47
+ innerPts.push([innerBodyR, bodyH - shoulderR]);
48
+
49
+ for (let i = 0; i <= steps; i++) {
50
+ const t = (i / steps) * Math.PI / 2;
51
+ const x = innerNeckR + (innerBodyR - innerNeckR) * Math.cos(t);
52
+ const y = bodyH - shoulderR + shoulderR * Math.sin(t);
53
+ innerPts.push([x, y]);
54
+ }
55
+
56
+ innerPts.push([innerNeckR, bodyH + neckH + 1]); // extend past top
57
+ innerPts.push([0, bodyH + neckH + 1]);
58
+
59
+ const innerProfile = polygon(innerPts);
60
+ const innerBody = innerProfile.revolve();
61
+
62
+ const bottle = outerBody.subtract(innerBody);
63
+
64
+ // Thread ridge on neck (simple ring)
65
+ const threadZ = bodyH + neckH * 0.3;
66
+ const threadRing = cylinder(2, neckR + 1.5, neckR + 1.5)
67
+ .subtract(cylinder(4, neckR - 0.5, neckR - 0.5).translate(0, 0, -1))
68
+ .translate(0, 0, threadZ);
69
+
70
+ const threadRing2 = cylinder(2, neckR + 1.5, neckR + 1.5)
71
+ .subtract(cylinder(4, neckR - 0.5, neckR - 0.5).translate(0, 0, -1))
72
+ .translate(0, 0, threadZ + 5);
73
+
74
+ const bottleWithThreads = union(bottle, threadRing, threadRing2);
75
+
76
+ // Cap — hollow cylinder that sits on top of neck
77
+ const capOuterR = neckR + 3;
78
+ const capOuter = cylinder(capH, capOuterR, capOuterR);
79
+ const capInner = cylinder(capH - 2, neckR + 0.5, neckR + 0.5).translate(0, 0, 2);
80
+ const cap = capOuter.subtract(capInner)
81
+ .translate(0, 0, bodyH + neckH);
82
+
83
+ // Grip ridges on cap (vertical grooves)
84
+ const ridgeCount = 24;
85
+ const ridges = [];
86
+ for (let i = 0; i < ridgeCount; i++) {
87
+ const angle = (i / ridgeCount) * 360;
88
+ const rad = angle * Math.PI / 180;
89
+ const rx = (capOuterR + 0.5) * Math.cos(rad);
90
+ const ry = (capOuterR + 0.5) * Math.sin(rad);
91
+ ridges.push(
92
+ box(1.5, 1.5, capH - 4, true)
93
+ .translate(rx, ry, bodyH + neckH + capH / 2)
94
+ );
95
+ }
96
+ const capWithGrip = difference(cap, ...ridges);
97
+
98
+ return [
99
+ { name: "Bottle", shape: bottleWithThreads, color: "#88ccee" },
100
+ { name: "Cap", shape: capWithGrip, color: "#2255aa" },
101
+ ];
@@ -0,0 +1,62 @@
1
+ // Four-Legged Chair — parametric dining chair
2
+
3
+ const seatW = param("Seat Width", 45, { min: 30, max: 60, unit: "mm" });
4
+ const seatD = param("Seat Depth", 42, { min: 30, max: 55, unit: "mm" });
5
+ const seatT = param("Seat Thickness", 3, { min: 2, max: 6, unit: "mm" });
6
+ const seatH = param("Seat Height", 45, { min: 35, max: 55, unit: "mm" });
7
+
8
+ const legW = param("Leg Width", 4, { min: 2, max: 8, unit: "mm" });
9
+ const legInset = param("Leg Inset", 3, { min: 0, max: 10, unit: "mm" });
10
+
11
+ const backH = param("Back Height", 40, { min: 25, max: 60, unit: "mm" });
12
+ const backT = param("Back Thickness", 3, { min: 2, max: 6, unit: "mm" });
13
+ const backTilt = param("Back Tilt", 5, { min: 0, max: 15, unit: "°" });
14
+
15
+ const stretcher = param("Stretchers", 1, { min: 0, max: 1, step: 1 });
16
+ const stretcherH = param("Stretcher Height", 12, { min: 5, max: 30, unit: "mm" });
17
+ const stretcherW = param("Stretcher Width", 2, { min: 1, max: 4, unit: "mm" });
18
+
19
+ // --- Seat ---
20
+ const seat = box(seatW, seatD, seatT).translate(0, 0, seatH);
21
+
22
+ // --- Legs ---
23
+ const legPositions = [
24
+ [legInset, legInset], // front-left
25
+ [seatW - legInset - legW, legInset], // front-right
26
+ [legInset, seatD - legInset - legW], // back-left
27
+ [seatW - legInset - legW, seatD - legInset - legW] // back-right
28
+ ];
29
+
30
+ const legs = union(
31
+ ...legPositions.map(([x, y]) =>
32
+ box(legW, legW, seatH).translate(x, y, 0)
33
+ )
34
+ );
35
+
36
+ // --- Backrest ---
37
+ // Tilted slightly backward — rotate around X then position at back edge
38
+ const backPanel = box(seatW, backT, backH)
39
+ .translate(-seatW / 2, -backT / 2, 0) // center for rotation
40
+ .rotate(-backTilt, 0, 0)
41
+ .translate(seatW / 2, seatD - legInset - legW / 2, seatH + seatT);
42
+
43
+ // --- Stretchers (side rails between legs) ---
44
+ const parts = [seat, legs, backPanel];
45
+
46
+ if (stretcher >= 1) {
47
+ // Side stretchers (along Y, connecting front-back legs)
48
+ const sideLen = seatD - 2 * legInset - legW;
49
+ const leftStr = box(stretcherW, sideLen, stretcherW)
50
+ .translate(legInset + legW / 2 - stretcherW / 2, legInset + legW, stretcherH);
51
+ const rightStr = box(stretcherW, sideLen, stretcherW)
52
+ .translate(seatW - legInset - legW / 2 - stretcherW / 2, legInset + legW, stretcherH);
53
+
54
+ // Front stretcher (along X, connecting front legs)
55
+ const frontLen = seatW - 2 * legInset - legW;
56
+ const frontStr = box(frontLen, stretcherW, stretcherW)
57
+ .translate(legInset + legW, legInset + legW / 2 - stretcherW / 2, stretcherH);
58
+
59
+ parts.push(leftStr, rightStr, frontStr);
60
+ }
61
+
62
+ return union(...parts);