diff --git a/acme_screw.scad b/acme_screw.scad new file mode 100644 index 0000000..f1ed557 --- /dev/null +++ b/acme_screw.scad @@ -0,0 +1,118 @@ +////////////////////////////////////////////////////////////////////// +// Square-threaded (ACME) Screw Rods and Nuts +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +include +include + + +// Constructs an acme threaded screw rod. This method makes much +// smoother threads than the naive linear_extrude method. +module acme_threaded_rod( + d=10.5, + l=100, + pitch=3.175, + thread_depth=1, + thread_angle=14.5 +) { + astep = 360/segs(d/2); + asteps = ceil(360/astep); + threads = ceil(l/pitch)+2; + pa_delta = min(pitch/4.1,(thread_depth+0.05)*tan(thread_angle)/2); + poly_points = [ + for ( + thread = [0 : threads-1], + astep = [0 : asteps-1], + i = [0 : 3] + ) let ( + r = max(0, d/2 - ((i==1||i==2)? 0 : (thread_depth+0.05))), + a = astep / asteps, + rx = r * cos(360 * a), + ry = r * sin(360 * a), + tz = (thread + a - threads/2 + (i<2? -0.25 : 0.25)) * pitch + (i%2==0? -pa_delta : pa_delta) + ) [rx, ry, tz] + ]; + point_count = len(poly_points); + poly_faces = concat( + [ + for ( + thread = [0 : threads-1], + astep = [0 : asteps-1], + j = [0 : 3], + i = [0 : 1] + ) let( + p0 = (thread*asteps + astep)*4 + j, + p1 = p0 + 4, + p2 = (thread*asteps + astep)*4 + ((j+1)%4), + p3 = p2 + 4, + tri = (i==0? [p0, p3, p1] : [p0, p2, p3]) + ) + if (p0 < point_count-4) tri + ], + [ + [0, 3, 2], + [0, 2, 1], + [point_count-4, point_count-3, point_count-2], + [point_count-4, point_count-2, point_count-1] + ] + ); + intersection() { + union() { + polyhedron(points=poly_points, faces=poly_faces, convexity=10); + cylinder(h=(threads+0.5)*pitch, d=d-2*thread_depth, center=true, $fn=asteps); + } + cube([d+1, d+1, l], center=true); + } +} +//!acme_threaded_rod(d=3/8*25.4, l=20, pitch=1/8*25.4, thread_depth=1.3, thread_angle=29, $fn=32); +//!acme_threaded_rod(d=60, l=16, pitch=8, thread_depth=3, thread_angle=45, $fa=2, $fs=2); + + +module acme_threaded_nut( + od=17.4, + id=10.5, + h=10, + pitch=3.175, + thread_depth=1, + slop=printer_slop +) { + difference() { + cylinder(r=od/2/cos(30), h=h, center=true, $fn=6); + zspread(slop, n=slop>0?2:1) { + acme_threaded_rod(d=id+2*slop, l=h+1, pitch=pitch, thread_depth=thread_depth); + } + } +} + +!acme_threaded_nut(od=17.4, id=10.5, h=10, pitch=3.175, thread_depth=1, slop=printer_slop); + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/beziers.scad b/beziers.scad new file mode 100644 index 0000000..0aca4b9 --- /dev/null +++ b/beziers.scad @@ -0,0 +1,230 @@ +////////////////////////////////////////////////////////////////////// +// Bezier functions and modules. +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +include +include + + +// Formulae to calculate points on a cubic bezier curve. +function bez_B0(curve,u) = curve[0]*pow((1-u),3); +function bez_B1(curve,u) = curve[1]*(3*u*pow((1-u),2)); +function bez_B2(curve,u) = curve[2]*(3*pow(u,2)*(1-u)); +function bez_B3(curve,u) = curve[3]*pow(u,3); +function bez_point(curve,u) = bez_B0(curve,u) + bez_B1(curve,u) + bez_B2(curve,u) + bez_B3(curve,u); + +function bezier_polyline(bezier, splinesteps=16) = concat( + [ + for ( + b = [0 : 3 : len(bezier)-4], + l = [0 : splinesteps-1] + ) let ( + crv = [bezier[b+0], bezier[b+1], bezier[b+2], bezier[b+3]], + u = l / splinesteps + ) bez_point(crv, u) + ], + [bez_point([bezier[len(bezier)-4], bezier[len(bezier)-3], bezier[len(bezier)-2], bezier[len(bezier)-1]], 1.0)] +); + + +// Takes a closed 2D bezier path, and creates a 2D polygon from it. +module bezier_polygon(bezier, splinesteps=16) { + polypoints=bezier_polyline(bezier, splinesteps); + polygon(points=slice(polypoints, 0, -1)); +} + + +// Generate bezier curve to fillet 2 line segments between 3 points. +// Returns two path points with surrounding cubic bezier control points. +function fillet3pts(p0, p1, p2, r) = let( + v0 = normalize(p0-p1), + v1 = normalize(p2-p1), + a = vector3d_angle(v0,v1), + mr = min(distance(p0,p1), distance(p2,p1))*0.9, + tr = min(r/tan(a/2), mr), + tp0 = p1+v0*tr, + tp1 = p1+v1*tr, + w=-2.7e-5*a*a + 8.5e-3*a - 3e-3, + nw=max(0, w), + cp0 = tp0+nw*(p1-tp0), + cp1 = tp1+nw*(p1-tp1) + ) [tp0, tp0, cp0, cp1, tp1, tp1]; + + +// Takes a 3D polyline path and fillets it into a 3d cubic bezier path. +function fillet_path(pts, fillet) = concat( + [pts[0], pts[0]], + (len(pts) < 3)? [] : [ + for ( + p = [1 : len(pts)-2], + pt = fillet3pts(pts[p-1], pts[p], pts[p+1], fillet) + ) pt + ], + [pts[len(pts)-1], pts[len(pts)-1]] +); + + +// Takes a closed 2D bezier and rotates it around the X axis, forming a solid. +// bezier = array of points for the bezier path to rotate. +// splinesteps = number of segments to divide each bezier segment into. +// Example: +// path = [ +// [ 0, 10], [ 50, 0], [ 50, 40], +// [ 95, 40], [100, 40], [100, 45], +// [ 95, 45], [ 66, 45], [ 0, 20], +// [ 0, 12], [ 0, 12], [ 0, 10], +// [ 0, 10] +// ]; +// revolve_bezier(path, splinesteps=32, $fn=180); +module revolve_bezier(bezier, splinesteps=16) { + yrot(90) rotate_extrude(convexity=10) { + xrot(180) zrot(-90) bezier_polygon(bezier, splinesteps); + } +} + + +// Takes a bezier path and closes it to the X axis. +function bezier_close_to_axis(bezier) = + let(bezend = len(bezier)-1) + concat( + [ [bezier[0][0], 0], [bezier[0][0], 0], bezier[0] ], + bezier, + [ bezier[bezend], [bezier[bezend][0], 0], [bezier[bezend][0], 0] ] + ); + + +// Takes a bezier curve and closes it with a matching path that is +// lowered by a given amount towards the X axis. +function bezier_offset(inset, bezier) = + let(backbez = reverse([ for (pt = bezier) [pt[0], pt[1]-inset] ])) + concat( + bezier, + [bezier[len(bezier)-1]], + [backbez[0]], + backbez, + [backbez[len(backbez)-1]], + [bezier[0]], + [bezier[0]] + ); + + +// Takes a 2D bezier and rotates it around the X axis, forming a solid. +// bezier = array of points for the bezier path to rotate. +// splinesteps = number of segments to divide each bezier segment into. +// Example: +// path = [ [0, 10], [33, 10], [66, 40], [100, 40] ]; +// revolve_bezier_solid_to_axis(path, splinesteps=32, $fn=72); +module revolve_bezier_solid_to_axis(bezier, splinesteps=16) { + revolve_bezier(bezier=bezier_close_to_axis(bezier), splinesteps=splinesteps); +} + + +// Takes a 2D bezier and rotates it around the X axis, into a hollow shell. +// bezier = array of points for the bezier path to rotate. +// offset = the thickness of the created shell. +// splinesteps = number of segments to divide each bezier segment into. +// Example: +// path = [ [0, 10], [33, 10], [66, 40], [100, 40] ]; +// revolve_bezier_offset_shell(path, offset=1, splinesteps=32, $fn=72); +module revolve_bezier_offset_shell(bezier, offset=1, splinesteps=16) { + revolve_bezier(bezier=bezier_offset(offset, bezier), splinesteps=splinesteps); +} + + +// Extrudes 2D children along a bezier path. +// bezier = array of points for the bezier path to extrude along. +// splinesteps = number of segments to divide each bezier segment into. +// Example: +// path = [ [0, 0, 0], [33, 33, 33], [66, -33, -33], [100, 0, 0] ]; +// extrude_2d_shapes_along_bezier(path, splinesteps=32) +// circle(r=10, center=true); +module extrude_2d_shapes_along_bezier(bezier, splinesteps=16) { + pointslist = slice(bezier_polyline(bezier, splinesteps), 0, -1); + ptcount = len(pointslist); + for (i = [0 : ptcount-2]) { + pt1 = pointslist[i]; + pt2 = pointslist[i+1]; + pt0 = i==0? pt1 : pointslist[i-1]; + pt3 = (i>=ptcount-2)? pt2 : pointslist[i+2]; + dist = distance(pt1,pt2); + v1 = pt2-pt1; + v0 = (i==0)? v1 : (pt1-pt0); + v2 = (i==ptcount-2)? v1 : (pt3-pt2); + az1 = atan2(v1[1], v1[0]); + alt1 = (len(pt1)<3)? 0 : atan2(v1[2], hypot(v1[1], v1[0])); + az0 = atan2(v0[1], v0[0]); + alt0 = (len(pt0)<3)? 0 : atan2(v0[2], hypot(v0[1], v0[0])); + az2 = atan2(v2[1], v2[0]); + alt2 = (len(pt2)<3)? 0 : atan2(v2[2], hypot(v2[1], v2[0])); + translate(pt1) { + difference() { + rotate([0, 90-alt1, az1]) { + translate([0, 0, -1]) { + linear_extrude(height=dist*3, convexity=10) { + children(); + } + } + } + rotate([0, 90-(alt0+alt1)/2, (az0+az1)/2]) { + translate([0, 0, -dist-0.05]) { + cube(size=[99,99,dist*2], center=true); + } + } + rotate([0, 90-(alt1+alt2)/2, (az1+az2)/2]) { + translate([0, 0, dist+dist]) { + cube(size=[99,99,dist*2], center=true); + } + } + } + } + } +} + + +// Takes a closed 2D bezier path, centered on the XY plane, and +// extrudes it perpendicularly along a 3D bezier path, forming a solid. +// bezier = Array of points of a bezier path, to be extruded. +// path = Array of points of a bezier path, to extrude along. +// pathsteps = number of steps to divide each path segment into. +// bezsteps = number of steps to divide each bezier segment into. +// Example: +// bez = [ [-15, 0], [25, -15], [-5, 10], [0, 10], [5, 10], [10, 5], [15, 0], [10, -5], [5, -10], [0, -10], [-5, -10], [-10, -5], [-15, 0] ]; +// path = [ [0, 0, 0], [33, 33, 33], [66, -33, -33], [100, 0, 0] ]; +// extrude_bezier_along_bezier(bez, path, pathsteps=64, bezsteps=32); +module extrude_bezier_along_bezier(bezier, path, pathsteps=16, bezsteps=16) { + bez_points = simplify2d_path(bezier_polyline(bezier, bezsteps)); + path_points = simplify3d_path(path3d(bezier_polyline(path, pathsteps))); + extrude_2dpath_along_3dpath(bez_points, path_points); +} + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/involute_gears.scad b/involute_gears.scad new file mode 100644 index 0000000..d1682cf --- /dev/null +++ b/involute_gears.scad @@ -0,0 +1,182 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// Public Domain Parametric Involute Spur Gear (and involute helical gear and involute rack) +// version 1.1 +// by Leemon Baird, 2011, Leemon@Leemon.com +// Corrected and tweaked by Revar Desmera, 2017, revarbat@gmail.com +//http://www.thingiverse.com/thing:5505 +// +// This file is public domain. Use it for any purpose, including commercial +// applications. Attribution would be nice, but is not required. There is +// no warranty of any kind, including its correctness, usefulness, or safety. +// +// This is parameterized involute spur (or helical) gear. It is much simpler and less powerful than +// others on Thingiverse. But it is public domain. I implemented it from scratch from the +// descriptions and equations on Wikipedia and the web, using Mathematica for calculations and testing, +// and I now release it into the public domain. +// +// http://en.wikipedia.org/wiki/Involute_gear +// http://en.wikipedia.org/wiki/Gear +// http://en.wikipedia.org/wiki/List_of_gear_nomenclature +// http://gtrebaol.free.fr/doc/catia/spur_gear.html +// http://www.cs.cmu.edu/~rapidproto/mechanisms/chpt7.html +// +// The module gear() gives an involute spur gear, with reasonable defaults for all the parameters. +// Normally, you should just choose the first 4 parameters, and let the rest be default values. +// The module gear() gives a gear in the XY plane, centered on the origin, with one tooth centered on +// the positive Y axis. The various functions below it take the same parameters, and return various +// measurements for the gear. The most important is pitch_radius, which tells how far apart to space +// gears that are meshing, and adendum_radius, which gives the size of the region filled by the gear. +// A gear has a "pitch circle", which is an invisible circle that cuts through the middle of each +// tooth (though not the exact center). In order for two gears to mesh, their pitch circles should +// just touch. So the distance between their centers should be pitch_radius() for one, plus pitch_radius() +// for the other, which gives the radii of their pitch circles. +// +// In order for two gears to mesh, they must have the same mm_per_tooth and pressure_angle parameters. +// mm_per_tooth gives the number of millimeters of arc around the pitch circle covered by one tooth and one +// space between teeth. The pitch angle controls how flat or bulged the sides of the teeth are. Common +// values include 14.5 degrees and 20 degrees, and occasionally 25. Though I've seen 28 recommended for +// plastic gears. Larger numbers bulge out more, giving stronger teeth, so 28 degrees is the default here. +// +// The ratio of number_of_teeth for two meshing gears gives how many times one will make a full +// revolution when the the other makes one full revolution. If the two numbers are coprime (i.e. +// are not both divisible by the same number greater than 1), then every tooth on one gear +// will meet every tooth on the other, for more even wear. So coprime numbers of teeth are good. +// +// The module rack() gives a rack, which is a bar with teeth. A rack can mesh with any +// gear that has the same mm_per_tooth and pressure_angle. +// +// Some terminology: +// The outline of a gear is a smooth circle (the "pitch circle") which has mountains and valleys +// added so it is toothed. So there is an inner circle (the "root circle") that touches the +// base of all the teeth, an outer circle that touches the tips of all the teeth, +// and the invisible pitch circle in between them. There is also a "base circle", which can be smaller than +// all three of the others, which controls the shape of the teeth. The side of each tooth lies on the path +// that the end of a string would follow if it were wrapped tightly around the base circle, then slowly unwound. +// That shape is an "involute", which gives this type of gear its name. +// +////////////////////////////////////////////////////////////////////////////////////////////// + +pi = 3.141592653589793236; + + +//An involute spur gear, with reasonable defaults for all the parameters. +//Normally, you should just choose the first 4 parameters, and let the rest be default values. +//Meshing gears must match in mm_per_tooth, pressure_angle, and twist, +//and be separated by the sum of their pitch radii, which can be found with pitch_radius(). +module gear ( + mm_per_tooth = 3, //this is the "circular pitch", the circumference of the pitch circle divided by the number of teeth + number_of_teeth = 11, //total number of teeth around the entire perimeter + thickness = 6, //thickness of gear in mm + hole_diameter = 3, //diameter of the hole in the center, in mm + twist = 0, //teeth rotate this many degrees from bottom of gear to top. 360 makes the gear a screw with each thread going around once + teeth_to_hide = 0, //number of teeth to delete to make this only a fraction of a circle + pressure_angle = 28, //Controls how straight or bulged the tooth sides are. In degrees. + clearance = 0.0, //gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) + backlash = 0.0 //gap between two meshing teeth, in the direction along the circumference of the pitch circle +) { + p = mm_per_tooth * number_of_teeth / pi / 2; //radius of pitch circle + c = p + mm_per_tooth / pi - clearance; //radius of outer circle + b = p*cos(pressure_angle); //radius of base circle + r = p-(c-p)-clearance; //radius of root circle + t = mm_per_tooth/2-backlash/2; //tooth thickness at pitch circle + k = -iang(b, p) - t/2/p/pi*180; //angle to where involute meets base circle on each side of tooth + difference() { + linear_extrude(height = thickness, center = true, convexity = 10, twist = twist, slices = ceil(abs(twist)/5)+1) + for (i = [0:number_of_teeth-teeth_to_hide-1] ) + rotate([0,0,i*360/number_of_teeth]) + polygon( + points=[ + polar(hole_diameter/10, -181/number_of_teeth), + polar(r, -181/number_of_teeth), + polar(r, r + + +module half_joiner_clear(h=20, w=10, a=30, clearance=0) +{ + dmnd_height = h*1.0; + dmnd_width = dmnd_height*tan(a); + guide_size = w/3; + guide_width = 2*(dmnd_height/2-guide_size)*tan(a); + + difference() { + // Diamonds. + scale([w+clearance, dmnd_width/2, dmnd_height/2]) { + xrot(45) cube(size=[1,sqrt(2),sqrt(2)], center=true); + } + // Blunt point of tab. + grid_of(ya=[-(guide_width/2+2), (guide_width/2+2)]) { + cube(size=[(w+clearance)*1.05, 4, h*0.99], center=true); + } + } +} +//half_joiner_clear(); + + + +module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, slop=printer_slop) +{ + dmnd_height = h*1.0; + dmnd_width = dmnd_height*tan(a); + guide_size = w/3; + guide_width = 2*(dmnd_height/2-guide_size)*tan(a); + + difference() { + union() { + // Make base. + difference() { + // Solid backing base. + translate([0,-l/2,0]) + cube(size=[w, l, h], center=true); + + // Clear diamond for tab + grid_of(xa=[-(w*2/3), (w*2/3)]) { + half_joiner_clear(h=h+0.01, w=w, clearance=slop*2, a=a); + } + } + + difference() { + // Make tab + scale([w/3-slop*2, dmnd_width/2, dmnd_height/2]) xrot(45) + cube(size=[1,sqrt(2),sqrt(2)], center=true); + + // Blunt point of tab. + translate([0,guide_width/2+2,0]) + cube(size=[w*0.99,4,guide_size*2], center=true); + } + + + // Guide ridges. + if (guides == true) { + xspread(w/3-slop*2) { + // Guide ridge. + fwd(0.05/2) { + scale([0.75, 1, 2]) yrot(45) + cube(size=[guide_size/sqrt(2), guide_width+0.05, guide_size/sqrt(2)], center=true); + } + + // Snap ridge. + scale([0.25, 0.5, 1]) zrot(45) + cube(size=[guide_size/sqrt(2), guide_size/sqrt(2), dmnd_width], center=true); + } + } + } + + // Make screwholes, if needed. + if (screwsize != undef) { + yrot(90) cylinder(r=screwsize*1.1/2, h=w+1, center=true, $fn=12); + } + } +} +//half_joiner(screwsize=3); + + + +module half_joiner2(h=20, w=10, l=10, a=30, screwsize=undef, guides=true) +{ + difference() { + union () { + translate([0,-l/2,0]) + cube(size=[w, l, h], center=true); + half_joiner_clear(h=h, w=w, a=a); + } + + // Subtract mated half_joiner. + zrot(180) half_joiner(h=h+0.05, w=w+0.05, l=l+0.05, a=a, screwsize=undef, guides=guides, slop=0.0); + + // Make screwholes, if needed. + if (screwsize != undef) { + yrot(90) cylinder(r=screwsize*1.1/2, h=w+1, center=true, $fn=12); + } + } +} +//half_joiner2(screwsize=3); + + + +module joiner(h=40, w=10, l=10, a=30, screwsize=undef, guides=true, slop=printer_slop) +{ + union() { + translate([0,0,h/4]) + half_joiner(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); + translate([0,0,-h/4]) + half_joiner2(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides); + } +} +//joiner(screwsize=3); + + + +module joiner_clear(h=40, w=10, a=30, clearance=0) +{ + grid_of(za=[-h/4,h/4]) { + half_joiner_clear(h=h/2.0, w=w, a=a, clearance=clearance); + } +} +//joiner_clear(); + + + +module joiner_pair(spacing=100, h=40, w=10, l=10, a=30, screwsize=undef, guides=true) +{ + yrot_copies([0,180]) { + translate([spacing/2, 0, 0]) { + joiner(h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides); + } + } +} +//joiner_pair(spacing=100, h=40, w=10, l=10, a=30, screwsize=3, guides=true); + + + +module joiner_pair_clear(spacing=100, h=40, w=10, a=30, clearance=0) +{ + yrot_copies([0,180]) { + translate([spacing/2, 0, 0]) { + joiner_clear(h=h, w=w, a=a, clearance=clearance); + } + } +} +//joiner_pair_clear(spacing=100, h=40, w=10, a=30); + + + +module joiner_quad(xspacing=100, yspacing=50, h=40, w=10, l=10, a=30, screwsize=undef, guides=true) +{ + zrot_copies([0,180]) { + translate([0, yspacing/2, 0]) { + joiner_pair(spacing=xspacing, h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides); + } + } +} +//joiner_quad(xspacing=100, yspacing=50, h=40, w=10, l=10, a=30, screwsize=3, guides=true); + + + +module joiner_quad_clear(xspacing=100, yspacing=50, h=40, w=10, a=30, clearance=0) +{ + zrot_copies([0,180]) { + translate([0, yspacing/2, 0]) { + joiner_pair_clear(spacing=xspacing, h=h, w=w, a=a, clearance=clearance); + } + } +} +//joiner_quad_clear(xspacing=100, yspacing=50, h=40, w=10, a=30); + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap + diff --git a/linear_bearings.scad b/linear_bearings.scad new file mode 100644 index 0000000..53f40ae --- /dev/null +++ b/linear_bearings.scad @@ -0,0 +1,128 @@ +////////////////////////////////////////////////////////////////////// +// Linear Bearings. +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +include +include + + +function get_lmXuu_bearing_diam(size) = lookup(size, [ + [ 4.0, 8.0], + [ 5.0, 10.0], + [ 6.0, 12.0], + [ 8.0, 15.0], + [ 10.0, 19.0], + [ 12.0, 21.0], + [ 13.0, 23.0], + [ 16.0, 28.0], + [ 20.0, 32.0], + [ 25.0, 40.0], + [ 30.0, 45.0], + [ 35.0, 52.0], + [ 40.0, 60.0], + [ 50.0, 80.0], + [ 60.0, 90.0], + [ 80.0, 120.0], + [100.0, 150.0] + ]); + + +function get_lmXuu_bearing_length(size) = lookup(size, [ + [ 4.0, 12.0], + [ 5.0, 15.0], + [ 6.0, 19.0], + [ 8.0, 24.0], + [ 10.0, 29.0], + [ 12.0, 30.0], + [ 13.0, 32.0], + [ 16.0, 37.0], + [ 20.0, 42.0], + [ 25.0, 59.0], + [ 30.0, 64.0], + [ 35.0, 70.0], + [ 40.0, 80.0], + [ 50.0, 100.0], + [ 60.0, 110.0], + [ 80.0, 140.0], + [100.0, 175.0] + ]); + + +// Creates a model of a clamp to hold a given linear bearing cartridge. +// d = Diameter of linear bearing. (Default: 15) +// l = Length of linear bearing. (Default: 24) +// tab = Clamp tab height. (Default: 7) +// tabwall = Clamp Tab thickness. (Default: 5) +// wall = Wall thickness of clamp housing. (Default: 3) +// gap = Gap in clamp. (Default: 5) +// screwsize = Size of screw to use to tighten clamp. (Default: 3) +module linear_bearing_housing(d=15,l=24,tab=7,gap=5,wall=3,tabwall=5,screwsize=3) +{ + od = d+2*wall; + ogap = gap+2*tabwall; + tabh = tab/2+od/2*sqrt(2)-ogap/2; + translate([0,0,od/2]) difference() { + union() { + rotate([0,0,90]) + teardrop(r=od/2,h=l); + translate([0,0,tabh]) + cube(size=[l,ogap,tab+0.05], center=true); + translate([0,0,-od/4]) + cube(size=[l,od,od/2], center=true); + } + rotate([0,0,90]) + teardrop(r=d/2,h=l+0.05); + translate([0,0,(d*sqrt(2)+tab)/2]) + cube(size=[l+0.05,gap,d+tab], center=true); + translate([0,0,tabh]) { + translate([0,-ogap/2+2-0.05,0]) + rotate([90,0,0]) + screw(screwsize=screwsize*1.06, screwlen=ogap, headsize=screwsize*2, headlen=10); + translate([0,ogap/2+0.05,0]) + rotate([90,0,0]) + metric_nut(size=screwsize,hole=false); + } + } +} + + +module lmXuu_housing(size=8,tab=7,gap=5,wall=3,tabwall=5,screwsize=3) +{ + d = get_lmXuu_bearing_diam(size); + l = get_lmXuu_bearing_length(size); + linear_bearing_housing(d=d,l=l,tab=tab,gap=gap,wall=wall,tabwall=tabwall,screwsize=screwsize); +} +lmXuu_housing(size=8); +//lmXuu_housing(size=10); + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/masks.scad b/masks.scad new file mode 100644 index 0000000..87d5288 --- /dev/null +++ b/masks.scad @@ -0,0 +1,364 @@ +////////////////////////////////////////////////////////////////////// +// Masking shapes. +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +include + + +module angle_half_pie_mask( + ang=45, h=1, + r=undef, r1=undef, r2=undef, + d=1.0, d1=undef, d2=undef, +) { + r = (r != undef)? r : (d/2); + r1 = (r1 != undef)? r1 : ((d1 != undef)? (d1/2) : r); + r2 = (r2 != undef)? r2 : ((d2 != undef)? (d2/2) : r); + rm = max(r1,r2); + difference() { + cylinder(h=h, r1=r1, r2=r2, center=true); + translate([0, -rm/2, 0]) + cube(size=[rm*2+1, rm, h+1], center=true); + zrot(ang) { + translate([0, rm/2, 0]) { + cube(size=[rm*2.1, rm, h+1], center=true); + } + } + } +} + + +// Creates a pie wedge shape that can be used to mask other shapes. +// You must specify either r or d, or their r1/r2, d1/d2 variants. +// ang = angle of wedge in degrees. +// h = height of wedge. +// r = Radius of circle wedge is created from. (optional) +// r1 = Bottom radius of cone that wedge is created from. (optional) +// r2 = Upper radius of cone that wedge is created from. (optional) +// d = Diameter of circle wedge is created from. (optional) +// d1 = Bottom diameter of cone that wedge is created from. (optional) +// d2 = Upper diameter of cone that wedge is created from. (optional) +// Example: +// angle_pie_mask(ang=30, d=100, h=20); +module angle_pie_mask( + ang=45, h=1, + r=undef, r1=undef, r2=undef, + d=1.0, d1=undef, d2=undef, +) { + a1 = min(ang, 180.0); + a2 = max(0.0, ang-180.0); + r = (r != undef)? r : (d/2); + r1 = (r1 != undef)? r1 : ((d1 != undef)? (d1/2) : r); + r2 = (r2 != undef)? r2 : ((d2 != undef)? (d2/2) : r); + union() { + angle_half_pie_mask(h=h, r1=r1, r2=r2, ang=a1); + if (a2 > 0.0) { + zrot(180) angle_half_pie_mask(h=h, r1=r1, r2=r2, ang=a2); + } + } +} + + +// Creates a shape that can be used to chamfer a 90 degree edge along the Z axis. +// Difference it from the object to be chamfered. The center of the mask +// object should align exactly with the edge to be chamfered. +// l = Height of mask +// chamfer = size of chamfer +// Example: +// chamfer_mask_z(l=10.0, chamfer=2.0); +module chamfer_mask_z(l=1.0, chamfer=1.0) { + zrot(45) cube(size=[chamfer*sqrt(2.0), chamfer*sqrt(2.0), l], center=true); +} + + +// Creates a shape that can be used to chamfer a 90 degree edge along the Y axis. +// Difference it from the object to be chamfered. The center of the mask +// object should align exactly with the edge to be chamfered. +// l = Height of mask +// chamfer = size of chamfer +// Example: +// chamfer_mask_y(l=10.0, chamfer=2.0); +module chamfer_mask_y(l=1.0, chamfer=1.0) {xrot(90) chamfer_mask(h=l, r=chamfer);} + + +// Creates a shape that can be used to chamfer a 90 degree edge along the X axis. +// Difference it from the object to be chamfered. The center of the mask +// object should align exactly with the edge to be chamfered. +// l = Height of mask +// chamfer = size of chamfer +// Example: +// chamfer_mask_x(l=10.0, chamfer=2.0); +module chamfer_mask_x(l=1.0, chamfer=1.0) {yrot(90) chamfer_mask(h=l, r=chamfer);} + + +// Chamfers the edges of a cuboid region containing the given children. +// chamfer = inset of the chamfer from the edge. (Default: 1) +// size = The size of the rectangular cuboid we want to chamfer. +// edges = which edges do we want to chamfer. +// [ +// [Y+Z+, Y-Z+, Y-Z-, Y+Z-], +// [X+Z+, X-Z+, X-Z-, X+Z-], +// [X+Y+, X-Y+, X-Y-, X+Y-] +// ] +// Example: +// chamfer(chamfer=2, size=[10,40,90], edges=[[0,0,0,0], [1,1,0,0], [0,0,0,0]]) { +// cube(size=[10,40,90], center=true); +// } +module chamfer(chamfer=1, size=[1,1,1], edges=[[0,0,0,0], [1,1,0,0], [0,0,0,0]]) +{ + eps = 0.1; + x = size[0]; + y = size[1]; + z = size[2]; + lx = x + eps; + ly = y + eps; + lz = z + eps; + difference() { + union() { + children(); + } + union() { + if (edges[0][0] != 0) + up(z/2) back(y/2) chamfer_mask_x(l=lx, chamfer=chamfer); + if (edges[0][1] != 0) + up(z/2) fwd(y/2) chamfer_mask_x(l=lx, chamfer=chamfer); + if (edges[0][2] != 0) + down(z/2) back(y/2) chamfer_mask_x(l=lx, chamfer=chamfer); + if (edges[0][3] != 0) + down(z/2) fwd(y/2) chamfer_mask_x(l=lx, chamfer=chamfer); + + if (edges[1][0] != 0) + up(z/2) right(x/2) chamfer_mask_y(l=ly, chamfer=chamfer); + if (edges[1][1] != 0) + up(z/2) left(x/2) chamfer_mask_y(l=ly, chamfer=chamfer); + if (edges[1][2] != 0) + down(z/2) right(x/2) chamfer_mask_y(l=ly, chamfer=chamfer); + if (edges[1][3] != 0) + down(z/2) left(x/2) chamfer_mask_y(l=ly, chamfer=chamfer); + + if (edges[2][0] != 0) + back(y/2) right(x/2) chamfer_mask_z(l=lz, chamfer=chamfer); + if (edges[2][1] != 0) + back(y/2) left(x/2) chamfer_mask_z(l=lz, chamfer=chamfer); + if (edges[2][2] != 0) + fwd(y/2) right(x/2) chamfer_mask_z(l=lz, chamfer=chamfer); + if (edges[2][3] != 0) + fwd(y/2) left(x/2) chamfer_mask_z(l=lz, chamfer=chamfer); + } + } +} + + +// Creates a shape that can be used to fillet a vertical 90 degree edge. +// Difference it from the object to be filletted. The center of the mask +// object should align exactly with the edge to be filletted. +// h = height of vertical mask. +// r = radius of the fillet. +// center = If true, vertically center mask. +// Example: +// difference() { +// cube(size=100, center=false); +// up(50) fillet_mask(h=100.1, r=10.0); +// } +module fillet_mask(h=1.0, r=1.0, center=true) +{ + n = ceil(segs(r)/4)*4; + linear_extrude(height=h, convexity=4, center=center) { + polygon( + points=concat( + [for (a = [ 0:360/n: 90]) [r*cos(a)-r, r*sin(a)-r]], + [for (a = [270:360/n:360]) [r*cos(a)-r, r*sin(a)+r]], + [for (a = [180:360/n:270]) [r*cos(a)+r, r*sin(a)+r]], + [for (a = [ 90:360/n:180]) [r*cos(a)+r, r*sin(a)-r]] + ) + ); + } +} + + +// Creates a vertical mask that can be used to fillet the edge where two +// face meet, at any arbitrary angle. Difference it from the object to +// be filletted. The center of the mask should align exactly with the +// edge to be filletted. +// h = height of vertical mask. +// r = radius of the fillet. +// ang = angle that the planes meet at. +// center = If true, vertically center mask. +// Example: +// fillet_planes_joint_mask(h=50.0, r=10.0, ang=120, $fn=32); +module fillet_planes_joint_mask(h=1.0, r=1.0, ang=90, center=true) +{ + sweep = 180-ang; + n = ceil(segs(r)*sweep/360); + x = r*sin(90-(ang/2))/sin(ang/2); + linear_extrude(height=h, convexity=4, center=center) { + polygon( + points=concat( + [for (i = [0:n]) let (a=90+ang+i*sweep/n) [r*cos(a)+x, r*sin(a)+r]], + [for (i = [0:n]) let (a=90+i*sweep/n) [r*cos(a)+x, r*sin(a)-r]], + [ + [min(-1, r*cos(270-ang)+x-1), r*sin(270-ang)-r], + [min(-1, r*cos(90+ang)+x-1), r*sin(90+ang)+r], + ] + ) + ); + } +} + + +// Creates a shape that can be used to fillet the corner of an angle. +// Difference it from the object to be filletted. The center of the mask +// object should align exactly with the point of the corner to be filletted. +// fillet = radius of the fillet. +// ang = angle between planes that you need to fillet the corner of. +// Example: +// fillet_edge_joint_mask(fillet=100, ang=90); +module fillet_edge_joint_mask(fillet=1.0, ang=90) +{ + dy = fillet * tan(ang/2); + th = max(dy, fillet*2); + difference() { + down(dy) { + up(th/2) { + forward(fillet) { + cube(size=[fillet*2, fillet*4, th], center=true); + } + } + } + down(dy) { + forward(fillet) { + grid_of(count=2, spacing=fillet*2) { + sphere(r=fillet); + } + xrot(ang) { + up(fillet*2) { + cube(size=[fillet*8, fillet*8, fillet*4], center=true); + } + } + } + } + } +} + + +// Creates a shape that you can use to round 90 degree corners on a fillet. +// Difference it from the object to be filletted. The center of the mask +// object should align exactly with the corner to be filletted. +// r = radius of corner fillet. +// Example: +// $fa=1; $fs=1; +// difference() { +// cube(size=[6,10,16], center=true); +// translate([0, 5, 8]) yrot(90) fillet_mask(h=7, r=3); +// translate([3, 0, 8]) xrot(90) fillet_mask(h=11, r=3); +// translate([3, 5, 0]) fillet_mask(h=17, r=3); +// translate([3, 5, 8]) corner_fillet_mask(r=3); +// } +module corner_fillet_mask(r=1.0) +{ + difference() { + cube(size=r*2, center=true); + grid_of(count=[2,2,2], spacing=r*2-0.05) { + sphere(r=r, center=true); + } + } +} +//!corner_fillet_mask(r=10.0); + + +// Create a mask that can be used to round the end of a cylinder. +// Difference it from the cylinder to be filletted. The center of the +// mask object should align exactly with the center of the end of the +// cylinder to be filletted. +// r = radius of cylinder to fillet. (Default: 1.0) +// fillet = radius of the edge filleting. (Default: 0.25) +// xtilt = angle of tilt of end of cylinder in the X direction. (Default: 0) +// ytilt = angle of tilt of end of cylinder in the Y direction. (Default: 0) +// Example: +// $fa=2; $fs=2; +// difference() { +// cylinder(r=50, h=100, center=true); +// translate([0, 0, 50]) +// fillet_cylinder_mask(r=50, fillet=10, xtilt=30, ytilt=30); +// } +module fillet_cylinder_mask(r=1.0, fillet=0.25, xtilt=0, ytilt=0) +{ + dhx = 2*r*sin(xtilt); + dhy = 2*r*sin(ytilt); + dh = hypot(dhy, dhx); + down(dh/2) { + skew_xz(zang=xtilt) { + skew_yz(zang=ytilt) { + down(fillet) { + difference() { + up((dh+2*fillet)/2) { + cube(size=[r*2+10, r*2+10, dh+2*fillet], center=true); + } + torus(or=r, ir=r-2*fillet); + cylinder(r=r-fillet, h=2*fillet, center=true); + } + } + } + } + } +} + + + +// Create a mask that can be used to round the edge of a circular hole. +// Difference it from the hole to be filletted. The center of the +// mask object should align exactly with the center of the end of the +// hole to be filletted. +// r = radius of hole to fillet. (Default: 1.0) +// fillet = radius of the edge filleting. (Default: 0.25) +// xtilt = angle of tilt of end of cylinder in the X direction. (Default: 0) +// ytilt = angle of tilt of end of cylinder in the Y direction. (Default: 0) +// Example: +// $fa=2; $fs=2; +// difference() { +// cube([150,150,100], center=true); +// cylinder(r=50, h=100.1, center=true); +// up(50) fillet_hole_mask(r=50, fillet=10, xtilt=0, ytilt=0); +// } +module fillet_hole_mask(r=1.0, fillet=0.25, xtilt=0, ytilt=0) +{ + skew_xz(zang=xtilt) { + skew_yz(zang=ytilt) { + difference() { + cylinder(r=r+fillet, h=2*fillet, center=true); + down(fillet) torus(ir=r, or=r+2*fillet); + } + } + } +} + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/math.scad b/math.scad new file mode 100644 index 0000000..0ec1271 --- /dev/null +++ b/math.scad @@ -0,0 +1,192 @@ +////////////////////////////////////////////////////////////////////// +// Math helper functions. +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +function Cpi() = 3.141592653589793236; + + +// Quantize a value x to an integer multiple of y, rounding to the nearest multiple. +function quant(x,y) = floor(x/y+0.5)*y; + + +// Quantize a value x to an integer multiple of y, rounding down to the previous multiple. +function quantdn(x,y) = floor(x/y)*y; + + +// Quantize a value x to an integer multiple of y, rounding up to the next multiple. +function quantup(x,y) = ceil(x/y)*y; + + +// Calculate OpenSCAD standard number of segments in a circle based on $fn, $fa, and $fs. +// r = radius of circle to get the number of segments for. +function segs(r) = $fn>0?($fn>3?$fn:3):(ceil(max(min(360.0/$fa,abs(r)*2*Cpi()/$fs),5))); + + +// Calculate hypotenuse length of 2D triangle. +function hypot(x,y) = sqrt(x*x+y*y); + + +// Calculate hypotenuse length of 3D triangle. +function hypot3(x,y,z) = sqrt(x*x+y*y+z*z); + + +// Returns all but the first item of a given array. +function cdr(list) = len(list)>1?[for (i=[1:len(list)-1]) list[i]]:[]; + + +// Reverses a list/array. +function reverse(list) = [ for (i = [len(list)-1 : -1 : 0]) list[i] ]; + + +// Returns the sum of the square of each element of a vector. +function sum_of_squares(v,n=0) = (n>=len(v))? 0 : ((v[n]*v[n]) + sum_of_squares(v,n+1)); + + +// Returns a 3D vector/point from a 2D or 3D vector. +function point3d(p) = [p[0], p[1], ((len(p) < 3)? 0 : p[2])]; + + +// Returns an array of 3D vectors/points from a 2D or 3D vector array. +function path3d(points) = [for (point = points) point3d(point)]; + + +// Returns the distance between a pair of 2D or 3D points. +function distance(p1, p2) = let(d = point3d(p2) - point3d(p1)) hypot3(d[0], d[1], d[2]); + + +// Create an identity matrix, for a given number of axes. +function ident(n) = [for (i = [0:n-1]) [for (j = [0:n-1]) (i==j)?1:0]]; + + +// Create an identity matrix, for 3 axes. +ident3 = ident(3); +ident4 = ident(4); + + +// Takes a 3x3 matrix and returns its 4x4 equivalent. +function mat3_to_mat4(m) = concat( + [for (r = [0:2]) + concat( + [for (c = [0:2]) m[r][c]], + [0] + ) + ], + [[0, 0, 0, 1]] +); + + +// Returns the 3x3 matrix to perform a rotation of a vector around the X axis. +// ang = number of degrees to rotate. +function matrix3_xrot(ang) = [ + [1, 0, 0], + [0, cos(ang), -sin(ang)], + [0, sin(ang), cos(ang)] +]; + + +// Returns the 4x4 matrix to perform a rotation of a vector around the X axis. +// ang = number of degrees to rotate. +function matrix4_xrot(ang) = mat3_to_mat4(matrix3_xrot(ang)); + + +// Returns the 3x3 matrix to perform a rotation of a vector around the Y axis. +// ang = number of degrees to rotate. +function matrix3_yrot(ang) = [ + [ cos(ang), 0, sin(ang)], + [ 0, 1, 0], + [-sin(ang), 0, cos(ang)], +]; + + +// Returns the 4x4 matrix to perform a rotation of a vector around the Y axis. +// ang = number of degrees to rotate. +function matrix4_yrot(ang) = mat3_to_mat4(matrix3_yrot(ang)); + + +// Returns the 3x3 matrix to perform a rotation of a vector around the Z axis. +// ang = number of degrees to rotate. +function matrix3_zrot(ang) = [ + [cos(ang), -sin(ang), 0], + [sin(ang), cos(ang), 0], + [ 0, 0, 1] +]; + +// Returns the 4x4 matrix to perform a rotation of a vector around the Z axis. +// ang = number of degrees to rotate. +function matrix4_zrot(ang) = mat3_to_mat4(matrix3_zrot(ang)); + + +// Returns the 3x3 matrix to perform a rotation of a vector around an axis. +// u = axis vector to rotate around. +// ang = number of degrees to rotate. +function matrix3_rot_by_axis(u, ang) = let( + c = cos(ang), c2 = 1-c, s = sin(ang) +) [ + [u[0]*u[0]*c2+c, u[0]*u[1]*c2-u[2]*s, u[0]*u[2]*c2+u[1]*s], + [u[1]*u[0]*c2+u[2]*s, u[1]*u[1]*c2+c, u[1]*u[2]*c2-u[0]*s], + [u[2]*u[0]*c2-u[1]*s, u[2]*u[1]*c2+u[0]*s, u[2]*u[2]*c2+c ] +]; + + +// Returns the 4x4 matrix to perform a rotation of a vector around an axis. +// u = axis vector to rotate around. +// ang = number of degrees to rotate. +function matrix4_rot_by_axis(u, ang) = mat3_to_mat4(matrix3_rot_by_axis(u, ang)); + + +// Gives the sum of a series of sines, at a given angle. +// a = angle to get the value for. +// sines = array of [amplitude, frequency] pairs, where the frequency is the +// number of times the cycle repeats around the circle. +function sum_of_sines(a,sines) = len(sines)==0? 0 : + len(sines)==1?sines[0][0]*sin(a*sines[0][1]+(len(sines[0])>2?sines[0][2]:0)): + sum_of_sines(a,[sines[0]])+sum_of_sines(a,cdr(sines)); + + +// Returns unit length normalized version of vector v. +function normalize(v) = v/norm(v); + +// Returns angle in degrees between two 2D vectors. +function vector2d_angle(v1,v2) = atan2(v1[1],v1[0]) - atan2(v2[1],v2[0]); + +// Returns angle in degrees between two 3D vectors. +function vector3d_angle(v1,v2) = acos((v1*v2)/(norm(v1)*norm(v2))); + +// Returns a slice of an array. An index of 0 is the array start, -1 is array end +function slice(arr,st,end) = let( + s=st<0?(len(arr)+st):st, + e=end<0?(len(arr)+end+1):end + ) [for (i=[s:e-1]) if (e>s) arr[i]]; + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/metric_screws.scad b/metric_screws.scad new file mode 100644 index 0000000..fd1e926 --- /dev/null +++ b/metric_screws.scad @@ -0,0 +1,123 @@ +////////////////////////////////////////////////////////////////////// +// Screws, Bolts, and Nuts. +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +function get_metric_bolt_head_size(size) = lookup(size, [ + [ 4.0, 7.0], + [ 5.0, 8.0], + [ 6.0, 10.0], + [ 7.0, 11.0], + [ 8.0, 13.0], + [10.0, 16.0], + [12.0, 18.0], + [14.0, 21.0], + [16.0, 24.0], + [18.0, 27.0], + [20.0, 30.0] + ]); + + +function get_metric_nut_size(size) = lookup(size, [ + [ 2.0, 4.0], + [ 2.5, 5.0], + [ 3.0, 5.5], + [ 4.0, 7.0], + [ 5.0, 8.0], + [ 6.0, 10.0], + [ 7.0, 11.0], + [ 8.0, 13.0], + [10.0, 17.0], + [12.0, 19.0], + [14.0, 22.0], + [16.0, 24.0], + [18.0, 27.0], + [20.0, 30.0], + ]); + + +function get_metric_nut_thickness(size) = lookup(size, [ + [ 2.0, 1.6], + [ 2.5, 2.0], + [ 3.0, 2.4], + [ 4.0, 3.2], + [ 5.0, 4.0], + [ 6.0, 5.0], + [ 7.0, 5.5], + [ 8.0, 6.5], + [10.0, 8.0], + [12.0, 10.0], + [14.0, 11.0], + [16.0, 13.0], + [18.0, 15.0], + [20.0, 16.0] + ]); + + +// Makes a simple threadless screw, useful for making screwholes. +// screwsize = diameter of threaded part of screw. +// screwlen = length of threaded part of screw. +// headsize = diameter of the screw head. +// headlen = length of the screw head. +// Example: +// screw(screwsize=3,screwlen=10,headsize=6,headlen=3); +module screw(screwsize=3,screwlen=10,headsize=6,headlen=3,$fn=undef) +{ + $fn = ($fn==undef)?max(8,floor(180/asin(2/screwsize)/2)*2):$fn; + translate([0,0,-(screwlen)/2]) + cylinder(r=screwsize/2, h=screwlen+0.05, center=true, $fn=$fn); + translate([0,0,(headlen)/2]) + cylinder(r=headsize/2, h=headlen, center=true, $fn=$fn*2); +} + + +// Makes an unthreaded model of a standard nut for a standard metric screw. +// size = standard metric screw size in mm. (Default: 3) +// hole = include an unthreaded hole in the nut. (Default: true) +// Example: +// metric_nut(size=8, hole=true); +// metric_nut(size=3, hole=false); +module metric_nut(size=3, hole=true, $fn=undef, center=false) +{ + $fn = ($fn==undef)?max(8,floor(180/asin(2/size)/2)*2):$fn; + radius = get_metric_nut_size(size)/2/cos(30); + thick = get_metric_nut_thickness(size); + offset = (center == true)? 0 : thick/2; + translate([0,0,offset]) difference() { + cylinder(r=radius, h=thick, center=true, $fn=6); + if (hole == true) + cylinder(r=size/2, h=thick+0.5, center=true, $fn=$fn); + } +} + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/nema_steppers.scad b/nema_steppers.scad new file mode 100644 index 0000000..e6526f4 --- /dev/null +++ b/nema_steppers.scad @@ -0,0 +1,314 @@ +////////////////////////////////////////////////////////////////////// +// Masks and models for NEMA stepper motors. +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +include +include +include + + +function nema_motor_width(size) = lookup(size, [ + [11.0, 28.2], + [14.0, 35.2], + [17.0, 42.3], + [23.0, 57.0], + [34.0, 86.0], + ]); + +function nema_motor_plinth_height(size) = lookup(size, [ + [11.0, 1.5], + [14.0, 2.0], + [17.0, 2.0], + [23.0, 1.6], + [34.0, 2.03], + ]); + +function nema_motor_plinth_diam(size) = lookup(size, [ + [11.0, 22.0], + [14.0, 22.0], + [17.0, 22.0], + [23.0, 38.1], + [34.0, 73.0], + ]); + +function nema_motor_screw_spacing(size) = lookup(size, [ + [11.0, 23.11], + [14.0, 26.0], + [17.0, 30.99], + [23.0, 47.14], + [34.0, 69.6], + ]); + +function nema_motor_screw_size(size) = lookup(size, [ + [11.0, 2.6], + [14.0, 3.0], + [17.0, 3.0], + [23.0, 5.1], + [34.0, 5.5], + ]); + +function nema_motor_screw_depth(size) = lookup(size, [ + [11.0, 3.0], + [14.0, 4.5], + [17.0, 4.5], + [23.0, 4.8], + [34.0, 9.0], + ]); + + +module nema11_stepper(h=24, shaft=5, shaft_len=20) +{ + size = 11; + motor_width = nema_motor_width(size); + plinth_height = nema_motor_plinth_height(size); + plinth_diam = nema_motor_plinth_diam(size); + screw_spacing = nema_motor_screw_spacing(size); + screw_size = nema_motor_screw_size(size); + screw_depth = nema_motor_screw_depth(size); + + difference() { + color([0.4, 0.4, 0.4]) { + translate([0, 0, -h/2]) { + rrect(size=[motor_width, motor_width, h], r=2, center=true); + } + } + xspread(screw_spacing) + yspread(screw_spacing) + down(scred_depth/2-0.05) + cylinder(r=screw_size/2, h=screw_depth, center=true, $fn=max(12,segs(screw_size/2))); + } + color([0.4, 0.4, 0.4]) + translate([0, 0, plinth_height/2]) + cylinder(h=plinth_height, r=plinth_diam/2, center=true); + color("silver") + translate([0, 0, shaft_len/2]) + cylinder(h=shaft_len, r=shaft/2, center=true, $fn=max(12,segs(shaft/2))); +} +//!nema11_stepper(); + + + +module nema14_stepper(h=24, shaft=5, shaft_len=24) +{ + size = 14; + motor_width = nema_motor_width(size); + plinth_height = nema_motor_plinth_height(size); + plinth_diam = nema_motor_plinth_diam(size); + screw_spacing = nema_motor_screw_spacing(size); + screw_size = nema_motor_screw_size(size); + screw_depth = nema_motor_screw_depth(size); + + difference() { + color([0.4, 0.4, 0.4]) { + translate([0, 0, -h/2]) { + rrect(size=[motor_width, motor_width, h], r=2, center=true); + } + } + xspread(screw_spacing) + yspread(screw_spacing) + down(screw_depth/2-0.05) + cylinder(r=screw_size/2, h=screw_depth, center=true, $fn=max(12,segs(screw_size/2))); + } + color([0.4, 0.4, 0.4]) + translate([0, 0, plinth_height/2]) + cylinder(h=plinth_height, r=plinth_diam/2, center=true); + color("silver") + translate([0, 0, shaft_len/2]) + cylinder(h=shaft_len, r=shaft/2, center=true, $fn=max(12,segs(shaft/2))); +} +//!nema14_stepper(); + + + +module nema17_stepper(h=34, shaft=5, shaft_len=20) +{ + size = 17; + motor_width = nema_motor_width(size); + plinth_height = nema_motor_plinth_height(size); + plinth_diam = nema_motor_plinth_diam(size); + screw_spacing = nema_motor_screw_spacing(size); + screw_size = nema_motor_screw_size(size); + screw_depth = nema_motor_screw_depth(size); + + difference() { + color([0.4, 0.4, 0.4]) { + down(h/2) { + rrect(size=[motor_width, motor_width, h], r=2, center=true); + } + } + xspread(screw_spacing) + yspread(screw_spacing) + down(screw_depth/2-0.05) + cylinder(r=screw_size/2, h=screw_depth, center=true, $fn=max(12,segs(screw_size/2))); + } + color([0.4, 0.4, 0.4]) + up(plinth_height/2) + cylinder(h=plinth_height, r=plinth_diam/2, center=true); + color([0.9, 0.9, 0.9]) { + down(h-motor_width/12) { + fwd(motor_width/2+motor_width/24/2-0.1) { + difference() { + cube(size=[motor_width/8, motor_width/24, motor_width/8], center=true); + xrot(90) { + cylinder(d=motor_width/8-2, h=motor_width/6, center=true, $fn=12); + } + } + } + } + } + color("silver") { + difference() { + cylinder(h=shaft_len, r=shaft/2, $fn=max(12,segs(shaft/2))); + up(shaft_len/2+1) { + right(shaft_len/2+shaft/2-0.5) { + cube(shaft_len, center=true); + } + } + } + } +} +//!nema17_stepper(); + + + +module nema23_stepper(h=50, shaft=6.35, shaft_len=25) +{ + size = 23; + motor_width = nema_motor_width(size); + plinth_height = nema_motor_plinth_height(size); + plinth_diam = nema_motor_plinth_diam(size); + screw_spacing = nema_motor_screw_spacing(size); + screw_size = nema_motor_screw_size(size); + screw_depth = nema_motor_screw_depth(size); + + screw_inset = motor_width - screw_spacing + 1; + difference() { + union() { + color([0.4, 0.4, 0.4]) { + translate([0, 0, -h/2]) { + rrect(size=[motor_width, motor_width, h], r=2, center=true); + } + } + color([0.4, 0.4, 0.4]) + translate([0, 0, plinth_height/2]) + cylinder(h=plinth_height, r=plinth_diam/2, center=true); + color("silver") + translate([0, 0, shaft_len/2]) + cylinder(h=shaft_len, r=shaft/2, center=true, $fn=max(12,segs(shaft/2))); + } + xspread(screw_spacing) { + yspread(screw_spacing) { + down(screw_depth/2) + cylinder(r=screw_size/2, h=screw_depth+2, center=true, $fn=max(12,segs(screw_size/2))); + down(screw_depth+h/2) + cube(size=[screw_inset, screw_inset, h], center=true); + } + } + } +} +//!nema23_stepper(); + + + +module nema34_stepper(h=75, shaft=12.7, shaft_len=32) +{ + size = 34; + motor_width = nema_motor_width(size); + plinth_height = nema_motor_plinth_height(size); + plinth_diam = nema_motor_plinth_diam(size); + screw_spacing = nema_motor_screw_spacing(size); + screw_size = nema_motor_screw_size(size); + screw_depth = nema_motor_screw_depth(size); + + screw_inset = motor_width - screw_spacing + 1; + difference() { + union() { + color([0.4, 0.4, 0.4]) { + translate([0, 0, -h/2]) { + rrect(size=[motor_width, motor_width, h], r=2, center=true); + } + } + color([0.4, 0.4, 0.4]) + translate([0, 0, plinth_height/2]) + cylinder(h=plinth_height, r=plinth_diam/2, center=true); + color("silver") + translate([0, 0, shaft_len/2]) + cylinder(h=shaft_len, r=shaft/2, center=true, $fn=max(24,segs(shaft/2))); + } + xspread(screw_spacing) { + yspread(screw_spacing) { + down(screw_depth/2) + cylinder(r=screw_size/2, h=screw_depth+2, center=true, $fn=max(12,segs(screw_size/2))); + down(screw_depth+h/2) + cube(size=[screw_inset, screw_inset, h], center=true); + } + } + } +} +//!nema34_stepper(); + + + +module nema17_mount_holes(depth=5, l=5, slop=printer_slop) +{ + size = 17; + plinth_diam = nema_motor_plinth_diam(size)+slop; + screw_spacing = nema_motor_screw_spacing(size); + screw_size = nema_motor_screw_size(size)+slop; + + union() { + xspread(screw_spacing) { + yspread(screw_spacing) { + if (l>0) { + union() { + yspread(l) cylinder(h=depth, d=screw_size, center=true, $fn=max(8,segs(screw_size/2))); + cube([screw_size, l, depth], center=true); + } + } else { + cylinder(h=depth, d=screw_size, center=true, $fn=max(8,segs(screw_size/2))); + } + } + } + } + if (l>0) { + union () { + yspread(l) cylinder(h=depth, d=plinth_diam, center=true); + cube([plinth_diam, l, depth], center=true); + } + } else { + cylinder(h=depth, d=plinth_diam, center=true); + } +} +//!nema17_mount_holes(depth=5, l=5); + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/paths.scad b/paths.scad new file mode 100644 index 0000000..656835f --- /dev/null +++ b/paths.scad @@ -0,0 +1,224 @@ +////////////////////////////////////////////////////////////////////// +// 2D and Bezier Stuff. +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +include +include + + +// Creates a 2D polygon circle, modulated by one or more superimposed +// sine waves. +// r = radius of the base circle. +// sines = array of [amplitude, frequency] pairs, where the frequency is the +// number of times the cycle repeats around the circle. +// Example: +// modulated_circle(r=40, sines=[[3, 11], [1, 31]], $fn=6); +module modulated_circle(r=40, sines=[10]) +{ + freqs = len(sines)>0? [for (i=sines) i[1]] : [5]; + points = [ + for (a = [0 : (360/segs(r)/max(freqs)) : 360]) + let(nr=r+sum_of_sines(a,sines)) [nr*cos(a), nr*sin(a)] + ]; + polygon(points); +} + + +// Similar to linear_extrude(), except the result is a hollow shell. +// wall = thickness of shell wall. +// height = height of extrusion. +// twist = degrees of twist, from bottom to top. +// slices = how many slices to use when making extrusion. +// Example: +// extrude_2d_hollow(wall=2, height=100, twist=90, slices=50) +// circle(r=40, center=true, $fn=6); +module extrude_2d_hollow(wall=2, height=50, twist=90, slices=60) +{ + linear_extrude(height=height, twist=twist, slices=slices) { + difference() { + children(); + offset(r=-wall) { + children(); + } + } + } +} + + +// Takes a 2D polyline and removes uneccessary collinear points. +function simplify2d_path(path) = concat( + [path[0]], + [ + for ( + i = [1:len(path)-2] + ) let ( + v1 = path[i] - path[i-1], + v2 = path[i+1] - path[i-1] + ) if (abs(cross(v1,v2)) > 1e-6) path[i] + ], + [path[len(path)-1]] +); + + +// Takes a 3D polyline and removes uneccessary collinear points. +function simplify3d_path(path) = concat( + [path[0]], + [ + for ( + i = [1:len(path)-2] + ) let ( + v1 = path[i] - path[i-1], + v2 = path[i+1] - path[i-1] + ) if (vector3d_angle(v1,v2) > 1e-6) path[i] + ], + [path[len(path)-1]] +); + + + +// Takes a closed 2D polyline path, centered on the XY plane, and +// extrudes it along a 3D spiral path of a given radius, height and twist. +// polyline = Array of points of a polyline path, to be extruded. +// h = height of the spiral to extrude along. +// r = radius of the spiral to extrude along. +// twist = number of degrees of rotation to spiral up along height. +// Example: +// poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]]; +// extrude_2dpath_along_spiral(poly, h=200, r=50, twist=1000, $fn=36); +module extrude_2dpath_along_spiral(polyline, h, r, twist=360) { + pline_count = len(polyline); + steps = ceil(segs(r)*(twist/360)); + + poly_points = [ + for ( + p = [0:steps] + ) let ( + a = twist * (p/steps), + dx = r*cos(a), + dy = r*sin(a), + dz = h * (p/steps), + cp = [dx, dy, dz], + rotx = matrix3_xrot(90), + rotz = matrix3_zrot(a), + rotm = rotz * rotx + ) for ( + b = [0:pline_count-1] + ) rotm*point3d(polyline[b])+cp + ]; + + poly_faces = concat( + [[for (b = [0:pline_count-1]) b]], + [ + for ( + p = [0:steps-1], + b = [0:pline_count-1], + i = [0:1] + ) let ( + b2 = (b == pline_count-1)? 0 : b+1, + p0 = p * pline_count + b, + p1 = p * pline_count + b2, + p2 = (p+1) * pline_count + b2, + p3 = (p+1) * pline_count + b, + pt = (i==0)? [p0, p2, p1] : [p0, p3, p2] + ) pt + ], + [[for (b = [pline_count-1:-1:0]) b+(steps)*pline_count]] + ); + + polyhedron(points=poly_points, faces=poly_faces, convexity=10); +} + + +function points_along_path3d( + polyline, // The 2D polyline to drag along the 3D path. + path, // The 3D polyline path to follow. + q=Q_Ident(), // Used in recursion + n=0 // Used in recursion +) = let( + end = len(path)-1, + v1 = (n == 0)? [0, 0, 1] : normalize(path[n]-path[n-1]), + v2 = (n == end)? normalize(path[n]-path[n-1]) : normalize(path[n+1]-path[n]), + crs = cross(v1, v2), + axis = norm(crs) <= 0.001? [0, 0, 1] : crs, + ang = vector3d_angle(v1, v2), + hang = ang * (n==0? 1.0 : 0.5), + hrot = Quat(axis, hang), + arot = Quat(axis, ang), + roth = Q_Mul(hrot, q), + rotm = Q_Mul(arot, q) +) concat( + [for (i = [0:len(polyline)-1]) Q_Rot_Vector(point3d(polyline[i]),roth) + path[n]], + (n == end)? [] : points_along_path3d(polyline, path, rotm, n+1) +); + + +// Takes a closed 2D polyline path, centered on the XY plane, and +// extrudes it perpendicularly along a 3D polyline path, forming a solid. +// polyline = Array of points of a polyline path, to be extruded. +// path = Array of points of a polyline path, to extrude along. +// convexity = max number of surfaces any single ray can pass through. +// Example: +// shape = [ [-15, 0], [0, 0], [-5, 10], [0, 10], [5, 10], [10, 5], [15, 0], [10, -5], [5, -10], [0, -10], [-5, -10], [-10, -5], [-15, 0] ]; +// path = [ [0, 0, 0], [100, 33, 33], [200, -33, -33], [300, 0, 0] ]; +// extrude_2dpath_along_3dpath(shape, path, tilt=false); +module extrude_2dpath_along_3dpath(polyline, path, convexity=10) { + pline_count = len(polyline); + path_count = len(path); + + poly_points = points_along_path3d(polyline, path); + + poly_faces = concat( + [[for (b = [0:pline_count-1]) b]], + [ + for ( + p = [0:path_count-2], + b = [0:pline_count-1], + i = [0:1] + ) let ( + b2 = (b == pline_count-1)? 0 : b+1, + p0 = p * pline_count + b, + p1 = p * pline_count + b2, + p2 = (p+1) * pline_count + b2, + p3 = (p+1) * pline_count + b, + pt = (i==0)? [p0, p2, p1] : [p0, p3, p2] + ) pt + ], + [[for (b = [pline_count-1:-1:0]) b+(path_count-1)*pline_count]] + ); + + polyhedron(points=poly_points, faces=poly_faces, convexity=convexity); +} + + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/quaternions.scad b/quaternions.scad new file mode 100644 index 0000000..d16c141 --- /dev/null +++ b/quaternions.scad @@ -0,0 +1,92 @@ +/////////////////////////////////////////// +// Quaternions +/////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +// Quaternions are stored internally as a 4-value vector: +// [X, Y, Z, W] = W + Xi + Yj + Zk +function _Quat(a,s,w) = [a[0]*s, a[1]*s, a[2]*s, w]; +function Quat(ax, ang) = _Quat(ax/norm(ax), sin(ang/2), cos(ang/2)); + +function Q_Ident() = [0, 0, 0, 1]; + +function Q_Add_S(q, s) = [q[0], q[1], q[2], q[3]+s]; +function Q_Sub_S(q, s) = [q[0], q[1], q[2], q[3]-s]; +function Q_Mul_S(q, s) = [q[0]*s, q[1]*s, q[2]*s, q[3]*s]; +function Q_Div_S(q, s) = [q[0]/s, q[1]/s, q[2]/s, q[3]/s]; + +function Q_Add(a, b) = [a[0]+b[0], a[1]+b[1], a[2]+b[2], a[3]+b[3]]; +function Q_Sub(a, b) = [a[0]-b[0], a[1]-b[1], a[2]-b[2], a[3]-b[3]]; +function Q_Mul(a, b) = [ + a[3]*b[0] + a[0]*b[3] + a[1]*b[2] - a[2]*b[1], + a[3]*b[1] - a[0]*b[2] + a[1]*b[3] + a[2]*b[0], + a[3]*b[2] + a[0]*b[1] - a[1]*b[0] + a[2]*b[3], + a[3]*b[3] - a[0]*b[0] - a[1]*b[1] - a[2]*b[2], +]; +function Q_Dot(a, b) = a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]; + +function Q_Neg(q) = [-q[0], -q[1], -q[2], -q[3]]; +function Q_Conj(q) = [-q[0], -q[1], -q[2], q[3]]; +function Q_Norm(q) = sqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]); +function Q_Normalize(q) = q/Q_Norm(q); +function Q_Dist(q1, q2) = Q_Norm(Q_Sub(q1-q2)); + + +// Returns the 3x3 rotation matrix for the given normalized quaternion q. +function Q_Matrix3(q) = [ + [1-2*q[1]*q[1]-2*q[2]*q[2], 2*q[0]*q[1]-2*q[2]*q[3], 2*q[0]*q[2]+2*q[1]*q[3]], + [ 2*q[0]*q[1]+2*q[2]*q[3], 1-2*q[0]*q[0]-2*q[2]*q[2], 2*q[1]*q[2]-2*q[0]*q[3]], + [ 2*q[0]*q[2]-2*q[1]*q[3], 2*q[1]*q[2]+2*q[0]*q[3], 1-2*q[0]*q[0]-2*q[1]*q[1]] +]; + + +// Returns the 4x4 rotation matrix for the given normalized quaternion q. +function Q_Matrix4(q) = [ + [1-2*q[1]*q[1]-2*q[2]*q[2], 2*q[0]*q[1]-2*q[2]*q[3], 2*q[0]*q[2]+2*q[1]*q[3], 0], + [ 2*q[0]*q[1]+2*q[2]*q[3], 1-2*q[0]*q[0]-2*q[2]*q[2], 2*q[1]*q[2]-2*q[0]*q[3], 0], + [ 2*q[0]*q[2]-2*q[1]*q[3], 2*q[1]*q[2]+2*q[0]*q[3], 1-2*q[0]*q[0]-2*q[1]*q[1], 0], + [ 0, 0, 0, 1] +]; + + +// Returns the vector v after rotating it by the quaternion q. +function Q_Rot_Vector(v,q) = Q_Mul(Q_Mul(q,concat(v,0)),Q_Conj(q)); + + +// Rotates all children by the given quaternion q. +module Qrot(q) { + multmatrix(Q_Matrix4(q)) { + children(); + } +} + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/shapes.scad b/shapes.scad new file mode 100644 index 0000000..957fff5 --- /dev/null +++ b/shapes.scad @@ -0,0 +1,905 @@ +////////////////////////////////////////////////////////////////////// +// Compound Shapes. +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +include + + +// For when you MUST pass a child to a module, but you want it to be nothing. +module nil() difference() {cube(0.1, center=true); cube(0.2, center=true);} + + +// Makes a cube that is centered in X and Y axes, and has its bottom aligned with Z=0. +module upcube(size=[1,1,1]) {up(size[2]/2) cube(size, center=true);} + + + +// Makes a cube with chamfered edges. +// size = size of cube [X,Y,Z]. (Default: [1,1,1]) +// chamfer = chamfer inset along axis. (Default: 0.25) +// Example: +// chamfcube(size=[10,30,50], chamfer=1, chamfaxes=[1,1,1], chamfcorners=true); +module chamfcube( + size=[1,1,1], + chamfer=0.25, + chamfaxes=[1,1,1], + chamfcorners=false +) { + ch_width = sqrt(2)*chamfer; + ch_offset = 1; + difference() { + cube(size=size, center=true); + for (xs = [-1,1]) { + for (ys = [-1,1]) { + if (chamfaxes[0] == 1) { + translate([0,xs*size[1]/2,ys*size[2]/2]) { + rotate(a=[45,0,0]) cube(size=[size[0]+0.1,ch_width,ch_width], center=true); + } + } + if (chamfaxes[1] == 1) { + translate([xs*size[0]/2,0,ys*size[2]/2]) { + rotate(a=[0,45,0]) cube(size=[ch_width,size[1]+0.1,ch_width], center=true); + } + } + if (chamfaxes[2] == 1) { + translate([xs*size[0]/2,ys*size[1]/2],0) { + rotate(a=[0,0,45]) cube(size=[ch_width,ch_width,size[2]+0.1], center=true); + } + } + if (chamfcorners) { + for (zs = [-1,1]) { + translate([xs*size[0]/2,ys*size[1]/2,zs*size[2]/2]) { + scale([chamfer,chamfer,chamfer]) { + polyhedron( + points=[ + [0,-1,-1], [0,-1,1], [0,1,1], [0,1,-1], + [-1,0,-1], [-1,0,1], [1,0,1], [1,0,-1], + [-1,-1,0], [-1,1,0], [1,1,0], [1,-1,0] + ], + faces=[ + [ 8, 4, 9, 5], + [ 9, 3, 10, 2], + [10, 7, 11, 6], + [11, 0, 8, 1], + [ 0, 7, 3, 4], + [ 1, 5, 2, 6], + + [ 1, 8, 5], + [ 5, 9, 2], + [ 2, 10, 6], + [ 6, 11, 1], + + [ 0, 4, 8], + [ 4, 3, 9], + [ 3, 7, 10], + [ 7, 0, 11], + ] + ); + } + } + } + } + } + } + } +} + + +// Makes a cube with rounded (filletted) vertical edges. +// size = size of cube [X,Y,Z]. (Default: [1,1,1]) +// r = radius of edge/corner rounding. (Default: 0.25) +// Examples: +// rrect(size=[9,4,1], r=1, center=true); +// rrect(size=[5,7,3], r=1, $fn=24); +module rrect(size=[1,1,1], r=0.25, center=false) +{ + w = size[0]; + l = size[1]; + h = size[2]; + up(center? 0 : h/2) { + linear_extrude(height=h, convexity=2, center=true) { + left(w/2-r) { + back(l/2-r) circle(r=r, center=true); + fwd(l/2-r) circle(r=r, center=true); + } + right(w/2-r) { + back(l/2-r) circle(r=r, center=true); + fwd(l/2-r) circle(r=r, center=true); + } + square(size=[w, l-r*2], center=true); + square(size=[w-r*2, l], center=true); + } + } +} + + + +// Makes a cube with rounded (filletted) edges and corners. +// size = size of cube [X,Y,Z]. (Default: [1,1,1]) +// r = radius of edge/corner rounding. (Default: 0.25) +// Examples: +// rcube(size=[9,4,1], r=0.333, center=true, $fn=24); +// rcube(size=[5,7,3], r=1); +module rcube(size=[1,1,1], r=0.25, center=false) +{ + $fn = ($fn==undef)?max(18,floor(180/asin(1/r)/2)*2):$fn; + xoff=abs(size[0])/2-r; + yoff=abs(size[1])/2-r; + zoff=abs(size[2])/2-r; + offset = center?[0,0,0]:size/2; + translate(offset) { + union() { + grid_of([-xoff,xoff],[-yoff,yoff],[-zoff,zoff]) + sphere(r=r,center=true,$fn=$fn); + grid_of(xa=[-xoff,xoff],ya=[-yoff,yoff]) + cylinder(r=r,h=zoff*2,center=true,$fn=$fn); + grid_of(xa=[-xoff,xoff],za=[-zoff,zoff]) + rotate([90,0,0]) + cylinder(r=r,h=yoff*2,center=true,$fn=$fn); + grid_of(ya=[-yoff,yoff],za=[-zoff,zoff]) + rotate([90,0,0]) + rotate([0,90,0]) + cylinder(r=r,h=xoff*2,center=true,$fn=$fn); + cube(size=[xoff*2,yoff*2,size[2]], center=true); + cube(size=[xoff*2,size[1],zoff*2], center=true); + cube(size=[size[0],yoff*2,zoff*2], center=true); + } + } +} + + +// Creates a cylinder with chamferred edges. +// h = height of cylinder. (Default: 1.0) +// r = radius of cylinder. (Default: 1.0) +// d = diameter of cylinder. (use instead of r) +// chamfer = radial inset of the edge chamfer. (Default: 0.25) +// chamfedge = length of the chamfer edge. (Use instead of chamfer) +// center = boolean. If true, cylinder is centered. (Default: false) +// top = boolean. If true, chamfer the top edges. (Default: True) +// bottom = boolean. If true, chamfer the bottom edges. (Default: True) +// Example: +// chamferred_cylinder(h=50, r=20, chamfer=5, angle=45, bottom=false, center=true); +// chamferred_cylinder(h=50, r=20, chamfedge=10, angle=30, center=true); +module chamferred_cylinder(h=1, r=1, d=undef, chamfer=0.25, chamfedge=undef, angle=45, center=false, top=true, bottom=true) +{ + chamf = (chamfedge == undef)? chamfer * sqrt(2) : chamfedge; + x = (chamfedge == undef)? chamfer : (chamfedge * sin(angle)); + y = (chamfedge == undef)? chamfer*sin(90-angle)/sin(angle) : (chamfedge * sin(90-angle)); + rad = d == undef? r : d / 2.0; + up(center? 0 : h/2) { + rotate_extrude(angle=360, convexity=2) { + polygon( + points=[ + [0, h/2], + [rad-x*(top?1:0), h/2], + [rad, h/2-y*(top?1:0)], + [rad, -h/2+y*(bottom?1:0)], + [rad-x*(bottom?1:0), -h/2], + [0, -h/2], + [0, h/2], + ] + ); + } + } +} + +module chamf_cyl(h=1, r=1, d=undef, chamfer=0.25, chamfedge=undef, angle=45, center=false, top=true, bottom=true) + chamferred_cylinder(h=h, r=d, d=d, chamfer=chamfer, chamfedge=chamfedge, angle=angle, center=center, top=top, bottom=bottom); +//!chamf_cyl(h=20, d=20, chamfedge=10, angle=30, center=true, $fn=36); + + +// Creates a cylinder with filletted (rounded) ends. +// h = height of cylinder. (Default: 1.0) +// r = radius of cylinder. (Default: 1.0) +// d = diameter of cylinder. (Use instead of r) +// fillet = radius of the edge filleting. (Default: 0.25) +// center = boolean. If true, cylinder is centered. (Default: false) +// Example: +// rcylinder(h=50, r=20, fillet=5, center=true, $fa=1, $fs=1); +module rcylinder(h=1, r=1, d=undef, fillet=0.25, center=false) +{ + d = (d == undef)? r * 2.0 : d; + up(center? 0 : h/2) { + rotate_extrude(angle=360, convexity=2) { + left(d/2-fillet) { + back(h/2-fillet) circle(r=fillet, center=true); + fwd(h/2-fillet) circle(r=fillet, center=true); + } + left(d/2/2) square(size=[d/2, h-fillet*2], center=true); + left((d/2-fillet)/2) square(size=[d/2-fillet, h], center=true); + } + } +} + +module filleted_cylinder(h=1, r=1, d=undef, fillet=0.25, center=false) + rcylinder(h=h, r=r, d=d, fillet=fillet, center=center); + + + +// Creates a pyramidal prism with a given number of sides. +// n = number of pyramid sides. +// h = height of the pyramid. +// l = length of one side of the pyramid. (optional) +// r = radius of the base of the pyramid. (optional) +// d = diameter of the base of the pyramid. (optional) +// circum = base circumscribes the circle of the given radius or diam. +// Example: +// pyramid(h=3, d=4, n=6, circum=true); +module pyramid(n=4, h=1, l=1, r=undef, d=undef, circum=false) +{ + cm = circum? 1/cos(180/n) : 1.0; + radius = (r!=undef)? r*cm : ((d!=undef)? d*cm/2 : (l/(2*sin(180/n)))); + zrot(180/n) cylinder(r1=radius, r2=0, h=h, $fn=n, center=false); +} + + +// Creates a vertical prism with a given number of sides. +// n = number of sides. +// h = height of the prism. +// l = length of one side of the prism. (optional) +// r = radius of the prism. (optional) +// d = diameter of the prism. (optional) +// circum = prism circumscribes the circle of the given radius or diam. +// Example: +// prism(n=6, h=3, d=4, circum=true); +module prism(n=3, h=1, l=1, r=undef, d=undef, circum=false, center=false) +{ + cm = circum? 1/cos(180/n) : 1.0; + radius = (r!=undef)? r*cm : ((d!=undef)? d*cm/2 : (l/(2*sin(180/n)))); + zrot(180/n) cylinder(r=radius, h=h, center=center, $fn=n); +} + + +// Creates a right triangle, with the hypotenuse on the right (X+) side. +// size = [width, thickness, height] +// center = true if triangle will be centered. +// Examples: +// right_triangle([4, 1, 6], center=true); +// right_triangle([4, 1, 9]); +module right_triangle(size=[1, 1, 1], center=false) +{ + w = size[0]; + thick = size[1]; + h = size[2]; + translate(center? [-w/2, -thick/2, -h/2] : [0, 0, 0]) { + polyhedron( + points=[ + [0, 0, 0], + [0, 0, h], + [w, 0, 0], + [0, thick, 0], + [0, thick, h], + [w, thick, 0] + ], + faces=[ + [0, 1, 2], + [0, 2, 5, 3], + [0, 3, 4, 1], + [1, 4, 5, 2], + [3, 5, 4] + ], + convexity=2 + ); + } +} + + +// Creates a trapezoidal prism. +// size1 = [width, length] of the bottom of the prism. +// size2 = [width, length] of the top of the prism. +// h = Height of the prism. +// center = vertically center the prism. +// Example: +// trapezoid(size1=[1,4], size2=[4,1], h=4, center=false); +// trapezoid(size1=[2,6], size2=[4,0], h=4, center=false); +module trapezoid(size1=[1,1], size2=[1,1], h=1, center=false) +{ + s1 = [max(size1[0], 0.001), max(size1[1], 0.001)]; + s2 = [max(size2[0], 0.001), max(size2[1], 0.001)]; + up(center? 0 : h/2) { + polyhedron( + points=[ + [+s2[0]/2, +s2[1]/2, +h/2], + [+s2[0]/2, -s2[1]/2, +h/2], + [-s2[0]/2, -s2[1]/2, +h/2], + [-s2[0]/2, +s2[1]/2, +h/2], + [+s1[0]/2, +s1[1]/2, -h/2], + [+s1[0]/2, -s1[1]/2, -h/2], + [-s1[0]/2, -s1[1]/2, -h/2], + [-s1[0]/2, +s1[1]/2, -h/2], + ], + faces=[ + [0, 1, 2, 3], + [0, 4, 5, 1], + [1, 5, 6, 2], + [2, 6, 7, 3], + [3, 7, 4, 0], + [4, 7, 6, 5], + ], + convexity=2 + ); + } +} + + +// Created a sphere with a conical hat, to make a 3D teardrop. +// r = radius of spherical portion of the bottom. (Default: 1) +// d = diameter of spherical portion of bottom. (Use instead of r) +// h = height above sphere center to truncate teardrop shape. (Default: 1) +// maxang = angle of cone on top from vertical. +// Example: +// onion(h=15, r=10, maxang=30); +module onion(h=1, r=1, d=undef, maxang=45) +{ + rr = (d!=undef)? (d/2.0) : r; + xx = rr*cos(maxang); + yy = rr*sin(maxang); + tipy = xx*sin(90-maxang)/sin(maxang) + yy; + rotate_extrude(angle=360, convexity=4) { + difference() { + union() { + circle(r=rr, center=true); + polygon( + points=[ + [0, 0], + [0, tipy], + [xx, yy], + [rr, 0] + ] + ); + } + back(tipy/2+h) square(size=[rr*2, tipy], center=true); + left(rr) square(size=rr*2, center=true); + } + } +} + + +// Makes a hollow tube with the given outer size and wall thickness. +// h = height of tube. (Default: 1) +// r = Outer radius of tube. (Default: 1) +// r1 = Outer radius of bottom of tube. (Default: value of r) +// r2 = Outer radius of top of tube. (Default: value of r) +// wall = horizontal thickness of tube wall. (Default 0.5) +// Example: +// tube(h=3, r=4, wall=1, center=true); +// tube(h=6, r=4, wall=2, $fn=6); +// tube(h=3, r1=5, r2=7, wall=2, center=true); +module tube(h=1, r=1, r1=undef, r2=undef, wall=0.5, center=false) +{ + r1 = (r1==undef)? r : r1; + r2 = (r2==undef)? r : r2; + difference() { + cylinder(h=h, r1=r1, r2=r2, center=center); + down(0.25) cylinder(h=h+1, r1=r1-wall, r2=r2-wall, center=center); + } +} + + +// Creates a torus with a given outer radius and inner radius. +// or = outer radius of the torus. +// ir = inside radius of the torus. +// Example: +// torus(or=30, ir=20, $fa=1, $fs=1); +module torus(or=1, ir=0.5) +{ + rotate_extrude(convexity = 4) + translate([(or-ir)/2+ir, 0, 0]) + circle(r = (or-ir)/2); +} + + +// Makes a linear slot with rounded ends, appropriate for bolts to slide along. +// p1 = center of starting circle of slot. (Default: [0,0,0]) +// p2 = center of ending circle of slot. (Default: [1,0,0]) +// h = height of slot shape. (default: 1.0) +// r = radius of slot circle. (default: 0.5) +// r1 = bottom radius of slot cone. (use instead of r) +// r2 = top radius of slot cone. (use instead of r) +// d = diameter of slot circle. (default: 1.0) +// d1 = bottom diameter of slot cone. (use instead of d) +// d2 = top diameter of slot cone. (use instead of d) +module slot( + p1=[0,0,0], p2=[1,0,0], h=1.0, + r=undef, r1=undef, r2=undef, + d=1.0, d1=undef, d2=undef +) { + r = (r != undef)? r : (d/2); + r1 = (r1 != undef)? r1 : ((d1 != undef)? (d1/2) : r); + r2 = (r2 != undef)? r2 : ((d2 != undef)? (d2/2) : r); + delta = p2 - p1; + theta = atan2(delta[1], delta[0]); + xydist = sqrt(pow(delta[0],2) + pow(delta[1],2)); + phi = atan2(delta[2], xydist); + dist = sqrt(pow(delta[2],2) + xydist*xydist); + $fn = quantup(segs(max(r1,r2)),4); + translate(p1) { + zrot(theta) { + yrot(phi) { + cylinder(h=h, r1=r1, r2=r2, center=true); + right(dist/2) trapezoid([dist, r1*2], [dist, r2*2], h=h, center=true); + right(dist) cylinder(h=h, r1=r1, r2=r2, center=true); + } + } + } +} + + +// Makes an arced slot, appropriate for bolts to slide along. +// cp = centerpoint of slot arc. (default: [0, 0, 0]) +// h = height of slot arc shape. (default: 1.0) +// r = radius of slot arc. (default: 0.5) +// d = diameter of slot arc. (default: 1.0) +// sr = radius of slot channel. (default: 0.5) +// sd = diameter of slot channel. (default: 0.5) +// sr1 = bottom radius of slot channel cone. (use instead of sr) +// sr2 = top radius of slot channel cone. (use instead of sr) +// sd1 = bottom diameter of slot channel cone. (use instead of sd) +// sd2 = top diameter of slot channel cone. (use instead of sd) +// sa = starting angle. (Default: 0.0) +// ea = ending angle. (Default: 90.0) +// Examples: +// arced_slot(d=100, h=15, sd=10, sa=60, ea=280); +// arced_slot(r=100, h=10, sd1=30, sd2=10, sa=45, ea=180, $fa=5, $fs=2); +module arced_slot( + cp=[0,0,0], + r=undef, d=1.0, h=1.0, + sr=undef, sr1=undef, sr2=undef, + sd=1.0, sd1=undef, sd2=undef, + sa=0, ea=90 +) { + r = (r != undef)? r : (d/2); + sr = (sr != undef)? sr : (sd/2); + sr1 = (sr1 != undef)? sr1 : ((sd1 != undef)? (sd1/2) : sr); + sr2 = (sr2 != undef)? sr2 : ((sd2 != undef)? (sd2/2) : sr); + da = ea - sa; + zrot(sa) { + translate([r, 0, 0]) cylinder(h=h, r1=sr1, r2=sr2, center=true); + difference() { + angle_pie_mask(h=h, r1=(r+sr1), r2=(r+sr2), ang=da); + cylinder(h=h+0.05, r1=(r-sr1), r2=(r-sr2), center=true); + } + zrot(da) { + translate([r, 0, 0]) cylinder(h=h, r1=sr1, r2=sr2, center=true); + } + } +} + + +// Makes a teardrop shape in the XZ plane. Useful for 3D printable holes. +// r = radius of circular part of teardrop. (Default: 1) +// h = thickness of teardrop. (Default: 1) +// Example: +// teardrop(r=3, h=2, ang=30); +module teardrop(r=1, h=1, ang=45, $fn=undef) +{ + $fn = ($fn==undef)?max(12,floor(180/asin(1/r)/2)*2):$fn; + xrot(90) union() { + translate([0, r*sin(ang), 0]) { + scale([1, 1/tan(ang), 1]) { + difference() { + zrot(45) { + cube(size=[2*r*cos(ang)/sqrt(2), 2*r*cos(ang)/sqrt(2), h], center=true); + } + translate([0, -r/2, 0]) { + cube(size=[2*r, r, h+1], center=true); + } + } + } + } + cylinder(h=h, r=r, center=true); + } +} + + +// Makes a rectangular strut with the top side narrowing in a triangle. +// The shape created may be likened to an extruded home plate from baseball. +// This is useful for constructing parts that minimize the need to support +// overhangs. +// w = Width (thickness) of the strut. +// l = Length of the strut. +// wall = height of rectangular portion of the strut. +// ang = angle that the trianglar side will converge at. +// Example: +// narrowing_strut(w=10, l=100, wall=5, ang=30); +module narrowing_strut(w=10, l=100, wall=5, ang=30) +{ + tipy = wall + (w/2)*sin(90-ang)/sin(ang); + xrot(90) linear_extrude(height=l, center=true, steps=2) { + polygon( + points=[ + [-w/2, 0], + [-w/2, wall], + [0, tipy], + [w/2, wall], + [w/2, 0] + ] + ); + } +} + + +// Makes a rectangular wall which thins to a smaller width in the center, +// with angled supports to prevent critical overhangs. +// h = height of wall. +// l = length of wall. +// thick = thickness of wall. +// ang = maximum overhang angle of diagonal brace. +// strut = the width of the diagonal brace. +// wall = the thickness of the thinned portion of the wall. +// Example: +// thinning_wall(h=50, l=100, thick=4, ang=30, strut=5, wall=2); +module thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2) +{ + l1 = (l[0] == undef)? l : l[0]; + l2 = (l[1] == undef)? l : l[1]; + + trap_ang = atan2((l2-l1)/2, h); + corr1 = 1 + sin(trap_ang); + corr2 = 1 - sin(trap_ang); + + z1 = h/2; + z2 = max(0.1, z1 - strut); + z3 = max(0.05, z2 - (thick-wall)/2*sin(90-ang)/sin(ang)); + + x1 = l2/2; + x2 = max(0.1, x1 - strut*corr1); + x3 = max(0.05, x2 - (thick-wall)/2*sin(90-ang)/sin(ang)*corr1); + x4 = l1/2; + x5 = max(0.1, x4 - strut*corr2); + x6 = max(0.05, x5 - (thick-wall)/2*sin(90-ang)/sin(ang)*corr2); + + y1 = thick/2; + y2 = y1 - min(z2-z3, x2-x3) * sin(ang); + + zrot(90) { + polyhedron( + points=[ + [-x4, -y1, -z1], + [ x4, -y1, -z1], + [ x1, -y1, z1], + [-x1, -y1, z1], + + [-x5, -y1, -z2], + [ x5, -y1, -z2], + [ x2, -y1, z2], + [-x2, -y1, z2], + + [-x6, -y2, -z3], + [ x6, -y2, -z3], + [ x3, -y2, z3], + [-x3, -y2, z3], + + [-x4, y1, -z1], + [ x4, y1, -z1], + [ x1, y1, z1], + [-x1, y1, z1], + + [-x5, y1, -z2], + [ x5, y1, -z2], + [ x2, y1, z2], + [-x2, y1, z2], + + [-x6, y2, -z3], + [ x6, y2, -z3], + [ x3, y2, z3], + [-x3, y2, z3], + ], + faces=[ + [ 4, 5, 1], + [ 5, 6, 2], + [ 6, 7, 3], + [ 7, 4, 0], + + [ 4, 1, 0], + [ 5, 2, 1], + [ 6, 3, 2], + [ 7, 0, 3], + + [ 8, 9, 5], + [ 9, 10, 6], + [10, 11, 7], + [11, 8, 4], + + [ 8, 5, 4], + [ 9, 6, 5], + [10, 7, 6], + [11, 4, 7], + + [11, 10, 9], + [20, 21, 22], + + [11, 9, 8], + [20, 22, 23], + + [16, 17, 21], + [17, 18, 22], + [18, 19, 23], + [19, 16, 20], + + [16, 21, 20], + [17, 22, 21], + [18, 23, 22], + [19, 20, 23], + + [12, 13, 17], + [13, 14, 18], + [14, 15, 19], + [15, 12, 16], + + [12, 17, 16], + [13, 18, 17], + [14, 19, 18], + [15, 16, 19], + + [ 0, 1, 13], + [ 1, 2, 14], + [ 2, 3, 15], + [ 3, 0, 12], + + [ 0, 13, 12], + [ 1, 14, 13], + [ 2, 15, 14], + [ 3, 12, 15], + ], + convexity=2 + ); + } +} +//!thinning_wall(h=50, l=[100, 80], thick=4, ang=30, strut=5, wall=2); + + +module braced_thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2) +{ + dang = atan((h-2*strut)/(l-2*strut)); + dlen = (h-2*strut)/sin(dang); + union() { + xrot_copies([0, 180]) { + down(h/2) narrowing_strut(w=thick, l=l, wall=strut, ang=ang); + fwd(l/2) xrot(-90) narrowing_strut(w=thick, l=h-0.1, wall=strut, ang=ang); + intersection() { + cube(size=[thick, l, h], center=true); + xrot_copies([-dang,dang]) { + zspread(strut/2) { + scale([1,1,1.5]) yrot(45) { + cube(size=[thick/sqrt(2), dlen, thick/sqrt(2)], center=true); + } + } + cube(size=[thick, dlen, strut/2], center=true); + } + } + } + cube(size=[wall, l-0.1, h-0.1], center=true); + } +} + + +// Makes a triangular wall with thick edges, which thins to a smaller width in +// the center, with angled supports to prevent critical overhangs. +// h = height of wall. +// l = length of wall. +// thick = thickness of wall. +// ang = maximum overhang angle of diagonal brace. +// strut = the width of the diagonal brace. +// wall = the thickness of the thinned portion of the wall. +// diagonly = boolean, which denotes only the diagonal brace should be thick. +// Example: +// thinning_triangle(h=50, l=100, thick=4, ang=30, strut=5, wall=2, diagonly=true); +module thinning_triangle(h=50, l=100, thick=5, ang=30, strut=5, wall=3, diagonly=false) +{ + dang = atan(h/l); + dlen = h/sin(dang); + difference() { + union() { + if (!diagonly) { + translate([0, 0, -h/2]) + narrowing_strut(w=thick, l=l, wall=strut, ang=ang); + translate([0, -l/2, 0]) + xrot(-90) narrowing_strut(w=thick, l=h-0.1, wall=strut, ang=ang); + } + intersection() { + cube(size=[thick, l, h], center=true); + xrot(-dang) yrot(180) { + narrowing_strut(w=thick, l=dlen*1.2, wall=strut, ang=ang); + } + } + cube(size=[wall, l-0.1, h-0.1], center=true); + } + xrot(-dang) { + translate([0, 0, h/2]) { + cube(size=[thick+0.1, l*2, h], center=true); + } + } + } +} + + +// Makes a triangular wall which thins to a smaller width in the center, +// with angled supports to prevent critical overhangs. Basically an alias +// of thinning_triangle(), with diagonly=true. +// h = height of wall. +// l = length of wall. +// thick = thickness of wall. +// ang = maximum overhang angle of diagonal brace. +// strut = the width of the diagonal brace. +// wall = the thickness of the thinned portion of the wall. +// Example: +// thinning_brace(h=50, l=100, thick=4, ang=30, strut=5, wall=2); +module thinning_brace(h=50, l=100, thick=5, ang=30, strut=5, wall=3) +{ + thinning_triangle(h=h, l=l, thick=thick, ang=ang, strut=strut, wall=wall, diagonly=true); +} + + +// Makes an open rectangular strut with X-shaped cross-bracing, designed with 3D printing in mind. +// h = Z size of strut. +// w = X size of strut. +// l = Y size of strut. +// thick = thickness of strut walls. +// maxang = maximum overhang angle of cross-braces. +// max_bridge = maximum bridging distance between cross-braces. +// strut = the width of the cross-braces. +// Example: +// sparse_strut3d(h=40, w=40, l=120, thick=4, maxang=30, strut=5, max_bridge=20); +module sparse_strut3d(h=50, l=100, w=50, thick=3, maxang=40, strut=3, max_bridge = 20) +{ + + xoff = w - thick; + yoff = l - thick; + zoff = h - thick; + + xreps = ceil(xoff/yoff); + yreps = ceil(yoff/xoff); + + xstep = xoff / xreps; + ystep = yoff / yreps; + + cross_ang = atan2(xstep, ystep); + cross_len = hypot(xstep, ystep); + + union() { + if(xreps>1) { + yspread(yoff) { + xspread(xstep, n=xreps-1) { + cube(size=[thick, thick, h], center=true); + } + } + } + if(yreps>1) { + xspread(xoff) { + yspread(ystep, n=yreps-1) { + cube(size=[thick, thick, h], center=true); + } + } + } + xspread(xoff) sparse_strut(h=h, l=l, thick=thick, maxang=maxang, strut=strut, max_bridge=max_bridge); + yspread(yoff) zrot(90) sparse_strut(h=h, l=w, thick=thick, maxang=maxang, strut=strut, max_bridge=max_bridge); + for(xs = [0:xreps-1]) { + for(ys = [0:yreps-1]) { + translate([(xs+0.5)*xstep-xoff/2, (ys+0.5)*ystep-yoff/2, 0]) { + zrot( cross_ang) sparse_strut(h=h, l=cross_len, thick=thick, maxang=maxang, strut=strut, max_bridge=max_bridge); + zrot(-cross_ang) sparse_strut(h=h, l=cross_len, thick=thick, maxang=maxang, strut=strut, max_bridge=max_bridge); + } + } + } + } +} +//!sparse_strut3d(h=40, w=40, l=120, thick=3, strut=3); + + +// Makes an open rectangular strut with X-shaped cross-bracing, designed with 3D printing in mind. +// h = height of strut wall. +// l = length of strut wall. +// thick = thickness of strut wall. +// maxang = maximum overhang angle of cross-braces. +// max_bridge = maximum bridging distance between cross-braces. +// strut = the width of the cross-braces. +// Example: +// sparse_strut(h=40, l=120, thick=4, maxang=30, strut=5, max_bridge=20); +module sparse_strut(h=50, l=100, thick=4, maxang=30, strut=5, max_bridge = 20) +{ + + zoff = h/2 - strut/2; + yoff = l/2 - strut/2; + + maxhyp = 1.5 * (max_bridge+strut)/2 / sin(maxang); + maxz = 2 * maxhyp * cos(maxang); + + zreps = ceil(2*zoff/maxz); + zstep = 2*zoff / zreps; + + hyp = zstep/2 / cos(maxang); + maxy = min(2 * hyp * sin(maxang), max_bridge+strut); + + yreps = ceil(2*yoff/maxy); + ystep = 2*yoff / yreps; + + ang = atan(ystep/zstep); + len = zstep / cos(ang); + + union() { + zspread(zoff*2) + cube(size=[thick, l, strut], center=true); + yspread(yoff*2) + cube(size=[thick, strut, h], center=true); + grid_of(ya=[-yoff+ystep/2:ystep:yoff], za=[-zoff+zstep/2:zstep:zoff]) { + xrot( ang) cube(size=[thick, strut, len], center=true); + xrot(-ang) cube(size=[thick, strut, len], center=true); + } + } +} + + +// Makes a corrugated wall which relieves contraction stress while still +// providing support strength. Designed with 3D printing in mind. +// h = height of strut wall. +// l = length of strut wall. +// thick = thickness of strut wall. +// strut = the width of the cross-braces. +// wall = thickness of corrugations. +// Example: +// corrugated_wall(h=50, l=100, thick=4, strut=5, wall=2); +module corrugated_wall(h=50, l=100, thick=5, strut=5, wall=2) +{ + innerlen = l - strut*2; + inner_height = h - wall*2; + spacing = thick*sqrt(3); + corr_count = floor(innerlen/spacing/2)*2; + + yspread(l-strut) { + cube(size=[thick, strut, h], center=true); + } + zspread(h-wall) { + cube(size=[thick, l, wall], center=true); + } + + difference() { + for (ypos = [-innerlen/2:spacing:innerlen/2]) { + translate([0, ypos, 0]) { + translate([0, spacing/4, 0]) + zrot(-45) cube(size=[wall, thick*sqrt(2), inner_height], center=true); + translate([0, spacing*3/4, 0]) + zrot(45) cube(size=[wall, thick*sqrt(2), inner_height], center=true); + } + } + xspread(2*thick) { + cube(size=[thick, l, h], center=true); + } + yspread(2*l) { + cube(size=[thick*2, l, h], center=true); + } + } +} + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/sliders.scad b/sliders.scad new file mode 100644 index 0000000..bf70d69 --- /dev/null +++ b/sliders.scad @@ -0,0 +1,223 @@ +////////////////////////////////////////////////////////////////////// +// Sliders and Rails. +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +use + + +module slider(l=30, w=10, h=10, base=10, wall=5, ang=30, slop=printer_slop) +{ + full_width = w + 2*wall; + full_height = h + base; + + difference() { + // Overall slider shell + up(full_height/2) cube([w+2*wall, l, full_height], center=true); + + up(base-slop) { + // Clear slider gap + up((h+5)/2) { + cube([w+slop, l+1, h+5], center=true); + } + + // Horiz edge bevel + yspread(l) { + scale([1, 1, tan(30)]) { + xrot(45) cube([w+slop, 2*sqrt(2), 2*sqrt(2)], center=true); + } + } + } + + // Back top bevel + up(full_height) { + xspread(full_width) { + yrot(45) { + cube([wall/2*sqrt(2), l+1, wall/2*sqrt(2)], center=true); + } + } + } + } + up(base) { + up(h/2) { + xflip_copy() { + left((w+slop)/2) { + difference() { + // Rails + right_half() { + scale([tan(ang), 1, 1]) { + yrot(45) cube([h*sin(45), l, h*sin(45)], center=true); + } + } + + // Rail bevels + yflip_copy() { + right(sqrt(2)*h/2) { + fwd(l/2) { + zrot(45) cube(h, center=true); + } + } + } + } + } + } + } + } +} +//slider(l=30, base=10, wall=4, slop=0.2); + + + +module rail(l=30, w=10, h=10, chamfer=1.0, ang=30) +{ + attack_ang = 30; + attack_len = 2; + + fudge = 1.177; + chamf = sqrt(2) * chamfer; + cosa = cos(ang*fudge); + sina = sin(ang*fudge); + + z1 = h/2; + z2 = z1 - chamf * cosa; + z3 = z1 - attack_len * sin(attack_ang); + z4 = 0; + + x1 = w/2; + x2 = x1 - chamf * sina; + x3 = x1 - chamf; + x4 = x1 - attack_len * sin(attack_ang); + x5 = x2 - attack_len * sin(attack_ang); + x6 = x1 - z1 * sina; + x7 = x4 - z1 * sina; + + y1 = l/2; + y2 = y1 - attack_len * cos(attack_ang); + + polyhedron( + convexity=4, + points=[ + [-x5, -y1, z3], + [ x5, -y1, z3], + [ x7, -y1, z4], + [ x4, -y1, -z1-0.05], + [-x4, -y1, -z1-0.05], + [-x7, -y1, z4], + + [-x3, -y2, z1], + [ x3, -y2, z1], + [ x2, -y2, z2], + [ x6, -y2, z4], + [ x1, -y2, -z1-0.05], + [-x1, -y2, -z1-0.05], + [-x6, -y2, z4], + [-x2, -y2, z2], + + [ x5, y1, z3], + [-x5, y1, z3], + [-x7, y1, z4], + [-x4, y1, -z1-0.05], + [ x4, y1, -z1-0.05], + [ x7, y1, z4], + + [ x3, y2, z1], + [-x3, y2, z1], + [-x2, y2, z2], + [-x6, y2, z4], + [-x1, y2, -z1-0.05], + [ x1, y2, -z1-0.05], + [ x6, y2, z4], + [ x2, y2, z2], + ], + faces=[ + [0, 1, 2], + [0, 2, 5], + [2, 3, 4], + [2, 4, 5], + + [0, 13, 6], + [0, 6, 7], + [0, 7, 1], + [1, 7, 8], + [1, 8, 9], + [1, 9, 2], + [2, 9, 10], + [2, 10, 3], + [3, 10, 11], + [3, 11, 4], + [4, 11, 12], + [4, 12, 5], + [5, 12, 13], + [5, 13, 0], + + [14, 15, 16], + [14, 16, 19], + [16, 17, 18], + [16, 18, 19], + + [14, 27, 20], + [14, 20, 21], + [14, 21, 15], + [15, 21, 22], + [15, 22, 23], + [15, 23, 16], + [16, 23, 24], + [16, 24, 17], + [17, 24, 25], + [17, 25, 18], + [18, 25, 26], + [18, 26, 19], + [19, 26, 27], + [19, 27, 14], + + [6, 21, 20], + [6, 20, 7], + [7, 20, 27], + [7, 27, 8], + [8, 27, 26], + [8, 26, 9], + [9, 26, 25], + [9, 25, 10], + [10, 25, 24], + [10, 24, 11], + [11, 24, 23], + [11, 23, 12], + [12, 23, 22], + [12, 22, 13], + [13, 22, 21], + [13, 21, 6], + ] + ); +} +//!rail(l=30, w=10, h=10); + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/transforms.scad b/transforms.scad new file mode 100644 index 0000000..d980762 --- /dev/null +++ b/transforms.scad @@ -0,0 +1,523 @@ +////////////////////////////////////////////////////////////////////// +// Transformations, distributors, duplicators, and manipulators. +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +printer_slop = 0.20; // mm + + +////////////////////////////////////////////////////////////////////// +// Transformations. +////////////////////////////////////////////////////////////////////// + + +// Moves/translates children. +// x = X axis translation. +// y = Y axis translation. +// z = Z axis translation. +// Example: +// move([10,20,30]) sphere(r=1); +// move(y=10) sphere(r=1); +// move(x=10, z=20) sphere(r=1); +module move(a=[0,0,0], x=0, y=0, z=0) { + translate(a) translate([x,y,z]) children(); +} + + +// Moves/translates children the given amount along the X axis. +// Example: +// xmove(10) sphere(r=1); +module xmove(x=0) { translate([x,0,0]) children(); } + + +// Moves/translates children the given amount along the Y axis. +// Example: +// ymove(10) sphere(r=1); +module ymove(y=0) { translate([0,y,0]) children(); } + + +// Moves/translates children the given amount along the Z axis. +// Example: +// zmove(10) sphere(r=1); +module zmove(z=0) { translate([0,0,z]) children(); } + + +// Moves children left by the given amount in the -X direction. +// Example: +// left(10) sphere(r=1); +module left(x=0) { translate([-x,0,0]) children(); } + + +// Moves children right by the given amount in the +X direction. +// Example: +// right(10) sphere(r=1); +module right(x=0) { translate([x,0,0]) children(); } + + +// Moves children forward by x amount in the -Y direction. +// Example: +// forward(10) sphere(r=1); +module forward(y=0) { translate([0,-y,0]) children(); } +module fwd(y=0) { translate([0,-y,0]) children(); } + + +// Moves children back by the given amount in the +Y direction. +// Example: +// back(10) sphere(r=1); +module back(y=0) { translate([0,y,0]) children(); } + + +// Moves children down by the given amount in the -Z direction. +// Example: +// down(10) sphere(r=1); +module down(z=0) { translate([0,0,-z]) children(); } + + +// Moves children up by the given amount in the +Z direction. +// Example: +// up(10) sphere(r=1); +module up(z=0) { translate([0,0,z]) children(); } + + +// Rotates children around the Z axis by the given number of degrees. +// Example: +// xrot(90) cylinder(h=10, r=2, center=true); +module xrot(a=0) { rotate([a, 0, 0]) children(); } + + +// Rotates children around the Y axis by the given number of degrees. +// Example: +// yrot(90) cylinder(h=10, r=2, center=true); +module yrot(a=0) { rotate([0, a, 0]) children(); } + + +// Rotates children around the Z axis by the given number of degrees. +// Example: +// zrot(90) cube(size=[9,1,4], center=true); +module zrot(a=0) { rotate([0, 0, a]) children(); } + + +// Scales children by the given factor in the X axis. +// Example: +// xscale(3) sphere(r=100, center=true); +module xscale(x) {scale([x,1,1]) children();} + + +// Scales children by the given factor in the Y axis. +// Example: +// yscale(3) sphere(r=100, center=true); +module yscale(y) {scale([1,y,1]) children();} + + +// Scales children by the given factor in the Z axis. +// Example: +// zscale(3) sphere(r=100, center=true); +module zscale(z) {scale([1,1,z]) children();} + + +// Mirrors the children along the X axis, kind of like xscale(-1) +module xflip() mirror([1,0,0]) children(); + + +// Mirrors the children along the Y axis, kind of like yscale(-1) +module yflip() mirror([0,1,0]) children(); + + +// Mirrors the children along the Z axis, kind of like zscale(-1) +module zflip() mirror([0,0,1]) children(); + + +// Skews children on the X-Y plane, keeping constant in Z. +// xang = skew angle towards the X direction. +// yang = skew angle towards the Y direction. +// Examples: +// skew_xy(xang=15) cube(size=10); +// skew_xy(xang=15, yang=30) cube(size=10); +module skew_xy(xang=0, yang=0) +{ + multmatrix(m = [ + [1, 0, tan(xang), 0], + [0, 1, tan(yang), 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]) { + children(); + } +} +module zskew(xa=0,ya=0) skew_xy(xang=xa,yang=ya) children(); + + +// Skews children on the Y-Z plane, keeping constant in X. +// yang = skew angle towards the Y direction. +// zang = skew angle towards the Z direction. +// Examples: +// skew_yz(yang=15) cube(size=10); +// skew_yz(yang=15, zang=30) cube(size=10); +module skew_yz(yang=0, zang=0) +{ + multmatrix(m = [ + [1, 0, 0, 0], + [tan(yang), 1, 0, 0], + [tan(zang), 0, 1, 0], + [0, 0, 0, 1] + ]) { + children(); + } +} +module xskew(ya=0,za=0) skew_yz(yang=ya,zang=za) children(); + + +// Skews children on the X-Z plane, keeping constant in Y. +// xang = skew angle towards the X direction. +// zang = skew angle towards the Z direction. +// Examples: +// skew_xz(xang=15) cube(size=10); +// skew_xz(xang=15, zang=30) cube(size=10); +module skew_xz(xang=0, zang=0) +{ + multmatrix(m = [ + [1, tan(xang), 0, 0], + [0, 1, 0, 0], + [0, tan(zang), 1, 0], + [0, 0, 0, 1] + ]) { + children(); + } +} +module yskew(xa=0,za=0) skew_xz(xang=xa,zang=za) children(); + + + +////////////////////////////////////////////////////////////////////// +// Mutators. +////////////////////////////////////////////////////////////////////// + + +// Performs hull operations between consecutive pairs of children, +// then unions all of the hull results. +module chain_hull() { + union() { + if ($children == 1) { + children(); + } else if ($children > 1) { + for (i =[1:$children-1]) { + hull() { + children(i-1); + children(i); + } + } + } + } +} + + + +////////////////////////////////////////////////////////////////////// +// Duplicators and Distributers. +////////////////////////////////////////////////////////////////////// + + +// Makes a copy of the children, mirrored across the given axes. +// v = The normal vector of the plane to mirror across. +// Example: +// mirror_copy([1,-1,0]) yrot(30) cylinder(h=10, r=1, center=true); +module mirror_copy(v=[0,0,1]) +{ + union() { + children(); + mirror(v) children(); + } +} +module xflip_copy() {children(); mirror([1,0,0]) children();} +module yflip_copy() {children(); mirror([0,1,0]) children();} +module zflip_copy() {children(); mirror([0,0,1]) children();} + + +// Given a number of euller angles, rotates copies of the given children to each of those angles. +// Example: +// rot_copies(rots=[[0,0,0],[45,0,0],[0,45,120],[90,-45,270]]) +// translate([6,0,0]) cube(size=[9,1,4], center=true); +module rot_copies(rots=[[0,0,0]]) +{ + for (rot = rots) rotate(rot) children(); +} + + +// Given an array of angles, rotates copies of the children to each of those angles around the X axis. +// rots = Optional array of angles, in degrees, to make copies at. +// count = Optional number of evenly distributed copies, rotated around a circle. +// offset = Angle offset in degrees, for use with count. +// Example: +// xrot_copies(rots=[0,15,30,60,120,240]) translate([0,6,0]) cube(size=[4,9,1], center=true); +// xrot_copies(count=6, offset=15) translate([0,6,0]) cube(size=[4,9,1], center=true); +module xrot_copies(rots=[0], offset=0, count=undef) +{ + if (count != undef) { + for (i = [0 : count-1]) { + a = (i / count) * 360.0; + rotate([a+offset, 0, 0]) { + children(); + } + } + } else { + for (a = rots) { + rotate([a+offset, 0, 0]) { + children(); + } + } + } +} + + +// Given an array of angles, rotates copies of the children to each of those angles around the Y axis. +// rots = Optional array of angles, in degrees, to make copies at. +// count = Optional number of evenly distributed copies, rotated around a circle. +// offset = Angle offset in degrees, for use with count. +// Example: +// yrot_copies(rots=[0,15,30,60,120,240]) translate([6,0,0]) cube(size=[9,4,1], center=true); +// yrot_copies(count=6, offset=15) translate([6,0,0]) cube(size=[9,4,1], center=true); +module yrot_copies(rots=[0], offset=0, count=undef) +{ + if (count != undef) { + for (i = [0 : count-1]) { + a = (i / count) * 360.0; + rotate([0, a+offset, 0]) { + children(); + } + } + } else { + for (a = rots) { + rotate([0, a+offset, 0]) { + children(); + } + } + } +} + + +// Given an array of angles, rotates copies of the children to each of those angles around the Z axis. +// rots = Optional array of angles, in degrees, to make copies at. +// count = Optional number of evenly distributed copies, rotated around a circle. +// offset = Angle offset in degrees for first copy. +// Example: +// zrot_copies(rots=[0,15,30,60,120,240]) translate([6,0,0]) cube(size=[9,1,4], center=true); +// zrot_copies(count=6, offset=15) translate([6,0,0]) cube(size=[9,1,4], center=true); +module zrot_copies(rots=[0], offset=0, count=undef) +{ + if (count != undef) { + for (i = [0 : count-1]) { + a = (i / count) * 360.0; + rotate([0, 0, a+offset]) { + children(); + } + } + } else { + for (a = rots) { + rotate([0, 0, a+offset]) { + children(); + } + } + } +} + + +// Makes copies of the given children at each of the given offsets. +// offsets = array of XYZ offset vectors. Default [[0,0,0]] +// Example: +// translate_copies([[-5,-5,0], [5,-5,0], [0,-5,7], [0,5,0]]) +// sphere(r=3,center=true); +module translate_copies(offsets=[[0,0,0]]) +{ + for (off = offsets) translate(off) children(); +} +module place_copies(a=[[0,0,0]]) {translate_copies(a) children();} + + +// Evenly distributes n duplicate children along an XYZ line. +// p1 = starting point of line. (Default: [0,0,0]) +// p2 = ending point of line. (Default: [10,0,0]) +// n = number of copies to distribute along the line. (Default: 2) +// Examples: +// line_of(p1=[0,0,0], p2=[-10,15,20], n=5) cube(size=[3,1,1],center=true); +// +module line_of(p1=[0,0,0], p2=[10,0,0], n=2) +{ + delta = (p2 - p1) / (n-1); + for (i = [0:n-1]) translate(p1+delta*i) children(); +} +module spread(p1,p2,n=3) {line_of(p1,p2,n) children();} + + +// Evenly distributes n duplicate children around an ovoid arc on the XY plane. +// n = number of copies to distribute around the circle. (Default: 6) +// r = radius of circle (Default: 1) +// rx = radius of ellipse on X axis. Used instead of r. +// ry = radius of ellipse on Y axis. Used instead of r. +// d = diameter of circle. (Default: 2) +// dx = diameter of ellipse on X axis. Used instead of d. +// dy = diameter of ellipse on Y axis. Used instead of d. +// rot = whether to rotate the copied children. (Default: false) +// sa = starting angle. (Default: 0.0) +// ea = ending angle. Will distribute copies CCW from sa to ea. (Default: 360.0) +// Examples: +// arc_of(d=8,n=5) +// cube(size=[3,1,1],center=true); +// arc_of(r=10,n=12,rot=true) +// cube(size=[3,1,1],center=true); +// arc_of(rx=15,ry=10,n=12,rot=true) +// cube(size=[3,1,1],center=true); +// arc_of(r=10,n=5,rot=true,sa=30.0,ea=150.0) +// cube(size=[3,1,1],center=true); +// +module arc_of( + n=6, + r=1, rx=undef, ry=undef, + d=undef, dx=undef, dy=undef, + sa=0.0, ea=360.0, + rot=false +) { + r = (d == undef)?r:(d/2.0); + rx = (dx == undef)?rx:(dx/2.0); + ry = (dy == undef)?rx:(dy/2.0); + rx = (rx == undef)?r:rx; + ry = (ry == undef)?r:ry; + sa = ((sa % 360.0) + 360.0) % 360.0; // make 0 < ang < 360 + ea = ((ea % 360.0) + 360.0) % 360.0; // make 0 < ang < 360 + n = (abs(ea-sa)<0.01)?(n+1):n; + delt = (((ea<=sa)?360.0:0)+ea-sa)/(n-1); + for (i = [0:n-1]) { + ang = sa + (i * delt); + translate([cos(ang)*rx, sin(ang)*ry, 0]) { + zrot(rot? atan2(sin(ang)*ry,cos(ang)*rx) : 0) { + children(); + } + } + } +} + + +module xring(n=2,r=0,rot=true) {if (n>0) for (i=[0:n-1]) {a=i*360/n; xrot(a) back(r) xrot(rot?0:-a) children();}} +module yring(n=2,r=0,rot=true) {if (n>0) for (i=[0:n-1]) {a=i*360/n; yrot(a) right(r) yrot(rot?0:-a) children();}} +module zring(n=2,r=0,rot=true) {if (n>0) for (i=[0:n-1]) {a=i*360/n; zrot(a) right(r) zrot(rot?0:-a) children();}} + + +// Spreads out n copies of the given children along the X axis. +// spacing = spacing between copies. (Default: 1.0) +// n = Number of copies to spread out. (Default: 2) +// Examples: +// xspread(25) sphere(1); +// xspread(25,3) sphere(1) +// xspread(25, n=3) sphere(1) +// xspread(spacing=20, n=4) sphere(1) +module xspread(spacing=1,n=2) for (i=[0:n-1]) right((i-(n-1)/2.0)*spacing) children(); + + +// Spreads out n copies of the given children along the Y axis. +// spacing = spacing between copies. (Default: 1.0) +// n = Number of copies to spread out. (Default: 2) +// Examples: +// yspread(25) sphere(1); +// yspread(25,3) sphere(1) +// yspread(25, n=3) sphere(1) +// yspread(spacing=20, n=4) sphere(1) +module yspread(spacing=1,n=2) for (i=[0:n-1]) back((i-(n-1)/2.0)*spacing) children(); + + +// Spreads out n copies of the given children along the Z axis. +// spacing = spacing between copies. (Default: 1.0) +// n = Number of copies to spread out. (Default: 2) +// Examples: +// zspread(25) sphere(1); +// zspread(25,3) sphere(1) +// zspread(25, n=3) sphere(1) +// zspread(spacing=20, n=4) sphere(1) +module zspread(spacing=1,n=2) for (i=[0:n-1]) up((i-(n-1)/2.0)*spacing) children(); + + +// Makes a 3D grid of duplicate children. +// xa = array or range of X-axis values to offset by. (Default: [0]) +// ya = array or range of Y-axis values to offset by. (Default: [0]) +// za = array or range of Z-axis values to offset by. (Default: [0]) +// count = Optional number of copies to have per axis. (Default: none) +// spacing = spacing of copies per axis. Use with count. (Default: 0) +// Examples: +// grid_of(xa=[0,2,3,5],ya=[3:5],za=[-4:2:6]) sphere(r=0.5,center=true); +// grid_of(ya=[-6:3:6],za=[4,7]) sphere(r=1,center=true); +// grid_of(count=3, spacing=10) sphere(r=1,center=true); +// grid_of(count=[3, 1, 2], spacing=10) sphere(r=1,center=true); +// grid_of(count=[3, 4], spacing=[10, 8]) sphere(r=1,center=true); +// grid_of(count=[3, 4, 2], spacing=[10, 8, 5]) sphere(r=1,center=true, $fn=24); +module grid_of(xa=[0], ya=[0], za=[0], count=[], spacing=[]) +{ + count = (len(count) == undef)? [count,1,1] : + ((len(count) == 1)? [count[0], 1, 1] : + ((len(count) == 2)? [count[0], count[1], 1] : + ((len(count) == 3)? count : undef))); + + spacing = (len(spacing) == undef)? [spacing,spacing,spacing] : + ((len(spacing) == 1)? [spacing[0], 0, 0] : + ((len(spacing) == 2)? [spacing[0], spacing[1], 0] : + ((len(spacing) == 3)? spacing : undef))); + + if (count != undef && spacing != undef) { + for (x = [-(count[0]-1)/2 : (count[0]-1)/2 + 0.1]) { + for (y = [-(count[1]-1)/2 : (count[1]-1)/2 + 0.1]) { + for (z = [-(count[2]-1)/2 : (count[2]-1)/2 + 0.1]) { + translate([x*spacing[0], y*spacing[1], z*spacing[2]]) { + children(); + } + } + } + } + } else { + for (xoff = xa) { + for (yoff = ya) { + for (zoff = za) { + translate([xoff,yoff,zoff]) { + children(); + } + } + } + } + } +} + + +module top_half (s=100) difference() {children(); down(s/2) cube(s, center=true);} +module bottom_half(s=100) difference() {children(); up(s/2) cube(s, center=true);} +module left_half (s=100) difference() {children(); right(s/2) cube(s, center=true);} +module right_half (s=100) difference() {children(); left(s/2) cube(s, center=true);} +module front_half (s=100) difference() {children(); back(s/2) cube(s, center=true);} +module back_half (s=100) difference() {children(); fwd(s/2) cube(s, center=true);} + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/wiring.scad b/wiring.scad new file mode 100644 index 0000000..61e454c --- /dev/null +++ b/wiring.scad @@ -0,0 +1,106 @@ +////////////////////////////////////////////////////////////////////// +// Rendering for wiring bundles +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +include +include +include + + +// Returns an array of 1 or 6 points that form a ring, based on wire diam and ring level. +// Level 0 returns a single point at 0,0. All greater levels return 6 points. +function hex_offset_ring(wirediam, lev=0) = + (lev == 0)? [[0,0]] : [ + for ( + sideang = [0:60:359.999], + sidewire = [1:lev] + ) [ + lev*wirediam*cos(sideang)+sidewire*wirediam*cos(sideang+120), + lev*wirediam*sin(sideang)+sidewire*wirediam*sin(sideang+120) + ] + ]; + + +// Returns an array of 2D centerpoints for each of a bundle of wires of given diameter. +// The lev and arr variables are used for internal recursion. +function hex_offsets(wires, wirediam, lev=0, arr=[]) = + (len(arr) >= wires)? arr : + hex_offsets( + wires=wires, + wirediam=wirediam, + lev=lev+1, + arr=concat(arr, hex_offset_ring(wirediam, lev=lev)) + ); + + +// Returns a 3D object representing a bundle of wires that follow a given path, +// with the corners filleted to a given radius. There are 17 base wire colors. +// If you have more than 17 wires, colors will get re-used. +// Arguments: +// path: The 3D polyline path that the wire bundle should follow. +// wires: The number of wires in the wiring bundle. +// wirediam: The diameter of each wire in the bundle. +// fillet: The radius that the path corners will be filleted to. +// wirenum: The first wire's offset into the color table. +// bezsteps: The corner fillets in the path will be converted into this number of segments. +// Usage: +// wiring([[50,0,-50], [50,50,-50], [0,50,-50], [0,0,-50], [0,0,0]], fillet=10, wires=13); +module wiring(path, wires, wirediam=2, fillet=10, wirenum=0, bezsteps=12) { + vect = path[1]-path[0]; + theta = atan2(vect[1], vect[0]); + xydist = hypot(vect[1], vect[0]); + phi = atan2(vect[2],xydist); + colors = [ + [0.2, 0.2, 0.2], [1.0, 0.2, 0.2], [0.0, 0.8, 0.0], [1.0, 1.0, 0.2], + [0.3, 0.3, 1.0], [1.0, 1.0, 1.0], [0.7, 0.5, 0.0], [0.5, 0.5, 0.5], + [0.2, 0.9, 0.9], [0.8, 0.0, 0.8], [0.0, 0.6, 0.6], [1.0, 0.7, 0.7], + [1.0, 0.5, 1.0], [0.5, 0.6, 0.0], [1.0, 0.7, 0.0], [0.7, 1.0, 0.5], + [0.6, 0.6, 1.0], + ]; + offsets = hex_offsets(wires, wirediam); + bezpath = fillet_path(path, fillet); + poly = simplify3d_path(path3d(bezier_polyline(bezpath, bezsteps))); + n = max(segs(wirediam), 8); + r = wirediam/2; + for (i = [0:wires-1]) { + extpath = [for (a = [0:(360.0/n):360]) [r*cos(a), r*sin(a)] + offsets[i]]; + roty = matrix3_yrot(90-phi); + rotz = matrix3_zrot(theta); + color(colors[(i+wirenum)%len(colors)]) { + extrude_2dpath_along_3dpath(extpath, poly); + } + } +} + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap