Merge pull request #128 from rsheldiii/v2.0.0

Merging in V2.0.0
This commit is contained in:
Bob 2022-02-19 19:20:37 -05:00 committed by GitHub
commit 9b52a91db9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 3134 additions and 923 deletions

16
CHANGELOG.md Normal file
View File

@ -0,0 +1,16 @@
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
* `$linear_extrude_shape` and `$skin_extrude_shape` retired in favor of `$hull_shape_type`
* added regular_polygon shape and octagonal and hexagonal key profiles
* added beta kailh choc
* Finally got ISO Enter working correctly!

View File

@ -9,6 +9,14 @@ Relevant links:
* Shapeways: https://www.shapeways.com/designer/rsheldiii/creations
* Buy me a coffee: https://ko-fi.com/rsheldiii, but only if you want to!
# V2.0.0 Cutover
As of February 19th, 2022, the v2.0.0 branch has been merged into master.
This branch completely rewrote key.scad to be much simpler and faster. If you're just starting out, please use master and report any bugs you find.
If you branched off this repo previously, merging to master will probably break any additions you've made. If you need the prior version of the code for whatever reason, v1.1.0 is master just before the v2.0.0 merge. I will be backporting bugfixes to the v1 branch, so if you see v1.1.1 or higher, use that instead.
## How to run
#### OpenSCAD Proper (recommended way)

View File

@ -14,7 +14,7 @@ At the end of the day though, all the columnular sculpting is doing is adding ex
## skin mode
SA, HiPro and DSA keycaps take _forever_ to render. This is a multifaceted issue that I don't want to get into here, but suffice to say _one_ of the reasons it takes so long is how the keycap is constructed from multiple, smaller slices. OpenSCAD takes more time to render the more objects you have, regardless of how they interact. Enter `$skin_extrude_shape = true`.
SA, HiPro and DSA keycaps take _forever_ to render. This is a multifaceted issue that I don't want to get into here, but suffice to say _one_ of the reasons it takes so long is how the keycap is constructed from multiple, smaller slices. OpenSCAD takes more time to render the more objects you have, regardless of how they interact. Enter `$hull_shape_type = "skin"`.
`skin()` is a list comprehension function available [here](https://github.com/openscad/list-comprehension-demos/blob/master/skin.scad). The gist of it is that instead of having x number of keycap slices unioned together, we give `skin()` a set of profiles and it makes a single object out of it for us. This reduces the number of objects per keycap, which makes it easier to render them.

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
// a safe theoretical distance between two vertices such that they don't collapse. hard to use
SMALLEST_POSSIBLE = 1/128;
$fs = .1;
$unit = 19.05;
$fs=0.1;
$unit=19.05;

View File

@ -8,7 +8,7 @@ module 3d_surface_dish(width, height, depth, inverted) {
// 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,90]) polar_3d_surface(bottom=-10);
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); */
}

5
src/features.scad Normal file
View File

@ -0,0 +1,5 @@
// features are any premade self-contained objects that go on top or inside
include <features/key_bump.scad>
include <features/clearance_check.scad>
include <features/legends.scad>

View File

@ -0,0 +1,24 @@
// a fake cherry keyswitch, abstracted out to maybe replace with a better one later
module cherry_keyswitch() {
union() {
hull() {
cube([15.6, 15.6, 0.01], center=true);
translate([0,1,5 - 0.01]) cube([10.5,9.5, 0.01], center=true);
}
hull() {
cube([15.6, 15.6, 0.01], center=true);
translate([0,0,-5.5]) cube([13.5,13.5,0.01], center=true);
}
}
}
//approximate (fully depressed) cherry key to check clearances
module clearance_check() {
if($stem_type == "cherry" || $stem_type == "cherry_rounded"){
color($warning_color){
translate([0,0,3.6 + $stem_inset - 5]) {
cherry_keyswitch();
}
}
}
}

26
src/features/legends.scad Normal file
View File

