diff --git a/key.scad b/key.scad index a05b995..4a40f8d 100644 --- a/key.scad +++ b/key.scad @@ -135,6 +135,23 @@ module rounded_shape() { } } +//corollary is shape_hull +module ISOEnterShapeHull(thickness_difference, depth_difference, modifier){ + + 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,19.05 * 0.5,0]) + linear_extrude(height = height, scale = [width_scale, height_scale]) { + // TODO completely making up these numbers here + // 0.86mm is from the unit function, 18.16 - 19.02. no idea what the 18 is, shows me for not leaving better comments + translate([0,-19.05 * 0.5,0]) + fakeISOEnter(thickness_difference); + } +} + + // basic key shape, no dish, no inside // modifier multiplies the height and top differences of the shape, // which is only used for dishing to cut the dish off correctly @@ -158,18 +175,25 @@ module shape_hull(thickness_difference, depth_difference, modifier, extra_slices module shape_slice(index, total, thickness_difference, depth_difference, modifier) { progress = index / (total); + // TODO extract these out somehow so you can make custom rounded sides + // makes the sides bow extra_side_size = $enable_side_sculpting ? (total - index)/4 : 0; + // makes the rounded corners of the keycap grow larger as they move upwards extra_corner_size = $enable_side_sculpting ? pow(progress, 2) : 0; + // width and height differences for this slice + extra_width_difference = ($width_difference - extra_side_size) * progress * modifier; + extra_height_difference = ($height_difference - extra_side_size) * progress * modifier; + translate([ 0, $top_skew * progress, ($total_depth * modifier - depth_difference) * progress ]) rotate([-$top_tilt / $key_height * progress,0,0]){ roundedRect([ - total_key_width() - thickness_difference - (($width_difference - extra_side_size) * progress * modifier), - total_key_height() - thickness_difference - (($height_difference - extra_side_size) * progress * modifier), + total_key_width() - thickness_difference - extra_width_difference, + total_key_height() - thickness_difference - extra_height_difference, .001 ],$corner_radius + extra_corner_size); } @@ -311,92 +335,3 @@ module example_key(){ } example_key(); -//minkowski_key(); - - - - - - - - - - - - - - - -// Experimental stuff, except not really anymore - -// corollary is roundedRect -// NOT 3D -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 = $corner_radius + thickness_difference/2; - - function unit(length) = 19.02 * (length) + (18.16 - 19.02); - - pointArray = [ - [ 0 + t, 0 + t], - [unit(1.5) - t, 0 + t], - [unit(1.5) - t, unit(1) - t], - [unit(1.25) - t, unit(1) - t], - [unit(1.25) - t, unit(2) - t], - [ 0 + t, unit(2) - t] - ]; - - offset(r=$corner_radius) { - polygon(points=pointArray, center=true); - } -} - -//corollary is shape_hull -module ISOEnterShapeHull(thickness_difference, depth_difference, modifier){ - // TODO move this somewhere - function unit(length) = 19.02 * (length) + (18.16 - 19.02); - - height = $total_depth - depth_difference; - width_scale = top_total_key_width() / total_key_width(); - height_scale = top_total_key_height() / total_key_height(); - - linear_extrude(height = height, scale = [width_scale, height_scale]) { - - // TODO completely making up these numbers here - // 0.86mm is from the unit function, 18.16 - 19.02. no idea what the 18 is, shows me for not leaving better comments - translate([unit(-.5), unit(-1) + 0.86]) fakeISOEnter(thickness_difference); - } -} - - -// old stuff - -// old non-sliced shape hull - -/*module oldshape_hull(thickness_difference, depth_difference, modifier){ - if ($ISOEnter) { - ISOEnterShapeHull(thickness_difference, depth_difference, modifier); - } else { - hull(){ - // $bottom_key_width + ($key_length -1) * unit is the correct length of the - // key. only 1u of the key should be $bottom_key_width long; all others - // should be 1u - roundedRect([total_key_width() - thickness_difference, total_key_height() - thickness_difference, .001],$corner_radius); - - //depth_difference outside of modifier because that doesnt make sense - translate([0,$top_skew,$total_depth * modifier - depth_difference]){ - rotate([-$top_tilt / $key_height,0,0]){ - roundedRect([ - total_key_width() - thickness_difference - $width_difference * modifier, - total_key_height() - thickness_difference - $height_difference * modifier, - .001 - ],$corner_radius); - } - } - } - } -}*/ diff --git a/keys.scad b/keys.scad index 74cf3ad..4183c72 100644 --- a/keys.scad +++ b/keys.scad @@ -418,4 +418,5 @@ module legend(text, inset=false) { children(); } -translate_u(0, 0) oem_row(1) cherry() key(); +translate_u(1.125, 0.5) fake_iso_enter() cherry() key(); +translate_u(0, 0) dcs_row(2) cherry() key(); diff --git a/libraries/scad-utils/LICENSE b/libraries/scad-utils/LICENSE new file mode 100644 index 0000000..4731cb9 --- /dev/null +++ b/libraries/scad-utils/LICENSE @@ -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. diff --git a/libraries/scad-utils/README.md b/libraries/scad-utils/README.md new file mode 100644 index 0000000..8fdd4e3 --- /dev/null +++ b/libraries/scad-utils/README.md @@ -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]]); + } + diff --git a/libraries/scad-utils/hull.scad b/libraries/scad-utils/hull.scad new file mode 100644 index 0000000..5e0302e --- /dev/null +++ b/libraries/scad-utils/hull.scad @@ -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); + +} diff --git a/libraries/scad-utils/linalg.scad b/libraries/scad-utils/linalg.scad new file mode 100644 index 0000000..959a38e --- /dev/null +++ b/libraries/scad-utils/linalg.scad @@ -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]) ]; diff --git a/libraries/scad-utils/lists.scad b/libraries/scad-utils/lists.scad new file mode 100644 index 0000000..0d8e2b4 --- /dev/null +++ b/libraries/scad-utils/lists.scad @@ -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]]; diff --git a/libraries/scad-utils/mirror.scad b/libraries/scad-utils/mirror.scad new file mode 100644 index 0000000..bb7bbc0 --- /dev/null +++ b/libraries/scad-utils/mirror.scad @@ -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(); + } +} diff --git a/libraries/scad-utils/morphology.scad b/libraries/scad-utils/morphology.scad new file mode 100644 index 0000000..96ff221 --- /dev/null +++ b/libraries/scad-utils/morphology.scad @@ -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 + +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(); + } + } + } +} diff --git a/libraries/scad-utils/se3.scad b/libraries/scad-utils/se3.scad new file mode 100644 index 0000000..48b59e6 --- /dev/null +++ b/libraries/scad-utils/se3.scad @@ -0,0 +1,60 @@ +use +use + +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); diff --git a/libraries/scad-utils/shapes.scad b/libraries/scad-utils/shapes.scad new file mode 100644 index 0000000..6d33d8a --- /dev/null +++ b/libraries/scad-utils/shapes.scad @@ -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 \ No newline at end of file diff --git a/libraries/scad-utils/so3.scad b/libraries/scad-utils/so3.scad new file mode 100644 index 0000000..83308aa --- /dev/null +++ b/libraries/scad-utils/so3.scad @@ -0,0 +1,82 @@ +// so3 + +use + +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); diff --git a/libraries/scad-utils/spline.scad b/libraries/scad-utils/spline.scad new file mode 100644 index 0000000..dbf487b --- /dev/null +++ b/libraries/scad-utils/spline.scad @@ -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 +use + +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=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); + + + diff --git a/libraries/scad-utils/trajectory.scad b/libraries/scad-utils/trajectory.scad new file mode 100644 index 0000000..1a0e737 --- /dev/null +++ b/libraries/scad-utils/trajectory.scad @@ -0,0 +1,43 @@ +use + +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)); diff --git a/libraries/scad-utils/trajectory_path.scad b/libraries/scad-utils/trajectory_path.scad new file mode 100644 index 0000000..f9fffe3 --- /dev/null +++ b/libraries/scad-utils/trajectory_path.scad @@ -0,0 +1,89 @@ +use +use + +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_])); + diff --git a/libraries/scad-utils/transformations.scad b/libraries/scad-utils/transformations.scad new file mode 100644 index 0000000..89edff6 --- /dev/null +++ b/libraries/scad-utils/transformations.scad @@ -0,0 +1,43 @@ +use +use +use + +/*! + 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) ]; diff --git a/libraries/skin.scad b/libraries/skin.scad new file mode 100644 index 0000000..f97d3c4 --- /dev/null +++ b/libraries/skin.scad @@ -0,0 +1,137 @@ +use +use + +// 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 +use +use +use + +module fakeISOEnter(thickness_difference = 0){ + // 1u is the space taken upy by a 1u keycap. + // unit is the space taken up by a unit space for a keycap. + // formula is 1u + unit *(length - 1) + + // t is all modifications to the polygon array + t = thickness_difference/2 - (19.02 - 18.16); + + function unit(length) = 19.02 * length; + + pointArray = [ + [19.05 * (-.5) + t, 19.05 * (-1) + t], + [19.05 * (0.5) - t, 19.05 * (-1) + t], + [19.05 * (0.5) - t, 19.05 * (1) - t], + [19.05 * (-0.75) + t, 19.05 * (1) - t], + [19.05 * (-0.75) + t, 19.05 * (0) + t], + [19.05 * (-0.5) + t, 19.05 * (0) + t] + ]; + + + /*translate([unit(-.5), unit(-1) + 0.86]){*/ + minkowski() { + circle($corner_radius, $fn=20); + offset(r=-$corner_radius * 2, $fn=20) polygon(points=pointArray); + } + /*}*/ +} + +function isoEnter() = [ + [19.05 * (-.5) + (19.02 - 18.16), 19.05 * (-1) + (19.02 - 18.16)], + [19.05 * (0.5) - (19.02 - 18.16), 19.05 * (-1) + (19.02 - 18.16)], + [19.05 * (0.5) - (19.02 - 18.16), 19.05 * (1) - (19.02 - 18.16)], + [19.05 * (-0.75) + (19.02 - 18.16), 19.05 * (1) - (19.02 - 18.16)], + [19.05 * (-0.75) + (19.02 - 18.16), 19.05 * (0) + (19.02 - 18.16)], + [19.05 * (-0.5) + (19.02 - 18.16), 19.05 * (0) + (19.02 - 18.16)] + ]; + + +path_definition = [ +trajectory(forward = 10, roll = 0), +]; + +// sweep +path = quantize_trajectories(path_definition, steps=100); + +// skin +myLen = len(path)-1; +trans = [ for (i=[0:len(path)-1]) transform(path[i], isoEnter()) ]; + +translate([0,10,0]) + skin(trans); diff --git a/util.scad b/util.scad index 7dc2a0e..8317f3b 100644 --- a/util.scad +++ b/util.scad @@ -1,7 +1,6 @@ $fs=.1; -//centered -// offset, who knew? +// centered module roundedRect(size, radius, center=true) { linear_extrude(height = size[2]){ roundedSquare([size[0], size[1]], radius, center=center); @@ -14,26 +13,36 @@ module roundedSquare(size, radius, center = true) { } } -module oldroundedRect(size, radius) { - x = size[0]; - y = size[1]; - z = size[2]; +// corollary is roundedRect +// NOT 3D +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) - translate([-x/2,-y/2,0]) - linear_extrude(height=z) - hull() { - translate([radius, radius, 0]) - circle(r=radius); + // t is all modifications to the polygon array + // t used to contain a corner radius adjustment, but due to + // the offset we need that expansion back + t = (thickness_difference - (19.05 - 18.16)); - translate([x - radius, radius, 0]) - circle(r=radius); + function unit(length) = 19.05 * length; - translate([x - radius, y - radius, 0]) - circle(r=radius); + pointArray = [ + [unit(-.625) + t, unit(-1) + t], + [unit(0.625) - t, unit(-1) + t], + [unit(0.625) - t, unit(1) - t], + [unit(-0.875) + t, unit(1) - t], + [unit(-0.875) + t, unit(0) + t], + [unit(-0.625) + t, unit(0) + t] + ]; - translate([radius, y - radius, 0]) - circle(r=radius); - } + minkowski(){ + circle(r=$corner_radius); + // gives us rounded inner corner + offset(r=-$corner_radius*2) { + polygon(points=pointArray); + } + } } module functional_scaled_extrude(height = 10, slices=[]) {