add a massive chunk of stuff because you forgot to keep doing atomic commits

This commit is contained in:
Bob - Home - Windows 2017-12-20 00:47:03 -05:00
parent f252ec2c30
commit 6d2bb24ccb
31 changed files with 668 additions and 674 deletions

116
src/dishes.scad Normal file
View file

@ -0,0 +1,116 @@
include <libraries/geodesic_sphere.scad>
include <shapes.scad>
//geodesic looks much better, but runs very slow for anything above a 2u
geodesic=false;
//dish selector
module dish(width, height, depth, inverted, tilt) {
if($dish_type == "cylindrical"){
cylindrical_dish(width, height, depth, inverted, tilt);
}
else if ($dish_type == "spherical") {
spherical_dish(width, height, depth, inverted, tilt);
}
else if ($dish_type == "sideways cylindrical"){
sideways_cylindrical_dish(width, height, depth, inverted, tilt);
}
else if ($dish_type == "old spherical") {
old_spherical_dish(width, height, depth, inverted, tilt);
}
// else no dish, "no dish" is the value
}
module cylindrical_dish(width, height, depth, inverted, tilt){
// .5 has problems starting around 3u
$fa=.25;
/* we do some funky math here
* basically you want to have the dish "dig in" to the keycap x millimeters
* in order to do that you have to solve a small (2d) system of equations
* where the chord of the spherical cross section of the dish is
* the width of the keycap.
*/
// the distance you have to move the dish so it digs in depth millimeters
chord_length = (pow(width, 2) - 4 * pow(depth, 2)) / (8 * depth);
//the radius of the dish
rad = (pow(width, 2) + 4 * pow(depth, 2)) / (8 * depth);
direction = inverted ? -1 : 1;
rotate([90-tilt,0,0]){
translate([0,chord_length * direction,0]){
cylinder(h=height + 20, r=rad, center=true);
}
}
}
module sideways_cylindrical_dish(width, height, depth, inverted, tilt){
$fa=1;
chord_length = (pow(height, 2) - 4 * pow(depth, 2)) / (8 * depth);
rad = (pow(height, 2) + 4 * pow(depth, 2)) / (8 * depth);
direction = inverted ? -1 : 1;
rotate([90,tilt,90]){
translate([0,chord_length * direction,0]){
cylinder(h = width + 20,r=rad, center=true); // +20 for fudge factor
}
}
}
module spherical_dish(width, height, depth, inverted, tilt, txt=""){
//same thing as the cylindrical dish here, but we need the corners to just touch - so we have to find the hypotenuse of the top
chord = pow((pow(width,2) + pow(height, 2)),0.5); //getting diagonal of the top
// the distance you have to move the dish up so it digs in depth millimeters
chord_length = (pow(chord, 2) - 4 * pow(depth, 2)) / (8 * depth);
//the radius of the dish
rad = (pow(chord, 2) + 4 * pow(depth, 2)) / (8 * depth);
direction = inverted ? -1 : 1;
/*intersection(){*/
rotate([-tilt,0,0]){
translate([0,0,0 * direction]){
if (geodesic){
$fa=20;
scale([chord/2/depth, chord/2/depth]) {
geodesic_sphere(r=depth);
}
} else {
$fa=7;
// rotate 1 because the bottom of the sphere looks like trash.
scale([chord/2/depth, chord/2/depth]) {
geodesic_sphere(r=depth);
}
}
}
}
}
//the older, 'more accurate', and MUCH slower spherical dish.
// generates the largest sphere possible that still contains the chord we are looking for
// much more graduated curvature at an immense cost
module old_spherical_dish(width, height, depth, inverted, tilt, txt=""){
//same thing as the cylindrical dish here, but we need the corners to just touch - so we have to find the hypotenuse of the top
chord = pow((pow(width,2) + pow(height, 2)),0.5); //getting diagonal of the top
// the distance you have to move the dish up so it digs in depth millimeters
chord_length = (pow(chord, 2) - 4 * pow(depth, 2)) / (8 * depth);
//the radius of the dish
rad = (pow(chord, 2) + 4 * pow(depth, 2)) / (8 * depth);
direction = inverted ? -1 : 1;
/*intersection(){*/
rotate([-tilt,0,0]){
translate([0,0,chord_length * direction]){
if (geodesic){
$fa=7;
geodesic_sphere(r=rad);
} else {
$fa=1;
// rotate 1 because the bottom of the sphere looks like trash
sphere(r=rad);
}
}
}
}

250
src/key.scad Normal file
View file

