large key.scad restructuring

This commit is contained in:
Bob 2020-06-17 14:37:00 -04:00
parent a523c45f4a
commit c1bbaed9a2
13 changed files with 897 additions and 526 deletions

15
CHANGELOG.md Normal file
View File

@ -0,0 +1,15 @@
CHANGELOG:
V2.0.0:
* added $fa values for minkowski and shape - so you can customize how much rounding there is
* rejiggered `key.scad` pipeline for more clarity and less shapes
* implemented "3d_surface" dish - still in beta
* super cool though, you can even change the distribution of points on the surface! just make sure you use monotonically increasing functions
* created "hull" folder to house different ways of creating the overall key shape
* promoted "feature" folder to first-class folder with keytext and switch clearance as new residents
* wrote this changelog!
* implemented `$inner_shape_type`, use "flat" for less geometry or "disable" to make a completely solid key easily. didn't help render rounded keys though
* side-printed keycaps are first class! you can use the `sideways()` modifier to set up sideways keycaps that have flat sides to print on.
* it's much easier to make quick artisans now that the inside of the keycap is differenced from any additive features placed on top
* still todo: add a magic scaling variable so you can scale the whole world up, see if that fixes degeneracy
* still todo: rejigger supports
* still todo: rejigger inner shape. maybe just always make it flat

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,32 @@ include <./includes.scad>
// example key
dcs_row(5) legend("⇪", size=9) key();
/* $skin_key_shape = true; */
difference() {
/* top_of_key() { */
/* cube(10); */
/* } */
/* key(); */
}
/* simple_layout(lets_split_layout) {
dcs_row($row) key();
} */
dcs_row(1) legend("h") front_legend("q"
) {
$key_length = 2.75;
key();
}
translate_u(0,1) dcs_row(1) {
$key_length = 0.86;
key();
}
/* debug() key(); */
// example row
/* for (x = [0:1:4]) {

View File

@ -0,0 +1,14 @@
include <../libraries/3d_surface.scad>
module 3d_surface_dish(width, height, depth, inverted) {
echo(inverted ? "inverted" : "not inverted");
// scale_factor is dead reckoning
// it doesn't have to be dead reckoning for anything but sculpted sides
// we know the angle of the sides from the width difference, height difference,
// skew and tilt of the top. it's a pain to calculate though
scale_factor = 1.1;
// the edges on this behave differently than with the previous dish implementations
scale([width*scale_factor/$3d_surface_size/2,height*scale_factor/$3d_surface_size/2,depth]) rotate([inverted ? 0:180,0,180]) polar_3d_surface(bottom=-10);
/* %scale([width*scale_factor/$3d_surface_size/2,height*scale_factor/$3d_surface_size/2,depth]) rotate([180,0,0]) polar_3d_surface(bottom=-10); */
}

View File

@ -40,3 +40,27 @@ function vertical_inclination_due_to_top_tilt() = sin($top_tilt) * (top_total_ke
// of the keycap a flat plane. 1 = front, -1 = back
// I derived this through a bunch of trig reductions I don't really understand.
function extra_keytop_length_for_flat_sides() = ($width_difference * vertical_inclination_due_to_top_tilt()) / ($total_depth);
// 3d surface functions (still in beta)
// monotonically increasing function that distributes the points of the surface mesh
// only for polar_3d_surface right now
// if it's linear it's a grid. sin(dim) * size concentrates detail around the edges
function surface_distribution_function(dim, size) = sin(dim) * size;
// the function that actually determines what the surface is.
// feel free to override, the last one wins
// debug
function surface_function(x,y) = 1;
// cylindrical
function surface_function(x,y) = (sin(acos(x/$3d_surface_size)));
// spherical
function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size));
// (statically) random!
// ripples
/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */
// Rosenbrock's banana
/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */
// y=x revolved around the y axis
/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */

19
src/hulls.scad Normal file
View File

@ -0,0 +1,19 @@
include <hulls/skin.scad>
include <hulls/linear_extrude.scad>
include <hulls/hull.scad>
// basic key shape, no dish, no inside
// which is only used for dishing to cut the dish off correctly
// $height_difference used for keytop thickness
// extra_slices is a hack to make inverted dishes still work
module shape_hull(thickness_difference, depth_difference, extra_slices = 0){
render() {
if ($skin_extrude_shape) {
skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices);
} else if ($linear_extrude_shape) {
linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices);
} else {
hull_shape_hull(thickness_difference, depth_difference, extra_slices);
}
}
}

33
src/hulls/hull.scad Normal file
View File

