diff --git a/README.md b/README.md index b8fbfc7..292142d 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ The printable holes allow your slicer to bridge the gap inside the countersunk m ## Recommendations For best results, use a version of OpenSCAD with the fast-csg feature. As of writing, this feature is only implemented in the [development snapshots](https://openscad.org/downloads.html). To enable the feature, go to Edit > Preferences > Features > fast-csg. On my computer, this sped up rendering from 10 minutes down to a couple of seconds, even for comically large bins. +## External libraries + +- `threads-scad` (https://github.com/rcolyer/threads-scad) is used for creating threaded holes, and is included in this project under `external/threads-scad/threads.scad`. + ## Enjoy! []() diff --git a/external/threads-scad/threads.scad b/external/threads-scad/threads.scad new file mode 100644 index 0000000..344c51a --- /dev/null +++ b/external/threads-scad/threads.scad @@ -0,0 +1,631 @@ +// Created 2016-2017 by Ryan A. Colyer. +// This work is released with CC0 into the public domain. +// https://creativecommons.org/publicdomain/zero/1.0/ +// +// https://www.thingiverse.com/thing:1686322 +// +// v2.1 + + +screw_resolution = 0.2; // in mm + + +// Provides standard metric thread pitches. +function ThreadPitch(diameter) = + (diameter <= 64) ? + lookup(diameter, [ + [2, 0.4], + [2.5, 0.45], + [3, 0.5], + [4, 0.7], + [5, 0.8], + [6, 1.0], + [7, 1.0], + [8, 1.25], + [10, 1.5], + [12, 1.75], + [14, 2.0], + [16, 2.0], + [18, 2.5], + [20, 2.5], + [22, 2.5], + [24, 3.0], + [27, 3.0], + [30, 3.5], + [33, 3.5], + [36, 4.0], + [39, 4.0], + [42, 4.5], + [48, 5.0], + [52, 5.0], + [56, 5.5], + [60, 5.5], + [64, 6.0] + ]) : + diameter * 6.0 / 64; + + +// Provides standard metric hex head widths across the flats. +function HexAcrossFlats(diameter) = + (diameter <= 64) ? + lookup(diameter, [ + [2, 4], + [2.5, 5], + [3, 5.5], + [3.5, 6], + [4, 7], + [5, 8], + [6, 10], + [7, 11], + [8, 13], + [10, 16], + [12, 18], + [14, 21], + [16, 24], + [18, 27], + [20, 30], + [22, 34], + [24, 36], + [27, 41], + [30, 46], + [33, 50], + [36, 55], + [39, 60], + [42, 65], + [48, 75], + [52, 80], + [56, 85], + [60, 90], + [64, 95] + ]) : + diameter * 95 / 64; + +// Provides standard metric hex head widths across the corners. +function HexAcrossCorners(diameter) = + HexAcrossFlats(diameter) / cos(30); + + +// Provides standard metric hex (Allen) drive widths across the flats. +function HexDriveAcrossFlats(diameter) = + (diameter <= 64) ? + lookup(diameter, [ + [2, 1.5], + [2.5, 2], + [3, 2.5], + [3.5, 3], + [4, 3], + [5, 4], + [6, 5], + [7, 5], + [8, 6], + [10, 8], + [12, 10], + [14, 12], + [16, 14], + [18, 15], + [20, 17], + [22, 18], + [24, 19], + [27, 20], + [30, 22], + [33, 24], + [36, 27], + [39, 30], + [42, 32], + [48, 36], + [52, 36], + [56, 41], + [60, 42], + [64, 46] + ]) : + diameter * 46 / 64; + +// Provides standard metric hex (Allen) drive widths across the corners. +function HexDriveAcrossCorners(diameter) = + HexDriveAcrossFlats(diameter) / cos(30); + +// Provides metric countersunk hex (Allen) drive widths across the flats. +function CountersunkDriveAcrossFlats(diameter) = + (diameter <= 14) ? + HexDriveAcrossFlats(HexDriveAcrossFlats(diameter)) : + round(0.6*diameter); + +// Provides metric countersunk hex (Allen) drive widths across the corners. +function CountersunkDriveAcrossCorners(diameter) = + CountersunkDriveAcrossFlats(diameter) / cos(30); + +// Provides standard metric nut thickness. +function NutThickness(diameter) = + (diameter <= 64) ? + lookup(diameter, [ + [2, 1.6], + [2.5, 2], + [3, 2.4], + [3.5, 2.8], + [4, 3.2], + [5, 4.7], + [6, 5.2], + [7, 6.0], + [8, 6.8], + [10, 8.4], + [12, 10.8], + [14, 12.8], + [16, 14.8], + [18, 15.8], + [20, 18.0], + [22, 21.1], + [24, 21.5], + [27, 23.8], + [30, 25.6], + [33, 28.7], + [36, 31.0], + [42, 34], + [48, 38], + [56, 45], + [64, 51] + ]) : + diameter * 51 / 64; + + +// This generates a closed polyhedron from an array of arrays of points, +// with each inner array tracing out one loop outlining the polyhedron. +// pointarrays should contain an array of N arrays each of size P outlining a +// closed manifold. The points must obey the right-hand rule. For example, +// looking down, the P points in the inner arrays are counter-clockwise in a +// loop, while the N point arrays increase in height. Points in each inner +// array do not need to be equal height, but they usually should not meet or +// cross the line segments from the adjacent points in the other arrays. +// (N>=2, P>=3) +// Core triangles: +// [j][i], [j+1][i], [j+1][(i+1)%P] +// [j][i], [j+1][(i+1)%P], [j][(i+1)%P] +// Then triangles are formed in a loop with the middle point of the first +// and last array. +module ClosePoints(pointarrays) { + function recurse_avg(arr, n=0, p=[0,0,0]) = (n>=len(arr)) ? p : + recurse_avg(arr, n+1, p+(arr[n]-p)/(n+1)); + + N = len(pointarrays); + P = len(pointarrays[0]); + NP = N*P; + lastarr = pointarrays[N-1]; + midbot = recurse_avg(pointarrays[0]); + midtop = recurse_avg(pointarrays[N-1]); + + faces_bot = [ + for (i=[0:P-1]) + [0,i+1,1+(i+1)%len(pointarrays[0])] + ]; + + loop_offset = 1; + bot_len = loop_offset + P; + + faces_loop = [ + for (j=[0:N-2], i=[0:P-1], t=[0:1]) + [loop_offset, loop_offset, loop_offset] + (t==0 ? + [j*P+i, (j+1)*P+i, (j+1)*P+(i+1)%P] : + [j*P+i, (j+1)*P+(i+1)%P, j*P+(i+1)%P]) + ]; + + top_offset = loop_offset + NP - P; + midtop_offset = top_offset + P; + + faces_top = [ + for (i=[0:P-1]) + [midtop_offset,top_offset+(i+1)%P,top_offset+i] + ]; + + points = [ + for (i=[-1:NP]) + (i<0) ? midbot : + ((i==NP) ? midtop : + pointarrays[floor(i/P)][i%P]) + ]; + faces = concat(faces_bot, faces_loop, faces_top); + + polyhedron(points=points, faces=faces); +} + + + +// This creates a vertical rod at the origin with external threads. It uses +// metric standards by default. +module ScrewThread(outer_diam, height, pitch=0, tooth_angle=30, tolerance=0.4, tip_height=0, tooth_height=0, tip_min_fract=0) { + + pitch = (pitch==0) ? ThreadPitch(outer_diam) : pitch; + tooth_height = (tooth_height==0) ? pitch : tooth_height; + tip_min_fract = (tip_min_fract<0) ? 0 : + ((tip_min_fract>0.9999) ? 0.9999 : tip_min_fract); + + outer_diam_cor = outer_diam + 0.25*tolerance; // Plastic shrinkage correction + inner_diam = outer_diam - tooth_height/tan(tooth_angle); + or = (outer_diam_cor < screw_resolution) ? + screw_resolution/2 : outer_diam_cor / 2; + ir = (inner_diam < screw_resolution) ? screw_resolution/2 : inner_diam / 2; + height = (height < screw_resolution) ? screw_resolution : height; + + steps_per_loop_try = ceil(2*3.14159265359*or / screw_resolution); + steps_per_loop = (steps_per_loop_try < 4) ? 4 : steps_per_loop_try; + hs_ext = 3; + hsteps = ceil(3 * height / pitch) + 2*hs_ext; + + extent = or - ir; + + tip_start = height-tip_height; + tip_height_sc = tip_height / (1-tip_min_fract); + + tip_height_ir = (tip_height_sc > tooth_height/2) ? + tip_height_sc - tooth_height/2 : tip_height_sc; + + tip_height_w = (tip_height_sc > tooth_height) ? tooth_height : tip_height_sc; + tip_wstart = height + tip_height_sc - tip_height - tip_height_w; + + + function tooth_width(a, h, pitch, tooth_height, extent) = + let( + ang_full = h*360.0/pitch-a, + ang_pn = atan2(sin(ang_full), cos(ang_full)), + ang = ang_pn < 0 ? ang_pn+360 : ang_pn, + frac = ang/360, + tfrac_half = tooth_height / (2*pitch), + tfrac_cut = 2*tfrac_half + ) + (frac > tfrac_cut) ? 0 : ( + (frac <= tfrac_half) ? + ((frac / tfrac_half) * extent) : + ((1 - (frac - tfrac_half)/tfrac_half) * extent) + ); + + + pointarrays = [ + for (hs=[0:hsteps]) + [ + for (s=[0:steps_per_loop-1]) + let( + ang_full = s*360.0/steps_per_loop, + ang_pn = atan2(sin(ang_full), cos(ang_full)), + ang = ang_pn < 0 ? ang_pn+360 : ang_pn, + + h_fudge = pitch*0.001, + + h_mod = + (hs%3 == 2) ? + ((s == steps_per_loop-1) ? tooth_height - h_fudge : ( + (s == steps_per_loop-2) ? tooth_height/2 : 0)) : ( + (hs%3 == 0) ? + ((s == steps_per_loop-1) ? pitch-tooth_height/2 : ( + (s == steps_per_loop-2) ? pitch-tooth_height + h_fudge : 0)) : + ((s == steps_per_loop-1) ? pitch-tooth_height/2 + h_fudge : ( + (s == steps_per_loop-2) ? tooth_height/2 : 0)) + ), + + h_level = + (hs%3 == 2) ? tooth_height - h_fudge : ( + (hs%3 == 0) ? 0 : tooth_height/2), + + h_ub = floor((hs-hs_ext)/3) * pitch + + h_level + ang*pitch/360.0 - h_mod, + h_max = height - (hsteps-hs) * h_fudge, + h_min = hs * h_fudge, + h = (h_ub < h_min) ? h_min : ((h_ub > h_max) ? h_max : h_ub), + + ht = h - tip_start, + hf_ir = ht/tip_height_ir, + ht_w = h - tip_wstart, + hf_w_t = ht_w/tip_height_w, + hf_w = (hf_w_t < 0) ? 0 : ((hf_w_t > 1) ? 1 : hf_w_t), + + ext_tip = (h <= tip_wstart) ? extent : (1-hf_w) * extent, + wnormal = tooth_width(ang, h, pitch, tooth_height, ext_tip), + w = (h <= tip_wstart) ? wnormal : + (1-hf_w) * wnormal + + hf_w * (0.1*screw_resolution + (wnormal * wnormal * wnormal / + (ext_tip*ext_tip+0.1*screw_resolution))), + r = (ht <= 0) ? ir + w : + ( (ht < tip_height_ir ? ((2/(1+(hf_ir*hf_ir))-1) * ir) : 0) + w) + ) + [r*cos(ang), r*sin(ang), h] + ] + ]; + + + ClosePoints(pointarrays); +} + + +// This creates a vertical rod at the origin with external auger-style +// threads. +module AugerThread(outer_diam, inner_diam, height, pitch, tooth_angle=30, tolerance=0.4, tip_height=0, tip_min_fract=0) { + tooth_height = tan(tooth_angle)*(outer_diam-inner_diam); + ScrewThread(outer_diam, height, pitch, tooth_angle, tolerance, tip_height, + tooth_height, tip_min_fract); +} + + +// This creates a threaded hole in its children using metric standards by +// default. +module ScrewHole(outer_diam, height, position=[0,0,0], rotation=[0,0,0], pitch=0, tooth_angle=30, tolerance=0.4, tooth_height=0) { + extra_height = 0.001 * height; + + difference() { + children(); + translate(position) + rotate(rotation) + translate([0, 0, -extra_height/2]) + ScrewThread(1.01*outer_diam + 1.25*tolerance, height + extra_height, + pitch, tooth_angle, tolerance, tooth_height=tooth_height); + } +} + + +// This creates an auger-style threaded hole in its children. +module AugerHole(outer_diam, inner_diam, height, pitch, position=[0,0,0], rotation=[0,0,0], tooth_angle=30, tolerance=0.4) { + tooth_height = tan(tooth_angle)*(outer_diam-inner_diam); + ScrewHole(outer_diam, height, position, rotation, pitch, tooth_angle, + tolerance, tooth_height=tooth_height) children(); +} + + +// This inserts a ClearanceHole in its children. +// The rotation vector is applied first, then the position translation, +// starting from a position upward from the z-axis at z=0. +module ClearanceHole(diameter, height, position=[0,0,0], rotation=[0,0,0], tolerance=0.4) { + extra_height = 0.001 * height; + + difference() { + children(); + translate(position) + rotate(rotation) + translate([0, 0, -extra_height/2]) + cylinder(h=height + extra_height, r=(diameter/2+tolerance)); + } +} + + +// This inserts a ClearanceHole with a recessed bolt hole in its children. +// The rotation vector is applied first, then the position translation, +// starting from a position upward from the z-axis at z=0. The default +// recessed parameters fit a standard metric bolt. +module RecessedClearanceHole(diameter, height, position=[0,0,0], rotation=[0,0,0], recessed_diam=-1, recessed_height=-1, tolerance=0.4) { + recessed_diam = (recessed_diam < 0) ? + HexAcrossCorners(diameter) : recessed_diam; + recessed_height = (recessed_height < 0) ? diameter : recessed_height; + extra_height = 0.001 * height; + + difference() { + children(); + translate(position) + rotate(rotation) + translate([0, 0, -extra_height/2]) + cylinder(h=height + extra_height, r=(diameter/2+tolerance)); + translate(position) + rotate(rotation) + translate([0, 0, -extra_height/2]) + cylinder(h=recessed_height + extra_height/2, + r=(recessed_diam/2+tolerance)); + } +} + + +// This inserts a countersunk ClearanceHole in its children. +// The rotation vector is applied first, then the position translation, +// starting from a position upward from the z-axis at z=0. +// The countersunk side is on the bottom by default. +module CountersunkClearanceHole(diameter, height, position=[0,0,0], rotation=[0,0,0], sinkdiam=0, sinkangle=45, tolerance=0.4) { + extra_height = 0.001 * height; + sinkdiam = (sinkdiam==0) ? 2*diameter : sinkdiam; + sinkheight = ((sinkdiam-diameter)/2)/tan(sinkangle); + + difference() { + children(); + translate(position) + rotate(rotation) + translate([0, 0, -extra_height/2]) + union() { + cylinder(h=height + extra_height, r=(diameter/2+tolerance)); + cylinder(h=sinkheight + extra_height, r1=(sinkdiam/2+tolerance), r2=(diameter/2+tolerance), $fn=24*diameter); + } + } +} + + +// This inserts a Phillips tip shaped hole into its children. +// The rotation vector is applied first, then the position translation, +// starting from a position upward from the z-axis at z=0. +module PhillipsTip(width=7, thickness=0, straightdepth=0, position=[0,0,0], rotation=[0,0,0]) { + thickness = (thickness <= 0) ? width*2.5/7 : thickness; + straightdepth = (straightdepth <= 0) ? width*3.5/7 : straightdepth; + angledepth = (width-thickness)/2; + height = straightdepth + angledepth; + extra_height = 0.001 * height; + + difference() { + children(); + translate(position) + rotate(rotation) + union() { + hull() { + translate([-width/2, -thickness/2, -extra_height/2]) + cube([width, thickness, straightdepth+extra_height]); + translate([-thickness/2, -thickness/2, height-extra_height]) + cube([thickness, thickness, extra_height]); + } + hull() { + translate([-thickness/2, -width/2, -extra_height/2]) + cube([thickness, width, straightdepth+extra_height]); + translate([-thickness/2, -thickness/2, height-extra_height]) + cube([thickness, thickness, extra_height]); + } + } + } +} + + + +// Create a standard sized metric bolt with hex head and hex key. +module MetricBolt(diameter, length, tolerance=0.4) { + drive_tolerance = pow(3*tolerance/HexDriveAcrossCorners(diameter),2) + + 0.75*tolerance; + + difference() { + cylinder(h=diameter, r=(HexAcrossCorners(diameter)/2-0.5*tolerance), $fn=6); + cylinder(h=diameter, + r=(HexDriveAcrossCorners(diameter)+drive_tolerance)/2, $fn=6, + center=true); + } + translate([0,0,diameter-0.01]) + ScrewThread(diameter, length+0.01, tolerance=tolerance, + tip_height=ThreadPitch(diameter), tip_min_fract=0.75); +} + + +// Create a standard sized metric countersunk (flat) bolt with hex key drive. +// In compliance with convention, the length for this includes the head. +module MetricCountersunkBolt(diameter, length, tolerance=0.4) { + drive_tolerance = pow(3*tolerance/CountersunkDriveAcrossCorners(diameter),2) + + 0.75*tolerance; + + difference() { + cylinder(h=diameter/2, r1=diameter, r2=diameter/2, $fn=24*diameter); + cylinder(h=0.8*diameter, + r=(CountersunkDriveAcrossCorners(diameter)+drive_tolerance)/2, $fn=6, + center=true); + } + translate([0,0,diameter/2-0.01]) + ScrewThread(diameter, length-diameter/2+0.01, tolerance=tolerance, + tip_height=ThreadPitch(diameter), tip_min_fract=0.75); +} + + +// Create a standard sized metric countersunk (flat) bolt with hex key drive. +// In compliance with convention, the length for this includes the head. +module MetricWoodScrew(diameter, length, tolerance=0.4) { + drive_tolerance = pow(3*tolerance/CountersunkDriveAcrossCorners(diameter),2) + + 0.75*tolerance; + + PhillipsTip(diameter-2) + union() { + cylinder(h=diameter/2, r1=diameter, r2=diameter/2, $fn=24*diameter); + + translate([0,0,diameter/2-0.01]) + ScrewThread(diameter, length-diameter/2+0.01, tolerance=tolerance, + tip_height=diameter); + } +} + + +// Create a standard sized metric hex nut. +module MetricNut(diameter, thickness=0, tolerance=0.4) { + thickness = (thickness==0) ? NutThickness(diameter) : thickness; + ScrewHole(diameter, thickness, tolerance=tolerance) + cylinder(h=thickness, r=HexAcrossCorners(diameter)/2-0.5*tolerance, $fn=6); +} + + +// Create a convenient washer size for a metric nominal thread diameter. +module MetricWasher(diameter) { + difference() { + cylinder(h=diameter/5, r=1.15*diameter, $fn=24*diameter); + cylinder(h=2*diameter, r=0.575*diameter, $fn=12*diameter, center=true); + } +} + + +// Solid rod on the bottom, external threads on the top. +module RodStart(diameter, height, thread_len=0, thread_diam=0, thread_pitch=0) { + // A reasonable default. + thread_diam = (thread_diam==0) ? 0.75*diameter : thread_diam; + thread_len = (thread_len==0) ? 0.5*diameter : thread_len; + thread_pitch = (thread_pitch==0) ? ThreadPitch(thread_diam) : thread_pitch; + + cylinder(r=diameter/2, h=height, $fn=24*diameter); + + translate([0, 0, height]) + ScrewThread(thread_diam, thread_len, thread_pitch, + tip_height=thread_pitch, tip_min_fract=0.75); +} + + +// Solid rod on the bottom, internal threads on the top. +// Flips around x-axis after printing to pair with RodStart. +module RodEnd(diameter, height, thread_len=0, thread_diam=0, thread_pitch=0) { + // A reasonable default. + thread_diam = (thread_diam==0) ? 0.75*diameter : thread_diam; + thread_len = (thread_len==0) ? 0.5*diameter : thread_len; + thread_pitch = (thread_pitch==0) ? ThreadPitch(thread_diam) : thread_pitch; + + ScrewHole(thread_diam, thread_len, [0, 0, height], [180,0,0], thread_pitch) + cylinder(r=diameter/2, h=height, $fn=24*diameter); +} + + +// Internal threads on the bottom, external threads on the top. +module RodExtender(diameter, height, thread_len=0, thread_diam=0, thread_pitch=0) { + // A reasonable default. + thread_diam = (thread_diam==0) ? 0.75*diameter : thread_diam; + thread_len = (thread_len==0) ? 0.5*diameter : thread_len; + thread_pitch = (thread_pitch==0) ? ThreadPitch(thread_diam) : thread_pitch; + + max_bridge = height - thread_len; + // Use 60 degree slope if it will fit. + bridge_height = ((thread_diam/4) < max_bridge) ? thread_diam/4 : max_bridge; + + difference() { + union() { + ScrewHole(thread_diam, thread_len, pitch=thread_pitch) + cylinder(r=diameter/2, h=height, $fn=24*diameter); + + translate([0,0,height]) + ScrewThread(thread_diam, thread_len, pitch=thread_pitch, + tip_height=thread_pitch, tip_min_fract=0.75); + } + // Carve out a small conical area as a bridge. + translate([0,0,thread_len]) + cylinder(h=bridge_height, r1=thread_diam/2, r2=0.1); + } +} + + +// Produces a matching set of metric bolts, nuts, and washers. +module MetricBoltSet(diameter, length, quantity=1) { + for (i=[0:quantity-1]) { + translate([0, i*4*diameter, 0]) MetricBolt(diameter, length); + translate([4*diameter, i*4*diameter, 0]) MetricNut(diameter); + translate([8*diameter, i*4*diameter, 0]) MetricWasher(diameter); + } +} + + +module Demo() { + translate([0,-0,0]) MetricBoltSet(3, 8); + translate([0,-20,0]) MetricBoltSet(4, 8); + translate([0,-40,0]) MetricBoltSet(5, 8); + translate([0,-60,0]) MetricBoltSet(6, 8); + translate([0,-80,0]) MetricBoltSet(8, 8); + + translate([0,25,0]) MetricCountersunkBolt(5, 10); + translate([23,18,5]) + scale([1,1,-1]) + CountersunkClearanceHole(5, 8, [7,7,0], [0,0,0]) + cube([14, 14, 5]); + + translate([70, -10, 0]) + RodStart(20, 30); + translate([70, 20, 0]) + RodEnd(20, 30); + + translate([70, -45, 0]) + MetricWoodScrew(8, 20); + + translate([12, 50, 0]) + union() { + translate([0, 0, 5.99]) + AugerThread(15, 3.5, 22, 7, tooth_angle=15, tip_height=7); + translate([-4, -9, 0]) cube([8, 18, 6]); + } +} + + +Demo(); + +//MetricBoltSet(6, 8, 10); diff --git a/gridfinity-rebuilt-bins.scad b/gridfinity-rebuilt-bins.scad index 3b36dba..6a3aa9b 100644 --- a/gridfinity-rebuilt-bins.scad +++ b/gridfinity-rebuilt-bins.scad @@ -95,6 +95,8 @@ crush_ribs = true; chamfer_holes = true; // Magnet/Screw holes will be printed so supports are not needed. printable_hole_top = true; +// Enable "gridfinity-refined" thumbscrew hole in the center of each base: https://www.printables.com/model/413761-gridfinity-refined +enable_thumbscrew = false; hole_options = bundle_hole_options(refined_holes, magnet_holes, screw_holes, crush_ribs, chamfer_holes, printable_hole_top); @@ -112,7 +114,7 @@ gridfinityInit(gridx, gridy, height(gridz, gridz_define, style_lip, enable_zsnap cutCylinders(n_divx=cdivx, n_divy=cdivy, cylinder_diameter=cd, cylinder_height=ch, coutout_depth=c_depth, orientation=c_orientation, chamfer=c_chamfer); } } -gridfinityBase(gridx, gridy, l_grid, div_base_x, div_base_y, hole_options, only_corners=only_corners); +gridfinityBase(gridx, gridy, l_grid, div_base_x, div_base_y, hole_options, only_corners=only_corners, thumbscrew=enable_thumbscrew); } diff --git a/gridfinity-rebuilt-utility.scad b/gridfinity-rebuilt-utility.scad index 9130e30..ae826be 100644 --- a/gridfinity-rebuilt-utility.scad +++ b/gridfinity-rebuilt-utility.scad @@ -7,6 +7,7 @@ include use use +use // ===== User Modules ===== // @@ -202,8 +203,9 @@ module cut_move(x, y, w, h) { /** *@summary Create the base of a gridfinity bin, or use it for a custom object. * @param length X,Y size of a single Gridfinity base. + * @param thumbscrew Enable "gridfinity-refined" thumbscrew hole in the center of each base unit. This is a ISO Metric Profile, 15.0mm size, M15x1.5 designation. */ -module gridfinityBase(gx, gy, length, dx, dy, hole_options=bundle_hole_options(), off=0, final_cut=true, only_corners=false) { +module gridfinityBase(gx, gy, length, dx, dy, hole_options=bundle_hole_options(), off=0, final_cut=true, only_corners=false, thumbscrew=false) { assert( is_num(gx) && is_num(gy) && @@ -211,7 +213,8 @@ module gridfinityBase(gx, gy, length, dx, dy, hole_options=bundle_hole_options() is_num(dx) && is_num(dy) && is_bool(final_cut) && - is_bool(only_corners) + is_bool(only_corners) && + is_bool(thumbscrew) ); dbnxt = [for (i=[1:5]) if (abs(gx*i)%1 < 0.001 || abs(gx*i)%1 > 0.999) i]; @@ -242,7 +245,7 @@ module gridfinityBase(gx, gy, length, dx, dy, hole_options=bundle_hole_options() if(only_corners) { difference(){ pattern_linear(grid_size.x, grid_size.y, base_center_distance_mm.x, base_center_distance_mm.y) - block_base(bundle_hole_options(), 0, individual_base_size_mm); + block_base(bundle_hole_options(), 0, individual_base_size_mm, thumbscrew=thumbscrew); copy_mirror([0, 1, 0]) { copy_mirror([1, 0, 0]) { @@ -258,7 +261,7 @@ module gridfinityBase(gx, gy, length, dx, dy, hole_options=bundle_hole_options() } else { pattern_linear(grid_size.x, grid_size.y, base_center_distance_mm.x, base_center_distance_mm.y) - block_base(hole_options, off, individual_base_size_mm); + block_base(hole_options, off, individual_base_size_mm, thumbscrew=thumbscrew); } } @@ -267,9 +270,14 @@ module gridfinityBase(gx, gy, length, dx, dy, hole_options=bundle_hole_options() * @param hole_options @see block_base_hole.hole_options * @param off * @param size [x, y] size of a single base. Only set if deviating from the standard! + * @param thumbscrew Enable "gridfinity-refined" thumbscrew hole in the center of each base unit. This is a ISO Metric Profile, 15.0mm size, M15x1.5 designation. */ -module block_base(hole_options, off=0, size=[BASE_SIZE, BASE_SIZE]) { - assert(is_list(size) && len(size) == 2); +module block_base(hole_options, off=0, size=[BASE_SIZE, BASE_SIZE], thumbscrew=false) { + assert( + is_list(size) && + len(size) == 2 && + is_bool(thumbscrew) + ); // How far, in the +x direction, // the profile needs to be from it's [0, 0] point @@ -284,6 +292,13 @@ module block_base(hole_options, off=0, size=[BASE_SIZE, BASE_SIZE]) { str("Minimum size of a single base must be greater than ", outer_diameter) ); + thumbscrew_outerdiam = 15; + thumbscrew_height = 5; + thumbscrew_tolerance = 0.4; + thumbscrew_tooth_angle = 30; + thumbscrew_pitch = 1.5; + + render(convexity = 2) difference() { union() { @@ -302,6 +317,16 @@ module block_base(hole_options, off=0, size=[BASE_SIZE, BASE_SIZE]) { ); } + if (thumbscrew) { + ScrewThread( + 1.01 * thumbscrew_outerdiam + 1.25 * thumbscrew_tolerance, + thumbscrew_height, + thumbscrew_pitch, + thumbscrew_tooth_angle, + thumbscrew_tolerance, + tooth_height=0 + ); + } // 4 holes // Need this fancy code to support refined holes and non-square bases. for(a=[0:90:270]){