@ -0,0 +1,250 @@
// files
include <shapes.scad>
include <stems.scad>
include <dishes.scad>
include <libraries/geodesic_sphere.scad>
/* [Fancy Bowed Sides] */
// if you're doing fancy bowed keycap sides, this controls how many slices you take
// default of 1 for no sampling, just top/bottom
height_slices = 1;
enable_side_sculpting = false;
/* [Hidden] */
$fs = .1;
//beginning to use unit instead of baked in 19.05
unit = 19.05;
//minkowski radius. radius of sphere used in minkowski sum for minkowski_key function. 1.75 default for faux G20
$minkowski_radius = .33;
// derived values. can't be variables if we want them to change when the special variables do
// actual mm key width and height
function total_key_width() = $bottom_key_width + unit * ($key_length - 1);
function total_key_height() = $bottom_key_height + unit * ($key_height - 1);
// 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;
// side sculpting functions
// bows the sides out on stuff like SA and DSA keycaps
function side_sculpting(progress) = (1 - progress) * 2.5;
// makes the rounded corners of the keycap grow larger as they move upwards
function corner_sculpting(progress) = pow(progress, 2);
// key shape including dish. used as the ouside and inside shape in key()
module shape(thickness_difference, depth_difference){
intersection(){
dished(depth_difference, $inverted_dish) {
color([.2667,.5882,1]) shape_hull(thickness_difference, depth_difference);
}
if ($inverted_dish) {
// larger shape_hull to clip off bits of the inverted dish
color([.5412, .4784, 1]) shape_hull(thickness_difference, 0, 2);
}
}
}
// shape of the key but with soft, rounded edges. much more realistic, MUCH more complex. orders of magnitude more complex
module rounded_shape() {
render(){
minkowski(){
// half minkowski. that means the shape is neither circumscribed nor inscribed.
shape($minkowski_radius * 2, $minkowski_radius/2);
difference(){
sphere(r=$minkowski_radius, $fn=24);
translate([0,0,-$minkowski_radius])
cube([2*$minkowski_radius,2*$minkowski_radius,2*$minkowski_radius], 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 ($linear_extrude_shape) {
linear_extrude_shape_hull(thickness_difference, depth_difference, extra_slices);
} else {
hull_shape_hull(thickness_difference, depth_difference, extra_slices);
}
}
}
//corollary is 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, $corner_radius);
}
}
}
}
module hull_shape_hull(thickness_difference, depth_difference, extra_slices = 0) {
slices = 10;
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) {
// makes the sides bow
extra_side_size = $enable_side_sculpting ? side_sculpting(progress) : 0;
// makes the rounded corners of the keycap grow larger as they move upwards
extra_corner_size = $enable_side_sculpting ? corner_sculpting(progress) : 0;
// 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;
skew_this_slice = $top_skew * progress;
depth_this_slice = ($total_depth - depth_difference) * progress;
tilt_this_slice = -$top_tilt / $key_height * progress;
translate([0, skew_this_slice, depth_this_slice]) {
rotate([tilt_this_slice,0,0]){
linear_extrude(height = 0.001){
key_shape(
total_key_width(),
total_key_height(),
thickness_difference+extra_width_this_slice,
thickness_difference+extra_height_this_slice,
$corner_radius + extra_corner_size
);
}
}
}
}
module dished(depth_difference, inverted = false) {
if (inverted) {
union() {
children();
translate([$dish_skew_x, $top_skew + $dish_skew_y, $total_depth - depth_difference]){
color([.4078, .3569, .749]) dish(top_total_key_width() + $dish_overdraw_width, top_total_key_height() + $dish_overdraw_height, $dish_depth, $inverted_dish, $top_tilt / $key_height);
}
}
} else {
difference() {
children();
translate([$dish_skew_x, $top_skew + $dish_skew_y, $total_depth - depth_difference]){
color([.4078, .3569, .749]) dish(top_total_key_width() + $dish_overdraw_width, top_total_key_height() + $dish_overdraw_height, $dish_depth, $inverted_dish, $top_tilt / $key_height);
}
}
}
}
// puts it's children at the center of the dishing on the key. this DOES rotate them though, it's not straight up
module top_of_key(){
extra_dish_depth = ($dish_type == "no dish") ? 0 : $dish_depth;
translate([$dish_skew_x, $top_skew + $dish_skew_y, $total_depth - extra_dish_depth]){
rotate([-$top_tilt,0,0]){
children();
}
}
}
module keytext() {
extra_inset_depth = ($inset_text) ? 0.3 : 0;
translate([0, 0, -extra_inset_depth]){
top_of_key(){
linear_extrude(height=$dish_depth){
text(text=$text, font=$font, size=$font_size, halign="center", valign="center");
}
}
}
}
module connectors() {
intersection() {
for (connector_pos = $connectors) {
translate([connector_pos[0], connector_pos[1], $stem_inset]) {
rotate([0, 0, $stem_rotation]){
color([1, .6941, .2]) connector($stem_profile, $total_depth, $has_brim, $slop, $stem_inset, $support_type);
}
}
}
// cut off anything that isn't underneath the keytop
shape($wall_thickness, $keytop_thickness);
}
}
//approximate (fully depressed) cherry key to check clearances
module clearance_check() {
if($clearance_check == true && ($stem_profile == "cherry" || $stem_profile == "cherry_rounded")){
color([1,0,0, 0.5]){
translate([0,0,3.6 + $stem_inset - 5]) {
%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);
}
}
}
}
}
module keytop() {
difference(){
if ($rounded_key) {
rounded_shape();
} else {
shape(0, 0);
}
translate([0,0,-0.01]) shape($wall_thickness, $keytop_thickness);
}
}
// The final, penultimate key generation function.
// takes all the bits and glues them together. requires configuration with special variables.
module key() {
difference() {
union(){
keytop();
if($stem_profile != "blank") connectors();
if(!$inset_text) keytext();
clearance_check();
top_of_key() {
children();
}
}
if ($inset_text) keytext();
}
}
// actual full key with space carved out and keystem/stabilizer connectors
// this is an example key with all the fixins
module example_key(){
include <settings.scad>
key();
}
example_key();

View file

@ -0,0 +1,110 @@
// from https://www.thingiverse.com/thing:1484333
// public domain license
// same syntax and semantics as built-in sphere, so should be a drop-in replacement
// it's a bit slow for large numbers of facets
module geodesic_sphere(r=-1, d=-1) {
// if neither parameter specified, radius is taken to be 1
rad = r > 0 ? r : d > 0 ? d/2 : 1;
pentside_pr = 2*sin(36); // side length compared to radius of a pentagon
pentheight_pr = sqrt(pentside_pr*pentside_pr - 1);
// from center of sphere, icosahedron edge subtends this angle
edge_subtend = 2*atan(pentheight_pr);
// vertical rotation by 72 degrees
c72 = cos(72);
s72 = sin(72);
function zrot(pt) = [ c72*pt[0]-s72*pt[1], s72*pt[0]+c72*pt[1], pt[2] ];
// rotation from north to vertex along positive x
ces = cos(edge_subtend);
ses = sin(edge_subtend);
function yrot(pt) = [ ces*pt[0] + ses*pt[2], pt[1], ces*pt[2]-ses*pt[0] ];
// 12 icosahedron vertices generated from north, south, yrot and zrot
ic1 = [ 0, 0, 1 ]; // north
ic2 = yrot(ic1); // north and +x
ic3 = zrot(ic2); // north and +x and +y
ic4 = zrot(ic3); // north and -x and +y
ic5 = zrot(ic4); // north and -x and -y
ic6 = zrot(ic5); // north and +x and -y
ic12 = [ 0, 0, -1]; // south
ic10 = yrot(ic12); // south and -x
ic11 = zrot(ic10); // south and -x and -y
ic7 = zrot(ic11); // south and +x and -y
ic8 = zrot(ic7); // south and +x and +y
ic9 = zrot(ic8); // south and -x and +y
// start with icosahedron, icos[0] is vertices and icos[1] is faces
icos = [ [ic1, ic2, ic3, ic4, ic5, ic6, ic7, ic8, ic9, ic10, ic11, ic12 ],
[ [0, 2, 1], [0, 3, 2], [0, 4, 3], [0, 5, 4], [0, 1, 5],
[1, 2, 7], [2, 3, 8], [3, 4, 9], [4, 5, 10], [5, 1, 6],
[7, 6, 1], [8, 7, 2], [9, 8, 3], [10, 9, 4], [6, 10, 5],
[6, 7, 11], [7, 8, 11], [8, 9, 11], [9, 10, 11], [10, 6, 11]]];
// now for polyhedron subdivision functions
// given two 3D points on the unit sphere, find the half-way point on the great circle
// (euclidean midpoint renormalized to be 1 unit away from origin)
function midpt(p1, p2) =
let (midx = (p1[0] + p2[0])/2, midy = (p1[1] + p2[1])/2, midz = (p1[2] + p2[2])/2)
let (midlen = sqrt(midx*midx + midy*midy + midz*midz))
[ midx/midlen, midy/midlen, midz/midlen ];
// given a "struct" where pf[0] is vertices and pf[1] is faces, subdivide all faces into
// 4 faces by dividing each edge in half along a great circle (midpt function)
// and returns a struct of the same format, i.e. pf[0] is a (larger) list of vertices and
// pf[1] is a larger list of faces.
function subdivpf(pf) =
let (p=pf[0], faces=pf[1])
[ // for each face, barf out six points
[ for (f=faces)
let (p0 = p[f[0]], p1 = p[f[1]], p2=p[f[2]])
// "identity" for-loop saves having to flatten
for (outp=[ p0, p1, p2, midpt(p0, p1), midpt(p1, p2), midpt(p0, p2) ]) outp
],
// now, again for each face, spit out four faces that connect those six points
[ for (i=[0:len(faces)-1])
let (base = 6*i) // points generated in multiples of 6
for (outf =
[[ base, base+3, base+5],
[base+3, base+1, base+4],
[base+5, base+4, base+2],
[base+3, base+4, base+5]]) outf // "identity" for-loop saves having to flatten
]
];
// recursive wrapper for subdivpf that subdivides "levels" times
function multi_subdiv_pf(pf, levels) =
levels == 0 ? pf :
multi_subdiv_pf(subdivpf(pf), levels-1);
// subdivision level based on $fa:
// level 0 has edge angle of edge_subtend so subdivision factor should be edge_subtend/$fa
// must round up to next power of 2.
// Take log base 2 of angle ratio and round up to next integer
ang_levels = ceil(log(edge_subtend/$fa)/log(2));
// subdivision level based on $fs:
// icosahedron edge length is rad*2*tan(edge_subtend/2)
// actually a chord and not circumference but let's say it's close enough
// subdivision factor should be rad*2*tan(edge_subtend/2)/$fs
side_levels = ceil(log(rad*2*tan(edge_subtend/2)/$fs)/log(2));
// subdivision level based on $fn: (fragments around circumference, not total facets)
// icosahedron circumference around equator is about 5 (level 1 is exactly 10)
// ratio of requested to equatorial segments is $fn/5
// level of subdivison is log base 2 of $fn/5
// round up to the next whole level so we get at least $fn
facet_levels = ceil(log($fn/5)/log(2));
// $fn takes precedence, otherwise facet_levels is NaN (-inf) but it's ok
// because it falls back to $fa or $fs, whichever translates to fewer levels
levels = $fn ? facet_levels : min(ang_levels, side_levels);
// subdivide icosahedron by these levels
subdiv_icos = multi_subdiv_pf(icos, levels);
scale(rad)
polyhedron(points=subdiv_icos[0], faces=subdiv_icos[1]);
}