@ -0,0 +1,33 @@
module hull_shape_hull(thickness_difference, depth_difference, extra_slices = 0) {
for (index = [0:$height_slices - 1 + extra_slices]) {
hull() {
shape_slice(index / $height_slices, thickness_difference, depth_difference);
shape_slice((index + 1) / $height_slices, thickness_difference, depth_difference);
}
}
}
module shape_slice(progress, thickness_difference, depth_difference) {
skew_this_slice = $top_skew * progress;
x_skew_this_slice = $top_skew_x * progress;
depth_this_slice = ($total_depth - depth_difference) * progress;
tilt_this_slice = -$top_tilt / $key_height * progress;
y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0;
translate([x_skew_this_slice, skew_this_slice, depth_this_slice]) {
rotate([tilt_this_slice,y_tilt_this_slice,0]){
linear_extrude(height = SMALLEST_POSSIBLE + ($rounded_key ? $minkowski_radius : 0), scale = SMALLEST_POSSIBLE){
key_shape(
[
total_key_width(thickness_difference),
total_key_height(thickness_difference)
],
[$width_difference, $height_difference],
progress
);
}
}
}
}

View File

@ -0,0 +1,18 @@
// corollary is hull_shape_hull
// extra_slices unused, only to match argument signatures
module linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0){
height = $total_depth - depth_difference;
width_scale = top_total_key_width() / total_key_width();
height_scale = top_total_key_height() / total_key_height();
translate([0,$linear_extrude_height_adjustment,0]){
linear_extrude(height = height, scale = [width_scale, height_scale]) {
translate([0,-$linear_extrude_height_adjustment,0]){
key_shape(
[total_key_width(thickness_difference), total_key_height(thickness_difference)],
[$width_difference, $height_difference]
);
}
}
}
}

34
src/hulls/skin.scad Normal file
View File

@ -0,0 +1,34 @@
// use skin() instead of successive hulls. much more correct, and looks faster
// too, in most cases. successive hull relies on overlapping faces which are
// not good. But, skin works on vertex sets instead of shapes, which makes it
// a lot more difficult to use
module skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0 ) {
skin([
for (index = [0:$height_slices + extra_slices])
let(
progress = (index / $height_slices),
skew_this_slice = $top_skew * progress,
x_skew_this_slice = $top_skew_x * progress,
depth_this_slice = ($total_depth - depth_difference) * progress,
tilt_this_slice = -$top_tilt / $key_height * progress,
y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0
)
skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice)
]);
}
function skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice) =
transform(
translation([x_skew_this_slice,skew_this_slice,depth_this_slice]),
transform(
rotation([tilt_this_slice,y_tilt_this_slice,0]),
skin_key_shape([
total_key_width(0),
total_key_height(0),
],
[$width_difference, $height_difference],
progress,
thickness_difference
)
)
);

View File

