Compare commits

...

13 Commits

Author SHA1 Message Date
Ruud 36345f3efb
Merge pull request #175 from EmperorArthur/affine_hell
Use Affine Transformation Matrices For sweep_rounded
2024-04-18 20:15:49 +02:00
Ruud fc51ba875e
Merge pull request #173 from coderjo/main
Don't add holes if style_hole is 0 even if only_corners is set
2024-04-18 20:10:17 +02:00
Ruud cf1666fa2c
Merge pull request #171 from EmperorArthur/fix_wall_profile
Update Stacking Lip Profile
2024-04-18 20:07:55 +02:00
Arthur Moore e7ef96bbcf Stop using as many hacks in sweep_rounded 2024-04-17 22:34:49 -04:00
Arthur Moore ff3a325b37 Update affine_translate to support all axes. 2024-04-17 22:34:30 -04:00
Arthur Moore 20492634d8 Update affine_rotate to support all axes. 2024-04-17 22:34:03 -04:00
Arthur Moore 574d9dc6b1 Move helper functions / modules to their own file
These are not explicitly tied to Gridfinity.
2024-03-11 19:36:48 -04:00
Arthur Moore 637b98577f Match radius of bin to radius of base.
Fixes #169, and #165.
2024-03-10 23:34:30 -04:00
Arthur Moore 5b25e2e114 Replace "1.4" constant in profile_wall() with calculated value 2024-03-10 23:28:47 -04:00
Arthur Moore 69cb64e98d Rename chamfered profile to stacking_lip_chamfered() 2024-03-10 23:23:09 -04:00
Arthur Moore 1cf350121d Use Affine transformation matrices for creating walls.
This involves more math, but results in a cleaner abstract syntax tree.
Which also means it is cleaner when importing to something like FreeCAD.
2024-03-09 23:29:03 -05:00
Tracy Ward d9d92db544 Don't add holes if style_hole is 0 even if only_corners is set 2024-03-04 22:13:17 -05:00
Arthur Moore 015daff2e8 Update Stacking Lip Profile
Profile is now based on https://gridfinity.xyz/specification/
This does not change the final profile, merely how it is created.

Fixes #168 - Cura watertight issue.
Paritally fixes #169 - FreeCad errors.
2024-02-24 03:57:06 -05:00
3 changed files with 254 additions and 96 deletions

169
generic-helpers.scad Normal file
View File

@ -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();
}
}
}

View File

@ -5,6 +5,7 @@
*/
include <standard.scad>
use <generic-helpers.scad>
// ===== User Modules ===== //
@ -125,7 +126,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 +133,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
@ -227,7 +226,7 @@ module gridfinityBase(gx, gy, l, dx, dy, style_hole, off=0, final_cut=true, only
translate([0,0,-1])
rounded_rectangle(xx+0.005, yy+0.005, h_base+h_bot/2*10, r_fo1+0.001);
if(only_corners) {
if((style_hole != 0) && (only_corners)) {
difference(){
pattern_linear(gx/dbnx, gy/dbny, dbnx*l, dbny*l)
block_base(gx, gy, l, dbnx, dbny, 0, off);
@ -362,51 +361,76 @@ module refined_hole() {
}
}
module profile_wall_sub_sub() {
/**
* @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;
support_wall = stacking_lip_support_wall_height_mm;
s_total = stacking_lip_support_height_mm;
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 +624,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();
}
}

View File

@ -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,6 +16,11 @@ r_fo3 = 1.6 / 2;
// length of a grid unit
l_grid = 42;
// Outside rounded radius of bin
// Per spec, matches radius of upper base section.
r_base = r_fo1;
// screw hole radius
r_hole1 = 1.5;
// magnet hole radius
@ -55,6 +58,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)