@ -0,0 +1,26 @@
module keytext(text, position, font_size, depth) {
woffset = (top_total_key_width()/3.5) * position[0];
hoffset = (top_total_key_height()/3.5) * -position[1];
translate([woffset, hoffset, -depth]){
color($tertiary_color) linear_extrude(height=$dish_depth + depth){
text(text=text, font=$font, size=font_size, halign="center", valign="center");
}
}
}
module legends(depth=0) {
if (len($front_legends) > 0) {
front_of_key() {
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() {
for (i=[0:len($legends)-1]) {
keytext($legends[i][0], $legends[i][1], $legends[i][2], depth);
}
}
}
}

View File

@ -4,6 +4,8 @@ include <constants.scad>
// functions need to be explicitly included, unlike special variables, which
// just need to have been set before they are used. hence this file
function stem_height() = $total_depth - $dish_depth - $stem_inset;
// cherry stem dimensions
function outer_cherry_stem(slop) = [7.2 - slop * 2, 5.5 - slop * 2];
@ -57,9 +59,14 @@ function surface_function(x,y) = 1;
function surface_function(x,y) = (sin(acos(x/$3d_surface_size)));
// spherical
function surface_function(x,y) = (sin(acos(x/$3d_surface_size))) * sin(acos(y/$3d_surface_size));
// (statically) random!
// ripples
/* function surface_function(x,y) = cos(pow(pow(x,2)+pow(y,2),0.5)*10)/4+0.75; */
// Rosenbrock's banana
/* function surface_function(x,y) = (pow(1-(x/100), 2) + 100 * pow((y/100)-pow((x/100),2),2))/200 + 0.1; */
// y=x revolved around the y axis
/* function surface_function(x,y) = 1/(pow(pow(x,2)+pow(y,2),0.5)/100 + .01); */
/* function surface_function(x,y) = sin(rands(0,90,1,x+y)[0]); */
// adds uniform rounding radius for round-anything polyRound
function add_rounding(p, radius)=[for(i=[0:len(p)-1])[p[i].x,p[i].y, radius]];
// computes millimeter length from unit length
function unit_length(length) = unit * (length - 1) + 18.16;
function unit_length(length) = $unit * (length - 1) + 18.16;

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 ($hull_shape_type == "skin") {
skin_extrude_shape_hull(thickness_difference, depth_difference, extra_slices);
} else if ($hull_shape_type == "linear extrude") {
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, scale = 1){
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(), total_key_height()],
[thickness_difference, thickness_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) - thickness_difference,
total_key_height(0) - thickness_difference,
],
[$width_difference, $height_difference],
progress,
thickness_difference
)
)
);

View File