@ -6,6 +6,7 @@ include <stem_supports.scad>
include <dishes.scad>
include <supports.scad>
include <features.scad>
include <hulls.scad>
include <libraries/geodesic_sphere.scad>
@ -15,211 +16,45 @@ use <libraries/scad-utils/lists.scad>
use <libraries/scad-utils/shapes.scad>
use <libraries/skin.scad>
/* [Hidden] */
SMALLEST_POSSIBLE = 1/128;
$fs = .1;
// basically disable $fs - though it might be useful for these CGAL problems
$fs = .01;
$unit = 19.05;
// key shape including dish. used as the ouside and inside shape in hollow_key(). allows for itself to be shrunk in depth and width / height
module shape(thickness_difference, depth_difference=0){
dished(depth_difference, $inverted_dish) {
color($primary_color) shape_hull(thickness_difference, depth_difference, $inverted_dish ? 2 : 0);
color($primary_color) shape_hull(thickness_difference, depth_difference, $inverted_dish ? 200 : 0);
}
}
// shape of the key but with soft, rounded edges. no longer includes dish
// randomly doesnt work sometimes
// the dish doesn't _quite_ reach as far as it should
module rounded_shape() {
dished(-$minkowski_radius, $inverted_dish) {
color($primary_color) minkowski(){
// half minkowski in the z direction
color($primary_color) shape_hull($minkowski_radius * 2, $minkowski_radius/2, $inverted_dish ? 2 : 0);
/* cube($minkowski_radius); */
sphere(r=$minkowski_radius, $fn=$minkowski_facets);
}
}
/* %envelope(); */
}
// this function is more correct, but takes _forever_
// the main difference is minkowski happens after dishing, meaning the dish is
// also minkowski'd
/* module rounded_shape() {
module rounded_shape() {
color($primary_color) minkowski(){
// half minkowski in the z direction
shape($minkowski_radius * 2, $minkowski_radius/2);
minkowski_object();
}
}
// minkowski places this object at every vertex of the other object then mashes
// it all together
module minkowski_object() {
// alternative minkowski shape that needs the bottom of the keycap to be trimmed
/* sphere(1); */
difference(){
sphere(r=$minkowski_radius, $fn=20);
sphere(r=$minkowski_radius, $fa=360/$minkowski_facets);
translate([0,0,-$minkowski_radius]){
cube($minkowski_radius * 2, center=true);
}
}
}
} */
// basic key shape, no dish, no inside
// which is only used for dishing to cut the dish off correctly
// $height_difference used for keytop thickness
// extra_slices is a hack to make inverted dishes still work
module shape_hull(thickness_difference, depth_difference, extra_slices = 0){
render() {
if ($skin_extrude_shape) {
skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices);
} else if ($linear_extrude_shape) {
linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices);
} else {
hull_shape_hull(thickness_difference, depth_difference, extra_slices);
}
}
}
// use skin() instead of successive hulls. much more correct, and looks faster
// too, in most cases. successive hull relies on overlapping faces which are
// not good. But, skin works on vertex sets instead of shapes, which makes it
// a lot more difficult to use
module skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0 ) {
skin([
for (index = [0:$height_slices + extra_slices])
let(
progress = (index / $height_slices),
skew_this_slice = $top_skew * progress,
x_skew_this_slice = $top_skew_x * progress,
depth_this_slice = ($total_depth - depth_difference) * progress,
tilt_this_slice = -$top_tilt / $key_height * progress,
y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0
)
skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice)
]);
}
function skin_shape_slice(progress, thickness_difference, skew_this_slice, x_skew_this_slice, depth_this_slice, tilt_this_slice, y_tilt_this_slice) =
transform(
translation([x_skew_this_slice,skew_this_slice,depth_this_slice]),
transform(
rotation([tilt_this_slice,y_tilt_this_slice,0]),
skin_key_shape([
total_key_width(0),
total_key_height(0),
],
[$width_difference, $height_difference],
progress,
thickness_difference
)
)
);
// corollary is hull_shape_hull
// extra_slices unused, only to match argument signatures
module linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices = 0){
height = $total_depth - depth_difference;
width_scale = top_total_key_width() / total_key_width();
height_scale = top_total_key_height() / total_key_height();
translate([0,$linear_extrude_height_adjustment,0]){
linear_extrude(height = height, scale = [width_scale, height_scale]) {
translate([0,-$linear_extrude_height_adjustment,0]){
key_shape(
[total_key_width(thickness_difference), total_key_height(thickness_difference)],
[$width_difference, $height_difference]
);
}
}
}
}
module hull_shape_hull(thickness_difference, depth_difference, extra_slices = 0) {
for (index = [0:$height_slices - 1 + extra_slices]) {
hull() {
shape_slice(index / $height_slices, thickness_difference, depth_difference);
shape_slice((index + 1) / $height_slices, thickness_difference, depth_difference);
}
}
}
module shape_slice(progress, thickness_difference, depth_difference) {
skew_this_slice = $top_skew * progress;
x_skew_this_slice = $top_skew_x * progress;
depth_this_slice = ($total_depth - depth_difference) * progress;
tilt_this_slice = -$top_tilt / $key_height * progress;
y_tilt_this_slice = $double_sculpted ? (-$top_tilt_y / $key_length * progress) : 0;
translate([x_skew_this_slice, skew_this_slice, depth_this_slice]) {
rotate([tilt_this_slice,y_tilt_this_slice,0]){
linear_extrude(height = SMALLEST_POSSIBLE){
key_shape(
[
total_key_width(thickness_difference),
total_key_height(thickness_difference)
],
[$width_difference, $height_difference],
progress
);
}
}
}
}
// for when you want something to only exist inside the keycap.
// used for the support structure
module inside() {
intersection() {
shape($wall_thickness, $keytop_thickness);
children();
}
}
// for when you want something to only exist outside the keycap
module outside() {
difference() {
children();
shape($wall_thickness, $keytop_thickness);
}
}
// put something at the top of the key, with no adjustments for dishing
module top_placement(depth_difference=0) {
top_tilt_by_height = -$top_tilt / $key_height;
top_tilt_y_by_length = $double_sculpted ? (-$top_tilt_y / $key_length) : 0;
minkowski_height = $rounded_key ? $minkowski_radius : 0;
translate([$top_skew_x + $dish_skew_x, $top_skew + $dish_skew_y, $total_depth - depth_difference + minkowski_height/2]){
rotate([top_tilt_by_height, top_tilt_y_by_length,0]){
children();
}
}
}
module front_placement() {
// all this math is to take top skew and tilt into account
// we need to find the new effective height and depth of the top, front lip
// of the keycap to find the angle so we can rotate things correctly into place
total_depth_difference = sin(-$top_tilt) * (top_total_key_height()/2);
total_height_difference = $top_skew + (1 - cos(-$top_tilt)) * (top_total_key_height()/2);
angle = atan2(($total_depth - total_depth_difference), ($height_difference/2 + total_height_difference));
hypotenuse = ($total_depth -total_depth_difference) / sin(angle);
translate([0,-total_key_height()/2,0]) {
rotate([-(90-angle), 0, 0]) {
translate([0,0,hypotenuse/2]){
children();
}
}
}
}
// just to DRY up the code
module _dish() {
color($secondary_color) dish(top_total_key_width() + $dish_overdraw_width, top_total_key_height() + $dish_overdraw_height, $dish_depth, $inverted_dish);
}
module envelope(depth_difference=0) {
s = 1.5;
hull(){
@ -230,18 +65,6 @@ module envelope(depth_difference=0) {
}
}
// I think this is unused
module dished_for_show() {
difference(){
union() {
envelope();
if ($inverted_dish) top_placement(0) _dish();
}
if (!$inverted_dish) top_placement(0) _dish();
}
}
// for when you want to take the dish out of things
// used for adding the dish to the key shape and making sure stems don't stick out the top
// creates a bounding box 1.5 times larger in width and height than the keycap.
@ -250,15 +73,21 @@ module dished(depth_difference = 0, inverted = false) {
children();
difference(){
union() {
// envelope is needed to "fill in" the rest of the keycap
envelope(depth_difference);
if (inverted) top_placement(depth_difference) _dish();
if (inverted) top_placement(depth_difference) _dish(inverted);
}
if (!inverted) top_placement(depth_difference) _dish();
if (!inverted) top_placement(depth_difference) _dish(inverted);
}
}
}
// puts it's children at the center of the dishing on the key, including dish height
// just to DRY up the code
module _dish(inverted=$inverted_dish) {
color($secondary_color) dish(top_total_key_width() + $dish_overdraw_width, top_total_key_height() + $dish_overdraw_height, $dish_depth, inverted);
}
// puts its children at the center of the dishing on the key, including dish height
// more user-friendly than top_placement
module top_of_key(){
// if there is a dish, we need to account for how much it digs into the top
@ -271,6 +100,7 @@ module top_of_key(){
}
}
// puts its children at each keystem position provided
module keystem_positions(positions) {
for (connector_pos = positions) {
translate(connector_pos) {
@ -296,77 +126,120 @@ module stems_for(positions, stem_type) {
}
}
// legends / artisan support
module artisan(depth) {
top_of_key() {
// artisan objects / outset shape legends
color($secondary_color) children();
// put something at the top of the key, with no adjustments for dishing
module top_placement(depth_difference=0) {
top_tilt_by_height = -$top_tilt / $key_height;
top_tilt_y_by_length = $double_sculpted ? (-$top_tilt_y / $key_length) : 0;
minkowski_height = $rounded_key ? $minkowski_radius : 0;
translate([$top_skew_x + $dish_skew_x, $top_skew + $dish_skew_y, $total_depth - depth_difference + minkowski_height/2]){
rotate([top_tilt_by_height, top_tilt_y_by_length,0]){
children();
}
}
}
// key with hollowed inside but no stem
module hollow_key() {
difference(){
if ($rounded_key) {
rounded_shape();
} else {
module front_of_key() {
// all this math is to take top skew and tilt into account
// we need to find the new effective height and depth of the top, front lip
// of the keycap to find the angle so we can rotate things correctly into place
total_depth_difference = sin(-$top_tilt) * (top_total_key_height()/2);
total_height_difference = $top_skew + (1 - cos(-$top_tilt)) * (top_total_key_height()/2);
angle = atan2(($total_depth - total_depth_difference), ($height_difference/2 + total_height_difference));
hypotenuse = ($total_depth -total_depth_difference) / sin(angle);
translate([0,-total_key_height()/2,0]) {
rotate([-(90-angle), 0, 0]) {
translate([0,0,hypotenuse/2]){
children();
}
}
}
}
module outer_shape() {
shape(0, 0);
}
// translation purely for aesthetic purposes, to get rid of that awful lattice
module inner_shape(extra_wall_thickness = 0, extra_keytop_thickness = 0) {
translate([0,0,-SMALLEST_POSSIBLE]) {
shape($wall_thickness, $keytop_thickness);
if ($inner_shape_type == "flat") {
/* $key_shape_type="square"; */
$height_slices = 1;
color($primary_color) shape_hull($wall_thickness + extra_wall_thickness, $keytop_thickness + extra_keytop_thickness, 0);
} else {
shape($wall_thickness + extra_wall_thickness, $keytop_thickness + extra_keytop_thickness);
}
}
}
// The final, penultimate key generation function.
// takes all the bits and glues them together. requires configuration with special variables.
module key(inset = false) {
difference() {
union(){
// the shape of the key, inside and out
hollow_key();
if($key_bump) top_of_key() keybump($key_bump_depth, $key_bump_edge);
// additive objects at the top of the key
// outside() makes them stay out of the inside. it's a bad name
if(!inset && $children > 0) outside() artisan(0) children();
module additive_features(inset) {
top_of_key() {
if($key_bump) keybump($key_bump_depth, $key_bump_edge);
if(!inset && $children > 0) color($secondary_color) children();
}
if($outset_legends) legends(0);
// render the clearance check if it's enabled, but don't have it intersect with anything
if ($clearance_check) %clearance_check();
}
// subtractive objects at the top of the key
// no outside() - I can't think of a use for it. will save render time
if (inset && $children > 0) artisan($inset_legend_depth) children();
module subtractive_features(inset) {
top_of_key() {
if (inset && $children > 0) color($secondary_color) children();
}
if(!$outset_legends) legends($inset_legend_depth);
// subtract the clearance check if it's enabled, letting the user see the
// parts of the keycap that will hit the cherry switch
if ($clearance_check) %clearance_check();
}
module inside_features() {
translate([0, 0, $stem_inset]) {
// both stem and support are optional
if ($stem_type != "disable" || ($stabilizers != [] && $stabilizer_type != "disable")) {
dished($keytop_thickness, $inverted_dish) {
translate([0, 0, $stem_inset]) {
if ($stabilizer_type != "disable") stems_for($stabilizers, $stabilizer_type);
if ($stem_type != "disable") stems_for($stem_positions, $stem_type);
}
}
}
if ($support_type != "disable"){
inside() {
translate([0, 0, $stem_inset]) {
if ($stabilizer_type != "disable") support_for($stabilizers, $stabilizer_type);
// always render stem support even if there isn't a stem.
// rendering flat support w/no stem is much more common than a hollow keycap
// so if you want a hollow keycap you'll have to turn support off entirely
support_for($stem_positions, $stem_type);
if ($support_type != "disable") support_for($stem_positions, $stem_type);
}
}
// The final, penultimate key generation function.
// takes all the bits and glues them together. requires configuration with special variables.
module key(inset=false) {
difference(){
union() {
outer_shape();
additive_features(inset) {
children();
};
}
if ($inner_shape_type != "disable") difference() {
inner_shape();
inside_features();
}
subtractive_features(inset) {
children();
};
}
}
module display_key(inset=false) {
minkowski() {
outer_shape();
minkowski_object();
// minkowski doesn't work with difference
additive_features(false) {
children();
};
}
}

View File

@ -177,3 +177,14 @@ module debug() {
%children();
}
module display() {
$height_slices = 30;
$minkowski_facets = 64;
$shape_facets = 64;
$stem_type = "disable";
$support_type = "disable";
$stem_support_type = "disable";
children();
}

View File

@ -1,7 +1,7 @@
include <../layout.scad>
// negative numbers are used for spacing
lets_split_mapping = [
lets_split_layout = [
[1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1],
@ -9,5 +9,5 @@ lets_split_mapping = [
];
module lets_split_default(profile) {
layout(lets_split_mapping, profile, row_sculpting_offset=1) children();
layout(lets_split_layout, profile, row_sculpting_offset=1) children();
}

View File

@ -187,3 +187,12 @@ $warning_color = [1,0,0, 0.15];
// how many facets circles will have when used in these features
$minkowski_facets = 30;
$shape_facets =30;
// 3d surface settings
// unused for now
$3d_surface_size = 100;
// resolution in each axis. 10 = 10 divisions per x/y = 100 points total
$3d_surface_step = 5;
// "flat" / "dished" / "disable"
$inner_shape_type = "flat";