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,108 @@
1
+ // AC Unit Model
2
+ // Shows inside and outside units separated by a wall
3
+
4
+ // === Parameters ===
5
+ const wallThick = param("Wall Thickness", 200, { min: 100, max: 400, unit: "mm" });
6
+ const wallHeight = param("Wall Height", 2500, { min: 1500, max: 4000, unit: "mm" });
7
+ const wallWidth = param("Wall Width", 3000, { min: 1000, max: 5000, unit: "mm" });
8
+
9
+ // Inside Unit (Evaporator)
10
+ const inW = param("Inside Width", 800, { min: 400, max: 1200, unit: "mm" });
11
+ const inH = param("Inside Height", 300, { min: 200, max: 500, unit: "mm" });
12
+ const inD = param("Inside Depth", 250, { min: 150, max: 400, unit: "mm" });
13
+
14
+ // Outside Unit (Condenser)
15
+ const outW = param("Outside Width", 900, { min: 500, max: 1500, unit: "mm" });
16
+ const outH = param("Outside Height", 700, { min: 400, max: 1000, unit: "mm" });
17
+ const outD = param("Outside Depth", 400, { min: 200, max: 600, unit: "mm" });
18
+
19
+ // === Wall ===
20
+ // Create a wall with a hole for the pipes
21
+ const wall = box(wallWidth, wallThick, wallHeight, true);
22
+ const pipeHole = cylinder(wallThick + 10, 60).rotate(90, 0, 0);
23
+ const wallWithHole = wall.subtract(pipeHole);
24
+
25
+ // === Inside Unit ===
26
+ // Main housing
27
+ const insideBox = box(inW, inD, inH, true);
28
+
29
+ // Air vents (slats on the front facing the room)
30
+ const vents = [];
31
+ const ventCount = 8;
32
+ const ventPadZ = 20; // Padding from top/bottom
33
+ const ventSpacing = (inH - 2 * ventPadZ) / (ventCount - 1);
34
+
35
+ for (let i = 0; i < ventCount; i++) {
36
+ // Centered box (true) ensures it aligns with the unit's center X
37
+ vents.push(
38
+ box(inW - 20, 2, 4, true)
39
+ .translate(0, -inD / 2 - 1, -inH / 2 + ventPadZ + i * ventSpacing)
40
+ );
41
+ }
42
+
43
+ // Control panel area (simple box on top right)
44
+ // Centered box, translated so its right edge aligns with the unit's right edge
45
+ const panelW = 100;
46
+ const panel = box(panelW, 10, 40, true)
47
+ .translate(inW / 2 - panelW / 2, -inD / 2 - 5, inH / 2 - 20);
48
+
49
+ const insideUnit = union(insideBox, ...vents, panel)
50
+ .translate(0, -wallThick / 2 - inD / 2, 0);
51
+
52
+ // === Outside Unit ===
53
+ // Main housing
54
+ const outsideBox = box(outW, outD, outH, true);
55
+
56
+ // Fan grill (grid of holes on the back)
57
+ const grillHoles = [];
58
+ const grillRows = 5;
59
+ const grillCols = 8;
60
+ const grillPadX = 40;
61
+ const grillPadZ = 40;
62
+ const grillStepX = (outW - 2 * grillPadX) / (grillCols - 1);
63
+ const grillStepZ = (outH - 2 * grillPadZ) / (grillRows - 1);
64
+
65
+ for (let r = 0; r < grillRows; r++) {
66
+ for (let c = 0; c < grillCols; c++) {
67
+ grillHoles.push(
68
+ cylinder(10, 8).translate(
69
+ -outW / 2 + grillPadX + c * grillStepX,
70
+ outD / 2 + 1,
71
+ -outH / 2 + grillPadZ + r * grillStepZ
72
+ )
73
+ );
74
+ }
75
+ }
76
+
77
+ // Compressor bump (raised area on top)
78
+ // Centered box to align with unit
79
+ const compressorBump = box(outW * 0.6, outD * 0.6, 50, true)
80
+ .translate(0, 0, outH / 2 - 25);
81
+
82
+ const outsideUnit = outsideBox
83
+ .subtract(...grillHoles)
84
+ .add(compressorBump)
85
+ .translate(0, wallThick / 2 + outD / 2, 0);
86
+
87
+ // === Connecting Pipes ===
88
+ // Two pipes running through the wall connecting the units
89
+ // Length spans the wall thickness. Centered (true) so they bridge the gap exactly.
90
+ const pipeRadius = 15;
91
+ const pipeOffsetX = inW * 0.25; // Position pipes at 25% width
92
+ const pipeZ1 = -inH / 2 + inH * 0.2; // Lower pipe
93
+ const pipeZ2 = -inH / 2 + inH * 0.4; // Upper pipe
94
+
95
+ const pipe1 = cylinder(wallThick, pipeRadius, undefined, undefined, true)
96
+ .rotate(90, 0, 0)
97
+ .translate(-pipeOffsetX, 0, pipeZ1);
98
+ const pipe2 = cylinder(wallThick, pipeRadius, undefined, undefined, true)
99
+ .rotate(90, 0, 0)
100
+ .translate(pipeOffsetX, 0, pipeZ2);
101
+
102
+ // === Assembly ===
103
+ return [
104
+ { name: "Wall", shape: wallWithHole.color("#CCCCCC") },
105
+ { name: "Inside Unit", shape: insideUnit.color("#F0F0F0") },
106
+ { name: "Outside Unit", shape: outsideUnit.color("#E0E0E0") },
107
+ { name: "Pipes", shape: union(pipe1, pipe2).color("#333333") }
108
+ ];
@@ -0,0 +1,174 @@
1
+ // Home AC Unit V3 - Fixed cylinder orientation and positioning
2
+
3
+ // Parameters
4
+ const wallThick = param("Wall Thickness", 20, { min: 10, max: 40, unit: "mm" });
5
+ const indoorWidth = param("Indoor Width", 80, { min: 60, max: 120, unit: "mm" });
6
+ const indoorHeight = param("Indoor Height", 30, { min: 20, max: 50, unit: "mm" });
7
+ const indoorDepth = param("Indoor Depth", 20, { min: 15, max: 35, unit: "mm" });
8
+ const outdoorWidth = param("Outdoor Width", 70, { min: 50, max: 100, unit: "mm" });
9
+ const outdoorHeight = param("Outdoor Height", 55, { min: 40, max: 80, unit: "mm" });
10
+ const outdoorDepth = param("Outdoor Depth", 30, { min: 20, max: 50, unit: "mm" });
11
+
12
+ // --- Wall (reference) ---
13
+ // Centered on Y=0: front at Y=-wallThick/2, back at Y=+wallThick/2
14
+ const wall = box(120, wallThick, 100, true).color('#d4c4a8');
15
+
16
+ // --- INDOOR UNIT (on FRONT of wall, -Y side) ---
17
+ const indoorBody = box(indoorWidth, indoorDepth, indoorHeight, true)
18
+ .color('#f5f5f5')
19
+ .attachTo(wall, 'front', 'back', [0, 0, 20]); // Raised 20mm above center
20
+
21
+ // Front panel (flush with indoor body front)
22
+ const frontPanel = roundedRect(indoorWidth - 4, indoorHeight - 4, 3, true)
23
+ .extrude(2)
24
+ .color('#ffffff')
25
+ .attachTo(indoorBody, 'front', 'back');
26
+
27
+ // Air outlet vents
28
+ const ventCount = 5;
29
+ const ventSpacing = indoorHeight / (ventCount + 1);
30
+ const vents = [];
31
+ for (let i = 1; i <= ventCount; i++) {
32
+ const z = -indoorHeight/2 + i * ventSpacing;
33
+ const vent = box(indoorWidth - 20, 2, 3, true)
34
+ .color('#333333')
35
+ .attachTo(frontPanel, 'front', 'center', [0, -1, z]);
36
+ vents.push(vent);
37
+ }
38
+
39
+ // Display panel
40
+ const display = box(15, 1, 6, true)
41
+ .color('#1a1a2e')
42
+ .attachTo(frontPanel, 'front', 'center', [indoorWidth/4, -1, indoorHeight/4]);
43
+
44
+ // LED indicator
45
+ const led = sphere(1.5).color('#00ff00')
46
+ .attachTo(display, 'front', 'front', [0, -1, -2]);
47
+
48
+ // Mounting bracket
49
+ const bracket = box(indoorWidth - 10, 3, 5)
50
+ .color('#888888')
51
+ .attachTo(indoorBody, 'back', 'front');
52
+
53
+ // --- OUTDOOR UNIT (on BACK of wall, +Y side) ---
54
+ const outdoorBody = box(outdoorWidth, outdoorDepth, outdoorHeight, true)
55
+ .color('#e8e8e8')
56
+ .attachTo(wall, 'back', 'front', [0, 0, 10]); // Raised slightly
57
+
58
+ // Big fan housing - CYLINDER MUST BE ORIENTED ALONG Y FIRST!
59
+ const fanRadius = outdoorHeight * 0.35;
60
+ const fanDepth = outdoorDepth - 8;
61
+ const fanHousing = cylinder(fanDepth, fanRadius)
62
+ .pointAlong([0, 1, 0]) // Orient along Y axis (perpendicular to wall)
63
+ .color('#555555')
64
+ .attachTo(outdoorBody, 'front', 'back'); // Cylinder's back face at outdoor body's front
65
+
66
+ // Fan blades
67
+ const fanBlade = cylinder(3, fanRadius - 3)
68
+ .pointAlong([0, 1, 0])
69
+ .color('#222222')
70
+ .attachTo(fanHousing, 'front', 'front');
71
+
72
+ // Fan grille (concentric rings)
73
+ const grilleInner = circle2d(fanRadius * 0.4).color('#666666');
74
+ const grilleMiddle = circle2d(fanRadius * 0.7)
75
+ .subtract(circle2d(fanRadius * 0.6))
76
+ .color('#666666');
77
+ const grilleOuter = circle2d(fanRadius * 0.9)
78
+ .subtract(circle2d(fanRadius * 0.85))
79
+ .color('#666666');
80
+ const grille = union2d(grilleInner, grilleMiddle, grilleOuter)
81
+ .extrude(0.5)
82
+ .attachTo(fanBlade, 'front', 'front');
83
+
84
+ // Heat exchanger fins (on side, vertical)
85
+ const finCount = 8;
86
+ const fins = [];
87
+ for (let i = 0; i < finCount; i++) {
88
+ const fin = box(0.5, outdoorDepth - 8, outdoorHeight - 10, true)
89
+ .color('#cc8844')
90
+ .attachTo(outdoorBody, 'right', 'center', [outdoorWidth/2 - 3 + i * 3, 0, 0]);
91
+ fins.push(fin);
92
+ }
93
+
94
+ // Outdoor unit legs
95
+ const legH = 8;
96
+ const leg1 = box(8, 8, legH).color('#444444')
97
+ .attachTo(outdoorBody, 'bottom', 'top', [outdoorWidth/3, outdoorDepth/3, -legH/2]);
98
+ const leg2 = box(8, 8, legH).color('#444444')
99
+ .attachTo(outdoorBody, 'bottom', 'top', [-outdoorWidth/3, outdoorDepth/3, -legH/2]);
100
+
101
+ // --- CONNECTING PIPES ---
102
+ const pipeR = 4;
103
+
104
+ // Get bounding boxes for precise pipe routing
105
+ const indoorBB = indoorBody.boundingBox();
106
+ const outdoorBB = outdoorBody.boundingBox();
107
+
108
+ // Pipe exit point: bottom-back of indoor unit
109
+ const exitX = -indoorWidth/3;
110
+ const exitY = indoorBB.min[1]; // Back of indoor unit (most negative Y)
111
+ const exitZ = indoorBB.min[2] + 8; // Near bottom
112
+
113
+ // Pipe entry point: bottom-front of outdoor unit
114
+ const entryX = outdoorWidth/3;
115
+ const entryY = outdoorBB.max[1]; // Front of outdoor unit (most positive Y)
116
+ const entryZ = outdoorBB.min[2] + 12;
117
+
118
+ // Segment 1: Drop down from indoor unit
119
+ const dropH = 20;
120
+ const pipe1 = cylinder(dropH, pipeR)
121
+ .pointAlong([0, 0, -1]) // Pointing down
122
+ .translate(exitX, exitY + 2, exitZ)
123
+ .color('#c4a066');
124
+
125
+ // Segment 2: Horizontal through wall
126
+ const midZ = exitZ - dropH;
127
+ const pipe2Len = entryY - exitY + 4; // From indoor exit to outdoor entry
128
+ const pipe2 = cylinder(pipe2Len, pipeR)
129
+ .pointAlong([0, 1, 0]) // Pointing toward outdoor (positive Y)
130
+ .translate(exitX, exitY + pipe2Len/2, midZ)
131
+ .color('#c4a066');
132
+
133
+ // Segment 3: Rise up to outdoor unit
134
+ const riseH = entryZ - midZ;
135
+ const pipe3 = cylinder(riseH, pipeR)
136
+ .pointAlong([0, 0, 1]) // Pointing up
137
+ .translate(entryX, entryY - 2, midZ)
138
+ .color('#c4a066');
139
+
140
+ // Segment 4: Connect into outdoor unit
141
+ const pipe4 = cylinder(12, pipeR)
142
+ .pointAlong([0, -1, 0]) // Pointing toward outdoor unit
143
+ .translate(entryX, entryY + 6, entryZ)
144
+ .color('#c4a066');
145
+
146
+ // --- Return as named objects ---
147
+ return [
148
+ { name: "Wall", shape: wall },
149
+
150
+ // Indoor unit
151
+ { name: "Indoor Body", shape: indoorBody },
152
+ { name: "Front Panel", shape: frontPanel },
153
+ { name: "Vents", group: vents.map((v, i) => ({ name: `Vent ${i + 1}`, shape: v })) },
154
+ { name: "Display", shape: display },
155
+ { name: "LED", shape: led },
156
+ { name: "Mounting Bracket", shape: bracket },
157
+
158
+ // Outdoor unit
159
+ { name: "Outdoor Body", shape: outdoorBody },
160
+ { name: "Fan Housing", shape: fanHousing },
161
+ { name: "Fan Blades", shape: fanBlade },
162
+ { name: "Fan Grille", shape: grille },
163
+ { name: "Heat Fins", group: fins.map((f, i) => ({ name: `Fin ${i + 1}`, shape: f })) },
164
+ { name: "Legs", group: [
165
+ { name: "Left Leg", shape: leg1 },
166
+ { name: "Right Leg", shape: leg2 }
167
+ ] },
168
+
169
+ // Pipes
170
+ { name: "Pipe 1 (drop)", shape: pipe1 },
171
+ { name: "Pipe 2 (horizontal)", shape: pipe2 },
172
+ { name: "Pipe 3 (rise)", shape: pipe3 },
173
+ { name: "Pipe 4 (connect)", shape: pipe4 },
174
+ ];
@@ -0,0 +1,236 @@
1
+ // Home AC Unit V3 - Fixed pipe connections to properly enter indoor unit
2
+ // Coordinate system: Z-up (X=left/right, Y=forward/back, Z=up/down)
3
+ // Front (-Y) = indoor side, Back (+Y) = outdoor side
4
+
5
+ // Parameters
6
+ const wallThick = param("Wall Thickness", 150, { min: 100, max: 300, unit: "mm" });
7
+ const indoorW = param("Indoor Width", 700, { min: 500, max: 1000, unit: "mm" });
8
+ const indoorH = param("Indoor Height", 250, { min: 180, max: 350, unit: "mm" });
9
+ const indoorD = param("Indoor Depth", 200, { min: 150, max: 300, unit: "mm" });
10
+ const outdoorW = param("Outdoor Width", 800, { min: 600, max: 1200, unit: "mm" });
11
+ const outdoorH = param("Outdoor Height", 550, { min: 400, max: 700, unit: "mm" });
12
+ const outdoorD = param("Outdoor Depth", 300, { min: 200, max: 400, unit: "mm" });
13
+ const mountHeight = param("Mount Height", 2000, { min: 1500, max: 2500, unit: "mm" });
14
+ const pipeSpacing = param("Pipe Spacing", 80, { min: 50, max: 150, unit: "mm" });
15
+
16
+ // --- Wall (vertical, separates indoor and outdoor) ---
17
+ const wallW = Math.max(indoorW, outdoorW) + 200;
18
+ const wallH = 2500;
19
+ const wall = box(wallW, wallThick, wallH, true)
20
+ .translate(0, 0, wallH / 2)
21
+ .color('#E8E4D9');
22
+
23
+ // --- Indoor Unit (on the -Y side of wall) ---
24
+ // Position: back face flush with wall front (Y = -wallThick/2), centered at mountHeight
25
+ const indoorUnitY = -wallThick/2 - indoorD/2;
26
+ const indoorUnitZ = mountHeight;
27
+
28
+ // Main body
29
+ const indoorBody = box(indoorW, indoorD, indoorH, true)
30
+ .color('#F5F5F0')
31
+ .translate(0, indoorUnitY, indoorUnitZ);
32
+
33
+ // Front panel with vent slats
34
+ const ventPanel = box(indoorW - 20, 5, indoorH * 0.6, true)
35
+ .color('#E0E0E0')
36
+ .translate(0, indoorUnitY - indoorD/2 - 2, indoorUnitZ - indoorH * 0.15);
37
+
38
+ // Top vent (air outlet)
39
+ const topVent = box(indoorW - 40, indoorD - 10, 15, true)
40
+ .color('#D0D0D0')
41
+ .translate(0, indoorUnitY, indoorUnitZ + indoorH/2 - 5);
42
+
43
+ // Display panel
44
+ const display = box(80, 3, 30, true)
45
+ .color('#1a1a2e')
46
+ .translate(indoorW * 0.3, indoorUnitY - indoorD/2 - 1, indoorUnitZ + indoorH * 0.25);
47
+
48
+ // LED indicator
49
+ const led = cylinder(2, 3, undefined, undefined, true)
50
+ .color('#00ff00')
51
+ .pointAlong([0, -1, 0])
52
+ .translate(indoorW * 0.3, indoorUnitY - indoorD/2 - 3, indoorUnitZ + indoorH * 0.25);
53
+
54
+ // Indoor mounting brackets (L-brackets on wall)
55
+ const bracketThick = 10;
56
+ const bracketW = 60;
57
+ const bracketV = box(bracketThick, bracketW, 100, true).color('#606060');
58
+ const bracketH = box(60, bracketThick, bracketW, true).color('#606060')
59
+ .translate(0, bracketW/2, -50 + bracketThick/2);
60
+
61
+ const indoorBracketL = union(bracketV, bracketH)
62
+ .translate(-indoorW/2 + 80, -wallThick/2 - bracketThick/2, mountHeight - indoorH/2);
63
+
64
+ const indoorBracketR = union(bracketV, bracketH)
65
+ .translate(indoorW/2 - 80, -wallThick/2 - bracketThick/2, mountHeight - indoorH/2);
66
+
67
+ // --- Outdoor Unit (on the +Y side of wall) ---
68
+ // Position: front face flush with wall back (Y = wallThick/2)
69
+ const outdoorUnitY = wallThick/2 + outdoorD/2;
70
+ const outdoorUnitZ = mountHeight - (outdoorH - indoorH)/2;
71
+
72
+ // Main body
73
+ const outdoorBody = box(outdoorW, outdoorD, outdoorH, true)
74
+ .color('#FFFFFF')
75
+ .translate(0, outdoorUnitY, outdoorUnitZ);
76
+
77
+ // Fan grill - vertical cylinder facing forward (along -Y)
78
+ const fanRadius = Math.min(outdoorW, outdoorH) * 0.35;
79
+ const fanGrill = cylinder(10, fanRadius, fanRadius, undefined, true)
80
+ .color('#404040')
81
+ .pointAlong([0, -1, 0])
82
+ .translate(0, outdoorUnitY + outdoorD/2 + 5, outdoorUnitZ);
83
+
84
+ // Fan blades
85
+ const bladeCount = 5;
86
+ const fanBlades = [];
87
+ for (let i = 0; i < bladeCount; i++) {
88
+ const angle = (i / bladeCount) * 360;
89
+ const blade = box(fanRadius * 0.7, 5, 30, true)
90
+ .color('#333333')
91
+ .rotate(angle, 0, 0)
92
+ .translate(0, outdoorUnitY + outdoorD/2 + 5, outdoorUnitZ);
93
+ fanBlades.push(blade);
94
+ }
95
+
96
+ // Side vents
97
+ const sideVents = [];
98
+ for (let i = 0; i < 6; i++) {
99
+ const y = (i - 2.5) * (outdoorH * 0.6 / 5);
100
+ const sv = box(5, outdoorD - 20, 12, true)
101
+ .color('#C0C0C0')
102
+ .translate(outdoorW/2 + 2, outdoorUnitY, outdoorUnitZ + y);
103
+ sideVents.push(sv);
104
+ }
105
+
106
+ // Outdoor mounting feet
107
+ const footW = 100;
108
+ const footD = 15;
109
+ const footH = 40;
110
+
111
+ const leftFoot = box(footD, outdoorD - 20, footH, true)
112
+ .color('#505050')
113
+ .translate(-outdoorW/2 + 100, outdoorUnitY, outdoorUnitZ - outdoorH/2 - footH/2);
114
+
115
+ const rightFoot = box(footD, outdoorD - 20, footH, true)
116
+ .color('#505050')
117
+ .translate(outdoorW/2 - 100, outdoorUnitY, outdoorUnitZ - outdoorH/2 - footH/2);
118
+
119
+ // Wall mounting plate for outdoor unit
120
+ const mountPlate = box(200, 10, 200, true)
121
+ .color('#505050')
122
+ .translate(0, wallThick/2 + 5, outdoorUnitZ);
123
+
124
+ // --- Connecting Pipes ---
125
+ // Two refrigerant lines going from outdoor unit, through wall, into indoor unit
126
+ const pipeR = 12;
127
+
128
+ // Pipe Z position - aligned with bottom portion of indoor unit
129
+ const pipeZ = mountHeight - indoorH/2 + 50;
130
+
131
+ // Calculate Y positions for pipe segments
132
+ // Indoor unit back face is at Y = indoorUnitY + indoorD/2 = -75 + 100 = 25? No...
133
+ // indoorUnitY = -75 - 100 = -175
134
+ // Indoor extends from Y=-275 (front) to Y=-75 (back, against wall)
135
+ // Wall is at Y=-75 to Y=+75
136
+ // Outdoor extends from Y=+75 (front, facing wall) to Y=+375 (back)
137
+
138
+ // Pipe 1 - the larger insulated line
139
+ // Outdoor segment: from outdoor front (Y=75) extending backward into unit
140
+ const pipe1Outdoor = cylinder(200, pipeR + 6, undefined, undefined, true)
141
+ .pointAlong([0, -1, 0]) // Points toward -Y (toward wall)
142
+ .color('#8B4513') // Copper
143
+ .translate(-pipeSpacing/2, outdoorUnitY - outdoorD/2 - 50, pipeZ);
144
+
145
+ // Wall segment: through the wall thickness
146
+ const pipe1Wall = cylinder(wallThick + 20, pipeR + 8, undefined, undefined, true)
147
+ .pointAlong([0, -1, 0])
148
+ .color('#1a5f7a') // Blue insulation
149
+ .translate(-pipeSpacing/2, 0, pipeZ);
150
+
151
+ // Indoor segment: from wall (Y=-75) extending into indoor unit (to Y=-175 center)
152
+ const pipe1Indoor = cylinder(120, pipeR + 8, undefined, undefined, true)
153
+ .pointAlong([0, -1, 0]) // Points toward -Y (into indoor unit)
154
+ .color('#1a5f7a')
155
+ .translate(-pipeSpacing/2, indoorUnitY + indoorD/2 - 60, pipeZ);
156
+
157
+ // Pipe 2 - smaller line
158
+ const pipe2Outdoor = cylinder(200, pipeR, undefined, undefined, true)
159
+ .pointAlong([0, -1, 0])
160
+ .color('#8B4513')
161
+ .translate(pipeSpacing/2, outdoorUnitY - outdoorD/2 - 50, pipeZ);
162
+
163
+ const pipe2Wall = cylinder(wallThick + 20, pipeR, undefined, undefined, true)
164
+ .pointAlong([0, -1, 0])
165
+ .color('#1a5f7a')
166
+ .translate(pipeSpacing/2, 0, pipeZ);
167
+
168
+ const pipe2Indoor = cylinder(120, pipeR, undefined, undefined, true)
169
+ .pointAlong([0, -1, 0])
170
+ .color('#1a5f7a')
171
+ .translate(pipeSpacing/2, indoorUnitY + indoorD/2 - 60, pipeZ);
172
+
173
+ // Wall sleeves
174
+ const sleeve1 = cylinder(wallThick + 10, pipeR + 15, undefined, undefined, true)
175
+ .pointAlong([0, -1, 0])
176
+ .color('#888888')
177
+ .translate(-pipeSpacing/2, 0, pipeZ);
178
+
179
+ const sleeve2 = cylinder(wallThick + 10, pipeR + 10, undefined, undefined, true)
180
+ .pointAlong([0, -1, 0])
181
+ .color('#888888')
182
+ .translate(pipeSpacing/2, 0, pipeZ);
183
+
184
+ // Connection points (visual only - where pipes enter the units)
185
+ const conn1Indoor = cylinder(20, pipeR + 10, undefined, undefined, true)
186
+ .pointAlong([0, -1, 0])
187
+ .color('#606060')
188
+ .translate(-pipeSpacing/2, indoorUnitY + indoorD/2, pipeZ);
189
+
190
+ const conn1Outdoor = cylinder(20, pipeR + 8, undefined, undefined, true)
191
+ .pointAlong([0, 1, 0])
192
+ .color('#606060')
193
+ .translate(-pipeSpacing/2, outdoorUnitY - outdoorD/2, pipeZ);
194
+
195
+ const conn2Indoor = cylinder(20, pipeR + 4, undefined, undefined, true)
196
+ .pointAlong([0, -1, 0])
197
+ .color('#606060')
198
+ .translate(pipeSpacing/2, indoorUnitY + indoorD/2, pipeZ);
199
+
200
+ const conn2Outdoor = cylinder(20, pipeR + 2, undefined, undefined, true)
201
+ .pointAlong([0, 1, 0])
202
+ .color('#606060')
203
+ .translate(pipeSpacing/2, outdoorUnitY - outdoorD/2, pipeZ);
204
+
205
+ return [
206
+ { name: "Wall", shape: wall },
207
+ { name: "Indoor Brackets", group: [
208
+ { name: "Left Bracket", shape: indoorBracketL },
209
+ { name: "Right Bracket", shape: indoorBracketR }
210
+ ] },
211
+ { name: "Indoor Unit Body", shape: indoorBody },
212
+ { name: "Indoor Vent", shape: ventPanel },
213
+ { name: "Indoor Top Vent", shape: topVent },
214
+ { name: "Display Panel", shape: display },
215
+ { name: "Power LED", shape: led },
216
+ { name: "Outdoor Mount Plate", shape: mountPlate },
217
+ { name: "Outdoor Unit Body", shape: outdoorBody },
218
+ { name: "Fan Grill", shape: fanGrill },
219
+ { name: "Fan Blades", group: fanBlades.map((b, i) => ({ name: `Blade ${i + 1}`, shape: b })) },
220
+ { name: "Side Vents", group: sideVents.map((v, i) => ({ name: `Vent ${i + 1}`, shape: v })) },
221
+ { name: "Outdoor Feet", group: [
222
+ { name: "Left Foot", shape: leftFoot },
223
+ { name: "Right Foot", shape: rightFoot }
224
+ ] },
225
+ { name: "Pipe 1 (Outdoor)", shape: pipe1Outdoor },
226
+ { name: "Pipe 1 (Wall)", shape: pipe1Wall },
227
+ { name: "Pipe 1 (Indoor)", shape: pipe1Indoor },
228
+ { name: "Pipe 2 (Outdoor)", shape: pipe2Outdoor },
229
+ { name: "Pipe 2 (Wall)", shape: pipe2Wall },
230
+ { name: "Pipe 2 (Indoor)", shape: pipe2Indoor },
231
+ { name: "Wall Sleeves", shape: union(sleeve1, sleeve2) },
232
+ { name: "Conn Indoor 1", shape: conn1Indoor },
233
+ { name: "Conn Outdoor 1", shape: conn1Outdoor },
234
+ { name: "Conn Indoor 2", shape: conn2Indoor },
235
+ { name: "Conn Outdoor 2", shape: conn2Outdoor },
236
+ ];
@@ -0,0 +1,123 @@
1
+ // Home AC Unit V3 - Fixed positioning
2
+ // Indoor unit on front of wall, outdoor unit on back, connected through wall
3
+
4
+ // Parameters
5
+ const wallThick = param("Wall Thickness", 15, { min: 10, max: 30, unit: "mm" });
6
+ const wallWidth = param("Wall Width", 200, { min: 150, max: 300, unit: "mm" });
7
+ const wallHeight = param("Wall Height", 150, { min: 100, max: 250, unit: "mm" });
8
+
9
+ // Indoor unit parameters
10
+ const indoorW = param("Indoor Width", 70, { min: 50, max: 100, unit: "mm" });
11
+ const indoorD = param("Indoor Depth", 20, { min: 15, max: 35, unit: "mm" });
12
+ const indoorH = param("Indoor Height", 28, { min: 20, max: 45, unit: "mm" });
13
+
14
+ // Outdoor unit parameters
15
+ const outdoorW = param("Outdoor Width", 75, { min: 55, max: 110, unit: "mm" });
16
+ const outdoorD = param("Outdoor Depth", 30, { min: 20, max: 50, unit: "mm" });
17
+ const outdoorH = param("Outdoor Height", 55, { min: 35, max: 85, unit: "mm" });
18
+ const fanR = param("Fan Radius", 20, { min: 12, max: 30, unit: "mm" });
19
+
20
+ // Pipe parameters
21
+ const pipeR = param("Pipe Radius", 4, { min: 2, max: 8, unit: "mm" });
22
+
23
+ // === Build the wall (NOT centered - easier to position against) ===
24
+ const wall = box(wallWidth, wallThick, wallHeight).color('#999999');
25
+
26
+ // === Indoor Unit (mounted on FRONT of wall) ===
27
+ // Main body
28
+ const indoorMain = box(indoorW, indoorD, indoorH).color('#e8e8e8');
29
+
30
+ // Vent grille at bottom
31
+ const vent = rect(indoorW - 8, 4).extrude(2).color('#555555')
32
+ .translate(indoorW / 2, 0, -indoorH / 2 + 5);
33
+ const indoorWithVent = union(indoorMain, vent);
34
+
35
+ // Position: attach to front of wall, offset outward
36
+ const indoor = indoorWithVent.color('#f0f0f0')
37
+ .attachTo(wall, 'front', 'back', [0, -2, 20]);
38
+
39
+ // Display panel - 2D rect extruded, oriented vertically
40
+ const display = rect(15, 8).extrude(1).color('#222222')
41
+ .rotate(90, 0, 0) // Rotate to stand upright
42
+ .attachTo(indoor, 'front', 'back', [indoorW / 2 - 15, -1, indoorH / 2 - 8]);
43
+
44
+ // === Outdoor Unit (mounted on BACK of wall) ===
45
+ // Main body
46
+ const outdoorMain = box(outdoorW, outdoorD, outdoorH).color('#b8b8b8');
47
+
48
+ // Base/platform for outdoor unit
49
+ const outdoorBase = box(outdoorW + 8, outdoorD + 8, 6).color('#888888')
50
+ .attachTo(outdoorMain, 'bottom', 'top', [0, 0, -3]);
51
+
52
+ const outdoorWithBase = union(outdoorMain, outdoorBase);
53
+
54
+ // Position: attach to back of wall, offset outward
55
+ const outdoor = outdoorWithBase.color('#cccccc')
56
+ .attachTo(wall, 'back', 'front', [0, 2, 20]);
57
+
58
+ // === Fan Assembly (on front of outdoor unit, pointing outward) ===
59
+ // Fan grille - circle with hole, extruded
60
+ const grilleOuter = circle2d(fanR + 3).extrude(2).color('#666666');
61
+ const grilleInner = circle2d(fanR).extrude(3).color('#333333');
62
+ const grille = difference(grilleOuter, grilleInner);
63
+
64
+ // Rotate grille to be perpendicular to front face (facing outward along +Y)
65
+ const grilleRotated = grille.rotate(90, 0, 0)
66
+ .attachTo(outdoor, 'front', 'back', [0, -1, 0]);
67
+
68
+ // Fan blades - radial pattern
69
+ const blade = box(3, fanR - 4, 2).translate(0, (fanR - 4) / 2 + 2, 0).color('#555555');
70
+ const fan = circularPattern(blade, 5).color('#666666')
71
+ .rotate(90, 0, 0) // Rotate to stand upright in grille
72
+ .attachTo(outdoor, 'front', 'back', [0, -1, 0]);
73
+
74
+ // Fan hub - cylinder pointing outward
75
+ const hub = cylinder(8, fanR / 4).color('#444444')
76
+ .rotate(90, 0, 0) // Point along Y axis
77
+ .attachTo(outdoor, 'front', 'center', [0, -1, 0]);
78
+
79
+ // === Connector Pipes (through the wall) ===
80
+ // Get positions from the attached shapes
81
+ const indoorBB = indoor.boundingBox();
82
+ const outdoorBB = outdoor.boundingBox();
83
+
84
+ // Pipe goes from indoor back to outdoor front through wall
85
+ // Wall is at Y=0 to Y=wallThick, so pipe should span from indoor back to outdoor front
86
+ const pipeStartY = indoorBB.max[1]; // Back of indoor
87
+ const pipeEndY = outdoorBB.min[1]; // Front of outdoor
88
+ const pipeLen = pipeEndY - pipeStartY;
89
+
90
+ // Vertical offset on the units
91
+ const pipeZ = 20;
92
+
93
+ // Main refrigerant pipe - goes through wall along Y axis
94
+ const mainPipe = cylinder(pipeLen, pipeR)
95
+ .pointAlong([0, 1, 0]) // Along Y axis
96
+ .translate(0, (pipeStartY + pipeEndY) / 2, pipeZ)
97
+ .color('#aa6644');
98
+
99
+ // Second refrigerant line - offset to the right
100
+ const line2 = cylinder(pipeLen, pipeR * 0.7)
101
+ .pointAlong([0, 1, 0])
102
+ .translate(pipeR * 2 + 2, (pipeStartY + pipeEndY) / 2, pipeZ)
103
+ .color('#aa6644');
104
+
105
+ // Condensate drain line - lower down
106
+ const drain = cylinder(pipeLen, pipeR * 0.6)
107
+ .pointAlong([0, 1, 0])
108
+ .translate(0, (pipeStartY + pipeEndY) / 2, pipeZ - pipeR * 2 - 2)
109
+ .color('#44aa66');
110
+
111
+ // Return all objects as separate items to preserve colors
112
+ return [
113
+ { name: "Wall", shape: wall },
114
+ { name: "Indoor Unit", shape: indoor },
115
+ { name: "Display", shape: display },
116
+ { name: "Outdoor Unit", shape: outdoor },
117
+ { name: "Fan Grille", shape: grilleRotated },
118
+ { name: "Fan Blades", shape: fan },
119
+ { name: "Fan Hub", shape: hub },
120
+ { name: "Main Pipe", shape: mainPipe },
121
+ { name: "Refrigerant Line", shape: line2 },
122
+ { name: "Drain Line", shape: drain },
123
+ ];