Compare commits
22 Commits
85a5a50c1d
...
d35b0069e8
Author | SHA1 | Date |
---|---|---|
Arthur Moore | d35b0069e8 | |
Arthur Moore | 05ea14acd4 | |
Arthur Moore | 32f91a77ca | |
Arthur Moore | f222c262fc | |
Arthur Moore | 8fa885ac72 | |
Arthur Moore | 2e0e9bd363 | |
Arthur Moore | a4200a04bd | |
Arthur Moore | 67ba594585 | |
Arthur Moore | 88ad9dbf6c | |
Ruud | 36345f3efb | |
Ruud | fc51ba875e | |
Ruud | cf1666fa2c | |
Arthur Moore | e7ef96bbcf | |
Arthur Moore | ff3a325b37 | |
Arthur Moore | 20492634d8 | |
Arthur Moore | 574d9dc6b1 | |
Arthur Moore | 637b98577f | |
Arthur Moore | 5b25e2e114 | |
Arthur Moore | 69cb64e98d | |
Arthur Moore | 1cf350121d | |
Tracy Ward | d9d92db544 | |
Arthur Moore | 015daff2e8 |
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* @file generic-helpers.scad
|
||||
* @brief Generic Helper Functions. Not gridfinity specific.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unity (no change) affine transformation matrix.
|
||||
* @details For use with multmatrix transforms.
|
||||
*/
|
||||
unity_matrix = [
|
||||
[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1]
|
||||
];
|
||||
|
||||
/**
|
||||
* @brief Get the magnitude of a 2d or 3d vector
|
||||
* @param vector A 2d or 3d vectorm
|
||||
* @returns Magnitude of the vector.
|
||||
*/
|
||||
function vector_magnitude(vector) =
|
||||
sqrt(vector.x^2 + vector.y^2 + (len(vector) == 3 ? vector.z^2 : 0));
|
||||
|
||||
/**
|
||||
* @brief Convert a 2d or 3d vector into a unit vector
|
||||
* @returns The unit vector. Where total magnitude is 1.
|
||||
*/
|
||||
function vector_as_unit(vector) = vector / vector_magnitude(vector);
|
||||
|
||||
/**
|
||||
* @brief Convert a 2d vector into an angle.
|
||||
* @details Just a wrapper around atan2.
|
||||
* @param A 2d vectorm
|
||||
* @returns Angle of the vector.
|
||||
*/
|
||||
function atanv(vector) = atan2(vector.y, vector.x);
|
||||
|
||||
function _affine_rotate_x(angle_x) = [
|
||||
[1, 0, 0, 0],
|
||||
[0, cos(angle_x), -sin(angle_x), 0],
|
||||
[0, sin(angle_x), cos(angle_x), 0],
|
||||
[0, 0, 0, 1]
|
||||
];
|
||||
|
||||
function _affine_rotate_y(angle_y) = [
|
||||
[cos(angle_y), 0, sin(angle_y), 0],
|
||||
[0, 1, 0, 0],
|
||||
[-sin(angle_y), 0, cos(angle_y), 0],
|
||||
[0, 0, 0, 1]
|
||||
];
|
||||
|
||||
function _affine_rotate_z(angle_z) = [
|
||||
[cos(angle_z), -sin(angle_z), 0, 0],
|
||||
[sin(angle_z), cos(angle_z), 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1]
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @brief Affine transformation matrix equivalent of `rotate`
|
||||
* @param angle_vector @see `rotate`
|
||||
* @details Equivalent to `rotate([0, angle, 0])`
|
||||
* @returns An affine transformation matrix for use with `multmatrix()`
|
||||
*/
|
||||
function affine_rotate(angle_vector) =
|
||||
_affine_rotate_z(angle_vector.z) * _affine_rotate_y(angle_vector.y) * _affine_rotate_x(angle_vector.x);
|
||||
|
||||
/**
|
||||
* @brief Affine transformation matrix equivalent of `translate`
|
||||
* @param vector @see `translate`
|
||||
* @returns An affine transformation matrix for use with `multmatrix()`
|
||||
*/
|
||||
function affine_translate(vector) = [
|
||||
[1, 0, 0, vector.x],
|
||||
[0, 1, 0, vector.y],
|
||||
[0, 0, 1, vector.z],
|
||||
[0, 0, 0, 1]
|
||||
];
|
||||
|
||||
/**
|
||||
* @brief Create a rectangle with rounded corners by sweeping a 2d object along a path.
|
||||
* Centered on origin.
|
||||
*/
|
||||
module sweep_rounded(width=10, length=10) {
|
||||
half_width = width/2;
|
||||
half_length = length/2;
|
||||
path_points = [
|
||||
[-half_width, half_length], //Start
|
||||
[half_width, half_length], // Over
|
||||
[half_width, -half_length], //Down
|
||||
[-half_width, -half_length], // Back over
|
||||
[-half_width, half_length] // Up to start
|
||||
];
|
||||
path_vectors = [
|
||||
path_points[1] - path_points[0],
|
||||
path_points[2] - path_points[1],
|
||||
path_points[3] - path_points[2],
|
||||
path_points[4] - path_points[3],
|
||||
];
|
||||
// These contain the translations, but not the rotations
|
||||
// OpenSCAD requires this hacky for loop to get accumulate to work!
|
||||
first_translation = affine_translate([path_points[0].y, 0,path_points[0].x]);
|
||||
affine_translations = concat([first_translation], [
|
||||
for (i = 0, a = first_translation;
|
||||
i < len(path_vectors);
|
||||
a=a * affine_translate([path_vectors[i].y, 0, path_vectors[i].x]), i=i+1)
|
||||
a * affine_translate([path_vectors[i].y, 0, path_vectors[i].x])
|
||||
]);
|
||||
|
||||
// Bring extrusion to the xy plane
|
||||
affine_matrix = affine_rotate([90, 0, 90]);
|
||||
|
||||
walls = [
|
||||
for (i = [0 : len(path_vectors) - 1])
|
||||
affine_matrix * affine_translations[i]
|
||||
* affine_rotate([0, atanv(path_vectors[i]), 0])
|
||||
];
|
||||
|
||||
union()
|
||||
{
|
||||
for (i = [0 : len(walls) - 1]){
|
||||
multmatrix(walls[i])
|
||||
linear_extrude(vector_magnitude(path_vectors[i]))
|
||||
children();
|
||||
|
||||
// Rounded Corners
|
||||
multmatrix(walls[i] * affine_rotate([-90, 0, 0]))
|
||||
rotate_extrude(angle = 90, convexity = 4)
|
||||
children();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -72,17 +72,30 @@ style_tab = 1; //[0:Full,1:Auto,2:Left,3:Center,4:Right,5:None]
|
|||
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 = 4; // [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;
|
||||
|
||||
/* [Base Hole Options] */
|
||||
// only cut magnet/screw holes at the corners of the bin to save uneccesary print time
|
||||
only_corners = false;
|
||||
//Use gridfinity refined hole style. Not compatible with magnet_holes!
|
||||
refined_hole = true;
|
||||
// Base will have holes for 6mm Diameter x 2mm high magnets.
|
||||
magnet_holes = false;
|
||||
// Base will have holes for M3 screws.
|
||||
screw_holes = false;
|
||||
// Magnet holes will have crush ribs to hold the magnet.
|
||||
crush_ribs = false;
|
||||
// Magnet holes will have a chamfer to ease insertion.
|
||||
chamfer_magnet_holes = false;
|
||||
// Allows printing screw holes with magnet holes without using supports.
|
||||
printable_magnet_hole_top = false;
|
||||
|
||||
hole_options = bundle_hole_options(refined_hole, magnet_holes, screw_holes, crush_ribs, chamfer_magnet_holes, printable_magnet_hole_top);
|
||||
|
||||
// ===== IMPLEMENTATION ===== //
|
||||
|
||||
|
@ -98,7 +111,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, style_hole, only_corners=only_corners);
|
||||
gridfinityBase(gridx, gridy, l_grid, div_base_x, div_base_y, hole_options, only_corners=only_corners);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
/**
|
||||
* @file gridfinity-rebuilt-holes.scad
|
||||
* @brief Functions to create different types of holes in an object.
|
||||
*/
|
||||
|
||||
include <standard.scad>
|
||||
use <generic-helpers.scad>
|
||||
|
||||
/**
|
||||
* @brief Wave generation function for wrapping a circle.
|
||||
* @param t An angle of the circle. Between 0 and 360 degrees.
|
||||
* @param count The number of **full** waves in a 360 degree circle.
|
||||
* @param range **Half** the difference between minimum and maximum values.
|
||||
* @param vertical_offset A simple offset.
|
||||
* @details
|
||||
* If plotted on an x/y graph this produces a standard sin wave.
|
||||
* Range only seems weird because it describes half a wave.
|
||||
* Mapped by doing [sin(t), cost(t)] * wave_function(...).
|
||||
* When wrapping a circle:
|
||||
* Final Outer radius is (wave_vertical_offset + wave_range).
|
||||
* Final Inner radius is (wave_vertical_offset - wave_range).
|
||||
*/
|
||||
function wave_function(t, count, range, vertical_offset) =
|
||||
(sin(t * count) * range) + vertical_offset;
|
||||
|
||||
/**
|
||||
* @brief A circle with crush ribs to give a tighter press fit.
|
||||
* @details Extrude and use as a negative modifier.
|
||||
* Idea based on Slant3D's video at 5:20 https://youtu.be/Bd7Yyn61XWQ?t=320
|
||||
* Implementaiton is completely different.
|
||||
* Important: Lower ribs numbers just result in a deformed circle.
|
||||
* @param outer_radius Final outer radius.
|
||||
* @param inner_radius Final inner radius.
|
||||
* @param ribs Number of crush ribs the circle has.
|
||||
**/
|
||||
module ribbed_circle(outer_radius, inner_radius, ribs) {
|
||||
assert(outer_radius > 0, "outer_radius must be positive");
|
||||
assert(inner_radius > 0, "inner_radius must be positive");
|
||||
assert(ribs > 0, "ribs must be positive");
|
||||
assert(outer_radius > inner_radius, "outer_radius must be larger than inner_radius");
|
||||
|
||||
wave_range = (outer_radius - inner_radius) / 2;
|
||||
wave_vertical_offset = inner_radius + wave_range;
|
||||
|
||||
// Circe with a wave wrapped around it
|
||||
wrapped_circle = [ for (i = [0:360])
|
||||
[sin(i), cos(i)] * wave_function(i, ribs, wave_range, wave_vertical_offset)
|
||||
];
|
||||
|
||||
polygon(wrapped_circle);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief A cylinder with crush ribs to give a tighter press fit.
|
||||
* @details To be used as the negative for a hole.
|
||||
* @see ribbed_circle
|
||||
* @param outer_radius Outer Radius of the crush ribs.
|
||||
* @param inner_radius Inner Radius of the crush ribs.
|
||||
* @param height Cylinder's height.
|
||||
* @param ribs Number of crush ribs.
|
||||
*/
|
||||
module ribbed_cylinder(outer_radius, inner_radius, height, ribs) {
|
||||
assert(height > 0, "height must be positive");
|
||||
linear_extrude(height)
|
||||
ribbed_circle(
|
||||
outer_radius,
|
||||
inner_radius,
|
||||
ribs
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Make a hole printable without suports.
|
||||
* @see https://www.youtube.com/watch?v=W8FbHTcB05w
|
||||
* @param inner_radius Radius of the inner hole.
|
||||
* @param outer_radius Radius of the outer hole.
|
||||
* @param outer_depth Depth of the magnet hole.
|
||||
* @details This is the negative designed to be cut out of the magnet hole.
|
||||
* Use it with `difference()`.
|
||||
*/
|
||||
module make_hole_printable(inner_radius, outer_radius, outer_depth) {
|
||||
assert(inner_radius > 0, "inner_radius must be positive");
|
||||
assert(outer_radius > 0, "outer_radius must be positive");
|
||||
assert(outer_depth > 2*LAYER_HEIGHT, str("outer_depth must be at least ", 2*LAYER_HEIGHT));
|
||||
tollerance = 0.001; // To make sure the top layer is fully removed
|
||||
|
||||
translation_matrix = affine_translate([
|
||||
-outer_radius,
|
||||
inner_radius,
|
||||
outer_depth - 2*LAYER_HEIGHT
|
||||
]);
|
||||
second_translation_matrix = translation_matrix * affine_translate([0, 0, LAYER_HEIGHT]);
|
||||
|
||||
cube_dimensions = [
|
||||
outer_radius*2,
|
||||
outer_radius - inner_radius,
|
||||
LAYER_HEIGHT + tollerance
|
||||
];
|
||||
|
||||
union(){
|
||||
union() {
|
||||
multmatrix(translation_matrix)
|
||||
cube(cube_dimensions);
|
||||
multmatrix(affine_rotate([0, 0, 180]) * translation_matrix)
|
||||
cube(cube_dimensions);
|
||||
}
|
||||
//2nd level
|
||||
union() {
|
||||
multmatrix(second_translation_matrix)
|
||||
cube(cube_dimensions);
|
||||
multmatrix(affine_rotate([0, 0, 90]) * second_translation_matrix)
|
||||
cube(cube_dimensions);
|
||||
multmatrix(affine_rotate([0, 0, 180]) * second_translation_matrix)
|
||||
cube(cube_dimensions);
|
||||
multmatrix(affine_rotate([0, 0, 270]) * second_translation_matrix)
|
||||
cube(cube_dimensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Refined hole based on Printables @grizzie17's Gridfinity Refined
|
||||
* @details Magnet is pushed in from +X direction, and held in by friction.
|
||||
* Small slit on the bottom allows removing the magnet.
|
||||
* @see https://www.printables.com/model/413761-gridfinity-refined
|
||||
*/
|
||||
module refined_hole() {
|
||||
refined_offset = LAYER_HEIGHT * REFINED_HOLE_BOTTOM_LAYERS;
|
||||
|
||||
// Poke through - For removing a magnet using a toothpick
|
||||
ptl = refined_offset + LAYER_HEIGHT; // Additional layer just in case
|
||||
poke_through_height = REFINED_HOLE_HEIGHT + ptl;
|
||||
poke_hole_radius = 2.5;
|
||||
magic_constant = 5.60;
|
||||
poke_hole_center = [-12.53 + magic_constant, 0, -ptl];
|
||||
|
||||
translate([0, 0, refined_offset])
|
||||
union() {
|
||||
// Magnet hole
|
||||
translate([0, -REFINED_HOLE_RADIUS, 0])
|
||||
cube([11, REFINED_HOLE_RADIUS*2, REFINED_HOLE_HEIGHT]);
|
||||
cylinder(REFINED_HOLE_HEIGHT, r=REFINED_HOLE_RADIUS);
|
||||
|
||||
// Poke hole
|
||||
translate([poke_hole_center.x, -poke_hole_radius/2, poke_hole_center.z])
|
||||
cube([10 - magic_constant, poke_hole_radius, poke_through_height]);
|
||||
translate(poke_hole_center)
|
||||
cylinder(poke_through_height, d=poke_hole_radius);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a cone given a radius and an angle.
|
||||
* @param bottom_radius Radius of the bottom of the cone.
|
||||
* @param angle Angle as measured from the bottom of the cone.
|
||||
* @param max_height Optional maximum height. Cone will be cut off if higher.
|
||||
*/
|
||||
module cone(bottom_radius, angle, max_height=0) {
|
||||
assert(bottom_radius > 0);
|
||||
assert(angle > 0 && angle <= 90);
|
||||
assert(max_height >=0);
|
||||
|
||||
height = tan(angle) * bottom_radius;
|
||||
if(max_height == 0 || height < max_height) {
|
||||
// Normal Cone
|
||||
cylinder(h = height, r1 = bottom_radius, r2 = 0, center = false);
|
||||
} else {
|
||||
top_angle = 90 - angle;
|
||||
top_radius = bottom_radius - tan(top_angle) * max_height;
|
||||
cylinder(h = max_height, r1 = bottom_radius, r2 = top_radius, center = false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create an options list used to configure bin holes.
|
||||
* @param refined_hole Use gridfinity refined hole type. Not compatible with "magnet_hole".
|
||||
* @param magnet_hole Create a hole for a 6mm magnet.
|
||||
* @param screw_hole Create a hole for a M3 screw.
|
||||
* @param crush_ribs If the magnet hole should have crush ribs for a press fit.
|
||||
* @param chamfer Add a chamfer to the magnet hole.
|
||||
* @param supportless If the magnet hole should be printed in such a way that the screw hole does not require supports.
|
||||
*/
|
||||
function bundle_hole_options(refined_hole=true, magnet_hole=false, screw_hole=false, crush_ribs=false, chamfer=false, supportless=false) =
|
||||
[refined_hole, magnet_hole, screw_hole, crush_ribs, chamfer, supportless];
|
||||
|
||||
/**
|
||||
* @brief A single magnet/screw hole. To be cut out of the base.
|
||||
* @details Supports multiple options that can be mixed and matched.
|
||||
* @pram hole_options @see bundle_hole_options
|
||||
* @param o Offset
|
||||
*/
|
||||
module block_base_hole(hole_options, o=0) {
|
||||
// Destructure the options
|
||||
refined_hole = hole_options[0];
|
||||
magnet_hole = hole_options[1];
|
||||
screw_hole = hole_options[2];
|
||||
crush_ribs = hole_options[3];
|
||||
chamfer = hole_options[4];
|
||||
supportless = hole_options[5];
|
||||
|
||||
// Validate said options
|
||||
if(refined_hole) {
|
||||
assert(!magnet_hole, "magnet_hole is not compatible with refined_hole");
|
||||
}
|
||||
|
||||
screw_radius = SCREW_HOLE_RADIUS - (o/2);
|
||||
magnet_radius = MAGNET_HOLE_RADIUS - (o/2);
|
||||
magnet_inner_radius = MAGNET_HOLE_CRUSH_RIB_INNER_RADIUS - (o/2);
|
||||
screw_depth = h_base-o;
|
||||
// If using supportless / printable mode, need to add two additional layers, so they can be removed later.
|
||||
supportless_additional_depth = 2* LAYER_HEIGHT;
|
||||
magnet_depth = MAGNET_HOLE_DEPTH - o +
|
||||
(supportless ? supportless_additional_depth : 0);
|
||||
|
||||
union() {
|
||||
if(refined_hole) {
|
||||
refined_hole();
|
||||
}
|
||||
|
||||
if(magnet_hole) {
|
||||
difference() {
|
||||
if(crush_ribs) {
|
||||
ribbed_cylinder(magnet_radius, magnet_inner_radius, magnet_depth, MAGNET_HOLE_CRUSH_RIB_COUNT);
|
||||
} else {
|
||||
cylinder(h = magnet_depth, r=magnet_radius);
|
||||
}
|
||||
|
||||
if(supportless) {
|
||||
make_magnet_hole_printable(screw_radius, magnet_radius, magnet_depth);
|
||||
}
|
||||
}
|
||||
|
||||
if(chamfer) {
|
||||
cone(magnet_radius + MAGNET_HOLE_CHAMFER_ADDITIONAL_RADIUS, MAGNET_HOLE_CHAMFER_ANGLE, magnet_depth);
|
||||
}
|
||||
}
|
||||
|
||||
if(screw_hole) {
|
||||
cylinder(h = screw_depth, r = screw_radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//$fa = 8;
|
||||
//$fs = 0.25;
|
||||
//block_base_hole(bundle_hole_options(
|
||||
// refined_hole=false,
|
||||
// magnet_hole=true,
|
||||
// screw_hole=true,
|
||||
// supportless=true,
|
||||
// crush_ribs=true,
|
||||
// chamfer=true
|
||||
//));
|
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
|
||||
include <standard.scad>
|
||||
use <generic-helpers.scad>
|
||||
use <gridfinity-rebuilt-holes.scad>
|
||||
|
||||
// ===== User Modules ===== //
|
||||
|
||||
|
@ -125,7 +127,6 @@ module gridfinityInit(gx, gy, h, h0 = 0, l = l_grid, sl = 0) {
|
|||
$dh = h;
|
||||
$dh0 = h0;
|
||||
$style_lip = sl;
|
||||
color("tomato") {
|
||||
difference() {
|
||||
color("firebrick")
|
||||
block_bottom(h0==0?$dh-0.1:h0, gx, gy, l);
|
||||
|
@ -133,9 +134,8 @@ module gridfinityInit(gx, gy, h, h0 = 0, l = l_grid, sl = 0) {
|
|||
}
|
||||
color("royalblue")
|
||||
block_wall(gx, gy, l) {
|
||||
if ($style_lip == 0) profile_wall();
|
||||
else profile_wall2();
|
||||
}
|
||||
if ($style_lip == 0) profile_wall(h);
|
||||
else profile_wall2(h);
|
||||
}
|
||||
}
|
||||
// Function to include in the custom() module to individually slice bins
|
||||
|
@ -210,7 +210,7 @@ module profile_base() {
|
|||
]);
|
||||
}
|
||||
|
||||
module gridfinityBase(gx, gy, l, dx, dy, style_hole, off=0, final_cut=true, only_corners=false) {
|
||||
module gridfinityBase(gx, gy, l, dx, dy, hole_options, 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));
|
||||
|
@ -231,57 +231,45 @@ module gridfinityBase(gx, gy, l, dx, dy, style_hole, off=0, final_cut=true, only
|
|||
difference(){
|
||||
pattern_linear(gx/dbnx, gy/dbny, dbnx*l, dbny*l)
|
||||
block_base(gx, gy, l, dbnx, dbny, 0, off);
|
||||
if (style_hole == 4) {
|
||||
translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
|
||||
refined_hole();
|
||||
mirror([1, 0, 0])
|
||||
translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
|
||||
refined_hole();
|
||||
mirror([0, 1, 0]) {
|
||||
translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
|
||||
refined_hole();
|
||||
mirror([1, 0, 0])
|
||||
translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
|
||||
refined_hole();
|
||||
|
||||
copy_mirror([0, 1, 0]) {
|
||||
copy_mirror([1, 0, 0]) {
|
||||
translate([
|
||||
(gx/2)*l_grid - d_hole_from_side,
|
||||
(gy/2) * l_grid - d_hole_from_side,
|
||||
0
|
||||
])
|
||||
block_base_hole(hole_options, off);
|
||||
}
|
||||
}
|
||||
else {
|
||||
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);
|
||||
block_base(gx, gy, l, dbnx, dbny, hole_options, off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A single Gridfinity base.
|
||||
* @brief A single Gridfinity base. With holes (if set).
|
||||
* @param gx
|
||||
* @param gy
|
||||
* @param l
|
||||
* @param dbnx
|
||||
* @param dbny
|
||||
* @param style_hole
|
||||
* @param hole_options @see block_base_hole.hole_options
|
||||
* @param off
|
||||
*/
|
||||
module block_base(gx, gy, l, dbnx, dbny, style_hole, off) {
|
||||
module block_base(gx, gy, l, dbnx, dbny, hole_options, 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);
|
||||
}
|
||||
pattern_circular(abs(l-d_hole_from_side/2)<0.001?1:4)
|
||||
translate([l/2-d_hole_from_side, l/2-d_hole_from_side, 0])
|
||||
block_base_hole(hole_options, off);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -312,101 +300,76 @@ module block_base_solid(dbnx, dbny, l, o) {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
/**
|
||||
* @brief Stacking lip based on https://gridfinity.xyz/specification/
|
||||
* @details Also includes a support base.
|
||||
*/
|
||||
module stacking_lip() {
|
||||
// Technique: Descriptive constant names are useful, but can be unweildy.
|
||||
// Use abbreviations if they are going to be re-used repeatedly in a small piece of code.
|
||||
inner_slope = stacking_lip_inner_slope_height_mm;
|
||||
wall_height = stacking_lip_wall_height_mm;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
support_wall = stacking_lip_support_wall_height_mm;
|
||||
s_total = stacking_lip_support_height_mm;
|
||||
|
||||
|
||||
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]
|
||||
[0, 0], // Inner tip
|
||||
[inner_slope, inner_slope], // Go out 45 degrees
|
||||
[inner_slope, inner_slope+wall_height], // Vertical increase
|
||||
[stacking_lip_depth, stacking_lip_height], // Go out 45 degrees
|
||||
[stacking_lip_depth, -s_total], // Down to support bottom
|
||||
[0, -support_wall], // Up and in
|
||||
[0, 0] // Close the shape. Tehcnically not needed.
|
||||
]);
|
||||
}
|
||||
|
||||
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();
|
||||
/**
|
||||
* @brief Stacking lip with a with a chamfered (rounded) top.
|
||||
* @details Based on https://gridfinity.xyz/specification/
|
||||
* Also includes a support base.
|
||||
*/
|
||||
module stacking_lip_chamfered() {
|
||||
radius_center_y = h_lip - r_f1;
|
||||
|
||||
union() {
|
||||
// Create rounded top
|
||||
intersection() {
|
||||
translate([0, radius_center_y, 0])
|
||||
square([stacking_lip_depth, stacking_lip_height]);
|
||||
offset(r = r_f1)
|
||||
offset(delta = -r_f1)
|
||||
stacking_lip();
|
||||
}
|
||||
// Remove pointed top
|
||||
difference(){
|
||||
stacking_lip();
|
||||
translate([0, radius_center_y, 0])
|
||||
square([stacking_lip_depth*2, stacking_lip_height*2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
/**
|
||||
* @brief External wall profile, with a stacking lip.
|
||||
* @details Translated so a 90 degree rotation produces the expected outside radius.
|
||||
*/
|
||||
module profile_wall(height_mm) {
|
||||
assert(is_num(height_mm))
|
||||
translate([r_base - stacking_lip_depth, 0, 0]){
|
||||
translate([0, height_mm, 0])
|
||||
stacking_lip_chamfered();
|
||||
translate([stacking_lip_depth-d_wall/2, 0, 0])
|
||||
square([d_wall/2, height_mm]);
|
||||
}
|
||||
}
|
||||
|
||||
// lipless profile
|
||||
module profile_wall2() {
|
||||
module profile_wall2(height_mm) {
|
||||
assert(is_num(height_mm))
|
||||
translate([r_base,0,0])
|
||||
mirror([1,0,0])
|
||||
square([d_wall,$dh]);
|
||||
square([d_wall, height_mm]);
|
||||
}
|
||||
|
||||
module block_wall(gx, gy, l) {
|
||||
|
@ -600,60 +563,3 @@ module profile_cutter_tab(h, tab, ang) {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 19 KiB |
|
@ -1,8 +1,6 @@
|
|||
|
||||
// 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"
|
||||
|
@ -18,18 +16,44 @@ r_fo3 = 1.6 / 2;
|
|||
// length of a grid unit
|
||||
l_grid = 42;
|
||||
|
||||
// screw hole radius
|
||||
r_hole1 = 1.5;
|
||||
// magnet hole radius
|
||||
r_hole2 = 3.25;
|
||||
|
||||
// Outside rounded radius of bin
|
||||
// Per spec, matches radius of upper base section.
|
||||
r_base = r_fo1;
|
||||
|
||||
// ****************************************
|
||||
// Magnet / Screw Hole Constants
|
||||
// ****************************************
|
||||
LAYER_HEIGHT = 0.2;
|
||||
MAGNET_HEIGHT = 2;
|
||||
|
||||
SCREW_HOLE_RADIUS = 3 / 2;
|
||||
MAGNET_HOLE_RADIUS = 6.5 / 2;
|
||||
MAGNET_HOLE_DEPTH = MAGNET_HEIGHT + (LAYER_HEIGHT * 2);
|
||||
|
||||
// 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;
|
||||
|
||||
// Meassured diameter in Fusion360.
|
||||
// Smaller than the magnet to keep it squeezed.
|
||||
REFINED_HOLE_RADIUS = 5.86 / 2;
|
||||
REFINED_HOLE_HEIGHT = MAGNET_HEIGHT - 0.1;
|
||||
// How many layers are between a Gridfinity Refined Hole and the bottom
|
||||
REFINED_HOLE_BOTTOM_LAYERS = 2;
|
||||
|
||||
// Experimentally chosen for a press fit.
|
||||
MAGNET_HOLE_CRUSH_RIB_INNER_RADIUS = 5.9 / 2;
|
||||
// Mostly arbitrarily chosen.
|
||||
// 30 ribs does not print with a 0.4mm nozzle.
|
||||
// Anything 5 or under produces a hole that is not round.
|
||||
MAGNET_HOLE_CRUSH_RIB_COUNT = 8;
|
||||
|
||||
MAGNET_HOLE_CHAMFER_ADDITIONAL_RADIUS = 0.8;
|
||||
MAGNET_HOLE_CHAMFER_ANGLE = 45;
|
||||
|
||||
// ****************************************
|
||||
|
||||
// top edge fillet radius
|
||||
r_f1 = 0.6;
|
||||
|
@ -55,6 +79,25 @@ h_lip = 3.548;
|
|||
d_wall2 = r_base-r_c1-d_clear*sqrt(2);
|
||||
d_magic = -2*d_clear-2*d_wall+d_div;
|
||||
|
||||
// Stacking Lip
|
||||
// Based on https://gridfinity.xyz/specification/
|
||||
stacking_lip_inner_slope_height_mm = 0.7;
|
||||
stacking_lip_wall_height_mm = 1.8;
|
||||
stacking_lip_outer_slope_height_mm = 1.9;
|
||||
stacking_lip_depth =
|
||||
stacking_lip_inner_slope_height_mm +
|
||||
stacking_lip_outer_slope_height_mm;
|
||||
stacking_lip_height =
|
||||
stacking_lip_inner_slope_height_mm +
|
||||
stacking_lip_wall_height_mm +
|
||||
stacking_lip_outer_slope_height_mm;
|
||||
|
||||
// Extracted from `profile_wall_sub_sub`.
|
||||
stacking_lip_support_wall_height_mm = 1.2;
|
||||
stacking_lip_support_height_mm =
|
||||
stacking_lip_support_wall_height_mm + d_wall2;
|
||||
|
||||
|
||||
// Baseplate constants
|
||||
|
||||
// Baseplate bottom part height (part added with weigthed=true)
|
||||
|
|