@ -6,7 +6,8 @@ include <stems.scad>
include <stem_supports.scad>
include <dishes.scad>
include <supports.scad>
include <key_features.scad>
include <features.scad>
include <hulls.scad>
include <libraries/geodesic_sphere.scad>
@ -19,201 +20,31 @@ use <libraries/skin.scad>
// 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) {
/* %shape_hull(thickness_difference, depth_difference, $inverted_dish ? 2 : 0); */
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
// Not currently used due to CGAL errors. Rounds the shape via minkowski
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() {
color($primary_color) minkowski(){
// half minkowski in the z direction
shape($minkowski_radius * 2, $minkowski_radius/2);
difference(){
sphere(r=$minkowski_radius, $fn=20);
translate([0,0,-$minkowski_radius]){
cube($minkowski_radius * 2, center=true);
}
minkowski_object();
}
}
// minkowski places this object at every vertex of the other object then mashes
// it all together
module minkowski_object() {
// alternative minkowski shape that needs the bottom of the keycap to be trimmed
/* sphere(1); */
difference(){
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() {
translate([$dish_offset_x,0,0]) dish(top_total_key_width() + $dish_overdraw_width, top_total_key_height() + $dish_overdraw_height, $dish_depth, $inverted_dish);
}
module envelope(depth_difference=0) {
@ -226,18 +57,6 @@ module envelope(depth_difference=0) {
}
}
// I think this is unused
module dished_for_show() {
difference(){
union() {
envelope();
if ($inverted_dish) top_placement(0) color($secondary_color) _dish();
}
if (!$inverted_dish) top_placement(0) color($secondary_color) _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.
@ -246,38 +65,24 @@ 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) color($secondary_color) _dish();
if (inverted) top_placement(depth_difference) color($secondary_color) _dish(inverted);
}
if (!inverted) top_placement(depth_difference) color($secondary_color) _dish();
if (!inverted) top_placement(depth_difference) color($secondary_color) _dish(inverted);
/* %top_placement(depth_difference) _dish(); */
}
}
}
// puts it's 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
dish_depth = ($dish_type == "disable") ? 0 : $dish_depth;
// if the dish is inverted, we need to account for that too. in this case we do half, otherwise the children would be floating on top of the dish
corrected_dish_depth = ($inverted_dish) ? -dish_depth / 2 : dish_depth;
top_placement(corrected_dish_depth) {
children();
}
}
module keytext(text, position, font_size, depth) {
woffset = (top_total_key_width()/3.5) * position[0];
hoffset = (top_total_key_height()/3.5) * -position[1];
translate([woffset, hoffset, -depth]){
color($tertiary_color) linear_extrude(height=$dish_depth){
text(text=text, font=$font, size=font_size, halign="center", valign="center");
}
}
// just to DRY up the code
// TODO is putting special vars in function signatures legal
module _dish(inverted=$inverted_dish) {
translate([$dish_offset_x,0,0]) 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 each keystem position provided
module keystem_positions(positions) {
for (connector_pos = positions) {
translate(connector_pos) {
@ -296,131 +101,156 @@ module support_for(positions, stem_type) {
module stems_for(positions, stem_type) {
keystem_positions(positions) {
color($tertiary_color) stem(stem_type, $total_depth, $stem_slop, $stem_throw);
color($tertiary_color) stem(stem_type, stem_height(), $stem_slop, $stem_throw);
if ($stem_support_type != "disable") {
color($quaternary_color) stem_support($stem_support_type, stem_type, $stem_support_height, $stem_slop);
}
}
}
// a fake cherry keyswitch, abstracted out to maybe replace with a better one later
module cherry_keyswitch() {
union() {
hull() {
cube([15.6, 15.6, 0.01], center=true);
translate([0,1,5 - 0.01]) cube([10.5,9.5, 0.01], center=true);
}
hull() {
cube([15.6, 15.6, 0.01], center=true);
translate([0,0,-5.5]) cube([13.5,13.5,0.01], center=true);
// 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();
}
}
}
//approximate (fully depressed) cherry key to check clearances
module clearance_check() {
if($stem_type == "cherry" || $stem_type == "cherry_rounded"){
color($warning_color){
translate([0,0,3.6 + $stem_inset - 5]) {
cherry_keyswitch();
// 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
dish_depth = ($dish_type == "disable") ? 0 : $dish_depth;
// if the dish is inverted, we need to account for that too. in this case we do half, otherwise the children would be floating on top of the dish
corrected_dish_depth = ($inverted_dish) ? -dish_depth / 2 : dish_depth;
top_placement(corrected_dish_depth) {
children();
}
}
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 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);
}
}
}
module outer_shape() {
if ($rounded_key == true) {
rounded_shape();
} else {
shape(0, 0);
}
}
// legends / artisan support
module artisan(depth) {
module inner_shape(extra_wall_thickness = 0, extra_keytop_thickness = 0) {
if ($inner_shape_type == "flat") {
/* $key_shape_type="square"; */
$height_slices = 1;
// if inner_shape is flat, keytop_thickness will be dish_depth less than it should be, since the dish digs in that far.
// so, we add dish_depth here
color($primary_color) shape_hull($wall_thickness + extra_wall_thickness, $keytop_thickness + extra_keytop_thickness + $dish_depth, 0);
} else {
shape($wall_thickness + extra_wall_thickness, $keytop_thickness + extra_keytop_thickness);
}
}
// additive objects at the top of the key
module additive_features(inset) {
top_of_key() {
// artisan objects / outset shape legends
color($secondary_color) children();
if($key_bump) keybump($key_bump_depth, $key_bump_edge);
if(!inset && $children > 0) color($secondary_color) children();
}
if($outset_legends) legends(0);
// render the clearance check if it's enabled, but don't have it intersect with anything
if ($clearance_check) %clearance_check();
}
// subtractive objects at the top of the key
module subtractive_features(inset) {
top_of_key() {
if (inset && $children > 0) color($secondary_color) children();
}
if(!$outset_legends) legends($inset_legend_depth);
// subtract the clearance check if it's enabled, letting the user see the
// parts of the keycap that will hit the cherry switch
// this is a little confusing as it eats the stem too
/* if ($clearance_check) clearance_check(); */
}
// features inside the key itself (stem, supports, etc)
module inside_features() {
// Stems and stabilizers are not "inside features" unless they are fully
// contained inside the cap. otherwise they'd be cut off when they are
// differenced with the outside shape. this only matters if $stem_inset
// is negative
if ($stem_inset >= 0) stems_and_stabilizers();
if ($support_type != "disable") translate([0, 0, $stem_inset]) support_for($stem_positions, $stem_type);
if ($stabilizer_type != "disable") translate([0, 0, $stem_inset]) support_for($stabilizers, $stabilizer_type);
}
// all stems and stabilizers
module stems_and_stabilizers() {
translate([0, 0, $stem_inset]) {
if ($stabilizer_type != "disable") stems_for($stabilizers, $stabilizer_type);
if ($stem_type != "disable") stems_for($stem_positions, $stem_type);
}
}
// key with hollowed inside but no stem
module hollow_key() {
difference(){
if ($rounded_key) {
rounded_shape();
} else {
shape(0, 0);
}
// translation purely for aesthetic purposes, to get rid of that awful lattice
translate([0,0,-SMALLEST_POSSIBLE]) {
shape($wall_thickness, $keytop_thickness);
}
// helpers for doubleshot keycaps for now
module inner_total_shape() {
difference() {
inner_shape();
inside_features();
}
}
module outer_total_shape(inset=false) {
outer_shape();
additive_features(inset) {
children();
};
}
// The final, penultimate key generation function.
// takes all the bits and glues them together. requires configuration with special variables.
module key(inset = false) {
difference() {
union(){
// the shape of the key, inside and out
hollow_key();
if($key_bump) top_of_key() keybump($key_bump_depth, $key_bump_edge);
// additive objects at the top of the key
// outside() makes them stay out of the inside. it's a bad name
if(!inset && $children > 0) outside() artisan(0) children();
if($outset_legends) legends(0);
// render the clearance check if it's enabled, but don't have it intersect with anything
if ($clearance_check) %clearance_check();
}
module key(inset=false) {
difference(){
outer_total_shape(inset);
// subtractive objects at the top of the key
// no outside() - I can't think of a use for it. will save render time
if (inset && $children > 0) artisan($inset_legend_depth) children();
if(!$outset_legends) legends($inset_legend_depth);
// subtract the clearance check if it's enabled, letting the user see the
// parts of the keycap that will hit the cherry switch
if ($clearance_check) %clearance_check();
}
// both stem and support are optional
if ($stem_type != "disable" || ($stabilizers != [] && $stabilizer_type != "disable")) {
dished($keytop_thickness, $inverted_dish) {
translate([0, 0, $stem_inset]) {
if ($stabilizer_type != "disable") stems_for($stabilizers, $stabilizer_type);
if ($stem_type != "disable") stems_for($stem_positions, $stem_type);
if ($inner_shape_type != "disable") {
translate([0,0,-SMALLEST_POSSIBLE]) {
inner_total_shape();
}
}
subtractive_features(inset) {
children();
};
}
if ($support_type != "disable"){
inside() {
translate([0, 0, $stem_inset]) {
if ($stabilizer_type != "disable") support_for($stabilizers, $stabilizer_type);
// always render stem support even if there isn't a stem.
// rendering flat support w/no stem is much more common than a hollow keycap
// so if you want a hollow keycap you'll have to turn support off entirely
support_for($stem_positions, $stem_type);
}
}
// if $stem_inset is less than zero, we add the
if ($stem_inset < 0) {
stems_and_stabilizers();
}
}

View File

@ -9,6 +9,7 @@ include <key_profiles/sa.scad>
include <key_profiles/g20.scad>
include <key_profiles/hipro.scad>
include <key_profiles/grid.scad>
include <key_profiles/regular_polygon.scad>
include <key_profiles/cherry.scad>
include <key_profiles/dss.scad>
@ -30,6 +31,10 @@ module key_profile(key_profile_type, row, column=0) {
hipro_row(row, column) children();
} else if (key_profile_type == "grid") {
grid_row(row, column) children();
} else if (key_profile_type == "hexagon") {
hexagonal_row(row, column) children();
} else if (key_profile_type == "octagon") {
octagonal_row(row, column) children();
} else if (key_profile_type == "cherry") {
cherry_row(row, column) children();
} else if (key_profile_type == "disable") {

View File

@ -11,7 +11,7 @@ module grid_row(row=3, column = 0) {
$dish_skew_x = 0;
$dish_skew_y = 0;
$linear_extrude_shape = true;
$hull_shape_type = "linear extrude";
$dish_overdraw_width = -6.5;

View File

@ -0,0 +1,66 @@
include <../constants.scad>
// Regular polygon shapes CIRCUMSCRIBE the sphere of diameter $bottom_key_width
// This is to make tiling them easier, like in the case of hexagonal keycaps etc
// this function doesn't set the key shape, so you can't use it directly without some fiddling
module regular_polygon_row(n=3, column=0) {
$bottom_key_width = $unit - 0.5;
$bottom_key_height = $unit - 0.5;
$width_difference = 0;
$height_difference = 0;
$dish_type = "spherical";
$dish_depth = 0.85;
$dish_skew_x = 0;
$dish_skew_y = 0;
$top_skew = 0;
$height_slices = 1;
$corner_radius = 1;
// this is _incredibly_ intensive
/* $rounded_key = true; */
$top_tilt_y = side_tilt(column);
extra_height = $double_sculpted ? extra_side_tilt_height(column) : 0;
base_depth = 7.5;
if (n <= 1){
$total_depth = base_depth + 2.5 + extra_height;
$top_tilt = -13;
children();
} else if (n == 2) {
$total_depth = base_depth + 0.5 + extra_height;
$top_tilt = -7;
children();
} else if (n == 3) {
$total_depth = base_depth + extra_height;
$top_tilt = 0;
children();
} else if (n == 4){
$total_depth = base_depth + 0.5 + extra_height;
$top_tilt = 7;
children();
} else {
$total_depth = base_depth + extra_height;
$top_tilt = 0;
children();
}
}
module hexagonal_row(n=3, column=0) {
$key_shape_type = "hexagon";
regular_polygon_row(n,column) {
children();
}
}
module octagonal_row(n=3, column=0) {
$key_shape_type = "octagon";
regular_polygon_row(n, column) {
children();
}
}

View File

@ -1,10 +1,9 @@
// kind of a catch-all at this point for any directive that doesn't fit in the other files
//TODO duplicate def to not make this a special var. maybe not worth it
unit = 19.05;
include <constants.scad>
module translate_u(x=0, y=0, z=0){
translate([x * unit, y*unit, z*unit]) children();
translate([x * $unit, y*$unit, z*$unit]) children();
}
module no_stem_support() {
@ -92,30 +91,58 @@ module blank() {
children();
}
module cherry(slop) {
$stem_slop = slop ? slop : $stem_slop;
module cherry(slop = undef) {
$stem_slop = slop != undef ? slop : $stem_slop;
$stem_type = "cherry";
children();
}
module alps(slop) {
$stem_slop = slop ? slop : $stem_slop;
module alps(slop = undef) {
$stem_slop = slop != undef ? slop : $stem_slop;
$stem_type = "alps";
children();
}
module rounded_cherry(slop) {
$stem_slop = slop ? slop : $stem_slop;
module rounded_cherry(slop = undef) {
$stem_slop = slop != undef ? slop : $stem_slop;
$stem_type = "rounded_cherry";
children();
}
module box_cherry(slop) {
$stem_slop = slop ? slop : $stem_slop;
module box_cherry(slop = undef) {
$stem_slop = slop != undef ? slop : $stem_slop;
$stem_type = "box_cherry";
children();
}
module choc(slop = 0.05) {
echo("WARN:\n\n * choc support is experimental.\n * $stem_slop is overridden.\n * it is also recommended to print them upside down if you can\n\n");
$stem_throw = 3;
$stem_slop = slop;
$bottom_key_width = 18;
$bottom_key_height = 17;
$stem_type = "choc";
children();
}
// a hacky way to make "low profile" keycaps
module low_profile() {
$width_difference = $width_difference / 1.5;
$height_difference = $height_difference / 1.5;
// helps tilted keycaps not have holes if worst comes to worst
$inner_shape_type = "dished";
$top_tilt = $top_tilt / 1.25;
$total_depth = ($total_depth / 2) < 7 ? 7 : $total_depth / 2;
// just to make sure
$stem_throw = 3;
children();
}
module flared_support() {
$support_type = "flared";
children();
@ -183,3 +210,16 @@ module debug() {
%children();
}
// auto-place children in a grid.
// For this to work all children have to be single keys, no for loops etc
module auto_place() {
num_children = $children;
row_size = round(pow(num_children, 0.5));
for (child_index = [0:num_children-1]) {
x = child_index % row_size;
y = floor(child_index / row_size);
translate_u(x,-y) children(child_index);
}
}

View File

@ -50,7 +50,7 @@ module iso_enter() {
/* $top_tilt = 0; */
$stem_support_type = "disable";
$key_shape_type = "iso_enter";
/* $linear_extrude_shape = true; */
/* $hull_shape_type = "linear extrude"; */
$linear_extrude_height_adjustment = 19.05 * 0.5;
// this equals (unit_length(1.5) - unit_length(1.25)) / 2
/* $dish_overdraw_width = 2.38125; */

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

@ -74,6 +74,7 @@ $rounded_cherry_stem_d = 5.5;
// How much higher the stem is than the bottom of the keycap.
// Inset stem requires support but is more accurate in some profiles
// can be negative to make outset stems!
$stem_inset = 0;
// How many degrees to rotate the stems. useful for sideways keycaps, maybe
$stem_rotation = 0;
@ -122,12 +123,10 @@ $font="DejaVu Sans Mono:style=Book";
// Whether or not to render fake keyswitches to check clearances
$clearance_check = false;
// 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;
// warns in trajectory.scad but it looks benign
// brand new, more correct, hopefully faster, lots more work
$skin_extrude_shape = false;
// what kind of extrusion we use to create the keycap. "hull" is standard, "linear extrude" is legacy, "skin" is new and not well supported.
$hull_shape_type = "hull"; // ["hull", "linear extrude", "skin"]
// 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
@ -168,6 +167,9 @@ $inset_legend_depth = 0.2;
// Dimensions of alps stem
$alps_stem = [4.45, 2.25];
// Dimensions of choc stem
$choc_stem = [1.2, 3];
// Enable stabilizer stems, to hold onto your cherry or costar stabilizers
$stabilizer_type = "costar_stabilizer"; // [costar_stabilizer, cherry_stabilizer, disable]
@ -197,3 +199,6 @@ $shape_facets =30;
$3d_surface_size = 100;
// resolution in each axis. 10 = 10 divisions per x/y = 100 points total
$3d_surface_step = 10;
// "flat" / "dished" / "disable"
$inner_shape_type = "flat";

View File

@ -1,11 +1,10 @@
$fs=.1;
unit = 19.05;
include <constants.scad>
include <shapes/ISO_enter.scad>
include <shapes/sculpted_square.scad>
include <shapes/rounded_square.scad>
include <shapes/square.scad>
include <shapes/oblong.scad>
include <shapes/regular_polygon.scad>
// size: at progress 0, the shape is supposed to be this size
// delta: at progress 1, the keycap is supposed to be size - delta
@ -25,6 +24,10 @@ module key_shape(size, delta, progress = 0) {
square_shape(size, delta, progress);
} else if ($key_shape_type == "oblong") {
oblong_shape(size, delta, progress);
} else if ($key_shape_type == "hexagon") {
regular_polygon_shape(size, delta, progress);
} else if ($key_shape_type == "octagon") {
regular_polygon_shape(size, delta, progress, sides=8);
} else {
echo("Warning: unsupported $key_shape_type");
}

View File

@ -5,6 +5,7 @@ width_ratio = unit_length(1.25) / unit_length(1.5);
height_ratio = unit_length(1) / unit_length(2);
module ISO_enter_shape(size, delta, progress){
width = size[0];
height = size[1];

View File

@ -0,0 +1,14 @@
// we do this weird key_shape_type check here because rounded_square uses
// square_shape, and we want flat sides to work for that too.
// could be refactored, idk
module regular_polygon_shape(size, delta, progress, sides=6){
// https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/undersized_circular_objects
fudge = 1/cos(180/sides);
diameter = (size.x - delta.x * progress - $corner_radius*2) * fudge;
offset(r=$corner_radius) rotate([0,0,360/sides/2]) circle(d = diameter, $fn=sides);
}
// TODO not implemented
function skin_regular_polygon_shape(size, delta, progress, thickness_difference, sides=6) = echo("skin regular polygon not implemented");

View File

@ -43,5 +43,17 @@ module brim_support(stem_type, stem_support_height, slop) {
inside_cherry_cross(slop);
}
} else if(stem_type == "choc") {
translate([-5.7/2,0,0]) linear_extrude(height=stem_support_height) {
offset(r=1){
square($choc_stem + [3,3], center=true);
}
}
translate([5.7/2,0,0]) linear_extrude(height=stem_support_height) {
offset(r=1){
square($choc_stem + [3,3], center=true);
}
}
}
}

View File

@ -77,5 +77,9 @@ module tines_support(stem_type, stem_support_height, slop) {
}
} else if (stem_type == "alps"){
centered_tines(stem_support_height);
} else if (stem_type == "choc"){
if ($key_length < 2) translate([0,0,$stem_support_height / 2]) cube([total_key_width($wall_thickness)+$wall_thickness/4, 0.42, $stem_support_height], center = true);
/* translate([-5.7/2,0,$stem_support_height / 2]) cube([0.5, total_key_height($wall_thickness), $stem_support_height], center = true); */
/* translate([5.7/2,0,$stem_support_height / 2]) cube([0.5, total_key_height($wall_thickness), $stem_support_height], center = true); */
}
}

View File

@ -4,7 +4,7 @@ include <stems/box_cherry.scad>
include <stems/alps.scad>
include <stems/filled.scad>
include <stems/cherry_stabilizer.scad>
include <stems/custom.scad>
include <stems/choc.scad>
//whole stem, alps or cherry, trimmed to fit
@ -21,6 +21,8 @@ module stem(stem_type, depth, slop, throw){
filled_stem();
} else if (stem_type == "cherry_stabilizer") {
cherry_stabilizer_stem(depth, slop, throw);
} else if (stem_type == "choc") {
choc_stem(depth, slop, throw);
} else if (stem_type == "disable") {
children();
} else {

View File

@ -4,11 +4,11 @@ include <../functions.scad>
// splits the stem into halves - allows easier fitment
extra_vertical = 0.6;
module inside_cherry_stabilizer_cross(slop) {
module inside_cherry_stabilizer_cross(slop, throw) {
// inside cross
// translation purely for aesthetic purposes, to get rid of that awful lattice
translate([0,0,-SMALLEST_POSSIBLE]) {
linear_extrude(height = $stem_throw) {
linear_extrude(height = throw) {
square(cherry_cross(slop, extra_vertical)[0], center=true);
square(cherry_cross(slop, extra_vertical)[1], center=true);
}
@ -24,6 +24,6 @@ module cherry_stabilizer_stem(depth, slop, throw) {
}
}
inside_cherry_stabilizer_cross(slop);
inside_cherry_stabilizer_cross(slop, throw);
}
}

17
src/stems/choc.scad Normal file
View File

@ -0,0 +1,17 @@
separation = 5.7;
positions = [
[separation/2, 0],
[-separation/2, 0],
];
// TODO throw not used
module choc_stem(depth, slop, throw){
for (position=positions) {
translate([position.x,position.y, depth/2]) single_choc_stem(depth, slop);
}
}
module single_choc_stem(depth, slop) {
cube([$choc_stem.x - slop, $choc_stem.y - slop, depth], center=true);
}

View File

@ -40,6 +40,14 @@ module flared(stem_type, loft, height) {
square(outer_cherry_stabilizer_stem($stem_slop) - [2,2], center=true);
}
}
} else if (stem_type == "choc") {
// single support, just the stem
new_choc_scale = [scale_for_45(height, $choc_stem[0] + 5.7 - $stem_slop), scale_for_45(height, $choc_stem[1])];
translate([0,0,0]) linear_extrude(height=height, scale = new_choc_scale){
// TODO make a choc_stem() function so it can build in the slop
square([$choc_stem[0] + 5.7 - $stem_slop, $choc_stem[1] - $stem_slop], 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