├── Images ├── customizer.png ├── basic_cup_2x1x3.png ├── efficient_floor.png ├── frame_baseplate.png ├── overhang_remedy.png ├── basic_cup_halfx2x4.png ├── divided_cup_2x1x3x5.png ├── irregular_cup_2x1x3.png ├── lid_baseplate_combo.png ├── weighted_baseplate_top.png └── weighted_baseplate_bottom.png ├── Renders ├── base_lid.stl ├── baseplate.stl ├── chess │ ├── king.stl │ ├── pawn.stl │ ├── rook.stl │ ├── tile.stl │ ├── bishop.stl │ ├── knight.stl │ └── queen.stl ├── flsun_q5.stl ├── basic_cup_2x1x2.stl ├── glue_stick_cup.stl ├── basic_cup_halfx2x4.stl ├── divided_cup_2x1x2x5.stl ├── divided_cup_2x1x3x5.stl ├── filled_block_2x1x3x5.stl ├── socket_holder_metric.stl ├── weighted_baseplate.stl ├── basic_cup_1x1x3_nolip.stl ├── socket_holder_imperial.stl ├── socket_holder_imperial_big.stl ├── socket_holder_metric_small.stl ├── socket_holder_imperial_small.stl └── socket_holder_metric_stacking.stl ├── _render_chess.bat ├── gridfinity_glue_stick.scad ├── gridfinity_flsun_q5.scad ├── LICENSE ├── gridfinity_socket_holder.scad ├── gridfinity_baseplate.scad ├── _render_images.bat ├── README.md ├── canonicalize.py ├── _render_all.bat ├── gridfinity_basic_cup.scad ├── gridfinity_chess.scad ├── gridfinity_silverware.scad ├── gridfinity_modules.scad └── gridfinity_cup_modules.scad /Images/customizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/customizer.png -------------------------------------------------------------------------------- /Renders/base_lid.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/base_lid.stl -------------------------------------------------------------------------------- /Renders/baseplate.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/baseplate.stl -------------------------------------------------------------------------------- /Renders/chess/king.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/chess/king.stl -------------------------------------------------------------------------------- /Renders/chess/pawn.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/chess/pawn.stl -------------------------------------------------------------------------------- /Renders/chess/rook.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/chess/rook.stl -------------------------------------------------------------------------------- /Renders/chess/tile.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/chess/tile.stl -------------------------------------------------------------------------------- /Renders/flsun_q5.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/flsun_q5.stl -------------------------------------------------------------------------------- /Renders/chess/bishop.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/chess/bishop.stl -------------------------------------------------------------------------------- /Renders/chess/knight.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/chess/knight.stl -------------------------------------------------------------------------------- /Renders/chess/queen.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/chess/queen.stl -------------------------------------------------------------------------------- /Images/basic_cup_2x1x3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/basic_cup_2x1x3.png -------------------------------------------------------------------------------- /Images/efficient_floor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/efficient_floor.png -------------------------------------------------------------------------------- /Images/frame_baseplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/frame_baseplate.png -------------------------------------------------------------------------------- /Images/overhang_remedy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/overhang_remedy.png -------------------------------------------------------------------------------- /Renders/basic_cup_2x1x2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/basic_cup_2x1x2.stl -------------------------------------------------------------------------------- /Renders/glue_stick_cup.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/glue_stick_cup.stl -------------------------------------------------------------------------------- /Images/basic_cup_halfx2x4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/basic_cup_halfx2x4.png -------------------------------------------------------------------------------- /Images/divided_cup_2x1x3x5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/divided_cup_2x1x3x5.png -------------------------------------------------------------------------------- /Images/irregular_cup_2x1x3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/irregular_cup_2x1x3.png -------------------------------------------------------------------------------- /Images/lid_baseplate_combo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/lid_baseplate_combo.png -------------------------------------------------------------------------------- /Renders/basic_cup_halfx2x4.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/basic_cup_halfx2x4.stl -------------------------------------------------------------------------------- /Renders/divided_cup_2x1x2x5.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/divided_cup_2x1x2x5.stl -------------------------------------------------------------------------------- /Renders/divided_cup_2x1x3x5.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/divided_cup_2x1x3x5.stl -------------------------------------------------------------------------------- /Renders/filled_block_2x1x3x5.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/filled_block_2x1x3x5.stl -------------------------------------------------------------------------------- /Renders/socket_holder_metric.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/socket_holder_metric.stl -------------------------------------------------------------------------------- /Renders/weighted_baseplate.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/weighted_baseplate.stl -------------------------------------------------------------------------------- /Images/weighted_baseplate_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/weighted_baseplate_top.png -------------------------------------------------------------------------------- /Renders/basic_cup_1x1x3_nolip.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/basic_cup_1x1x3_nolip.stl -------------------------------------------------------------------------------- /Renders/socket_holder_imperial.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/socket_holder_imperial.stl -------------------------------------------------------------------------------- /Images/weighted_baseplate_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Images/weighted_baseplate_bottom.png -------------------------------------------------------------------------------- /Renders/socket_holder_imperial_big.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/socket_holder_imperial_big.stl -------------------------------------------------------------------------------- /Renders/socket_holder_metric_small.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/socket_holder_metric_small.stl -------------------------------------------------------------------------------- /Renders/socket_holder_imperial_small.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/socket_holder_imperial_small.stl -------------------------------------------------------------------------------- /Renders/socket_holder_metric_stacking.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector76/gridfinity_openscad/HEAD/Renders/socket_holder_metric_stacking.stl -------------------------------------------------------------------------------- /_render_chess.bat: -------------------------------------------------------------------------------- 1 | @set OPENSCAD="C:\Program Files\OpenSCAD\openscad.exe" 2 | @set PYTHON="C:\Users\Vector\Anaconda3\python.exe" 3 | 4 | for %%p in (tile, pawn, knight, bishop, rook, queen, king) do ( 5 | %OPENSCAD% gridfinity_chess.scad -D "part=""%%p""" -o Renders/chess/%%p.stl --export-format binstl 6 | %PYTHON% canonicalize.py Renders/chess/%%p.stl 7 | ) 8 | 9 | pause -------------------------------------------------------------------------------- /gridfinity_glue_stick.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | cup_height = 5; 4 | stick_diameter = 30; 5 | easement_z = 0.7; // a slightly large opening at the top for compliance while inserting. 6 | minimum_wall = 4; 7 | blocks_needed = ceil((stick_diameter+2*minimum_wall)/gridfinity_pitch); 8 | 9 | render() 10 | glue_stick_cup(blocks_needed, blocks_needed, cup_height); 11 | 12 | 13 | module glue_stick_cup(num_x=1, num_y=1, num_z=2) { 14 | difference() { 15 | grid_block(num_x, num_y, num_z, magnet_diameter=0, screw_depth=0, center=true); 16 | glue_stick(num_z, stick_diameter); 17 | } 18 | } 19 | 20 | module glue_stick(num_z=5, diam) { 21 | floor_thickness = blocks_needed > 1 ? 5.5 : 1.2; 22 | translate([0, 0, floor_thickness]) cylinder(h=num_z*gridfinity_zpitch, d=diam); 23 | translate([0, 0, (num_z - easement_z)*gridfinity_zpitch + 1.2]) 24 | cylinder(h=easement_z*gridfinity_zpitch, d1=diam, d2=diam*1.1); 25 | } 26 | -------------------------------------------------------------------------------- /gridfinity_flsun_q5.scad: -------------------------------------------------------------------------------- 1 | // include instead of use, so we get the pitch 2 | include 3 | use // for frame_plain 4 | 5 | translate([gridfinity_pitch/2, gridfinity_pitch/2, 0]) frame_plain(4, 1); 6 | ear_hole_x = 182.5; // distance between existing screw holes on FLSUN q5. 7 | ear_hole_y = 7; // distance of screw hole from the front panel. 8 | from_ends = (ear_hole_x - gridfinity_pitch*4) / 2; 9 | cube_z = 4.4; // ht from above. 10 | M4_d = 4.2; // diameter needed for an M4 bolt. 11 | 12 | for (i = [0,1]) { 13 | x = i * ear_hole_x - from_ends; 14 | difference() { 15 | hull() { 16 | translate([x, ear_hole_y, 0]) cylinder(h=cube_z, d=M4_d*2, $fn=20); 17 | translate([x - from_ends * i, ear_hole_y - M4_d, 0]) cube([from_ends, M4_d*2, cube_z]); 18 | } 19 | translate([x, ear_hole_y, -0.01]) cylinder(h=cube_z + 0.02, d=M4_d, $fn=20); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jamie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gridfinity_socket_holder.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | part = 5; 4 | 5 | if (part == 1) { 6 | socket_holder(4, [12,12,12,13,14,16,16,17,21,22], "METRIC"); 7 | } 8 | else if (part == 2) { 9 | socket_holder(4, [12,13,14,14,16,16,17,18,20,22], "IMPERIAL"); 10 | } 11 | else if (part == 3) { 12 | socket_holder(4, [12,12,12,12,12,12,13,14,14,16,16], "Imperial < 1/2\"", num_z=5); 13 | } 14 | else if (part == 4) { 15 | socket_holder(3, [17,18,20,22,24], "Imperial >= 1/2\""); 16 | } 17 | else if (part == 5) { 18 | socket_holder(4, [12,12,12,13,14,16,16,17,21,22], "Metric >=7mm", num_z=6); 19 | } 20 | else if (part == 6) { 21 | socket_holder(2, [12,12,12,12,12,12], "metric<7mm"); 22 | } 23 | 24 | function inc(v, a=.6) = [for (i = v) i+a ]; 25 | 26 | module socket_holder(num_x=1, widths=[], name="", num_z=3) { 27 | difference() { 28 | grid_block(num_x, 1, num_z); 29 | usable_w = 42*num_x - 6; 30 | rotate([-45,0,0])translate([-18,-5,1]) 31 | #sockets(inc(widths,0.6), usable_w); 32 | rotate([-45,0,0])translate([-18,-23-6,5]) 33 | cube([usable_w,18,26]); 34 | translate([-18,-20,10])rotate([90,0,0]) 35 | linear_extrude(10)text(name); 36 | translate([-18,-18,22])cube([usable_w,42-6,50]); 37 | } 38 | } 39 | 40 | function move(v, i=0, r=0, lo=0) = i > 0 ? move(v, i-1, r+(v[i-1]+v[i])/2, lo) + lo : r; 41 | function sum(v, i=0, r=0) = i < len(v) ? sum(v, i+1, v[i] + r) : r; 42 | 43 | module sockets(widths=[], width = 100) { 44 | leftover = width - sum(widths); 45 | echo(leftover=leftover, "(should be greater than 0)"); 46 | for (i = [0:len(widths)-1]) { 47 | s = move(widths, i, 0, leftover/(len(widths)-1)); 48 | translate([widths[0]/2,0,0]) 49 | translate([s,-widths[i]/2,0])cylinder(h=26, d=widths[i]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gridfinity_baseplate.scad: -------------------------------------------------------------------------------- 1 | // include instead of use, so we get the pitch 2 | include 3 | 4 | xsize = 5; 5 | ysize = 3; 6 | weighted = false; 7 | lid = false; 8 | 9 | if (lid) { 10 | base_lid(xsize, ysize); 11 | } 12 | else if (weighted) { 13 | weighted_baseplate(xsize, ysize); 14 | } 15 | else { 16 | frame_plain(xsize, ysize); 17 | } 18 | 19 | 20 | module base_lid(num_x, num_y) { 21 | magnet_od = 6.5; 22 | magnet_position = min(gridfinity_pitch/2-8, gridfinity_pitch/2-4-magnet_od/2); 23 | magnet_thickness = 2.4; 24 | eps = 0.1; 25 | 26 | translate([0, 0, 7]) frame_plain(xsize, ysize, trim=0.25); 27 | difference() { 28 | grid_block(xsize, ysize, 1, magnet_diameter=0, screw_depth=0); 29 | gridcopy(num_x, num_y) { 30 | cornercopy(magnet_position) { 31 | translate([0, 0, 7-magnet_thickness]) 32 | cylinder(d=magnet_od, h=magnet_thickness+eps, $fn=48); 33 | } 34 | } 35 | } 36 | } 37 | 38 | 39 | module weighted_baseplate(num_x, num_y) { 40 | magnet_od = 6.5; 41 | magnet_position = min(gridfinity_pitch/2-8, gridfinity_pitch/2-4-magnet_od/2); 42 | magnet_thickness = 2.4; 43 | eps = 0.1; 44 | 45 | difference() { 46 | frame_plain(num_x, num_y, 6.4); 47 | 48 | gridcopy(num_x, num_y) { 49 | cornercopy(magnet_position) { 50 | translate([0, 0, -magnet_thickness]) 51 | cylinder(d=magnet_od, h=magnet_thickness+eps, $fn=48); 52 | 53 | translate([0, 0, -6.4]) cylinder(d=3.5, h=6.4, $fn=24); 54 | 55 | // counter-sunk holes in the bottom 56 | translate([0, 0, -6.41]) cylinder(d1=8.5, d2=3.5, h=2.5, $fn=24); 57 | } 58 | 59 | translate([-10.7, -10.7, -6.41]) cube([21.4, 21.4, 4.01]); 60 | 61 | for (a2=[0,90]) rotate([0, 0, a2]) 62 | hull() for (a=[0, 180]) rotate([0, 0, a]) 63 | translate([-14.9519, 0, -6.41]) cylinder(d=8.5, h=2.01, $fn=24); 64 | } 65 | } 66 | } 67 | 68 | 69 | module frame_plain(num_x, num_y, extra_down=0, trim=0) { 70 | ht = extra_down > 0 ? 4.4 : 5; 71 | corner_radius = 3.75; 72 | corner_position = gridfinity_pitch/2-corner_radius-trim; 73 | difference() { 74 | hull() cornercopy(corner_position, num_x, num_y) 75 | translate([0, 0, -extra_down]) cylinder(r=corner_radius, h=ht+extra_down, $fn=44); 76 | translate([0, 0, trim ? 0 : -0.01]) 77 | render() gridcopy(num_x, num_y) pad_oversize(margins=1); 78 | } 79 | } -------------------------------------------------------------------------------- /_render_images.bat: -------------------------------------------------------------------------------- 1 | @set OPENSCAD="C:\Program Files\OpenSCAD\openscad.exe" 2 | 3 | rem basic cup with finger scoop 4 | %OPENSCAD% gridfinity_basic_cup.scad -D width=2 -D depth=1 -D height=3 -o Images/basic_cup_2x1x3.png --view=axes,scales --projection=o --camera=19.20,1.91,15.44,64.80,0,231.5,263.43 5 | 6 | rem basic cup with dividers and subdivisions into 5 7 | %OPENSCAD% gridfinity_basic_cup.scad -D width=2 -D depth=1 -D height=3 -D chambers=5 -D fingerslide=true -D withLabel=true -o Images/divided_cup_2x1x3x5.png --view=axes,scales --projection=o --camera=20.33,3.70,17.31,57.10,0,22.7,292.71 8 | 9 | rem basic cup with irregular subdivisions 10 | %OPENSCAD% gridfinity_basic_cup.scad -D width=2 -D depth=1 -D height=3 -D irregular_subdivisions=true -D separator_positions=[0.25,0.5,1.4] -D withLabel=true -o Images/irregular_cup_2x1x3.png --view=axes,scales --projection=o --camera=19.31,5.79,3.29,48.7,0,15.2,263.43 11 | 12 | rem half-width bin 13 | %OPENSCAD% gridfinity_basic_cup.scad -D width=0.5 -D depth=2 -D height=4 -D fingerslide=true -D withLabel=true -o Images/basic_cup_halfx2x4.png --view=axes,scales --projection=o --camera=-13.56,3.63,30.18,50.1,0,339.3,237.09 14 | 15 | rem show bottom hole print remedy 16 | %OPENSCAD% gridfinity_basic_cup.scad -D width=1 -D depth=1 -D height=4 -D magnet_diameter=6.5 -D screw_depth=6 -o Images/overhang_remedy.png --view=axes,scales --projection=o --camera=-23.23,7.43,25.06,151.6,0,43,102.06 17 | 18 | rem show material-efficient bottom 19 | %OPENSCAD% gridfinity_basic_cup.scad -D width=3 -D depth=3 -D height=3 -D fingerslide=false -D efficient_floor=true -o Images/efficient_floor.png --view=axes,scales --projection=o --camera=23.48,47.07,3.45,54.3,0,73.8,495.7 20 | 21 | rem frame baseplates: 22 | %OPENSCAD% gridfinity_baseplate.scad -o images/frame_baseplate.png --view=axes,scales --projection=o --camera=23.48,47.07,3.45,54.3,0,73.8,495.7 23 | 24 | rem weighted baseplate top and bottom 25 | %OPENSCAD% gridfinity_baseplate.scad -D xsize=3 -D ysize=2 -D weighted=true -o images/weighted_baseplate_top.png --view=axes,scales --projection=o --camera=52.23,10.30,16.49,37.50,0,47.4,446.13 26 | %OPENSCAD% gridfinity_baseplate.scad -D xsize=3 -D ysize=2 -D weighted=true -o images/weighted_baseplate_bottom.png --view=axes,scales --projection=o --camera=40.31,25.87,2.67,148.8,0,44.6,446.13 27 | 28 | rem lid/baseplate combo 29 | %OPENSCAD% gridfinity_baseplate.scad -D xsize=3 -D ysize=2 -D lid=true -o images/lid_baseplate_combo.png --view=axes,scales --projection=o --render --camera=38.04,24.18,3.69,60.60,0,47.4,446.13 30 | 31 | pause -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gridfinity OpenSCAD Model 2 | This is a recreation of Zack Freedman's gridfinity system in OpenSCAD, 3 | intended for both experienced OpenSCAD users to customize with added features, 4 | and also less-experienced OpenSCAD users to customize via options offered in the customizer. 5 | 6 | The fit-related dimensions are intended to match Zack's original design exactly. 7 | 8 | Options for bins: 9 | * Screw holes are optional, or alternative lengths can be specified 10 | * Magnet pockets are optional, or alternative diameters can be specified 11 | * User-selectable wall thickness and floor thickness 12 | * Generate a filled-in block as a starting point for generating other models (similar in spirit to [this](https://www.printables.com/model/210548-filled-in-gridfinity-boxes-for-customization)) 13 | * Any number of subdivisions along X axis (only X subdivisions are implemented) 14 | * Irregular subdivisions along X axis (only X subdivisions are implemented) 15 | * Finger-slide for removing small parts, available as an option 16 | * Label feature available as an option 17 | * Label feature can cover entire X length or only part 18 | * Label feature can be left-justified, right-justified, or centered 19 | * Magnet/screw hole can have printable overhangs as an option (if screw holes and magnet pockets are both used) (similar in spirit to [this](https://www.printables.com/model/269834-gridfinity-template-modified-for-mid-air-holes)) 20 | * Option for material-efficient floor that is not flat but saves material/time (similar in spirit to [this](https://www.printables.com/model/265271-gridfinity-lite-economical-plain-storage-bins)) 21 | * Fractional-width bins (0.5 units) supported (similar in spirit to [this](https://www.printables.com/model/241907-gridfinity-half-boxes-up-to-3-grids-long-and-6u-hi)) 22 | 23 | []() 24 | 25 | Some other models are also included and are also parametric. Not all of these will be interesting to most people. Some of them are cosntructions for my own personal use. 26 | * Gridfinity base options 27 | * Base (just frame) 28 | * Weighted base includes space for weights and/or screws or magnets 29 | * Lid/base combination can stack on top of bins and provides base for stacking on top (e.g. for stacking a 1x1 on top of a 3x3) 30 | * Glue stick holder 31 | * Socket holder 32 | * Gridfinity base for Flsun Q5 33 | * Silverware drawer 34 | 35 | []() 36 | []() 37 | []() 38 | []() 39 | []() 40 | []() 41 | []() 42 | []() 43 | []() 44 | []() 45 | -------------------------------------------------------------------------------- /canonicalize.py: -------------------------------------------------------------------------------- 1 | # Parts copied from nophead's canonlicalizer 2 | import sys 3 | import struct 4 | 5 | class Vertex: 6 | def __init__(self, x, y, z): 7 | self.x, self.y, self.z = x, y, z 8 | self.key = (float(x), float(y), float(z)) 9 | 10 | class Normal: 11 | def __init__(self, dx, dy, dz): 12 | self.dx, self.dy, self.dz = dx, dy, dz 13 | 14 | class Facet: 15 | def __init__(self, normal, v1, v2, v3): 16 | self.normal = normal 17 | if v1.key < v2.key: 18 | if v1.key < v3.key: 19 | self.vertices = (v1, v2, v3) #v1 is the smallest 20 | else: 21 | self.vertices = (v3, v1, v2) #v3 is the smallest 22 | else: 23 | if v2.key < v3.key: 24 | self.vertices = (v2, v3, v1) #v2 is the smallest 25 | else: 26 | self.vertices = (v3, v1, v2) #v3 is the smallest 27 | 28 | def key(self): 29 | return (self.vertices[0].x, self.vertices[0].y, self.vertices[0].z, 30 | self.vertices[1].x, self.vertices[1].y, self.vertices[1].z, 31 | self.vertices[2].x, self.vertices[2].y, self.vertices[2].z) 32 | 33 | def pack(self): 34 | facet_format = ' binary 44 | fileContent = file.read() 45 | 46 | self.header = fileContent[:80] 47 | 48 | # expected number of facets 49 | self.expected_facets = struct.unpack(" 2 | use 3 | 4 | // X dimension in grid units 5 | width = 2; // [ 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 ] 6 | // Y dimension in grid units 7 | depth = 1; // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 ] 8 | // Z dimension (multiples of 7mm) 9 | height = 3; 10 | // (Zack's design uses magnet diameter of 6.5) 11 | magnet_diameter = 0; // .1 12 | // (Zack's design uses depth of 6) 13 | screw_depth = 0; 14 | // Hole overhang remedy is active only when both screws and magnets are nonzero (and this option is selected) 15 | hole_overhang_remedy = true; 16 | //Only add attachments (magnets and screw) to box corners (prints faster). 17 | box_corner_attachments_only = false; 18 | // Fill in solid block (overrides all following options) 19 | filled_in = false; 20 | // X dimension subdivisions 21 | chambers = 1; 22 | // Include overhang for labeling (and specify left/right/center justification) 23 | withLabel = "disabled"; // ["disabled", "left", "right", "center", "leftchamber", "rightchamber", "centerchamber"] 24 | // Include larger corner fillet 25 | fingerslide = true; 26 | // Width of the label in number of units, or zero means full width 27 | labelWidth = 0; // .01 28 | // Minimum thickness above cutouts in base (Zack's design is effectively 1.2) 29 | floor_thickness = 0.7; 30 | // Wall thickness (Zack's design is 0.95) 31 | wall_thickness = 0.95; // .01 32 | // Efficient floor option saves material and time, but the floor is not smooth (only applies if no magnets, screws, or finger-slide used) 33 | efficient_floor = false; 34 | // When enabled, irregular subdivisions have to be defined in code 35 | irregular_subdivisions = false; 36 | // Enable to subdivide bottom pads to allow half-cell offsets 37 | half_pitch = false; 38 | // Remove some or all of lip 39 | lip_style = "normal"; // [ "normal", "reduced", "none" ] 40 | 41 | module end_of_customizer_opts() {} 42 | 43 | // Separator positions are defined in terms of grid units from the left end 44 | separator_positions = [ 0.25, 0.5, 1.4 ]; 45 | 46 | if (filled_in) { 47 | grid_block(width, depth, height, magnet_diameter=magnet_diameter, 48 | screw_depth=screw_depth, hole_overhang_remedy=hole_overhang_remedy, 49 | half_pitch=half_pitch, box_corner_attachments_only=box_corner_attachments_only); 50 | } 51 | else if (irregular_subdivisions) { 52 | irregular_cup( 53 | num_x=width, 54 | num_y=depth, 55 | num_z=height, 56 | withLabel=withLabel, 57 | labelWidth=labelWidth, 58 | fingerslide=fingerslide, 59 | magnet_diameter=magnet_diameter, 60 | screw_depth=screw_depth, 61 | floor_thickness=floor_thickness, 62 | wall_thickness=wall_thickness, 63 | hole_overhang_remedy=hole_overhang_remedy, 64 | separator_positions=separator_positions, 65 | half_pitch=half_pitch, 66 | lip_style=lip_style, 67 | box_corner_attachments_only=box_corner_attachments_only 68 | ); 69 | } 70 | else { 71 | basic_cup( 72 | num_x=width, 73 | num_y=depth, 74 | num_z=height, 75 | chambers=chambers, 76 | withLabel=withLabel, 77 | labelWidth=labelWidth, 78 | fingerslide=fingerslide, 79 | magnet_diameter=magnet_diameter, 80 | screw_depth=screw_depth, 81 | floor_thickness=floor_thickness, 82 | wall_thickness=wall_thickness, 83 | hole_overhang_remedy=hole_overhang_remedy, 84 | efficient_floor=efficient_floor, 85 | half_pitch=half_pitch, 86 | lip_style=lip_style, 87 | box_corner_attachments_only=box_corner_attachments_only 88 | ); 89 | } -------------------------------------------------------------------------------- /gridfinity_chess.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | // Select model 4 | part = "tile"; // [ board, tile, pawn, knight, bishop, rook, queen, king ] 5 | 6 | if (part == "tile") { 7 | tile(); 8 | } 9 | else if (part == "pawn") { 10 | pawn(); 11 | } 12 | else if (part == "knight") { 13 | knight(); 14 | } 15 | else if (part == "bishop") { 16 | bishop(); 17 | } 18 | else if (part == "rook") { 19 | rook(); 20 | } 21 | else if (part == "queen") { 22 | queen(); 23 | } 24 | else if (part == "king") { 25 | king(); 26 | } 27 | else if (part == "board") { 28 | board(); 29 | color("#DDDDDD") piece_set(); 30 | color("#505050") piece_set(false); 31 | } 32 | 33 | 34 | module piece_set(white=true) { 35 | pawnrow = white ? 1 : 6; 36 | restrow = white ? 0 : 7; 37 | 38 | for (i=[0:7]) { 39 | translate([i*42, 42*pawnrow, 0]) pawn(); 40 | } 41 | for (i=[0,1]) { 42 | translate([0 + 7*i*42, restrow*42, 0]) rook(); 43 | translate([(1 + 5*i)*42, restrow*42, 0]) 44 | rotate([0, 0, white ? 0 : 180]) knight(); 45 | translate([(2 + 3*i)*42, restrow*42, 0]) bishop(); 46 | } 47 | translate([3*42, restrow*42, 0]) queen(); 48 | translate([4*42, restrow*42, 0]) king(); 49 | } 50 | 51 | 52 | module king() { 53 | base(); 54 | 55 | rotate_extrude($fn=60) 56 | polygon([[0, 47], [0, 0], [11, 0], [11, 10], [8, 13], [6, 38], 57 | [9, 42], [9, 45], [7, 46]]); 58 | 59 | translate([-1.5, -1, 47-1]) cube([3, 2, 10]); 60 | translate([-3, -1, 51]) cube([6, 2, 3]); 61 | } 62 | 63 | 64 | module queen() { 65 | base(); 66 | 67 | difference() { 68 | rotate_extrude($fn=60) 69 | polygon([[0, 45], [0, 0], [11, 0], [11, 10], [8, 13], [6, 42], 70 | [8, 44], [8, 46], [11, 48], [13, 53], [12, 53], [8, 48]]); 71 | 72 | *%for (a=[0, 60, 120]) rotate([0, 0, a]) 73 | translate([-15, -1.5, 48.5]) cube([30, 3, 10]); 74 | 75 | for (a=[0, 30, 60, 90, 120, 150]) rotate([0, 0, a]) 76 | translate([0, 0, 48.5+6]) 77 | rotate([0, 90, 0]) cylinder(d=7.25, h=30, $fn=30, center=true); 78 | } 79 | } 80 | 81 | 82 | module knight() { 83 | base(); 84 | 85 | rotate_extrude($fn=60) 86 | polygon([[0, 13], [0, 0], [9, 0], [9, 10], [7, 13]]); 87 | 88 | hull() { 89 | translate([-4, -7.5, 0]) cube([8, 13, 14]); 90 | translate([-2.5, -7.5, 30]) cube([5, 8, 3]); 91 | } 92 | translate([-2.5-.125, -7.5, 30]) cube([5+.25, 17, 7]); 93 | 94 | // support for bridging knight nose (remove afterward) 95 | hull() { 96 | translate([-2.625, 9.5-0.8, 27]) cube([5.25, 0.8, 3]); 97 | translate([-5, 10, 0]) cube([10, 1, 1]); 98 | } 99 | translate([-0.6, 10, 0]) cube([1.2, 6, 5]); 100 | } 101 | 102 | 103 | module bishop() { 104 | base(); 105 | 106 | tz(42) sphere(d=3, $fn=60); 107 | 108 | difference() { 109 | rotate_extrude($fn=60) 110 | polygon([[0, 42], [0, 0], [9, 0], [9, 10], [7, 13], [3.5, 26], [6, 32]]); 111 | 112 | translate([4.5, 0, 40]) 113 | rotate([0, -56, 0]) 114 | cylinder(d=20, h=1, $fn=60); 115 | } 116 | } 117 | 118 | 119 | module rook() { 120 | base(); 121 | 122 | difference() { 123 | rotate_extrude($fn=60) 124 | polygon([[0, 34], [0, 0], [10, 0], [10, 10], [7, 13], [7, 29], 125 | [10, 32], [10, 39], [8, 39], [8, 34]]); 126 | 127 | for (a=[0, 90]) rotate([0, 0, a]) 128 | translate([-15, -1.5, 34.5]) cube([30, 3, 10]); 129 | } 130 | } 131 | 132 | 133 | module pawn() { 134 | base(); 135 | 136 | tz(25) sphere(d=11, $fn=60); 137 | 138 | rotate_extrude($fn=60) 139 | polygon([[0, 25], [0, 0], [7, 0], [7, 8], [4.5, 11], [3, 24]]); 140 | } 141 | 142 | 143 | module base() { 144 | difference() { 145 | grid_block(1, 1, 0.72, magnet_diameter=0, screw_depth=0); 146 | difference() { 147 | cube([35, 35, 30], center=true); 148 | for (a=[45, 135]) rotate([0, 0, a]) cube([60, 2, 30], center=true); 149 | } 150 | 151 | translate([-21, -21, 5]) cube([42, 42, 42]); 152 | } 153 | } 154 | 155 | 156 | module board() { // 64 tiles in alternating colors 157 | tz(-3.55) for (ti=[0:7]) for (tj=[0:7]) translate([42*ti, 42*tj, 0]) 158 | color((ti+tj)%2 == 0 ? "darkblue" : "lightblue") 159 | render() tile(); 160 | } 161 | 162 | 163 | module tile() { 164 | grid_block(1, 1, 0.5, magnet_diameter=0, screw_depth=0); 165 | } 166 | 167 | 168 | module tz(z) { 169 | translate([0, 0, z]) children(); 170 | } 171 | -------------------------------------------------------------------------------- /gridfinity_silverware.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | /* [Utensil count and measurements] */ 4 | // Utensil definitions above this number are ignored 5 | number_of_utensils = 7; 6 | 7 | utensil_1_wide = 28; utensil_1_narrow = 15; utensil_1_length = 202; 8 | utensil_2_wide = 24; utensil_2_narrow = 14; utensil_2_length = 181; 9 | utensil_3_wide = 37; utensil_3_narrow = 14; utensil_3_length = 181; 10 | utensil_4_wide = 33; utensil_4_narrow = 12; utensil_4_length = 155; 11 | utensil_5_wide = 32; utensil_5_narrow = 15; utensil_5_length = 191; 12 | utensil_6_wide = 32; utensil_6_narrow = 15; utensil_6_length = 150; 13 | utensil_7_wide = 24; utensil_7_narrow = 16; utensil_7_length = 180; 14 | 15 | /* [Even more utensils?] */ 16 | utensil_8_wide = 28; utensil_8_narrow = 15; utensil_8_length = 202; 17 | utensil_9_wide = 24; utensil_9_narrow = 14; utensil_9_length = 181; 18 | utensil_10_wide = 37; utensil_10_narrow = 14; utensil_10_length = 181; 19 | utensil_11_wide = 33; utensil_11_narrow = 12; utensil_11_length = 155; 20 | utensil_12_wide = 32; utensil_12_narrow = 15; utensil_12_length = 191; 21 | utensil_13_wide = 32; utensil_13_narrow = 15; utensil_13_length = 150; 22 | utensil_14_wide = 24; utensil_14_narrow = 16; utensil_14_length = 180; 23 | 24 | /* [Other parametrs] */ 25 | // Separation wall thickness 26 | separator_wall = 2; // .1 27 | // Clearance on sides and ends of utensils 28 | margin = 1; // .1 29 | // Height to upper surface excluding perimeter lip 30 | height_in_mm = 35; 31 | 32 | /* [Gridfinity features] */ 33 | // (Zack's design uses magnet diameter of 6.5) 34 | magnet_diameter = 0; // .1 35 | // (Zack's design uses depth of 6) 36 | screw_depth = 0; 37 | // Minimum thickness above cutouts in base (Zack's design is effectively 1.2) 38 | floor_thickness = 1.0; 39 | 40 | module end_of_customizer() {} 41 | 42 | // Maximum utensil definitions 43 | silver_defs_all = [ 44 | [ utensil_1_wide, utensil_1_narrow, utensil_1_length ], 45 | [ utensil_2_wide, utensil_2_narrow, utensil_2_length ], 46 | [ utensil_3_wide, utensil_3_narrow, utensil_3_length ], 47 | [ utensil_4_wide, utensil_4_narrow, utensil_4_length ], 48 | [ utensil_5_wide, utensil_5_narrow, utensil_5_length ], 49 | [ utensil_6_wide, utensil_6_narrow, utensil_6_length ], 50 | [ utensil_7_wide, utensil_7_narrow, utensil_7_length ], 51 | [ utensil_8_wide, utensil_8_narrow, utensil_8_length ], 52 | [ utensil_9_wide, utensil_9_narrow, utensil_9_length ], 53 | [ utensil_10_wide,utensil_10_narrow,utensil_10_length ], 54 | [ utensil_11_wide,utensil_11_narrow,utensil_11_length ], 55 | [ utensil_12_wide,utensil_12_narrow,utensil_12_length ], 56 | [ utensil_13_wide,utensil_13_narrow,utensil_13_length ], 57 | [ utensil_14_wide,utensil_14_narrow,utensil_14_length ], 58 | ]; 59 | 60 | // ##### Utility functions 61 | 62 | // tail of a list with at least 2 elements 63 | function cdr(list) = [ for (i=[1:len(list)-1]) list[i] ]; 64 | // sum of a bunch of values (recursive functional style) 65 | function vecsum(vals) = len(vals) > 1 ? vals[0] + vecsum(cdr(vals)) : vals[0]; 66 | // total width of a list of utensils 67 | function totwidth(defs) = vecsum(pitches(defs)) + 2*margin + 68 | max(defs[0][0], defs[0][1])/2 + max(defs[len(defs)-1][0], defs[len(defs)-1][1])/2; 69 | // maximum length of list of utensils 70 | function maxlen(defs) = len(defs) > 1 ? max(defs[0][2], maxlen(cdr(defs))) : defs[0][2]; 71 | // convert a list of utensils into a list of center-to-center distances 72 | function pitches(defs) = [ for (i=[0:len(defs)-2]) separator_wall + 2*margin + 73 | max( defs[i][1]/2 + defs[i+1][0]/2, defs[i][0]/2 + defs[i+1][1]/2) ]; 74 | 75 | // ##### Derived variables and values 76 | 77 | // subset of all utensil definitions up to the requested number of utensils 78 | silver_defs = [ for (i=[0:number_of_utensils-1]) silver_defs_all[i] ]; 79 | // width of combination of all silverware 80 | silver_w = totwidth(silver_defs); 81 | // gridfinity modules expect height in units of 7 mm (but fractions are allowed) 82 | height = height_in_mm / 7; 83 | // X dimension in gridfinity units 84 | width = ceil((silver_w + 5.7)/42); 85 | // Y dimension in gridfinity units 86 | depth = ceil((maxlen(silver_defs)+2*margin+5.7)/42); 87 | 88 | echo("maxlen: ", maxlen(silver_defs)); 89 | 90 | // ##### Top level model 91 | 92 | silverware_pockets(silver_defs); 93 | 94 | // ##### Modules 95 | 96 | // Polygon shape for a single utensil 97 | module poly_pocket(topw, botw, oal, wall=separator_wall) { 98 | b2 = botw/2; t2 = topw/2; o2 = oal/2; qd = abs(topw-botw)/4; // quarter of delta 99 | f = (topw > botw) ? (1-sqrt(2)/2)*wall/2 : -(1-sqrt(2)/2)*wall/2; 100 | polygon([[-b2,-qd+f],[-b2,-o2],[b2,-o2],[b2,-qd+f],[t2,qd+f], 101 | [t2,o2],[-t2,o2],[-t2,qd+f]]); 102 | } 103 | 104 | // top level module to generate packed polygons 105 | module stack_silver(defs) { 106 | xtop = 0; 107 | xbot = 0; 108 | translate([-totwidth(defs)/2, 0]) 109 | recur_stack_silver(xtop, xbot, defs, 0); 110 | } 111 | 112 | // recursive helper function essentially implements loop 113 | module recur_stack_silver(xtop, xbot, silv, inverted) { 114 | s1 = silv[0]; 115 | topw = 2*margin + (inverted ? s1[1] : s1[0]); 116 | botw = 2*margin + (inverted ? s1[0] : s1[1]); 117 | 118 | topmid = xtop+topw/2; 119 | botmid = xbot+botw/2; 120 | mid = max(topmid, botmid); 121 | 122 | translate([mid, 0]) poly_pocket(topw, botw, s1[2]+2*margin); 123 | 124 | xtop2 = mid + topw/2 + separator_wall; 125 | xbot2 = mid + botw/2 + separator_wall; 126 | if (len(silv) > 1) { // more pieces to stack, call recursively 127 | recur_stack_silver(xtop2, xbot2, cdr(silv), 1-inverted); 128 | } 129 | } 130 | 131 | // top level generator 132 | module silverware_pockets(defs, md=magnet_diameter, sd=screw_depth) { 133 | mag_ht = md > 0 ? 2.4: 0; 134 | m3_ht = sd; 135 | part_ht = 5; // height of bottom side groove between gridfinity units 136 | floorht = max(mag_ht, m3_ht, part_ht) + floor_thickness; 137 | 138 | difference() { 139 | grid_block(width, depth, height, magnet_diameter=md, screw_depth=sd, center=true); 140 | translate([0, 0, floorht]) linear_extrude(height=7*height) stack_silver(defs); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /gridfinity_modules.scad: -------------------------------------------------------------------------------- 1 | gridfinity_pitch = 42; 2 | gridfinity_zpitch = 7; 3 | gridfinity_clearance = 0.5; // each bin is undersize by this much 4 | 5 | // set this to produce sharp corners on baseplates and bins 6 | // not for general use (breaks compatibility) but may be useful for special cases 7 | sharp_corners = 0; 8 | 9 | 10 | // basic block with cutout in top to be stackable, optional holes in bottom 11 | // start with this and begin 'carving' 12 | module grid_block(num_x=1, num_y=1, num_z=2, magnet_diameter=6.5, screw_depth=6, center=false, hole_overhang_remedy=false, half_pitch=false, box_corner_attachments_only = false) { 13 | corner_radius = 3.75; 14 | outer_size = gridfinity_pitch - gridfinity_clearance; // typically 41.5 15 | block_corner_position = outer_size/2 - corner_radius; // need not match center of pad corners 16 | magnet_thickness = 2.4; 17 | magnet_position = min(gridfinity_pitch/2-8, gridfinity_pitch/2-4-magnet_diameter/2); 18 | screw_hole_diam = 3; 19 | gp = gridfinity_pitch; 20 | 21 | suppress_holes = num_x < 1 || num_y < 1; 22 | 23 | emd = suppress_holes ? 0 : magnet_diameter; // effective magnet diameter after override 24 | esd = suppress_holes ? 0 : screw_depth; // effective screw depth after override 25 | 26 | overhang_fix = hole_overhang_remedy && emd > 0 && esd > 0; 27 | overhang_fix_depth = 0.3; // assume this is enough 28 | 29 | totalht=gridfinity_zpitch*num_z+3.75; 30 | translate( center ? [-(num_x-1)*gridfinity_pitch/2, -(num_y-1)*gridfinity_pitch/2, 0] : [0, 0, 0] ) 31 | difference() { 32 | intersection() { 33 | union() { 34 | // logic for constructing odd-size grids of possibly half-pitch pads 35 | pad_grid(num_x, num_y, half_pitch); 36 | // main body will be cut down afterward 37 | translate([-gridfinity_pitch/2, -gridfinity_pitch/2, 5]) 38 | cube([gridfinity_pitch*num_x, gridfinity_pitch*num_y, totalht-5]); 39 | } 40 | 41 | // crop with outer cylinders 42 | translate([0, 0, -0.1]) 43 | hull() 44 | cornercopy(block_corner_position, num_x, num_y) 45 | cylinder(r=corner_radius, h=totalht+0.2, $fn=32); 46 | } 47 | 48 | // remove top so XxY can fit on top 49 | color("blue") 50 | translate([0, 0, gridfinity_zpitch*num_z]) 51 | pad_oversize(num_x, num_y, 1); 52 | 53 | if (esd > 0) { // add pockets for screws if requested 54 | gridcopycorners(ceil(num_x), ceil(num_y), magnet_position, box_corner_attachments_only) 55 | translate([0, 0, -0.1]) cylinder(d=screw_hole_diam, h=esd+0.1, $fn=28); 56 | } 57 | 58 | if (emd > 0) { // add pockets for magnets if requested 59 | gridcopycorners(ceil(num_x), ceil(num_y), magnet_position, box_corner_attachments_only) 60 | translate([0, 0, -0.1]) cylinder(d=emd, h=magnet_thickness+0.1, $fn=41); 61 | } 62 | 63 | if (overhang_fix) { // people seem to really like this overhang fix 64 | gridcopycorners(ceil(num_x), ceil(num_y), magnet_position, box_corner_attachments_only) 65 | translate([0, 0, magnet_thickness-0.1]) 66 | render() intersection() { // for some reason OpenSCAD blows up if I don't render here 67 | translate([-emd/2, -screw_hole_diam/2, 0]) cube([emd, screw_hole_diam, overhang_fix_depth+0.1]); 68 | cylinder(d=emd, h=1, $fn=41); 69 | } 70 | } 71 | } 72 | } 73 | 74 | 75 | module pad_grid(num_x, num_y, half_pitch=false) { 76 | // if num_x (or num_y) is less than 1 (or less than 0.5 if half_pitch is enabled) then round over the far side 77 | cut_far_x = (num_x < 1 && !half_pitch) || (num_x < 0.5); 78 | cut_far_y = (num_y < 1 && !half_pitch) || (num_y < 0.5); 79 | 80 | if (half_pitch) { 81 | gridcopy(ceil(num_x), ceil(num_y)) intersection() { 82 | pad_halfsize(); 83 | if (cut_far_x) { 84 | translate([gridfinity_pitch*(-0.5+num_x), 0, 0]) pad_halfsize(); 85 | } 86 | if (cut_far_y) { 87 | translate([0, gridfinity_pitch*(-0.5+num_y), 0]) pad_halfsize(); 88 | } 89 | if (cut_far_x && cut_far_y) { 90 | // without this the far corner would be rectangular 91 | translate([gridfinity_pitch*(-0.5+num_x), gridfinity_pitch*(-0.5+num_y), 0]) pad_halfsize(); 92 | } 93 | } 94 | } 95 | else { 96 | gridcopy(ceil(num_x), ceil(num_y)) intersection() { 97 | pad_oversize(); 98 | if (cut_far_x) { 99 | translate([gridfinity_pitch*(-1+num_x), 0, 0]) pad_oversize(); 100 | } 101 | if (cut_far_y) { 102 | translate([0, gridfinity_pitch*(-1+num_y), 0]) pad_oversize(); 103 | } 104 | if (cut_far_x && cut_far_y) { 105 | // without this the far corner would be rectangular 106 | translate([gridfinity_pitch*(-1+num_x), gridfinity_pitch*(-1+num_y), 0]) pad_oversize(); 107 | } 108 | } 109 | } 110 | } 111 | 112 | 113 | module pad_halfsize() { 114 | render() // render here to keep tree from blowing up 115 | for (xi=[0:1]) for (yi=[0:1]) translate([xi*gridfinity_pitch/2, yi*gridfinity_pitch/2, 0]) 116 | intersection() { 117 | pad_oversize(); 118 | translate([-gridfinity_pitch/2, 0, 0]) pad_oversize(); 119 | translate([0, -gridfinity_pitch/2, 0]) pad_oversize(); 120 | translate([-gridfinity_pitch/2, -gridfinity_pitch/2, 0]) pad_oversize(); 121 | } 122 | } 123 | 124 | // like a cylinder but produces a square solid instead of a round one 125 | // specified 'diameter' is the side length of the square, not the diagonal diameter 126 | module cylsq(d, h) { 127 | translate([-d/2, -d/2, 0]) cube([d, d, h]); 128 | } 129 | 130 | // like a tapered cylinder with two diameters, but square instead of round 131 | module cylsq2(d1, d2, h) { 132 | linear_extrude(height=h, scale=d2/d1) 133 | square([d1, d1], center=true); 134 | } 135 | 136 | // unit pad slightly oversize at the top to be trimmed or joined with other feet or the rest of the model 137 | // also useful as cutouts for stacking 138 | module pad_oversize(num_x=1, num_y=1, margins=0) { 139 | pad_corner_position = gridfinity_pitch/2 - 4; // must be 17 to be compatible 140 | bevel1_top = 0.8; // z of top of bottom-most bevel (bottom of bevel is at z=0) 141 | bevel2_bottom = 2.6; // z of bottom of second bevel 142 | bevel2_top = 5; // z of top of second bevel 143 | bonus_ht = 0.2; // extra height (and radius) on second bevel 144 | 145 | // female parts are a bit oversize for a nicer fit 146 | radialgap = margins ? 0.25 : 0; // oversize cylinders for a bit of clearance 147 | axialdown = margins ? 0.1 : 0; // a tiny bit of axial clearance present in Zack's design 148 | 149 | translate([0, 0, -axialdown]) 150 | difference() { 151 | union() { 152 | hull() cornercopy(pad_corner_position, num_x, num_y) { 153 | if (sharp_corners) { 154 | cylsq(d=1.6+2*radialgap, h=0.1); 155 | translate([0, 0, bevel1_top]) cylsq(d=3.2+2*radialgap, h=1.9); 156 | } 157 | else { 158 | cylinder(d=1.6+2*radialgap, h=0.1, $fn=24); 159 | translate([0, 0, bevel1_top]) cylinder(d=3.2+2*radialgap, h=1.9, $fn=32); 160 | } 161 | } 162 | 163 | hull() cornercopy(pad_corner_position, num_x, num_y) { 164 | if (sharp_corners) { 165 | translate([0, 0, bevel2_bottom]) 166 | cylsq2(d1=3.2+2*radialgap, d2=7.5+0.5+2*radialgap+2*bonus_ht, h=bevel2_top-bevel2_bottom+bonus_ht); 167 | } 168 | else { 169 | translate([0, 0, bevel2_bottom]) 170 | cylinder(d1=3.2+2*radialgap, d2=7.5+0.5+2*radialgap+2*bonus_ht, h=bevel2_top-bevel2_bottom+bonus_ht, $fn=32); 171 | } 172 | } 173 | } 174 | 175 | // cut off bottom if we're going to go negative 176 | if (margins) { 177 | translate([-gridfinity_pitch/2, -gridfinity_pitch/2, 0]) 178 | cube([gridfinity_pitch*num_x, gridfinity_pitch*num_y, axialdown]); 179 | } 180 | } 181 | } 182 | 183 | // similar to cornercopy, can only copy to box corners 184 | module gridcopycorners(num_x, num_y, r, onlyBoxCorners = false) { 185 | for (xi=[1:num_x]) for (yi=[1:num_y]) 186 | for (xx=[-1, 1]) for (yy=[-1, 1]) 187 | if(!onlyBoxCorners || 188 | (xi == 1 && yi == 1 && xx == -1 && yy == -1) || 189 | (xi == num_x && yi == num_y && xx == 1 && yy == 1) || 190 | (xi == 1 && yi == num_y && xx == -1 && yy == 1) || 191 | (xi == num_x && yi == 1 && xx == 1 && yy == -1)) 192 | translate([gridfinity_pitch*(xi-1), gridfinity_pitch*(yi-1), 0]) 193 | translate([xx*r, yy*r, 0]) children(); 194 | } 195 | 196 | // similar to quadtranslate but expands to extremities of a block 197 | module cornercopy(r, num_x=1, num_y=1) { 198 | for (xx=[-r, gridfinity_pitch*(num_x-1)+r]) for (yy=[-r, gridfinity_pitch*(num_y-1)+r]) 199 | translate([xx, yy, 0]) children(); 200 | } 201 | 202 | 203 | // make repeated copies of something(s) at the gridfinity spacing of 42mm 204 | module gridcopy(num_x, num_y) { 205 | for (xi=[1:num_x]) for (yi=[1:num_y]) translate([gridfinity_pitch*(xi-1), gridfinity_pitch*(yi-1), 0]) children(); 206 | } 207 | 208 | 209 | -------------------------------------------------------------------------------- /gridfinity_cup_modules.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | // X dimension subdivisions 4 | default_chambers = 1; 5 | 6 | // Include overhang for labeling 7 | default_withLabel = "disabled"; //[disabled: no label, left: left aligned label, right: right aligned label, center: center aligned label, leftchamber: left aligned chamber label, rightchamber: right aligned chamber label, centerchamber: center aligned chamber label] 8 | // Width of the label in number of units, or zero for full width 9 | default_labelWidth = 0; // 0.01 10 | // Include larger corner fillet 11 | default_fingerslide = true; 12 | // Set magnet diameter and depth to 0 to print without magnet holes 13 | // (Zack's design uses magnet diameter of 6.5) 14 | default_magnet_diameter = 6.5; // .1 15 | // (Zack's design uses depth of 6) 16 | default_screw_depth = 6; 17 | // Minimum thickness above cutouts in base (Zack's design is effectively 1.2) 18 | default_floor_thickness = 1.2; 19 | // Thickness of outer walls (Zack's design is 0.95 mm) 20 | default_wall_thickness = 0.95; 21 | // Use rectangular inset for better bridging/printability 22 | default_hole_overhang_remedy = false; 23 | // Save material with thinner floor (only if no magnets, screws, or finger-slide used) 24 | default_efficient_floor = false; 25 | // Half-pitch base pads for offset stacking 26 | default_half_pitch = false; 27 | // Might want to remove inner lip of cup 28 | default_lip_style = "normal"; 29 | // Limit attachments (magnets and scres) to box corners for faster printing. 30 | box_corner_attachments_only = false; 31 | basic_cup( 32 | num_x=2, 33 | num_y=1, 34 | num_z=3, 35 | chambers=default_chambers, 36 | withLabel=default_withLabel, 37 | labelWidth=default_labelWidth, 38 | magnet_diameter=default_magnet_diameter, 39 | screw_depth=default_screw_depth, 40 | floor_thickness=default_floor_thickness, 41 | wall_thickness=default_wall_thickness, 42 | hole_overhang_remedy=default_hole_overhang_remedy, 43 | efficient_floor=default_efficient_floor, 44 | half_pitch=default_half_pitch, 45 | lip_style=default_lip_style, 46 | box_corner_attachments_only=box_corner_attachments_only 47 | ); 48 | 49 | 50 | // It's recommended that all parameters other than x, y, z size should be specified by keyword 51 | // and not by position. The number of parameters makes positional parameters error prone, and 52 | // additional parameters may be added over time and break things. 53 | module basic_cup( 54 | num_x, 55 | num_y, 56 | num_z, 57 | chambers=default_chambers, 58 | withLabel=default_withLabel, 59 | labelWidth=default_labelWidth, 60 | fingerslide=default_fingerslide, 61 | magnet_diameter=default_magnet_diameter, 62 | screw_depth=default_screw_depth, 63 | floor_thickness=default_floor_thickness, 64 | wall_thickness=default_wall_thickness, 65 | hole_overhang_remedy=default_hole_overhang_remedy, 66 | efficient_floor=default_efficient_floor, 67 | half_pitch=default_half_pitch, 68 | lip_style=default_lip_style, 69 | box_corner_attachments_only=box_corner_attachments_only 70 | ) { 71 | num_separators = chambers-1; 72 | sep_pitch = num_x/(num_separators+1); 73 | separator_positions = num_separators < 1 ? [] : [ for (i=[1:num_separators]) i*sep_pitch ]; 74 | 75 | difference() { 76 | grid_block(num_x, num_y, num_z, magnet_diameter, screw_depth, hole_overhang_remedy=hole_overhang_remedy, half_pitch=half_pitch, box_corner_attachments_only=box_corner_attachments_only); 77 | color("red") partitioned_cavity(num_x, num_y, num_z, withLabel=withLabel, 78 | labelWidth=labelWidth, fingerslide=fingerslide, magnet_diameter=magnet_diameter, 79 | screw_depth=screw_depth, floor_thickness=floor_thickness, wall_thickness=wall_thickness, 80 | efficient_floor=efficient_floor, separator_positions=separator_positions, lip_style=lip_style); 81 | } 82 | } 83 | 84 | 85 | // separator positions are defined in units from the left side 86 | module irregular_cup( 87 | num_x, 88 | num_y, 89 | num_z, 90 | withLabel=default_withLabel, 91 | labelWidth=default_labelWidth, 92 | fingerslide=default_fingerslide, 93 | magnet_diameter=default_magnet_diameter, 94 | screw_depth=default_screw_depth, 95 | floor_thickness=default_floor_thickness, 96 | wall_thickness=default_wall_thickness, 97 | hole_overhang_remedy=default_hole_overhang_remedy, 98 | efficient_floor=default_efficient_floor, 99 | half_pitch=default_half_pitch, 100 | separator_positions=[], 101 | lip_style=default_lip_style 102 | ) { 103 | difference() { 104 | grid_block(num_x, num_y, num_z, magnet_diameter, screw_depth, hole_overhang_remedy=hole_overhang_remedy, half_pitch=half_pitch, box_corner_attachments_only=box_corner_attachments_only); 105 | color("red") partitioned_cavity(num_x, num_y, num_z, withLabel=withLabel, 106 | labelWidth=labelWidth, fingerslide=fingerslide, magnet_diameter=magnet_diameter, 107 | screw_depth=screw_depth, floor_thickness=floor_thickness, wall_thickness=wall_thickness, 108 | efficient_floor=efficient_floor, separator_positions=separator_positions, lip_style=lip_style); 109 | } 110 | } 111 | 112 | 113 | module partitioned_cavity(num_x, num_y, num_z, withLabel=default_withLabel, 114 | labelWidth=default_labelWidth, fingerslide=default_fingerslide, 115 | magnet_diameter=default_magnet_diameter, screw_depth=default_screw_depth, 116 | floor_thickness=default_floor_thickness, wall_thickness=default_wall_thickness, 117 | efficient_floor=default_efficient_floor, separator_positions=[], lip_style=default_lip_style) { 118 | // cavity with removed segments so that we leave dividing walls behind 119 | gp = gridfinity_pitch; 120 | outer_wall_th = 1.8; // cavity is this far away from the 42mm 'ideal' block 121 | inner_wall_th =1.2; 122 | 123 | bar_d = 1.2; 124 | zpoint = gridfinity_zpitch*num_z; 125 | 126 | yz = [[ (num_y-0.5)*gridfinity_pitch-14, zpoint-bar_d/2 ], 127 | [ (num_y-0.5)*gridfinity_pitch, zpoint-bar_d/2 ], 128 | [ (num_y-0.5)*gridfinity_pitch, zpoint-bar_d/2-10.18 ] 129 | ]; 130 | 131 | cavity_xsize = gp*num_x-2*outer_wall_th; 132 | 133 | difference() { 134 | basic_cavity(num_x, num_y, num_z, fingerslide=fingerslide, magnet_diameter=magnet_diameter, 135 | screw_depth=screw_depth, floor_thickness=floor_thickness, wall_thickness=wall_thickness, 136 | efficient_floor=efficient_floor, lip_style=lip_style); 137 | 138 | if (len(separator_positions) > 0) { 139 | for (i=[0:len(separator_positions)-1]) { 140 | translate([gp*(-0.5+separator_positions[i])-inner_wall_th/2, -gp/2, 0]) cube([inner_wall_th, gp*num_y, gridfinity_zpitch*(num_z+1)]); 141 | } 142 | } 143 | 144 | // this is the label 145 | if (withLabel != "disabled") { 146 | // calcualte list of chambers. 147 | chamberWidths = len(separator_positions) < 1 || 148 | labelWidth == 0 || 149 | withLabel == "left" || 150 | withLabel == "center" || 151 | withLabel == "right" ? 152 | [ num_x ] // single chamber equal to the bin length 153 | : [ for (i=[0:len(separator_positions)]) (i==len(separator_positions) ? num_x : separator_positions[i]) - (i==0 ? 0 : separator_positions[i-1]) ]; 154 | 155 | for (i=[0:len(chamberWidths)-1]) { 156 | chamberStart = i == 0 ? 0 : separator_positions[i-1]; 157 | chamberWidth = chamberWidths[i]; 158 | label_num_x = (labelWidth == 0 || labelWidth > chamberWidth) ? chamberWidth : labelWidth; 159 | label_pos_x = (withLabel == "center" || withLabel == "centerchamber" )? (chamberWidth - label_num_x) / 2 160 | : (withLabel == "right" || withLabel == "rightchamber" )? chamberWidth - label_num_x 161 | : 0 ; 162 | 163 | hull() for (i=[0,1, 2]) 164 | translate([(-gridfinity_pitch/2) + ((chamberStart + label_pos_x) * gridfinity_pitch), yz[i][0], yz[i][1]]) 165 | rotate([0, 90, 0]) 166 | union(){ 167 | tz(abs(label_num_x)*gridfinity_pitch) 168 | sphere(d=bar_d, $fn=24); 169 | sphere(d=bar_d, $fn=24); 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | 177 | module basic_cavity(num_x, num_y, num_z, fingerslide=default_fingerslide, 178 | magnet_diameter=default_magnet_diameter, screw_depth=default_screw_depth, 179 | floor_thickness=default_floor_thickness, wall_thickness=default_wall_thickness, 180 | efficient_floor=default_efficient_floor, lip_style=default_lip_style) { 181 | eps = 0.1; 182 | // I couldn't think of a good name for this ('q') but effectively it's the 183 | // size of the overhang that produces a wall thickness that's less than the lip 184 | // arount the top inside edge. 185 | q = 1.65-wall_thickness+0.95; // default 1.65 corresponds to wall thickness of 0.95 186 | q2 = 0.1; 187 | inner_lip_ht = 1.2; 188 | part_ht = 5; // height of partition between cells 189 | // the Z height of the bottom of the inside edge of the standard lip 190 | zpoint = max(part_ht+floor_thickness, gridfinity_zpitch*num_z-inner_lip_ht); 191 | facets = 13; 192 | mag_ht = magnet_diameter > 0 ? 2.4: 0; 193 | m3_ht = screw_depth; 194 | efloor = efficient_floor && magnet_diameter == 0 && screw_depth == 0 && !fingerslide; 195 | seventeen = gridfinity_pitch/2-4; 196 | 197 | floorht = max(mag_ht, m3_ht, part_ht) + floor_thickness; 198 | 199 | // replace "normal" with "reduced" if z-height is less than 1.8 200 | lip_style2 = (num_z < 1.8 && lip_style == "normal") ? "reduced" : lip_style; 201 | // replace "reduced" with "none" if z-height is less than 1.1 202 | lip_style3 = (num_z < 1.2 && lip_style2 == "reduced") ? "none" : lip_style2; 203 | 204 | difference() { 205 | union() { 206 | // cut out inside edge of standard lip 207 | hull() cornercopy(seventeen, num_x, num_y) { 208 | tz(zpoint-eps) cylinder(d=2.3, h=inner_lip_ht+2*eps, $fn=24); // lip 209 | } 210 | 211 | hull() cornercopy(seventeen, num_x, num_y) { 212 | // create bevels below the lip 213 | if (lip_style3 == "reduced") { 214 | tz(zpoint+1.8) cylinder(d=3.7, h=0.1, $fn=32); // transition from lip (where top of lip would be) ... 215 | // radius increases by (2.3+2*q-3.7)/2 = q-1.4/2 = q-0.7 216 | tz(zpoint-(q-0.7)+1.9-q2) cylinder(d=2.3+2*q, h=q2, $fn=32); // ... to top of thin wall ... 217 | } 218 | else if (lip_style3 == "none") { 219 | tz(zpoint) cylinder(d=2.3+2*q, h=6, $fn=32); // remove entire lip 220 | } 221 | else { // normal 222 | tz(zpoint-0.1) cylinder(d=2.3, h=0.1, $fn=24); // transition from lip ... 223 | tz(zpoint-q-q2) cylinder(d=2.3+2*q, h=q2, $fn=32); // ... to top of thin wall ... 224 | } 225 | // create rounded bottom of bowl (8.5 is high enough to not expose gaps) 226 | tz(2.3/2+q+floorht) sphere(d=2.3+2*q, $fn=32); // .. to bottom of thin wall and floor 227 | tz(2.3/2+q+floorht) mirror([0, 0, 1]) cylinder(d1=2.3+2*q, d2=0, h=1.15+q, $fn=32); 228 | } 229 | } 230 | 231 | // cut away from the negative to leave behind wall to make it easier to remove piece 232 | pivot_z = 13.6-0.45+floorht-5+seventeen-17; 233 | pivot_y = -10; 234 | 235 | // rounded inside bottom 236 | if(fingerslide){ 237 | for (ai=[0:facets-1]) 238 | // normal slide position is -seventeen-1.15 which is the edge of the inner lip 239 | // reduced slide position is -seventeen-1.85 which is the edge of the upper lip 240 | // no lip means we need -gridfinity_pitch/2+1.5+0.25+wall_thickness ? 241 | translate([0, ( 242 | lip_style3 == "reduced" ? -0.7 243 | : (lip_style3=="none" ? seventeen+1.15-gridfinity_pitch/2+0.25+wall_thickness 244 | : 0 245 | ) ), 0]) 246 | translate([0, pivot_y, pivot_z]) 247 | rotate([90*ai/(facets-1), 0, 0]) 248 | translate([0, -pivot_y, -pivot_z]) 249 | translate([-gridfinity_pitch/2, -10-seventeen-1.15, 0]) 250 | cube([gridfinity_pitch*num_x, 10, gridfinity_zpitch*num_z+5]); 251 | } 252 | } 253 | 254 | // cut away side lips if num_x is less than 1 255 | if (num_x < 1) { 256 | hull() for (x=[-gridfinity_pitch/2+1.5+0.25+wall_thickness, -gridfinity_pitch/2+num_x*gridfinity_pitch-1.5-0.25-wall_thickness]) 257 | for (y=[-10, (num_y-0.5)*gridfinity_pitch-seventeen]) 258 | translate([x, y, (floorht+7*num_z)/2]) 259 | cylinder(d=3, h=7*num_z, $fn=24); 260 | } 261 | 262 | if (efloor) { 263 | if (num_x < 1) { 264 | gridcopy(1, num_y) { 265 | tz(floor_thickness) intersection() { 266 | hull() cornercopy(seventeen-0.5) cylinder(r=1, h=5, $fn=32); 267 | translate([gridfinity_pitch*(-1+num_x), 0, 0]) hull() cornercopy(seventeen-0.5) cylinder(r=1, h=5, $fn=32); 268 | } 269 | 270 | // tapered top portion 271 | intersection() { 272 | hull() { 273 | tz(3) cornercopy(seventeen-0.5) cylinder(r=1, h=1, $fn=32); 274 | tz(5) cornercopy(seventeen+2.5-1.15-q) cylinder(r=1.15+q, h=4, $fn=32); 275 | } 276 | translate([gridfinity_pitch*(-1+num_x), 0, 0]) hull() { 277 | tz(3) cornercopy(seventeen-0.5) cylinder(r=1, h=1, $fn=32); 278 | tz(5) cornercopy(seventeen+2.5-1.15-q) cylinder(r=1.15+q, h=4, $fn=32); 279 | } 280 | } 281 | } 282 | } 283 | else { 284 | // establishes floor 285 | gridcopy(num_x, num_y) hull() tz(floor_thickness) cornercopy(seventeen-0.5) cylinder(r=1, h=5, $fn=32); 286 | 287 | // tapered top portion 288 | gridcopy(num_x, num_y) hull() { 289 | tz(3) cornercopy(seventeen-0.5) cylinder(r=1, h=1, $fn=32); 290 | tz(5-(+2.5-1.15-q)) cornercopy(seventeen) cylinder(r=1.15+q, h=4, $fn=32); 291 | } 292 | } 293 | } 294 | } 295 | 296 | 297 | module tz(z) { 298 | translate([0, 0, z]) children(); 299 | } --------------------------------------------------------------------------------