// Height of the holder cylinder, in mm. height = 240; // Diameter of the holder cylinder, in mm. diameter = 25; // The thickness of the holder cylinder walls, in mm. thickness = 4; // number of bases along x-axis gridx = 3; // number of bases along y-axis gridy = 3; module __hide_parameters() {}; /* Code below copied from Gridfinity Rebuilt. The whole file is licensed under the MIT license. License: MIT License Copyright (c) 2023 Kenneth Hodson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This repository is based on Gridfinity: MIT License Copyright (c) 2023 Zachary Freedman and Voidstar Lab LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // height of the base h_base = 5; // outside rounded radius of bin r_base = 4; // lower base chamfer "radius" r_c1 = 0.8; // upper base chamfer "radius" r_c2 = 2.4; // bottom thiccness of bin h_bot = 2.2; // outside radii 1 r_fo1 = 8.5; // outside radii 2 r_fo2 = 3.2; // outside radii 3 r_fo3 = 1.6; // length of a grid unit l_grid = 42; // screw hole radius r_hole1 = 1.5; // magnet hole radius r_hole2 = 3.25; // center-to-center distance between holes d_hole = 26; // distance of hole from side of bin d_hole_from_side=8; // magnet hole depth h_hole = 2.4; // slit depth (printer layer height) h_slit = 0.2; // top edge fillet radius r_f1 = 0.6; // internal fillet radius r_f2 = 2.8; // width of divider between compartments d_div = 1.2; // minimum wall thickness d_wall = 0.95; // tolerance fit factor d_clear = 0.25; // height of tab (yaxis, measured from inner wall) d_tabh = 15.85; // maximum width of tab d_tabw = 42; // angle of tab a_tab = 36; // lip height h_lip = 3.548; d_wall2 = r_base-r_c1-d_clear*sqrt(2); d_magic = -2*d_clear-2*d_wall+d_div; // Baseplate constants // Baseplate bottom part height (part added with weigthed=true) bp_h_bot = 6.4; // Baseplate bottom cutout rectangle size bp_cut_size = 21.4; // Baseplate bottom cutout rectangle depth bp_cut_depth = 4; // Baseplate bottom cutout rounded thingy width bp_rcut_width = 8.5; // Baseplate bottom cutout rounded thingy left bp_rcut_length = 4.25; // Baseplate bottom cutout rounded thingy depth bp_rcut_depth = 2; // countersink diameter for baseplate d_cs = 2.5; // radius of cutout for skeletonized baseplate r_skel = 2; // baseplate counterbore radius r_cb = 2.75; // baseplate counterbore depth h_cb = 3; // minimum baseplate thickness (when skeletonized) h_skel = 1; // ===== User Modules ===== // // functions to convert gridz values to mm values function hf (z, d, l) = ((d==0)?z*7:(d==1)?h_bot+z+h_base:z-((l==1)?h_lip:0))+(l==2?h_lip:0); function height (z,d=0,l=0,s=true) = (s?((abs(hf(z,d,l))%7==0)?hf(z,d,l):hf(z,d,l)+7-abs(hf(z,d,l))%7):hf(z,d,l))-h_base; // Creates equally divided cutters for the bin // // n_divx: number of x compartments (ideally, coprime w/ gridx) // n_divy: number of y compartments (ideally, coprime w/ gridy) // set n_div values to 0 for a solid bin // style_tab: tab style for all compartments. see cut() // scoop_weight: scoop toggle for all compartments. see cut() module cutEqual(n_divx=1, n_divy=1, style_tab=1, scoop_weight=1) { for (i = [1:n_divx]) for (j = [1:n_divy]) cut((i-1)*$gxx/n_divx,(j-1)*$gyy/n_divy, $gxx/n_divx, $gyy/n_divy, style_tab, scoop_weight); } // initialize gridfinity module gridfinityInit(gx, gy, h, h0 = 0, l = l_grid) { $gxx = gx; $gyy = gy; $dh = h; $dh0 = h0; color("tomato") { difference() { color("firebrick") block_bottom(h0==0?$dh-0.1:h0, gx, gy, l); children(); } color("royalblue") block_wall(gx, gy, l) { if (style_lip == 0) profile_wall(); else profile_wall2(); } } } // Function to include in the custom() module to individually slice bins // Will try to clamp values to fit inside the provided base size // // x: start coord. x=1 is the left side of the bin. // y: start coord. y=1 is the bottom side of the bin. // w: width of compartment, in # of bases covered // h: height of compartment, in # of basese covered // t: tab style of this specific compartment. // alignment only matters if the compartment size is larger than d_tabw // 0:full, 1:auto, 2:left, 3:center, 4:right, 5:none // Automatic alignment will use left tabs for bins on the left edge, right tabs for bins on the right edge, and center tabs everywhere else. // s: toggle the rounded back corner that allows for easy removal module cut(x=0, y=0, w=1, h=1, t=1, s=1) { translate([0,0,-$dh-h_base]) cut_move(x,y,w,h) block_cutter(clp(x,0,$gxx), clp(y,0,$gyy), clp(w,0,$gxx-x), clp(h,0,$gyy-y), t, s); } // Translates an object from the origin point to the center of the requested compartment block, can be used to add custom cuts in the bin // See cut() module for parameter descriptions module cut_move(x, y, w, h) { translate([0,0,$dh0==0?$dh+h_base:$dh0+h_base]) cut_move_unsafe(clp(x,0,$gxx), clp(y,0,$gyy), clp(w,0,$gxx-x), clp(h,0,$gyy-y)) children(); } // ===== Modules ===== // module profile_base() { polygon([ [0,0], [0,h_base], [r_base,h_base], [r_base-r_c2,h_base-r_c2], [r_base-r_c2,r_c1], [r_base-r_c2-r_c1,0] ]); } module gridfinityBase(gx, gy, l, dx, dy, style_hole, off=0, final_cut=true, only_corners=false) { dbnxt = [for (i=[1:5]) if (abs(gx*i)%1 < 0.001 || abs(gx*i)%1 > 0.999) i]; dbnyt = [for (i=[1:5]) if (abs(gy*i)%1 < 0.001 || abs(gy*i)%1 > 0.999) i]; dbnx = 1/(dx==0 ? len(dbnxt) > 0 ? dbnxt[0] : 1 : round(dx)); dbny = 1/(dy==0 ? len(dbnyt) > 0 ? dbnyt[0] : 1 : round(dy)); xx = gx*l-0.5; yy = gy*l-0.5; if (final_cut) translate([0,0,h_base]) rounded_rectangle(xx+0.002, yy+0.002, h_bot/1.5, r_fo1/2+0.001); intersection(){ if (final_cut) translate([0,0,-1]) rounded_rectangle(xx+0.005, yy+0.005, h_base+h_bot/2*10, r_fo1/2+0.001); if(only_corners) { difference(){ pattern_linear(gx/dbnx, gy/dbny, dbnx*l, dbny*l) block_base(gx, gy, l, dbnx, dbny, 0, off); pattern_linear(2, 2, (gx-1)*l_grid+d_hole, (gy-1)*l_grid+d_hole) block_base_hole(style_hole, off); } } else { pattern_linear(gx/dbnx, gy/dbny, dbnx*l, dbny*l) block_base(gx, gy, l, dbnx, dbny, style_hole, off); } } } module block_base(gx, gy, l, dbnx, dbny, style_hole, off) { render(convexity = 2) difference() { block_base_solid(dbnx, dbny, l, off); if (style_hole > 0) pattern_circular(abs(l-d_hole_from_side/2)<0.001?1:4) if (style_hole == 4) translate([l/2-d_hole_from_side, l/2-d_hole_from_side, h_slit*2]) refined_hole(); else translate([l/2-d_hole_from_side, l/2-d_hole_from_side, 0]) block_base_hole(style_hole, off); } } module block_base_solid(dbnx, dbny, l, o) { xx = dbnx*l-0.05; yy = dbny*l-0.05; oo = (o/2)*(sqrt(2)-1); translate([0,0,h_base]) mirror([0,0,1]) union() { hull() { rounded_rectangle(xx-2*r_c2-2*r_c1+o, yy-2*r_c2-2*r_c1+o, h_base+oo, r_fo3/2); rounded_rectangle(xx-2*r_c2+o, yy-2*r_c2+o, h_base-r_c1+oo, r_fo2/2); } translate([0,0,oo]) hull() { rounded_rectangle(xx-2*r_c2+o, yy-2*r_c2+o, r_c2, r_fo2/2); mirror([0,0,1]) rounded_rectangle(xx+o, yy+o, h_bot/2+abs(10*o), r_fo1/2); } } } module block_base_hole(style_hole, o=0) { r1 = r_hole1-o/2; r2 = r_hole2-o/2; union() { difference() { cylinder(h = 2*(h_hole-o+(style_hole==3?h_slit:0)), r=r2, center=true); if (style_hole==3) copy_mirror([0,1,0]) translate([-1.5*r2,r1+0.1,h_hole-o]) cube([r2*3,r2*3, 10]); } if (style_hole > 1) cylinder(h = 2*h_base-o, r = r1, center=true); } } module refined_hole() { /** * Refined hole based on Printables @grizzie17's Gridfinity Refined * https://www.printables.com/model/413761-gridfinity-refined */ // Meassured magnet hole diameter to be 5.86mm (meassured in fusion360 r = r_hole2-0.32; // Magnet height m = 2; mh = m-0.1; // Poke through - For removing a magnet using a toothpick ptl = h_slit*3; // Poke Through Layers pth = mh+ptl; // Poke Through Height ptr = 2.5; // Poke Through Radius union() { hull() { // Magnet hole - smaller than the magnet to keep it squeezed translate([10, -r, 0]) cube([1, r*2, mh]); cylinder(1.9, r=r); } hull() { // Poke hole translate([-9+5.60, -ptr/2, -ptl]) cube([1, ptr, pth]); translate([-12.53+5.60, 0, -ptl]) cylinder(pth, d=ptr); } } } module profile_wall_sub_sub() { polygon([ [0,0], [d_wall/2,0], [d_wall/2,$dh-1.2-d_wall2+d_wall/2], [d_wall2-d_clear,$dh-1.2], [d_wall2-d_clear,$dh+h_base], [0,$dh+h_base] ]); } module profile_wall_sub() { difference() { profile_wall_sub_sub(); color("red") offset(delta = d_clear) translate([r_base-d_clear,$dh,0]) mirror([1,0,0]) profile_base(); } } module profile_wall() { translate([r_base,0,0]) mirror([1,0,0]) difference() { profile_wall_sub(); difference() { translate([0, $dh+h_base-d_clear*sqrt(2), 0]) circle(r_base/2); offset(r = r_f1) offset(delta = -r_f1) profile_wall_sub(); } // remove any negtive geometry in edge cases mirror([0,1,0]) square(100*l_grid); } } // lipless profile module profile_wall2() { translate([r_base,0,0]) mirror([1,0,0]) square([d_wall,$dh]); } module block_wall(gx, gy, l) { translate([0,0,h_base]) sweep_rounded(gx*l-2*r_base-0.5-0.001, gy*l-2*r_base-0.5-0.001) children(); } module block_bottom( h = 2.2, gx, gy, l ) { translate([0,0,h_base+0.1]) rounded_rectangle(gx*l-0.5-d_wall/4, gy*l-0.5-d_wall/4, h, r_base+0.01); } module cut_move_unsafe(x, y, w, h) { xx = ($gxx*l_grid+d_magic); yy = ($gyy*l_grid+d_magic); translate([(x)*xx/$gxx,(y)*yy/$gyy,0]) translate([(-xx+d_div)/2,(-yy+d_div)/2,0]) translate([(w*xx/$gxx-d_div)/2,(h*yy/$gyy-d_div)/2,0]) children(); } module block_cutter(x,y,w,h,t,s) { v_len_tab = d_tabh; v_len_lip = d_wall2-d_wall+1.2; v_cut_tab = d_tabh - (2*r_f1)/tan(a_tab); v_cut_lip = d_wall2-d_wall-d_clear; v_ang_tab = a_tab; v_ang_lip = 45; ycutfirst = y == 0 && style_lip == 0; ycutlast = abs(y+h-$gyy)<0.001 && style_lip == 0; xcutfirst = x == 0 && style_lip == 0; xcutlast = abs(x+w-$gxx)<0.001 && style_lip == 0; zsmall = ($dh+h_base)/7 < 3; ylen = h*($gyy*l_grid+d_magic)/$gyy-d_div; xlen = w*($gxx*l_grid+d_magic)/$gxx-d_div; height = $dh; extent = (abs(s) > 0 && ycutfirst ? d_wall2-d_wall-d_clear : 0); tab = (zsmall || t == 5) ? (ycutlast?v_len_lip:0) : v_len_tab; ang = (zsmall || t == 5) ? (ycutlast?v_ang_lip:0) : v_ang_tab; cut = (zsmall || t == 5) ? (ycutlast?v_cut_lip:0) : v_cut_tab; style = (t > 1 && t < 5) ? t-3 : (x == 0 ? -1 : xcutlast ? 1 : 0); translate([0,ylen/2,h_base+h_bot]) rotate([90,0,-90]) { if (!zsmall && xlen - d_tabw > 4*r_f2 && (t != 0 && t != 5)) { fillet_cutter(3,"bisque") difference() { transform_tab(style, xlen, ((xcutfirst&&style==-1)||(xcutlast&&style==1))?v_cut_lip:0) translate([ycutlast?v_cut_lip:0,0]) profile_cutter(height-h_bot, ylen/2, s); if (xcutfirst) translate([0,0,(xlen/2-r_f2)-v_cut_lip]) cube([ylen,height,v_cut_lip*2]); if (xcutlast) translate([0,0,-(xlen/2-r_f2)-v_cut_lip]) cube([ylen,height,v_cut_lip*2]); } if (t != 0 && t != 5) fillet_cutter(2,"indigo") difference() { transform_tab(style, xlen, ((xcutfirst&&style==-1)||(xcutlast&&style==1))?v_cut_lip:0) difference() { intersection() { profile_cutter(height-h_bot, ylen-extent, s); profile_cutter_tab(height-h_bot, v_len_tab, v_ang_tab); } if (ycutlast) profile_cutter_tab(height-h_bot, v_len_lip, 45); } if (xcutfirst) translate([ylen/2,0,xlen/2]) rotate([0,90,0]) transform_main(2*ylen) profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip); if (xcutlast) translate([ylen/2,0,-xlen/2]) rotate([0,-90,0]) transform_main(2*ylen) profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip); } } fillet_cutter(1,"seagreen") translate([0,0,xcutlast?v_cut_lip/2:0]) translate([0,0,xcutfirst?-v_cut_lip/2:0]) transform_main(xlen-(xcutfirst?v_cut_lip:0)-(xcutlast?v_cut_lip:0)) translate([cut,0]) profile_cutter(height-h_bot, ylen-extent-cut-(!s&&ycutfirst?v_cut_lip:0), s); fillet_cutter(0,"hotpink") difference() { transform_main(xlen) difference() { profile_cutter(height-h_bot, ylen-extent, s); if (!((zsmall || t == 5) && !ycutlast)) profile_cutter_tab(height-h_bot, tab, ang); if (!(abs(s) > 0)&& y == 0) translate([ylen-extent,0,0]) mirror([1,0,0]) profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip); } if (xcutfirst) color("indigo") translate([ylen/2+0.001,0,xlen/2+0.001]) rotate([0,90,0]) transform_main(2*ylen) profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip); if (xcutlast) color("indigo") translate([ylen/2+0.001,0,-xlen/2+0.001]) rotate([0,-90,0]) transform_main(2*ylen) profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip); } } } module transform_main(xlen) { translate([0,0,-(xlen-2*r_f2)/2]) linear_extrude(xlen-2*r_f2) children(); } module transform_tab(type, xlen, cut) { mirror([0,0,type==1?1:0]) copy_mirror([0,0,-(abs(type)-1)]) translate([0,0,-(xlen)/2]) translate([0,0,r_f2]) linear_extrude((xlen-d_tabw-abs(cut))/(1-(abs(type)-1))-2*r_f2) children(); } module fillet_cutter(t = 0, c = "goldenrod") { color(c) minkowski() { children(); sphere(r = r_f2-t/1000); } } module profile_cutter(h, l, s) { scoop = max(s*$dh/2-r_f2,0); translate([r_f2,r_f2]) hull() { if (l-scoop-2*r_f2 > 0) square(0.1); if (scoop < h) { translate([l-2*r_f2,h-r_f2/2]) mirror([1,1]) square(0.1); translate([0,h-r_f2/2]) mirror([0,1]) square(0.1); } difference() { translate([l-scoop-2*r_f2, scoop]) if (scoop != 0) { intersection() { circle(scoop); mirror([0,1]) square(2*scoop); } } else mirror([1,0]) square(0.1); translate([l-scoop-2*r_f2,-1]) square([-(l-scoop-2*r_f2),2*h]); translate([0,h]) square([2*l,scoop]); } } } module profile_cutter_tab(h, tab, ang) { if (tab > 0) color("blue") offset(delta = r_f2) polygon([[0,h],[tab,h],[0,h-tab*tan(ang)]]); } // ==== Utilities ===== function clp(x,a,b) = min(max(x,a),b); module rounded_rectangle(length, width, height, rad) { linear_extrude(height) offset(rad) offset(-rad) square([length,width], center = true); } module rounded_square(length, height, rad) { rounded_rectangle(length, length, height, rad); } module copy_mirror(vec=[0,1,0]) { children(); if (vec != [0,0,0]) mirror(vec) children(); } module pattern_linear(x = 1, y = 1, sx = 0, sy = 0) { yy = sy <= 0 ? sx : sy; translate([-(x-1)*sx/2,-(y-1)*yy/2,0]) for (i = [1:ceil(x)]) for (j = [1:ceil(y)]) translate([(i-1)*sx,(j-1)*yy,0]) children(); } module pattern_circular(n=2) { for (i = [1:n]) rotate(i*360/n) children(); } module sweep_rounded(w=10, h=10) { union() pattern_circular(2) { copy_mirror([1,0,0]) translate([w/2,h/2,0]) rotate_extrude(angle = 90, convexity = 4) children(); translate([w/2,0,0]) rotate([90,0,0]) linear_extrude(height = h, center = true) children(); rotate([0,0,90]) translate([h/2,0,0]) rotate([90,0,0]) linear_extrude(height = w, center = true) children(); } } /** Code below this point is original code by Kaan Genc. MIT License Copyright (c) 2024 Kaan Genc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // bin height. See bin height information and "gridz_define" below. gridz = 0; // determine what the variable "gridz" applies to based on your use case gridz_define = 0; // [0:gridz is the height of bins in units of 7mm increments - Zack's method,1:gridz is the internal height in millimeters, 2:gridz is the overall external height of the bin in millimeters] $fn = $preview ? 12 : 240; /* [General Settings] */ /* [Compartments] */ // number of X Divisions (set to zero to have solid bin) divx = 0; // number of y Divisions (set to zero to have solid bin) divy = 0; /* [Height] */ // overrides internal block height of bin (for solid containers). Leave zero for default height. Units: mm height_internal = 0; // snap gridz height to nearest 7mm increment enable_zsnap = false; /* [Features] */ // the type of tabs style_tab = 5; //[0:Full,1:Auto,2:Left,3:Center,4:Right,5:None] // how should the top lip act style_lip = 0; //[0: Regular lip, 1:remove lip subtractively, 2: remove lip and retain height] // scoop weight percentage. 0 disables scoop, 1 is regular scoop. Any real number will scale the scoop. scoop = 1; //[0:0.1:1] // only cut magnet/screw holes at the corners of the bin to save uneccesary print time only_corners = false; /* [Base] */ style_hole = 3; // [0:no holes, 1:magnet holes only, 2: magnet and screw holes - no printable slit, 3: magnet and screw holes - printable slit, 4: Gridfinity Refined hole - no glue needed] // number of divisions per 1 unit of base along the X axis. (default 1, only use integers. 0 means automatically guess the right division) div_base_x = 0; // number of divisions per 1 unit of base along the Y axis. (default 1, only use integers. 0 means automatically guess the right division) div_base_y = 0; difference() { union() { color("tomato") { gridfinityInit(gridx, gridy, height(gridz, gridz_define, style_lip, enable_zsnap), height_internal) { if (divx > 0 && divy > 0) cutEqual(n_divx = divx, n_divy = divy, style_tab = style_tab, scoop_weight = scoop); } gridfinityBase(gridx, gridy, l_grid, div_base_x, div_base_y, style_hole, only_corners=only_corners); } rounding_height = 5; translate([0, 0, 6.47]) rotate_extrude() polygon( [ for (i = [0:1/$fn:1]) [ pow(3, (-i)) * 12 + pow(64, (-i)) + diameter / 2 - 4 , (i) * rounding_height ], [0, rounding_height], [0, 0] ] ); base_offset = 6.47; sphere_height = diameter + 1; difference() { union() { translate([0, 0, height - sphere_height / 2 + base_offset]) sphere(d=diameter + 4); color("green") translate([0, 0, base_offset]) linear_extrude(height = height - sphere_height / 2) circle(d = diameter); } translate([0, 0, base_offset + 1]) linear_extrude(height = height - sphere_height / 2 - 1) circle(d = diameter - thickness * 2); translate([0, 0, height - sphere_height / 2 + base_offset]) sphere(d=diameter + 1 - thickness); } } translate([-200, 0, 0]) cube([400, 400, 400]); }