diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8197d44 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +CHANGELOG: + V2.0.0: + * added $fa values for minkowski and shape - so you can customize how much rounding there is + * rejiggered `key.scad` pipeline for more clarity and less shapes + * implemented "3d_surface" dish - still in beta + * super cool though, you can even change the distribution of points on the surface! just make sure you use monotonically increasing functions + * created "hull" folder to house different ways of creating the overall key shape + * promoted "feature" folder to first-class folder with keytext and switch clearance as new residents + * wrote this changelog! + * implemented `$inner_shape_type`, use "flat" for less geometry or "disable" to make a completely solid key easily. didn't help render rounded keys though + * side-printed keycaps are first class! you can use the `sideways()` modifier to set up sideways keycaps that have flat sides to print on. + * it's much easier to make quick artisans now that the inside of the keycap is differenced from any additive features placed on top + * still todo: add a magic scaling variable so you can scale the whole world up, see if that fixes degeneracy + * still todo: rejigger supports + * still todo: rejigger inner shape. maybe just always make it flat diff --git a/customizer.scad b/customizer.scad index 0d0cf72..f940ccb 100644 --- a/customizer.scad +++ b/customizer.scad @@ -52,7 +52,7 @@ $outset_legends = false; // Height in units of key. should remain 1 for most uses $key_height = 1.0; // Keytop thickness, aka how many millimeters between the inside and outside of the top surface of the key -$keytop_thickness = 2; +$keytop_thickness = 1; // Wall thickness, aka the thickness of the sides of the keycap. note this is the total thickness, aka 3 = 1.5mm walls $wall_thickness = 3; // Radius of corners of keycap @@ -199,19 +199,19 @@ $tertiary_color = [1, .6941, .2]; $quaternary_color = [.4078, .3569, .749]; $warning_color = [1,0,0, 0.15]; -// 3d surface variables -// see functions.scad for the surface function -$3d_surface_size = 10; -$3d_surface_step = 1; -// normally the bottom of the keytop looks like the top - curved, at least -// underneath the support structure. This ensures there's a minimum thickness for the -// underside of the keycap, but it's a fair bit of geometry -$flat_keytop_bottom = true; - // how many facets circles will have when used in these features $minkowski_facets = 30; $shape_facets =30; +// 3d surface settings +// unused for now +$3d_surface_size = 100; +// resolution in each axis. 10 = 10 divisions per x/y = 100 points total +$3d_surface_step = 5; + +// "flat" / "dished" / "disable" +$inner_shape_type = "flat"; + // key width functions module u(u=1) { @@ -851,6 +851,17 @@ module debug() { %children(); } + +module display() { + $height_slices = 30; + $minkowski_facets = 64; + $shape_facets = 64; + $stem_type = "disable"; + $support_type = "disable"; + $stem_support_type = "disable"; + + children(); +} module arrows(profile, rows = [4,4,4,3]) { positions = [[0, 0], [1, 0], [2, 0], [1, 1]]; legends = ["←", "↓", "→", "↑"]; @@ -925,6 +936,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // of the keycap a flat plane. 1 = front, -1 = back // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); + +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ $fs=.1; unit = 19.05; @@ -1200,6 +1235,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // of the keycap a flat plane. 1 = front, -1 = back // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); + +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ function sign_x(i,n) = i < n/4 || i > n*3/4 ? 1 : i > n/4 && i < n*3/4 ? -1 : @@ -1367,6 +1426,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -1444,6 +1527,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // of the keycap a flat plane. 1 = front, -1 = back // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); + +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ SMALLEST_POSSIBLE = 1/128; // I use functions when I need to compute special variables off of other special variables @@ -1487,6 +1594,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -1574,6 +1705,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // of the keycap a flat plane. 1 = front, -1 = back // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); + +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ SMALLEST_POSSIBLE = 1/128; // I use functions when I need to compute special variables off of other special variables @@ -1617,6 +1772,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -1723,6 +1902,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -1815,6 +2018,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // of the keycap a flat plane. 1 = front, -1 = back // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); + +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ SMALLEST_POSSIBLE = 1/128; // I use functions when I need to compute special variables off of other special variables @@ -1858,6 +2085,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -1980,6 +2231,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // of the keycap a flat plane. 1 = front, -1 = back // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); + +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ SMALLEST_POSSIBLE = 1/128; // I use functions when I need to compute special variables off of other special variables @@ -2023,6 +2298,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -2414,6 +2713,30 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // of the keycap a flat plane. 1 = front, -1 = back // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); + +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ // TODO this define doesn't do anything besides tell me I used flat() in this file // is it better than not having it at all? module flat(stem_type, loft, height) { @@ -2555,6 +2878,107 @@ module legends(depth=0) { } } } +// use skin() instead of successive hulls. much more correct, and looks faster +// too, in most cases. successive hull relies on overlapping faces which are +// not good. But, skin works on vertex sets instead of shapes, which makes it +// a lot more difficult to use +module skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0 ) { + skin([ + for (index = [0:$height_slices + extra_slices]) + let( + progress = (index / $height_slices), + skew_this_slice = $top_skew * progress, + x_skew_this_slice = $top_skew_x * progress, + depth_this_slice = ($total_depth - depth_difference) * progress, + tilt_this_slice = -$top_tilt / $key_height * progress, + y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0 + ) + skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice) + ]); +} + +function skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice) = + transform( + translation([x_skew_this_slice,skew_this_slice,depth_this_slice]), + transform( + rotation([tilt_this_slice,y_tilt_this_slice,0]), + skin_key_shape([ + total_key_width(0), + total_key_height(0), + ], + [$width_difference, $height_difference], + progress, + thickness_difference + ) + ) + ); +// corollary is hull_shape_hull +// extra_slices unused, only to match argument signatures +module linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0){ + height = $total_depth - depth_difference; + width_scale = top_total_key_width() / total_key_width(); + height_scale = top_total_key_height() / total_key_height(); + + translate([0,$linear_extrude_height_adjustment,0]){ + linear_extrude(height = height, scale = [width_scale, height_scale]) { + translate([0,-$linear_extrude_height_adjustment,0]){ + key_shape( + [total_key_width(thickness_difference), total_key_height(thickness_difference)], + [$width_difference, $height_difference] + ); + } + } + } +} +module hull_shape_hull(thickness_difference, depth_difference, extra_slices = 0) { + for (index = [0:$height_slices - 1 + extra_slices]) { + hull() { + shape_slice(index / $height_slices, thickness_difference, depth_difference); + shape_slice((index + 1) / $height_slices, thickness_difference, depth_difference); + } + } +} + +module shape_slice(progress, thickness_difference, depth_difference) { + skew_this_slice = $top_skew * progress; + x_skew_this_slice = $top_skew_x * progress; + + depth_this_slice = ($total_depth - depth_difference) * progress; + + tilt_this_slice = -$top_tilt / $key_height * progress; + y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0; + + translate([x_skew_this_slice, skew_this_slice, depth_this_slice]) { + rotate([tilt_this_slice,y_tilt_this_slice,0]){ + linear_extrude(height = SMALLEST_POSSIBLE + ($rounded_key ? $minkowski_radius : 0), scale = SMALLEST_POSSIBLE){ + key_shape( + [ + total_key_width(thickness_difference), + total_key_height(thickness_difference) + ], + [$width_difference, $height_difference], + progress + ); + } + } + } +} + +// basic key shape, no dish, no inside +// which is only used for dishing to cut the dish off correctly +// $height_difference used for keytop thickness +// extra_slices is a hack to make inverted dishes still work +module shape_hull(thickness_difference, depth_difference, extra_slices = 0){ + render() { + if ($skin_extrude_shape) { + skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices); + } else if ($linear_extrude_shape) { + linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices); + } else { + hull_shape_hull(thickness_difference, depth_difference, extra_slices); + } + } +} // from https://www.thingiverse.com/thing:1484333 // public domain license @@ -3499,210 +3923,44 @@ function profile_segment_length(profile,i) = norm(profile[(i+1)%len(profile)] - // Generates an array with n copies of value (default 0) function dup(value=0,n) = [for (i = [1:n]) value]; - /* [Hidden] */ SMALLEST_POSSIBLE = 1/128; -$fs = .1; +// basically disable $fs - though it might be useful for these CGAL problems +$fs = .01; $unit = 19.05; // key shape including dish. used as the ouside and inside shape in hollow_key(). allows for itself to be shrunk in depth and width / height module shape(thickness_difference, depth_difference=0){ dished(depth_difference, $inverted_dish) { - color($primary_color) shape_hull(thickness_difference, depth_difference, $inverted_dish ? 2 : 0); + color($primary_color) shape_hull(thickness_difference, depth_difference, $inverted_dish ? 200 : 0); } } -// shape of the key but with soft, rounded edges. no longer includes dish -// randomly doesnt work sometimes -// the dish doesn't _quite_ reach as far as it should -module rounded_shape() { - dished(-$minkowski_radius, $inverted_dish) { - color($primary_color) minkowski(){ - // half minkowski in the z direction - color($primary_color) shape_hull($minkowski_radius * 2, $minkowski_radius/2, $inverted_dish ? 2 : 0); - /* cube($minkowski_radius); */ - sphere(r=$minkowski_radius, $fn=$minkowski_facets); - } - } - /* %envelope(); */ -} - // this function is more correct, but takes _forever_ // the main difference is minkowski happens after dishing, meaning the dish is // also minkowski'd -/* module rounded_shape() { +module rounded_shape() { color($primary_color) minkowski(){ // half minkowski in the z direction shape($minkowski_radius * 2, $minkowski_radius/2); - difference(){ - sphere(r=$minkowski_radius, $fn=20); - translate([0,0,-$minkowski_radius]){ - cube($minkowski_radius * 2, center=true); - } - } + minkowski_object(); } -} */ +} +// minkowski places this object at every vertex of the other object then mashes +// it all together +module minkowski_object() { + // alternative minkowski shape that needs the bottom of the keycap to be trimmed + /* sphere(1); */ - -// basic key shape, no dish, no inside -// which is only used for dishing to cut the dish off correctly -// $height_difference used for keytop thickness -// extra_slices is a hack to make inverted dishes still work -module shape_hull(thickness_difference, depth_difference, extra_slices = 0){ - render() { - if ($skin_extrude_shape) { - skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices); - } else if ($linear_extrude_shape) { - linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices); - } else { - hull_shape_hull(thickness_difference, depth_difference, extra_slices); + difference(){ + sphere(r=$minkowski_radius, $fa=360/$minkowski_facets); + translate([0,0,-$minkowski_radius]){ + cube($minkowski_radius * 2, center=true); } } } -// use skin() instead of successive hulls. much more correct, and looks faster -// too, in most cases. successive hull relies on overlapping faces which are -// not good. But, skin works on vertex sets instead of shapes, which makes it -// a lot more difficult to use -module skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0 ) { - skin([ - for (index = [0:$height_slices + extra_slices]) - let( - progress = (index / $height_slices), - skew_this_slice = $top_skew * progress, - x_skew_this_slice = $top_skew_x * progress, - depth_this_slice = ($total_depth - depth_difference) * progress, - tilt_this_slice = -$top_tilt / $key_height * progress, - y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0 - ) - skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice) - ]); -} - -function skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice) = - transform( - translation([x_skew_this_slice,skew_this_slice,depth_this_slice]), - transform( - rotation([tilt_this_slice,y_tilt_this_slice,0]), - skin_key_shape([ - total_key_width(0), - total_key_height(0), - ], - [$width_difference, $height_difference], - progress, - thickness_difference - ) - ) - ); - -// corollary is hull_shape_hull -// extra_slices unused, only to match argument signatures -module linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0){ - height = $total_depth - depth_difference; - width_scale = top_total_key_width() / total_key_width(); - height_scale = top_total_key_height() / total_key_height(); - - translate([0,$linear_extrude_height_adjustment,0]){ - linear_extrude(height = height, scale = [width_scale, height_scale]) { - translate([0,-$linear_extrude_height_adjustment,0]){ - key_shape( - [total_key_width(thickness_difference), total_key_height(thickness_difference)], - [$width_difference, $height_difference] - ); - } - } - } -} - -module hull_shape_hull(thickness_difference, depth_difference, extra_slices = 0) { - for (index = [0:$height_slices - 1 + extra_slices]) { - hull() { - shape_slice(index / $height_slices, thickness_difference, depth_difference); - shape_slice((index + 1) / $height_slices, thickness_difference, depth_difference); - } - } -} - -module shape_slice(progress, thickness_difference, depth_difference) { - skew_this_slice = $top_skew * progress; - x_skew_this_slice = $top_skew_x * progress; - - depth_this_slice = ($total_depth - depth_difference) * progress; - - tilt_this_slice = -$top_tilt / $key_height * progress; - y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0; - - translate([x_skew_this_slice, skew_this_slice, depth_this_slice]) { - rotate([tilt_this_slice,y_tilt_this_slice,0]){ - linear_extrude(height = SMALLEST_POSSIBLE){ - key_shape( - [ - total_key_width(thickness_difference), - total_key_height(thickness_difference) - ], - [$width_difference, $height_difference], - progress - ); - } - } - } -} - -// for when you want something to only exist inside the keycap. -// used for the support structure -module inside() { - intersection() { - shape($wall_thickness, $keytop_thickness); - children(); - } -} - -// for when you want something to only exist outside the keycap -module outside() { - difference() { - children(); - shape($wall_thickness, $keytop_thickness); - } -} - -// put something at the top of the key, with no adjustments for dishing -module top_placement(depth_difference=0) { - top_tilt_by_height = -$top_tilt / $key_height; - top_tilt_y_by_length = $double_sculpted ? (-$top_tilt_y / $key_length) : 0; - - minkowski_height = $rounded_key ? $minkowski_radius : 0; - - translate([$top_skew_x + $dish_skew_x, $top_skew + $dish_skew_y, $total_depth - depth_difference + minkowski_height/2]){ - rotate([top_tilt_by_height, top_tilt_y_by_length,0]){ - children(); - } - } -} - -module front_placement() { - // all this math is to take top skew and tilt into account - // we need to find the new effective height and depth of the top, front lip - // of the keycap to find the angle so we can rotate things correctly into place - total_depth_difference = sin(-$top_tilt) * (top_total_key_height()/2); - total_height_difference = $top_skew + (1 - cos(-$top_tilt)) * (top_total_key_height()/2); - - angle = atan2(($total_depth - total_depth_difference), ($height_difference/2 + total_height_difference)); - hypotenuse = ($total_depth -total_depth_difference) / sin(angle); - - translate([0,-total_key_height()/2,0]) { - rotate([-(90-angle), 0, 0]) { - translate([0,0,hypotenuse/2]){ - children(); - } - } - } -} - -// just to DRY up the code -module _dish() { - color($secondary_color) dish(top_total_key_width() + $dish_overdraw_width, top_total_key_height() + $dish_overdraw_height, $dish_depth, $inverted_dish); -} module envelope(depth_difference=0) { s = 1.5; @@ -3714,18 +3972,6 @@ module envelope(depth_difference=0) { } } -// I think this is unused -module dished_for_show() { - difference(){ - union() { - envelope(); - if ($inverted_dish) top_placement(0) _dish(); - } - if (!$inverted_dish) top_placement(0) _dish(); - } -} - - // for when you want to take the dish out of things // used for adding the dish to the key shape and making sure stems don't stick out the top // creates a bounding box 1.5 times larger in width and height than the keycap. @@ -3734,15 +3980,21 @@ module dished(depth_difference = 0, inverted = false) { children(); difference(){ union() { + // envelope is needed to "fill in" the rest of the keycap envelope(depth_difference); - if (inverted) top_placement(depth_difference) _dish(); + if (inverted) top_placement(depth_difference) _dish(inverted); } - if (!inverted) top_placement(depth_difference) _dish(); + if (!inverted) top_placement(depth_difference) _dish(inverted); } } } -// puts it's children at the center of the dishing on the key, including dish height +// just to DRY up the code +module _dish(inverted=$inverted_dish) { + color($secondary_color) dish(top_total_key_width() + $dish_overdraw_width, top_total_key_height() + $dish_overdraw_height, $dish_depth, inverted); +} + +// puts its children at the center of the dishing on the key, including dish height // more user-friendly than top_placement module top_of_key(){ // if there is a dish, we need to account for how much it digs into the top @@ -3755,6 +4007,7 @@ module top_of_key(){ } } +// puts its children at each keystem position provided module keystem_positions(positions) { for (connector_pos = positions) { translate(connector_pos) { @@ -3780,78 +4033,121 @@ module stems_for(positions, stem_type) { } } -// legends / artisan support -module artisan(depth) { - top_of_key() { - // artisan objects / outset shape legends - color($secondary_color) children(); +// put something at the top of the key, with no adjustments for dishing +module top_placement(depth_difference=0) { + top_tilt_by_height = -$top_tilt / $key_height; + top_tilt_y_by_length = $double_sculpted ? (-$top_tilt_y / $key_length) : 0; + + minkowski_height = $rounded_key ? $minkowski_radius : 0; + + translate([$top_skew_x + $dish_skew_x, $top_skew + $dish_skew_y, $total_depth - depth_difference + minkowski_height/2]){ + rotate([top_tilt_by_height, top_tilt_y_by_length,0]){ + children(); + } } } -// key with hollowed inside but no stem -module hollow_key() { - difference(){ - if ($rounded_key) { - rounded_shape(); +module front_of_key() { + // all this math is to take top skew and tilt into account + // we need to find the new effective height and depth of the top, front lip + // of the keycap to find the angle so we can rotate things correctly into place + total_depth_difference = sin(-$top_tilt) * (top_total_key_height()/2); + total_height_difference = $top_skew + (1 - cos(-$top_tilt)) * (top_total_key_height()/2); + + angle = atan2(($total_depth - total_depth_difference), ($height_difference/2 + total_height_difference)); + hypotenuse = ($total_depth -total_depth_difference) / sin(angle); + + translate([0,-total_key_height()/2,0]) { + rotate([-(90-angle), 0, 0]) { + translate([0,0,hypotenuse/2]){ + children(); + } + } + } +} + +module outer_shape() { + shape(0, 0); +} + +module inner_shape(extra_wall_thickness = 0, extra_keytop_thickness = 0) { + translate([0,0,-SMALLEST_POSSIBLE]) { + if ($inner_shape_type == "flat") { + /* $key_shape_type="square"; */ + $height_slices = 1; + color($primary_color) shape_hull($wall_thickness + extra_wall_thickness, $keytop_thickness + extra_keytop_thickness, 0); } else { - shape(0, 0); - } - // translation purely for aesthetic purposes, to get rid of that awful lattice - translate([0,0,-SMALLEST_POSSIBLE]) { - shape($wall_thickness, $keytop_thickness); + shape($wall_thickness + extra_wall_thickness, $keytop_thickness + extra_keytop_thickness); } } } +// additive objects at the top of the key +module additive_features(inset) { + top_of_key() { + if($key_bump) keybump($key_bump_depth, $key_bump_edge); + if(!inset && $children > 0) color($secondary_color) children(); + } + if($outset_legends) legends(0); + // render the clearance check if it's enabled, but don't have it intersect with anything + if ($clearance_check) %clearance_check(); +} + +// subtractive objects at the top of the key +module subtractive_features(inset) { + top_of_key() { + if (inset && $children > 0) color($secondary_color) children(); + } + if(!$outset_legends) legends($inset_legend_depth); + // subtract the clearance check if it's enabled, letting the user see the + // parts of the keycap that will hit the cherry switch + if ($clearance_check) %clearance_check(); +} + +module inside_features() { + translate([0, 0, $stem_inset]) { + // both stem and support are optional + if ($stabilizer_type != "disable") stems_for($stabilizers, $stabilizer_type); + if ($stem_type != "disable") stems_for($stem_positions, $stem_type); + if ($stabilizer_type != "disable") support_for($stabilizers, $stabilizer_type); + // always render stem support even if there isn't a stem. + // rendering flat support w/no stem is much more common than a hollow keycap + // so if you want a hollow keycap you'll have to turn support off entirely + if ($support_type != "disable") support_for($stem_positions, $stem_type); + } +} // The final, penultimate key generation function. // takes all the bits and glues them together. requires configuration with special variables. -module key(inset = false) { - difference() { - union(){ - // the shape of the key, inside and out - hollow_key(); - if($key_bump) top_of_key() keybump($key_bump_depth, $key_bump_edge); - // additive objects at the top of the key - // outside() makes them stay out of the inside. it's a bad name - if(!inset && $children > 0) outside() artisan(0) children(); - if($outset_legends) legends(0); - // render the clearance check if it's enabled, but don't have it intersect with anything - if ($clearance_check) %clearance_check(); +module key(inset=false) { + difference(){ + union() { + outer_shape(); + additive_features(inset) { + children(); + }; } - // subtractive objects at the top of the key - // no outside() - I can't think of a use for it. will save render time - if (inset && $children > 0) artisan($inset_legend_depth) children(); - if(!$outset_legends) legends($inset_legend_depth); - // subtract the clearance check if it's enabled, letting the user see the - // parts of the keycap that will hit the cherry switch - if ($clearance_check) %clearance_check(); - } - - // both stem and support are optional - if ($stem_type != "disable" || ($stabilizers != [] && $stabilizer_type != "disable")) { - dished($keytop_thickness, $inverted_dish) { - translate([0, 0, $stem_inset]) { - if ($stabilizer_type != "disable") stems_for($stabilizers, $stabilizer_type); - - if ($stem_type != "disable") stems_for($stem_positions, $stem_type); - } + if ($inner_shape_type != "disable") difference() { + inner_shape(); + inside_features(); } + + subtractive_features(inset) { + children(); + }; } +} - if ($support_type != "disable"){ - inside() { - translate([0, 0, $stem_inset]) { - if ($stabilizer_type != "disable") support_for($stabilizers, $stabilizer_type); - - // always render stem support even if there isn't a stem. - // rendering flat support w/no stem is much more common than a hollow keycap - // so if you want a hollow keycap you'll have to turn support off entirely - support_for($stem_positions, $stem_type); - } +module display_key(inset=false) { + minkowski() { + outer_shape(); + minkowski_object(); + // minkowski doesn't work with difference + additive_features(false) { + children(); + }; } - } } // actual full key with space carved out and keystem/stabilizer connectors @@ -3896,7 +4192,7 @@ $outset_legends = false; // Height in units of key. should remain 1 for most uses $key_height = 1.0; // Keytop thickness, aka how many millimeters between the inside and outside of the top surface of the key -$keytop_thickness = 2; +$keytop_thickness = 1; // Wall thickness, aka the thickness of the sides of the keycap. note this is the total thickness, aka 3 = 1.5mm walls $wall_thickness = 3; // Radius of corners of keycap @@ -4043,18 +4339,18 @@ $tertiary_color = [1, .6941, .2]; $quaternary_color = [.4078, .3569, .749]; $warning_color = [1,0,0, 0.15]; -// 3d surface variables -// see functions.scad for the surface function -$3d_surface_size = 10; -$3d_surface_step = 1; -// normally the bottom of the keytop looks like the top - curved, at least -// underneath the support structure. This ensures there's a minimum thickness for the -// underside of the keycap, but it's a fair bit of geometry -$flat_keytop_bottom = true; - // how many facets circles will have when used in these features $minkowski_facets = 30; $shape_facets =30; + +// 3d surface settings +// unused for now +$3d_surface_size = 100; +// resolution in each axis. 10 = 10 divisions per x/y = 100 points total +$3d_surface_step = 5; + +// "flat" / "dished" / "disable" +$inner_shape_type = "flat"; key(); } diff --git a/keys.scad b/keys.scad index 768110e..0a80ad0 100644 --- a/keys.scad +++ b/keys.scad @@ -9,7 +9,32 @@ include <./includes.scad> // example key -dcs_row(5) legend("⇪", size=9) key(); +/* $skin_key_shape = true; */ + +difference() { + /* top_of_key() { */ + /* cube(10); */ + /* } */ + /* key(); */ +} + +/* simple_layout(lets_split_layout) { + dcs_row($row) key(); +} */ + +dcs_row(1) legend("h") front_legend("q" +) { + $key_length = 2.75; + key(); +} + +translate_u(0,1) dcs_row(1) { + $key_length = 0.86; + key(); +} + +/* debug() key(); */ + // example row /* for (x = [0:1:4]) { @@ -17,4 +42,4 @@ dcs_row(5) legend("⇪", size=9) key(); } */ // example layout -/* preonic_default("dcs"); */ \ No newline at end of file +/* preonic_default("dcs"); */ diff --git a/src/dishes/3d_surface.scad b/src/dishes/3d_surface.scad new file mode 100644 index 0000000..413c608 --- /dev/null +++ b/src/dishes/3d_surface.scad @@ -0,0 +1,14 @@ +include <../libraries/3d_surface.scad> + +module 3d_surface_dish(width, height, depth, inverted) { + echo(inverted ? "inverted" : "not inverted"); + // scale_factor is dead reckoning + // it doesn't have to be dead reckoning for anything but sculpted sides + // we know the angle of the sides from the width difference, height difference, + // skew and tilt of the top. it's a pain to calculate though + scale_factor = 1.1; + // the edges on this behave differently than with the previous dish implementations + scale([width*scale_factor/$3d_surface_size/2,height*scale_factor/$3d_surface_size/2,depth]) rotate([inverted ? 0:180,0,180]) polar_3d_surface(bottom=-10); + /* %scale([width*scale_factor/$3d_surface_size/2,height*scale_factor/$3d_surface_size/2,depth]) rotate([180,0,0]) polar_3d_surface(bottom=-10); */ + +} diff --git a/src/functions.scad b/src/functions.scad index eabc9c3..376e6c5 100644 --- a/src/functions.scad +++ b/src/functions.scad @@ -40,3 +40,27 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke // of the keycap a flat plane. 1 = front, -1 = back // I derived this through a bunch of trig reductions I don't really understand. function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth); + +// 3d surface functions (still in beta) + +// monotonically increasing function that distributes the points of the surface mesh +// only for polar_3d_surface right now +// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges +function surface_distribution_function(dim, size) = sin(dim) * size; + +// the function that actually determines what the surface is. +// feel free to override, the last one wins + +// debug +function surface_function(x,y) = 1; +// cylindrical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))); +// spherical +function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size)); +// (statically) random! +// ripples +/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */ +// Rosenbrock's banana +/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */ +// y=x revolved around the y axis +/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */ diff --git a/src/hulls.scad b/src/hulls.scad new file mode 100644 index 0000000..2fb7218 --- /dev/null +++ b/src/hulls.scad @@ -0,0 +1,19 @@ +include +include +include + +// basic key shape, no dish, no inside +// which is only used for dishing to cut the dish off correctly +// $height_difference used for keytop thickness +// extra_slices is a hack to make inverted dishes still work +module shape_hull(thickness_difference, depth_difference, extra_slices = 0){ + render() { + if ($skin_extrude_shape) { + skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices); + } else if ($linear_extrude_shape) { + linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices); + } else { + hull_shape_hull(thickness_difference, depth_difference, extra_slices); + } + } +} diff --git a/src/hulls/hull.scad b/src/hulls/hull.scad new file mode 100644 index 0000000..12df0bb --- /dev/null +++ b/src/hulls/hull.scad @@ -0,0 +1,33 @@ +module hull_shape_hull(thickness_difference, depth_difference, extra_slices = 0) { + for (index = [0:$height_slices - 1 + extra_slices]) { + hull() { + shape_slice(index / $height_slices, thickness_difference, depth_difference); + shape_slice((index + 1) / $height_slices, thickness_difference, depth_difference); + } + } +} + +module shape_slice(progress, thickness_difference, depth_difference) { + skew_this_slice = $top_skew * progress; + x_skew_this_slice = $top_skew_x * progress; + + depth_this_slice = ($total_depth - depth_difference) * progress; + + tilt_this_slice = -$top_tilt / $key_height * progress; + y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0; + + translate([x_skew_this_slice, skew_this_slice, depth_this_slice]) { + rotate([tilt_this_slice,y_tilt_this_slice,0]){ + linear_extrude(height = SMALLEST_POSSIBLE + ($rounded_key ? $minkowski_radius : 0), scale = SMALLEST_POSSIBLE){ + key_shape( + [ + total_key_width(thickness_difference), + total_key_height(thickness_difference) + ], + [$width_difference, $height_difference], + progress + ); + } + } + } +} diff --git a/src/hulls/linear_extrude.scad b/src/hulls/linear_extrude.scad new file mode 100644 index 0000000..f62299e --- /dev/null +++ b/src/hulls/linear_extrude.scad @@ -0,0 +1,18 @@ +// corollary is hull_shape_hull +// extra_slices unused, only to match argument signatures +module linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0){ + height = $total_depth - depth_difference; + width_scale = top_total_key_width() / total_key_width(); + height_scale = top_total_key_height() / total_key_height(); + + translate([0,$linear_extrude_height_adjustment,0]){ + linear_extrude(height = height, scale = [width_scale, height_scale]) { + translate([0,-$linear_extrude_height_adjustment,0]){ + key_shape( + [total_key_width(thickness_difference), total_key_height(thickness_difference)], + [$width_difference, $height_difference] + ); + } + } + } +} diff --git a/src/hulls/skin.scad b/src/hulls/skin.scad new file mode 100644 index 0000000..af8d6d8 --- /dev/null +++ b/src/hulls/skin.scad @@ -0,0 +1,34 @@ +// use skin() instead of successive hulls. much more correct, and looks faster +// too, in most cases. successive hull relies on overlapping faces which are +// not good. But, skin works on vertex sets instead of shapes, which makes it +// a lot more difficult to use +module skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0 ) { + skin([ + for (index = [0:$height_slices + extra_slices]) + let( + progress = (index / $height_slices), + skew_this_slice = $top_skew * progress, + x_skew_this_slice = $top_skew_x * progress, + depth_this_slice = ($total_depth - depth_difference) * progress, + tilt_this_slice = -$top_tilt / $key_height * progress, + y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0 + ) + skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice) + ]); +} + +function skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice) = + transform( + translation([x_skew_this_slice,skew_this_slice,depth_this_slice]), + transform( + rotation([tilt_this_slice,y_tilt_this_slice,0]), + skin_key_shape([ + total_key_width(0), + total_key_height(0), + ], + [$width_difference, $height_difference], + progress, + thickness_difference + ) + ) + ); diff --git a/src/key.scad b/src/key.scad index 8b87f16..06197ff 100644 --- a/src/key.scad +++ b/src/key.scad @@ -6,6 +6,7 @@ include include include include +include include @@ -15,210 +16,44 @@ use use use - /* [Hidden] */ SMALLEST_POSSIBLE = 1/128; -$fs = .1; +// basically disable $fs - though it might be useful for these CGAL problems +$fs = .01; $unit = 19.05; // key shape including dish. used as the ouside and inside shape in hollow_key(). allows for itself to be shrunk in depth and width / height module shape(thickness_difference, depth_difference=0){ dished(depth_difference, $inverted_dish) { - color($primary_color) shape_hull(thickness_difference, depth_difference, $inverted_dish ? 2 : 0); + color($primary_color) shape_hull(thickness_difference, depth_difference, $inverted_dish ? 200 : 0); } } -// shape of the key but with soft, rounded edges. no longer includes dish -// randomly doesnt work sometimes -// the dish doesn't _quite_ reach as far as it should -module rounded_shape() { - dished(-$minkowski_radius, $inverted_dish) { - color($primary_color) minkowski(){ - // half minkowski in the z direction - color($primary_color) shape_hull($minkowski_radius * 2, $minkowski_radius/2, $inverted_dish ? 2 : 0); - /* cube($minkowski_radius); */ - sphere(r=$minkowski_radius, $fn=$minkowski_facets); - } - } - /* %envelope(); */ -} - // this function is more correct, but takes _forever_ // the main difference is minkowski happens after dishing, meaning the dish is // also minkowski'd -/* module rounded_shape() { +module rounded_shape() { color($primary_color) minkowski(){ // half minkowski in the z direction shape($minkowski_radius * 2, $minkowski_radius/2); - difference(){ - sphere(r=$minkowski_radius, $fn=20); - translate([0,0,-$minkowski_radius]){ - cube($minkowski_radius * 2, center=true); - } - } + minkowski_object(); } -} */ +} +// minkowski places this object at every vertex of the other object then mashes +// it all together +module minkowski_object() { + // alternative minkowski shape that needs the bottom of the keycap to be trimmed + /* sphere(1); */ - -// basic key shape, no dish, no inside -// which is only used for dishing to cut the dish off correctly -// $height_difference used for keytop thickness -// extra_slices is a hack to make inverted dishes still work -module shape_hull(thickness_difference, depth_difference, extra_slices = 0){ - render() { - if ($skin_extrude_shape) { - skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices); - } else if ($linear_extrude_shape) { - linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices); - } else { - hull_shape_hull(thickness_difference, depth_difference, extra_slices); + difference(){ + sphere(r=$minkowski_radius, $fa=360/$minkowski_facets); + translate([0,0,-$minkowski_radius]){ + cube($minkowski_radius * 2, center=true); } } } -// use skin() instead of successive hulls. much more correct, and looks faster -// too, in most cases. successive hull relies on overlapping faces which are -// not good. But, skin works on vertex sets instead of shapes, which makes it -// a lot more difficult to use -module skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0 ) { - skin([ - for (index = [0:$height_slices + extra_slices]) - let( - progress = (index / $height_slices), - skew_this_slice = $top_skew * progress, - x_skew_this_slice = $top_skew_x * progress, - depth_this_slice = ($total_depth - depth_difference) * progress, - tilt_this_slice = -$top_tilt / $key_height * progress, - y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0 - ) - skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice) - ]); -} - -function skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice) = - transform( - translation([x_skew_this_slice,skew_this_slice,depth_this_slice]), - transform( - rotation([tilt_this_slice,y_tilt_this_slice,0]), - skin_key_shape([ - total_key_width(0), - total_key_height(0), - ], - [$width_difference, $height_difference], - progress, - thickness_difference - ) - ) - ); - -// corollary is hull_shape_hull -// extra_slices unused, only to match argument signatures -module linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0){ - height = $total_depth - depth_difference; - width_scale = top_total_key_width() / total_key_width(); - height_scale = top_total_key_height() / total_key_height(); - - translate([0,$linear_extrude_height_adjustment,0]){ - linear_extrude(height = height, scale = [width_scale, height_scale]) { - translate([0,-$linear_extrude_height_adjustment,0]){ - key_shape( - [total_key_width(thickness_difference), total_key_height(thickness_difference)], - [$width_difference, $height_difference] - ); - } - } - } -} - -module hull_shape_hull(thickness_difference, depth_difference, extra_slices = 0) { - for (index = [0:$height_slices - 1 + extra_slices]) { - hull() { - shape_slice(index / $height_slices, thickness_difference, depth_difference); - shape_slice((index + 1) / $height_slices, thickness_difference, depth_difference); - } - } -} - -module shape_slice(progress, thickness_difference, depth_difference) { - skew_this_slice = $top_skew * progress; - x_skew_this_slice = $top_skew_x * progress; - - depth_this_slice = ($total_depth - depth_difference) * progress; - - tilt_this_slice = -$top_tilt / $key_height * progress; - y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0; - - translate([x_skew_this_slice, skew_this_slice, depth_this_slice]) { - rotate([tilt_this_slice,y_tilt_this_slice,0]){ - linear_extrude(height = SMALLEST_POSSIBLE){ - key_shape( - [ - total_key_width(thickness_difference), - total_key_height(thickness_difference) - ], - [$width_difference, $height_difference], - progress - ); - } - } - } -} - -// for when you want something to only exist inside the keycap. -// used for the support structure -module inside() { - intersection() { - shape($wall_thickness, $keytop_thickness); - children(); - } -} - -// for when you want something to only exist outside the keycap -module outside() { - difference() { - children(); - shape($wall_thickness, $keytop_thickness); - } -} - -// put something at the top of the key, with no adjustments for dishing -module top_placement(depth_difference=0) { - top_tilt_by_height = -$top_tilt / $key_height; - top_tilt_y_by_length = $double_sculpted ? (-$top_tilt_y / $key_length) : 0; - - minkowski_height = $rounded_key ? $minkowski_radius : 0; - - translate([$top_skew_x + $dish_skew_x, $top_skew + $dish_skew_y, $total_depth - depth_difference + minkowski_height/2]){ - rotate([top_tilt_by_height, top_tilt_y_by_length,0]){ - children(); - } - } -} - -module front_placement() { - // all this math is to take top skew and tilt into account - // we need to find the new effective height and depth of the top, front lip - // of the keycap to find the angle so we can rotate things correctly into place - total_depth_difference = sin(-$top_tilt) * (top_total_key_height()/2); - total_height_difference = $top_skew + (1 - cos(-$top_tilt)) * (top_total_key_height()/2); - - angle = atan2(($total_depth - total_depth_difference), ($height_difference/2 + total_height_difference)); - hypotenuse = ($total_depth -total_depth_difference) / sin(angle); - - translate([0,-total_key_height()/2,0]) { - rotate([-(90-angle), 0, 0]) { - translate([0,0,hypotenuse/2]){ - children(); - } - } - } -} - -// just to DRY up the code -module _dish() { - color($secondary_color) dish(top_total_key_width() + $dish_overdraw_width, top_total_key_height() + $dish_overdraw_height, $dish_depth, $inverted_dish); -} module envelope(depth_difference=0) { s = 1.5; @@ -230,18 +65,6 @@ module envelope(depth_difference=0) { } } -// I think this is unused -module dished_for_show() { - difference(){ - union() { - envelope(); - if ($inverted_dish) top_placement(0) _dish(); - } - if (!$inverted_dish) top_placement(0) _dish(); - } -} - - // for when you want to take the dish out of things // used for adding the dish to the key shape and making sure stems don't stick out the top // creates a bounding box 1.5 times larger in width and height than the keycap. @@ -250,15 +73,21 @@ module dished(depth_difference = 0, inverted = false) { children(); difference(){ union() { + // envelope is needed to "fill in" the rest of the keycap envelope(depth_difference); - if (inverted) top_placement(depth_difference) _dish(); + if (inverted) top_placement(depth_difference) _dish(inverted); } - if (!inverted) top_placement(depth_difference) _dish(); + if (!inverted) top_placement(depth_difference) _dish(inverted); } } } -// puts it's children at the center of the dishing on the key, including dish height +// just to DRY up the code +module _dish(inverted=$inverted_dish) { + color($secondary_color) dish(top_total_key_width() + $dish_overdraw_width, top_total_key_height() + $dish_overdraw_height, $dish_depth, inverted); +} + +// puts its children at the center of the dishing on the key, including dish height // more user-friendly than top_placement module top_of_key(){ // if there is a dish, we need to account for how much it digs into the top @@ -271,6 +100,7 @@ module top_of_key(){ } } +// puts its children at each keystem position provided module keystem_positions(positions) { for (connector_pos = positions) { translate(connector_pos) { @@ -296,78 +126,121 @@ module stems_for(positions, stem_type) { } } -// legends / artisan support -module artisan(depth) { - top_of_key() { - // artisan objects / outset shape legends - color($secondary_color) children(); +// put something at the top of the key, with no adjustments for dishing +module top_placement(depth_difference=0) { + top_tilt_by_height = -$top_tilt / $key_height; + top_tilt_y_by_length = $double_sculpted ? (-$top_tilt_y / $key_length) : 0; + + minkowski_height = $rounded_key ? $minkowski_radius : 0; + + translate([$top_skew_x + $dish_skew_x, $top_skew + $dish_skew_y, $total_depth - depth_difference + minkowski_height/2]){ + rotate([top_tilt_by_height, top_tilt_y_by_length,0]){ + children(); + } } } -// key with hollowed inside but no stem -module hollow_key() { - difference(){ - if ($rounded_key) { - rounded_shape(); +module front_of_key() { + // all this math is to take top skew and tilt into account + // we need to find the new effective height and depth of the top, front lip + // of the keycap to find the angle so we can rotate things correctly into place + total_depth_difference = sin(-$top_tilt) * (top_total_key_height()/2); + total_height_difference = $top_skew + (1 - cos(-$top_tilt)) * (top_total_key_height()/2); + + angle = atan2(($total_depth - total_depth_difference), ($height_difference/2 + total_height_difference)); + hypotenuse = ($total_depth -total_depth_difference) / sin(angle); + + translate([0,-total_key_height()/2,0]) { + rotate([-(90-angle), 0, 0]) { + translate([0,0,hypotenuse/2]){ + children(); + } + } + } +} + +module outer_shape() { + shape(0, 0); +} + +module inner_shape(extra_wall_thickness = 0, extra_keytop_thickness = 0) { + translate([0,0,-SMALLEST_POSSIBLE]) { + if ($inner_shape_type == "flat") { + /* $key_shape_type="square"; */ + $height_slices = 1; + color($primary_color) shape_hull($wall_thickness + extra_wall_thickness, $keytop_thickness + extra_keytop_thickness, 0); } else { - shape(0, 0); - } - // translation purely for aesthetic purposes, to get rid of that awful lattice - translate([0,0,-SMALLEST_POSSIBLE]) { - shape($wall_thickness, $keytop_thickness); + shape($wall_thickness + extra_wall_thickness, $keytop_thickness + extra_keytop_thickness); } } } +// additive objects at the top of the key +module additive_features(inset) { + top_of_key() { + if($key_bump) keybump($key_bump_depth, $key_bump_edge); + if(!inset && $children > 0) color($secondary_color) children(); + } + if($outset_legends) legends(0); + // render the clearance check if it's enabled, but don't have it intersect with anything + if ($clearance_check) %clearance_check(); +} + +// subtractive objects at the top of the key +module subtractive_features(inset) { + top_of_key() { + if (inset && $children > 0) color($secondary_color) children(); + } + if(!$outset_legends) legends($inset_legend_depth); + // subtract the clearance check if it's enabled, letting the user see the + // parts of the keycap that will hit the cherry switch + if ($clearance_check) %clearance_check(); +} + +module inside_features() { + translate([0, 0, $stem_inset]) { + // both stem and support are optional + if ($stabilizer_type != "disable") stems_for($stabilizers, $stabilizer_type); + if ($stem_type != "disable") stems_for($stem_positions, $stem_type); + if ($stabilizer_type != "disable") support_for($stabilizers, $stabilizer_type); + // always render stem support even if there isn't a stem. + // rendering flat support w/no stem is much more common than a hollow keycap + // so if you want a hollow keycap you'll have to turn support off entirely + if ($support_type != "disable") support_for($stem_positions, $stem_type); + } +} // The final, penultimate key generation function. // takes all the bits and glues them together. requires configuration with special variables. -module key(inset = false) { - difference() { - union(){ - // the shape of the key, inside and out - hollow_key(); - if($key_bump) top_of_key() keybump($key_bump_depth, $key_bump_edge); - // additive objects at the top of the key - // outside() makes them stay out of the inside. it's a bad name - if(!inset && $children > 0) outside() artisan(0) children(); - if($outset_legends) legends(0); - // render the clearance check if it's enabled, but don't have it intersect with anything - if ($clearance_check) %clearance_check(); +module key(inset=false) { + difference(){ + union() { + outer_shape(); + additive_features(inset) { + children(); + }; } - // subtractive objects at the top of the key - // no outside() - I can't think of a use for it. will save render time - if (inset && $children > 0) artisan($inset_legend_depth) children(); - if(!$outset_legends) legends($inset_legend_depth); - // subtract the clearance check if it's enabled, letting the user see the - // parts of the keycap that will hit the cherry switch - if ($clearance_check) %clearance_check(); - } - - // both stem and support are optional - if ($stem_type != "disable" || ($stabilizers != [] && $stabilizer_type != "disable")) { - dished($keytop_thickness, $inverted_dish) { - translate([0, 0, $stem_inset]) { - if ($stabilizer_type != "disable") stems_for($stabilizers, $stabilizer_type); - - if ($stem_type != "disable") stems_for($stem_positions, $stem_type); - } + if ($inner_shape_type != "disable") difference() { + inner_shape(); + inside_features(); } + + subtractive_features(inset) { + children(); + }; } +} - if ($support_type != "disable"){ - inside() { - translate([0, 0, $stem_inset]) { - if ($stabilizer_type != "disable") support_for($stabilizers, $stabilizer_type); - - // always render stem support even if there isn't a stem. - // rendering flat support w/no stem is much more common than a hollow keycap - // so if you want a hollow keycap you'll have to turn support off entirely - support_for($stem_positions, $stem_type); - } +module display_key(inset=false) { + minkowski() { + outer_shape(); + minkowski_object(); + // minkowski doesn't work with difference + additive_features(false) { + children(); + }; } - } } // actual full key with space carved out and keystem/stabilizer connectors diff --git a/src/key_transformations.scad b/src/key_transformations.scad index f873a59..e443ae0 100644 --- a/src/key_transformations.scad +++ b/src/key_transformations.scad @@ -177,3 +177,14 @@ module debug() { %children(); } + +module display() { + $height_slices = 30; + $minkowski_facets = 64; + $shape_facets = 64; + $stem_type = "disable"; + $support_type = "disable"; + $stem_support_type = "disable"; + + children(); +} diff --git a/src/layouts/lets_split/default.scad b/src/layouts/lets_split/default.scad index e3824de..c236d43 100644 --- a/src/layouts/lets_split/default.scad +++ b/src/layouts/lets_split/default.scad @@ -1,7 +1,7 @@ include <../layout.scad> // negative numbers are used for spacing -lets_split_mapping = [ +lets_split_layout = [ [1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1], @@ -9,5 +9,5 @@ lets_split_mapping = [ ]; module lets_split_default(profile) { - layout(lets_split_mapping, profile, row_sculpting_offset=1) children(); + layout(lets_split_layout, profile, row_sculpting_offset=1) children(); } diff --git a/src/settings.scad b/src/settings.scad index cd1fe25..0869c1e 100644 --- a/src/settings.scad +++ b/src/settings.scad @@ -187,3 +187,12 @@ $warning_color = [1,0,0, 0.15]; // how many facets circles will have when used in these features $minkowski_facets = 30; $shape_facets =30; + +// 3d surface settings +// unused for now +$3d_surface_size = 100; +// resolution in each axis. 10 = 10 divisions per x/y = 100 points total +$3d_surface_step = 5; + +// "flat" / "dished" / "disable" +$inner_shape_type = "flat";