From 5edbb339bf088bc4f0e5de3ff36fd558988a45d0 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 11 Jun 2019 19:26:06 -0700 Subject: [PATCH 01/10] Added list_set, list_increasing, list_decreasing --- arrays.scad | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/arrays.scad b/arrays.scad index 28f46073..e694cb87 100644 --- a/arrays.scad +++ b/arrays.scad @@ -150,6 +150,44 @@ function list_range(n=undef, s=0, e=undef, step=1) = function reverse(list) = [ for (i = [len(list)-1 : -1 : 0]) list[i] ]; +// Function: list_set() +// Usage: +// list_set(indices, values, list, [dflt], [minlen]) +// Description: +// Takes the input list and returns a new list such that `list[indices[i]] = values[i]` for all of +// the (index,value) pairs supplied. If you supply `indices` that are beyond the length of the list +// then the list is extended and filled in with the `dflt` value. If you set `minlen` then the list is +// lengthed, if necessary, by padding with `dflt` to that length. The `indices` list can be in any +// order but run time will be (much) faster for long lists if it is already sorted. Reptitions are +// not allowed. +// Arguments: +// indices = List of indices into `list` to set. +// values = List of values to set. +// list = List to set items in. +// dflt = Default value to store in sparse skipped indices. +// minlen = Minimum length to expand list to. +function list_set(indices,values,list=[],dflt=0,minlen=0) = + !is_list(indices) ? list_set(list,[indices],[values],dflt) : + assert(len(indices)==len(values),"Index list and value list must have the same length") + let( + sortind = list_increasing(indices) ? list_range(len(indices)) : sortidx(indices), + lastind = indices[select(sortind,-1)] + ) + concat( + [for(j=[0:1:indices[sortind[0]]-1]) j>=len(list) ? dflt : list[j]], + [values[sortind[0]]], + [for(i=[1:1:len(sortind)-1]) each + assert(indices[sortind[i]]!=indices[sortind[i-1]],"Repeated index") + concat( + [for(j=[1+indices[sortind[i-1]]:1:indices[sortind[i]]-1]) j>=len(list) ? dflt : list[j]], + [values[sortind[i]]] + ) + ], + slice(list,1+lastind, len(list)), + replist(dflt, minlen-lastind-1) + ); + + // Function: list_remove() // Usage: // list_remove(list, elements) @@ -158,9 +196,16 @@ function reverse(list) = [ for (i = [len(list)-1 : -1 : 0]) list[i] ]; // Arguments: // list = The list to remove items from. // elements = The list of indexes of items to remove. -function list_remove(list, elements) = [ - for (i = [0:1:len(list)-1]) if (!search(i, elements)) list[i] -]; +function list_remove(list, elements) = + !is_list(elements) ? list_remove(list,[elements]) : + let( sortind = list_increasing(elements) ? list_range(len(elements)) : sortidx(elements), + lastind = elements[select(sortind,-1)] + ) + assert(lastind=len(list)-1 ? true : false); + + +// True if the list is (non-strictly) decreasing +function list_decreasing(list,ind=0) = ind < len(list)-1 && list[ind]>=list[ind+1] ? list_increasing(list,ind+1) : + (ind>=len(list)-1 ? true : false); + + // Function: list_shortest() // Description: // Returns the length of the shortest sublist in a list of lists. From aa429aa1a19c9b8e258001052e8e6f44f9b4f7cb Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 11 Jun 2019 19:27:04 -0700 Subject: [PATCH 02/10] Bugfixes for rotate_points3d --- coords.scad | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/coords.scad b/coords.scad index a906700d..4be23e1e 100644 --- a/coords.scad +++ b/coords.scad @@ -91,18 +91,20 @@ function scale_points(pts, v=[0,0,0], cp=[0,0,0]) = [for (pt = pts) [for (i = [0 // Function: rotate_points2d() // Usage: -// rotate_points2d(pts, ang, [cp]); +// rotate_points2d(pts, a, [cp]); // Description: // Rotates each 2D point in an array by a given amount, around an optional centerpoint. // Arguments: // pts = List of 3D points to rotate. -// ang = Angle to rotate by. +// a = Angle to rotate by. // cp = 2D Centerpoint to rotate around. Default: `[0,0]` -function rotate_points2d(pts, ang, cp=[0,0]) = - approx(ang,0)? pts : +function rotate_points2d(pts, a, cp=[0,0]) = + approx(a,0)? pts : let( - m = affine2d_zrot(ang) - ) [for (pt = pts) m*point3d(pt-cp)+cp]; + cp = point2d(cp), + pts = path2d(pts), + m = affine2d_zrot(a) + ) [for (pt = pts) point2d(m*concat(pt-cp, [1])+cp)]; // Function: rotate_points3d() @@ -125,9 +127,11 @@ function rotate_points3d(pts, a=0, v=undef, cp=[0,0,0], from=undef, to=undef, re (is_undef(from) && (a==0 || a==[0,0,0]))? pts : let ( from = is_undef(from)? undef : (from / norm(from)), - to = is_undef(to)? undef : (to / norm(to)) + to = is_undef(to)? undef : (to / norm(to)), + cp = point3d(cp), + pts2 = path3d(pts) ) - (!is_undef(from) && approx(from,to))? pts : + (!is_undef(from) && approx(from,to))? pts2 : let ( mrot = reverse? ( !is_undef(from)? ( @@ -151,7 +155,6 @@ function rotate_points3d(pts, a=0, v=undef, cp=[0,0,0], from=undef, to=undef, re ang = vector_angle(from, to), v = vector_axis(from, to) ) - echo("EEE",from=from,to=to,ang=ang,v=v,a=a) affine3d_rot_by_axis(v, ang) * affine3d_rot_by_axis(from, a) ) : !is_undef(v)? ( affine3d_rot_by_axis(v, a) @@ -163,7 +166,7 @@ function rotate_points3d(pts, a=0, v=undef, cp=[0,0,0], from=undef, to=undef, re ), m = affine3d_translate(cp) * mrot * affine3d_translate(-cp) ) - [for (pt = pts) point3d(m*concat(point3d(pt),[1]))]; + [for (pt = pts2) point3d(m*concat(pt, fill=1))]; From 48478434ff08c6e8900d1ddf92e2b2c01eed6b2e Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 11 Jun 2019 19:28:04 -0700 Subject: [PATCH 03/10] Bugfixes for rot() --- transforms.scad | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transforms.scad b/transforms.scad index 2c04e2f0..258c5f45 100644 --- a/transforms.scad +++ b/transforms.scad @@ -284,9 +284,9 @@ function rot(a=0, v=undef, cp=undef, from=undef, to=undef, reverse=false, p=unde rot(a=a, v=v, cp=cp, from=from, to=to, reverse=reverse, p=[p], planar=planar)[0] ) : ( planar? ( - is_undef(from)? rotate_points2d(p, a=ang*rev, cp=cp) : ( + is_undef(from)? rotate_points2d(p, a=a*rev, cp=cp) : ( approx(from,to)? p : - rotate_points2d(p, ang=vector_angle(from,to)*sign(vector_axis(from,to)[2])*rev, cp=cp) + rotate_points2d(p, a=vector_angle(from,to)*sign(vector_axis(from,to)[2])*rev, cp=cp) ) ) : ( rotate_points3d(p, a=a, v=v, cp=(is_undef(cp)? [0,0,0] : cp), from=from, to=to, reverse=reverse) From f7d650f251bd81c551bbff4faa0fc42443c9c51b Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 11 Jun 2019 19:29:39 -0700 Subject: [PATCH 04/10] Added bevel_gear() --- involute_gears.scad | 638 ++++++++++++++++++++++++++++++-------------- 1 file changed, 433 insertions(+), 205 deletions(-) diff --git a/involute_gears.scad b/involute_gears.scad index eb5259a7..892f27e4 100644 --- a/involute_gears.scad +++ b/involute_gears.scad @@ -47,194 +47,255 @@ // Function: circular_pitch() // Description: Get tooth density expressed as "circular pitch". // Arguments: -// mm_per_tooth = Distance between teeth around the pitch circle, in mm. -function circular_pitch(mm_per_tooth=5) = mm_per_tooth; +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +function circular_pitch(pitch=5) = pitch; // Function: diametral_pitch() // Description: Get tooth density expressed as "diametral pitch". // Arguments: -// mm_per_tooth = Distance between teeth around the pitch circle, in mm. -function diametral_pitch(mm_per_tooth=5) = PI / mm_per_tooth; +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +function diametral_pitch(pitch=5) = PI / pitch; // Function: module_value() // Description: Get tooth density expressed as "module" or "modulus" in millimeters // Arguments: -// mm_per_tooth = Distance between teeth around the pitch circle, in mm. -function module_value(mm_per_tooth=5) = mm_per_tooth / PI; +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +function module_value(pitch=5) = pitch / PI; // Function: adendum() // Description: The height of the gear tooth above the pitch radius. // Arguments: -// mm_per_tooth = Distance between teeth around the pitch circle, in mm. -function adendum(mm_per_tooth=5) = module_value(mm_per_tooth); +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +function adendum(pitch=5) = module_value(pitch); // Function: dedendum() // Description: The depth of the gear tooth valley, below the pitch radius. // Arguments: -// mm_per_tooth = Distance between teeth around the pitch circle, in mm. +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. // clearance = If given, sets the clearance between meshing teeth. -function dedendum(mm_per_tooth=5, clearance=undef) = - (clearance==undef)? (1.25 * module_value(mm_per_tooth)) : (module_value(mm_per_tooth) + clearance); +function dedendum(pitch=5, clearance=undef) = + (clearance==undef)? (1.25 * module_value(pitch)) : (module_value(pitch) + clearance); // Function: pitch_radius() // Description: Calculates the pitch radius for the gear. // Arguments: -// mm_per_tooth = Distance between teeth around the pitch circle, in mm. -// number of teeth = The number of teeth on the gear. -function pitch_radius(mm_per_tooth=5, number_of_teeth=11) = - mm_per_tooth * number_of_teeth / PI / 2; +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = The number of teeth on the gear. +function pitch_radius(pitch=5, teeth=11) = + pitch * teeth / PI / 2; // Function: outer_radius() // Description: // Calculates the outer radius for the gear. The gear fits entirely within a cylinder of this radius. // Arguments: -// mm_per_tooth = Distance between teeth around the pitch circle, in mm. -// number of teeth = The number of teeth on the gear. +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = The number of teeth on the gear. // clearance = If given, sets the clearance between meshing teeth. // interior = If true, calculate for an interior gear. -function outer_radius(mm_per_tooth=5, number_of_teeth=11, clearance=undef, interior=false) = - pitch_radius(mm_per_tooth, number_of_teeth) + - (interior? dedendum(mm_per_tooth, clearance) : adendum(mm_per_tooth)); +function outer_radius(pitch=5, teeth=11, clearance=undef, interior=false) = + pitch_radius(pitch, teeth) + + (interior? dedendum(pitch, clearance) : adendum(pitch)); // Function: root_radius() // Description: // Calculates the root radius for the gear, at the base of the dedendum. // Arguments: -// mm_per_tooth = Distance between teeth around the pitch circle, in mm. -// number of teeth = The number of teeth on the gear. +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = The number of teeth on the gear. // clearance = If given, sets the clearance between meshing teeth. // interior = If true, calculate for an interior gear. -function root_radius(mm_per_tooth=5, number_of_teeth=11, clearance=undef, interior=false) - = pitch_radius(mm_per_tooth, number_of_teeth) - - (interior? adendum(mm_per_tooth) : dedendum(mm_per_tooth, clearance)); +function root_radius(pitch=5, teeth=11, clearance=undef, interior=false) = + pitch_radius(pitch, teeth) - + (interior? adendum(pitch) : dedendum(pitch, clearance)); // Function: base_radius() // Description: Get the base circle for involute teeth. // Arguments: -// mm_per_tooth = Distance between teeth around the pitch circle, in mm. -// number_of_teeth = The number of teeth on the gear. -// pressure_angle = Pressure angle in degrees. Controls how straight or bulged the tooth sides are. -function base_radius(mm_per_tooth=5, number_of_teeth=11, pressure_angle=28) - = pitch_radius(mm_per_tooth, number_of_teeth) * cos(pressure_angle); +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = The number of teeth on the gear. +// PA = Pressure angle in degrees. Controls how straight or bulged the tooth sides are. +function base_radius(pitch=5, teeth=11, PA=28) = + pitch_radius(pitch, teeth) * cos(PA); +// Function bevel_pitch_angle() +// Usage: +// bevel_pitch_angle(teeth, mate_teeth, [drive_angle]); +// Description: +// Returns the correct pitch angle (bevelang) for a bevel gear with a given number of tooth, that is +// matched to another bevel gear with a (possibly different) number of teeth. +// Arguments: +// teeth = Number of teeth that this gear has. +// mate_teeth = Number of teeth that the matching gear has. +// drive_angle = Angle between the drive shafts of each gear. Usually 90º. +function bevel_pitch_angle(teeth, mate_teeth, drive_angle=90) = + atan(sin(drive_angle)/((mate_teeth/teeth)+cos(drive_angle))); + + +function _gear_polar(r,t) = r*[sin(t),cos(t)]; +function _gear_iang(r1,r2) = sqrt((r2/r1)*(r2/r1) - 1)/PI*180 - acos(r1/r2); //unwind a string this many degrees to go from radius r1 to radius r2 +function _gear_q6(b,s,t,d) = _gear_polar(d,s*(_gear_iang(b,d)+t)); //point at radius d on the involute curve +function _gear_q7(f,r,b,r2,t,s) = _gear_q6(b,s,t,(1-f)*max(b,r)+f*r2); //radius a fraction f up the curved side of the tooth + // Section: Modules -// Module: gear_tooth_profile() +// Function&Module: gear_tooth_profile() // Description: -// Creates the 2D profile for an individual gear tooth. +// When called as a function, returns the 2D profile path for an individual gear tooth. +// When called as a module, creates the 2D profile shape for an individual gear tooth. // Arguments: -// mm_per_tooth = This is the "circular pitch", the circumference of the pitch circle divided by the number of teeth -// number_of_teeth = Total number of teeth along the rack -// pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = Total number of teeth along the rack +// PA = Controls how straight or bulged the tooth sides are. In degrees. // backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle -// bevelang = Angle of beveled gear face. // clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) // interior = If true, create a mask for difference()ing from something else. +// valleys = If true, add the valley bottoms on either side of the tooth. // Example(2D): -// gear_tooth_profile(mm_per_tooth=5, number_of_teeth=20, pressure_angle=20); -module gear_tooth_profile( - mm_per_tooth = 3, - number_of_teeth = 11, - pressure_angle = 28, - backlash = 0.0, - bevelang = 0.0, - clearance = undef, - interior = false -) { - function polar(r,theta) = r*[sin(theta), cos(theta)]; //convert polar to cartesian coordinates - function iang(r1,r2) = sqrt((r2/r1)*(r2/r1) - 1)/PI*180 - acos(r1/r2); //unwind a string this many degrees to go from radius r1 to radius r2 - function q7(f,r,b,r2,t,s) = q6(b,s,t,(1-f)*max(b,r)+f*r2); //radius a fraction f up the curved side of the tooth - function q6(b,s,t,d) = polar(d,s*(iang(b,d)+t)); //point at radius d on the involute curve +// gear_tooth_profile(pitch=5, teeth=20, PA=20); +// Example(2D): +// gear_tooth_profile(pitch=5, teeth=20, PA=20, valleys=true); +function gear_tooth_profile( + pitch = 3, + teeth = 11, + PA = 28, + backlash = 0.0, + clearance = undef, + interior = false, + valleys = true +) = let( + p = pitch_radius(pitch, teeth), + c = outer_radius(pitch, teeth, clearance, interior), + r = root_radius(pitch, teeth, clearance, interior), + b = base_radius(pitch, teeth, PA), + t = pitch/2-backlash/2, //tooth thickness at pitch circle + k = -_gear_iang(b, p) - t/2/p/PI*180, //angle to where involute meets base circle on each side of tooth + kk = r0? [[0,0]] : [] + ) +) pts; + + module gear2d( - mm_per_tooth = 3, - number_of_teeth = 11, - teeth_to_hide = 0, - pressure_angle = 28, - clearance = undef, - backlash = 0.0, - bevelang = 0.0, - interior = false + pitch = 3, + teeth = 11, + hide = 0, + PA = 28, + clearance = undef, + backlash = 0.0, + interior = false ) { - r = root_radius(mm_per_tooth, number_of_teeth, clearance, interior); - ang = 360/number_of_teeth/2; - union() { - for (i = [0:1:number_of_teeth-teeth_to_hide-1] ) { - rotate(i*360/number_of_teeth) { - translate([0,r,0]) { - gear_tooth_profile( - mm_per_tooth = mm_per_tooth, - number_of_teeth = number_of_teeth, - pressure_angle = pressure_angle, - clearance = clearance, - backlash = backlash, - bevelang = bevelang, - interior = interior - ); - } - polygon([ - [-r*sin(ang), r*cos(ang)], - [0,0], - [r*sin(ang), r*cos(ang)] - ]); - } - } - } + polygon( + gear2d( + pitch = pitch, + teeth = teeth, + hide = hide, + PA = PA, + clearance = clearance, + backlash = backlash, + interior = interior + ) + ); } @@ -256,85 +317,252 @@ module gear2d( // 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 +// In order for two gears to mesh, they must have the same `pitch` +// and `PA` parameters. `pitch` gives the number // of millimeters of arc around the pitch circle covered by one tooth -// and one space between teeth. The `pressure_angle` controls how flat or +// and one space between teeth. The `PA` 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 +// The ratio 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. // Arguments: -// mm_per_tooth = This is the "circular pitch", the circumference of the pitch circle divided by the number of teeth -// number_of_teeth = Total number of teeth around the entire perimeter +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = Total number of teeth around the entire perimeter // thickness = Thickness of gear in mm -// hole_diameter = Diameter of the hole in the center, in mm -// teeth_to_hide = Number of teeth to delete to make this only a fraction of a circle -// pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. +// shaft_diam = Diameter of the hole in the center, in mm +// hide = Number of teeth to delete to make this only a fraction of a circle +// PA = Controls how straight or bulged the tooth sides are. In degrees. // clearance = Clearance gap at the bottom of the inter-tooth valleys. // backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle -// bevelang = Angle of beveled gear face. -// twist = Teeth rotate this many degrees from bottom of gear to top. 360 makes the gear a screw with each thread going around once. -// slices = Number of vertical layers to divide gear into. Useful for refining gears with `twist`. +// helical = Teeth rotate this many degrees from bottom of gear to top. 360 makes the gear a screw with each thread going around once. +// slices = Number of vertical layers to divide gear into. Useful for refining gears with `helical`. // scale = Scale of top of gear compared to bottom. Useful for making crown gears. // interior = If true, create a mask for difference()ing from something else. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` // Example: Spur Gear -// gear(mm_per_tooth=5, number_of_teeth=20, thickness=8, hole_diameter=5); +// gear(pitch=5, teeth=20, thickness=8, shaft_diam=5); // Example: Beveled Gear -// gear(mm_per_tooth=5, number_of_teeth=20, thickness=10*cos(45), hole_diameter=5, twist=-30, bevelang=45, slices=12, $fa=1, $fs=1); +// gear(pitch=5, teeth=20, thickness=10*cos(45), shaft_diam=5, helical=-30, slices=12, $fa=1, $fs=1); module gear( - mm_per_tooth = 3, - number_of_teeth = 11, - thickness = 6, - hole_diameter = 3, - teeth_to_hide = 0, - pressure_angle = 28, - clearance = undef, - backlash = 0.0, - bevelang = 0.0, - twist = undef, - slices = undef, - interior = false, - anchor = CENTER, - spin = 0, - orient = UP + pitch = 3, + teeth = 11, + PA = 28, + thickness = 6, + hide = 0, + shaft_diam = 3, + clearance = undef, + backlash = 0.0, + helical = 0, + slices = 2, + interior = false, + anchor = CENTER, + spin = 0, + orient = UP ) { - p = pitch_radius(mm_per_tooth, number_of_teeth); - c = outer_radius(mm_per_tooth, number_of_teeth, clearance, interior); - r = root_radius(mm_per_tooth, number_of_teeth, clearance, interior); - p2 = p - (thickness*tan(bevelang)); + p = pitch_radius(pitch, teeth); + c = outer_radius(pitch, teeth, clearance, interior); + r = root_radius(pitch, teeth, clearance, interior); + twist = atan2(thickness*tan(helical),p); orient_and_anchor([p, p, thickness], orient, anchor, spin=spin, geometry="cylinder", chain=true) { difference() { - linear_extrude(height=thickness, center=true, convexity=10, twist=twist, scale=p2/p, slices=slices) { + linear_extrude(height=thickness, center=true, convexity=10, twist=twist) { gear2d( - mm_per_tooth = mm_per_tooth, - number_of_teeth = number_of_teeth, - teeth_to_hide = teeth_to_hide, - pressure_angle = pressure_angle, - clearance = clearance, - backlash = backlash, - bevelang = bevelang, - interior = interior + pitch = pitch, + teeth = teeth, + PA = PA, + hide = hide, + clearance = clearance, + backlash = backlash, + interior = interior ); } - if (hole_diameter > 0) { - cylinder(h=2*thickness+1, r=hole_diameter/2, center=true); + if (shaft_diam > 0) { + cylinder(h=2*thickness+1, r=shaft_diam/2, center=true); } - if (bevelang != 0) { - h = (c-r)*sin(bevelang); - translate([0,0,-thickness/2]) { - difference() { - cube([2*c/cos(bevelang),2*c/cos(bevelang),2*h], center=true); - cylinder(h=h, r1=r, r2=c, center=false); + } + children(); + } +} + + + +// Module: bevel_gear() +// Description: +// Creates a (potentially spiral) bevel gear. +// The module `bevel_gear()` gives an bevel 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 `bevel_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 `outer_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 `pitch` +// and `PA` parameters. `pitch` gives the number +// of millimeters of arc around the pitch circle covered by one tooth +// and one space between teeth. The `PA` 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 `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. +// Arguments: +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = Total number of teeth around the entire perimeter +// face_width = Width of the toothed surface in mm, from inside to outside. +// shaft_diam = Diameter of the hole in the center, in mm +// hide = Number of teeth to delete to make this only a fraction of a circle +// PA = Controls how straight or bulged the tooth sides are. In degrees. +// clearance = Clearance gap at the bottom of the inter-tooth valleys. +// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle +// bevelang = Angle of beveled gear face. +// spiral = Radius of spiral arc for teeth, if given. If 0, then gear will not be spiral. +// slices = Number of vertical layers to divide gear into. Useful for refining gears with `spiral`. +// scale = Scale of top of gear compared to bottom. Useful for making crown gears. +// interior = If true, create a mask for difference()ing from something else. +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` +// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` +// Example: Beveled Gear +// bevel_gear(pitch=5, teeth=20, face_width=10, shaft_diam=5, spiral=-30, bevelang=45, slices=12, $fa=1, $fs=1); +module bevel_gear( + pitch = 3, + teeth = 11, + PA = 20, + face_width = 6, + bevelang = 45, + hide = 0, + shaft_diam = 3, + clearance = undef, + backlash = 0.0, + spiral_rad = 0, + spiral_ang = 0, + slices = 2, + interior = false, + anchor = CENTER, + spin = 0, + orient = UP +) { + thickness = face_width * cos(bevelang); + slices = spiral_rad==0? 1 : slices; + spiral_rad = spiral_rad==0? 10000 : spiral_rad; + p1 = pitch_radius(pitch, teeth); + r1 = root_radius(pitch, teeth, clearance, interior); + c1 = outer_radius(pitch, teeth, clearance, interior); + dx = thickness * tan(bevelang); + dy = (p1-r1) * sin(bevelang); + scl = (p1-dx)/p1; + p2 = pitch_radius(pitch*scl, teeth); + r2 = root_radius(pitch*scl, teeth, clearance, interior); + c2 = outer_radius(pitch*scl, teeth, clearance, interior); + slice_u = 1/slices; + Rm = (p1+p2)/2; + H = spiral_rad * cos(spiral_ang); + V = Rm - abs(spiral_rad) * sin(spiral_ang); + spiral_cp = [H,V,0]; + S = norm(spiral_cp); + theta_r = acos((S*S+spiral_rad*spiral_rad-p1*p1)/(2*S*spiral_rad)) - acos((S*S+spiral_rad*spiral_rad-p2*p2)/(2*S*spiral_rad)); + theta_ro = acos((S*S+spiral_rad*spiral_rad-p1*p1)/(2*S*spiral_rad)) - acos((S*S+spiral_rad*spiral_rad-Rm*Rm)/(2*S*spiral_rad)); + theta_ri = theta_r - theta_ro; + extent_u = 2*(p2-r2)*tan(bevelang) / thickness; + slice_us = concat( + [for (u = [0:slice_u:1+extent_u]) u] + ); + lsus = len(slice_us); + vertices = concat( + [ + for (u=slice_us, tooth=[0:1:teeth-1]) let( + p = lerp(p1,p2,u), + r = lerp(r1,r2,u), + theta = lerp(-theta_ro, theta_ri, u), + profile = gear_tooth_profile( + pitch = pitch*(p/p1), + teeth = teeth, + PA = PA, + clearance = clearance, + backlash = backlash, + interior = interior, + valleys = false + ), + pp = rot(theta, cp=spiral_cp, p=[0,Rm,0]), + ang = atan2(pp.y,pp.x)-90, + pts = affine3d_apply(pts=profile, affines=[ + move([0,-p,0]), + rot([0,ang,0]), + rot([bevelang,0,0]), + move(pp), + rot(tooth*360/teeth), + move([0,0,thickness*u]) + ]) + ) each pts + ], [ + [0,0,-dy], [0,0,thickness] + ] + ); + lcnt = (len(vertices)-2)/lsus/teeth; + function _gv(layer,tooth,i) = ((layer*teeth)+(tooth%teeth))*lcnt+(i%lcnt); + function _lv(layer,i) = layer*teeth*lcnt+(i%(teeth*lcnt)); + faces = concat( + [ + for (sl=[0:1:lsus-2], i=[0:1:lcnt*teeth-1]) each [ + [_lv(sl,i), _lv(sl+1,i), _lv(sl,i+1)], + [_lv(sl+1,i), _lv(sl+1,i+1), _lv(sl,i+1)] + ] + ], [ + for (tooth=[0:1:teeth-1], i=[0:1:lcnt/2-1]) each [ + [_gv(0,tooth,i), _gv(0,tooth,i+1), _gv(0,tooth,lcnt-1-(i+1))], + [_gv(0,tooth,i), _gv(0,tooth,lcnt-1-(i+1)), _gv(0,tooth,lcnt-1-i)], + [_gv(lsus-1,tooth,i), _gv(lsus-1,tooth,lcnt-1-(i+1)), _gv(lsus-1,tooth,i+1)], + [_gv(lsus-1,tooth,i), _gv(lsus-1,tooth,lcnt-1-i), _gv(lsus-1,tooth,lcnt-1-(i+1))], + ] + ], [ + for (tooth=[0:1:teeth-1]) each [ + [len(vertices)-2, _gv(0,tooth,0), _gv(0,tooth,lcnt-1)], + [len(vertices)-2, _gv(0,tooth,lcnt-1), _gv(0,tooth+1,0)], + [len(vertices)-1, _gv(lsus-1,tooth,lcnt-1), _gv(lsus-1,tooth,0)], + [len(vertices)-1, _gv(lsus-1,tooth+1,0), _gv(lsus-1,tooth,lcnt-1)], + ] + ] + ); + orient_and_anchor([p1, p1, thickness], orient, anchor, spin=spin, size2=[p2,p2], geometry="cylinder", chain=true) { + union() { + difference() { + down(thickness/2) { + polyhedron(points=vertices, faces=faces, convexity=floor(teeth/2)); + } + if (shaft_diam > 0) { + cylinder(h=2*thickness+1, r=shaft_diam/2, center=true); + } + if (bevelang != 0) { + h = (c1-r1)/tan(45); + down(thickness/2+dy) { + difference() { + cube([2*c1/cos(45),2*c1/cos(45),2*h], center=true); + cylinder(h=h, r1=r1-0.5, r2=c1-0.5, center=false, $fn=teeth*4); + } + } + up(thickness/2-0.01) { + cylinder(h=(c2-r2)/tan(45)*5, r1=r2-0.5, r2=lerp(r2-0.5,c2-0.5,5), center=false, $fn=teeth*4); } } } @@ -347,14 +575,14 @@ module gear( // Module: rack() // Description: // 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`. +// rack can mesh with any gear that has the same `pitch` and +// `PA`. // Arguments: -// mm_per_tooth = This is the "circular pitch", the circumference of the pitch circle divided by the number of teeth -// number_of_teeth = Total number of teeth along the rack +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = Total number of teeth along the rack // thickness = Thickness of rack in mm (affects each tooth) // height = Height of rack in mm, from tooth top to back of rack. -// pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. +// PA = Controls how straight or bulged the tooth sides are. In degrees. // backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` @@ -371,24 +599,24 @@ module gear( // "dedendum-top" = At the base of the teeth, at the top of the rack. // "dedendum-bottom" = At the base of the teeth, at the bottom of the rack. // Example: -// rack(mm_per_tooth=5, number_of_teeth=10, thickness=5, height=5, pressure_angle=20); +// rack(pitch=5, teeth=10, thickness=5, height=5, PA=20); module rack( - mm_per_tooth = 5, - number_of_teeth = 20, - thickness = 5, - height = 10, - pressure_angle = 28, - backlash = 0.0, - clearance = undef, - anchor = CENTER, - spin = 0, - orient = UP + pitch = 5, + teeth = 20, + thickness = 5, + height = 10, + PA = 28, + backlash = 0.0, + clearance = undef, + anchor = CENTER, + spin = 0, + orient = UP ) { - a = adendum(mm_per_tooth); - d = dedendum(mm_per_tooth, clearance); - xa = a * sin(pressure_angle); - xd = d * sin(pressure_angle); - l = number_of_teeth * mm_per_tooth; + a = adendum(pitch); + d = dedendum(pitch, clearance); + xa = a * sin(PA); + xd = d * sin(PA); + l = teeth * pitch; anchors = [ anchorpt("adendum", [0,a,0], BACK), anchorpt("adendum-left", [-l/2,a,0], LEFT), @@ -402,20 +630,20 @@ module rack( anchorpt("dedendum-bottom", [0,-d,-thickness/2], DOWN), ]; orient_and_anchor([l, 2*abs(a-height), thickness], orient, anchor, spin=spin, anchors=anchors, chain=true) { - left((number_of_teeth-1)*mm_per_tooth/2) { + left((teeth-1)*pitch/2) { linear_extrude(height = thickness, center = true, convexity = 10) { - for (i = [0:1:number_of_teeth-1] ) { - translate([i*mm_per_tooth,0,0]) { + for (i = [0:1:teeth-1] ) { + translate([i*pitch,0,0]) { polygon( points=[ - [-1/2 * mm_per_tooth - 0.01, a-height], - [-1/2 * mm_per_tooth, -d], - [-1/4 * mm_per_tooth + backlash - xd, -d], - [-1/4 * mm_per_tooth + backlash + xa, a], - [ 1/4 * mm_per_tooth - backlash - xa, a], - [ 1/4 * mm_per_tooth - backlash + xd, -d], - [ 1/2 * mm_per_tooth, -d], - [ 1/2 * mm_per_tooth + 0.01, a-height], + [-1/2 * pitch - 0.01, a-height], + [-1/2 * pitch, -d], + [-1/4 * pitch + backlash - xd, -d], + [-1/4 * pitch + backlash + xa, a], + [ 1/4 * pitch - backlash - xa, a], + [ 1/4 * pitch - backlash + xd, -d], + [ 1/2 * pitch, -d], + [ 1/2 * pitch + 0.01, a-height], ] ); } @@ -438,22 +666,22 @@ n2 = 20; //green gear n3 = 5; //blue gear n4 = 20; //orange gear n5 = 8; //gray rack -mm_per_tooth = 9; //all meshing gears need the same mm_per_tooth (and the same pressure_angle) +pitch = 9; //all meshing gears need the same `pitch` (and the same `PA`) thickness = 6; hole = 3; height = 12; -d1 =pitch_radius(mm_per_tooth,n1); -d12=pitch_radius(mm_per_tooth,n1) + pitch_radius(mm_per_tooth,n2); -d13=pitch_radius(mm_per_tooth,n1) + pitch_radius(mm_per_tooth,n3); -d14=pitch_radius(mm_per_tooth,n1) + pitch_radius(mm_per_tooth,n4); +d1 =pitch_radius(pitch,n1); +d12=pitch_radius(pitch,n1) + pitch_radius(pitch,n2); +d13=pitch_radius(pitch,n1) + pitch_radius(pitch,n3); +d14=pitch_radius(pitch,n1) + pitch_radius(pitch,n4); -translate([ 0, 0, 0]) rotate([0,0, $t*360/n1]) color([1.00,0.75,0.75]) gear(mm_per_tooth,n1,thickness,hole); -translate([ 0, d12, 0]) rotate([0,0,-($t+n2/2-0*n1+1/2)*360/n2]) color([0.75,1.00,0.75]) gear(mm_per_tooth,n2,thickness,hole); -translate([ d13, 0, 0]) rotate([0,0,-($t-n3/4+n1/4+1/2)*360/n3]) color([0.75,0.75,1.00]) gear(mm_per_tooth,n3,thickness,hole); -translate([ d13, 0, 0]) rotate([0,0,-($t-n3/4+n1/4+1/2)*360/n3]) color([0.75,0.75,1.00]) gear(mm_per_tooth,n3,thickness,hole); -translate([-d14, 0, 0]) rotate([0,0,-($t-n4/4-n1/4+1/2-floor(n4/4)-3)*360/n4]) color([1.00,0.75,0.50]) gear(mm_per_tooth,n4,thickness,hole,teeth_to_hide=n4-3); -translate([(-floor(n5/2)-floor(n1/2)+$t+n1/2-1/2)*9, -d1+0.0, 0]) rotate([0,0,0]) color([0.75,0.75,0.75]) rack(mm_per_tooth,n5,thickness,height); +translate([ 0, 0, 0]) rotate([0,0, $t*360/n1]) color([1.00,0.75,0.75]) gear(pitch,n1,thickness,hole); +translate([ 0, d12, 0]) rotate([0,0,-($t+n2/2-0*n1+1/2)*360/n2]) color([0.75,1.00,0.75]) gear(pitch,n2,thickness,hole); +translate([ d13, 0, 0]) rotate([0,0,-($t-n3/4+n1/4+1/2)*360/n3]) color([0.75,0.75,1.00]) gear(pitch,n3,thickness,hole); +translate([ d13, 0, 0]) rotate([0,0,-($t-n3/4+n1/4+1/2)*360/n3]) color([0.75,0.75,1.00]) gear(pitch,n3,thickness,hole); +translate([-d14, 0, 0]) rotate([0,0,-($t-n4/4-n1/4+1/2-floor(n4/4)-3)*360/n4]) color([1.00,0.75,0.50]) gear(pitch,n4,thickness,hole,hide=n4-3); +translate([(-floor(n5/2)-floor(n1/2)+$t+n1/2-1/2)*9, -d1+0.0, 0]) rotate([0,0,0]) color([0.75,0.75,0.75]) rack(pitch,n5,thickness,height); */ From 4d72a0944453a0383f39f01916c23e3ef0480df2 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 11 Jun 2019 19:47:31 -0700 Subject: [PATCH 05/10] Improved gear examples. --- involute_gears.scad | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/involute_gears.scad b/involute_gears.scad index 892f27e4..d7ddab0e 100644 --- a/involute_gears.scad +++ b/involute_gears.scad @@ -350,7 +350,7 @@ module gear2d( // Example: Spur Gear // gear(pitch=5, teeth=20, thickness=8, shaft_diam=5); // Example: Beveled Gear -// gear(pitch=5, teeth=20, thickness=10*cos(45), shaft_diam=5, helical=-30, slices=12, $fa=1, $fs=1); +// gear(pitch=5, teeth=20, thickness=10, shaft_diam=5, helical=-30, slices=12, $fa=1, $fs=1); module gear( pitch = 3, teeth = 11, @@ -436,7 +436,8 @@ module gear( // clearance = Clearance gap at the bottom of the inter-tooth valleys. // backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle // bevelang = Angle of beveled gear face. -// spiral = Radius of spiral arc for teeth, if given. If 0, then gear will not be spiral. +// spiral_rad = Radius of spiral arc for teeth. If 0, then gear will not be spiral. Default: 0 +// spiral_ang = The base angle for spiral teeth. Default: 0 // slices = Number of vertical layers to divide gear into. Useful for refining gears with `spiral`. // scale = Scale of top of gear compared to bottom. Useful for making crown gears. // interior = If true, create a mask for difference()ing from something else. @@ -444,7 +445,7 @@ module gear( // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` // Example: Beveled Gear -// bevel_gear(pitch=5, teeth=20, face_width=10, shaft_diam=5, spiral=-30, bevelang=45, slices=12, $fa=1, $fs=1); +// bevel_gear(pitch=5, teeth=36, face_width=10, shaft_diam=5, spiral_rad=-20, spiral_ang=35, bevelang=45, slices=12, $fa=1, $fs=1); module bevel_gear( pitch = 3, teeth = 11, From 14c5847743a636e02173dd075b260c31f3e75668 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 11 Jun 2019 21:47:48 -0700 Subject: [PATCH 06/10] Fixed cylinder() where r1=0 --- primitives.scad | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/primitives.scad b/primitives.scad index dbcefe77..52483b3d 100644 --- a/primitives.scad +++ b/primitives.scad @@ -176,11 +176,12 @@ module cylinder(r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=unde r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1); r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1); l = first_defined([h, l]); + hh = h/2; sides = segs(max(r1,r2)); size = [r1*2, r1*2, l]; orient_and_anchor(size, orient, anchor, center, spin=spin, size2=[r2*2,r2*2], noncentered=BOTTOM, geometry="cylinder", chain=true) { - linear_extrude(height=l, scale=r2/r1, convexity=2, center=true) { - circle(r=r1, $fn=sides); + rotate_extrude(convexity=2, $fn=sides) { + polygon([[0,hh],[r2,hh],[r1,-hh],[0,-hh]]); } children(); } From 6242ec6dd945dc32789807b4a1bf0c2b233c340d Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 11 Jun 2019 22:12:49 -0700 Subject: [PATCH 07/10] Make [xyz]flip[_copy]() take ,, args instead of . --- transforms.scad | 56 ++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/transforms.scad b/transforms.scad index 258c5f45..49ec38a5 100644 --- a/transforms.scad +++ b/transforms.scad @@ -466,10 +466,10 @@ module zscale(z) scale([1,1,z]) children(); // Mirrors the children along the X axis, like `mirror([1,0,0])` or `xscale(-1)` // // Usage: -// xflip([cp]) ... +// xflip([x]) ... // // Arguments: -// cp = A point that lies on the plane of reflection. +// x = The X coordinate of the plane of reflection. Default: 0 // // Example: // xflip() yrot(90) cylinder(d1=10, d2=0, h=20); @@ -477,10 +477,10 @@ module zscale(z) scale([1,1,z]) children(); // color("red", 0.333) yrot(90) cylinder(d1=10, d2=0, h=20); // // Example: -// xflip(cp=[-5,0,0]) yrot(90) cylinder(d1=10, d2=0, h=20); +// xflip(x=-5) yrot(90) cylinder(d1=10, d2=0, h=20); // color("blue", 0.25) left(5) cube([0.01,15,15], center=true); // color("red", 0.333) yrot(90) cylinder(d1=10, d2=0, h=20); -module xflip(cp=[0,0,0]) translate(cp) mirror([1,0,0]) translate(-cp) children(); +module xflip(x=0) translate([x,0,0]) mirror([1,0,0]) translate([-x,0,0]) children(); // Module: yflip() @@ -489,10 +489,10 @@ module xflip(cp=[0,0,0]) translate(cp) mirror([1,0,0]) translate(-cp) children() // Mirrors the children along the Y axis, like `mirror([0,1,0])` or `yscale(-1)` // // Usage: -// yflip([cp]) ... +// yflip([y]) ... // // Arguments: -// cp = A point that lies on the plane of reflection. +// y = The Y coordinate of the plane of reflection. Default: 0 // // Example: // yflip() xrot(90) cylinder(d1=10, d2=0, h=20); @@ -500,10 +500,10 @@ module xflip(cp=[0,0,0]) translate(cp) mirror([1,0,0]) translate(-cp) children() // color("red", 0.333) xrot(90) cylinder(d1=10, d2=0, h=20); // // Example: -// yflip(cp=[0,5,0]) xrot(90) cylinder(d1=10, d2=0, h=20); +// yflip(y=5) xrot(90) cylinder(d1=10, d2=0, h=20); // color("blue", 0.25) back(5) cube([15,0.01,15], center=true); // color("red", 0.333) xrot(90) cylinder(d1=10, d2=0, h=20); -module yflip(cp=[0,0,0]) translate(cp) mirror([0,1,0]) translate(-cp) children(); +module yflip(y=0) translate([0,y,0]) mirror([0,1,0]) translate([0,-y,0]) children(); // Module: zflip() @@ -512,10 +512,10 @@ module yflip(cp=[0,0,0]) translate(cp) mirror([0,1,0]) translate(-cp) children() // Mirrors the children along the Z axis, like `mirror([0,0,1])` or `zscale(-1)` // // Usage: -// zflip([cp]) ... +// zflip([z]) ... // // Arguments: -// cp = A point that lies on the plane of reflection. +// z = The Z coordinate of the plane of reflection. Default: 0 // // Example: // zflip() cylinder(d1=10, d2=0, h=20); @@ -523,10 +523,10 @@ module yflip(cp=[0,0,0]) translate(cp) mirror([0,1,0]) translate(-cp) children() // color("red", 0.333) cylinder(d1=10, d2=0, h=20); // // Example: -// zflip(cp=[0,0,-5]) cylinder(d1=10, d2=0, h=20); +// zflip(z=-5) cylinder(d1=10, d2=0, h=20); // color("blue", 0.25) down(5) cube([15,15,0.01], center=true); // color("red", 0.333) cylinder(d1=10, d2=0, h=20); -module zflip(cp=[0,0,0]) translate(cp) mirror([0,0,1]) translate(-cp) children(); +module zflip(z=0) translate([0,0,z]) mirror([0,0,1]) translate([0,0,-z]) children(); @@ -1661,11 +1661,11 @@ module mirror_copy(v=[0,0,1], offset=0, cp=[0,0,0]) // Makes a copy of the children, mirrored across the X axis. // // Usage: -// xflip_copy([cp], [offset]) ... +// xflip_copy([x], [offset]) ... // // Arguments: // offset = Distance to offset children right, before copying. -// cp = A point that lies on the mirroring plane. +// x = The X coordinate of the mirroring plane. Default: 0 // // Side Effects: // `$orig` is true for the original instance of children. False for the copy. @@ -1680,11 +1680,11 @@ module mirror_copy(v=[0,0,1], offset=0, cp=[0,0,0]) // color("blue",0.25) cube([0.01,15,15], center=true); // // Example: -// xflip_copy(cp=[-5,0,0]) yrot(90) cylinder(h=20, r1=4, r2=0); +// xflip_copy(x=-5) yrot(90) cylinder(h=20, r1=4, r2=0); // color("blue",0.25) left(5) cube([0.01,15,15], center=true); -module xflip_copy(offset=0, cp=[0,0,0]) +module xflip_copy(offset=0, x=0) { - mirror_copy(v=[1,0,0], offset=offset, cp=cp) children(); + mirror_copy(v=[1,0,0], offset=offset, cp=[x,0,0]) children(); } @@ -1694,11 +1694,11 @@ module xflip_copy(offset=0, cp=[0,0,0]) // Makes a copy of the children, mirrored across the Y axis. // // Usage: -// yflip_copy([cp], [offset]) ... +// yflip_copy([y], [offset]) ... // // Arguments: // offset = Distance to offset children back, before copying. -// cp = A point that lies on the mirroring plane. +// y = The Y coordinate of the mirroring plane. Default: 0 // // Side Effects: // `$orig` is true for the original instance of children. False for the copy. @@ -1713,11 +1713,11 @@ module xflip_copy(offset=0, cp=[0,0,0]) // color("blue",0.25) cube([15,0.01,15], center=true); // // Example: -// yflip_copy(cp=[0,-5,0]) xrot(-90) cylinder(h=20, r1=4, r2=0); +// yflip_copy(y=-5) xrot(-90) cylinder(h=20, r1=4, r2=0); // color("blue",0.25) fwd(5) cube([15,0.01,15], center=true); -module yflip_copy(offset=0, cp=[0,0,0]) +module yflip_copy(offset=0, y=0) { - mirror_copy(v=[0,1,0], offset=offset, cp=cp) children(); + mirror_copy(v=[0,1,0], offset=offset, cp=[0,y,0]) children(); } @@ -1727,15 +1727,15 @@ module yflip_copy(offset=0, cp=[0,0,0]) // Makes a copy of the children, mirrored across the Z axis. // // Usage: -// zflip_copy([cp], [offset]) ... -// `$idx` is set to the index value of each copy. +// zflip_copy([z], [offset]) ... // // Arguments: // offset = Distance to offset children up, before copying. -// cp = A point that lies on the mirroring plane. +// z = The Z coordinate of the mirroring plane. Default: 0 // // Side Effects: // `$orig` is true for the original instance of children. False for the copy. +// `$idx` is set to the index value of each copy. // // Example: // zflip_copy() cylinder(h=20, r1=4, r2=0); @@ -1746,11 +1746,11 @@ module yflip_copy(offset=0, cp=[0,0,0]) // color("blue",0.25) cube([15,15,0.01], center=true); // // Example: -// zflip_copy(cp=[0,0,-5]) cylinder(h=20, r1=4, r2=0); +// zflip_copy(z=-5) cylinder(h=20, r1=4, r2=0); // color("blue",0.25) down(5) cube([15,15,0.01], center=true); -module zflip_copy(offset=0, cp=[0,0,0]) +module zflip_copy(offset=0, z=0) { - mirror_copy(v=[0,0,1], offset=offset, cp=cp) children(); + mirror_copy(v=[0,0,1], offset=offset, cp=[0,0,z]) children(); } From f927ac6c105e30b628561d287f56881f97fdd02e Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 11 Jun 2019 22:26:09 -0700 Subject: [PATCH 08/10] Fixed function scale() for scalar scaling. --- transforms.scad | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/transforms.scad b/transforms.scad index 49ec38a5..101fc13a 100644 --- a/transforms.scad +++ b/transforms.scad @@ -386,9 +386,10 @@ module zrot(a=0, cp=undef) // Function&Module: scale() // Usage: As Module +// scale(SCALAR) ... // scale([X,Y,Z]) ... // Usage: Scale Points -// pts = scale(a, pts); +// pts = scale(a, p); // Usage: Get Scaling Matrix // mat = scale(a); // Description: @@ -397,14 +398,16 @@ module zrot(a=0, cp=undef) // scaling factors in `a`. When called as a function with a list of points in the `p` argument, // returns the list of points, with each one scaled by the [X,Y,Z] scaling factors in `a`. // Arguments: -// a = The [X,Y,Z] scaling factors. +// a = The [X,Y,Z] scaling factors, or a scalar value for uniform scaling across all axes. Default: 1 // p = If called as a function, the point or list of points to scale. // Example(NORENDER): -// pt1 = scale([2,3,4], p=[3,1,4]); // Returns: [6,3,16] +// pt1 = scale(3, p=[3,1,4]); // Returns: [9,3,12] +// pt2 = scale([2,3,4], p=[3,1,4]); // Returns: [6,3,16] // pt3 = scale([2,3,4], p=[[1,2,3],[4,5,6]]); // Returns: [[2,6,12], [8,15,24]] // mat2d = scale([2,3]); // Returns: [[2,0,0],[0,3,0],[0,0,1]] // mat3d = scale([2,3,4]); // Returns: [[2,0,0,0],[0,3,0,0],[0,0,4,0],[0,0,0,1]] -function scale(a=[1,1,1], p=undef) = +function scale(a=1, p=undef) = + let(a = is_num(a)? [a,a,a] : a) is_undef(p)? ( len(a)==2? affine2d_scale(a) : affine3d_scale(point3d(a)) ) : ( From bf6cfd1d65e39f6b95faa27c7254dc880994a55c Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Wed, 12 Jun 2019 02:27:42 -0700 Subject: [PATCH 09/10] Rewrote cyl() to allow external chamfers and roundings. --- geometry.scad | 29 +++++++++++ shapes.scad | 130 ++++++++++++++++++-------------------------------- 2 files changed, 75 insertions(+), 84 deletions(-) diff --git a/geometry.scad b/geometry.scad index 845577fd..fc702b99 100644 --- a/geometry.scad +++ b/geometry.scad @@ -182,6 +182,35 @@ function line_segment_intersection(line,segment) = ) isect[2]<0-eps || isect[2]>1+eps ? undef : isect[0]; +// Function: find_circle_2tangents() +// Usage: +// find_circle_2tangents(pt1, pt2, pt3, r|d); +// Description: +// Returns [centerpoint, normal] of a circle of known size that is between and tangent to two rays with the same starting point. +// Both rays start at `pt2`, and one passes through `pt1`, while the other passes through `pt3`. +// If the rays given are 180º apart, `undef` is returned. If the rays are 3D, the normal returned is the plane normal of the circle. +// Arguments: +// pt1 = A point that the first ray passes though. +// pt2 = The starting point of both rays. +// pt3 = A point that the second ray passes though. +// r = The radius of the circle to find. +// d = The diameter of the circle to find. +function find_circle_2tangents(pt1, pt2, pt3, r=undef, d=undef) = + let( + r = get_radius(r=r, d=d, dflt=undef), + v1 = normalize(pt1 - pt2), + v2 = normalize(pt3 - pt2) + ) approx(norm(v1+v2))? undef : + assert(r!=undef, "Must specify either r or d.") + let( + a = vector_angle(v1,v2), + n = vector_axis(v1,v2), + v = normalize(mean([v1,v2])), + s = r/sin(a/2), + cp = pt2 + s*v/norm(v) + ) [cp, n]; + + // Function: triangle_area2d() // Usage: // triangle_area2d(a,b,c); diff --git a/shapes.scad b/shapes.scad index 219e90fe..ce03438d 100644 --- a/shapes.scad +++ b/shapes.scad @@ -438,6 +438,12 @@ module right_triangle(size=[1, 1, 1], anchor=ALLNEG, spin=0, orient=UP, center=u // Example: Putting it all together // cyl(l=40, d1=25, d2=15, chamfer1=10, chamfang1=30, from_end=true, rounding2=5); // +// Example: External Chamfers +// cyl(l=50, r=30, chamfer=-5, chamfang=30, $fa=1, $fs=1); +// +// Example: External Roundings +// cyl(l=50, r=30, rounding1=-5, rounding2=5, $fa=1, $fs=1); +// // Example: Standard Connectors // xdistribute(40) { // cyl(l=30, d=25) show_anchors(); @@ -461,7 +467,7 @@ module cyl( size2 = [r2*2,r2*2,l]; sides = segs(max(r1,r2)); sc = circum? 1/cos(180/sides) : 1; - phi = atan2(l, r1-r2); + phi = atan2(l, r2-r1); orient_and_anchor(size1, orient, anchor, spin=spin, center=center, size2=size2, geometry="cylinder", chain=true) { zrot(realign? 180/sides : 0) { if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])) { @@ -477,110 +483,66 @@ module cyl( if (chamfer != undef) { assert(chamfer <= r1, "chamfer is larger than the r1 radius of the cylinder."); assert(chamfer <= r2, "chamfer is larger than the r2 radius of the cylinder."); - assert(chamfer <= l/2, "chamfer is larger than half the length of the cylinder."); } if (cham1 != undef) { assert(cham1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder."); - assert(cham1 <= l/2, "chamfer1 is larger than half the length of the cylinder."); } if (cham2 != undef) { assert(cham2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder."); - assert(cham2 <= l/2, "chamfer2 is larger than half the length of the cylinder."); } if (rounding != undef) { assert(rounding <= r1, "rounding is larger than the r1 radius of the cylinder."); assert(rounding <= r2, "rounding is larger than the r2 radius of the cylinder."); - assert(rounding <= l/2, "rounding is larger than half the length of the cylinder."); } if (fil1 != undef) { assert(fil1 <= r1, "rounding1 is larger than the r1 radius of the cylinder."); - assert(fil1 <= l/2, "rounding1 is larger than half the length of the cylinder."); } if (fil2 != undef) { assert(fil2 <= r2, "rounding2 is larger than the r1 radius of the cylinder."); - assert(fil2 <= l/2, "rounding2 is larger than half the length of the cylinder."); } + dy1 = abs(first_defined([cham1, fil1, 0])); + dy2 = abs(first_defined([cham2, fil2, 0])); + assert(dy1+dy2 <= l, "Sum of fillets and chamfer sizes must be less than the length of the cylinder."); - dy1 = first_defined([cham1, fil1, 0]); - dy2 = first_defined([cham2, fil2, 0]); - maxd = max(r1,r2,l); + path = concat( + [[0,l/2]], + !is_undef(cham2)? ( + let( + p1 = [r2-cham2/tan(chang2),l/2], + p2 = lerp([r2,l/2],[r1,-l/2],abs(cham2)/l) + ) [p1,p2] + ) : !is_undef(fil2)? ( + let( + cn = find_circle_2tangents([r2-fil2,l/2], [r2,l/2], [r1,-l/2], r=abs(fil2)), + ang = fil2<0? phi : phi-180, + steps = ceil(abs(ang)/360*segs(abs(fil2))), + step = ang/steps, + pts = [for (i=[0:1:steps]) let(a=90+i*step) cn[0]+abs(fil2)*[cos(a),sin(a)]] + ) pts + ) : [[r2,l/2]], + + !is_undef(cham1)? ( + let( + p1 = lerp([r1,-l/2],[r2,l/2],abs(cham1)/l), + p2 = [r1-cham1/tan(chang1),-l/2] + ) [p1,p2] + ) : !is_undef(fil1)? ( + let( + cn = find_circle_2tangents([r1-fil1,-l/2], [r1,-l/2], [r2,l/2], r=abs(fil1)), + ang = fil1<0? 180-phi : -phi, + steps = ceil(abs(ang)/360*segs(abs(fil1))), + step = ang/steps, + pts = [for (i=[0:1:steps]) let(a=(fil1<0?180:0)+(phi-90)+i*step) cn[0]+abs(fil1)*[cos(a),sin(a)]] + ) pts + ) : [[r1,-l/2]], + + [[0,-l/2]] + ); rotate_extrude(convexity=2) { - hull() { - difference() { - union() { - difference() { - back(l/2) { - if (cham2!=undef && cham2>0) { - rr2 = sc * (r2 + (r1-r2)*dy2/l); - chlen2 = min(rr2, cham2/sin(chang2)); - translate([rr2,-cham2]) { - rotate(-chang2) { - translate([-chlen2,-chlen2]) { - square(chlen2, center=false); - } - } - } - } else if (fil2!=undef && fil2>0) { - translate([r2-fil2*tan(vang),-fil2]) { - circle(r=fil2); - } - } else { - translate([r2-0.005,-0.005]) { - square(0.01, center=true); - } - } - } - - // Make sure the corner fiddly bits never cross the X axis. - fwd(maxd) square(maxd, center=false); - } - difference() { - fwd(l/2) { - if (cham1!=undef && cham1>0) { - rr1 = sc * (r1 + (r2-r1)*dy1/l); - chlen1 = min(rr1, cham1/sin(chang1)); - translate([rr1,cham1]) { - rotate(chang1) { - left(chlen1) { - square(chlen1, center=false); - } - } - } - } else if (fil1!=undef && fil1>0) { - right(r1) { - translate([-fil1/tan(vang),fil1]) { - fsegs1 = quantup(segs(fil1),4); - circle(r=fil1,$fn=fsegs1); - } - } - } else { - right(r1-0.01) { - square(0.01, center=false); - } - } - } - - // Make sure the corner fiddly bits never cross the X axis. - square(maxd, center=false); - } - - // Force the hull to extend to the axis - right(0.01/2) square([0.01, l], center=true); - } - - // Clear anything left of the Y axis. - left(maxd/2) square(maxd, center=true); - - // Clear anything right of face - right((r1+r2)/2) { - rotate(90-vang*2) { - fwd(maxd/2) square(maxd, center=false); - } - } - } - } + polygon(path); } + //!place_copies(path) sphere(d=1); } } children(); From 9611a181db2814684f0fc2f61492518c6760a42f Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Wed, 12 Jun 2019 16:52:26 -0700 Subject: [PATCH 10/10] Added external roundings and chamfers to cuboid(). Fixed [xyz]cyl() anchoring. --- shapes.scad | 177 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 152 insertions(+), 25 deletions(-) diff --git a/shapes.scad b/shapes.scad index ce03438d..c7899e9e 100644 --- a/shapes.scad +++ b/shapes.scad @@ -15,6 +15,8 @@ // // Description: // Creates a cube or cuboid object, with optional chamfering or rounding. +// Negative chamfers and roundings can be applied to create external masks, +// but only apply to edges around the top or bottom faces. // // Arguments: // size = The size of the cube. @@ -34,20 +36,28 @@ // cuboid(20, p1=[10,0,0]); // Example: Rectangular cube, with given X, Y, and Z sizes. // cuboid([20,40,50]); -// Example: Rectangular cube defined by opposing cornerpoints. +// Example: Cube by Opposing Corners. // cuboid(p1=[0,10,0], p2=[20,30,30]); -// Example: Rectangular cube with chamferred edges and corners. +// Example: Chamferred Edges and Corners. // cuboid([30,40,50], chamfer=5); -// Example: Rectangular cube with chamferred edges, without trimmed corners. +// Example: Chamferred Edges, Untrimmed Corners. // cuboid([30,40,50], chamfer=5, trimcorners=false); -// Example: Rectangular cube with rounded edges and corners. +// Example: Rounded Edges and Corners // cuboid([30,40,50], rounding=10); -// Example: Rectangular cube with rounded edges, without trimmed corners. +// Example: Rounded Edges, Untrimmed Corners // cuboid([30,40,50], rounding=10, trimcorners=false); -// Example: Rectangular cube with only some edges chamferred. +// Example: Chamferring Selected Edges // cuboid([30,40,50], chamfer=5, edges=edges([TOP+FRONT,TOP+RIGHT,FRONT+RIGHT]), $fn=24); -// Example: Rectangular cube with only some edges rounded. +// Example: Rounding Selected Edges // cuboid([30,40,50], rounding=5, edges=edges([TOP+FRONT,TOP+RIGHT,FRONT+RIGHT]), $fn=24); +// Example: Negative Chamferring +// cuboid([30,40,50], chamfer=-5, edges=edges([TOP,BOT], RIGHT), $fn=24); +// Example: Negative Chamferring, Untrimmed Corners +// cuboid([30,40,50], chamfer=-5, edges=edges([TOP,BOT], RIGHT), trimcorners=false, $fn=24); +// Example: Negative Rounding +// cuboid([30,40,50], rounding=-5, edges=edges([TOP,BOT], RIGHT), $fn=24); +// Example: Negative Rounding, Untrimmed Corners +// cuboid([30,40,50], rounding=-5, edges=edges([TOP,BOT], RIGHT), trimcorners=false, $fn=24); // Example: Standard Connectors // cuboid(40) show_anchors(); module cuboid( @@ -78,12 +88,61 @@ module cuboid( majrots = [[0,90,0], [90,0,0], [0,0,0]]; orient_and_anchor(size, orient, anchor, spin=spin, chain=true) { if (chamfer != undef) { - isize = [for (v = size) max(0.001, v-2*chamfer)]; if (edges == EDGES_ALL && trimcorners) { - hull() { - cube([size.x, isize.y, isize.z], center=true); - cube([isize.x, size.y, isize.z], center=true); - cube([isize.x, isize.y, size.z], center=true); + if (chamfer<0) { + cube(size, center=true) { + attach(TOP) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP); + attach(BOT) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP); + } + } else { + isize = [for (v = size) max(0.001, v-2*chamfer)]; + hull() { + cube([size.x, isize.y, isize.z], center=true); + cube([isize.x, size.y, isize.z], center=true); + cube([isize.x, isize.y, size.z], center=true); + } + } + } else if (chamfer<0) { + ach = abs(chamfer); + cube(size, center=true); + + // External-Chamfer mask edges + difference() { + union() { + for (i = [0:3], axis=[0:1]) { + if (edges[axis][i]>0) { + vec = EDGE_OFFSETS[axis][i]; + translate(vmul(vec/2, size+[ach,ach,-ach])) { + rotate(majrots[axis]) { + cube([ach, ach, size[axis]], center=true); + } + } + } + } + + // Add multi-edge corners. + if (trimcorners) { + for (za=[-1,1], ya=[-1,1], xa=[-1,1]) { + if (corner_edge_count(edges, [xa,ya,za]) > 1) { + translate(vmul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) { + cube([ach+0.01,ach+0.01,ach], center=true); + } + } + } + } + } + + // Remove bevels from overhangs. + for (i = [0:3], axis=[0:1]) { + if (edges[axis][i]>0) { + vec = EDGE_OFFSETS[axis][i]; + translate(vmul(vec/2, size+[2*ach,2*ach,-2*ach])) { + rotate(majrots[axis]) { + zrot(45) cube([ach*sqrt(2), ach*sqrt(2), size[axis]+2.1*ach], center=true); + } + } + } + } } } else { difference() { @@ -117,17 +176,74 @@ module cuboid( } else if (rounding != undef) { sides = quantup(segs(rounding),4); sc = 1/cos(180/sides); - isize = [for (v = size) max(0.001, v-2*rounding)]; if (edges == EDGES_ALL) { - minkowski() { - cube(isize, center=true); - if (trimcorners) { - sphere(r=rounding*sc, $fn=sides); - } else { - intersection() { - zrot(180/sides) cylinder(r=rounding*sc, h=rounding*2, center=true, $fn=sides); - rotate([90,0,0]) zrot(180/sides) cylinder(r=rounding*sc, h=rounding*2, center=true, $fn=sides); - rotate([0,90,0]) zrot(180/sides) cylinder(r=rounding*sc, h=rounding*2, center=true, $fn=sides); + if(rounding<0) { + cube(size, center=true); + zflip_copy() { + up(size.z/2) { + difference() { + down(-rounding/2) cube([size.x-2*rounding, size.y-2*rounding, -rounding], center=true); + down(-rounding) { + yspread(size.y-2*rounding) xcyl(l=size.x-3*rounding, r=-rounding); + xspread(size.x-2*rounding) ycyl(l=size.y-3*rounding, r=-rounding); + } + } + } + } + } else { + isize = [for (v = size) max(0.001, v-2*rounding)]; + minkowski() { + cube(isize, center=true); + if (trimcorners) { + sphere(r=rounding*sc, $fn=sides); + } else { + intersection() { + zrot(180/sides) cylinder(r=rounding*sc, h=rounding*2, center=true, $fn=sides); + rotate([90,0,0]) zrot(180/sides) cylinder(r=rounding*sc, h=rounding*2, center=true, $fn=sides); + rotate([0,90,0]) zrot(180/sides) cylinder(r=rounding*sc, h=rounding*2, center=true, $fn=sides); + } + } + } + } + } else if (rounding<0) { + ard = abs(rounding); + cube(size, center=true); + + // External-Chamfer mask edges + difference() { + union() { + for (i = [0:3], axis=[0:1]) { + if (edges[axis][i]>0) { + vec = EDGE_OFFSETS[axis][i]; + translate(vmul(vec/2, size+[ard,ard,-ard])) { + rotate(majrots[axis]) { + cube([ard, ard, size[axis]], center=true); + } + } + } + } + + // Add multi-edge corners. + if (trimcorners) { + for (za=[-1,1], ya=[-1,1], xa=[-1,1]) { + if (corner_edge_count(edges, [xa,ya,za]) > 1) { + translate(vmul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) { + cube([ard+0.01,ard+0.01,ard], center=true); + } + } + } + } + } + + // Remove roundings from overhangs. + for (i = [0:3], axis=[0:1]) { + if (edges[axis][i]>0) { + vec = EDGE_OFFSETS[axis][i]; + translate(vmul(vec/2, size+[2*ard,2*ard,-2*ard])) { + rotate(majrots[axis]) { + cyl(l=size[axis]+2.1*ard, r=ard); + } + } } } } @@ -583,7 +699,11 @@ module cyl( // } module xcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, anchor=CENTER) { - cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=RIGHT, anchor=anchor) children(); + anchor = rot(from=RIGHT, to=UP, p=anchor); + cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=RIGHT, anchor=anchor) { + for (i=[0:1:$children-2]) children(i); + if ($children>0) children(0); + } } @@ -620,7 +740,11 @@ module xcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h // } module ycyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, anchor=CENTER) { - cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=BACK, anchor=anchor) children(); + anchor = rot(from=BACK, to=UP, p=anchor); + cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=BACK, anchor=anchor) { + for (i=[0:1:$children-2]) children(i); + if ($children>0) children(0); + } } @@ -657,7 +781,10 @@ module ycyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h // } module zcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, anchor=CENTER) { - cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=UP, anchor=anchor) children(); + cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=UP, anchor=anchor) { + for (i=[0:1:$children-2]) children(i); + if ($children>0) children(0); + } }