View file

@ -0,0 +1,63 @@
function cat(L1, L2) = [for (i=[0:len(L1)+len(L2)-1])
i < len(L1)? L1[i] : L2[i-len(L1)]] ;
module dish(type = "sphere", sides = 10, length = 10){
//TODO length scaling
function pointyDish(x,y) = (x == 0 || y == 0 || x== sides || y == sides ) ? 0 : abs(5-x) + abs(5-y);
/*
matrix looks like this:
[
[0, 0, 0], [0, 1, 0], [0, 2, 0],
[1, 0, 0], [1, 1, 0], [1, 2, 0],
[2, 0, 0], [2, 1, 0], [2, 2, 0]
]
*/
// row is how many verts are actually in a row
row = sides + 1;
// first make an x-first matrix of points. (0,0), (1,0) etc
matrix = cat([
for ( y = [0 : sides], x = [ 0 : sides ]) [x, y, pointyDish(x,y)]
], [[sides / 2, sides / 2, 0]]);
echo(matrix);
echo(matrix[121]);
echo(len(matrix));
translate([sides / 2, sides / 2, 0]) sphere(r=1);
// then make 2 faces for each set of four points: (0,1,4), (0,4,3)
// sides - 1 because we are doing this fromt the bottom left corner and extending up and out 1 vertex
// so the rightmost and topmost vertexes are already covered
f1 = [
for (y = [0 : sides-1], x = [ 0 : sides-1 ], num = [0, 1]) (
num == 0 ?
[(x + row * y), (x + row * y + 1), (x + row * y + 1 + row)] :
[(x + row * y), (x + row * y + 1 + row), (x + row * y + row)])
];
f2 = cat(f1, [for (n = [0: sides-1]) [n, n+1, len(matrix) - 1]]);
faces = cat(f2, [for (n = [len(matrix) - sides-1 : len(matrix)]) [n-1, n, len(matrix) - 1]]);
// add
/*
faces needs to start like this:
[
[0,1,4,3],
[1,2,5,4]
]*/
/*index, index + 1, index + 1 + row, index + row*/
polyhedron(points = matrix, faces=faces);
}
dish();

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2014 Oskar Linde
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,81 @@
scad-utils
==========
Utility libraries for OpenSCAD
Morphology
----------
contains basic 2D morphology operations
inset(d=1) - creates a polygon at an offset d inside a 2D shape
outset(d=1) - creates a polygon at an offset d outside a 2D shape
fillet(r=1) - adds fillets of radius r to all concave corners of a 2D shape
rounding(r=1) - adds rounding to all convex corners of a 2D shape
shell(d,center=false) - makes a shell of width d along the edge of a 2D shape
- positive values of d places the shell on the outside
- negative values of d places the shell on the inside
- center=true and positive d places the shell centered on the edge
### Examples
With a basic sample polygon shape,
module shape() {
polygon([[0,0],[1,0],[1.5,1],[2.5,1],[2,-1],[0,-1]]);
}
and `$fn=32;`.
* `inset(d=0.3) shape();`
![](http://oskarlinde.github.io/scad-utils/img/morph-0.png)
* `outset(d=0.3) shape();`
![](http://oskarlinde.github.io/scad-utils/img/morph-1.png)
* `rounding(r=0.3) shape();`
![](http://oskarlinde.github.io/scad-utils/img/morph-2.png)
* `fillet(r=0.3) shape();`
![](http://oskarlinde.github.io/scad-utils/img/morph-3.png)
*`shell(d=0.3) shape();`
![](http://oskarlinde.github.io/scad-utils/img/morph-4.png)
*`shell(d=-0.3) shape();`
![](http://oskarlinde.github.io/scad-utils/img/morph-5.png)
*`shell(d=0.3,center=true) shape();`
![](http://oskarlinde.github.io/scad-utils/img/morph-6.png)
Mirror
------
contains simple mirroring functions
mirror_x()
mirror_y()
mirror_z()
example:
module arrow(l=1,w=.6,t=0.15) {
mirror_y() polygon([[0,0],[l,0],[l-w/2,w/2],[l-w/2-sqrt(2)*t,w/2],[l-t/2-sqrt(2)*t,t/2],[0,t/2]]);
}

View file

@ -0,0 +1,324 @@
// NOTE: this code uses
// * experimental let() syntax
// * experimental list comprehension syntax
// * search() bugfix and feature addition
// * vector min()/max()
// Calculates the convex hull of a set of points.
// The result is expressed in point indices.
// If the points are collinear (or 2d), the result is a convex
// polygon [i1,i2,i3,...], otherwise a triangular
// polyhedron [[i1,i2,i3],[i2,i3,i4],...]
function hull(points) =
!(len(points) > 0) ? [] :
len(points[0]) == 2 ? convexhull2d(points) :
len(points[0]) == 3 ? convexhull3d(points) : [];
epsilon = 1e-9;
// 2d version
function convexhull2d(points) =
len(points) < 3 ? [] : let(
a=0, b=1,
c = find_first_noncollinear([a,b], points, 2)
) c == len(points) ? convexhull_collinear(points) : let(
remaining = [ for (i = [2:len(points)-1]) if (i != c) i ],
polygon = area_2d(points[a], points[b], points[c]) > 0 ? [a,b,c] : [b,a,c]
) convex_hull_iterative_2d(points, polygon, remaining);
// Adds the remaining points one by one to the convex hull
function convex_hull_iterative_2d(points, polygon, remaining, i_=0) = i_ >= len(remaining) ? polygon :
let (
// pick a point
i = remaining[i_],
// find the segments that are in conflict with the point (point not inside)
conflicts = find_conflicting_segments(points, polygon, points[i])
// no conflicts, skip point and move on
) len(conflicts) == 0 ? convex_hull_iterative_2d(points, polygon, remaining, i_+1) : let(
// find the first conflicting segment and the first not conflicting
// conflict will be sorted, if not wrapping around, do it the easy way
polygon = remove_conflicts_and_insert_point(polygon, conflicts, i)
) convex_hull_iterative_2d(
points,
polygon,
remaining,
i_+1
);
function find_conflicting_segments(points, polygon, point) = [
for (i = [0:len(polygon)-1]) let(j = (i+1) % len(polygon))
if (area_2d(points[polygon[i]], points[polygon[j]], point) < 0)
i
];
// remove the conflicting segments from the polygon
function remove_conflicts_and_insert_point(polygon, conflicts, point) =
conflicts[0] == 0 ? let(
nonconflicting = [ for(i = [0:len(polygon)-1]) if (!contains(conflicts, i)) i ],
new_indices = concat(nonconflicting, (nonconflicting[len(nonconflicting)-1]+1) % len(polygon)),
polygon = concat([ for (i = new_indices) polygon[i] ], point)
) polygon : let(
prior_to_first_conflict = [ for(i = [0:1:min(conflicts)]) polygon[i] ],
after_last_conflict = [ for(i = [max(conflicts)+1:1:len(polygon)-1]) polygon[i] ],
polygon = concat(prior_to_first_conflict, point, after_last_conflict)
) polygon;
// 3d version
function convexhull3d(points) =
len(points) < 3 ? [ for(i = [0:1:len(points)-1]) i ] : let (
// start with a single triangle
a=0, b=1, c=2,
plane = plane(points,a,b,c),
d = find_first_noncoplanar(plane, points, 3)
) d == len(points) ? /* all coplanar*/ let (
pts2d = [ for (p = points) plane_project(p, points[a], points[b], points[c]) ],
hull2d = convexhull2d(pts2d)
) hull2d : let(
remaining = [for (i = [3:len(points)-1]) if (i != d) i],
// Build an initial tetrahedron
// swap b,c if d is in front of triangle t
bc = in_front(plane, points[d]) ? [c,b] : [b,c],
b = bc[0], c = bc[1],
triangles = [
[a,b,c],
[d,b,a],
[c,d,a],
[b,d,c],
],
// calculate the plane equations
planes = [ for (t = triangles) plane(points, t[0], t[1], t[2]) ]
) convex_hull_iterative(points, triangles, planes, remaining);
// A plane equation (normal, offset)
function plane(points, a, b, c) = let(
normal = unit(cross(points[c]-points[a], points[b]-points[a]))
) [
normal,
normal * points[a]
];
// Adds the remaining points one by one to the convex hull
function convex_hull_iterative(points, triangles, planes, remaining, i_=0) = i_ >= len(remaining) ? triangles :
let (
// pick a point
i = remaining[i_],
// find the triangles that are in conflict with the point (point not inside)
conflicts = find_conflicts(points[i], planes),
// for all triangles that are in conflict, collect their halfedges
halfedges = [
for(c = conflicts)
for(i = [0:2]) let(j = (i+1)%3)
[triangles[c][i], triangles[c][j]]
],
// find the outer perimeter of the set of conflicting triangles
horizon = remove_internal_edges(halfedges),
// generate a new triangle for each horizon halfedge together with the picked point i
new_triangles = [ for (h = horizon) concat(h,i) ],
// calculate the corresponding plane equations
new_planes = [ for (t = new_triangles) plane(points, t[0], t[1], t[2]) ]
) convex_hull_iterative(
points,
// remove the conflicting triangles and add the new ones
concat(remove_elements(triangles, conflicts), new_triangles),
concat(remove_elements(planes, conflicts), new_planes),
remaining,
i_+1
);
function convexhull_collinear(points) = let(
n = points[1] - points[0],
a = points[0],
points1d = [ for(p = points) (p-a)*n ],
min_i = min_index(points1d),
max_i = max_index(points1d)
) [ min_i, max_i ];
function min_index(values,min_,min_i_,i_) =
i_ == undef ? min_index(values,values[0],0,1) :
i_ >= len(values) ? min_i_ :
values[i_] < min_ ? min_index(values,values[i_],i_,i_+1)
: min_index(values,min_,min_i_,i_+1);
function max_index(values,max_,max_i_,i_) =
i_ == undef ? max_index(values,values[0],0,1) :
i_ >= len(values) ? max_i_ :
values[i_] > max_ ? max_index(values,values[i_],i_,i_+1)
: max_index(values,max_,max_i_,i_+1);
function remove_elements(array, elements) = [
for (i = [0:len(array)-1])
if (!search(i, elements))
array[i]
];
function remove_internal_edges(halfedges) = [
for (h = halfedges)
if (!contains(halfedges, reverse(h)))
h
];
function plane_project(point, a, b, c) = let(
u = b-a,
v = c-a,
n = cross(u,v),
w = cross(n,u),
relpoint = point-a
) [relpoint * u, relpoint * w];
function plane_unproject(point, a, b, c) = let(
u = b-a,
v = c-a,
n = cross(u,v),
w = cross(n,u)
) a + point[0] * u + point[1] * w;
function reverse(arr) = [ for (i = [len(arr)-1:-1:0]) arr[i] ];
function contains(arr, element) = search([element],arr)[0] != [] ? true : false;
function find_conflicts(point, planes) = [
for (i = [0:len(planes)-1])
if (in_front(planes[i], point))
i
];
function find_first_noncollinear(line, points, i) =
i >= len(points) ? len(points) :
collinear(points[line[0]],
points[line[1]],
points[i]) ? find_first_noncollinear(line, points, i+1)
: i;
function find_first_noncoplanar(plane, points, i) =
i >= len(points) ? len(points) :
coplanar(plane, points[i]) ? find_first_noncoplanar(plane, points, i+1)
: i;
function distance(plane, point) = plane[0] * point - plane[1];
function in_front(plane, point) = distance(plane, point) > epsilon;
function coplanar(plane, point) = abs(distance(plane,point)) <= epsilon;
function unit(v) = v/norm(v);
function area_2d(a,b,c) = (
a[0] * (b[1] - c[1]) +
b[0] * (c[1] - a[1]) +
c[0] * (a[1] - b[1])) / 2;
function collinear(a,b,c) = abs(area_2d(a,b,c)) < epsilon;
function spherical(cartesian) = [
atan2(cartesian[1], cartesian[0]),
asin(cartesian[2])
];
function cartesian(spherical) = [
cos(spherical[1]) * cos(spherical[0]),
cos(spherical[1]) * sin(spherical[0]),
sin(spherical[1])
];
/// TESTCODE
phi = 1.618033988749895;
testpoints_on_sphere = [ for(p =
[
[1,phi,0], [-1,phi,0], [1,-phi,0], [-1,-phi,0],
[0,1,phi], [0,-1,phi], [0,1,-phi], [0,-1,-phi],
[phi,0,1], [-phi,0,1], [phi,0,-1], [-phi,0,-1]
])
unit(p)
];
testpoints_spherical = [ for(p = testpoints_on_sphere) spherical(p) ];
testpoints_circular = [ for(a = [0:15:360-epsilon]) [cos(a),sin(a)] ];
testpoints_coplanar = let(u = unit([1,3,7]), v = unit([-2,1,-2])) [ for(i = [1:10]) rands(-1,1,1)[0] * u + rands(-1,1,1)[0] * v ];
testpoints_collinear_2d = let(u = unit([5,3])) [ for(i = [1:20]) rands(-1,1,1)[0] * u ];
testpoints_collinear_3d = let(u = unit([5,3,-5])) [ for(i = [1:20]) rands(-1,1,1)[0] * u ];
testpoints2d = 20 * [for (i = [1:10]) concat(rands(-1,1,2))];
testpoints3d = 20 * [for (i = [1:50]) concat(rands(-1,1,3))];
// All points are on the sphere, no point should be red
translate([-50,0]) visualize_hull(20*testpoints_on_sphere);
// 2D points
translate([50,0]) visualize_hull(testpoints2d);
// All points on a circle, no point should be red
translate([0,50]) visualize_hull(20*testpoints_circular);
// All points 3d but collinear
translate([0,-50]) visualize_hull(20*testpoints_coplanar);
// Collinear
translate([50,50]) visualize_hull(20*testpoints_collinear_2d);
// Collinear
translate([-50,50]) visualize_hull(20*testpoints_collinear_3d);
// 3D points
visualize_hull(testpoints3d);
module visualize_hull(points) {
hull = hull(points);
%if (len(hull) > 0 && len(hull[0]) > 0)
polyhedron(points=points, faces = hull);
else
polyhedron(points=points, faces = [hull]);
for (i = [0:len(points)-1]) assign(p = points[i], $fn = 16) {
translate(p) {
if (hull_contains_index(hull,i)) {
color("blue") sphere(1);
} else {
color("red") sphere(1);
}
}
}
function hull_contains_index(hull, index) =
search(index,hull,1,0) ||
search(index,hull,1,1) ||
search(index,hull,1,2);
}

View file

@ -0,0 +1,32 @@
// 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]) ];

View file

@ -0,0 +1,48 @@
// 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]];

View file

@ -0,0 +1,30 @@
// Copyright (c) 2013 Oskar Linde. All rights reserved.
// License: BSD
//
// This library contains simple mirroring functions
//
// mirror_x()
// mirror_y()
// mirror_z()
module mirror_x() {
union() {
child();
scale([-1,1,1]) child();
}
}
module mirror_y() {
union() {
child();
scale([1,-1,1]) child();
}
}
module mirror_z() {
union() {
child();
scale([1,1,-1]) child();
}
}

View file

@ -0,0 +1,109 @@
// Copyright (c) 2013 Oskar Linde. All rights reserved.
// License: BSD
//
// This library contains basic 2D morphology operations
//
// outset(d=1) - creates a polygon at an offset d outside a 2D shape
// inset(d=1) - creates a polygon at an offset d inside a 2D shape
// fillet(r=1) - adds fillets of radius r to all concave corners of a 2D shape
// rounding(r=1) - adds rounding to all convex corners of a 2D shape
// shell(d,center=false) - makes a shell of width d along the edge of a 2D shape
// - positive values of d places the shell on the outside
// - negative values of d places the shell on the inside
// - center=true and positive d places the shell centered on the edge
module outset(d=1) {
// Bug workaround for older OpenSCAD versions
if (version_num() < 20130424) render() outset_extruded(d) child();
else minkowski() {
circle(r=d);
child();
}
}
module outset_extruded(d=1) {
projection(cut=true) minkowski() {
cylinder(r=d);
linear_extrude(center=true) child();
}
}
module inset(d=1) {
render() inverse() outset(d=d) inverse() child();
}
module fillet(r=1) {
inset(d=r) render() outset(d=r) child();
}
module rounding(r=1) {
outset(d=r) inset(d=r) child();
}
module shell(d,center=false) {
if (center && d > 0) {
difference() {
outset(d=d/2) child();
inset(d=d/2) child();
}
}
if (!center && d > 0) {
difference() {
outset(d=d) child();
child();
}
}
if (!center && d < 0) {
difference() {
child();
inset(d=-d) child();
}
}
if (d == 0) child();
}
// Below are for internal use only
module inverse() {
difference() {
square(1e5,center=true);
child();
}
}
// TEST CODE
use <mirror.scad>
module arrow(l=1,w=.6,t=0.15) {
mirror_y() polygon([[0,0],[l,0],[l-w/2,w/2],[l-w/2-sqrt(2)*t,w/2],[l-t/2-sqrt(2)*t,t/2],[0,t/2]]);
}
module shape() {
polygon([[0,0],[1,0],[1.5,1],[2.5,1],[2,-1],[0,-1]]);
}
if(0) assign($fn=32) {
for (p = [0:10*3-1]) assign(o=floor(p/3)) {
translate([(p%3)*2.5,-o*3]) {
//%if (p % 3 == 1) translate([0,0,1]) shape();
if (p % 3 == 0) shape();
if (p % 3 == 1) translate([0.6,0]) arrow();
if (p % 3 == 2) {
if (o == 0) inset(d=0.3) shape();
if (o == 1) outset(d=0.3) shape();
if (o == 2) rounding(r=0.3) shape();
if (o == 3) fillet(r=0.3) shape();
if (o == 4) shell(d=0.3) shape();
if (o == 5) shell(d=-0.3) shape();
if (o == 6) shell(d=0.3,center=true) shape();
if (o == 7) rounding(r=0.3) fillet(r=0.3) shape();
if (o == 8) shell(d=0.3,center=true) fillet(r=0.3) rounding(r=0.3) shape();
if (o == 9) shell(d=-0.3) fillet(r=0.3) rounding(r=0.3) shape();
}
}
}
}

View file

@ -0,0 +1,60 @@
use <linalg.scad>
use <so3.scad>
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);

View file

@ -0,0 +1,16 @@
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

View file

@ -0,0 +1,82 @@
// so3
use <linalg.scad>
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);

View file

@ -0,0 +1,113 @@
// Spline module for scad-util library
// Author Sergei Kuzmin, 2014.
// For n+1 given point and hense n intervals returns the spline coefficient matrix.
// param p defines the anchor points.
// File defines two functions: spline_args and spline.
// example usage:
// spl1 = spline_args(point, v1=[0,1,0], closed=false);
// interpolated_points = [for(t=[0:0.1:len(point)-1]) spline(spl1, t)]
use <linalg.scad>
use <lists.scad>
q1=[[1,0,0,0],[1,1,1,1],[0,1,2,3],[0,0,1,3]];
q1inv=[[1,0,0,0],[-3,3,-2,1],[3,-3,3,-2],[-1,1,-1,1]];
q2=[[0,0,0,0],[0,0,0,0],[0,-1,0,0],[0,0,-1,0]];
qn1i2=-q1inv*q2;
z3=[0,0,0];
z4=[0,0,0,0];
function matrix_power(m,n)= n==0? (len(m)==3?identity3():identity4()) :
n==1 ? m : (n%2==1) ? matrix_power(m*m,floor(n/2))*m : matrix_power(m*m,n/2);
function det(m) = let(r=[for(i=[0:1:len(m)-1]) i]) det_help(m, 0, r);
// Construction indices list is inefficient, but currently there is no way to imperatively
// assign to a list element
function det_help(m, i, r) = len(r) == 0 ? 1 :
m[len(m)-len(r)][r[i]]*det_help(m,0,remove(r,i)) - (i+1<len(r)? det_help(m, i+1, r) : 0);
function matrix_invert(m) = let(r=[for(i=[0:len(m)-1]) i]) [for(i=r) [for(j=r)
((i+j)%2==0 ? 1:-1) * matrix_minor(m,0,remove(r,j),remove(r,i))]] / det(m);
function matrix_minor(m,k,ri, rj) = let(len_r=len(ri)) len_r == 0 ? 1 :
m[ri[0]][rj[k]]*matrix_minor(m,0,remove(ri,0),remove(rj,k)) - (k+1<len_r?matrix_minor(m,k+1,ri,rj) : 0);
function spline_u(i,p) = [p[i],p[i+1],z3,z3];
function spline_args(p, closed=false, v1=undef, v2=undef)=len(p)<2 ? []:
let(q3=closed?q2:[z4, z4, v1==undef?[0,0,1,0]:[0,1,0,0], z4],
q4=closed?q1:[[1,0,0,0], [1,1,1,1], z4, v2==undef?[0,0,1,3]:[0,1,2,3]],
pcnt=closed? len(p) + 1 : len(p),
un=[p[pcnt-2],p[closed?0:pcnt-1],v1==undef?z4:v1, v2==undef?z4:v2],
sn=matrix_invert(q4+q3*matrix_power(qn1i2,pcnt-2))*(un-q3*q1inv*spline_helper(0, pcnt, p)))
// result[i+1] recurrently defines result[i]. This is O(n) runtime with imperative language and
// may be O(n^2) if OpenSCAD doesn't cache spline_si(i+1).
[for(i=[0:pcnt-2]) spline_si(i, pcnt-2, p, sn)];
// n is number of points including pseudopoint for closed contour
// Weird construct cause there is no if statement for functions
function spline_helper(i, n, p) = let(u=[p[i], p[i+1], z3, z3]) i+3>=n? u : u-q2*q1inv*spline_helper(i+1, n, p);
// knowing s[j+1], calculate s[j]. Stop when found s[i]
function spline_si(i,n, p, sn) = i == n ? sn : q1inv*(spline_u(i,p)-q2*spline_si(i+1, n, p, sn));
// Takes array of (3n+1) points or (2n + 2) points, if tangent segments are symmetric.
// For non-symmetric version input is: point0, normal0, neg_normal1, point1, normal1, ... neg_normal_n, point_n
// For symmetric version: point0, normal0, point1, normal1, ... , normal_n_sub_1, point_n
// In the second case second tangent is constructed from the next tangent by symmetric map.
// I.e. if current points are p0,p1,p2 then anchor points are p0 and p2, first tangent defined by p1-p0,
// second tangent defined by p3-p2.
// Return array of coefficients accepted by spline(), spline_tan() and similar
function bezier3_args(p, symmetric=false) = let(step=symmetric?2:3)
[for(i=[0:step:len(p)-3]) [[1,0,0,0],[-3,3,0,0],[3,-6,3,0],[-1,3,-3,1]]*
(symmetric?[p[i],p[i]+p[i+1],p[i+2]-p[i+3],p[i+2]] : [p[i], p[i]+p[i+1], p[i+3]+p[i+2], p[i+3]])];
// s - spline arguments calculated by spline_args
// t - defines point on curve. each segment length is 1. I.e. t= 0..1 is first segment, t=1..2 - second.
function spline(s, t)= let(i=t>=len(s)?len(s)-1: floor(t), t2=t-i) [1,t2,t2*t2,t2*t2*t2]*s[i];
function spline_tan(s, t)= let(i=t>=len(s)?len(s)-1: floor(t), t2=t-i) [0,1,2*t2,3*t2*t2]*s[i];
function spline_tan_unit(s, t)= unit(spline_tan(s,t));
function spline_d2(s,t)= let(i=t>=len(s)?len(s)-1: floor(t), t2=t-i) [0,0,2,6*t2]*s[i];
function spline_binormal_unit(s,t)= unit(cross(spline_tan(s, t), spline_d2(s,t)));
function spline_normal_unit(s,t)= unit(cross(spline_tan(s, t), spline_binormal_unit(s,t)));
function spline_transform(s, t)=
construct_Rt(transpose_3([spline_normal_unit(s,t), spline_binormal_unit(s,t), spline_tan_unit(s,t)]), spline(s,t));
// Unit tests
__s = spline_args([[0,10,0], [10,0,0],[0,-5,2]], v1=[0,1,0], v2=[-1,0,0], closed=true);
for(t=[0:0.01:len(__s)]) translate(spline(__s, t))
cube([0.2,0.2,0.2], center=true);
__s1=spline_args([[0,0,0],[0,0,15], [26,0,26+15]], /*v1=[0,0,100],*/ v2=[40,0,0]);
for(t=[0:0.01:len(s1)]) translate(spline(__s1, t))
cube([0.2,0.2,0.2], center=true);
__s2=bezier3_args([[0,0,0],[0,0,10],[0,0,15],[0,0,26*0.552284],[26,0,41],[26*0.552284,0,0]],symmetric=true);
echo(__s2);
for(t=[0:0.01:len(__s2)]) translate(spline(__s2, t))
cube([0.2,0.2,0.2], center=true);
// Rotation methods taken from list-comprehension-demos/sweep.scad to demonstrate normal and binormal
// Normally spline_transform is more convenient
function __rotation_from_axis(x,y,z) = [[x[0],y[0],z[0]],[x[1],y[1],z[1]],[x[2],y[2],z[2]]];
function __rotate_from_to(a,b,_axis=[]) =
len(_axis) == 0
? __rotate_from_to(a,b,unit(cross(a,b)))
: _axis*_axis >= 0.99 ? __rotation_from_axis(unit(b),_axis,cross(_axis,unit(b))) *
transpose_3(__rotation_from_axis(unit(a),_axis,cross(_axis,unit(a)))) : identity3();
__s3 = spline_args([[0,10,0], [6,6,0], [10,0,0],[0,-5,4]], v1=[0,1,0], v2=[-1,0,0], closed=true);
for(t=[0:0.05:len(__s3)]) translate(spline(__s3, t)) {
translate([0,0,3]) multmatrix(m=__rotate_from_to([0,0,1],spline_normal_unit(__s3,t)))
cylinder(r1=0.1, r2=0, h=1, $fn=3);
translate([0,0,6]) multmatrix(m=__rotate_from_to([0,0,1],spline_binormal_unit(__s3,t)))
cylinder(r1=0.1, r2=0, h=1, $fn=3);
}
translate([0,0,9]) for(t=[0:0.025:len(__s3)])
multmatrix(spline_transform(__s3,t)) cube([1,1,0.1],center=true);

View file

@ -0,0 +1,43 @@
use <so3.scad>
function val(a=undef,default=undef) = a == undef ? default : a;
function vec_is_undef(x,index_=0) = index_ >= len(x) ? true :
is_undef(x[index_]) && vec_is_undef(x,index_+1);
function is_undef(x) = len(x) > 0 ? vec_is_undef(x) : x == undef;
// Either a or b, but not both
function either(a,b,default=undef) = is_undef(a) ? (is_undef(b) ? default : b) : is_undef(b) ? a : undef;
function translationv(left=undef,right=undef,up=undef,down=undef,forward=undef,backward=undef,translation=undef) =
translationv_2(
x = either(up,-down),
y = either(right,-left),
z = either(forward,-backward),
translation = translation);
function translationv_2(x,y,z,translation) =
x == undef && y == undef && z == undef ? translation :
is_undef(translation) ? [val(x,0),val(y,0),val(z,0)]
: undef;
function rotationv(pitch=undef,yaw=undef,roll=undef,rotation=undef) =
rotation == undef ? [val(yaw,0),val(pitch,0),val(roll,0)] :
pitch == undef && yaw == undef && roll == undef ? rotation :
undef;
function trajectory(
left=undef, right=undef,
up=undef, down=undef,
forward=undef, backward=undef,
translation=undef,
pitch=undef,
yaw=undef,
roll=undef,
rotation=undef
) = concat(
translationv(left=left,right=right,up=up,down=down,forward=forward,backward=backward,translation=translation),
rotationv(pitch=pitch,yaw=yaw,roll=roll,rotation=rotation)
);
function rotationm(rotation=undef,pitch=undef,yaw=undef,roll=undef) = so3_exp(rotationv(rotation=rotation,pitch=pitch,yaw=yaw,roll=roll));

View file

@ -0,0 +1,89 @@
use <linalg.scad>
use <se3.scad>
function left_multiply(a,bs,i_=0) = i_ >= len(bs) ? [] :
concat([
a * bs[i_]
], left_multiply(a,bs,i_+1));
function right_multiply(as,b,i_=0) = i_ >= len(as) ? [] :
concat([
as[i_] * b
], right_multiply(as,b,i_+1));
function quantize_trajectory(trajectory,step=undef,start_position=0,steps=undef,i_=0,length_=undef) =
length_ == undef ? quantize_trajectory(
trajectory=trajectory,
start_position=(step==undef?norm(take3(trajectory))/steps*start_position:start_position),
length_=norm(take3(trajectory)),
step=step,steps=steps,i_=i_) :
(steps==undef?start_position > length_:i_>=steps) ? [] :
concat([
// if steps is defined, ignore start_position
se3_exp(trajectory*(steps==undef ? start_position/length_
: i_/(steps>1?steps-1:1)))
], quantize_trajectory(trajectory=trajectory,step=step,start_position=(steps==undef?start_position+step:start_position),steps=steps,i_=i_+1,length_=length_));
function close_trajectory_loop(trajectories) = concat(trajectories,[se3_ln(invert_rt(trajectories_end_position(trajectories)))]);
function quantize_trajectories(trajectories,step=undef,start_position=0,steps=undef,loop=false,last_=identity4(),i_=0,current_length_=undef,j_=0) =
// due to quantization differences, the last step may be missed. In that case, add it:
loop==true ? quantize_trajectories(
trajectories=close_trajectory_loop(trajectories),
step=step,
start_position = start_position,
steps=steps,
loop=false,
last_=last_,
i_=i_,
current_length_=current_length_,
j_=j_) :
i_ >= len(trajectories) ? (j_ < steps ? [last_] : []) :
current_length_ == undef ?
quantize_trajectories(
trajectories=trajectories,
step = (step == undef ? trajectories_length(trajectories) / steps : step),
start_position = (step == undef ? start_position * trajectories_length(trajectories) / steps : start_position),
steps=steps,
loop=loop,
last_=last_,
i_=i_,
current_length_=norm(take3(trajectories[i_])),
j_=j_) :
concat(
left_multiply(last_,quantize_trajectory(
trajectory=trajectories[i_],
start_position=start_position,
step=step)),
quantize_trajectories(
trajectories=trajectories,
step=step,
start_position = start_position > current_length_
? start_position - current_length_
: step - ((current_length_-start_position) % step),
steps=steps,
loop=loop,
last_=last_ * se3_exp(trajectories[i_]),
i_=i_+1,
current_length_ = undef,
j_=j_+len(
quantize_trajectory(
trajectory=trajectories[i_],
start_position=start_position,
step=step
))
))
;
function trajectories_length(trajectories, i_=0) = i_ >= len(trajectories) ? 0
: norm(take3(trajectories[i_])) + trajectories_length(trajectories,i_+1);
function trajectories_end_position(rt,i_=0,last_=identity4()) =
i_ >= len(rt) ? last_ :
trajectories_end_position(rt, i_+1, last_ * se3_exp(rt[i_]));

View file

@ -0,0 +1,43 @@
use <se3.scad>
use <linalg.scad>
use <lists.scad>
/*!
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) ];

137
src/libraries/skin.scad Normal file
View file

@ -0,0 +1,137 @@
use <scad-utils/transformations.scad>
use <scad-utils/lists.scad>
// 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];
use <scad-utils/transformations.scad>
use <scad-utils/trajectory_path.scad>
use <scad-utils/trajectory.scad>
use <scad-utils/shapes.scad>
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);

80
src/settings.scad Normal file
View file

@ -0,0 +1,80 @@
// keytop thickness, aka how many millimeters between the inside and outside of the top surface of the key
$keytop_thickness = 1;
// wall thickness, aka the thickness of the sides of the keycap. note this is the total thickness, aka 3 = 1.5mm walls
$wall_thickness = 3;
//whether stabilizer connectors are enabled
$stabilizers = false;
// font used for text
$font="DejaVu Sans Mono:style=Book";
// font size used for text
$font_size = 6;
// whether or not to render fake keyswitches to check clearances
$clearance_check = false;
/* [Key profile] */
// width of the very bottom of the key
$bottom_key_width = 18.16;
// height (from the front) of the very bottom of the ke
$bottom_key_height = 18.16;
// how much less width there is on the top. eg top_key_width = bottom_key_width - width_difference
$width_difference = 6;
// how much less height there is on the top
$height_difference = 4;
// how deep the key is, before adding a dish
$total_depth = 11.5;
// the tilt of the dish in degrees. divided by key height
$top_tilt = -6;
// how skewed towards the back the top is (0 for center)
$top_skew = 1.7;
// what type of dish the key has. 0 for cylindrical, 1 for spherical, 2 for something else idk TODO
$dish_type = "cylindrical";
// how deep the dish 'digs' into the top of the keycap. this is max depth, so you can't find the height from total_depth - dish_depth. besides the top is skewed anyways
$dish_depth = 1;
// how skewed in the x direction the dish is
$dish_skew_x = 0;
// how skewed in the y direction (height) the dish is
$dish_skew_y = 0;
//length in units of key
$key_length = 1;
//height in units of key. should remain 1 for most uses
$key_height = 1;
//print brim for connector to help with bed adhesion
$has_brim = false;
// invert dishing. mostly for spacebar
$inverted_dish = false;
// array of positions of all stems. includes stabilizers as well, for now
// ternary is a bad hack to keep the stabilizers flag working
$connectors = $stabilizers ? [[0,0],[-50,0],[50,0]] : [[0,0]];
// use linear_extrude instead of hull slices to make the shape of the key
// should be faster, also required for concave shapes
$linear_extrude_shape = false;
//should the key be rounded? unnecessary for most printers, and very slow
$rounded_key = false;
// 'cherry', 'alps' or 'cherry_rounded'
$stem_profile = "cherry";
// how much higher the stem is than the bottom of the keycap.
// inset stem requires support but is more accurate in some profiles
$stem_inset = 0;
// how many degrees to rotate the stems. useful for sideways keycaps, maybe
$stem_rotation = 0;
//text to be rendered in the center of the key, if any
$text = "";
// is the text on the key inset? inset text is still experimental
$inset_text = false;
// radius of corners of keycap
$corner_radius = 1;
// keystem slop - lengthens the cross and thins out the connector
$slop = 0.3;
// support type. default is 'flared' for easy FDM printing
$support_type = "flared";
// key shape type. default is 'normal'. only other supported option is 'iso_enter'
$key_shape_type = "normal";
// ISO enter needs to be linear extruded NOT from the center. this tells the program how far up 'not from the center' is
$linear_extrude_height_adjustment = 0;
// if you need the dish to extend further, you can 'overdraw' the rectangle it will hit
$dish_overdraw_width = 0;
// same as width but for height
$dish_overdraw_height = 0;
// how many slices will be made, to approximate curves on corners. Leave at 1 if you are not curving corners
$height_slices = 1;

60
src/shapes.scad Normal file
View file

@ -0,0 +1,60 @@
$fs=.1;
unit = 19.05;
module key_shape(width, height, width_difference, height_difference, corner_size) {
if ($key_shape_type == "iso_enter") {
ISO_enter(width, height, width_difference, height_difference, corner_size);
} else if ($key_shape_type == "normal") {
roundedSquare([width - width_difference, height - height_difference], corner_size);
} else if ($key_shape_type == "circle") {
circle(d=width - width_difference);
}
}
// centered
module roundedRect(size, radius, center=true) {
linear_extrude(height = size[2]){
roundedSquare([size[0], size[1]], radius, center=center);
}
}
module roundedSquare(size, radius, center = true) {
offset(r=radius){
square([size[0] - radius * 2, size[1] - radius * 2], center=center);
}
}
// corollary is roundedSquare
// NOT 3D
module ISO_enter(width, height, width_difference, height_difference, corner_size){
function unit_length(length) = unit * (length - 1) + 18.16;
// in order to make the ISO keycap shape generic, we are going to express the
// 'elbow point' in terms of ratios. an ISO enter is just a 1.5u key stuck on
// top of a 1.25u key, but since our key_shape function doesnt understand that
// and wants to pass just width and height, we make these ratios to know where
// to put the elbow joint
width_ratio = unit_length(1.25) / unit_length(1.5);
height_ratio = unit_length(1) / unit_length(2);
// height and width difference currently don't do anything - but I think I should keep them. they don't do anything because we currently use scaling in the linear_extrude to express the difference in height and width of the top of the keycap
pointArray = [
[ -width_difference/2, -height_difference/2], // top right
[ -width_difference/2, -height + height_difference/2], // bottom right
[-width * width_ratio + width_difference/2, -height + height_difference/2], // bottom left
[-width * width_ratio + width_difference/2,-height * height_ratio + height_difference/2], // inner middle point
[ -width + width_difference/2,-height * height_ratio + height_difference/2], // outer middle point
[ -width + width_difference/2, -height_difference/2] // top left
];
minkowski(){
circle(r=corner_size);
// gives us rounded inner corner
offset(r=-corner_size*2) {
translate([(width * width_ratio)/2, height/2]) polygon(points=pointArray);
}
}
}

146
src/stems.scad Normal file
View file

@ -0,0 +1,146 @@
include <supports.scad>
brim_height = 0.4;
//whole connector, alps or cherry, trimmed to fit
module connector(stem_profile, depth, has_brim, slop, stem_inset, support_type){
echo(slop);
if (stem_profile == "alps") {
alps_stem(depth, has_brim, slop, stem_inset, support_type);
} else if (stem_profile == "cherry_rounded") {
cherry_stem_rounded(depth, has_brim, slop, stem_inset, support_type);
} else if (stem_profile == "cherry") {
cherry_stem(depth, has_brim, slop, stem_inset, support_type);
} else if (stem_profile == "filled") {
// just a cube, so no args
filled_stem();
}
}
module cherry_stem(depth, has_brim, slop, stem_inset, support_type) {
stem_width = 7.2 - slop * 2;
stem_height = 5.5 - slop * 2;
vertical_cross_width = 1.25;
// currently unused, as we want a split stem
vertical_cross_length = 3.93;
horizontal_cross_width = 1.15;
horizontal_cross_length = 4.03;
cross_depth = 4;
stem = [stem_width, stem_height];
vertical_cross = [vertical_cross_width, stem_height];
horizontal_cross = [horizontal_cross_length + slop, horizontal_cross_width];
translate([0,0,stem_inset]) {
difference(){
union() {
linear_extrude(height = depth) {
roundedSquare(stem, 1, center=true);
}
if(has_brim) {
roundedRect([stem_width*2, stem_height*2,brim_height], 1, 1, center=true);
}
}
linear_extrude(height = cross_depth) {
square(vertical_cross, center=true);
square(horizontal_cross, center=true);
}
}
// supports
if (support_type == "flared") {
flared(cross_depth, (depth - cross_depth), [stem_width, stem_height]) {
roundedSquare(stem, 1, center=true);
}
} else if (support_type == "flat") {
flat(cross_depth, (depth - cross_depth), [stem_width, stem_height]);
} else if (support_type == "bars") {
bars(cross_depth, (depth - cross_depth), [stem_width, stem_height]);
}
}
}
module cherry_stem_rounded(depth, has_brim, slop, stem_inset, support_type) {
// cross length
cross_length = 4.4;
//dimensions of connector
// outer cross extra length in y
extra_outer_cross_height = 1.1;
// dimensions of cross
// horizontal cross bar width
horizontal_cross_width = 1.4;
// vertical cross bar width
vertical_cross_width = 1.3;
// cross depth, stem height is 3.4mm
cross_depth = 4;
total_diameter = cross_length+extra_outer_cross_height;
translate([0,0,stem_inset]){
difference(){
union(){
cylinder(d=total_diameter, h=depth);
if(has_brim) {
cylinder(d=total_diameter * 2, h=brim_height);
}
}
//the cross part of the steam
translate([0,0,(cross_depth)/2]){
cube([vertical_cross_width,cross_length,cross_depth], center=true );
cube([cross_length,horizontal_cross_width,cross_depth], center=true );
}
}
// supports
if (support_type == "flared") {
flared(cross_depth, (depth - cross_depth)) {
circle(d = cross_length+extra_outer_cross_height);
}
} else if (support_type == "flat") {
flat(cross_depth, (depth - cross_depth));
} else if (support_type == "bars") {
bars(cross_depth, (depth - cross_depth));
}
}
}
module alps_stem(depth, has_brim, slop, stem_inset, support_type){
// not really cross depth, basically just the max length of stem we need for the key to function properly
cross_depth = 4;
width = 4.45;
height = 2.25;
base_width = 12;
base_height = 15;
translate([0,0,stem_inset]){
if(has_brim) {
translate([0,0,brim_height / 2]) cube([width*2,height*2,brim_height], center = true);
}
translate([0,0,depth/2]){
cube([width,height,depth], center = true);
}
}
translate([0, 0, stem_inset]){
if (support_type == "flared") {
flared(cross_depth, (depth - cross_depth)) {
square([width,height]);
}
} else if (support_type == "flat") {
flat(cross_depth, (depth - cross_depth));
} else if (support_type == "bars") {
bars(cross_depth, (depth - cross_depth));
}
}
}
module filled_stem() {
// this is mostly for testing. we don't pass the size of the keycp in here
// so we can't make this work for all keys
cube(1000, center=true);
}

21
src/supports.scad Normal file
View file

@ -0,0 +1,21 @@
// flared support designed for FDM printing, for the normal cherry stem
module flared(loft, height) {
translate([0,0,loft]){
linear_extrude(height=height, scale = [height/2,height/2]){
children();
}
}
}
module flat(loft, height) {
translate([0,0,loft + 500]){
cube(1000, center=true);
}
}
module bars(loft, height) {
translate([0,0,loft + height / 2]){
cube([2, 100, height], center = true);
cube([100, 2, height], center = true);
}
}