1- // Lenovo ThinkCentre Tiny Series Under-desk Mount
1+ // Lenovo ThinkCentre Tiny Series Under-desk Mount (Stackable)
22// Designed for 180mm x 180mm print beds (Must print diagonally!)
33//
4- // Print it twice. Inset M4 nut into the retaining holdes.
5- // Mount the brackets, use a thumbscrew or grubscrew to secure.
6- //
7- // Print on its side, else you're going to find you're Tiny on the floor.
4+ // Usage:
5+ // 1. Print 2 brackets per PC.
6+ // 2. For the first PC (Desk Mount): Screw top tabs into desk.
7+ // 3. For the second PC (Stack): Insert nuts into the bottom tabs of the first brackets.
8+ // Use countersunk screws going UP through the top tabs of the second bracket
9+ // into the bottom tabs of the first bracket.
810
11+ /* [Preview Options] */
12+ // Enable to see the stacked assembly with PCs. Disable for printing!
13+ preview_assembly = true ;
14+ // How many units to show in the preview stack
15+ preview_stack_count = 3 ;
16+ // Distance between the front and rear brackets in the preview
17+ bracket_spacing = 120 ;
918
1019/* [Printer Settings] */
11- // To fit 180mm bed, we rotate the part 45 degrees
12- rotate_for_print = true ;
20+ // To fit 180mm bed, we rotate the part 45 degrees (Ignored if preview_assembly is true)
21+ rotate_for_print = true ;
1322
1423/* [PC Dimensions] */
1524// Width of the PC (usually ~179mm-182mm for Tiny series)
1625pc_width = 183.0 ; // Added 1mm tolerance
1726// Height/Thickness of the PC (usually ~37mm)
1827pc_height = 38.0 ; // Added 1mm tolerance
28+ // Depth of the PC (for preview only - M-series Tiny is ~183mm)
29+ pc_real_depth = 183.0 ;
1930
2031/* [Bracket Dimensions] */
2132// Depth of a single bracket (Z-axis height during print)
22- bracket_depth = 30 ;
33+ bracket_depth = 30 ;
2334// Thickness of the walls
2435wall_thick = 5 ;
2536// Width of the mounting tabs
@@ -29,55 +40,77 @@ airflow_gap = 10;
2940// Width of the retaining clips/rails that hold the PC down
3041retainer_clip_width = 3.0 ;
3142
43+ /* [Stacking] */
44+ // Add mounting tabs to the bottom to allow hanging another unit
45+ enable_stacking = true ;
46+ // Width of the nut for stacking (M5=~8mm, M4=~7mm)
47+ stacking_nut_width = 8.2 ;
48+ // Depth of the nut trap for stacking
49+ stacking_nut_depth = 3.5 ;
50+
3251/* [Security] */
3352// Diameter of the locking screw hole (M5 = 5.2, M4 = 4.2)
34- screw_hole_dia = 4.8 ;
35- // Size of the nut trap (0 to disable nut trap)
53+ screw_hole_dia = 4.8 ;
54+ // Size of the side lock nut trap (0 to disable nut trap)
3655nut_trap_width = 8.2 ; // Standard M5 nut is ~8mm flat-to-flat
3756nut_trap_depth = 3.5 ;
3857
3958/* [Hidden] */
4059$ fn = 60 ;
4160
42- module main_body() {
61+ // Added 'stacking' parameter to allow overriding the global setting per-instance
62+ module main_body(stacking = enable_stacking) {
4363 total_width = pc_width + (wall_thick * 2 );
4464 // Total height now includes the bottom wall, the PC, and the airflow gap
45- total_height = pc_height + wall_thick + airflow_gap;
46-
65+ total_height = pc_height + wall_thick + airflow_gap;
66+
4767 difference () {
4868 // Outer Shell
4969 union () {
5070 // Main U-Shape
5171 translate ([- total_width/2 , 0 , 0 ])
5272 cube ([total_width, total_height, bracket_depth]);
53-
54- // Mounting Tabs (Ears)
73+
74+ // Mounting Tabs (Top Ears - To Desk )
5575 for (i = [- 1 , 1 ]) {
5676 translate ([(i * (total_width/2 + tab_width/2 )), total_height - wall_thick/2 , bracket_depth/2 ])
5777 hull () {
5878 cube ([tab_width, wall_thick, bracket_depth], center= true );
5979 // Fillet to main body
60- // Shifted down (-wall_thick/2) so the top of the cylinder is flush with the top of the tab
6180 translate ([- i * tab_width/2 , - wall_thick/2 , 0 ])
6281 cylinder (r= wall_thick, h= bracket_depth, center= true );
6382 }
6483 }
84+
85+ // Stacking Tabs (Bottom Ears - To Next Unit)
86+ // Uses the local parameter 'stacking' instead of global 'enable_stacking'
87+ if (stacking) {
88+ for (i = [- 1 , 1 ]) {
89+ translate ([(i * (total_width/2 + tab_width/2 )), wall_thick/2 , bracket_depth/2 ])
90+ hull () {
91+ cube ([tab_width, wall_thick, bracket_depth], center= true );
92+ // Fillet to main body (matching top style)
93+ translate ([- i * tab_width/2 , wall_thick/2 , 0 ])
94+ cylinder (r= wall_thick, h= bracket_depth, center= true );
95+ }
96+ }
97+ }
6598 }
6699
67100 // Inner Cutout (Stepped Profile)
68101 union () {
69102 // 1. The PC Slot (Wide bottom section)
70103 translate ([- pc_width/2 , wall_thick, - 1 ])
71104 cube ([pc_width, pc_height, bracket_depth + 2 ]);
72-
105+
73106 // 2. The Airflow Gap (Narrower top section to create retaining clips)
74107 // The slot narrows by retainer_clip_width on both sides
75108 translate ([- (pc_width - 2 * retainer_clip_width)/2 , wall_thick + pc_height - 0.1 , - 1 ])
76109 cube ([pc_width - 2 * retainer_clip_width, airflow_gap + 10 , bracket_depth + 2 ]);
77110 }
78-
111+
79112 // Chamfers (Bevels) for smooth insertion
80-
113+
81114 // 1. Bottom Floor Chamfer (Existing)
82115 translate ([- pc_width/2 , wall_thick- 10 , - 5 ])
83116 rotate ([- 45 , 0 , 0 ])
@@ -89,22 +122,42 @@ module main_body() {
89122 rotate ([- 135 , 0 , 0 ]) // Angles the cut to slope the ceiling entry
90123 cube ([pc_width, 5 , bracket_depth + 10 ]);
91124
92- // Mounting Screw Holes (Countersunk )
125+ // Mounting Screw Holes (Top Tabs )
93126 for (i = [- 1 , 1 ]) {
94127 translate ([(i * (total_width/2 + tab_width/2 )), total_height + 5 , bracket_depth/2 ])
95128 rotate ([90 , 0 , 0 ]) {
96129 cylinder (d= 5 , h= 20 ); // Screw shaft
97130 translate ([0 , 0 , 5 + wall_thick])
98- cylinder (d= 10 , h= 10 ); // Screw head clearance
131+ cylinder (d= 10 , h= 10 ); // Screw head clearance (Countersunk)
99132 }
100133 }
101134
135+ // Stacking Holes & Traps (Bottom Tabs)
136+ if (stacking) {
137+ for (i = [- 1 , 1 ]) {
138+ translate ([(i * (total_width/2 + tab_width/2 )), 0 , bracket_depth/2 ]) {
139+
140+ // Through hole for screw from below
141+ rotate ([90 , 0 , 0 ])
142+ translate ([0 , 0 , - wall_thick - 5 ]) // Extend well past geometry
143+ cylinder (d= 5 , h= wall_thick + 10 );
144+
145+ // Nut Trap (On TOP surface of bottom tab)
146+ // Located at Y=wall_thick, facing down (-Y)
147+ translate ([0 , wall_thick + 0.1 , 0 ])
148+ rotate ([90 , 0 , 0 ])
149+ rotate ([0 , 0 , 30 ]) // Align hex flat
150+ cylinder (r= stacking_nut_width/2 / cos (30 ), h= stacking_nut_depth + 0.1 , $ fn= 6 );
151+ }
152+ }
153+ }
154+
102155 // Security/Locking Screw Hole (Bottom Center)
103156 translate ([0 , - 1 , bracket_depth/2 ])
104157 rotate ([- 90 , 0 , 0 ])
105158 cylinder (d= screw_hole_dia, h= wall_thick + 10 );
106-
107- // Hex Nut Trap (Optional)
159+
160+ // Hex Nut Trap (Optional Side Lock )
108161 if (nut_trap_width > 0 ) {
109162 translate ([0 , wall_thick - nut_trap_depth + 0.1 , bracket_depth/2 ])
110163 rotate ([- 90 , 90 , 0 ])
@@ -113,13 +166,66 @@ module main_body() {
113166 }
114167}
115168
116- // Positioning logic
117- if (rotate_for_print) {
169+ // Visual mockup of the PC for preview purposes
170+ module computer_mockup() {
171+ // Calculate PC placement relative to the bracket pair
172+ // Center the PC length (pc_real_depth) over the span of the two brackets (bracket_spacing + bracket_depth)
173+ z_start = (bracket_spacing + bracket_depth)/2 - pc_real_depth/2 ;
174+
175+ // A dark grey box for the PC chassis
176+ color ("#333333" )
177+ translate ([- pc_width/2 , wall_thick, z_start])
178+ cube ([pc_width, pc_height, pc_real_depth]);
179+
180+ // Front Face (Lenovo Style)
181+ // Assuming positive Z is "Front"
182+ color ("#111111" )
183+ translate ([- pc_width/2 , wall_thick, z_start + pc_real_depth - 2 ])
184+ cube ([pc_width, pc_height, 2 ]);
185+
186+ // Red Logo Tag
187+ color ("red" )
188+ translate ([pc_width/2 - 25 , wall_thick + 5 , z_start + pc_real_depth])
189+ cube ([15 , 5 , 0.5 ]);
190+
191+ // Power Button
192+ color ("black" )
193+ translate ([pc_width/2 - 50 , wall_thick + pc_height/2 , z_start + pc_real_depth])
194+ rotate ([0 ,90 ,0 ])
195+ cylinder (r= 5 , h= 1 );
196+ }
197+
198+ // Logic to display preview or print version
199+ if (preview_assembly) {
200+ // Calculate vertical offset for stacking
201+ // We want the 'top tabs' of the lower unit (Y = total_height - wall/2)
202+ // to align with 'bottom tabs' of upper unit (Y = wall/2)
203+ // Shift = wall/2 - (total_height - wall/2) = wall - total_height
204+ total_height = pc_height + wall_thick + airflow_gap;
205+ stack_offset = wall_thick - total_height;
206+
207+ for (i = [0 : preview_stack_count- 1 ]) {
208+ translate ([0 , i * stack_offset, 0 ]) {
209+ // Determine if this layer needs stacking tabs
210+ // The last layer (bottom-most, where i == count-1) should NOT have stacking tabs
211+ // Otherwise, respect the global enable_stacking flag
212+ render_tabs = (i == preview_stack_count - 1 ) ? false : enable_stacking;
213+
214+ // Bracket 1 (Rear/Base)
215+ main_body(stacking= render_tabs);
216+
217+ // Bracket 2 (Front)
218+ translate ([0 , 0 , bracket_spacing])
219+ main_body(stacking= render_tabs);
220+
221+ // The PC
222+ % computer_mockup(); // % makes it transparent/ghosted
223+ }
224+ }
225+ } else if (rotate_for_print) {
118226 // Rotates 45 degrees to fit diagonal of 180mm bed
119- // (180^2 + 180^2)^0.5 = ~254mm diagonal space
120- // Part width is approx 183 + 10 + 30 = ~223mm
121227 rotate ([0 , 0 , 45 ])
122228 main_body();
123229} else {
124230 main_body();
125- }
231+ }
0 commit comments