From 45940e8059bf36619a835fad8e453ef9c47de36e Mon Sep 17 00:00:00 2001 From: Bob Date: Fri, 24 Apr 2020 19:51:43 -0400 Subject: [PATCH] Fix up custimzer.scad also add git post-commit hook to generate it, so I don't have to do this again --- customizer.scad | 1859 +++++++++++++++++++++++++++++++++++---- customizer_base.scad | 5 +- openscad.rb | 4 +- src/key.scad | 6 +- src/libraries/skin.scad | 63 -- src/settings.scad | 39 +- 6 files changed, 1718 insertions(+), 258 deletions(-) diff --git a/customizer.scad b/customizer.scad index 559dca7..c3a31b9 100644 --- a/customizer.scad +++ b/customizer.scad @@ -11,6 +11,8 @@ row = 1; // [5,1,2,3,4,0] // What does the top of your key say? legend = ""; +$using_customizer = true; + /* [Basic-Settings] */ // Length in units of key. A regular key is 1 unit; spacebar is usually 6.25 @@ -20,7 +22,10 @@ $key_length = 1.0; // Range not working in thingiverse customizer atm [1:0.25:16 $stem_type = "cherry"; // [cherry, alps, rounded_cherry, box_cherry, filled, disable] // The stem is the hardest part to print, so this variable controls how much 'slop' there is in the stem -$stem_slop = 0.3; // Not working in thingiverse customizer atm [0:0.01:1] +// if your keycaps stick in the switch raise this value +$stem_slop = 0.35; // Not working in thingiverse customizer atm [0:0.01:1] +// broke this out. if your keycaps are falling off lower this value. only works for cherry stems rn +$stem_inner_slop = 0.2; // Font size used for text $font_size = 6; @@ -28,6 +33,10 @@ $font_size = 6; // Set this to true if you're making a spacebar! $inverted_dish = false; +// change aggressiveness of double sculpting +// this is the radius of the cylinder the keytops are placed on +$double_sculpt_radius = 200; + // Support type. default is "flared" for easy FDM printing; bars are more realistic, and flat could be for artisans $support_type = "flared"; // [flared, bars, flat, disable] @@ -35,9 +44,11 @@ $support_type = "flared"; // [flared, bars, flat, disable] // Supports for the stem, as it often comes off during printing. Reccommended for most machines $stem_support_type = "tines"; // [tines, brim, disabled] -/* [Advanced] */ +// make legends outset instead of inset. +// broken off from artisan support since who wants outset legends? +$outset_legends = false; -/* Key */ +/* [Key] */ // 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 @@ -58,10 +69,17 @@ $height_difference = 4; $total_depth = 11.5; // The tilt of the dish in degrees. divided by key height $top_tilt = -6; +// the y tilt of the dish in degrees. divided by key width. +// for double axis sculpted keycaps and probably not much else +$top_tilt_y = 0; // How skewed towards the back the top is (0 for center) $top_skew = 1.7; -/* Stem */ +// how skewed towards the right the top is. unused, but implemented. +// for double axis sculpted keycaps and probably not much else +$top_skew_x = 0; + +/* [Stem] */ // How far the throw distance of the switch is. determines how far the 'cross' in the cherry switch digs into the stem, and how long the keystem needs to be before supports can start. luckily, alps and cherries have a pretty similar throw. can modify to have stouter keycaps for low profile switches, etc $stem_throw = 4; @@ -75,7 +93,11 @@ $stem_inset = 0; // How many degrees to rotate the stems. useful for sideways keycaps, maybe $stem_rotation = 0; -/* Shape */ +// enable to have stem support extend past the keycap bottom, to (hopefully) the next +// keycap. only works on tines right now +$extra_long_stem_support = false; + +/* [Shape] */ // Key shape type, determines the shape of the key. default is 'rounded square' $key_shape_type = "rounded_square"; @@ -85,7 +107,7 @@ $linear_extrude_height_adjustment = 0; // If you're doing fancy bowed keycap sides, this controls how many slices you take $height_slices = 1; -/* Dish */ +/* [Dish] */ // What type of dish the key has. note that unlike stems and supports a dish ALWAYS gets rendered. $dish_type = "cylindrical"; // [cylindrical, spherical, sideways cylindrical, old spherical, disable] @@ -100,25 +122,29 @@ $dish_overdraw_width = 0; // Same as width but for height $dish_overdraw_height = 0; -/* Misc */ +/* [Misc] */ // There's a bevel on the cherry stems to aid insertion / guard against first layer squishing making a hard-to-fit stem. $cherry_bevel = true; // How tall in mm the stem support is, if there is any. stem support sits around the keystem and helps to secure it while printing. -$stem_support_height = 0.4; +$stem_support_height = .8; // Font used for text $font="DejaVu Sans Mono:style=Book"; // Whether or not to render fake keyswitches to check clearances $clearance_check = false; -// Use linear_extrude instead of hull slices to make the shape of the key // Should be faster, also required for concave shapes +// Use linear_extrude instead of hull slices to make the shape of the key $linear_extrude_shape = false; -//should the key be rounded? unnecessary for most printers, and very slow + +// warns in trajectory.scad but it looks benign +// brand new, more correct, hopefully faster, lots more work +$skin_extrude_shape = false; +// This doesn't work very well, but you can try $rounded_key = false; //minkowski radius. radius of sphere used in minkowski sum for minkowski_key function. 1.75 for G20 $minkowski_radius = .33; -/* Features */ +/* [Features] */ //insert locating bump $key_bump = false; @@ -129,17 +155,32 @@ $key_bump_edge = 0.4; /* [Hidden] */ +// set this to true if you are making double sculpted keycaps +$double_sculpted = false; + //list of legends to place on a key format: [text, halign, valign, size] //halign = "left" or "center" or "right" //valign = "top" or "center" or "bottom" // Currently does not work with thingiverse customizer, and actually breaks it $legends = []; +//list of front legends to place on a key format: [text, halign, valign, size] +//halign = "left" or "center" or "right" +//valign = "top" or "center" or "bottom" +// Currently does not work with thingiverse customizer, and actually breaks it +$front_legends = []; + +// print legends on the front of the key instead of the top +$front_print_legends = false; + +// how recessed inset legends / artisans are from the top of the key +$inset_legend_depth = 0.2; + // Dimensions of alps stem $alps_stem = [4.45, 2.25]; -// Enable stabilizers. If you don't want stabilizers use disable; most other keycaps use Cherry stabilizers -$stabilizer_type = "cherry"; // [cherry, rounded_cherry, alps, disable] +// Enable stabilizer stems, to hold onto your cherry or costar stabilizers +$stabilizer_type = "costar_stabilizer"; // [costar_stabilizer, cherry_stabilizer, disable] // Ternaries are ONLY for customizer. they will NOT work if you're using this in // OpenSCAD. you should use stabilized(), openSCAD customizer, @@ -224,43 +265,54 @@ module 6_25uh() { // unlike the other files with their own dedicated folders, this one doesn't // need a selector. I wrote one anyways for customizer support though -module dcs_row(n=3) { - // names, so I don't go crazy +module dcs_row(row=3, column=0) { $bottom_key_width = 18.16; $bottom_key_height = 18.16; $width_difference = 6; $height_difference = 4; $dish_type = "cylindrical"; - $dish_depth = 1; + $dish_depth = 0.5; $dish_skew_x = 0; $dish_skew_y = 0; $top_skew = 1.75; - if (n == 5) { - $total_depth = 11.5; + $top_tilt_y = side_tilt(column); + extra_height = $double_sculpted ? extra_side_tilt_height(column) : 0; + + // this dish depth should match the depth of the uberdishing in fully sculpted mode + // but it doesn't, and it's very slight for any reasonable double sculpting + /* $dish_depth = $double_sculpt_radius - sin(acos(top_total_key_width()/2 /$double_sculpt_radius)) * $double_sculpt_radius; */ + + /* echo("DISH DEPTH", $dish_depth, "column", column); */ + + // 5/0 is a hack so you can do these in a for loop + if (row == 5 || row == 0) { + $total_depth = 11.5 + extra_height; $top_tilt = -6; + children(); - } else if (n == 1) { - $total_depth = 8.5; + } else if (row == 1) { + $total_depth = 8.5 + extra_height; $top_tilt = -1; + children(); - } else if (n == 2) { - $total_depth = 7.5; + } else if (row == 2) { + $total_depth = 7.5 + extra_height; $top_tilt = 3; children(); - } else if (n == 3) { - $total_depth = 6; + } else if (row == 3) { + $total_depth = 6 + extra_height; $top_tilt = 7; children(); - } else if (n == 4) { - $total_depth = 6; + } else if (row == 4) { + $total_depth = 6 + extra_height; $top_tilt = 16; children(); } else { children(); } } -module oem_row(n=3) { +module oem_row(row=3, column = 0) { $bottom_key_width = 18.05; $bottom_key_height = 18.05; $width_difference = 5.8; @@ -272,37 +324,40 @@ module oem_row(n=3) { $top_skew = 1.75; $stem_inset = 1.2; - if (n == 5) { - $total_depth = 11.2; + $top_tilt_y = side_tilt(column); + extra_height = $double_sculpted ? extra_side_tilt_height(column) : 0; + + if (row == 5 || row == 0) { + $total_depth = 11.2 + extra_height; $top_tilt = -3; children(); - } else if (n == 1) { - $total_depth = 9.45; + } else if (row == 1) { + $total_depth = 9.45 + extra_height; $top_tilt = 1; children(); - } else if (n == 2) { - $total_depth = 9; + } else if (row == 2) { + $total_depth = 9 + extra_height; $top_tilt = 6; children(); - } else if (n == 3) { - $total_depth = 9.25; + } else if (row == 3) { + $total_depth = 9.25 + extra_height; $top_tilt = 9; children(); - } else if (n == 4) { - $total_depth = 9.25; + } else if (row == 4) { + $total_depth = 9.25 + extra_height; $top_tilt = 10; children(); } else { children(); } } -module dsa_row(n=3) { +module dsa_row(row=3, column = 0) { $key_shape_type = "sculpted_square"; $bottom_key_width = 18.24; // 18.4; $bottom_key_height = 18.24; // 18.4; $width_difference = 6; // 5.7; $height_difference = 6; // 5.7; - $top_tilt = n == 5 ? -21 : (n-3) * 7; + $top_tilt = row == 5 ? -21 : (row-3) * 7; $top_skew = 0; $dish_type = "spherical"; $dish_depth = 1.2; @@ -310,31 +365,32 @@ module dsa_row(n=3) { $dish_skew_y = 0; $height_slices = 10; $enable_side_sculpting = true; - // might wanna change this if you don't minkowski - // do you even minkowski bro $corner_radius = 0.25; + $top_tilt_y = side_tilt(column); + extra_height = $double_sculpted ? extra_side_tilt_height(column) : 0; + depth_raisers = [0, 3.5, 1, 0, 1, 3]; - if (n == 5) { - $total_depth = 8.1 + depth_raisers[n]; + if (row < 1 || row > 4) { + $total_depth = 8.1 + depth_raisers[row] + extra_height; children(); - } else if (n == 1) { - $total_depth = 8.1 + depth_raisers[n]; + } else if (row == 1) { + $total_depth = 8.1 + depth_raisers[row] + extra_height; children(); - } else if (n == 2) { - $total_depth = 8.1 + depth_raisers[n]; + } else if (row == 2) { + $total_depth = 8.1 + depth_raisers[row] + extra_height; children(); - } else if (n == 3) { - $total_depth = 8.1 + depth_raisers[n]; + } else if (row == 3) { + $total_depth = 8.1 + depth_raisers[row] + extra_height; children(); - } else if (n == 4) { - $total_depth = 8.1 + depth_raisers[n]; + } else if (row == 4) { + $total_depth = 8.1 + depth_raisers[row] + extra_height; children(); } else { children(); } } -module sa_row(n=3) { +module sa_row(n=3, column=0) { $key_shape_type = "sculpted_square"; $bottom_key_width = 18.4; $bottom_key_height = 18.4; @@ -349,31 +405,40 @@ module sa_row(n=3) { // might wanna change this if you don't minkowski // do you even minkowski bro $corner_radius = 0.25; + + // this is _incredibly_ intensive + /* $rounded_key = true; */ + + $top_tilt_y = side_tilt(column); + extra_height = $double_sculpted ? extra_side_tilt_height(column) : 0; + // 5th row is usually unsculpted or the same as the row below it // making a super-sculpted top row (or bottom row!) would be real easy // bottom row would just be 13 tilt and 14.89 total depth // top row would be something new entirely - 18 tilt maybe? - if (n == 1 || n == 5){ - $total_depth = 14.89; + if (n <= 1){ + $total_depth = 14.89 + extra_height; $top_tilt = -13; children(); } else if (n == 2) { - $total_depth = 12.925; + $total_depth = 12.925 + extra_height; $top_tilt = -7; children(); - } else if (n == 3 || n == 5) { - $total_depth = 12.5; + } else if (n == 3) { + $total_depth = 12.5 + extra_height; $top_tilt = 0; children(); } else if (n == 4){ - $total_depth = 12.925; + $total_depth = 12.925 + extra_height; $top_tilt = 7; children(); } else { + $total_depth = 12.5 + extra_height; + $top_tilt = 0; children(); } } -module g20_row(n=3) { +module g20_row(row=3, column = 0) { $bottom_key_width = 18.16; $bottom_key_height = 18.16; $width_difference = 2; @@ -391,25 +456,116 @@ module g20_row(n=3) { //also, $rounded_key = true; - if (n == 5) { - $total_depth = 6 + abs((n-3) * 0.5); + $top_tilt_y = side_tilt(column); + extra_height = $double_sculpted ? extra_side_tilt_height(column) : 0; + + $total_depth = 6 + abs((row-3) * 0.5) + extra_height; + + if (row == 5 || row == 0) { + $top_tilt = -18.55; children(); - } else if (n == 1) { - $total_depth = 6 + abs((n-3) * 0.5); - $top_tilt = (n-3) * 7 + 2.5; + } else if (row == 1) { + $top_tilt = (row-3) * 7 + 2.5; children(); - } else if (n == 2) { - $total_depth = 6 + abs((n-3) * 0.5); - $top_tilt = (n-3) * 7 + 2.5; + } else if (row == 2) { + $top_tilt = (row-3) * 7 + 2.5; children(); - } else if (n == 3) { - $total_depth = 6 + abs((n-3) * 0.5); - $top_tilt = (n-3) * 7 + 2.5; + } else if (row == 3) { + $top_tilt = (row-3) * 7 + 2.5; children(); - } else if (n == 4) { - $total_depth = 6 + abs((n-3) * 0.5); - $top_tilt = (n-3) * 7 + 2.5; + } else if (row == 4) { + $top_tilt = (row-3) * 7 + 2.5; + children(); + } else { + children(); + } +} +// my own measurements +module hipro_row(row=3, column=0) { + $key_shape_type = "sculpted_square"; + + $bottom_key_width = 18.35; + $bottom_key_height = 18.17; + + $width_difference = ($bottom_key_width - 12.3); + $height_difference = ($bottom_key_height - 12.65); + $dish_type = "spherical"; + $dish_depth = 0.75; + $dish_skew_x = 0; + $dish_skew_y = 0; + $top_skew = 0; + $height_slices = 10; + // might wanna change this if you don't minkowski + // do you even minkowski bro + $corner_radius = 0.25; + + $top_tilt_y = side_tilt(column); + extra_height = $double_sculpted ? extra_side_tilt_height(column) : 0; + + if (row <= 1){ + $total_depth = 13.7 + extra_height; + // TODO I didn't change these yet + $top_tilt = -13; + children(); + } else if (row == 2) { + $total_depth = 11.1 + extra_height; + $top_tilt = -7; + children(); + } else if (row == 3) { + $total_depth = 11.1 + extra_height; + $top_tilt = 7; + children(); + } else if (row == 4 || row == 5){ + $total_depth = 12.25 + extra_height; + $top_tilt = 13; + children(); + } else { + children(); + } +} +module grid_row(row=3, column = 0) { + $bottom_key_width = 18.16; + $bottom_key_height = 18.16; + $width_difference = 0.2; + $height_difference = 0.2; + $top_tilt = 0; + $top_skew = 0; + $dish_type = "old spherical"; + // something weird is going on with this and legends - can't put it below 1.2 or they won't show + $dish_depth = 1; + $dish_skew_x = 0; + $dish_skew_y = 0; + + $linear_extrude_shape = true; + + + $dish_overdraw_width = -8; + $dish_overdraw_height = -8; + + $minkowski_radius = 0.5; + //also, + /* $rounded_key = true; */ + + $top_tilt_y = side_tilt(column); + extra_height = $double_sculpted ? extra_side_tilt_height(column) : 0; + + $total_depth = 6 + abs((row-3) * 0.5) + extra_height; + + if (row == 5 || row == 0) { + /* $top_tilt = -18.55; */ + children(); + } else if (row == 1) { + /* $top_tilt = (row-3) * 7 + 2.5; */ + children(); + } else if (row == 2) { + /* $top_tilt = (row-3) * 7 + 2.5; */ + children(); + } else if (row == 3) { + /* $top_tilt = (row-3) * 7 + 2.5; */ + children(); + } else if (row == 4) { + /* $top_tilt = (row-3) * 7 + 2.5; */ children(); } else { children(); @@ -417,17 +573,21 @@ module g20_row(n=3) { } // man, wouldn't it be so cool if functions were first order -module key_profile(key_profile_type, row) { +module key_profile(key_profile_type, row, column=0) { if (key_profile_type == "dcs") { - dcs_row(row) children(); + dcs_row(row, column) children(); } else if (key_profile_type == "oem") { - oem_row(row) children(); + oem_row(row, column) children(); } else if (key_profile_type == "dsa") { - dsa_row(row) children(); + dsa_row(row, column) children(); } else if (key_profile_type == "sa") { - sa_row(row) children(); + sa_row(row, column) children(); } else if (key_profile_type == "g20") { - g20_row(row) children(); + g20_row(row, column) children(); + } else if (key_profile_type == "hipro") { + hipro_row(row, column) children(); + } else if (key_profile_type == "grid") { + grid_row(row, column) children(); } else if (key_profile_type == "disable") { children(); } else { @@ -480,8 +640,9 @@ module iso_enter() { $key_height = 2; $top_tilt = 0; + $stem_support_type = "disable"; $key_shape_type = "iso_enter"; - $linear_extrude_shape = true; + /* $linear_extrude_shape = true; */ $linear_extrude_height_adjustment = 19.05 * 0.5; // this equals (unit_length(1.5) - unit_length(1.25)) / 2 $dish_overdraw_width = 2.38125; @@ -500,6 +661,11 @@ module translate_u(x=0, y=0, z=0){ translate([x * unit, y*unit, z*unit]) children(); } +module no_stem_support() { + $stem_support_type = "disable"; + children(); +} + module brimmed_stem_support(height = 0.4) { $stem_support_type = "brim"; $stem_support_height = height; @@ -512,6 +678,11 @@ module tined_stem_support(height = 0.4) { children(); } +module unsupported_stem() { + $stem_support_type = "disable"; + children(); +} + module rounded() { $rounded_key = true; children(); @@ -527,9 +698,9 @@ module rotated() { children(); } -module stabilized(mm=12, vertical = false, type="cherry") { +module stabilized(mm=12, vertical = false, type=undef) { if (vertical) { - $stabilizer_type = type; + $stabilizer_type = (type ? type : ($stabilizer_type ? $stabilizer_type : "costar_stabilizer")); $stabilizers = [ [0, mm], [0, -mm] @@ -537,7 +708,9 @@ module stabilized(mm=12, vertical = false, type="cherry") { children(); } else { - $stabilizer_type = type; + $stabilizer_type = (type ? type : ($stabilizer_type ? $stabilizer_type : "costar_stabilizer")); + + $stabilizers = [ [mm, 0], [-mm, 0] @@ -612,15 +785,33 @@ module legend(text, position=[0,0], size=undef) { children(); } +module front_legend(text, position=[0,0], size=undef) { + font_size = size == undef ? $font_size : size; + $front_legends = [for(L=[$front_legends, [[text, position, font_size]]], a=L) a]; + children(); +} + module bump(depth=undef) { $key_bump = true; $key_bump_depth = depth == undef ? $key_bump_depth : depth; children(); } + +// kinda dirty, but it works +// might not work great with fully sculpted profiles yet +module upside_down() { + if ($stem_inner_slop != 0) { + echo("it is recommended you set inner stem slop to 0 when you use upside_down()"); + } + // $top_tilt*2 because top_placement rotates by top_tilt for us + // first rotate 180 to get the keycaps to face the same direction + rotate([0,0,180]) top_placement() rotate([180+$top_tilt*2,0,0]) { + children(); + } +} module arrows(profile, rows = [4,4,4,3]) { positions = [[0, 0], [1, 0], [2, 0], [1, 1]]; - /* doesn't work in thingiverse customize lol */ - /* legends = [UNICODE WUZ HERE]; */ + legends = ["←", "↓", "→", "↑"]; for (i = [0:3]) { translate_u(positions[i].x, positions[i].y) key_profile(profile, rows[i]) legend(legends[i]) cherry() key(true); @@ -649,8 +840,9 @@ module row_profile(profile, unsculpted = false) { translate_u(0, -row) key_profile(profile, unsculpted ? 3 : rows[row]) children(); } } - // files +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -658,15 +850,18 @@ module row_profile(profile, unsculpted = false) { // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -676,15 +871,20 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height // actual mm key width and height at the top function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; + +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; $fs=.1; unit = 19.05; // corollary is rounded_square // NOT 3D +function unit_length(length) = unit * (length - 1) + 18.16; + module ISO_enter_shape(size, delta, progress){ width = size[0]; height = size[1]; - function unit_length(length) = unit * (length - 1) + 18.16; // in order to make the ISO keycap shape generic, we are going to express the @@ -713,6 +913,44 @@ module ISO_enter_shape(size, delta, progress){ } } } + +function iso_enter_vertices(width, height, width_ratio, height_ratio, wd, hd) = [ + [ 0-wd, 0-hd], // top right + [ 0-wd, -height+hd], // bottom right + [-width * width_ratio+wd, -height+hd], // bottom left + [-width * width_ratio+wd,-height * height_ratio+hd], // inner middle point + [ -width+wd,-height * height_ratio+hd], // outer middle point + [ -width+wd, 0-hd] // top left +] + [ + [(width * width_ratio)/2, height/2 ], + [(width * width_ratio)/2, height/2 ], + [(width * width_ratio)/2, height/2 ], + [(width * width_ratio)/2, height/2 ], + [(width * width_ratio)/2, height/2 ], + [(width * width_ratio)/2, height/2 ] +]; + +// no rounding on the corners at all +function skin_iso_enter_shape(size, delta, progress, thickness_difference) = + iso_enter_vertices(size.x, size.y, unit_length(1.25) / unit_length(1.5), unit_length(1) / unit_length(2), thickness_difference/2 + delta.x * progress/2, thickness_difference/2 + delta.y * progress/2); +function rounded_rectangle_profile(size=[1,1],r=1,fn=32) = [ + for (index = [0:fn-1]) + let(a = index/fn*360) + r * [cos(a), sin(a)] + + sign_x(index, fn) * [size[0]/2-r,0] + + sign_y(index, fn) * [0,size[1]/2-r] +]; + +function sign_x(i,n) = + i < n/4 || i > n-n/4 ? 1 : + i > n/4 && i < n-n/4 ? -1 : + 0; + +function sign_y(i,n) = + i > 0 && i < n/2 ? 1 : + i > n/2 ? -1 : + 0; + // rounded square shape with additional sculpting functions to better approximate // When sculpting sides, how much in should the tops come @@ -757,6 +995,38 @@ module sculpted_square_shape(size, delta, progress) { } } +// fudging the hell out of this, I don't remember what the negative-offset-positive-offset was doing in the module above +// also no 'bowed' square shape for now +function skin_sculpted_square_shape(size, delta, progress) = + let( + width = size[0], + height = size[1], + + width_difference = delta[0], + height_difference = delta[1], + // makes the sides bow + extra_side_size = side_sculpting(progress), + // makes the rounded corners of the keycap grow larger as they move upwards + extra_corner_size = corner_sculpting(progress), + + // computed values for this slice + extra_width_this_slice = (width_difference - extra_side_size) * progress, + extra_height_this_slice = (height_difference - extra_side_size) * progress, + extra_corner_radius_this_slice = ($corner_radius + extra_corner_size), + + square_size = [ + width - extra_width_this_slice, + height - extra_height_this_slice + ] + ) rounded_rectangle_profile(square_size - [extra_corner_radius_this_slice, extra_corner_radius_this_slice]/4, fn=36, r=extra_corner_radius_this_slice/1.5 + $more_side_sculpting_factor * progress); + + /* offset(r = extra_corner_radius_this_slice) { + offset(r = -extra_corner_radius_this_slice) { + side_rounded_square(square_size, r = $more_side_sculpting_factor * progress); + } + } */ + + module side_rounded_square(size, r) { iw = size.x - 2 * r; ih = size.y - 2 * r; @@ -774,6 +1044,24 @@ module side_rounded_square(size, r) { square([iw, ih], center=true); } } +function rounded_rectangle_profile(size=[1,1],r=1,fn=32) = [ + for (index = [0:fn-1]) + let(a = index/fn*360) + r * [cos(a), sin(a)] + + sign_x(index, fn) * [size[0]/2-r,0] + + sign_y(index, fn) * [0,size[1]/2-r] +]; + +function sign_x(i,n) = + i < n/4 || i > n-n/4 ? 1 : + i > n/4 && i < n-n/4 ? -1 : + 0; + +function sign_y(i,n) = + i > 0 && i < n/2 ? 1 : + i > n/2 ? -1 : + 0; + module rounded_square_shape(size, delta, progress, center = true) { width = size[0]; height = size[1]; @@ -796,6 +1084,11 @@ module rounded_square_shape(size, delta, progress, center = true) { ); } } + +// for skin + +function skin_rounded_square(size, delta, progress) = + rounded_rectangle_profile(size - (delta * progress), fn=36, r=$corner_radius); module square_shape(size, delta, progress){ square(size - delta * progress, center = true); } @@ -811,6 +1104,10 @@ module oblong_shape(size, delta, progress) { } } +// size: at progress 0, the shape is supposed to be this size +// delta: at progress 1, the keycap is supposed to be size - delta +// progress: how far along the transition you are. +// it's not always linear - specifically sculpted_square module key_shape(size, delta, progress = 0) { if ($key_shape_type == "iso_enter") { ISO_enter_shape(size, delta, progress); @@ -826,6 +1123,17 @@ module key_shape(size, delta, progress = 0) { echo("Warning: unsupported $key_shape_type"); } } + +function skin_key_shape(size, delta, progress = 0, thickness_difference) = + $key_shape_type == "rounded_square" ? + skin_rounded_square(size, delta, progress) : + $key_shape_type == "sculpted_square" ? + skin_sculpted_square_shape(size, delta, progress) : + $key_shape_type == "iso_enter" ? + skin_iso_enter_shape(size, delta, progress, thickness_difference) : + echo("Warning: unsupported $key_shape_type for skin shape. disable skin_extrude_shape or pick a new shape"); +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -833,15 +1141,18 @@ module key_shape(size, delta, progress = 0) { // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -852,6 +1163,10 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -859,7 +1174,7 @@ extra_vertical = 0.6; module inside_cherry_cross(slop) { // inside cross // translation purely for aesthetic purposes, to get rid of that awful lattice - translate([0,0,-0.005]) { + translate([0,0,-SMALLEST_POSSIBLE]) { linear_extrude(height = $stem_throw) { square(cherry_cross(slop, extra_vertical)[0], center=true); square(cherry_cross(slop, extra_vertical)[1], center=true); @@ -884,9 +1199,11 @@ module cherry_stem(depth, slop) { } } - inside_cherry_cross(slop); + inside_cherry_cross($stem_inner_slop); } } +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -894,15 +1211,18 @@ module cherry_stem(depth, slop) { // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -912,6 +1232,12 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height // actual mm key width and height at the top function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; + +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -919,15 +1245,18 @@ function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1 // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -938,6 +1267,10 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -945,7 +1278,7 @@ extra_vertical = 0.6; module inside_cherry_cross(slop) { // inside cross // translation purely for aesthetic purposes, to get rid of that awful lattice - translate([0,0,-0.005]) { + translate([0,0,-SMALLEST_POSSIBLE]) { linear_extrude(height = $stem_throw) { square(cherry_cross(slop, extra_vertical)[0], center=true); square(cherry_cross(slop, extra_vertical)[1], center=true); @@ -970,7 +1303,7 @@ module cherry_stem(depth, slop) { } } - inside_cherry_cross(slop); + inside_cherry_cross($stem_inner_slop); } } @@ -983,6 +1316,8 @@ module rounded_cherry_stem(depth, slop) { inside_cherry_cross(slop); } } +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -990,15 +1325,18 @@ module rounded_cherry_stem(depth, slop) { // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -1008,6 +1346,12 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height // actual mm key width and height at the top function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; + +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -1015,15 +1359,18 @@ function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1 // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -1034,6 +1381,10 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -1041,7 +1392,7 @@ extra_vertical = 0.6; module inside_cherry_cross(slop) { // inside cross // translation purely for aesthetic purposes, to get rid of that awful lattice - translate([0,0,-0.005]) { + translate([0,0,-SMALLEST_POSSIBLE]) { linear_extrude(height = $stem_throw) { square(cherry_cross(slop, extra_vertical)[0], center=true); square(cherry_cross(slop, extra_vertical)[1], center=true); @@ -1066,7 +1417,7 @@ module cherry_stem(depth, slop) { } } - inside_cherry_cross(slop); + inside_cherry_cross($stem_inner_slop); } } @@ -1097,13 +1448,75 @@ module filled_stem() { shape($wall_thickness); } +SMALLEST_POSSIBLE = 1/128; + +// I use functions when I need to compute special variables off of other special variables +// functions need to be explicitly included, unlike special variables, which +// just need to have been set before they are used. hence this file + +// cherry stem dimensions +function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; + +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + +// box (kailh) switches have a bit less to work with +function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; + +// .005 purely for aesthetics, to get rid of that ugly crosshatch +function cherry_cross(slop, extra_vertical = 0) = [ + // horizontal tine + [4.03 + slop, 1.25 + slop / 3], + // vertical tine + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], +]; + +// actual mm key width and height +function total_key_width(delta = 0) = $bottom_key_width + $unit * ($key_length - 1) - delta; +function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height - 1) - delta; + +// actual mm key width and height at the top +function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; +function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; + +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; + +// extra length to the vertical tine of the inside cherry cross +// splits the stem into halves - allows easier fitment +extra_vertical = 0.6; + +module inside_cherry_stabilizer_cross(slop) { + // inside cross + // translation purely for aesthetic purposes, to get rid of that awful lattice + translate([0,0,-SMALLEST_POSSIBLE]) { + linear_extrude(height = $stem_throw) { + square(cherry_cross(slop, extra_vertical)[0], center=true); + square(cherry_cross(slop, extra_vertical)[1], center=true); + } + } +} + +module cherry_stabilizer_stem(depth, slop) { + difference(){ + // outside shape + linear_extrude(height = depth) { + offset(r=1){ + square(outer_cherry_stabilizer_stem(slop) - [2,2], center=true); + } + } + + inside_cherry_stabilizer_cross(slop); + } +} //whole stem, alps or cherry, trimmed to fit module stem(stem_type, depth, slop){ if (stem_type == "alps") { alps_stem(depth, slop); - } else if (stem_type == "cherry") { + } else if (stem_type == "cherry" || stem_type == "costar_stabilizer") { cherry_stem(depth, slop); } else if (stem_type == "rounded_cherry") { rounded_cherry_stem(depth, slop); @@ -1111,12 +1524,17 @@ module stem(stem_type, depth, slop){ box_cherry_stem(depth, slop); } else if (stem_type == "filled") { filled_stem(); + } else if (stem_type == "cherry_stabilizer") { + cherry_stabilizer_stem(depth, slop); } else if (stem_type == "disable") { children(); } else { - echo("Warning: unsupported $stem_type"); + echo("Warning: unsupported $stem_type: "); + echo(stem_type); } } +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -1124,15 +1542,18 @@ module stem(stem_type, depth, slop){ // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -1142,6 +1563,12 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height // actual mm key width and height at the top function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; + +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -1149,15 +1576,18 @@ function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1 // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -1168,6 +1598,10 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -1175,7 +1609,7 @@ extra_vertical = 0.6; module inside_cherry_cross(slop) { // inside cross // translation purely for aesthetic purposes, to get rid of that awful lattice - translate([0,0,-0.005]) { + translate([0,0,-SMALLEST_POSSIBLE]) { linear_extrude(height = $stem_throw) { square(cherry_cross(slop, extra_vertical)[0], center=true); square(cherry_cross(slop, extra_vertical)[1], center=true); @@ -1200,7 +1634,7 @@ module cherry_stem(depth, slop) { } } - inside_cherry_cross(slop); + inside_cherry_cross($stem_inner_slop); } } @@ -1211,7 +1645,7 @@ module brim_support(stem_type, stem_support_height, slop) { square($alps_stem + [2,2], center=true); } } - } else if (stem_type == "cherry") { + } else if (stem_type == "cherry" || stem_type == "costar_stabilizer") { difference() { linear_extrude(height = stem_support_height){ offset(r=1){ @@ -1234,10 +1668,22 @@ module brim_support(stem_type, stem_support_height, slop) { } } + inside_cherry_cross(slop); + } + } else if (stem_type == "cherry_stabilizer") { + difference() { + linear_extrude(height = stem_support_height){ + offset(r=1){ + square(outer_cherry_stabilizer_stem(slop) + [2,2], center=true); + } + } + inside_cherry_cross(slop); } } } +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -1245,15 +1691,18 @@ module brim_support(stem_type, stem_support_height, slop) { // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -1263,6 +1712,12 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height // actual mm key width and height at the top function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; + +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -1270,15 +1725,18 @@ function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1 // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -1289,6 +1747,10 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; + // extra length to the vertical tine of the inside cherry cross // splits the stem into halves - allows easier fitment extra_vertical = 0.6; @@ -1296,7 +1758,7 @@ extra_vertical = 0.6; module inside_cherry_cross(slop) { // inside cross // translation purely for aesthetic purposes, to get rid of that awful lattice - translate([0,0,-0.005]) { + translate([0,0,-SMALLEST_POSSIBLE]) { linear_extrude(height = $stem_throw) { square(cherry_cross(slop, extra_vertical)[0], center=true); square(cherry_cross(slop, extra_vertical)[1], center=true); @@ -1321,26 +1783,72 @@ module cherry_stem(depth, slop) { } } - inside_cherry_cross(slop); + inside_cherry_cross($stem_inner_slop); } } module centered_tines(stem_support_height) { - translate([0,0,$stem_support_height / 2]) cube([total_key_width($wall_thickness), 1, $stem_support_height], center = true); - translate([0,0,$stem_support_height / 2]) cube([1, total_key_height($wall_thickness), $stem_support_height], center = true); + if ($key_length < 2) { + translate([0,0,$stem_support_height / 2]) { + cube([total_key_width(), 0.5, $stem_support_height], center = true); + } + } + + translate([0,0,$stem_support_height / 2]) { + cube([ + 1, + total_key_height(), + $stem_support_height + ], + center = true); + } } module tines_support(stem_type, stem_support_height, slop) { - if (stem_type == "cherry") { + extra_height = $extra_long_stem_support ? ($unit - total_key_height()) + 0.1 : -$wall_thickness/4; // fudge + extra_width = $extra_long_stem_support ? ($unit - total_key_width()) + 0.1 : -$wall_thickness/4; + + if (stem_type == "cherry" || stem_type == "costar_stabilizer") { difference () { union() { - if ($key_length < 2) translate([0,0,$stem_support_height / 2]) cube([total_key_width($wall_thickness), 1, $stem_support_height], center = true); - translate([2,0,$stem_support_height / 2]) cube([1, total_key_height($wall_thickness), $stem_support_height], center = true); - translate([-2,0,$stem_support_height / 2]) cube([1, total_key_height($wall_thickness), $stem_support_height], center = true); + if ($key_length < 2) { + translate([0,0,$stem_support_height / 2]) { + cube([ + total_key_width() + extra_width*2, + 0.5, + $stem_support_height + ], center = true); + } + } + + // 2 vertical tines holding either side of the cruciform + for (x = [1.15, -1.15]) { + translate([x,0,$stem_support_height / 2]) { + cube([ + 0.5, + total_key_height() + extra_height*2, // this is to extend past + $stem_support_height + ], center = true); + } + } } inside_cherry_cross(slop); } + } else if (stem_type == "cherry_stabilizer") { + difference () { + for (x = [1.15, -1.15]) { + translate([x,0,$stem_support_height / 2]) { + cube([ + 1, + total_key_height($wall_thickness), + $stem_support_height + ], center = true); + } + } + + inside_cherry_stabilizer_cross(slop); + } } else if (stem_type == "box_cherry") { difference () { centered_tines(stem_support_height); @@ -1564,6 +2072,9 @@ module spherical_dish(width, height, depth, inverted){ } } } +module flat_dish(width, height, depth, inverted){ + cube([width + 100,height + 100, depth], center=true); +} //geodesic looks much better, but runs very slow for anything above a 2u geodesic=false; @@ -1581,12 +2092,16 @@ module dish(width, height, depth, inverted) { } else if ($dish_type == "old spherical") { old_spherical_dish(width, height, depth, inverted); + } else if ($dish_type == "flat") { + flat_dish(width, height, depth, inverted); } else if ($dish_type == "disable") { // else no dish } else { echo("WARN: $dish_type unsupported"); } } +SMALLEST_POSSIBLE = 1/128; + // I use functions when I need to compute special variables off of other special variables // functions need to be explicitly included, unlike special variables, which // just need to have been set before they are used. hence this file @@ -1594,15 +2109,18 @@ module dish(width, height, depth, inverted) { // cherry stem dimensions function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2]; +// cherry stabilizer stem dimensions +function outer_cherry_stabilizer_stem(slop) = [4.85 - slop * 2, 6.05 - slop * 2]; + // box (kailh) switches have a bit less to work with function outer_box_cherry_stem(slop) = [6 - slop, 6 - slop]; // .005 purely for aesthetics, to get rid of that ugly crosshatch function cherry_cross(slop, extra_vertical = 0) = [ // horizontal tine - [4.03 + slop, 1.15 + slop / 3], + [4.03 + slop, 1.25 + slop / 3], // vertical tine - [1.25 + slop / 3, 4.23 + extra_vertical + slop / 3 + .005], + [1.15 + slop / 3, 4.23 + extra_vertical + slop / 3 + SMALLEST_POSSIBLE], ]; // actual mm key width and height @@ -1613,12 +2131,28 @@ function total_key_height(delta = 0) = $bottom_key_height + $unit * ($key_height function top_total_key_width() = $bottom_key_width + ($unit * ($key_length - 1)) - $width_difference; function top_total_key_height() = $bottom_key_height + ($unit * ($key_height - 1)) - $height_difference; +function side_tilt(column) = asin($unit * column / $double_sculpt_radius); +// tan of 0 is 0, division by 0 is nan, so we have to guard +function extra_side_tilt_height(column) = side_tilt(column) ? ($double_sculpt_radius - (unit * abs(column)) / tan(abs(side_tilt(column)))) : 0; +// 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) { + translate([0,0,loft + 500]){ + cube(1000, center=true); + } +} + // figures out the scale factor needed to make a 45 degree wall function scale_for_45(height, starting_size) = (height * 2 + starting_size) / starting_size; // complicated since we want the different stems to work well // also kind of messy... oh well module flared(stem_type, loft, height) { + // flat support. straight flat support has a tendency to shear off; flared support + // all the way to the top has a tendency to warp the outside of the keycap. + // hopefully the compromise is both + flat(stem_type, loft + height/4, height); + translate([0,0,loft]){ if (stem_type == "rounded_cherry") { linear_extrude(height=height, scale = scale_for_45(height, $rounded_cherry_stem_d)){ @@ -1638,6 +2172,13 @@ module flared(stem_type, loft, height) { square(outer_box_cherry_stem($stem_slop) - [2,2], center=true); } } + } else if (stem_type == "cherry_stabilizer") { + cherry_scale = [scale_for_45(height, outer_cherry_stabilizer_stem($stem_slop)[0]), scale_for_45(height, outer_cherry_stabilizer_stem($stem_slop)[1])]; + linear_extrude(height=height, scale = cherry_scale){ + offset(r=1){ + square(outer_cherry_stabilizer_stem($stem_slop) - [2,2], center=true); + } + } } else { // always render cherry if no stem type. this includes stem_type = false! // this avoids a bug where the keycap is rendered filled when not desired @@ -1794,8 +2335,841 @@ module geodesic_sphere(r=-1, d=-1) { polyhedron(points=subdiv_icos[0], faces=subdiv_icos[1]); } +// for skin hulls +// very minimal set of linalg functions needed by so3, se3 etc. + +// cross and norm are builtins +//function cross(x,y) = [x[1]*y[2]-x[2]*y[1], x[2]*y[0]-x[0]*y[2], x[0]*y[1]-x[1]*y[0]]; +//function norm(v) = sqrt(v*v); + +function vec3(p) = len(p) < 3 ? concat(p,0) : p; +function vec4(p) = let (v3=vec3(p)) len(v3) < 4 ? concat(v3,1) : v3; +function unit(v) = v/norm(v); + +function identity3()=[[1,0,0],[0,1,0],[0,0,1]]; +function identity4()=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + + +function take3(v) = [v[0],v[1],v[2]]; +function tail3(v) = [v[3],v[4],v[5]]; +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function rot_trace(m) = m[0][0] + m[1][1] + m[2][2]; +function rot_cos_angle(m) = (rot_trace(m)-1)/2; + +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function translation_part(m) = [m[0][3],m[1][3],m[2][3]]; +function transpose_3(m) = [[m[0][0],m[1][0],m[2][0]],[m[0][1],m[1][1],m[2][1]],[m[0][2],m[1][2],m[2][2]]]; +function transpose_4(m) = [[m[0][0],m[1][0],m[2][0],m[3][0]], + [m[0][1],m[1][1],m[2][1],m[3][1]], + [m[0][2],m[1][2],m[2][2],m[3][2]], + [m[0][3],m[1][3],m[2][3],m[3][3]]]; +function invert_rt(m) = construct_Rt(transpose_3(rotation_part(m)), -(transpose_3(rotation_part(m)) * translation_part(m))); +function construct_Rt(R,t) = [concat(R[0],t[0]),concat(R[1],t[1]),concat(R[2],t[2]),[0,0,0,1]]; + +// Hadamard product of n-dimensional arrays +function hadamard(a,b) = !(len(a)>0) ? a*b : [ for(i = [0:len(a)-1]) hadamard(a[i],b[i]) ]; +// so3 + +// very minimal set of linalg functions needed by so3, se3 etc. + +// cross and norm are builtins +//function cross(x,y) = [x[1]*y[2]-x[2]*y[1], x[2]*y[0]-x[0]*y[2], x[0]*y[1]-x[1]*y[0]]; +//function norm(v) = sqrt(v*v); + +function vec3(p) = len(p) < 3 ? concat(p,0) : p; +function vec4(p) = let (v3=vec3(p)) len(v3) < 4 ? concat(v3,1) : v3; +function unit(v) = v/norm(v); + +function identity3()=[[1,0,0],[0,1,0],[0,0,1]]; +function identity4()=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + + +function take3(v) = [v[0],v[1],v[2]]; +function tail3(v) = [v[3],v[4],v[5]]; +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function rot_trace(m) = m[0][0] + m[1][1] + m[2][2]; +function rot_cos_angle(m) = (rot_trace(m)-1)/2; + +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function translation_part(m) = [m[0][3],m[1][3],m[2][3]]; +function transpose_3(m) = [[m[0][0],m[1][0],m[2][0]],[m[0][1],m[1][1],m[2][1]],[m[0][2],m[1][2],m[2][2]]]; +function transpose_4(m) = [[m[0][0],m[1][0],m[2][0],m[3][0]], + [m[0][1],m[1][1],m[2][1],m[3][1]], + [m[0][2],m[1][2],m[2][2],m[3][2]], + [m[0][3],m[1][3],m[2][3],m[3][3]]]; +function invert_rt(m) = construct_Rt(transpose_3(rotation_part(m)), -(transpose_3(rotation_part(m)) * translation_part(m))); +function construct_Rt(R,t) = [concat(R[0],t[0]),concat(R[1],t[1]),concat(R[2],t[2]),[0,0,0,1]]; + +// Hadamard product of n-dimensional arrays +function hadamard(a,b) = !(len(a)>0) ? a*b : [ for(i = [0:len(a)-1]) hadamard(a[i],b[i]) ]; + +function rodrigues_so3_exp(w, A, B) = [ +[1.0 - B*(w[1]*w[1] + w[2]*w[2]), B*(w[0]*w[1]) - A*w[2], B*(w[0]*w[2]) + A*w[1]], +[B*(w[0]*w[1]) + A*w[2], 1.0 - B*(w[0]*w[0] + w[2]*w[2]), B*(w[1]*w[2]) - A*w[0]], +[B*(w[0]*w[2]) - A*w[1], B*(w[1]*w[2]) + A*w[0], 1.0 - B*(w[0]*w[0] + w[1]*w[1])] +]; + +function so3_exp(w) = so3_exp_rad(w/180*PI); +function so3_exp_rad(w) = +combine_so3_exp(w, + w*w < 1e-8 + ? so3_exp_1(w*w) + : w*w < 1e-6 + ? so3_exp_2(w*w) + : so3_exp_3(w*w)); + +function combine_so3_exp(w,AB) = rodrigues_so3_exp(w,AB[0],AB[1]); + +// Taylor series expansions close to 0 +function so3_exp_1(theta_sq) = [ + 1 - 1/6*theta_sq, + 0.5 +]; + +function so3_exp_2(theta_sq) = [ + 1.0 - theta_sq * (1.0 - theta_sq/20) / 6, + 0.5 - 0.25/6 * theta_sq +]; + +function so3_exp_3_0(theta_deg, inv_theta) = [ + sin(theta_deg) * inv_theta, + (1 - cos(theta_deg)) * (inv_theta * inv_theta) +]; + +function so3_exp_3(theta_sq) = so3_exp_3_0(sqrt(theta_sq)*180/PI, 1/sqrt(theta_sq)); + + +function rot_axis_part(m) = [m[2][1] - m[1][2], m[0][2] - m[2][0], m[1][0] - m[0][1]]*0.5; + +function so3_ln(m) = 180/PI*so3_ln_rad(m); +function so3_ln_rad(m) = so3_ln_0(m, + cos_angle = rot_cos_angle(m), + preliminary_result = rot_axis_part(m)); + +function so3_ln_0(m, cos_angle, preliminary_result) = +so3_ln_1(m, cos_angle, preliminary_result, + sin_angle_abs = sqrt(preliminary_result*preliminary_result)); + +function so3_ln_1(m, cos_angle, preliminary_result, sin_angle_abs) = + cos_angle > sqrt(1/2) + ? sin_angle_abs > 0 + ? preliminary_result * asin(sin_angle_abs)*PI/180 / sin_angle_abs + : preliminary_result + : cos_angle > -sqrt(1/2) + ? preliminary_result * acos(cos_angle)*PI/180 / sin_angle_abs + : so3_get_symmetric_part_rotation( + preliminary_result, + m, + angle = PI - asin(sin_angle_abs)*PI/180, + d0 = m[0][0] - cos_angle, + d1 = m[1][1] - cos_angle, + d2 = m[2][2] - cos_angle + ); + +function so3_get_symmetric_part_rotation(preliminary_result, m, angle, d0, d1, d2) = +so3_get_symmetric_part_rotation_0(preliminary_result,angle,so3_largest_column(m, d0, d1, d2)); + +function so3_get_symmetric_part_rotation_0(preliminary_result, angle, c_max) = + angle * unit(c_max * preliminary_result < 0 ? -c_max : c_max); + +function so3_largest_column(m, d0, d1, d2) = + d0*d0 > d1*d1 && d0*d0 > d2*d2 + ? [d0, (m[1][0]+m[0][1])/2, (m[0][2]+m[2][0])/2] + : d1*d1 > d2*d2 + ? [(m[1][0]+m[0][1])/2, d1, (m[2][1]+m[1][2])/2] + : [(m[0][2]+m[2][0])/2, (m[2][1]+m[1][2])/2, d2]; + +__so3_test = [12,-125,110]; +echo(UNITTEST_so3=norm(__so3_test-so3_ln(so3_exp(__so3_test))) < 1e-8); + +function combine_se3_exp(w, ABt) = construct_Rt(rodrigues_so3_exp(w, ABt[0], ABt[1]), ABt[2]); + +// [A,B,t] +function se3_exp_1(t,w) = concat( + so3_exp_1(w*w), + [t + 0.5 * cross(w,t)] +); + +function se3_exp_2(t,w) = se3_exp_2_0(t,w,w*w); +function se3_exp_2_0(t,w,theta_sq) = +se3_exp_23( + so3_exp_2(theta_sq), + C = (1.0 - theta_sq/20) / 6, + t=t,w=w); + +function se3_exp_3(t,w) = se3_exp_3_0(t,w,sqrt(w*w)*180/PI,1/sqrt(w*w)); + +function se3_exp_3_0(t,w,theta_deg,inv_theta) = +se3_exp_23( + so3_exp_3_0(theta_deg = theta_deg, inv_theta = inv_theta), + C = (1 - sin(theta_deg) * inv_theta) * (inv_theta * inv_theta), + t=t,w=w); + +function se3_exp_23(AB,C,t,w) = +[AB[0], AB[1], t + AB[1] * cross(w,t) + C * cross(w,cross(w,t)) ]; + +function se3_exp(mu) = se3_exp_0(t=take3(mu),w=tail3(mu)/180*PI); + +function se3_exp_0(t,w) = +combine_se3_exp(w, +// Evaluate by Taylor expansion when near 0 + w*w < 1e-8 + ? se3_exp_1(t,w) + : w*w < 1e-6 + ? se3_exp_2(t,w) + : se3_exp_3(t,w) +); + +function se3_ln(m) = se3_ln_to_deg(se3_ln_rad(m)); +function se3_ln_to_deg(v) = concat(take3(v),tail3(v)*180/PI); + +function se3_ln_rad(m) = se3_ln_0(m, + rot = so3_ln_rad(rotation_part(m))); +function se3_ln_0(m,rot) = se3_ln_1(m,rot, + theta = sqrt(rot*rot)); +function se3_ln_1(m,rot,theta) = se3_ln_2(m,rot,theta, + shtot = theta > 0.00001 ? sin(theta/2*180/PI)/theta : 0.5, + halfrotator = so3_exp_rad(rot * -.5)); +function se3_ln_2(m,rot,theta,shtot,halfrotator) = +concat( (halfrotator * translation_part(m) - + (theta > 0.001 + ? rot * ((translation_part(m) * rot) * (1-2*shtot) / (rot*rot)) + : rot * ((translation_part(m) * rot)/24) + )) / (2 * shtot), rot); + +__se3_test = [20,-40,60,-80,100,-120]; +echo(UNITTEST_se3=norm(__se3_test-se3_ln(se3_exp(__se3_test))) < 1e-8); +// very minimal set of linalg functions needed by so3, se3 etc. + +// cross and norm are builtins +//function cross(x,y) = [x[1]*y[2]-x[2]*y[1], x[2]*y[0]-x[0]*y[2], x[0]*y[1]-x[1]*y[0]]; +//function norm(v) = sqrt(v*v); + +function vec3(p) = len(p) < 3 ? concat(p,0) : p; +function vec4(p) = let (v3=vec3(p)) len(v3) < 4 ? concat(v3,1) : v3; +function unit(v) = v/norm(v); + +function identity3()=[[1,0,0],[0,1,0],[0,0,1]]; +function identity4()=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + + +function take3(v) = [v[0],v[1],v[2]]; +function tail3(v) = [v[3],v[4],v[5]]; +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function rot_trace(m) = m[0][0] + m[1][1] + m[2][2]; +function rot_cos_angle(m) = (rot_trace(m)-1)/2; + +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function translation_part(m) = [m[0][3],m[1][3],m[2][3]]; +function transpose_3(m) = [[m[0][0],m[1][0],m[2][0]],[m[0][1],m[1][1],m[2][1]],[m[0][2],m[1][2],m[2][2]]]; +function transpose_4(m) = [[m[0][0],m[1][0],m[2][0],m[3][0]], + [m[0][1],m[1][1],m[2][1],m[3][1]], + [m[0][2],m[1][2],m[2][2],m[3][2]], + [m[0][3],m[1][3],m[2][3],m[3][3]]]; +function invert_rt(m) = construct_Rt(transpose_3(rotation_part(m)), -(transpose_3(rotation_part(m)) * translation_part(m))); +function construct_Rt(R,t) = [concat(R[0],t[0]),concat(R[1],t[1]),concat(R[2],t[2]),[0,0,0,1]]; + +// Hadamard product of n-dimensional arrays +function hadamard(a,b) = !(len(a)>0) ? a*b : [ for(i = [0:len(a)-1]) hadamard(a[i],b[i]) ]; +// List helpers + +/*! + Flattens a list one level: + + flatten([[0,1],[2,3]]) => [0,1,2,3] +*/ +function flatten(list) = [ for (i = list, v = i) v ]; + + +/*! + Creates a list from a range: + + range([0:2:6]) => [0,2,4,6] +*/ +function range(r) = [ for(x=r) x ]; + +/*! + Reverses a list: + + reverse([1,2,3]) => [3,2,1] +*/ +function reverse(list) = [for (i = [len(list)-1:-1:0]) list[i]]; + +/*! + Extracts a subarray from index begin (inclusive) to end (exclusive) + FIXME: Change name to use list instead of array? + + subarray([1,2,3,4], 1, 2) => [2,3] +*/ +function subarray(list,begin=0,end=-1) = [ + let(end = end < 0 ? len(list) : end) + for (i = [begin : 1 : end-1]) + list[i] +]; + +/*! + Returns a copy of a list with the element at index i set to x + + set([1,2,3,4], 2, 5) => [1,2,5,4] +*/ +function set(list, i, x) = [for (i_=[0:len(list)-1]) i == i_ ? x : list[i_]]; + +/*! + Remove element from the list by index. + remove([4,3,2,1],1) => [4,2,1] +*/ +function remove(list, i) = [for (i_=[0:1:len(list)-2]) list[i_ < i ? i_ : i_ + 1]]; + +/*! + Creates a rotation matrix + + xyz = euler angles = rz * ry * rx + axis = rotation_axis * rotation_angle +*/ +function rotation(xyz=undef, axis=undef) = + xyz != undef && axis != undef ? undef : + xyz == undef ? se3_exp([0,0,0,axis[0],axis[1],axis[2]]) : + len(xyz) == undef ? rotation(axis=[0,0,xyz]) : + (len(xyz) >= 3 ? rotation(axis=[0,0,xyz[2]]) : identity4()) * + (len(xyz) >= 2 ? rotation(axis=[0,xyz[1],0]) : identity4()) * + (len(xyz) >= 1 ? rotation(axis=[xyz[0],0,0]) : identity4()); + +/*! + Creates a scaling matrix +*/ +function scaling(v) = [ + [v[0],0,0,0], + [0,v[1],0,0], + [0,0,v[2],0], + [0,0,0,1], +]; + +/*! + Creates a translation matrix +*/ +function translation(v) = [ + [1,0,0,v[0]], + [0,1,0,v[1]], + [0,0,1,v[2]], + [0,0,0,1], +]; + +// Convert between cartesian and homogenous coordinates +function project(x) = subarray(x,end=len(x)-1) / x[len(x)-1]; + +function transform(m, list) = [for (p=list) project(m * vec4(p))]; +function to_3d(list) = [ for(v = list) vec3(v) ]; +// List helpers + +/*! + Flattens a list one level: + + flatten([[0,1],[2,3]]) => [0,1,2,3] +*/ +function flatten(list) = [ for (i = list, v = i) v ]; + + +/*! + Creates a list from a range: + + range([0:2:6]) => [0,2,4,6] +*/ +function range(r) = [ for(x=r) x ]; + +/*! + Reverses a list: + + reverse([1,2,3]) => [3,2,1] +*/ +function reverse(list) = [for (i = [len(list)-1:-1:0]) list[i]]; + +/*! + Extracts a subarray from index begin (inclusive) to end (exclusive) + FIXME: Change name to use list instead of array? + + subarray([1,2,3,4], 1, 2) => [2,3] +*/ +function subarray(list,begin=0,end=-1) = [ + let(end = end < 0 ? len(list) : end) + for (i = [begin : 1 : end-1]) + list[i] +]; + +/*! + Returns a copy of a list with the element at index i set to x + + set([1,2,3,4], 2, 5) => [1,2,5,4] +*/ +function set(list, i, x) = [for (i_=[0:len(list)-1]) i == i_ ? x : list[i_]]; + +/*! + Remove element from the list by index. + remove([4,3,2,1],1) => [4,2,1] +*/ +function remove(list, i) = [for (i_=[0:1:len(list)-2]) list[i_ < i ? i_ : i_ + 1]]; +function square(size) = [[-size,-size], [-size,size], [size,size], [size,-size]] / 2; + +function circle(r) = [for (i=[0:$fn-1]) let (a=i*360/$fn) r * [cos(a), sin(a)]]; + +function regular(r, n) = circle(r, $fn=n); + +function rectangle_profile(size=[1,1]) = [ + // The first point is the anchor point, put it on the point corresponding to [cos(0),sin(0)] + [ size[0]/2, 0], + [ size[0]/2, size[1]/2], + [-size[0]/2, size[1]/2], + [-size[0]/2, -size[1]/2], + [ size[0]/2, -size[1]/2], +]; + +// FIXME: Move rectangle and rounded rectangle from extrusion +// very minimal set of linalg functions needed by so3, se3 etc. + +// cross and norm are builtins +//function cross(x,y) = [x[1]*y[2]-x[2]*y[1], x[2]*y[0]-x[0]*y[2], x[0]*y[1]-x[1]*y[0]]; +//function norm(v) = sqrt(v*v); + +function vec3(p) = len(p) < 3 ? concat(p,0) : p; +function vec4(p) = let (v3=vec3(p)) len(v3) < 4 ? concat(v3,1) : v3; +function unit(v) = v/norm(v); + +function identity3()=[[1,0,0],[0,1,0],[0,0,1]]; +function identity4()=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + + +function take3(v) = [v[0],v[1],v[2]]; +function tail3(v) = [v[3],v[4],v[5]]; +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function rot_trace(m) = m[0][0] + m[1][1] + m[2][2]; +function rot_cos_angle(m) = (rot_trace(m)-1)/2; + +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function translation_part(m) = [m[0][3],m[1][3],m[2][3]]; +function transpose_3(m) = [[m[0][0],m[1][0],m[2][0]],[m[0][1],m[1][1],m[2][1]],[m[0][2],m[1][2],m[2][2]]]; +function transpose_4(m) = [[m[0][0],m[1][0],m[2][0],m[3][0]], + [m[0][1],m[1][1],m[2][1],m[3][1]], + [m[0][2],m[1][2],m[2][2],m[3][2]], + [m[0][3],m[1][3],m[2][3],m[3][3]]]; +function invert_rt(m) = construct_Rt(transpose_3(rotation_part(m)), -(transpose_3(rotation_part(m)) * translation_part(m))); +function construct_Rt(R,t) = [concat(R[0],t[0]),concat(R[1],t[1]),concat(R[2],t[2]),[0,0,0,1]]; + +// Hadamard product of n-dimensional arrays +function hadamard(a,b) = !(len(a)>0) ? a*b : [ for(i = [0:len(a)-1]) hadamard(a[i],b[i]) ]; +// so3 + +// very minimal set of linalg functions needed by so3, se3 etc. + +// cross and norm are builtins +//function cross(x,y) = [x[1]*y[2]-x[2]*y[1], x[2]*y[0]-x[0]*y[2], x[0]*y[1]-x[1]*y[0]]; +//function norm(v) = sqrt(v*v); + +function vec3(p) = len(p) < 3 ? concat(p,0) : p; +function vec4(p) = let (v3=vec3(p)) len(v3) < 4 ? concat(v3,1) : v3; +function unit(v) = v/norm(v); + +function identity3()=[[1,0,0],[0,1,0],[0,0,1]]; +function identity4()=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + + +function take3(v) = [v[0],v[1],v[2]]; +function tail3(v) = [v[3],v[4],v[5]]; +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function rot_trace(m) = m[0][0] + m[1][1] + m[2][2]; +function rot_cos_angle(m) = (rot_trace(m)-1)/2; + +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function translation_part(m) = [m[0][3],m[1][3],m[2][3]]; +function transpose_3(m) = [[m[0][0],m[1][0],m[2][0]],[m[0][1],m[1][1],m[2][1]],[m[0][2],m[1][2],m[2][2]]]; +function transpose_4(m) = [[m[0][0],m[1][0],m[2][0],m[3][0]], + [m[0][1],m[1][1],m[2][1],m[3][1]], + [m[0][2],m[1][2],m[2][2],m[3][2]], + [m[0][3],m[1][3],m[2][3],m[3][3]]]; +function invert_rt(m) = construct_Rt(transpose_3(rotation_part(m)), -(transpose_3(rotation_part(m)) * translation_part(m))); +function construct_Rt(R,t) = [concat(R[0],t[0]),concat(R[1],t[1]),concat(R[2],t[2]),[0,0,0,1]]; + +// Hadamard product of n-dimensional arrays +function hadamard(a,b) = !(len(a)>0) ? a*b : [ for(i = [0:len(a)-1]) hadamard(a[i],b[i]) ]; + +function rodrigues_so3_exp(w, A, B) = [ +[1.0 - B*(w[1]*w[1] + w[2]*w[2]), B*(w[0]*w[1]) - A*w[2], B*(w[0]*w[2]) + A*w[1]], +[B*(w[0]*w[1]) + A*w[2], 1.0 - B*(w[0]*w[0] + w[2]*w[2]), B*(w[1]*w[2]) - A*w[0]], +[B*(w[0]*w[2]) - A*w[1], B*(w[1]*w[2]) + A*w[0], 1.0 - B*(w[0]*w[0] + w[1]*w[1])] +]; + +function so3_exp(w) = so3_exp_rad(w/180*PI); +function so3_exp_rad(w) = +combine_so3_exp(w, + w*w < 1e-8 + ? so3_exp_1(w*w) + : w*w < 1e-6 + ? so3_exp_2(w*w) + : so3_exp_3(w*w)); + +function combine_so3_exp(w,AB) = rodrigues_so3_exp(w,AB[0],AB[1]); + +// Taylor series expansions close to 0 +function so3_exp_1(theta_sq) = [ + 1 - 1/6*theta_sq, + 0.5 +]; + +function so3_exp_2(theta_sq) = [ + 1.0 - theta_sq * (1.0 - theta_sq/20) / 6, + 0.5 - 0.25/6 * theta_sq +]; + +function so3_exp_3_0(theta_deg, inv_theta) = [ + sin(theta_deg) * inv_theta, + (1 - cos(theta_deg)) * (inv_theta * inv_theta) +]; + +function so3_exp_3(theta_sq) = so3_exp_3_0(sqrt(theta_sq)*180/PI, 1/sqrt(theta_sq)); + + +function rot_axis_part(m) = [m[2][1] - m[1][2], m[0][2] - m[2][0], m[1][0] - m[0][1]]*0.5; + +function so3_ln(m) = 180/PI*so3_ln_rad(m); +function so3_ln_rad(m) = so3_ln_0(m, + cos_angle = rot_cos_angle(m), + preliminary_result = rot_axis_part(m)); + +function so3_ln_0(m, cos_angle, preliminary_result) = +so3_ln_1(m, cos_angle, preliminary_result, + sin_angle_abs = sqrt(preliminary_result*preliminary_result)); + +function so3_ln_1(m, cos_angle, preliminary_result, sin_angle_abs) = + cos_angle > sqrt(1/2) + ? sin_angle_abs > 0 + ? preliminary_result * asin(sin_angle_abs)*PI/180 / sin_angle_abs + : preliminary_result + : cos_angle > -sqrt(1/2) + ? preliminary_result * acos(cos_angle)*PI/180 / sin_angle_abs + : so3_get_symmetric_part_rotation( + preliminary_result, + m, + angle = PI - asin(sin_angle_abs)*PI/180, + d0 = m[0][0] - cos_angle, + d1 = m[1][1] - cos_angle, + d2 = m[2][2] - cos_angle + ); + +function so3_get_symmetric_part_rotation(preliminary_result, m, angle, d0, d1, d2) = +so3_get_symmetric_part_rotation_0(preliminary_result,angle,so3_largest_column(m, d0, d1, d2)); + +function so3_get_symmetric_part_rotation_0(preliminary_result, angle, c_max) = + angle * unit(c_max * preliminary_result < 0 ? -c_max : c_max); + +function so3_largest_column(m, d0, d1, d2) = + d0*d0 > d1*d1 && d0*d0 > d2*d2 + ? [d0, (m[1][0]+m[0][1])/2, (m[0][2]+m[2][0])/2] + : d1*d1 > d2*d2 + ? [(m[1][0]+m[0][1])/2, d1, (m[2][1]+m[1][2])/2] + : [(m[0][2]+m[2][0])/2, (m[2][1]+m[1][2])/2, d2]; + +__so3_test = [12,-125,110]; +echo(UNITTEST_so3=norm(__so3_test-so3_ln(so3_exp(__so3_test))) < 1e-8); + +function combine_se3_exp(w, ABt) = construct_Rt(rodrigues_so3_exp(w, ABt[0], ABt[1]), ABt[2]); + +// [A,B,t] +function se3_exp_1(t,w) = concat( + so3_exp_1(w*w), + [t + 0.5 * cross(w,t)] +); + +function se3_exp_2(t,w) = se3_exp_2_0(t,w,w*w); +function se3_exp_2_0(t,w,theta_sq) = +se3_exp_23( + so3_exp_2(theta_sq), + C = (1.0 - theta_sq/20) / 6, + t=t,w=w); + +function se3_exp_3(t,w) = se3_exp_3_0(t,w,sqrt(w*w)*180/PI,1/sqrt(w*w)); + +function se3_exp_3_0(t,w,theta_deg,inv_theta) = +se3_exp_23( + so3_exp_3_0(theta_deg = theta_deg, inv_theta = inv_theta), + C = (1 - sin(theta_deg) * inv_theta) * (inv_theta * inv_theta), + t=t,w=w); + +function se3_exp_23(AB,C,t,w) = +[AB[0], AB[1], t + AB[1] * cross(w,t) + C * cross(w,cross(w,t)) ]; + +function se3_exp(mu) = se3_exp_0(t=take3(mu),w=tail3(mu)/180*PI); + +function se3_exp_0(t,w) = +combine_se3_exp(w, +// Evaluate by Taylor expansion when near 0 + w*w < 1e-8 + ? se3_exp_1(t,w) + : w*w < 1e-6 + ? se3_exp_2(t,w) + : se3_exp_3(t,w) +); + +function se3_ln(m) = se3_ln_to_deg(se3_ln_rad(m)); +function se3_ln_to_deg(v) = concat(take3(v),tail3(v)*180/PI); + +function se3_ln_rad(m) = se3_ln_0(m, + rot = so3_ln_rad(rotation_part(m))); +function se3_ln_0(m,rot) = se3_ln_1(m,rot, + theta = sqrt(rot*rot)); +function se3_ln_1(m,rot,theta) = se3_ln_2(m,rot,theta, + shtot = theta > 0.00001 ? sin(theta/2*180/PI)/theta : 0.5, + halfrotator = so3_exp_rad(rot * -.5)); +function se3_ln_2(m,rot,theta,shtot,halfrotator) = +concat( (halfrotator * translation_part(m) - + (theta > 0.001 + ? rot * ((translation_part(m) * rot) * (1-2*shtot) / (rot*rot)) + : rot * ((translation_part(m) * rot)/24) + )) / (2 * shtot), rot); + +__se3_test = [20,-40,60,-80,100,-120]; +echo(UNITTEST_se3=norm(__se3_test-se3_ln(se3_exp(__se3_test))) < 1e-8); +// very minimal set of linalg functions needed by so3, se3 etc. + +// cross and norm are builtins +//function cross(x,y) = [x[1]*y[2]-x[2]*y[1], x[2]*y[0]-x[0]*y[2], x[0]*y[1]-x[1]*y[0]]; +//function norm(v) = sqrt(v*v); + +function vec3(p) = len(p) < 3 ? concat(p,0) : p; +function vec4(p) = let (v3=vec3(p)) len(v3) < 4 ? concat(v3,1) : v3; +function unit(v) = v/norm(v); + +function identity3()=[[1,0,0],[0,1,0],[0,0,1]]; +function identity4()=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + + +function take3(v) = [v[0],v[1],v[2]]; +function tail3(v) = [v[3],v[4],v[5]]; +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function rot_trace(m) = m[0][0] + m[1][1] + m[2][2]; +function rot_cos_angle(m) = (rot_trace(m)-1)/2; + +function rotation_part(m) = [take3(m[0]),take3(m[1]),take3(m[2])]; +function translation_part(m) = [m[0][3],m[1][3],m[2][3]]; +function transpose_3(m) = [[m[0][0],m[1][0],m[2][0]],[m[0][1],m[1][1],m[2][1]],[m[0][2],m[1][2],m[2][2]]]; +function transpose_4(m) = [[m[0][0],m[1][0],m[2][0],m[3][0]], + [m[0][1],m[1][1],m[2][1],m[3][1]], + [m[0][2],m[1][2],m[2][2],m[3][2]], + [m[0][3],m[1][3],m[2][3],m[3][3]]]; +function invert_rt(m) = construct_Rt(transpose_3(rotation_part(m)), -(transpose_3(rotation_part(m)) * translation_part(m))); +function construct_Rt(R,t) = [concat(R[0],t[0]),concat(R[1],t[1]),concat(R[2],t[2]),[0,0,0,1]]; + +// Hadamard product of n-dimensional arrays +function hadamard(a,b) = !(len(a)>0) ? a*b : [ for(i = [0:len(a)-1]) hadamard(a[i],b[i]) ]; +// List helpers + +/*! + Flattens a list one level: + + flatten([[0,1],[2,3]]) => [0,1,2,3] +*/ +function flatten(list) = [ for (i = list, v = i) v ]; + + +/*! + Creates a list from a range: + + range([0:2:6]) => [0,2,4,6] +*/ +function range(r) = [ for(x=r) x ]; + +/*! + Reverses a list: + + reverse([1,2,3]) => [3,2,1] +*/ +function reverse(list) = [for (i = [len(list)-1:-1:0]) list[i]]; + +/*! + Extracts a subarray from index begin (inclusive) to end (exclusive) + FIXME: Change name to use list instead of array? + + subarray([1,2,3,4], 1, 2) => [2,3] +*/ +function subarray(list,begin=0,end=-1) = [ + let(end = end < 0 ? len(list) : end) + for (i = [begin : 1 : end-1]) + list[i] +]; + +/*! + Returns a copy of a list with the element at index i set to x + + set([1,2,3,4], 2, 5) => [1,2,5,4] +*/ +function set(list, i, x) = [for (i_=[0:len(list)-1]) i == i_ ? x : list[i_]]; + +/*! + Remove element from the list by index. + remove([4,3,2,1],1) => [4,2,1] +*/ +function remove(list, i) = [for (i_=[0:1:len(list)-2]) list[i_ < i ? i_ : i_ + 1]]; + +/*! + Creates a rotation matrix + + xyz = euler angles = rz * ry * rx + axis = rotation_axis * rotation_angle +*/ +function rotation(xyz=undef, axis=undef) = + xyz != undef && axis != undef ? undef : + xyz == undef ? se3_exp([0,0,0,axis[0],axis[1],axis[2]]) : + len(xyz) == undef ? rotation(axis=[0,0,xyz]) : + (len(xyz) >= 3 ? rotation(axis=[0,0,xyz[2]]) : identity4()) * + (len(xyz) >= 2 ? rotation(axis=[0,xyz[1],0]) : identity4()) * + (len(xyz) >= 1 ? rotation(axis=[xyz[0],0,0]) : identity4()); + +/*! + Creates a scaling matrix +*/ +function scaling(v) = [ + [v[0],0,0,0], + [0,v[1],0,0], + [0,0,v[2],0], + [0,0,0,1], +]; + +/*! + Creates a translation matrix +*/ +function translation(v) = [ + [1,0,0,v[0]], + [0,1,0,v[1]], + [0,0,1,v[2]], + [0,0,0,1], +]; + +// Convert between cartesian and homogenous coordinates +function project(x) = subarray(x,end=len(x)-1) / x[len(x)-1]; + +function transform(m, list) = [for (p=list) project(m * vec4(p))]; +function to_3d(list) = [ for(v = list) vec3(v) ]; +// List helpers + +/*! + Flattens a list one level: + + flatten([[0,1],[2,3]]) => [0,1,2,3] +*/ +function flatten(list) = [ for (i = list, v = i) v ]; + + +/*! + Creates a list from a range: + + range([0:2:6]) => [0,2,4,6] +*/ +function range(r) = [ for(x=r) x ]; + +/*! + Reverses a list: + + reverse([1,2,3]) => [3,2,1] +*/ +function reverse(list) = [for (i = [len(list)-1:-1:0]) list[i]]; + +/*! + Extracts a subarray from index begin (inclusive) to end (exclusive) + FIXME: Change name to use list instead of array? + + subarray([1,2,3,4], 1, 2) => [2,3] +*/ +function subarray(list,begin=0,end=-1) = [ + let(end = end < 0 ? len(list) : end) + for (i = [begin : 1 : end-1]) + list[i] +]; + +/*! + Returns a copy of a list with the element at index i set to x + + set([1,2,3,4], 2, 5) => [1,2,5,4] +*/ +function set(list, i, x) = [for (i_=[0:len(list)-1]) i == i_ ? x : list[i_]]; + +/*! + Remove element from the list by index. + remove([4,3,2,1],1) => [4,2,1] +*/ +function remove(list, i) = [for (i_=[0:1:len(list)-2]) list[i_ < i ? i_ : i_ + 1]]; + +// Skin a set of profiles with a polyhedral mesh +module skin(profiles, loop=false /* unimplemented */) { + P = max_len(profiles); + N = len(profiles); + + profiles = [ + for (p = profiles) + for (pp = augment_profile(to_3d(p),P)) + pp + ]; + + function quad(i,P,o) = [[o+i, o+i+P, o+i%P+P+1], [o+i, o+i%P+P+1, o+i%P+1]]; + + function profile_triangles(tindex) = [ + for (index = [0:P-1]) + let (qs = quad(index+1, P, P*(tindex-1)-1)) + for (q = qs) q + ]; + + triangles = [ + for(index = [1:N-1]) + for(t = profile_triangles(index)) + t + ]; + + start_cap = [range([0:P-1])]; + end_cap = [range([P*N-1 : -1 : P*(N-1)])]; + + polyhedron(convexity=2, points=profiles, faces=concat(start_cap, triangles, end_cap)); +} + +// Augments the profile with steiner points making the total number of vertices n +function augment_profile(profile, n) = + subdivide(profile,insert_extra_vertices_0([profile_lengths(profile),dup(0,len(profile))],n-len(profile))[1]); + +function subdivide(profile,subdivisions) = let (N=len(profile)) [ + for (i = [0:N-1]) + let(n = len(subdivisions)>0 ? subdivisions[i] : subdivisions) + for (p = interpolate(profile[i],profile[(i+1)%N],n+1)) + p +]; + +function interpolate(a,b,subdivisions) = [ + for (index = [0:subdivisions-1]) + let(t = index/subdivisions) + a*(1-t)+b*t +]; + +function distribute_extra_vertex(lengths_count,ma_=-1) = ma_<0 ? distribute_extra_vertex(lengths_count, max_element(lengths_count[0])) : + concat([set(lengths_count[0],ma_,lengths_count[0][ma_] * (lengths_count[1][ma_]+1) / (lengths_count[1][ma_]+2))], [increment(lengths_count[1],max_element(lengths_count[0]),1)]); + +function insert_extra_vertices_0(lengths_count,n_extra) = n_extra <= 0 ? lengths_count : + insert_extra_vertices_0(distribute_extra_vertex(lengths_count),n_extra-1); + +// Find the index of the maximum element of arr +function max_element(arr,ma_,ma_i_=-1,i_=0) = i_ >= len(arr) ? ma_i_ : + i_ == 0 || arr[i_] > ma_ ? max_element(arr,arr[i_],i_,i_+1) : max_element(arr,ma_,ma_i_,i_+1); + +function max_len(arr) = max([for (i=arr) len(i)]); + +function increment(arr,i,x=1) = set(arr,i,arr[i]+x); + +function profile_lengths(profile) = [ + for (i = [0:len(profile)-1]) + profile_segment_length(profile,i) +]; + +function profile_segment_length(profile,i) = norm(profile[(i+1)%len(profile)] - profile[i]); + +// 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; $unit = 19.05; blue = [.2667,.5882,1]; @@ -1805,14 +3179,31 @@ yellow = [1, .6941, .2]; transparent_red = [1,0,0, 0.15]; // key shape including dish. used as the ouside and inside shape in keytop(). allows for itself to be shrunk in depth and width / height -module shape(thickness_difference, depth_difference){ +module shape(thickness_difference, depth_difference=0){ dished(depth_difference, $inverted_dish) { - color(blue) shape_hull(thickness_difference, depth_difference, 2); + color(blue) shape_hull(thickness_difference, depth_difference, $inverted_dish ? 2 : 0); } } -// shape of the key but with soft, rounded edges. much more realistic, MUCH more complex. orders of magnitude more complex +// 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(blue) minkowski(){ + // half minkowski in the z direction + color(blue) shape_hull($minkowski_radius * 2, $minkowski_radius/2, $inverted_dish ? 2 : 0); + /* cube($minkowski_radius); */ + sphere(r=$minkowski_radius, $fn=48); + } + } + /* %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() { color(blue) minkowski(){ // half minkowski in the z direction shape($minkowski_radius * 2, $minkowski_radius/2); @@ -1823,7 +3214,8 @@ module rounded_shape() { } } } -} +} */ + // basic key shape, no dish, no inside @@ -1832,7 +3224,9 @@ module rounded_shape() { // extra_slices is a hack to make inverted dishes still work module shape_hull(thickness_difference, depth_difference, extra_slices = 0){ render() { - if ($linear_extrude_shape) { + 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); @@ -1840,6 +3234,41 @@ module shape_hull(thickness_difference, depth_difference, extra_slices = 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){ @@ -1850,7 +3279,10 @@ module linear_extrude_shape_hull(thickness_difference, depth_difference, extra_s 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)); + key_shape( + [total_key_width(thickness_difference), total_key_height(thickness_difference)], + [$width_difference, $height_difference] + ); } } } @@ -1867,12 +3299,16 @@ module hull_shape_hull(thickness_difference, depth_difference, extra_slices = 0) module shape_slice(progress, thickness_difference, depth_difference) { skew_this_slice = $top_skew * progress; - depth_this_slice = ($total_depth - depth_difference) * progress; - tilt_this_slice = -$top_tilt / $key_height * progress; + x_skew_this_slice = $top_skew_x * progress; - translate([0, skew_this_slice, depth_this_slice]) { - rotate([tilt_this_slice,0,0]){ - linear_extrude(height = 0.001){ + 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), @@ -1896,29 +3332,54 @@ module inside() { } // put something at the top of the key, with no adjustments for dishing -module top_placement(depth_difference) { - translate([$dish_skew_x, $top_skew + $dish_skew_y, $total_depth - depth_difference]){ - rotate([-$top_tilt / $key_height,0,0]){ +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() { dish(top_total_key_width() + $dish_overdraw_width, top_total_key_height() + $dish_overdraw_height, $dish_depth, $inverted_dish); } -module envelope(depth_difference) { +module envelope(depth_difference=0) { s = 1.5; hull(){ cube([total_key_width() * s, total_key_height() * s, 0.01], center = true); - top_placement(0.005 + depth_difference){ + top_placement(SMALLEST_POSSIBLE + depth_difference){ cube([top_total_key_width() * s, top_total_key_height() * s, 0.01], center = true); } } } +// I think this is unused module dished_for_show() { difference(){ union() { @@ -1933,7 +3394,7 @@ module dished_for_show() { // 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. -module dished(depth_difference, inverted = false) { +module dished(depth_difference = 0, inverted = false) { intersection() { children(); difference(){ @@ -2019,15 +3480,32 @@ module clearance_check() { } } +module legends(depth=0) { + + if (len($front_legends) > 0) { + front_placement() { + if (len($front_legends) > 0) { + for (i=[0:len($front_legends)-1]) { + rotate([90,0,0]) keytext($front_legends[i][0], $front_legends[i][1], $front_legends[i][2], depth); + } + } + } + } + if (len($legends) > 0) { + top_of_key() { + // outset legend + if (len($legends) > 0) { + for (i=[0:len($legends)-1]) { + keytext($legends[i][0], $legends[i][1], $legends[i][2], depth); + } + } + } + } +} + // legends / artisan support module artisan(depth) { top_of_key() { - // outset legend - if (len($legends) > 0) { - for (i=[0:len($legends)-1]) { - keytext($legends[i][0], $legends[i][1], $legends[i][2], depth); - } - } // artisan objects / outset shape legends children(); } @@ -2042,7 +3520,7 @@ module keytop() { shape(0, 0); } // translation purely for aesthetic purposes, to get rid of that awful lattice - translate([0,0,-0.005]) { + translate([0,0,-SMALLEST_POSSIBLE]) { shape($wall_thickness, $keytop_thickness); } } @@ -2058,23 +3536,26 @@ module key(inset = false) { keytop(); if($key_bump) top_of_key() keybump($key_bump_depth, $key_bump_edge); // additive objects at the top of the key - if(!inset) artisan() children(); + if(!inset) 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(); } // subtractive objects at the top of the key - if (inset) artisan(0.3) children(); + if (inset) 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" || $stabilizer_type != "disable") { + 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); } } @@ -2106,7 +3587,10 @@ $key_length = 1.0; // Range not working in thingiverse customizer atm [1:0.25:16 $stem_type = "cherry"; // [cherry, alps, rounded_cherry, box_cherry, filled, disable] // The stem is the hardest part to print, so this variable controls how much 'slop' there is in the stem -$stem_slop = 0.3; // Not working in thingiverse customizer atm [0:0.01:1] +// if your keycaps stick in the switch raise this value +$stem_slop = 0.35; // Not working in thingiverse customizer atm [0:0.01:1] +// broke this out. if your keycaps are falling off lower this value. only works for cherry stems rn +$stem_inner_slop = 0.2; // Font size used for text $font_size = 6; @@ -2114,6 +3598,10 @@ $font_size = 6; // Set this to true if you're making a spacebar! $inverted_dish = false; +// change aggressiveness of double sculpting +// this is the radius of the cylinder the keytops are placed on +$double_sculpt_radius = 200; + // Support type. default is "flared" for easy FDM printing; bars are more realistic, and flat could be for artisans $support_type = "flared"; // [flared, bars, flat, disable] @@ -2121,9 +3609,11 @@ $support_type = "flared"; // [flared, bars, flat, disable] // Supports for the stem, as it often comes off during printing. Reccommended for most machines $stem_support_type = "tines"; // [tines, brim, disabled] -/* [Advanced] */ +// make legends outset instead of inset. +// broken off from artisan support since who wants outset legends? +$outset_legends = false; -/* Key */ +/* [Key] */ // 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 @@ -2144,10 +3634,17 @@ $height_difference = 4; $total_depth = 11.5; // The tilt of the dish in degrees. divided by key height $top_tilt = -6; +// the y tilt of the dish in degrees. divided by key width. +// for double axis sculpted keycaps and probably not much else +$top_tilt_y = 0; // How skewed towards the back the top is (0 for center) $top_skew = 1.7; -/* Stem */ +// how skewed towards the right the top is. unused, but implemented. +// for double axis sculpted keycaps and probably not much else +$top_skew_x = 0; + +/* [Stem] */ // How far the throw distance of the switch is. determines how far the 'cross' in the cherry switch digs into the stem, and how long the keystem needs to be before supports can start. luckily, alps and cherries have a pretty similar throw. can modify to have stouter keycaps for low profile switches, etc $stem_throw = 4; @@ -2161,7 +3658,11 @@ $stem_inset = 0; // How many degrees to rotate the stems. useful for sideways keycaps, maybe $stem_rotation = 0; -/* Shape */ +// enable to have stem support extend past the keycap bottom, to (hopefully) the next +// keycap. only works on tines right now +$extra_long_stem_support = false; + +/* [Shape] */ // Key shape type, determines the shape of the key. default is 'rounded square' $key_shape_type = "rounded_square"; @@ -2171,7 +3672,7 @@ $linear_extrude_height_adjustment = 0; // If you're doing fancy bowed keycap sides, this controls how many slices you take $height_slices = 1; -/* Dish */ +/* [Dish] */ // What type of dish the key has. note that unlike stems and supports a dish ALWAYS gets rendered. $dish_type = "cylindrical"; // [cylindrical, spherical, sideways cylindrical, old spherical, disable] @@ -2186,25 +3687,29 @@ $dish_overdraw_width = 0; // Same as width but for height $dish_overdraw_height = 0; -/* Misc */ +/* [Misc] */ // There's a bevel on the cherry stems to aid insertion / guard against first layer squishing making a hard-to-fit stem. $cherry_bevel = true; // How tall in mm the stem support is, if there is any. stem support sits around the keystem and helps to secure it while printing. -$stem_support_height = 0.4; +$stem_support_height = .8; // Font used for text $font="DejaVu Sans Mono:style=Book"; // Whether or not to render fake keyswitches to check clearances $clearance_check = false; -// Use linear_extrude instead of hull slices to make the shape of the key // Should be faster, also required for concave shapes +// Use linear_extrude instead of hull slices to make the shape of the key $linear_extrude_shape = false; -//should the key be rounded? unnecessary for most printers, and very slow + +// warns in trajectory.scad but it looks benign +// brand new, more correct, hopefully faster, lots more work +$skin_extrude_shape = false; +// This doesn't work very well, but you can try $rounded_key = false; //minkowski radius. radius of sphere used in minkowski sum for minkowski_key function. 1.75 for G20 $minkowski_radius = .33; -/* Features */ +/* [Features] */ //insert locating bump $key_bump = false; @@ -2215,17 +3720,32 @@ $key_bump_edge = 0.4; /* [Hidden] */ +// set this to true if you are making double sculpted keycaps +$double_sculpted = false; + //list of legends to place on a key format: [text, halign, valign, size] //halign = "left" or "center" or "right" //valign = "top" or "center" or "bottom" // Currently does not work with thingiverse customizer, and actually breaks it $legends = []; +//list of front legends to place on a key format: [text, halign, valign, size] +//halign = "left" or "center" or "right" +//valign = "top" or "center" or "bottom" +// Currently does not work with thingiverse customizer, and actually breaks it +$front_legends = []; + +// print legends on the front of the key instead of the top +$front_print_legends = false; + +// how recessed inset legends / artisans are from the top of the key +$inset_legend_depth = 0.2; + // Dimensions of alps stem $alps_stem = [4.45, 2.25]; -// Enable stabilizers. If you don't want stabilizers use disable; most other keycaps use Cherry stabilizers -$stabilizer_type = "cherry"; // [cherry, rounded_cherry, alps, disable] +// Enable stabilizer stems, to hold onto your cherry or costar stabilizers +$stabilizer_type = "costar_stabilizer"; // [costar_stabilizer, cherry_stabilizer, disable] // Ternaries are ONLY for customizer. they will NOT work if you're using this in // OpenSCAD. you should use stabilized(), openSCAD customizer, @@ -2239,6 +3759,9 @@ $stem_positions = [[0,0]]; key(); } +if (!$using_customizer) { + example_key(); +} key_profile(key_profile, row) legend(legend) { key(); diff --git a/customizer_base.scad b/customizer_base.scad index 052e734..2690216 100644 --- a/customizer_base.scad +++ b/customizer_base.scad @@ -11,6 +11,8 @@ row = 1; // [5,1,2,3,4,0] // What does the top of your key say? legend = ""; +$using_customizer = true; + include include @@ -18,8 +20,7 @@ include include include include - -use +include key_profile(key_profile, row) legend(legend) { key(); diff --git a/openscad.rb b/openscad.rb index ca2e529..f8850f5 100644 --- a/openscad.rb +++ b/openscad.rb @@ -5,12 +5,10 @@ module OpenSCAD Dir.chdir File.dirname(filename) lines = lines.flat_map do |line| + # please note we do not implement `use` at all if line =~ /(include|use)\s*<(.*)>/ # File.readlines("./#{$2}") expand("./#{$2}") - # in lieu of actually implementing `use`, we can just cull this final line from key.scad - elsif line =~ /example\_key\(\);/ - "" else line end diff --git a/src/key.scad b/src/key.scad index 0eb7ca1..261c4ba 100644 --- a/src/key.scad +++ b/src/key.scad @@ -336,7 +336,7 @@ module legends(depth=0) { for (i=[0:len($front_legends)-1]) { rotate([90,0,0]) keytext($front_legends[i][0], $front_legends[i][1], $front_legends[i][2], depth); } - } + } } } if (len($legends) > 0) { @@ -430,4 +430,6 @@ module example_key(){ key(); } -example_key(); +if (!$using_customizer) { + example_key(); +} diff --git a/src/libraries/skin.scad b/src/libraries/skin.scad index e372074..9c01afc 100644 --- a/src/libraries/skin.scad +++ b/src/libraries/skin.scad @@ -72,66 +72,3 @@ 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]; - - - - - - - -use -use -use -use - -module fakeISOEnter(thickness_difference = 0){ - // 1u is the space taken upy by a 1u keycap. - // unit is the space taken up by a unit space for a keycap. - // formula is 1u + unit *(length - 1) - - // t is all modifications to the polygon array - t = thickness_difference/2 - (19.02 - 18.16); - - function unit(length) = 19.02 * length; - - pointArray = [ - [19.05 * (-.5) + t, 19.05 * (-1) + t], - [19.05 * (0.5) - t, 19.05 * (-1) + t], - [19.05 * (0.5) - t, 19.05 * (1) - t], - [19.05 * (-0.75) + t, 19.05 * (1) - t], - [19.05 * (-0.75) + t, 19.05 * (0) + t], - [19.05 * (-0.5) + t, 19.05 * (0) + t] - ]; - - - /*translate([unit(-.5), unit(-1) + 0.86]){*/ - minkowski() { - circle($corner_radius, $fn=20); - offset(r=-$corner_radius * 2, $fn=20) polygon(points=pointArray); - } - /*}*/ -} - -function isoEnter() = [ - [19.05 * (-.5) + (19.02 - 18.16), 19.05 * (-1) + (19.02 - 18.16)], - [19.05 * (0.5) - (19.02 - 18.16), 19.05 * (-1) + (19.02 - 18.16)], - [19.05 * (0.5) - (19.02 - 18.16), 19.05 * (1) - (19.02 - 18.16)], - [19.05 * (-0.75) + (19.02 - 18.16), 19.05 * (1) - (19.02 - 18.16)], - [19.05 * (-0.75) + (19.02 - 18.16), 19.05 * (0) + (19.02 - 18.16)], - [19.05 * (-0.5) + (19.02 - 18.16), 19.05 * (0) + (19.02 - 18.16)] - ]; - - -path_definition = [ -trajectory(forward = 10, roll = 0), -]; - -// sweep -path = quantize_trajectories(path_definition, steps=100); - -// skin -myLen = len(path)-1; -trans = [ for (i=[0:len(path)-1]) transform(path[i], isoEnter()) ]; - -translate([0,10,0]) - skin(trans); diff --git a/src/settings.scad b/src/settings.scad index 482c061..bfe9521 100644 --- a/src/settings.scad +++ b/src/settings.scad @@ -18,8 +18,6 @@ $font_size = 6; // Set this to true if you're making a spacebar! $inverted_dish = false; -// set this to true if you are making double sculpted keycaps -$double_sculpted = false; // change aggressiveness of double sculpting // this is the radius of the cylinder the keytops are placed on $double_sculpt_radius = 200; @@ -31,13 +29,11 @@ $support_type = "flared"; // [flared, bars, flat, disable] // Supports for the stem, as it often comes off during printing. Reccommended for most machines $stem_support_type = "tines"; // [tines, brim, disabled] -// enable to have stem support extend past the keycap bottom, to (hopefully) the next -// keycap. only works on tines right now -$extra_long_stem_support = false; +// make legends outset instead of inset. +// broken off from artisan support since who wants outset legends? +$outset_legends = false; -/* [Advanced] */ - -/* Key */ +/* [Key] */ // 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 @@ -68,7 +64,7 @@ $top_skew = 1.7; // for double axis sculpted keycaps and probably not much else $top_skew_x = 0; -/* Stem */ +/* [Stem] */ // How far the throw distance of the switch is. determines how far the 'cross' in the cherry switch digs into the stem, and how long the keystem needs to be before supports can start. luckily, alps and cherries have a pretty similar throw. can modify to have stouter keycaps for low profile switches, etc $stem_throw = 4; @@ -82,7 +78,11 @@ $stem_inset = 0; // How many degrees to rotate the stems. useful for sideways keycaps, maybe $stem_rotation = 0; -/* Shape */ +// enable to have stem support extend past the keycap bottom, to (hopefully) the next +// keycap. only works on tines right now +$extra_long_stem_support = false; + +/* [Shape] */ // Key shape type, determines the shape of the key. default is 'rounded square' $key_shape_type = "rounded_square"; @@ -92,7 +92,7 @@ $linear_extrude_height_adjustment = 0; // If you're doing fancy bowed keycap sides, this controls how many slices you take $height_slices = 1; -/* Dish */ +/* [Dish] */ // What type of dish the key has. note that unlike stems and supports a dish ALWAYS gets rendered. $dish_type = "cylindrical"; // [cylindrical, spherical, sideways cylindrical, old spherical, disable] @@ -107,7 +107,7 @@ $dish_overdraw_width = 0; // Same as width but for height $dish_overdraw_height = 0; -/* Misc */ +/* [Misc] */ // There's a bevel on the cherry stems to aid insertion / guard against first layer squishing making a hard-to-fit stem. $cherry_bevel = true; @@ -117,19 +117,19 @@ $stem_support_height = .8; $font="DejaVu Sans Mono:style=Book"; // Whether or not to render fake keyswitches to check clearances $clearance_check = false; -// Use linear_extrude instead of hull slices to make the shape of the key // Should be faster, also required for concave shapes +// Use linear_extrude instead of hull slices to make the shape of the key $linear_extrude_shape = false; -// brand new, more correct, hopefully faster, lots more work // warns in trajectory.scad but it looks benign +// brand new, more correct, hopefully faster, lots more work $skin_extrude_shape = false; -//should the key be rounded? unnecessary for most printers, and very slow +// This doesn't work very well, but you can try $rounded_key = false; //minkowski radius. radius of sphere used in minkowski sum for minkowski_key function. 1.75 for G20 $minkowski_radius = .33; -/* Features */ +/* [Features] */ //insert locating bump $key_bump = false; @@ -140,6 +140,9 @@ $key_bump_edge = 0.4; /* [Hidden] */ +// set this to true if you are making double sculpted keycaps +$double_sculpted = false; + //list of legends to place on a key format: [text, halign, valign, size] //halign = "left" or "center" or "right" //valign = "top" or "center" or "bottom" @@ -152,10 +155,6 @@ $legends = []; // Currently does not work with thingiverse customizer, and actually breaks it $front_legends = []; -// make legends outset instead of inset. -// broken off from artisan support since who wants outset legends? -$outset_legends = false; - // print legends on the front of the key instead of the top $front_print_legends = false;