1//////////////////////////////////////////////////////////////////////////////////////////////
   2// LibFile: gears.scad
   3//   Spur Gears, Bevel Gears, Racks, Worms and Worm Gears.
   4//   Inspired by code by Leemon Baird, 2011, Leemon@Leemon.com
   5// Includes:
   6//   include <BOSL2/std.scad>
   7//   include <BOSL2/gears.scad>
   8// FileGroup: Parts
   9// FileSummary: Gears, racks, worms, and worm gears.
  10//////////////////////////////////////////////////////////////////////////////////////////////
  11
  12
  13_GEAR_PITCH = 5;
  14_GEAR_HELICAL = 0;
  15_GEAR_THICKNESS = 10;
  16_GEAR_PA = 20;
  17
  18
  19$parent_gear_type = undef;
  20$parent_gear_pitch = undef;
  21$parent_gear_teeth = undef;
  22$parent_gear_pa = undef;
  23$parent_gear_helical = undef;
  24$parent_gear_thickness = undef;
  25$parent_gear_dir = undef;
  26$parent_gear_travel = 0;
  27
  28
  29function _inherit_gear_param(name, val, pval, dflt, invert=false) =
  30    is_undef(val)
  31      ? is_undef(pval)
  32        ? dflt
  33        : (invert?-1:1)*pval
  34      : is_undef(pval)
  35        ? assert(is_finite(val), str("Invalid ",name," value: ",val))
  36          val
  37        : (invert?-1:1)*val;
  38
  39
  40function _inherit_gear_pitch(fname,pitch,circ_pitch,diam_pitch,mod,warn=true) =
  41    pitch != undef?
  42        assert(is_finite(pitch) && pitch>0)
  43        warn? echo(str(
  44            "WARNING: The use of the argument pitch= in ", fname,
  45            " is deprecated.  Please use circ_pitch= instead."
  46        )) pitch : pitch :
  47    circ_pitch != undef?
  48        assert(is_finite(circ_pitch) && circ_pitch>0)
  49        circ_pitch :
  50    diam_pitch != undef?
  51        assert(is_finite(diam_pitch) && diam_pitch>0)
  52        circular_pitch(diam_pitch=diam_pitch) :
  53    mod != undef?
  54        assert(is_finite(mod) && mod>0)
  55        circular_pitch(mod=mod) :
  56    $parent_gear_pitch != undef? $parent_gear_pitch :
  57    5;
  58
  59function _inherit_gear_pa(pressure_angle) =
  60    _inherit_gear_param("pressure_angle", pressure_angle, $parent_gear_pa, dflt=20);
  61
  62function _inherit_gear_helical(helical,invert=false) =
  63    _inherit_gear_param("helical", helical, $parent_gear_helical, dflt=0, invert=invert);
  64
  65function _inherit_gear_thickness(thickness,dflt=10) =
  66    _inherit_gear_param("thickness", thickness, $parent_gear_thickness, dflt=dflt);
  67
  68
  69// Section: Quick Introduction to Gears
  70//   This section gives a quick overview of gears with a focus on the information you need
  71//   to know to understand the gear parameters and create some gears.  The topic of gears is very complex and highly technical and
  72//   this section provides the minimal information needed for gear making.  If you want more information about the
  73//   details of gears, consult the references below, which are the ones that we consulted when writing the library code.
  74//   - Tec Science
  75//       * [Involute Gears](https://www.tec-science.com/mechanical-power-transmission/involute-gear/geometry-of-involute-gears/)
  76//       * [Gear engagement](https://www.tec-science.com/mechanical-power-transmission/involute-gear/meshing-line-action-contact-pitch-circle-law/)
  77//       * [Gears meshing with racks](https://www.tec-science.com/mechanical-power-transmission/involute-gear/rack-meshing/)
  78//       * [Gear undercutting](https://www.tec-science.com/mechanical-power-transmission/involute-gear/undercut/)
  79//       * [Profile shifting](https://www.tec-science.com/mechanical-power-transmission/involute-gear/profile-shift/)
  80//       * [Detailed gear calculations](https://www.tec-science.com/mechanical-power-transmission/involute-gear/calculation-of-involute-gears/)
  81//       * [Worm drive](https://www.tec-science.com/mechanical-power-transmission/gear-types/worms-and-worm-gears/)
  82//       * [Bevel gears](https://www.tec-science.com/mechanical-power-transmission/gear-types/bevel-gears/)
  83//   - SDPSI (A long document covering a variety of gear types and gear calculations)
  84//       * [Elements of Gear Technology](https://www.sdp-si.com/resources/elements-of-metric-gear-technology/index.php)
  85//   - Drivetrain Hub (A collection of "notebooks" on some gear topics)
  86//       * [Gear Geometry, Strength, Tooling and Mechanics](https://drivetrainhub.com/notebooks/#toc)
  87//   - Crown Face Gears
  88//       * [Crown Gearboxes](https://mag.ebmpapst.com/en/industries/drives/crown-gearboxes-efficiency-energy-savings-decentralized-drive-technology_14834/)
  89//       * [Crown gear pressure angle](https://mag.ebmpapst.com/en/industries/drives/the-formula-for-the-pressure-angle_14624/)
  90//       * [Face Gears: Geometry and Strength](https://www.geartechnology.com/ext/resources/issues/0107x/kissling.pdf)
  91
  92// Subsection: Involute Spur Gears
  93// The simplest gear form is the involute spur gear, which is an extrusion of a two dimensional form.
  94// Figure(3D,Med,NoAxes,VPT=[4.62654,-1.10349,0.281802],VPR=[55,0,25],VPD=236.957): Involute Spur Gear
  95//   spur_gear(mod=5,teeth=18,pressure_angle=20,thickness=25,shaft_diam=15);
  96// Continues:
  97//   The term "involute" refers to the shape of the teeth:  the curves of the teeth are involutes of circles, 
  98//   which are curves that optimize gear performance.
  99// Figure(2D,Med,NoAxes,VPT=[8,74,0],VPR=[0,0,0],VPD=150): The three marked circles are key references on gear teeth.  The pitch circle, which is roughly in the middle of the teeth, is the reference used to define the pitch of teeth on the gear.  The pressure angle is the angle the tooth makes with the pitch circle.  In this example, the pressure angle is 20 degrees as shown by the red lines.  
 100//   $fn=128;
 101//   intersection(){
 102//     spur_gear2d(mod=5,teeth=30,pressure_angle=20);
 103//     back(82)rect([45, 20],anchor=BACK);
 104//   }
 105//   color("black"){
 106//     stroke(arc(r=_root_radius(mod=5,teeth=30),angle=[70,110]),width=.25);
 107//     stroke(arc(r=pitch_radius(mod=5,teeth=30),angle=[70,110]),width=.25);
 108//     stroke(arc(r=outer_radius(mod=5,teeth=30),angle=[70,110]),width=.25);
 109//     back(63.5)right(24.2)text("root circle",size=2.5);
 110//     back(69.5)right(26.5)text("pitch circle",size=2.5);
 111//     back(74)right(28)text("outer circle",size=2.5);    
 112//   }  
 113//   base = _base_radius(mod=5, teeth=30);
 114//   pitchpt = pitch_radius(mod=5, teeth=30);
 115//   color("red"){
 116//     zrot(87-360/30) zrot(20,cp=[pitchpt,0]) stroke([[base-5,0],[base+15,0]], width=0.25);
 117//     zrot(87-360/30) stroke([[pitchpt,0],[pitchpt+11,0]], width=0.25);
 118//     right(8.3) back(74) zrot(87-360/30) zrot(10,cp=[pitchpt,0]) stroke(arc(angle=[0,20],r=10.5),endcaps="arrow2",width=.25);
 119//     back(84) right(13) text("pressure angle",size=2.5);
 120//   }
 121//   stroke(arc(r=pitch_radius(mod=5,teeth=30),angle=[87,87+12]),width=.4,endcaps="arrow2",color="red");
 122//   color([1,0,0,1])     back(70)right(-13)zrot(4)text("circular pitch", size=2.5);
 123// Continues:
 124//   The size of the teeth can be specified as the *circular pitch*, which is the tooth width, or more precisely,
 125//   the distance along the pitch circle from the start of one tooth to the start of the text tooth.
 126//   The circular pitch can be computed as
 127//   `PI*d/teeth` where `d` is the diameter of the pitch circle and `teeth` is the number of teeth on the gear.
 128//   This simply divides up the pitch circle into the specified number of teeth.  However, the customary
 129//   way to specify metric gears is using the module, ratio of the diameter of the gear to the number of teeth: `m=d/teeth`.
 130//   The module is hence the circular pitch divided by a factor of π.  A third way to specify gear sizes is the diametral pitch,
 131//   which is the number of teeth that fit on a gear with a diameter of one inch, or π times the number of teeth per inch.
 132//   Note that for the module or circular pitch, larger values make larger teeth,
 133//   but for the diametral pitch, the opposite is true.  Throughout this library, module and circular pitch
 134//   are specified basic OpenSCAD units, so if you work in millimeters and want to give circular pitch in inches, be
 135//   sure to multiply by `INCH`.  The diametral pitch is given based on inches under the assumption that OpenSCAD units are millimeters.
 136//   .
 137//   Note that there is no direct way to specify the size of a gear.  The diameter of a gear depends on its tooth count
 138//   and tooth size.  If you want a gear with a particular diameter you can get close by seeting the module to `d/teeth`,
 139//   but that specifies the pitch circle, so the gear teeth will have a somewhat larger radius.  You should **not**
 140//   apply scale() to gears.  Always change their size by adjusting the tooth size parameters.  
 141//   .
 142//   Basic gears as shown above will mesh when their pitch circles are tangent.
 143//   The critical requirements for two gears to mesh are that
 144//   - The teeth are the same size
 145//   - The pressure angles are identical
 146//   .
 147//   Increasing pressure angle makes the tooth stronger, increases power transmission, and can reduce tooth interference for
 148//   gears with a small number of teeth, but it also increases gear wear and meshing noise.  Higher pressure angles also
 149//   increase the force that tries to push the gears apart, and hence the load on the gear axles.  The current standard pressure
 150//   angle is 20 degrees.  It replaces an old 14.5 degree standard.  
 151// Figure(2D,Med,NoAxes): Teeth of the same size with different pressure angles.  Note that 20 deg is the industry standard. 
 152//   pang = [30,20,14.5];
 153//   ycopies(n=3, spacing=25){
 154//     intersection(){
 155//       spur_gear2d(mod=5, teeth=30, pressure_angle=pang[$idx]);
 156//       back(82) rect([45,20], anchor=BACK);
 157//     }
 158//     back(68) right(26) text(str(pang[$idx]), size=6.5);
 159//   }
 160// Continues:
 161//   In order for the gear teeth to fit together, and to allow space for lubricant, the valleys of the teeth
 162//   are made deeper by the `clearance` distance.  This defaults to `module/4`.  
 163// Figure(2D,Med,NoAxes,VPT=[5.62512,-1.33268,-0.0144912],VPR=[0,0,0],VPD=126): The clearance is extra space at the tooth valley that separates the tooth tip (in green) from the tooth valley below it.  
 164//   intersection(){
 165//     rack2d(mod=5, teeth=10, bottom=15, pressure_angle=14.5);
 166//     rect([35,20]);
 167//   }  
 168//   color("lightgreen")render()
 169//   intersection(){
 170//      back(gear_dist(mod=5, teeth1=146, teeth2=0 ,profile_shift1=0))
 171//          spur_gear2d(mod=5, teeth=146, profile_shift=0, pressure_angle=14.5);
 172//      rect([45,20]);
 173//   }   
 174//   color("black") {
 175//       stroke([[-10,-5],[20,-5]], width=.25);
 176//       stroke([[-10,-6.2],[20,-6.2]], width=.25);
 177//       fwd(6.4) right(22) text("clearance", size=2.5);
 178//   }    
 179// Continues:
 180//   Another clearance requirement can present a serious problem when the number of teeth is low.  As the gear rotates, the
 181//   teeth may interfere with each other.  This may require undercutting the gear teeth to create space, which weakens the teeth.
 182//   Is is best to avoid gears with very small numbers of teeth when possible.  
 183// Figure(2D,Med,NoAxes,VPT=[0.042845,6.5338,-0.0144912],VPR=[0,0,0],VPD=126):  The green gear with only five teeth has a severe undercut, which weakens its teeth.  This undercut is necessary to avoid interference with the teeth from the other gear during rotation.  Note that the yellow rack tooth is deep into the undercut space.
 184//   ang=16;
 185//   rack2d(mod=5, teeth=3, bottom=15, pressure_angle=14.5, rounding=0);
 186//   left(2*PI*pitch_radius(mod=5, teeth=5)*ang/360)
 187//   color("lightgreen")
 188//     back(gear_dist(mod=5, teeth1=5, profile_shift1=0, teeth2=0))
 189//       zrot(ang)
 190//         spur_gear2d(mod=5, teeth=5, clearance=.00001, profile_shift=0, pressure_angle=14.5, shaft_diam=5);
 191
 192// Subsection: Corrected Gears and Profile Shifting
 193//   A solution to the problem of undercutting is to use profile shifting.  Profile shifting uses a different portion of the
 194//   involute curve to form the gear teeth, and this adjustment to the tooth form can eliminate undercutting, while
 195//   still allowing the gear to mesh with unmodified gears.  Profile shifting
 196//   changes the diameter at which the gear meshes so it no longer meshes at the pitch circle.  
 197//   A profile shift of `x`
 198//   will increase the mesh distance by approximately `x*m` where `m` is the gear module.  The exact adjustment,
 199//   which you compute with {{gear_dist()}}, is a complex calculation that depends on the profile shifts of both meshing gears.  This means that profile shifting
 200//   can also be used to fine tune the spacing between gears.  When the gear has many teeth a negative profile shift may
 201//   be able to bring the gears slightly closer together, while still avoiding undercutting.
 202//   Profile shifting also changes the effective pressure angle of the gear engagement.
 203// Figure(2D,Med,NoAxes): The green gear is a 7 tooth gear without profile shifting.  In yellow is the same gear, profile shifted.  Note that the teeth too longer narrow at their base.  Also note that the effective root circle has a larger radius, and the teeth are also longer.
 204//   spur_gear2d(mod=5, teeth=7);
 205//   color("green")spur_gear2d(mod=5, teeth=7, profile_shift=0);
 206// Continues:
 207//   The minimum number of teeth to avoid undercutting is 17 for a pressure angle of 20, but it is 32 for a pressure
 208//   angle of 14.5 degrees.  It can be computed as `2/(sin(alpha))^2` where `alpha` is the pressure angle.
 209//   By default, the gear modules produce corrected gears.  You can override this by specifying the profile shift
 210//   yourself.  A small undercut may be acceptable, for example: a rule of thumb indicates that gears as small as 14
 211//   teeth are OK with a 20 degree pressure angle, because the undercut is too small to weaken the teeth significantly.  
 212// Figure(2D,Med,NoAxes,VPT=[1.33179,10.6532,-0.0144912],VPR=[0,0,0],VPD=155.556): Basic five tooth gear form on the left.  Corrected gear with profile shifting on the right.  The profile shifted teeth lack the weak undercut section.  The axis of the corrected gear is shifted away from the mating rack.
 213//   $fn=32;
 214//   ang1=-20;
 215//   ang2=20;
 216//   color("blue")
 217//   left(2*PI*pitch_radius(mod=5, teeth=5)*ang1/360)
 218//   left(3*5*PI/2)
 219//     back(gear_dist(mod=5,teeth1=5,profile_shift1=0,teeth2=0,pressure_angle=14.5))
 220//       zrot(ang1)
 221//          spur_gear2d(mod=5, teeth=5, profile_shift=0, pressure_angle=14.5, shaft_diam=2);
 222//   color("green")
 223//   left(2*PI*pitch_radius(mod=5, teeth=5)*ang2/360)
 224//   right(3*5*PI/2)
 225//     back(gear_dist(mod=5, teeth1=5, teeth2=0,pressure_angle=14.5))
 226//       zrot(ang2)
 227//         spur_gear2d(mod=5, teeth=5, pressure_angle=14.5, shaft_diam=2);
 228//   rack2d(teeth=4, bottom=15, mod=5, pressure_angle=14.5);
 229// Continues:
 230//   Profile shifting brings with it another complication: in order to maintain the specified clearance, the tips of the
 231//   gear teeth need to be shortened.  The shortening factor depends on characteristics of both gears, so it cannot
 232//   be automatically incorporated.  (Consider the situation where one gear mates with multiple other gears.)  With modest
 233//   profile shifts, you can probably ignore this adjustment, but with more extreme profile shifts, it may be important.
 234//   You can compute the shortening parameter using {{gear_shorten()}}.  Note that the actual shortening distance is obtained
 235//   by scaling the shortening factor by the gear's module.  
 236// Figure(2D,Big,NoAxes,VPT=[55.8861,-4.31463,8.09832],VPR=[0,0,0],VPD=325.228): With large profile shifts the teeth need to be shortened or they don't have clearance in the valleys of the teeth in the meshing gear.  
 237//   teeth1=25;
 238//   teeth2=19;
 239//   mod=4;
 240//   ps1 = 0.75;
 241//   ps2 = 0.75;
 242//   d = gear_dist(mod=mod, teeth1,teeth2,0,ps1,ps2);
 243//   color("lightblue")
 244//     spur_gear2d(mod=mod,teeth=teeth1,profile_shift=ps1,gear_spin=-90);
 245//   right(d)
 246//     spur_gear2d(mod=mod,teeth=teeth2,profile_shift=ps2,gear_spin=-90);
 247//   right(9)stroke([[1.3*d/2,0],[d/2+4,0]], endcap2="arrow2",color="black");
 248//   fwd(2)right(d/2+25)color("black"){back(4)text("No clearance",size=6);
 249//                           fwd(4)text("at tooth tip",size=6);}
 250// Figure(2D,Big,NoAxes,VPT=[55.8861,-4.31463,8.09832],VPR=[0,0,0],VPD=325.228): Applying the correct shortening factor restores the clearance to its set value.  
 251//   teeth1=25;
 252//   teeth2=19;
 253//   mod=4;
 254//   ps1 = 0.75;
 255//   ps2 = 0.75;
 256//   d = gear_dist(mod=mod, teeth1,teeth2,0,ps1,ps2);
 257//   shorten=gear_shorten(teeth1,teeth2,0,ps1,ps2);
 258//   color("lightblue")
 259//     spur_gear2d(mod=mod,teeth=teeth1,profile_shift=ps1,shorten=shorten,gear_spin=-90);
 260//   right(d)
 261//     spur_gear2d(mod=mod,teeth=teeth2,profile_shift=ps2,shorten=shorten,gear_spin=-90);
 262//   right(9)stroke([[1.3*d/2,0],[d/2+4,0]], endcap2="arrow2",color="black");
 263//   fwd(2)right(d/2+25)color("black"){back(4)text("Normal",size=6);
 264//                           fwd(4)text("Clearance",size=6);}
 265// Subsection: Helical Gears
 266//   Helicals gears are a modification of spur gears.  They can replace spur gears in any application.  The teeth are cut
 267//   following a slanted, helical path.  The angled teeth engage more gradually than spur gear teeth, so they run more smoothly
 268//   and quietly.  A disadvantage of helical gears is that they have thrust along the axis of the gear that must be 
 269//   accomodated.  Helical gears also have more sliding friction between the meshing teeth compared to spur gears. 
 270// Figure(3D,Med,NoAxes,VPT=[3.5641,-7.03148,4.86523],VPR=[62.7,0,29.2],VPD=263.285): A Helical Gear
 271//   spur_gear(mod=5,teeth=18,pressure_angle=20,thickness=35,helical=-29,shaft_diam=15,slices=15);
 272// Continues:
 273//   Helical gears have the same compatibility requirements as spur gears, with the additional requirement that
 274//   the helical angles must be opposite each other, so a gear with a helical angle of 35 must mesh with one
 275//   that has an angle of −35.  The industry convention refers to these as left-handed and right handed.  In
 276//   this library, positive helical angles produce a left handed gear and negative angles produce a right handed gear.
 277// Figure(3D,Med,NoAxes,VPT=[73.6023,-29.9518,-12.535],VPR=[76,0,1.2],VPD=610): Left and right handed helical gears at 35 degrees.
 278//   spur_gear(mod=5, teeth=20, helical=35, thickness=70,slices=15);
 279//   right(150)
 280//   spur_gear(mod=5, teeth=20, helical=-35, thickness=70,slices=15);
 281//   down(22)
 282//   left(60)
 283//   fwd(220)
 284//   rot($vpr)
 285//   color("black")text3d("left handed    right handed",size=18);
 286//   down(52)
 287//   left(55)
 288//   fwd(220)
 289//   rot($vpr)
 290//   color("black")text3d("helical=35     helical=−35",size=18);
 291// Continues:
 292//   The pitch circle of a helical gear is larger compared to a spur gear
 293//   by the cosine of the helical angle, so you cannot simply drop helical gears in to replace spur gears without
 294//   making other adjustments.  This dependence does allow you to make 
 295//   make much bigger spacing adjustments than are possible with profile shifting—without changing the tooth count.
 296//   The {{gear_dist()}} function will also compute the appropriate gear spacing for helical gears.
 297//   The effective pressure angle of helical gears is larger than the nominal pressure angle.  This can make it possible
 298//   to avoid undercutting without having to use profile shifting, so smaller tooth count gears can be more effective
 299//   using the helical form. 
 300// Figure(Anim,Med,Frames=10,NoAxes,VPT=[43.8006,15.9214,3.52727],VPR=[62.3,0,20.3],VPD=446.129): Meshing compatible helical gears
 301//   zrot($t*360/18)
 302//     spur_gear(mod=5, teeth=18, pressure_angle=20, thickness=25, helical=-29, shaft_diam=15);
 303//   right(gear_dist(mod=5, teeth1=18, teeth2=18, helical=29))
 304//     zrot(360/18/2)
 305//       zrot(-$t*360/18)
 306//         spur_gear(mod=5, teeth=18, pressure_angle=20, thickness=25, helical=29, shaft_diam=15);
 307// Continues:
 308//   Helical gears can mesh in a second manner that is different from spur gears: they can turn on skew, or crossed axes.  These are also
 309//   sometimes called "screw gears".  The general requirement for two non-profile-shifted helical gears to mesh is that the angle 
 310//   between the gears' axes must equal the sum of the helical angles of the two gears, thus for parallel axes, the helical
 311//   angles must sum to zero.  If helical gears are profile shifted, then in addition to adjusting the distance between the
 312//   gears, a small adjustment in the angle is needed, so profile shifted gears won't mesh exactly at the sum of their angles.
 313//   The calculation for gear spacing is different for skew axis gears than for parallel gears, so you do this using {{gear_dist_skew()}},
 314//   and if you use profile shifting, then you can compute the angle using {{gear_skew_angle()}}. 
 315// Figure(Anim,Med,NoAxes,Frames=10,VPT=[44.765,6.09492,-3.01199],VPR=[55.7,0,33.2],VPD=401.289): Two helical gears meshing with axes at a 45 degree angle
 316//   dist = gear_dist_skew(mod=5, teeth1=18, teeth2=18, helical1=22.5,helical2=22.5);
 317//    axiscolor="darkgray";
 318//      down(10)color(axiscolor) cyl(d=15, l=145);
 319//       zrot($t*360/18)
 320//              color("lightblue")spur_gear(mod=5,teeth=18,pressure_angle=20,thickness=25,helical=22.5,shaft_diam=15);
 321//   right(dist)
 322//       xrot(45) {color(axiscolor)cyl(d=15,l=85);
 323//           zrot(360/18/2)
 324//               zrot(-$t*360/18)
 325//                   spur_gear(mod=5,teeth=18,pressure_angle=20,thickness=25,helical=22.5,shaft_diam=15);}
 326// Subsection: Herringbone Gears
 327//   The herringbone gear is made from two stacked helical gears with opposite angles.  This design addresses the problem
 328//   of axial forces that afflict helical gears by having one section that slopes to the
 329//   right and another that slopes to the left.  Herringbone gears also have the advantage of being self-aligning.
 330// Figure(3D,Med,NoAxes,VPT=[3.5641,-7.03148,4.86523],VPR=[62.7,0,29.2],VPD=263.285): A herringbone gear
 331//   spur_gear(mod=5, teeth=16, pressure_angle=20, thickness=35, helical=-20, herringbone=true, shaft_diam=15);
 332// Subsection: Ring Gears (Internal Gears)
 333//   A ring gear (or internal gear) is a gear where the teeth are on the inside of a circle.  Such gears must be mated
 334//   to a regular (external) gear, which rotates around the inside.
 335// Figure(2D,Med,NoAxes,VPT=[0.491171,1.07815,0.495977],VPR=[0,0,0],VPD=292.705): A interior or ring gear (yellow) with a mating spur gear (blue)
 336//   teeth1=18;
 337//   teeth2=30;
 338//   ps1=undef;
 339//   ps2=auto_profile_shift(teeth=teeth1);
 340//   mod=3;
 341//   d = gear_dist(mod=mod, teeth1=teeth1, teeth2=teeth2,profile_shift1=ps1, profile_shift2=ps2,helical=0, internal2=true);
 342//   ang = 0;
 343//     ring_gear2d(mod=mod, teeth=teeth2,profile_shift=ps2,helical=0,backing=4);
 344//     zrot(ang*360/teeth2)
 345//     color("lightblue")
 346//     fwd(d)
 347//        spur_gear2d(mod=mod, teeth=teeth1, profile_shift=ps1,gear_spin=-ang*360/teeth1,helical=0);
 348// Continues:
 349//    Ring gears are subject to all the usual mesh requirements: the teeth must be the same size, the pressure angles must
 350//    match and they must have opposite helical angles.  The {{gear_dist()}} function can give the center separation of
 351//    a ring gear and its mating spur gear.  Ring gears have additional complications that tend to arise when the number of
 352//    teeth is small or the teeth counts of the ring gear and spur gear are too close together.  The mating spur gear must
 353//    have few enough teeth so that the teeth don't interfere on the other side of the ring.  Very small spur gears can interfere
 354//    on the tips of the ring gear's teeth.  
 355// Figure(2D,Med,NoAxes,VPT=[-1.16111,0.0525612,0.495977],VPR=[0,0,0],VPD=213.382): The red regions show interference between the two gears: the 18 tooth spur gear does not fit inside the 20 tooth ring gear. 
 356//    teeth1=18;
 357//    teeth2=20;
 358//    ps1=undef;
 359//    ps2=auto_profile_shift(teeth=teeth1);
 360//    mod=3;
 361//    d = gear_dist(mod=mod, teeth1=teeth1, teeth2=teeth2,profile_shift1=ps1, profile_shift2=ps2,helical=0, internal2=true);
 362//    ang = 0;
 363//    color_overlaps(){
 364//      ring_gear2d(mod=mod, teeth=teeth2,profile_shift=ps2,helical=0,backing=4);
 365//      zrot(ang*360/teeth2)
 366//      fwd(d)
 367//         spur_gear2d(mod=mod, teeth=teeth1, profile_shift=ps1,gear_spin=-ang*360/teeth1,helical=0);
 368//    }
 369// Figure(2D,Big,NoAxes,VPT=[10.8821,-26.1226,-0.0685569],VPD=43.9335,VPR=[0,0,16.8]): Interference at teeth tips, shown in red, with a 5 tooth and 19 tooth gear.  
 370//    $fn=128;
 371//    teeth1=5;
 372//    teeth2=19;
 373//    ps1=0;
 374//    ps2=0;
 375//    mod=3;
 376//    d = gear_dist(mod=mod, teeth1=teeth1, teeth2=teeth2,profile_shift1=ps1, profile_shift2=ps2,helical=0, internal2=true);
 377//    ang = 1;
 378//    color_overlaps(){
 379//      ring_gear2d(mod=mod, teeth=teeth2,profile_shift=ps2,helical=0,backing=4);
 380//      zrot(ang*360/teeth2)
 381//      fwd(d)
 382//         spur_gear2d(mod=mod, teeth=teeth1, profile_shift=ps1,gear_spin=-ang*360/teeth1,helical=0);
 383//    }
 384// Continues:
 385//    The tooth tip interference can often be controlled using profile shifting of the ring gear, but another requirement is
 386//    that the profile shift of the ring gear must be at least as big as the profile shift of the mated spur gear.  In order
 387//    to ensure that this condition holds, you may need to use {{auto_profile_shift()}} to find the profile shift that is
 388//    automatically applied to the spur gear you want to use.
 389// Figure(2D,Med,VPT=[4.02885,-46.6334,1.23363],VPR=[0,0,6.3],VPD=75.2671,NoAxes): Ring gear without profile shifting doesn't have room for the fat profile shifted teeth of the 5-tooth spur gear, with overlaps shown in red.  
 390//    $fn=128;
 391//    teeth1=5;
 392//    teeth2=35;
 393//    ps1=undef;
 394//    ps2=0;
 395//    mod=3;
 396//    d=45-.7;
 397//    ang = .5;
 398//    color_overlaps(){
 399//      ring_gear2d(mod=mod, teeth=teeth2,profile_shift=ps2,helical=0,backing=4);
 400//      zrot(ang*360/teeth2)
 401//      fwd(d)
 402//         spur_gear2d(mod=mod, teeth=teeth1, profile_shift=ps1,gear_spin=-ang*360/teeth1,helical=0);
 403//    }
 404// Figure(2D,Med,VPT=[9.87969,-45.6706,0.60448],VPD=82.6686,VPR=[0,0,11],NoAxes): When the ring gear is profile shifted to match the spur gear, then the gears mesh without interference.  
 405//    $fn=128;
 406//    teeth1=5;
 407//    teeth2=35;
 408//    ps1=undef;
 409//    ps2=auto_profile_shift(teeth=teeth1);
 410//    mod=3;
 411//    d = gear_dist(mod=mod, teeth1=teeth1, teeth2=teeth2,profile_shift1=ps1, profile_shift2=ps2,helical=0, internal2=true);
 412//    ang = .5;
 413//    color_overlaps(){
 414//      ring_gear2d(mod=mod, teeth=teeth2,profile_shift=ps2,helical=0,backing=4);
 415//      zrot(ang*360/teeth2)
 416//      fwd(d)
 417//         spur_gear2d(mod=mod, teeth=teeth1, profile_shift=ps1,gear_spin=-ang*360/teeth1,helical=0);
 418//    }
 419// Figure(3D,Med,NoAxes,VPT=[2.48983,2.10149,0.658081],VPR=[70.4,0,123],VPD=237.091): A helical ring gear (yellow) mating with the compatible spur gear (blue)
 420//    $fn=128;
 421//    teeth1=18;
 422//    teeth2=30;
 423//    ps1=undef;
 424//    ps2=auto_profile_shift(teeth=teeth1);
 425//    mod=3;
 426//    d = gear_dist(mod=mod, teeth1=teeth1, teeth2=teeth2,profile_shift1=ps1, profile_shift2=ps2,helical=30, internal2=true);
 427//    ang = 0;
 428//      ring_gear(mod=mod, teeth=teeth2,profile_shift=ps2,backing=4,helical=30,thickness=15);
 429//      zrot(ang*360/teeth2)
 430//      color("lightblue")
 431//      fwd(d)
 432//         spur_gear(mod=mod, teeth=teeth1, profile_shift=ps1,gear_spin=-ang*360/teeth1,helical=-30,thickness=15);
 433// Subsection: Worm Drive
 434//   A worm drive is a gear system for connecting skew shafts at 90 degrees.  They offer higher load capacity compared to
 435//   crossed helical gears.  The assembly is driven by the "worm", which is a gear that resembles a screw.
 436//   Like a screw, it can have one, or several starts.  These starts correspond to teeth on a helical gear;
 437//   in fact, the worm can be regarded as a type of helical gear at a very extreme angle, where the teeth wrap
 438//   around the gear.  The worm mates with the "worm gear" which is also called the "worm wheel".  The worm gear
 439//   resembles a helical gear at a very slight angle.
 440// Figure(3D,Med,NoAxes,VPT=[38.1941,-7.67869,7.95996],VPR=[56.4,0,25],VPD=361.364):  Worm drive assembly, with worm on the left and worm gear (worm wheel) on the right.  When the worm turns its screwing action drives the worm gear.  
 441//   starts=2;
 442//   ps=0;
 443//   dist_ba=0;
 444//   gear_ba=0;
 445//     worm(
 446//          d=44, // mate_teeth=30,
 447//          circ_pitch=3*PI,
 448//          starts=starts,orient=BACK);
 449//   right(worm_dist(d=44,mod=3,teeth=30, starts=starts,profile_shift=ps,backlash=dist_ba))
 450//     zrot(360/30*.5) 
 451//       worm_gear(
 452//          circ_pitch=3*PI, 
 453//          teeth=30,
 454//          worm_diam=44,profile_shift=ps,
 455//          worm_starts=starts,backlash=gear_ba);
 456// Continues:
 457//   A close look at the worm gear reveals that it differs significantly from a helical or spur gear.
 458//   This gear is an "enveloping" gear, which is designed to follow the curved profile of the worm,
 459//   resulting in much better contact between the teeth of the worm and the teeth of the worm gear.
 460//   The worm shown above is a cylindrical worm, which is the most common type.
 461//   It is possible to design the worm to follow the curved shape of its mated gear, resulting
 462//   in an enveloping (also called "globoid") worm.  This type of worm makes better contact with
 463//   the worm gear, but is less often used due to manufacturing complexity and consequent expense.  
 464// Figure(3D,Big,NoAxes,VPT=[0,0,0],VPR=[192,0,180],VPD=172.84): A cylindrical worm appears on the left in green.  Note it's straight sides.  The enveloping (globoid) worm gears appears on the right in green.  Note that its sides curve so several teeth can mate with the worm gear, and it requires a complex tooth form
 465//   tilt=20;
 466//   starts=1;
 467//   ps=0;
 468//   pa=27;
 469//   dist_ba=0;
 470//   gear_ba=0;
 471//   xdistribute(spacing=25){
 472//      xflip()yrot(-tilt)  
 473//      union(){
 474//       color("lightgreen")
 475//         xrot(90) 
 476//         zrot(-90)
 477//         enveloping_worm(     mate_teeth=60,$fn=128,
 478//             d=14, pressure_angle=pa,  mod=3/2,
 479//             starts=starts);
 480//        right(worm_dist(d=14,mod=3/2,teeth=60, starts=starts,profile_shift=ps,backlash=dist_ba,pressure_angle=pa))
 481//          zrot(360/30*.25)
 482//            worm_gear(
 483//             mod=3/2,pressure_angle=pa,
 484//             teeth=60,crowning=0,
 485//             worm_diam=14,profile_shift=ps,
 486//             worm_starts=starts,backlash=gear_ba);
 487//      }
 488//      yrot(-tilt)
 489//      union(){
 490//       color("lightgreen")
 491//         xrot(90) 
 492//         zrot(-90)
 493//                                worm(l=43, $fn=128,
 494//             d=14, pressure_angle=pa, left_handed=true,
 495//             mod=3/2,//circ_pitch=3*PI/2,
 496//             starts=starts);
 497//        right(worm_dist(d=14,mod=3/2,teeth=60, starts=starts,profile_shift=ps,backlash=dist_ba,pressure_angle=pa))
 498//          zrot(360/30*.25)
 499//            worm_gear(
 500//             mod=3/2,pressure_angle=pa,
 501//             teeth=60,crowning=0,left_handed=true,
 502//             worm_diam=14,profile_shift=ps,
 503//             worm_starts=starts,backlash=gear_ba);
 504//      }  
 505//   }
 506// Continues:
 507//   As usual, a proper mesh requires that the pressure angles match and the teeth of the worm and worm gear
 508//   are the same size.  Additionally the worm gear must be constructed to match the diameter of the worm
 509//   and the number of starts on the worm.  Note that the number of starts changes the angle at of the 
 510//   teeth on the worm, and hence requires a change to the angle of teeth on the worm gear.  
 511//   Of course an enveloping worm needs to know the diameter of the worm gear; you provide this
 512//   information indirectly by giving the number of teeth on the worm gear.
 513//   The {{worm_dist()}} function will give the correct center spacing for the worm from its mating worm gear.  
 514//   .  
 515//   Worm drives are often "self-locking", which means that torque transmission can occur only from the worm to the worm gear,
 516//   so they must be driven by the worm.  Self-locking results from the small lead angle of the worm threads, which produces
 517//   high frictional forces at contact.  A multi-start worm has a higher lead angle and as a result is less likely
 518//   to be self-locking, so a multi-start worm can be chosen to avoid self-locking.
 519//   Since self-locking is associated with friction, self-locking drives have lower efficiency,
 520//   usually less than 50%.  Worm drive efficiency can exceed 90% if self-locking is not required.  One consideration
 521//   with self-locking systems is that if the worm gear moves a large mass and the drive is suddenly shut off, the
 522//   worm wheel is still trying to move due to inertia, which can create large loads that fracture the worm.
 523//   In such cases, the worm cannot be stopped abruptly but must rotate a little further (called "over travel")
 524//   after switching off the drive.
 525// Subsection: Bevel Gears
 526//   Bevel gearing is another way of dealing with intersecting gear shafts.  For bevel gears, the teeth centers lie on
 527//   the surface of an imaginary cone, which is the "pitch cone" of the bevel gear.  Two bevel gears can mesh when their pitch cone
 528//   apexes coincide and the cones touch along their length.  The teeth of bevel gears shrink as they get closer to the center of the gear.
 529//   Tooth dimensions and pitch diameter (the base of the pitch cone) are referenced to the outer end of the teeth.
 530//   Note that the pitch radius, computed the same was as for other gears, gives the radius of the pitch cone's base.  
 531//   Bevel gears can be made with straight teeth, analogous to spur gears, and with the
 532//   same disadvantage of sudden full contact that is noisy.  Spiral teeth are analogous to helical
 533//   teeth on cylindrical gears: the teeth engage gradually and smoothly, transmitting motion more smoothly
 534//   and quietly.  Also like helical gears, they have the disadvantage of introducing axial forces, and
 535//   usually they can only operate in one rotation direction.  
 536//   A third type of tooth is the zerol tooth, which has curved teeth like the spiral teeth,
 537//   but with a zero angle.  These share advantages of straight teeth and spiral teeth: they are quiet like
 538//   straight teeth but they lack the axial thrust of spiral gears, and they can operate in both directions.
 539//   They are also reportedly stronger than either spiral or bevel gears.
 540// Figure(3D,Med,VPT=[-5.10228,-3.09311,3.06426],VPR=[67.6,0,131.9],VPD=237.091,NoAxes): Straight tooth bevel gear with 45 degree angled teeth.  To get a gear like this you must specify a spiral angle of zero and a cutter radius of zero.  This gear would mate with a copy of itself and would change direction of rotation without changing the rotation rate. 
 541//   bevel_gear(mod=3,teeth=35,mate_teeth=35,face_width=20,spiral=0,cutter_radius=0);
 542// Figure(3D,Med,VPT=[-5.10228,-3.09311,3.06426],VPR=[67.6,0,131.9],VPD=237.091,NoAxes): Straight tooth bevel gear with 45 degree angled teeth.  A gear like this has a positive spiral angle, which determines how sloped the teeth are and a positive cutter radius, which determines how curved the teeth are.  
 543//   bevel_gear(mod=3,teeth=35,mate_teeth=35,face_width=20,slices=12);
 544// Figure(3D,Med,VPT=[-5.10228,-3.09311,3.06426],VPR=[67.6,0,131.9],VPD=237.091,NoAxes): Zerol tooth bevel gear with 45 degree angled teeth.  A gear like this has a spiral angle of zero, but a positive cutter radius, which determines how curved the teeth are.  
 545//   bevel_gear(mod=3,teeth=35,mate_teeth=35,face_width=20,spiral=0,slices=12);
 546// Continues:
 547//   Bevel gears have demanding requirements for successful mating of two gears.  Of course the tooth size
 548//   and pressure angle must match.  But beyond that, their pitch cones have to meet at their points.
 549//   This means that if you specify the tooth counts
 550//   of two gears and the desired shaft angle, then that information completely determines the pitch cones, and hence
 551//   the geometry of the gear.  You cannot simply mate two arbitary gears that have the same tooth size
 552//   and pressure angle like you can with helical gears: the gears must be designed in pairs to work together.
 553//   .
 554//   It is most common to design bevel gears so operate with their shafts at 90 degree angles, but
 555//   this is not required, and you can design pairs of bevel gears for any desired shaft angle.
 556//   Note, however, that some shaft angles may result in extreme bevel gear configurations.  
 557// Figure(3D,Med,NoAxes,VPT=[-1.42254,-1.98925,13.5702],VPR=[76,0,145],VPD=263.435): Two zerol bevel gears mated with shafts at 90 degrees.  
 558//   bevel_gear(mod=3,teeth=35,face_width=undef,spiral=0,mate_teeth=15,backing=3);
 559//   cyl(h=28,d=3,$fn=16,anchor=BOT);
 560//   color("lightblue")left(pitch_radius(mod=3,teeth=35))up(pitch_radius(mod=3,teeth=15))
 561//   yrot(90){zrot(360/15/2)bevel_gear(mod=3,teeth=15,face_width=undef,spiral=0,right_handed=true,mate_teeth=35);
 562//             cyl(h=57,d=3,$fn=16,anchor=BOT);}
 563// Figure(3D,Med,NoAxes,VPT=[2.01253,-0.673328,8.98056],VPD=263.435,VPR=[79.5,0,68.6]): Two zerol bevel gears mated with shafts at a 115.38 deg angle.  This is a planar bevel gear.  The axes intersect on the pitch base of the yellow gear.  If the blue gear is tipped slightly more its shaft will intersect the shaft of the yellow gear underneath that gear's pitch base, indicating an impossible angle for a normal bevel gear at this pair of teeth counts.
 564//   ang=acos(-15/35);
 565//   bevel_gear(mod=3,35,15,ang,spiral=0,face_width=undef,backing=5,anchor="apex")   
 566//     cyl(h=25,d=3,$fn=16,anchor=BOT);
 567//   color("lightblue")
 568//   xrot(ang)
 569//   bevel_gear(mod=3,15,35,ang,spiral=0,face_width=undef,right_handed=true,anchor="apex")
 570//     cyl(h=70,d=3,$fn=16,anchor=BOT);
 571// Continues:
 572//   In the above figure you can see a flat bevel gear.  Such a bevel gear is called a planar bevel gear or
 573//   sometimes also a crown gear.  The latter term may be confusing because it also refers to a similar looking
 574//   but very different type of gear that is described below.  A planar bevel gear can only mate with another
 575//   compatible bevel gear.  It has a degenerate cone with its apex on the gear itself, so the mating pinion gear cannot
 576//   mate at a 90 degree angle because if it did, its cone could not meet the center of the planar bevel gear.
 577//   If you request a larger shaft angle, the teeth of the bevel gear will tilt inward, producing an internal bevel gear.
 578//   Gears with this design are rarely used.  The mate of an interior gear is always an exterior gear.  
 579// Figure(Med,VPT=[-1.07698,0.67915,-2.25898],VPD=263.435,VPR=[69.7,0,49.3],NoAxes): Internal bevel gear (yellow) mated to an external bevel gear (blue) to achieve a 135 degree shaft angle.  
 580//   ang=135;
 581//   bevel_gear(mod=3,35,15,ang,spiral=0,cone_backing=false);
 582//      down(15)cyl(h=40,d=3,$fn=16,anchor=BOT);
 583//   color("lightblue")
 584//     back(pitch_radius(mod=3,teeth=35)+pitch_radius(mod=3,teeth=15))
 585//     xrot(ang,cp=[0,-pitch_radius(mod=3,teeth=15),0]){
 586//         bevel_gear(mod=3,15,35,ang,right_handed=true,spiral=0);
 587//               cyl(h=40,d=3,$fn=16,anchor=BOT);
 588//     }
 589// Subsection: Crown Gears (Face Gears)
 590//   Crown gears, sometimes called Face Crown Gears or just Face Gears, are gears with teeth pointing straight up so
 591//   the gear resembles a crown.  This type of gear is not the same as a bevel gear with vertical teeth, which would mate
 592//   to another bevel gear.  A crown gear mates to a spur gear at a ninety degree angle.  A feature of the crown gear assembly
 593//   is that the spur gear can shift along its axis without affecting the mesh.  
 594// Figure(Med,NoAxes,VPT=[-2.19006,-1.67419,-4.49379],VPR=[67.6,0,131.9],VPD=113.4): A Crown or Face gear with its mating spur gear in blue.  
 595//   crown_gear(mod=1, teeth=32, backing=3, face_width=7);
 596//   color("lightblue")
 597//   back(pitch_radius(mod=1,teeth=32)+7/2)
 598//     up(gear_dist(mod=1,teeth1=0,teeth2=9))spur_gear(mod=1, teeth=9,orient=BACK,thickness=7,gear_spin=360/9/2);
 599// Continues:
 600//   When constructing a crown gear you need to make it with the same given pressure and and tooth size as
 601//   the spur gear you wish to mate to it.  However, the teeth of a crown gear have pressure angle that varies
 602//   along the width of the tooth.  The vertical separation of the spur gear from the crown gear is given
 603//   by {{gear_dist()}} where you treat the crown gear as a rack.  The inner radius of the teeth on the
 604//   crown gear is the pitch radius determined by the gear's tooth size and number of teeth.  The face width
 605//   of a crown gear is limited by geometry, so if you make it too large you will get an error.
 606//   .
 607//   Note that the geometry of these crown gears is tricky and not well documented by sources we have found.
 608//   If you know something about crown gears that could improve the implementation, please open an issue
 609//   on github.  
 610// Section: Backlash (Fitting Real Gears Together)
 611//   You may have noticed that the example gears shown fit together perfectly, making contact on both sides of
 612//   the teeth.  Real gears need space between the teeth to prevent the gears from jamming, to provide space
 613//   for lubricant, and to provide allowance for fabrication error.  This space is called backlash.  Excessive backlash
 614//   is undesirable, especially if the drive reverses frequently.
 615//   .
 616//   Backlash can be introduced in two ways.  One is to make the teeth narrower, so the gaps between the teeth are
 617//   larger than the teeth.  Alternatively, you can move the gears farther apart than their ideal spacing.
 618//   Backlash can be measured in several different ways.  The gear modules in this library accept a backlash
 619//   parameter which specifies backlash as a circular distance at the pitch circle.  The modules narrow
 620//   the teeth by the amount specified, which means the spaces between the teeth grow larger.  Of course, if you apply
 621//   backlash to both gears then the total backlash in the system is the combined amount from both gears.
 622//   Usually it is best to apply backlash symmetrically to both gears, but if one gear is very small it may
 623//   be better to place the backlash entirely on the larger gear to avoid weakening the teeth of the small gear.  
 624// Figure(2D,Big,VPT=[4.5244,64.112,0.0383045],VPR=[0,0,0],VPD=48.517,NoAxes): Backlash narrows the teeth by the specified length along the pitch circle.  Below the ideal gear appears in the lighter color and the darker color shows the same gear with a very large backlash, which appears with half of the backlash on either side of the tooth.  
 625//   teeth1=20;
 626//   mod=5;
 627//   r1 = pitch_radius(mod=mod,teeth=teeth1,helical=40);
 628//   bang=4/(2*PI*r1) * 360 ;
 629//   zrot(-180/teeth1*.5){
 630//   color("white")
 631//   dashed_stroke(arc(r=r1, n=30, angle=[80,110]), width=.05);
 632//     spur_gear2d(mod=mod, teeth=teeth1,backlash=0+.5*0,profile_shift="auto",gear_spin=180/teeth1*.5,helical=40);
 633//   %spur_gear2d(mod=mod, teeth=teeth1,backlash=4+.5*0,profile_shift="auto",gear_spin=180/teeth1*.5,helical=40);
 634//   color("black")stroke(arc(n=32,r=r1,angle=[90+bang/2,90]),width=.1,endcaps="arrow2");
 635//   }    
 636//   color("black")back(r1+.25)right(5.5)text("backlash/2",size=1);
 637// Figure(2D,Med,VPT=[0.532987,50.0891,0.0383045],VPR=[0,0,0],VPD=53.9078): Here two gears appear together with a more reasonable backlash applied to both gears. Again the lighter color shows the ideal gears and the darker shade shows the gear with backlash.  Note that in this example, backlash is present on both of the meshing gears, so the total backlash of the system is the combined backlash from both gears.
 638//   teeth1=20;teeth2=33;
 639//   mod=5;
 640//   ha=0;
 641//   r1 = pitch_radius(mod=mod,teeth=teeth1,helical=ha);
 642//   r2=pitch_radius(mod=mod,teeth=teeth2,helical=ha);
 643//   bang=4/(2*PI*r1) * 360 ;
 644//   
 645//   back(r1+pitch_radius(mod=mod,teeth=teeth2,helical=ha)){
 646//      spur_gear2d(mod=mod, teeth=teeth2,backlash=.5*0,helical=ha,gear_spin=-180/teeth2/2);
 647//      %spur_gear2d(mod=mod, teeth=teeth2,backlash=1,helical=ha,gear_spin=-180/teeth2/2);
 648//      }
 649//   {
 650//     spur_gear2d(mod=mod, teeth=teeth1,backlash=0+.5*0,profile_shift=0,gear_spin=180/teeth1*.5,helical=ha);
 651//   %spur_gear2d(mod=mod, teeth=teeth1,backlash=1+.5*0,profile_shift=0,gear_spin=180/teeth1*.5,helical=ha);
 652//   *color("white"){
 653//     dashed_stroke(arc(r=r1, n=30, angle=[80,110]), width=.05);
 654//     back(r1+r2)
 655//        dashed_stroke(arc(r=r2, n=30, angle=[-80,-110]), width=.05);
 656//   }
 657//   //color("black")stroke(arc(n=32,r=r1,angle=[90+bang/2,90]),width=.1,endcaps="arrow2");
 658//   }
 659// Figure(2D,Med,VPT=[0.532987,50.0891,0.0383045],VPR=[0,0,0],VPD=53.9078): Here the same gears as in the previous figure appear with backlash applied using the `backlash` parameter to {{gear_dist()}} to shift them apart.  The original ideal gears are in the lighter shade and the darker colored gears have been separated to create the backlash.  
 660//   teeth1=20;teeth2=33;
 661//   mod=5;
 662//   ha=0;
 663//   r1 = pitch_radius(mod=mod,teeth=teeth1,helical=ha);
 664//   r2 = pitch_radius(mod=mod,teeth=teeth2,helical=ha);
 665//   bang=4/(2*PI*r1) * 360 ;
 666//   shift = 1 * cos(ha)/2/tan(20);
 667//   back(r1+pitch_radius(mod=mod,teeth=teeth2,helical=ha)){
 668//      zrot(-180/teeth2/2){
 669//      %back(shift)spur_gear2d(mod=mod, teeth=teeth2,backlash=0,helical=ha);
 670//      spur_gear2d(mod=mod, teeth=teeth2,backlash=0,helical=ha);
 671//      }
 672//      }
 673//   zrot(180/teeth1*.5){
 674//     %fwd(shift)spur_gear2d(mod=mod, teeth=teeth1,backlash=0+.5*0,profile_shift=0,helical=ha);     
 675//     spur_gear2d(mod=mod, teeth=teeth1,backlash=0,profile_shift=0,helical=ha);
 676//   }  
 677
 678// Section: Gears
 679
 680// Function&Module: spur_gear()
 681// Synopsis: Creates a spur gear, helical gear, or internal ring gear.
 682// SynTags: Geom, VNF
 683// Topics: Gears, Parts
 684// See Also: rack(), spur_gear(), spur_gear2d(), bevel_gear()
 685// Usage: As a Module
 686//   spur_gear(circ_pitch, teeth, [thickness], [helical=], [pressure_angle=], [profile_shift=], [backlash=], [shaft_diam=], [hide=], [clearance=], [slices=], [internal=], [herringbone=]) [ATTACHMENTS];
 687//   spur_gear(mod=|diam_pitch=, teeth=, [thickness=], ...) [ATTACHMENTS];
 688// Usage: As a Function
 689//   vnf = spur_gear(circ_pitch, teeth, [thickness], ...);
 690//   vnf = spur_gear(mod=|diam_pitch=, teeth=, [thickness=], ...);
 691// Description:
 692//   Creates a involute spur gear, helical gear, herringbone gear, or a mask for an internal ring gear.
 693//   For more information about gears, see [A Quick Introduction to Gears](gears.scad#section-a-quick-introduction-to-gears).
 694//   You must specify the teeth size using either `mod=`, `circ_pitch=` or `diam_pitch=`, and you
 695//   must give the number of teeth of the gear.   Spur gears have straight teeth and
 696//   mesh together on parallel shafts without creating any axial thrust.  The teeth engage suddenly across their
 697//   entire width, creating stress and noise.  Helical gears have angled teeth and engage more gradually, so they
 698//   run more smoothly and quietly, however they do produce thrust along the gear axis.  This can be
 699//   circumvented using herringbone or double helical gears, which have no axial thrust and also self-align.
 700//   Helical gears can mesh along shafts that are not parallel, where the angle between the shafts is
 701//   the sum of the helical angles of the two gears.
 702//   .
 703//   The module creates the gear in the XY plane, centered on the origin, with one tooth centered on the positive Y axis.
 704//   In order for two gears to mesh they must have the same tooth size and `pressure_angle`, and
 705//   generally the helical angles should be of opposite sign.  
 706//   The usual pressure angle (and default) is 20 degrees.  Another common value is 14.5 degrees.
 707//   Ideally the teeth count of two meshing gears will be relatively prime because this ensures that
 708//   every tooth on one gear will meet every tooth on the other, creating even wear.
 709//   .
 710//   The "pitch circle" of the gear is a reference circle where the circular pitch is defined that
 711//   is used to construct the gear.  It runs approximately through the centers of the teeth.  
 712//   Two basic gears will mesh when their pitch circles are tangent.  Anchoring for these gears is
 713//   done on the pitch circle by default, so basic gears can be meshed using anchoring.
 714//   However, when a gear has a small number of teeth, the basic gear form will result in undercutting,
 715//   which weakens the teeth.  To avoid this, profile shifting is automatically applied and in this
 716//   case, the distance between the gears is a complicated calculation and must be determined using {{gear_dist()}}.  
 717//   If you wish to override this correction, you can use `profile_shift=0`, or set it to a specific
 718//   value like 0.5.  Another complication with profile shifted gears is that the tips may be too long,
 719//   which can eat into the clearance space.  To address this problem you can use the `shorten` parameter,
 720//   which you can compute using {{gear_shorten()}}.
 721//   .
 722//   Helical gears can mesh with skew or crossed axes, a configuration sometimes called "screw gears".  
 723//   Without profile shifting, that angle is the  sum of the helical angles.
 724//   With profile shifting it is slightly different and is given by {{gear_skew_angle()}}.
 725//   These gears still mesh on the pitch circle when they are not profile shifted, but the correction to
 726//   gear separation for a proper mesh of profile shifted gears is different for skew gears and is
 727//   computed using {{gear_dist_skew()}}. 
 728//   .
 729//   To create space for gears to mesh in practice you will need to set a positive value for backlash, or
 730//   use the `backlash` argument to {{gear_dist()}}.  
 731// Arguments:
 732//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
 733//   teeth = Total number of teeth around the entire perimeter
 734//   thickness = Thickness of gear.  Default: 10 
 735//   ---
 736//   mod = The module of the gear (pitch diameter / teeth)
 737//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
 738//   helical = Teeth spiral around the gear at this angle, positive for left handed, negative for right handed.  Default: 0
 739//   herringbone = If true, and helical is set, creates a herringbone gear.  Default: False
 740//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees.  Default: 20
 741//   profile_shift = Profile shift factor x.  Default: "auto"
 742//   shorten = Shorten gear tips by the module times this value.  Needed for large profile shifted gears.  Default: 0
 743//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
 744//   shaft_diam = Diameter of the hole in the center.  Default: 0 (no shaft hole)
 745//   hide = Number of teeth to delete to make this only a fraction of a circle.  Default: 0
 746//   gear_spin = Rotate gear and children around the gear center, regardless of how gear is anchored.  Default: 0
 747//   clearance = Clearance gap at the bottom of the inter-tooth valleys.  Default: mod/4
 748//   slices = Number of vertical layers to divide gear into.  Useful for refining gears with `helical`.
 749//   internal = If true, create a mask for difference()ing from something else.
 750//   atype = Set to "root", "tip" or "pitch" to determine anchoring circle.  Default: "pitch"
 751//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 752//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 753//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 754// Side Effects:
 755//   If internal is true then the default tag is "remove"
 756// Anchor Types:
 757//   root = anchor on the root circle
 758//   pitch = anchor on the pitch circle (default)
 759//   tip = anchor on the tip circle
 760// Example: Spur Gear
 761//   spur_gear(circ_pitch=5, teeth=20, thickness=8, shaft_diam=5);
 762// Example: Metric Gear
 763//   spur_gear(mod=2, teeth=20, thickness=8, shaft_diam=5);
 764// Example: Helical Gear
 765//   spur_gear(
 766//       circ_pitch=5, teeth=20, thickness=10,
 767//       shaft_diam=5, helical=-30, slices=12,
 768//       $fa=1, $fs=1
 769//   );
 770// Example: Herringbone Gear
 771//   spur_gear(
 772//       circ_pitch=5, teeth=20, thickness=10, shaft_diam=5,
 773//       helical=30, herringbone=true, slices=5
 774//   );
 775// Example(Med,VPT=[-0.0213774,2.42972,-0.2709],VPR=[36.1,0,20.1],VPD=74.3596): Effects of Profile Shifting.
 776//   circ_pitch=5; teeth=7; thick=10; shaft=5; strokewidth=0.2;
 777//   pr = pitch_radius(circ_pitch, teeth);
 778//   left(10) {
 779//       profile_shift = 0;
 780//       d = gear_dist(circ_pitch=circ_pitch,teeth,0,profile_shift1=profile_shift);
 781//       back(d) spur_gear(circ_pitch, teeth, thick, shaft, profile_shift=profile_shift);
 782//       rack(circ_pitch, teeth=3, thickness=thick, orient=BACK);
 783//       color("black") up(thick/2) linear_extrude(height=0.1) {
 784//           back(d) dashed_stroke(circle(r=pr), width=strokewidth, closed=true);
 785//           dashed_stroke([[-7.5,0],[7.5,0]], width=strokewidth);
 786//       }
 787//   }
 788//   right(10) {
 789//       profile_shift = 0.59;
 790//       d = gear_dist(circ_pitch=circ_pitch,teeth,0,profile_shift1=profile_shift);
 791//       back(d) spur_gear(circ_pitch, teeth, thick, shaft, profile_shift=profile_shift);
 792//       rack(circ_pitch, teeth=3, thickness=thick, orient=BACK);
 793//       color("black") up(thick/2) linear_extrude(height=0.1) {
 794//           back(d)
 795//               dashed_stroke(circle(r=pr), width=strokewidth, closed=true);
 796//           dashed_stroke([[-7.5,0],[7.5,0]], width=strokewidth);
 797//       }
 798//   }
 799// Example(Anim,Med,NoAxes,Frames=8,VPT=[0,30,0],VPR=[0,0,0],VPD=300): Assembly of Gears
 800//   $fn=12;
 801//   n1 = 11; //red gear number of teeth
 802//   n2 = 20; //green gear
 803//   n3 = 6;  //blue gear
 804//   n4 = 16; //orange gear
 805//   n5 = 9;  //gray rack
 806//   circ_pitch = 9; //all meshing gears need the same `circ_pitch` (and the same `pressure_angle`)
 807//   thickness    = 6;
 808//   hole         = 3;
 809//   rack_base    = 12;
 810//   d12 = gear_dist(circ_pitch=circ_pitch,teeth1=n1,teeth2=n2);
 811//   d13 = gear_dist(circ_pitch=circ_pitch,teeth1=n1,teeth2=n3);
 812//   d14 = gear_dist(circ_pitch=circ_pitch,teeth1=n1,teeth2=n4);
 813//   d1r = gear_dist(circ_pitch=circ_pitch,teeth1=n1,teeth2=0);
 814//   a1 =  $t * 360 / n1;
 815//   a2 = -$t * 360 / n2 + 180/n2;
 816//   a3 = -$t * 360 / n3 - 3*90/n3;
 817//   a4 = -$t * 360 / n4 - 3.5*180/n4;
 818//   color("#f77")              zrot(a1) spur_gear(circ_pitch,n1,thickness,hole);
 819//   color("#7f7") back(d12)  zrot(a2) spur_gear(circ_pitch,n2,thickness,hole);
 820//   color("#77f") right(d13) zrot(a3) spur_gear(circ_pitch,n3,thickness,hole);
 821//   color("#fc7") left(d14)  zrot(a4) spur_gear(circ_pitch,n4,thickness,hole,hide=n4-3);
 822//   color("#ccc") fwd(d1r) right(circ_pitch*$t)
 823//       rack(pitch=circ_pitch,teeth=n5,thickness=thickness,width=rack_base,anchor=CENTER,orient=BACK);
 824// Example(NoAxes,VPT=[1.13489,-4.48517,1.04995],VPR=[55,0,25],VPD=139.921): Helical gears meshing with non-parallel shafts
 825//   ang1 = 30;
 826//   ang2 = 10;
 827//   circ_pitch = 5;
 828//   n = 20;
 829//   dist = gear_dist_skew(
 830//      circ_pitch=circ_pitch,
 831//      teeth1=n, teeth2=n,
 832//      helical1=ang1, helical2=ang2);
 833//   left(dist/2) spur_gear(
 834//          circ_pitch, teeth=n, thickness=10,
 835//          shaft_diam=5, helical=ang1, slices=12,
 836//          gear_spin=-90
 837//      );
 838//   right(dist/2)
 839//   xrot(ang1+ang2)
 840//   spur_gear(
 841//          circ_pitch=circ_pitch, teeth=n, thickness=10,
 842//          shaft_diam=5, helical=ang2, slices=12,
 843//          gear_spin=90-180/n
 844//      );
 845// Example(Anim,Big,NoAxes,Frames=36,VPT=[0,0,0],VPR=[55,0,25],VPD=220): Planetary Gear Assembly
 846//   $fn=128;
 847//   rteeth=56; pteeth=16; cteeth=24;
 848//   circ_pitch=5; thick=10; pa=20;
 849//   gd = gear_dist(circ_pitch=circ_pitch, cteeth, pteeth);
 850//   ring_gear(
 851//       circ_pitch=circ_pitch,
 852//       teeth=rteeth,
 853//       thickness=thick,
 854//       pressure_angle=pa);
 855//   for (a=[0:3]) {
 856//       zrot($t*90+a*90) back(gd) {
 857//           color("green")
 858//           spur_gear(
 859//               circ_pitch=circ_pitch,
 860//               teeth=pteeth,
 861//               thickness=thick,
 862//               shaft_diam=5,
 863//               pressure_angle=pa,
 864//               spin=-$t*90*rteeth/pteeth);
 865//       }
 866//   }
 867//   color("orange")
 868//   zrot($t*90*rteeth/cteeth+$t*90+180/cteeth)
 869//   spur_gear(
 870//       circ_pitch=circ_pitch,
 871//       teeth=cteeth,
 872//       thickness=thick,
 873//       shaft_diam=5,
 874//       pressure_angle=pa);
 875
 876function spur_gear(
 877    circ_pitch,
 878    teeth,
 879    thickness,
 880    shaft_diam = 0,
 881    hide = 0,
 882    pressure_angle,
 883    clearance,
 884    backlash = 0.0,
 885    helical,
 886    interior,
 887    internal,
 888    profile_shift="auto",
 889    slices,
 890    herringbone=false,
 891    shorten=0,
 892    diam_pitch,
 893    mod,
 894    pitch,
 895    gear_spin = 0,
 896    atype = "pitch", 
 897    anchor = CENTER,
 898    spin = 0,
 899    orient = UP
 900) =
 901    let(  
 902        dummy = !is_undef(interior) ? echo("In spur_gear(), the argument 'interior=' has been deprecated, and may be removed in the future.  Please use 'internal=' instead."):0,
 903        internal = first_defined([internal,interior,false]),
 904        circ_pitch = _inherit_gear_pitch("spur_gear()", pitch, circ_pitch, diam_pitch, mod),
 905        PA = _inherit_gear_pa(pressure_angle),
 906        helical = _inherit_gear_helical(helical, invert=!internal),
 907        thickness = _inherit_gear_thickness(thickness)
 908    )
 909    assert(is_integer(teeth) && teeth>3)
 910    assert(is_finite(thickness) && thickness>0)
 911    assert(is_finite(shaft_diam) && shaft_diam>=0)
 912    assert(is_integer(hide) && hide>=0 && hide<teeth)
 913    assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
 914    assert(clearance==undef || (is_finite(clearance) && clearance>=0))
 915    assert(is_finite(backlash) && backlash>=0)
 916    assert(is_finite(helical) && abs(helical)<90)
 917    assert(is_bool(herringbone))
 918    assert(slices==undef || (is_integer(slices) && slices>0))
 919    assert(is_finite(gear_spin))
 920    let(
 921        profile_shift = auto_profile_shift(teeth,PA,helical,profile_shift=profile_shift),
 922        pr = pitch_radius(circ_pitch, teeth, helical),
 923        or = outer_radius(circ_pitch, teeth, helical=helical, profile_shift=profile_shift, internal=internal,shorten=shorten),
 924        rr = _root_radius(circ_pitch, teeth, clearance, profile_shift=profile_shift, internal=internal),
 925        anchor_rad = atype=="pitch" ? pr
 926                   : atype=="tip" ? or
 927                   : atype=="root" ? rr
 928                   : assert(false,"atype must be one of \"root\", \"tip\" or \"pitch\""),
 929        circum = 2 * PI * pr,
 930        twist = 360*thickness*tan(helical)/circum,
 931        slices = default(slices, ceil(twist/360*segs(pr)+1)),
 932        rgn = spur_gear2d(
 933                circ_pitch = circ_pitch,
 934                teeth = teeth,
 935                pressure_angle = PA,
 936                hide = hide,
 937                helical = helical,
 938                clearance = clearance,
 939                backlash = backlash,
 940                internal = internal,
 941                shorten = shorten,
 942                profile_shift = profile_shift,
 943                shaft_diam = shaft_diam
 944            ),
 945        rvnf = herringbone
 946          ? zrot(twist/2, p=linear_sweep(rgn, height=thickness, twist=twist, slices=slices, center=true))
 947          : let(
 948                wall_vnf = linear_sweep(rgn, height=thickness/2, twist=twist/2, slices=ceil(slices/2), center=false, caps=false),
 949                cap_vnf = vnf_from_region(rgn, transform=up(thickness/2)*zrot(twist/2))
 950            )
 951            vnf_join([
 952                wall_vnf, zflip(p=wall_vnf),
 953                cap_vnf,  zflip(p=cap_vnf),
 954            ]),
 955        vnf = zrot(gear_spin, p=rvnf)
 956    ) reorient(anchor,spin,orient, h=thickness, r=anchor_rad, p=vnf);
 957
 958
 959module spur_gear(
 960    circ_pitch,
 961    teeth,
 962    thickness,
 963    shaft_diam = 0,
 964    hide = 0,
 965    pressure_angle,
 966    clearance,
 967    backlash = 0.0,
 968    helical,
 969    internal,
 970    interior,
 971    profile_shift="auto",
 972    slices,
 973    herringbone=false,
 974    shorten=0,
 975    pitch,
 976    diam_pitch,
 977    mod,
 978    atype="pitch",
 979    gear_spin = 0,
 980    anchor = CENTER,
 981    spin = 0,
 982    orient = UP
 983) {
 984    dummy = !is_undef(interior) ? echo("In spur_gear(), the argument 'interior=' has been deprecated, and may be removed in the future.  Please use 'internal=' instead."):0;
 985    internal = first_defined([internal,interior,false]);
 986    circ_pitch = _inherit_gear_pitch("spur_gear()", pitch, circ_pitch, diam_pitch, mod);
 987    PA = _inherit_gear_pa(pressure_angle);
 988    helical = _inherit_gear_helical(helical, invert=!internal);
 989    thickness = _inherit_gear_thickness(thickness);
 990    checks =
 991        assert(is_integer(teeth) && teeth>3)
 992        assert(is_finite(thickness) && thickness>0)
 993        assert(is_finite(shaft_diam) && shaft_diam>=0)
 994        assert(is_integer(hide) && hide>=0 && hide<teeth)
 995        assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
 996        assert(clearance==undef || (is_finite(clearance) && clearance>=0))
 997        assert(is_finite(backlash) && backlash>=0)
 998        assert(is_finite(helical) && abs(helical)<90)
 999        assert(is_bool(herringbone))
1000        assert(slices==undef || (is_integer(slices) && slices>0))
1001        assert(is_finite(gear_spin));
1002    profile_shift = auto_profile_shift(teeth,PA,helical,profile_shift=profile_shift);
1003    pr = pitch_radius(circ_pitch, teeth, helical);
1004    or = outer_radius(circ_pitch, teeth, helical=helical, profile_shift=profile_shift, internal=internal,shorten=shorten);
1005    rr = _root_radius(circ_pitch, teeth, clearance, profile_shift=profile_shift, internal=internal);
1006    anchor_rad = atype=="pitch" ? pr
1007               : atype=="tip" ? or
1008               : atype=="root" ? rr
1009               : assert(false,"atype must be one of \"root\", \"tip\" or \"pitch\"");
1010    circum = 2 * PI * pr;
1011    twist = 360*thickness*tan(helical)/circum;
1012    slices = default(slices, ceil(twist/360*segs(pr)+1));
1013    default_tag("remove", internal) {
1014        attachable(anchor,spin,orient, r=anchor_rad, l=thickness) {
1015            zrot(gear_spin)
1016            if (herringbone) {
1017                zflip_copy() down(0.01)
1018                linear_extrude(
1019                    height=thickness/2+0.01, center=false,
1020                    twist=twist/2, slices=ceil(slices/2),
1021                    convexity=teeth/2
1022                ) {
1023                    spur_gear2d(
1024                        circ_pitch = circ_pitch,
1025                        teeth = teeth,
1026                        pressure_angle = PA,
1027                        hide = hide,
1028                        helical = helical,
1029                        clearance = clearance,
1030                        backlash = backlash,
1031                        internal = internal,
1032                        shorten = shorten, 
1033                        profile_shift = profile_shift,
1034                        shaft_diam = shaft_diam
1035                    );
1036                }
1037            } else {
1038                zrot(twist/2)
1039                linear_extrude(
1040                    height=thickness, center=true,
1041                    twist=twist, slices=slices,
1042                    convexity=teeth/2
1043                ) {
1044                    spur_gear2d(
1045                        circ_pitch = circ_pitch,
1046                        teeth = teeth,
1047                        pressure_angle = PA,
1048                        hide = hide,
1049                        helical = helical,
1050                        clearance = clearance,
1051                        backlash = backlash,
1052                        internal = internal,
1053                        profile_shift = profile_shift,
1054                        shaft_diam = shaft_diam
1055                    );
1056                }
1057            }
1058            union() {
1059                $parent_gear_type = "spur";
1060                $parent_gear_pitch = circ_pitch;
1061                $parent_gear_teeth = teeth;
1062                $parent_gear_pa = PA;
1063                $parent_gear_helical = helical;
1064                $parent_gear_thickness = thickness;
1065                union() children();
1066            }
1067        }
1068    }
1069}
1070
1071
1072// Function&Module: spur_gear2d()
1073// Synopsis: Creates a 2D spur gear or internal ring gear.
1074// SynTags: Geom, Region
1075// Topics: Gears, Parts
1076// See Also: rack(), spur_gear(), spur_gear2d(), bevel_gear()
1077// Usage: As Module
1078//   spur_gear2d(circ_pitch, teeth, [pressure_angle=], [profile_shift=], [shorten=], [hide=], [shaft_diam=], [clearance=], [backlash=], [internal=]) [ATTACHMENTS];
1079//   spur_gear2d(mod=|diam_pitch=, teeth=, [pressure_angle=], [profile_shift=], [shorten=], [hide=], [shaft_diam=], [clearance=], [backlash=], [internal=]) [ATTACHMENTS];
1080// Usage: As Function
1081//   rgn = spur_gear2d(circ_pitch, teeth, [pressure_angle=], [profile_shift=], [shorten=], [hide=], [shaft_diam=], [clearance=], [backlash=], [internal=]);
1082//   rgn = spur_gear2d(mod=, teeth=, [pressure_angle=], [profile_shift=], [shorten=], [hide=], [shaft_diam=], [clearance=], [backlash=], [internal=]);
1083// Description:
1084//   Creates a 2D involute spur gear, or a mask for an internal ring gear.
1085//   For more information about gears, see [A Quick Introduction to Gears](gears.scad#section-a-quick-introduction-to-gears).
1086//   You must specify the teeth size using either `mod=`, `circ_pitch=` or `diam_pitch=`, and you
1087//   must give the number of teeth.  
1088//   .
1089//   The module creates the gear in centered on the origin, with one tooth centered on the positive Y axis.
1090//   In order for two gears to mesh they must have the same tooth size and `pressure_angle`
1091//   The usual pressure angle (and default) is 20 degrees.  Another common value is 14.5 degrees.
1092//   Ideally the teeth count of two meshing gears will be relatively prime because this ensures that
1093//   every tooth on one gear will meet every tooth on the other, creating even wear.
1094//   .
1095//   The "pitch circle" of the gear is a reference circle where the circular pitch is defined that
1096//   is used to construct the gear.  It runs approximately through the centers of the teeth.  
1097//   Two basic gears will mesh when their pitch circles are tangent.  Anchoring for these gears is
1098//   done on the pitch circle by default, so basic gears can be meshed using anchoring.
1099//   However, when a gear has a small number of teeth, the basic gear form will result in undercutting,
1100//   which weakens the teeth.  To avoid this, profile shifting is automatically applied and in this
1101//   case, the distance between the gears is a complicated calculation and must be determined using {{gear_dist()}}.  
1102//   If you wish to override this correction, you can use `profile_shift=0`, or set it to a specific
1103//   value like 0.5.  Another complication with profile shifted gears is that the tips may be too long,
1104//   which can eat into the clearance space.  To address this problem you can use the `shorten` parameter,
1105//   which you can compute using {{gear_shorten()}}.
1106//   .
1107//   To create space for gears to mesh in practice you will need to set a positive value for backlash, or
1108//   use the `backlash` argument to {{gear_dist()}}.  
1109// Arguments:
1110//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
1111//   teeth = Total number of teeth around the spur gear.
1112//   ---
1113//   mod = The module of the gear (pitch diameter / teeth)
1114//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
1115//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees.
1116//   profile_shift = Profile shift factor x.  Default: "auto"
1117//   shorten = Shorten gear tips by the module times this value.  Needed for large profile shifted gears.  Default: 0
1118//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
1119//   helical = Adjust teeth form (stretch out the teeth) to give the cross section of a gear with this helical angle.  Default: 0
1120//   hide = Number of teeth to delete to make this only a fraction of a circle
1121//   gear_spin = Rotate gear and children around the gear center, regardless of how gear is anchored.  Default: 0
1122//   clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear.  Default: mod/4
1123//   internal = If true, create a mask for difference()ing from something else.
1124//   shaft_diam = If given, the diameter of the central shaft hole.
1125//   atype = Set to "root", "tip" or "pitch" to determine anchoring circle.  Default: "pitch"
1126//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1127//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1128// Side Effects:
1129//   If internal is true then the default tag is "remove"
1130// Anchor Types:
1131//   root = anchor on the root circle
1132//   pitch = anchor on the pitch circle (default)
1133//   tip = anchor on the tip circle
1134// Example(2D): Typical Gear Shape
1135//   spur_gear2d(circ_pitch=5, teeth=20, shaft_diam=5);
1136// Example(2D): By Metric Module
1137//   spur_gear2d(mod=2, teeth=20, shaft_diam=5);
1138// Example(2D): By Imperial Gear Pitch
1139//   spur_gear2d(diam_pitch=10, teeth=20, shaft_diam=5);
1140// Example(2D): Lower Pressure Angle
1141//   spur_gear2d(circ_pitch=5, teeth=20, pressure_angle=14);
1142// Example(2D): Partial Gear
1143//   spur_gear2d(circ_pitch=5, teeth=20, hide=15, pressure_angle=20);
1144// Example(2D,Med,VPT=[0.151988,3.93719,1.04995],VPR=[0,0,0],VPD=74.3596): Effects of Profile Shifting.
1145//   circ_pitch=5; teeth=7; shaft=5; strokewidth=0.2;
1146//   module the_gear(profile_shift=0) {
1147//       $fn=72;
1148//       pr = pitch_radius(circ_pitch,teeth);
1149//       mr = gear_dist(circ_pitch=circ_pitch,teeth,profile_shift1=profile_shift,teeth2=0);
1150//       back(mr) {
1151//           spur_gear2d(circ_pitch, teeth, shaft_diam=shaft, profile_shift=profile_shift);
1152//           up(0.1) color("black")
1153//               dashed_stroke(circle(r=pr), width=strokewidth, closed=true);
1154//       }
1155//   }
1156//   module the_rack() {
1157//       $fn=72;
1158//       rack2d(circ_pitch, teeth=3);
1159//       up(0.1) color("black")
1160//           dashed_stroke([[-7.5,0],[7.5,0]], width=strokewidth);
1161//   }
1162//   left(10) { the_gear(0); the_rack(); }
1163//   right(10) { the_gear(0.59); the_rack(); }
1164// Example(2D): Planetary Gear Assembly
1165//   rteeth=56; pteeth=16; cteeth=24;
1166//   circ_pitch=5; pa=20;
1167//   gd = gear_dist(circ_pitch=circ_pitch, cteeth,pteeth);
1168//   ring_gear2d(
1169//       circ_pitch=circ_pitch,
1170//       teeth=rteeth,
1171//       pressure_angle=pa);
1172//   for (a=[0:3]) {
1173//       zrot(a*90) back(gd) {
1174//           color("green")
1175//           spur_gear2d(
1176//               circ_pitch=circ_pitch,
1177//               teeth=pteeth,
1178//               pressure_angle=pa);
1179//       }
1180//   }
1181//   color("orange")
1182//     zrot(180/cteeth)
1183//       spur_gear2d(
1184//           circ_pitch=circ_pitch,
1185//           teeth=cteeth,
1186//           pressure_angle=pa);
1187// Example(2D): Called as a Function
1188//   rgn = spur_gear2d(circ_pitch=8, teeth=16, shaft_diam=5);
1189//   region(rgn);
1190
1191function spur_gear2d(
1192    circ_pitch,
1193    teeth,
1194    hide = 0,
1195    pressure_angle,
1196    clearance,
1197    backlash = 0.0,
1198    internal,
1199    interior,
1200    profile_shift="auto",
1201    helical,
1202    shaft_diam = 0,
1203    shorten = 0, 
1204    pitch,
1205    diam_pitch,
1206    mod,
1207    gear_spin = 0,
1208    atype="pitch", 
1209    anchor = CENTER,
1210    spin = 0
1211) = let(
1212        dummy = !is_undef(interior) ? echo("In spur_gear2d(), the argument 'interior=' has been deprecated, and may be removed in the future.  Please use 'internal=' instead."):0,
1213        internal = first_defined([internal,interior,false]),
1214        circ_pitch = _inherit_gear_pitch("spur_gear2d()", pitch, circ_pitch, diam_pitch, mod),
1215        PA = _inherit_gear_pa(pressure_angle),
1216        helical = _inherit_gear_helical(helical, invert=!internal)
1217    )
1218    assert(is_integer(teeth) && teeth>3)
1219    assert(is_finite(shaft_diam) && shaft_diam>=0)
1220    assert(is_integer(hide) && hide>=0 && hide<teeth)
1221    assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
1222    assert(clearance==undef || (is_finite(clearance) && clearance>=0))
1223    assert(is_finite(backlash) && backlash>=0)
1224    assert(is_finite(helical) && abs(helical)<90)
1225    assert(is_finite(gear_spin))
1226    let(
1227        profile_shift = auto_profile_shift(teeth,PA,helical,profile_shift=profile_shift),
1228        pr = pitch_radius(circ_pitch, teeth, helical=helical),
1229        or = outer_radius(circ_pitch, teeth, helical=helical, profile_shift=profile_shift, internal=internal,shorten=shorten),
1230        rr = _root_radius(circ_pitch, teeth, clearance, profile_shift=profile_shift, internal=internal),
1231        anchor_rad = atype=="pitch" ? pr
1232                   : atype=="tip" ? or
1233                   : atype=="root" ? rr
1234                   : assert(false,"atype must be one of \"root\", \"tip\" or \"pitch\""),
1235        tooth = _gear_tooth_profile(
1236                circ_pitch=circ_pitch,
1237                teeth=teeth,
1238                pressure_angle=PA,
1239                clearance=clearance,
1240                backlash=backlash,
1241                profile_shift=profile_shift,
1242                helical=helical,
1243                shorten=shorten,
1244                internal=internal
1245            ),
1246        perim = [
1247            for (i = [0:1:teeth-1-hide])
1248                each zrot(-i*360/teeth+gear_spin, p=tooth),
1249            if (hide>0) [0,0],
1250        ],
1251        rgn = [
1252            list_unwrap(deduplicate(perim)),
1253            if (shaft_diam>0 && !hide)
1254                reverse(circle(d=shaft_diam, $fn=max(16,segs(shaft_diam/2)))),
1255        ]
1256    ) reorient(anchor,spin, two_d=true, r=anchor_rad, p=rgn);
1257
1258
1259module spur_gear2d(
1260    circ_pitch,
1261    teeth,
1262    hide = 0,
1263    pressure_angle,
1264    clearance,
1265    backlash = 0.0,
1266    internal,
1267    interior,
1268    profile_shift="auto",
1269    helical,
1270    shorten = 0, 
1271    shaft_diam = 0,
1272    pitch,
1273    diam_pitch,
1274    mod,
1275    gear_spin = 0,
1276    atype="pitch",
1277    anchor = CENTER,
1278    spin = 0
1279) {
1280    dummy = !is_undef(interior) ? echo("In spur_gear2d(), the argument 'interior=' has been deprecated, and may be removed in the future.  Please use 'internal=' instead."):0;
1281    internal = first_defined([internal,interior,false]);
1282    circ_pitch = _inherit_gear_pitch("spur_gear2d()", pitch, circ_pitch, diam_pitch, mod);
1283    PA = _inherit_gear_pa(pressure_angle);
1284    helical = _inherit_gear_helical(helical, invert=!internal);
1285    checks =
1286        assert(is_integer(teeth) && teeth>3)
1287        assert(is_finite(shaft_diam) && shaft_diam>=0)
1288        assert(is_integer(hide) && hide>=0 && hide<teeth)
1289        assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
1290        assert(clearance==undef || (is_finite(clearance) && clearance>=0))
1291        assert(is_finite(backlash) && backlash>=0)
1292        assert(is_finite(helical) && abs(helical)<90)
1293        assert(is_finite(gear_spin));
1294    profile_shift = auto_profile_shift(teeth,PA,helical,profile_shift=profile_shift);
1295    rgn = spur_gear2d(
1296        circ_pitch = circ_pitch,
1297        teeth = teeth,
1298        hide = hide,
1299        pressure_angle = PA,
1300        clearance = clearance,
1301        helical = helical,
1302        backlash = backlash,
1303        profile_shift = profile_shift,
1304        internal = internal,
1305        shorten = shorten, 
1306        shaft_diam = shaft_diam
1307    );
1308    pr = pitch_radius(circ_pitch, teeth, helical=helical);
1309    or = outer_radius(circ_pitch, teeth, helical=helical, profile_shift=profile_shift, internal=internal,shorten=shorten);
1310    rr = _root_radius(circ_pitch, teeth, clearance, profile_shift=profile_shift, internal=internal);
1311    anchor_rad = atype=="pitch" ? pr
1312               : atype=="tip" ? or
1313               : atype=="root" ? rr
1314               : assert(false,"atype must be one of \"root\", \"tip\" or \"pitch\"");
1315    attachable(anchor,spin, two_d=true, r=anchor_rad) {
1316        zrot(gear_spin) region(rgn);
1317        union() {
1318            $parent_gear_type = "spur2D";
1319            $parent_gear_pitch = circ_pitch;
1320            $parent_gear_teeth = teeth;
1321            $parent_gear_pa = PA;
1322            $parent_gear_helical = helical;
1323            $parent_gear_thickness = 0;
1324            union() children();
1325        }
1326    }
1327}
1328
1329
1330// Module: ring_gear()
1331// Synopsis: Creates a 3D ring gear.
1332// SynTags: Geom
1333// Topics: Gears, Parts
1334// See Also: rack(), ring_gear2d(), spur_gear(), spur_gear2d(), bevel_gear()
1335// Usage:
1336//   ring_gear(circ_pitch, teeth, thickness, [backing|od=|or=|width=], [pressure_angle=], [helical=], [herringbone=], [profile_shift=], [clearance=], [backlash=]) [ATTACHMENTS];
1337//   ring_gear(mod=, teeth=, thickness=, [backing=|od=|or=|width=], [pressure_angle=], [helical=], [herringbone=], [profile_shift=], [clearance=], [backlash=]) [ATTACHMENTS];
1338//   ring_gear(diam_pitch=, teeth=, thickness=, [backing=|od=|or=|width=], [pressure_angle=], [helical=], [herringbone=], [profile_shift=], [clearance=], [backlash=]) [ATTACHMENTS];
1339// Description:
1340//   Creates a 3D involute ring gear.
1341//   Meshing gears must have the same tooth size, pressure angle and helical angle as usual.
1342//   Additionally, you must have more teeth on an internal gear than its mating external gear, and
1343//   the profile shift on the ring gear must be at least as big as the profile shift on the mating gear.
1344//   You may need to use {{auto_profile_shift()}} to find this value if your mating gear has a small number of teeth.
1345//   The gear spacing is given by {{gear_dist()}}.
1346// Arguments:
1347//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
1348//   teeth = Total number of teeth around the spur gear.
1349//   thickness = Thickness of ring gear
1350//   backing = The width of the ring gear backing.  Default: height of teeth
1351//   ---
1352//   od = outer diameter of the ring
1353//   or = outer radius of the ring
1354//   width = width of the ring, measuring from tips of teeth to outside of ring.  
1355//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees.
1356//   helical = The angle of the rack teeth away from perpendicular to the gear axis of rotation.  Stretches out the tooth shapes.  Used to match helical spur gear pinions.  Default: 0
1357//   herringbone = If true, and helical is set, creates a herringbone gear.
1358//   profile_shift = Profile shift factor x for tooth profile.  Default: 0
1359//   clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters)
1360//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
1361//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
1362//   mod = The module of the gear (pitch diameter / teeth)
1363//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1364//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1365//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1366// Example:
1367//   ring_gear(circ_pitch=5, teeth=48, thickness=10);
1368// Example: Adjusting Backing
1369//   ring_gear(circ_pitch=5, teeth=48, thickness=10, backing=30);
1370// Example(Med): Adjusting Pressure Angle
1371//   ring_gear(circ_pitch=5, teeth=48, thickness=10, pressure_angle=28);
1372// Example(Med): Tooth Profile Shifting
1373//   ring_gear(circ_pitch=5, teeth=48, thickness=10, profile_shift=0.5);
1374// Example(Med): Helical Ring Gear
1375//   ring_gear(circ_pitch=5, teeth=48, thickness=15, helical=30);
1376// Example(Med): Herringbone Ring Gear
1377//   ring_gear(circ_pitch=5, teeth=48, thickness=30, helical=30, herringbone=true);
1378
1379module ring_gear(
1380    circ_pitch,
1381    teeth,
1382    thickness = 10,
1383    backing,
1384    pressure_angle,
1385    helical,
1386    herringbone = false,
1387    profile_shift=0,
1388    clearance,
1389    backlash = 0.0,
1390    or,od,width,
1391    pitch,
1392    diam_pitch,
1393    mod,
1394    slices,
1395    gear_spin = 0,
1396    anchor = CENTER,
1397    atype = "pitch",
1398    spin = 0,
1399    orient = UP
1400) {
1401    circ_pitch = _inherit_gear_pitch("ring_gear()",pitch, circ_pitch, diam_pitch, mod);
1402    PA = _inherit_gear_pa(pressure_angle);
1403    helical = _inherit_gear_helical(helical);       //Maybe broken???
1404    thickness = _inherit_gear_thickness(thickness);
1405    checks =
1406        assert(in_list(atype,["outside","pitch"]))
1407        assert(is_finite(profile_shift), "Profile shift for ring gears must be numerical")
1408        assert(is_integer(teeth) && teeth>3)
1409        assert(is_finite(thickness) && thickness>0)
1410        assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
1411        assert(is_finite(helical) && abs(helical)<90)
1412        assert(is_bool(herringbone))
1413        assert(clearance==undef || (is_finite(clearance) && clearance>=0))
1414        assert(is_finite(backlash) && backlash>=0)
1415        assert(slices==undef || (is_integer(slices) && slices>0))
1416        assert(num_defined([backing,or,od,width])<=1, "Cannot define more than one of backing, or, od and width")
1417        assert(is_finite(gear_spin));
1418    pr = pitch_radius(circ_pitch, teeth, helical=helical);
1419    ar = outer_radius(circ_pitch, teeth, helical=helical, profile_shift=profile_shift, internal=true);
1420    rr=_root_radius(circ_pitch, teeth, clearance, profile_shift=profile_shift, internal=true);
1421    or = is_def(or) ?
1422            assert(is_finite(or) && or>ar, "or is invalid or too small for teeth")
1423            or
1424       : is_def(od) ?
1425            assert(is_finite(od) && od>2*ar, "od is invalid or too small for teeth")
1426            od/2
1427       : is_def(width) ?
1428            assert(is_finite(width) && width>ar-rr, "width is invalid or too small for teeth")
1429            rr+width
1430       : is_def(backing) ?
1431            assert(all_positive([backing]), "backing must be a positive value")
1432            ar+backing
1433       : 2*ar - rr;    // default case
1434    circum = 2 * PI * pr;
1435    twist = 360*thickness*tan(-helical)/circum;
1436    slices = default(slices, ceil(twist/360*segs(pr)+1));
1437    attachable(anchor,spin,orient, h=thickness, r=atype=="outside"?or:pr) {
1438        zrot(gear_spin)
1439        if (herringbone) {
1440            zflip_copy() down(0.01)
1441            linear_extrude(height=thickness/2, center=false, twist=twist/2, slices=ceil(slices/2), convexity=teeth/4) {
1442                difference() {
1443                    circle(r=or);
1444                    spur_gear2d(
1445                        circ_pitch = circ_pitch,
1446                        teeth = teeth,
1447                        pressure_angle = PA,
1448                        helical = helical,
1449                        clearance = clearance,
1450                        backlash = backlash,
1451                        profile_shift = profile_shift,
1452                        internal = true
1453                    );
1454                }
1455            }
1456        } else {
1457            zrot(twist/2)
1458            linear_extrude(height=thickness,center=true, twist=twist, convexity=teeth/4) {
1459                difference() {
1460                    circle(r=or);
1461                    spur_gear2d(
1462                        circ_pitch = circ_pitch,
1463                        teeth = teeth,
1464                        pressure_angle = PA,
1465                        helical = helical,
1466                        clearance = clearance,
1467                        backlash = backlash,
1468                        profile_shift = profile_shift,
1469                        internal = true
1470                    );
1471                }
1472            }
1473        }
1474        children();
1475    }
1476}
1477
1478
1479// Module: ring_gear2d()
1480// Synopsis: Creates a 2D ring gear.
1481// SynTags: Geom
1482// Topics: Gears, Parts
1483// See Also: rack(), spur_gear(), spur_gear2d(), bevel_gear()
1484// Usage:
1485//   ring_gear2d(circ_pitch, teeth, [backing|od=|or=|width=], [pressure_angle=], [helical=], [profile_shift=], [clearance=], [backlash=]) [ATTACHMENTS];
1486//   ring_gear2d(mod=, teeth=, [backing=|od=|or=|width=], [pressure_angle=], [helical=], [profile_shift=], [clearance=], [backlash=]) [ATTACHMENTS];
1487//   ring_gear2d(diam_pitch=, teeth=, [backing=|od=|or=|width=], [pressure_angle=], [helical=], [profile_shift=], [clearance=], [backlash=]) [ATTACHMENTS];
1488// Description:
1489//   Creates a 2D involute ring gear.  
1490//   Meshing gears must have the same tooth size, pressure angle and helical angle as usual.
1491//   Additionally, you must have more teeth on an internal gear than its mating external gear, and
1492//   the profile shift on the ring gear must be at least as big as the profile shift on the mating gear.
1493//   You may need to use {{auto_profile_shift()}} to find this value if your mating gear has a small number of teeth.
1494//   The gear spacing is given by {{gear_dist()}}.
1495// Arguments:
1496//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
1497//   teeth = Total number of teeth around the spur gear.
1498//   backing = The width of the ring gear backing.  Default: height of teeth
1499//   ---
1500//   od = outer diameter of the ring
1501//   or = outer radius of the ring
1502//   width = width of the ring, measuring from tips of teeth to outside of ring.  
1503//   helical = The angle of the rack teeth away from perpendicular to the gear axis of rotation.  Stretches out the tooth shapes.  Used to match helical spur gear pinions.  Default: 0
1504//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees.
1505//   profile_shift = Profile shift factor x for tooth profile.  Default: 0
1506//   clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters)
1507//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
1508//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
1509//   mod = The module of the gear (pitch diameter / teeth)
1510//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1511//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1512// Anchor Types:
1513//   pitch = anchor on the pitch circle (default)
1514//   outside = outside edge of the gear
1515// Example(2D,Big):  Meshing a ring gear with a spur gear
1516//   circ_pitch=5; teeth1=50; teeth2=18;
1517//   dist = gear_dist(circ_pitch=circ_pitch, teeth1, teeth2, internal1=true);
1518//   ring_gear2d(circ_pitch=circ_pitch, teeth=teeth1);
1519//   color("lightblue")back(dist)
1520//     spur_gear2d(circ_pitch=circ_pitch, teeth=teeth2);
1521// Example(2D,Med,VPT=[-0.117844,-0.439102,-0.372203],VPR=[0,0,0],VPD=192.044): Meshing a ring gear with an auto-profile-shifted spur gear:
1522//   teeth1=7;    teeth2=15;
1523//   ps1=undef;     // Allow auto profile shifting for first gear
1524//   ps2=auto_profile_shift(teeth=teeth1);
1525//   mod=3;
1526//   d = gear_dist(mod=mod, teeth1=teeth1, teeth2=teeth2, profile_shift1=ps1, profile_shift2=ps2, internal2=true);
1527//   ring_gear2d(mod=mod, teeth=teeth2,profile_shift=ps2);
1528//   color("lightblue") fwd(d)
1529//      spur_gear2d(mod=mod, teeth=teeth1, profile_shift=ps1);
1530
1531module ring_gear2d(
1532    circ_pitch,
1533    teeth,
1534    backing,
1535    pressure_angle,
1536    helical,
1537    profile_shift=0,
1538    clearance,
1539    backlash = 0.0,
1540    or,od,width,
1541    pitch,
1542    diam_pitch,
1543    mod,
1544    atype="pitch",
1545    gear_spin = 0,shorten=0,
1546    anchor = CENTER,
1547    spin = 0
1548) {
1549    
1550    circ_pitch = _inherit_gear_pitch("ring_gear2d()",pitch, circ_pitch, diam_pitch, mod);
1551    PA = _inherit_gear_pa(pressure_angle);
1552    helical = _inherit_gear_helical(helical);
1553    checks =
1554        assert(in_list(atype,["outside","pitch"]))
1555        assert(is_finite(profile_shift), "Profile shift for ring gears must be numerical")
1556        assert(is_integer(teeth) && teeth>3)
1557        assert(num_defined([backing,or,od,width])<=1, "Cannot define more than one of backing, or, od and width")
1558        assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
1559        assert(is_finite(helical) && abs(helical)<90)
1560        assert(clearance==undef || (is_finite(clearance) && clearance>=0))
1561        assert(is_finite(backlash) && backlash>=0)
1562        assert(is_finite(gear_spin));
1563    pr = pitch_radius(circ_pitch, teeth, helical=helical);
1564    ar = outer_radius(circ_pitch, teeth, helical=helical, profile_shift=profile_shift, internal=true);
1565    rr=_root_radius(circ_pitch, teeth, clearance, profile_shift=profile_shift, internal=true);
1566    or = is_def(or) ?
1567            assert(is_finite(or) && or>ar, "or is invalid or too small for teeth")
1568            or
1569       : is_def(od) ?
1570            assert(is_finite(od) && od>2*ar, "od is invalid or too small for teeth")
1571            od/2
1572       : is_def(width) ?
1573            assert(is_finite(width) && width>ar-rr, "width is invalid or too small for teeth")
1574            rr+width
1575       : is_def(backing) ?
1576            assert(all_positive([backing]), "backing must be a positive value")
1577            ar+backing
1578       : 2*ar - rr;    // default case
1579    attachable(anchor,spin, two_d=true, r=atype=="pitch"?pr:or) {
1580        zrot(gear_spin)
1581        difference() {
1582            circle(r=or);
1583            spur_gear2d(
1584                circ_pitch = circ_pitch,
1585                teeth = teeth,
1586                pressure_angle = PA,
1587                helical = helical,
1588                clearance = clearance,
1589                backlash = backlash,shorten=shorten,
1590                profile_shift = profile_shift,
1591                internal = true 
1592            );
1593        }
1594        children();
1595    }
1596}
1597
1598
1599
1600
1601// Function&Module: rack()
1602// Synopsis: Creates a straight or helical gear rack.
1603// SynTags: Geom, VNF
1604// Topics: Gears, Parts
1605// See Also: rack2d(), spur_gear(), spur_gear2d(), bevel_gear()
1606// Usage: As a Module
1607//   rack(pitch, teeth, thickness, [base|bottom=|width=], [helical=], [pressure_angle=], [backlash=], [clearance=]) [ATTACHMENTS];
1608//   rack(mod=, teeth=, thickness=, [base=|bottom=|width=], [helical=], [pressure_angle=], [backlash]=, [clearance=]) [ATTACHMENTS];
1609// Usage: As a Function
1610//   vnf = rack(pitch, teeth, thickness, [base|bottom=|width=], [helical=], [pressure_angle=], [backlash=], [clearance=]);
1611//   vnf = rack(mod=, teeth=, thickness=, [base=|bottom=|width=], [helical=], [pressure_angle=], [backlash=], [clearance=]);
1612// Description:
1613//   This is used to create a 3D rack, which is a linear bar with teeth that a gear can roll along.
1614//   A rack can mesh with any gear that has the same `pitch` and `pressure_angle`.  A helical rack meshes with a gear with the opposite
1615//   helical angle.   
1616//   When called as a function, returns a 3D [VNF](vnf.scad) for the rack.
1617//   When called as a module, creates a 3D rack shape.
1618//   .
1619//   By default the rack has a backing whose height is equal to the height of the teeth.  You can specify a different backing size
1620//   or you can specify the total width of the rack (from the bottom of the rack to tooth tips) or the
1621//   bottom point of the rack, which is the distance from the pitch line to the bottom of the rack.
1622//   .
1623//   The rack appears oriented with
1624//   its teeth pointed UP, so to mesh with gears in the XY plane, use `orient=BACK` or `orient=FWD` and apply any desired rotation.  
1625//   The pitch line of the rack is aligned with the x axis, the TOP anchors are at the tips of the teeth and the BOTTOM anchors at
1626//   the bottom of the backing.  Note that for helical racks the corner anchors still point at 45 degr angles.  
1627// Arguments:
1628//   pitch = The pitch, or distance between teeth centers along the rack. Matches up with circular pitch on a spur gear.  Default: 5
1629//   teeth = Total number of teeth along the rack.  Default: 20
1630//   thickness = Thickness of rack.  Default: 5
1631//   backing = Distance from bottom of rack to the roots of the rack's teeth.  (Alternative to bottom or width.)  Default: height of rack teeth
1632//   ---
1633//   bottom = Distance from rack's pitch line (the x-axis) to the bottom of the rack.  (Alternative to backing or width)
1634//   width = Distance from base of rack to tips of teeth (alternative to bottom and backing).
1635//   mod = The module of the gear (pitch diameter / teeth)
1636//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
1637//   helical = The angle of the rack teeth away from perpendicular to the rack length.  Used to match helical spur gear pinions.  Default: 0
1638//   herringbone = If true, and helical is set, creates a herringbone rack.
1639//   profile_shift = Profile shift factor x.  Default: 0
1640//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees.  Default: 20
1641//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
1642//   clearance = Clearance gap at the bottom of the inter-tooth valleys.  Default: module/4
1643//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1644//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1645//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1646// Named Anchors:
1647//   "root" = At the base of the teeth, at the center of rack.
1648//   "root-left" = At the base of the teeth, at the left end of the rack.
1649//   "root-right" = At the base of the teeth, at the right end of the rack.
1650//   "root-back" = At the base of the teeth, at the back of the rack.
1651//   "root-front" = At the base of the teeth, at the front of the rack.
1652// Example(NoScales,VPR=[60,0,325],VPD=130):
1653//   rack(pitch=5, teeth=10, thickness=5);
1654// Example(NoScales,VPT=[0.317577,3.42688,7.83665],VPR=[27.7,0,359.8],VPD=139.921): Rack for Helical Gear
1655//   rack(pitch=5, teeth=10, thickness=5, backing=5, helical=30);
1656// Example(NoScales): Metric Rack, oriented BACK to align with a gear in default orientation.  With profile shifting set to zero the gears mesh at their pitch circles.  
1657//   rack(mod=2, teeth=10, thickness=5, bottom=5, pressure_angle=14.5,orient=BACK);
1658//   color("red") spur_gear(mod=2, teeth=18, thickness=5, pressure_angle=14.5,anchor=FRONT,profile_shift=0);
1659// Example(NoScales): Orienting the rack to the right using {zrot()}.  In this case the gear has automatic profile shifting so we must use {{gear_dist()}} to correctly position the gear.  
1660//   zrot(-90)rack(mod=2, teeth=6, thickness=5, bottom=5, pressure_angle=14.5,orient=BACK);
1661//   color("red")
1662//    right(gear_dist(mod=2,0,12,pressure_angle=14.5))
1663//      spur_gear(mod=2, teeth=12, thickness=5, pressure_angle=14.5);
1664// Example(NoScales,Anim,VPT=[0,0,12],VPD=100,Frames=18): Rack and Pinion with helical teeth
1665//   teeth1 = 16; teeth2 = 16;
1666//   pitch = 5; thick = 5; helical = 30;
1667//   pr = pitch_radius(pitch, teeth2, helical=helical);
1668//   pos = 3*(1-2*abs($t-1/2))-1.5;
1669//   right(pr*2*PI/teeth2*pos)
1670//       rack(pitch, teeth1, thickness=thick, helical=helical);
1671//   up(pr)
1672//       spur_gear(
1673//           pitch, teeth2,
1674//           thickness = thick,
1675//           helical = -helical,
1676//           shaft_diam = 5,
1677//           orient = BACK,
1678//           gear_spin = 180-pos*360/teeth2);
1679// Example(NoAxes,VPT=[-7.10396,-9.70691,3.50121],VPR=[60.2,0,325],VPD=213.262): Skew axis helical gear and rack engagement.
1680//    mod=5; teeth=8; helical1=17.5; helical2=22.5;
1681//    d = gear_dist_skew(mod=mod, teeth, 0, helical1,helical2);
1682//    rack(mod=mod, teeth=5, thickness=30, helical=helical2, orient=FWD);
1683//    color("lightblue")
1684//      yrot(-helical1-helical2) fwd(d)
1685//      spur_gear(mod=mod, teeth=teeth, helical=helical1, gear_spin=180/teeth, thickness=30);
1686
1687module rack(
1688    pitch,
1689    teeth,
1690    thickness,
1691    backing,
1692    width, bottom,
1693    pressure_angle,
1694    backlash = 0.0,
1695    clearance,
1696    helical,
1697    herringbone = false,
1698    profile_shift = 0,
1699    gear_travel = 0,
1700    circ_pitch,
1701    diam_pitch,
1702    mod,
1703    anchor = CENTER,
1704    spin = 0,
1705    orient = UP
1706) {
1707    pitch = _inherit_gear_pitch("rack()",pitch, circ_pitch, diam_pitch, mod, warn=false);
1708    PA = _inherit_gear_pa(pressure_angle);
1709    helical = _inherit_gear_helical(helical);
1710    thickness = _inherit_gear_thickness(thickness);
1711    checks=
1712        assert(is_integer(teeth) && teeth>0)
1713        assert(is_finite(thickness) && thickness>0)
1714        assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
1715        assert(clearance==undef || (is_finite(clearance) && clearance>=0))
1716        assert(is_finite(backlash) && backlash>=0)
1717        assert(is_finite(helical) && abs(helical)<90)
1718        assert(is_bool(herringbone))
1719        assert(is_finite(profile_shift))
1720        assert(is_finite(gear_travel));
1721    trans_pitch = pitch / cos(helical);
1722    a = _adendum(pitch, profile_shift);
1723    d = _dedendum(pitch, clearance, profile_shift);
1724    bottom = is_def(bottom) ?
1725                 assert(is_finite(bottom) && bottom>d, "bottom is invalid or too small for teeth")
1726                 bottom
1727           : is_def(width) ?
1728                 assert(is_finite(width) && width>a+d, "width is invalid or too small for teeth")
1729                 width - a
1730           : is_def(backing) ?
1731                 assert(all_positive([backing]), "backing must be a positive value")
1732                 backing+d
1733           : 2*d+a;  // default case
1734    l = teeth * trans_pitch;
1735    anchors = [
1736        named_anchor("root",        [0,0,-d],            BACK),
1737        named_anchor("root-left",   [-l/2,0,-d],         LEFT),
1738        named_anchor("root-right",  [ l/2,0,-d],         RIGHT),
1739        named_anchor("root-front",  [0,-thickness/2,-d], FWD),
1740        named_anchor("root-back",   [0, thickness/2,-d], BACK),
1741    ];
1742    endfix = sin(helical)*thickness/2;
1743    override = function(anchor)
1744        anchor.z==1 ? [ [anchor.x*l/2-endfix*anchor.y,anchor.y*thickness/2,a], undef, undef]
1745      : anchor.x!=0 ? [ [anchor.x*l/2-endfix*anchor.y,anchor.y*thickness/2,anchor.z*bottom], undef,undef]
1746      :               undef;
1747    size = [l, thickness, 2*bottom];
1748    attachable(anchor,spin,orient, size=size, anchors=anchors, override=override) {
1749        right(gear_travel)
1750        xrot(90) {
1751            if (herringbone) {
1752                zflip_copy()
1753                skew(axz=-helical) 
1754                linear_extrude(height=thickness/2, center=false, convexity=teeth*2) {
1755                    rack2d(
1756                        pitch = pitch,
1757                        teeth = teeth,
1758                        bottom = bottom,
1759                        pressure_angle = PA,
1760                        backlash = backlash,
1761                        clearance = clearance,
1762                        helical = helical,
1763                        profile_shift = profile_shift
1764                    );
1765                }
1766            } else {
1767                skew(axz=helical)
1768                linear_extrude(height=thickness, center=true, convexity=teeth*2) {
1769                    rack2d(
1770                        pitch = pitch,
1771                        teeth = teeth,
1772                        bottom = bottom,
1773                        pressure_angle = PA,
1774                        backlash = backlash,
1775                        clearance = clearance,
1776                        helical = helical,
1777                        profile_shift = profile_shift
1778                    );
1779                }
1780            }
1781        }
1782        children();
1783    }
1784}
1785
1786
1787function rack(
1788    pitch,
1789    teeth,
1790    thickness,
1791    backing, bottom, width, 
1792    pressure_angle,
1793    backlash = 0.0,
1794    clearance,
1795    helical,
1796    herringbone = false,
1797    profile_shift = 0,
1798    circ_pitch,
1799    diam_pitch,
1800    mod,
1801    gear_travel = 0,
1802    anchor = CENTER,
1803    spin = 0,
1804    orient = UP
1805) =
1806    let(
1807        pitch = _inherit_gear_pitch("rack()",pitch, circ_pitch, diam_pitch, mod, warn=false),
1808        PA = _inherit_gear_pa(pressure_angle),
1809        helical = _inherit_gear_helical(helical),
1810        thickness = _inherit_gear_thickness(thickness)
1811    )
1812    assert(is_integer(teeth) && teeth>0)
1813    assert(is_finite(thickness) && thickness>0)
1814    assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
1815    assert(clearance==undef || (is_finite(clearance) && clearance>=0))
1816    assert(is_finite(backlash) && backlash>=0)
1817    assert(is_finite(helical) && abs(helical)<90)
1818    assert(is_bool(herringbone))
1819    assert(is_finite(profile_shift))
1820    assert(is_finite(gear_travel))
1821    let(
1822        trans_pitch = pitch / cos(helical),
1823        a = _adendum(pitch, profile_shift),
1824        d = _dedendum(pitch, clearance, profile_shift),
1825        bottom = is_def(bottom) ?
1826                     assert(is_finite(bottom) && bottom>d, "bottom is invalid or too small for teeth")
1827                     bottom
1828               : is_def(width) ?
1829                     assert(is_finite(width) && width>a+d, "width is invalid or too small for teeth")
1830                     width - a
1831               : is_def(backing) ?
1832                     assert(all_positive([backing]), "backing must be a positive value")
1833                     backing+d
1834               : 2*d+a,  // default case
1835        l = teeth * trans_pitch,
1836        path = rack2d(
1837            pitch = pitch,
1838            teeth = teeth,
1839            bottom = bottom,
1840            pressure_angle = PA,
1841            backlash = backlash,
1842            clearance = clearance,
1843            helical = helical,
1844            profile_shift = profile_shift
1845        ),
1846        vnf = herringbone
1847          ? sweep(path, [
1848                left(adj_ang_to_opp(thickness/2,helical)) *
1849                    back(thickness/2) * xrot(90),
1850                xrot(90),
1851                left(adj_ang_to_opp(thickness/2,helical)) *
1852                    fwd(thickness/2) * xrot(90),
1853            ], style="alt", orient=FWD)
1854          : skew(axy=-helical, p=linear_sweep(path, height=thickness, anchor="origin", orient=FWD)),
1855        out = right(gear_travel, p=vnf),
1856        size = [l, thickness, 2*bottom],
1857        anchors = [
1858            named_anchor("tip",         [0,0,a],             BACK),
1859            named_anchor("tip-left",    [-l/2,0,a],          LEFT),
1860            named_anchor("tip-right",   [ l/2,0,a],          RIGHT),
1861            named_anchor("tip-front",   [0,-thickness/2,a],  DOWN),
1862            named_anchor("tip-back",    [0, thickness/2,a],  UP),
1863            named_anchor("root",        [0,0,-d],            BACK),
1864            named_anchor("root-left",   [-l/2,0,-d],         LEFT),
1865            named_anchor("root-right",  [ l/2,0,-d],         RIGHT),
1866            named_anchor("root-front",  [0,-thickness/2,-d], DOWN),
1867            named_anchor("root-back",   [0, thickness/2,-d], UP),
1868        ]
1869    ) reorient(anchor,spin,orient, size=size, anchors=anchors, p=out);
1870
1871
1872
1873
1874// Function&Module: rack2d()
1875// Synopsis: Creates a 2D gear rack.
1876// SynTags: Geom, Path
1877// Topics: Gears, Parts
1878// See Also: rack(), spur_gear(), spur_gear2d(), bevel_gear()
1879// Usage: As a Module
1880//   rack2d(pitch, teeth, [base|bottom=|width=], [pressure_angle=], [backlash=], [clearance=]) [ATTACHMENTS];
1881//   rack2d(mod=, teeth=, [base=|bottom=|width=], [pressure_angle=], [backlash=], [clearance=]) [ATTACHMENTS];
1882// Usage: As a Function
1883//   path = rack2d(pitch, teeth, [base|bottom=|width=], [pressure_angle=], [backlash=], [clearance=]);
1884//   path = rack2d(mod=, teeth=, [base=|bottom=|width=], [pressure_angle=], [backlash=], [clearance=]);
1885// Description:
1886//   Create a 2D rack, a linear bar with teeth that a gear can roll along.
1887//   A rack can mesh with any spur gear or helical gear that has the same `pitch` and `pressure_angle`.  
1888//   When called as a function, returns a 2D path for the outline of the rack.
1889//   When called as a module, creates a 2D rack shape.
1890//   .
1891//   By default the rack has a backing whose height is equal to the height of the teeth.  You can specify a different backing size
1892//   or you can specify the total width of the rack (from the bottom of the rack to tooth tips) or the
1893//   bottom point of the rack, which is the distance from the pitch line to the bottom of the rack.
1894//   .
1895//   The rack appears with its pitch line on top of the x axis.  The BACK anchor refers to the tips of the teeth and the FRONT
1896//   anchor refers to the front of the backing.  You can use named anchors to access the roots of the teeth.  
1897// Arguments:
1898//   pitch = The pitch, or distance between teeth centers along the rack. Matches up with circular pitch on a spur gear.  Default: 5
1899//   teeth = Total number of teeth along the rack
1900//   backing = Distance from bottom of rack to the roots of the rack's teeth.  (Alternative to bottom or width.)  Default: height of rack teeth
1901//   ---
1902//   bottom = Distance from rack's pitch line (the x-axis) to the bottom of the rack.  (Alternative to backing or width)
1903//   width = Distance from base of rack to tips of teeth (alternative to bottom and backing).
1904//   mod = The module of the gear (pitch diameter / teeth)
1905//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
1906//   helical = The angle of the rack teeth away from perpendicular to the rack length.  Stretches out the tooth shapes.  Used to match helical spur gear pinions.  Default: 0
1907//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees.
1908//   profile_shift = Profile shift factor x for tooth shape.  Default: 0
1909//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
1910//   clearance = Clearance gap at the bottom of the inter-tooth valleys.  Default: module/4
1911//   gear_travel = The distance the rack should be moved by linearly.  Default: 0
1912//   rounding = If true, rack tips and valleys are slightly rounded.  Default: true
1913//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1914//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1915// Named Anchors:
1916//   "root" = At the height of the teeth, at the center of rack.
1917//   "root-left" = At the height of the teeth, at the left end of the rack.
1918//   "root-right" = At the height of the teeth, at the right end of the rack.
1919// Example(2D):
1920//   rack2d(pitch=5, teeth=10);
1921// Example(2D): Called as a Function
1922//   path = rack2d(pitch=8, teeth=8, pressure_angle=25);
1923//   polygon(path);
1924
1925function rack2d(
1926    pitch,
1927    teeth,
1928    backing,
1929    pressure_angle,
1930    backlash = 0,
1931    clearance,
1932    helical,
1933    profile_shift = 0,
1934    circ_pitch,
1935    diam_pitch,
1936    mod,
1937    width, bottom,
1938    gear_travel = 0,
1939    rounding = true,
1940    anchor = CENTER,
1941    spin = 0
1942) = let(
1943        pitch = _inherit_gear_pitch("rack2d()",pitch, circ_pitch, diam_pitch, mod, warn=false),
1944        PA = _inherit_gear_pa(pressure_angle),
1945        helical = _inherit_gear_helical(helical),
1946        mod = module_value(circ_pitch=pitch)
1947    )
1948    assert(is_integer(teeth) && teeth>0)
1949    assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
1950    assert(clearance==undef || (is_finite(clearance) && clearance>=0))
1951    assert(is_finite(backlash) && backlash>=0)
1952    assert(is_finite(helical) && abs(helical)<90)
1953    assert(is_finite(gear_travel))
1954    assert(num_defined([width,backing,bottom])<=1, "Can define only one of width, backing and bottom")
1955    let(
1956        adendum = _adendum(pitch, profile_shift),
1957        dedendum = _dedendum(pitch, clearance, profile_shift),
1958        clear = default(clearance, 0.25 * mod),
1959        bottom = is_def(bottom) ?
1960                     assert(is_finite(bottom) && bottom>dedendum, "bottom is invalid or too small for teeth")
1961                     bottom
1962               : is_def(width) ?
1963                     assert(is_finite(width) && width>adendum+dedendum, "width is invalid or too small for teeth")
1964                     width - adendum
1965               : is_def(backing) ?
1966                     assert(all_positive([backing]), "backing must be a positive value")
1967                     backing+dedendum
1968               : 2*dedendum+adendum  // default case
1969    )
1970    let(
1971        trans_pitch = pitch / cos(helical),
1972        trans_pa = atan(tan(PA)/cos(helical)),
1973        tthick = trans_pitch/PI * (PI/2 + 2*profile_shift * tan(PA)) - backlash,
1974        l = teeth * trans_pitch,
1975        ax = ang_adj_to_opp(trans_pa, adendum),
1976        dx = dedendum*tan(trans_pa),
1977        poff = tthick/2,
1978        tooth = [
1979            [-trans_pitch/2, -dedendum],
1980            if (rounding) each arc(n=4, r=clear, corner=[
1981                [-trans_pitch/2, -dedendum],
1982                [-poff-dx, -dedendum],
1983                [-poff+ax, +adendum],
1984            ]) else [-poff-dx, -dedendum],
1985            if (rounding) each arc(n=4, r=trans_pitch/16, corner=[
1986                [-poff-dx, -dedendum],
1987                [-poff+ax, +adendum],
1988                [+poff-ax, +adendum],
1989            ]) else [-poff+ax, +adendum],
1990            if (rounding) each arc(n=4, r=trans_pitch/16, corner=[
1991                [-poff+ax, +adendum],
1992                [+poff-ax, +adendum],
1993                [+poff+dx, -dedendum],
1994            ]) else [+poff-ax, +adendum],
1995            if (rounding) each arc(n=4, r=clear, corner=[
1996                [+poff-ax, +adendum],
1997                [+poff+dx, -dedendum],
1998                [+trans_pitch/2, -dedendum],
1999            ]) else [+poff+dx, -dedendum],
2000            [+trans_pitch/2, -dedendum],
2001        ],
2002        path2 = [
2003            for(m = xcopies(trans_pitch,n=teeth))
2004                each apply(m,tooth)
2005        ],
2006        path = right(gear_travel, p=[
2007            [path2[0].x, -bottom],
2008            each path2,
2009            [last(path2).x, -bottom],
2010        ]),
2011        size=[l,2*bottom],
2012        anchors = [
2013            named_anchor("root",        [   0,-dedendum,0],  BACK),
2014            named_anchor("root-left",   [-l/2,-dedendum,0],  LEFT),
2015            named_anchor("root-right",  [ l/2,-dedendum,0],  RIGHT),
2016        ],
2017        override = [
2018           [[0,1] , [[0,adendum]]],
2019           [[1,1] , [[l/2,adendum]]],
2020           [[-1,1] , [[-l/2,adendum]]],
2021        ]
2022    ) reorient(anchor,spin, two_d=true, size=size, anchors=anchors, override=override, p=path);
2023
2024
2025
2026module rack2d(
2027    pitch,
2028    teeth,
2029    backing,
2030    width, bottom,
2031    pressure_angle,
2032    backlash = 0,
2033    clearance,
2034    helical,
2035    profile_shift = 0,
2036    gear_travel = 0,
2037    circ_pitch,
2038    diam_pitch,
2039    mod,
2040    rounding = true, 
2041    anchor = CENTER,
2042    spin = 0
2043) {
2044    pitch = _inherit_gear_pitch("rack2d()",pitch, circ_pitch, diam_pitch, mod, warn=false);
2045    PA = _inherit_gear_pa(pressure_angle);
2046    helical = _inherit_gear_helical(helical);
2047    checks =
2048        assert(is_integer(teeth) && teeth>0)
2049        assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
2050        assert(clearance==undef || (is_finite(clearance) && clearance>=0))
2051        assert(is_finite(backlash) && backlash>=0)
2052        assert(is_finite(helical) && abs(helical)<90)
2053        assert(is_finite(gear_travel))
2054        assert(num_defined([width,backing,bottom])<=1, "Can define only one of width, backing and bottom");
2055    trans_pitch = pitch / cos(helical);
2056    a = _adendum(pitch, profile_shift);
2057    d = _dedendum(pitch, clearance, profile_shift);
2058    bottom = is_def(bottom) ?
2059                 assert(is_finite(bottom) && bottom>d, "bottom is invalid or too small for teeth")
2060                 bottom
2061           : is_def(width) ?
2062                 assert(is_finite(width) && width>a+d, "width is invalid or too small for teeth")
2063                 width - a
2064           : is_def(backing) ?
2065                 assert(all_positive([backing]), "backing must be a positive value")
2066                 backing+d
2067           : 2*d+a;  // default case
2068    l = teeth * trans_pitch;
2069    path = rack2d(
2070        pitch = pitch,
2071        teeth = teeth,
2072        bottom=bottom, 
2073        pressure_angle = PA,
2074        backlash = backlash,
2075        clearance = clearance,
2076        helical = helical,
2077        rounding=rounding, 
2078        profile_shift= profile_shift
2079    );
2080    size = [l, 2*bottom];
2081    anchors = [
2082        named_anchor("root",        [   0,-d,0],  BACK),
2083        named_anchor("root-left",   [-l/2,-d,0],  LEFT),
2084        named_anchor("root-right",  [ l/2,-d,0],  RIGHT),
2085    ];
2086    override = [
2087       [[0,1] , [[0,a]]],
2088       [[1,1] , [[l/2,a]]],
2089       [[-1,1] , [[-l/2,a]]],
2090    ];
2091    attachable(anchor,spin, two_d=true, size=size, anchors=anchors, override=override) {
2092        right(gear_travel) polygon(path);
2093        children();
2094    }
2095}
2096
2097
2098
2099// Function&Module: crown_gear()
2100// Synopsis: Creates a crown gear that can mesh with a spur gear.
2101// SynTags: Geom, VNF
2102// Topics: Gears, Parts
2103// See Also: rack(), rack2d(), spur_gear(), spur_gear2d(), bevel_pitch_angle(), bevel_gear()
2104// Usage: As a Module
2105//   crown_gear(circ_pitch, teeth, backing, face_width, [pressure_angle=], [clearance=], [backlash=], [profile_shift=], [slices=]);
2106//   crown_gear(diam_pitch=, teeth=, backing=, face_width=, [pressure_angle=], [clearance=], [backlash=], [profile_shift=], [slices=]);
2107//   crown_gear(mod=, teeth=, backing=, face_width=, [pressure_angle=], [clearance=], [backlash=], [profile_shift=], [slices=]);
2108// Usage: As a Function
2109//   vnf = crown_gear(circ_pitch, teeth, backing, face_width, [pressure_angle=], [clearance=], [backlash=], [profile_shift=], [slices=]);
2110//   vnf = crown_gear(diam_pitch=, teeth=, backing=, face_width=, [pressure_angle=], [clearance=], [backlash=], [profile_shift=], [slices=]);
2111//   vnf = crown_gear(mod=, teeth=, backing=, face_width=, [pressure_angle=], [clearance=], [backlash=], [profile_shift=], [slices=]);
2112// Description:
2113//   Creates a crown gear.  The module `crown_gear()` gives a crown gear, with reasonable defaults
2114//   for all the parameters.  Normally, you should just choose the first 4 parameters, and let the
2115//   rest be default values.
2116//   .
2117//   The module `crown_gear()` gives a crown gear in the XY plane, centered on the origin, with one tooth
2118//   centered on the positive Y axis.  The crown gear will have the pitch circle of the teeth at Z=0 by default.
2119//   The inner radius of the crown teeth can be calculated with the `pitch_radius()` function, and the outer
2120//   radius of the teeth is `face_width=` more than that.
2121// Arguments:
2122//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.  Default: 5
2123//   teeth = Total number of teeth around the entire perimeter.  Default: 20
2124//   backing = Distance from base of crown gear to roots of teeth (alternative to bottom and thickness).
2125//   face_width = Width of the toothed surface, from inside radius to outside.  Default: 5
2126//   ---
2127//   bottom = Distance from crown's pitch plane (Z=0) to the bottom of the crown gear.  (Alternative to backing or thickness)
2128//   thickness = Distance from base of crown gear to tips of teeth (alternative to bottom and backing).
2129//   pitch_angle = Angle of beveled gear face.  Default: 45
2130//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. Default: 20
2131//   clearance = Clearance gap at the bottom of the inter-tooth valleys.  Default: module/4
2132//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
2133//   slices = Number of vertical layers to divide gear into.  Useful for refining gears with `spiral`.  Default: 1
2134//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
2135//   mod = The module of the gear (pitch diameter / teeth)
2136//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2137//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2138//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2139// Example:
2140//   crown_gear(mod=1, teeth=40, backing=3, face_width=5, pressure_angle=20);
2141// Example:
2142//   mod=1; cteeth=40; pteeth=17; backing=3; PA=20; face=5;
2143//   cpr = pitch_radius(mod=mod, teeth=cteeth);
2144//   ppr = pitch_radius(mod=mod, teeth=pteeth);
2145//   crown_gear(mod=mod, teeth=cteeth, backing=backing,
2146//       face_width=face, pressure_angle=PA);
2147//   back(cpr+face/2)
2148//     up(ppr)
2149//       spur_gear(mod=mod, teeth=pteeth,
2150//           pressure_angle=PA, thickness=face,
2151//           orient=BACK, gear_spin=180/pteeth,
2152//           profile_shift=0);
2153
2154function crown_gear(
2155    circ_pitch,
2156    teeth,
2157    backing,
2158    face_width=5,
2159    pressure_angle=20,
2160    clearance,
2161    backlash=0,
2162    profile_shift=0,
2163    slices=10,
2164    bottom,
2165    thickness,
2166    diam_pitch,
2167    pitch,
2168    mod,
2169    gear_spin=0,
2170    anchor=CTR,
2171    spin=0,
2172    orient=UP
2173) = let(
2174        pitch = _inherit_gear_pitch("crown_gear()", pitch, circ_pitch, diam_pitch, mod, warn=false),
2175        PA = _inherit_gear_pa(pressure_angle)
2176    )
2177    assert(is_integer(teeth) && teeth>0)
2178    assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
2179    assert(clearance==undef || (is_finite(clearance) && clearance>=0))
2180    assert(is_finite(backlash) && backlash>=0)
2181    assert(is_finite(gear_spin))
2182    assert(num_defined([thickness,backing,bottom])<=1, "Can define only one of thickness, backing and bottom")
2183    let(
2184        a = _adendum(pitch, profile_shift),
2185        d = _dedendum(pitch, clearance, profile_shift),
2186        bottom = is_def(bottom) ?
2187                     assert(is_finite(bottom) && bottom>d, "bottom is invalid or too small for teeth")
2188                     bottom
2189               : is_def(thickness) ?
2190                     assert(is_finite(thickness) && thickness>a+d, "thickness is invalid or too small for teeth")
2191                     thickness - a
2192               : is_def(backing) ?
2193                     assert(all_positive([backing]), "backing must be a positive value")
2194                     backing+d
2195               : 2*d+a,  // default case
2196        mod = module_value(circ_pitch=pitch),
2197        ir = mod * teeth / 2,
2198        or = ir + face_width,
2199        profiles = [
2200            for (slice = [0:1:slices-1])
2201            let(
2202                u = slice / (slices-1),
2203                r = or - u*face_width,
2204                wpa = acos(ir * cos(PA) / r),
2205                profile = select(
2206                    rack2d(
2207                        mod=mod, teeth=1,
2208                        pressure_angle=wpa,
2209                        clearance=clearance,
2210                        backlash=backlash,
2211                        profile_shift=profile_shift,
2212                        rounding=false
2213                    ), 2, -3
2214                ),
2215                delta = profile[1] - profile[0],
2216                slope = delta.y / delta.x,
2217                C = profile[0].y - slope * profile[0].x,
2218                profile2 = profile[1].x > 0
2219                  ? [profile[0], [0,C], [0,C], profile[3]]
2220                  : profile,
2221                m = back(r) * xrot(90),
2222                tooth = apply(m, path3d(profile2)),
2223                rpitch = pitch * r / ir
2224            )
2225            assert(profile[3].x <= rpitch/2, "face_width is too wide for the given gear geometry.  Either decrease face_width, or increase the module or tooth count.")
2226            [
2227                for (i = [0:1:teeth-1])
2228                let(a = gear_spin - i * 360 / teeth) 
2229                each zrot(a, p=tooth)
2230            ]
2231        ],
2232        rows = [
2233            [for (p=profiles[0]) [p.x,p.y,-bottom]],
2234            each profiles,
2235            [for (p=last(profiles)) [p.x,p.y,last(profiles)[0].z]],
2236        ],
2237        vnf = vnf_vertex_array(rows, col_wrap=true, caps=true)
2238    ) reorient(anchor,spin,orient, r=or, h=2*bottom, p=vnf);
2239
2240
2241module crown_gear(
2242    circ_pitch,
2243    teeth,
2244    backing,
2245    face_width=10,
2246    pressure_angle=20,
2247    clearance,
2248    backlash=0,
2249    profile_shift=0,
2250    slices=10,
2251    bottom,
2252    thickness,
2253    diam_pitch,
2254    pitch,
2255    mod,
2256    gear_spin=0,
2257    anchor=CTR,
2258    spin=0,
2259    orient=UP
2260) {
2261    pitch = _inherit_gear_pitch("crown_gear()", pitch, circ_pitch, diam_pitch, mod, warn=false);
2262    PA = _inherit_gear_pa(pressure_angle);
2263    checks =
2264        assert(is_integer(teeth) && teeth>0)
2265        assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
2266        assert(clearance==undef || (is_finite(clearance) && clearance>=0))
2267        assert(is_finite(backlash) && backlash>=0)
2268        assert(is_finite(gear_spin))
2269        assert(num_defined([thickness,backing,bottom])<=1, "Can define only one of width, backing and bottom")
2270        ;
2271    pr = pitch_radius(circ_pitch=pitch, teeth=teeth);
2272    a = _adendum(pitch, profile_shift);
2273    d = _dedendum(pitch, clearance, profile_shift);
2274    bottom = is_def(bottom) ?
2275                 assert(is_finite(bottom) && bottom>d, "bottom is invalid or too small for teeth")
2276                 bottom
2277           : is_def(thickness) ?
2278                 assert(is_finite(thickness) && thickness>a+d, "thickness is invalid or too small for teeth")
2279                 thickness - a
2280           : is_def(backing) ?
2281                 assert(all_positive([backing]), "backing must be a positive value")
2282                 backing+d
2283           : 2*d+a;  // default case
2284    vnf = crown_gear(
2285        circ_pitch=pitch,
2286        teeth=teeth,
2287        bottom=bottom,
2288        face_width=face_width,
2289        pressure_angle=PA,
2290        clearance=clearance,
2291        backlash=backlash,
2292        profile_shift=profile_shift,
2293        slices=slices,
2294        gear_spin=gear_spin
2295    );
2296    attachable(anchor,spin,orient, r=pr+face_width, h=2*bottom) {
2297        vnf_polyhedron(vnf, convexity=teeth/2);
2298        children();
2299    }
2300}
2301
2302
2303// Function&Module: bevel_gear()
2304// Synopsis: Creates a straight, zerol, or spiral bevel gear.
2305// SynTags: Geom, VNF
2306// Topics: Gears, Parts
2307// See Also: rack(), rack2d(), spur_gear(), spur_gear2d(), bevel_pitch_angle(), bevel_gear()
2308// Usage: As a Module
2309//   gear_dist(mod=|diam_pitch=|circ_pitch=, teeth, mate_teeth, [shaft_angle], [shaft_diam], [face_width=], [hide=], [spiral=], [cutter_radius=], [right_handed=], [pressure_angle=], [backing=|thickness=|bottom=], [cone_backing=], [backlash=], [slices=], [internal=], [gear_spin=], ...) [ATTACHMENTS];
2310// Usage: As a Function
2311//   vnf = gear_dist(mod=|diam_pitch=|circ_pitch=, teeth, mate_teeth, [shaft_angle], [face_width=], [hide=], [spiral=], [cutter_radius=], [right_handed=], [pressure_angle=], , [backing=|thickness=|bottom=], [cone_backing=], [backlash=], [slices=], [internal=], [gear_spin=], ...);
2312// Description:
2313//   Creates a spiral, zerol, or straight bevel gear.  In straight bevel gear sets, when each tooth
2314//   engages it inpacts the corresponding tooth.  The abrupt tooth engagement causes impact stress
2315//   which makes them more prone to breakage.  Spiral bevel gears have teeth formed along spirals so
2316//   they engage more gradually, resulting in a less abrupt transfer of force, so they are quieter
2317//   in operation and less likely to break.
2318//   .
2319//   Bevel gears must be created in mated pairs to work together at a chosen shaft angle.  You therefore
2320//   must specify both the number of teeth on the gear and the number of teeth on its mating gear.
2321//   Additional requirements for bevel gears to mesh are that they share the same
2322//   tooth size and the same pressure angle and they must be of opposite handedness.
2323//   The pressure angle controls how much the teeth bulge at their
2324//   sides and is almost always 20 degrees for standard bevel gears.  The ratio of `teeth` for two meshing gears
2325//   gives how many times one will make a full
2326//   revolution when the the other makes one full revolution.  If the two numbers are coprime (i.e.
2327//   are not both divisible by the same number greater than 1), then every tooth on one gear will meet
2328//   every tooth on the other, for more even wear.  So relatively prime numbers of teeth are good.
2329//   .
2330//   The gear appears centered on the origin, with one tooth
2331//   centered on the positive Y axis.  The base of the pitch cone (the "pitchbase") will lie in the XY plane.  This is
2332//   the natural position: in order to mesh the mating gear must be positioned so their pitch bases are tangent.
2333//   The apexes of the pitch cones must coincide.
2334//   . 
2335//   By default backing will be added to ensure
2336//   that the center of the gear (where there are no teeth) is at least half the face width in thickness.
2337//   You can change this using the `backing`, `thickness` or `bottom` parameters.  The backing appears with
2338//   a conical shape, extended the sloped edges of the teeth.  You can set `cone_backing=false` if your application
2339//   requires cylindrical backing.  
2340// Arguments:
2341//   teeth = Number of teeth on the gear
2342//   mate_teeth = Number of teeth on the gear that will mate to this gear
2343//   shaft_angle = Angle between the shafts of the two gears.  Default: 90
2344//   --
2345//   mod = The module of the gear (pitch diameter / teeth)
2346//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
2347//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
2348//   backing = Distance from bottom of bevel gear to bottom corner of teeth (Alternative to bottom or thickness).  Default: 0 if the gear is thick enough (see above)
2349//   bottom = Distance from bevel gear's pitch base to the bottom of the bevel gear.  (Alternative to backing or thickness)
2350//   thickness = Thickness of bevel gear at the center, where there are no teeth.  (Alternative to backing or bottom). 
2351//   cone_backing = If true backing extends conical shape of the gear; otherwise backing is an attached cylinder.  Default: true
2352//   face_width = Width of teeth.  Default: minimum of one third the cone distance and 10*module
2353//   shaft_diam = Diameter of the hole in the center, or zero for no hole.  (Module only.)  Default: 0
2354//   hide = Number of teeth to delete to make this only a fraction of a circle.  Default: 0
2355//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. Default: 20
2356//   clearance = Clearance gap at the bottom of the inter-tooth valleys.  Default: module/4
2357//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
2358//   spiral = The base angle for spiral teeth.  If zero the teeth will be zerol or straight.  Default: 35
2359//   cutter_radius = Radius of spiral arc for teeth.  If 0, then gear will have straight teeth.  Default: face_width/2/cos(spiral)
2360//   right_handed = If true, the gear returned will have a right-handed teeth.  Default: false 
2361//   slices = Number of vertical layers to divide gear into.  Useful for refining gears with `spiral`.  Default: 1
2362//   gear_spin = Rotate gear and children around the gear center, regardless of how gear is anchored.  Default: 0
2363//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: "pitchbase"
2364//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2365//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2366// Named Anchors:
2367//   "pitchbase" = With the base of the pitch cone in the XY plane, centered at the origin.  This is the natural height for the gear, and the default anchor.
2368//   "apex" = At the pitch cone apex for the bevel gear.
2369//   "flattop" = At the top of the flat top of the bevel gear.
2370// Example(NoAxes): Bevel Gear with zerol teeth
2371//   bevel_gear(
2372//       circ_pitch=5, teeth=36, mate_teeth=36,
2373//       shaft_diam=5, spiral=0
2374//   );
2375// Example(NoAxes): Spiral Beveled Gear and Pinion.  Note conical backing added to the yellow gear to prevent it from being thin.
2376//   t1 = 16; t2 = 28;
2377//   color("lightblue")bevel_gear(
2378//       circ_pitch=5, teeth=t1, mate_teeth=t2,
2379//       slices=12, anchor="apex", orient=FWD
2380//   );
2381//   bevel_gear(
2382//       circ_pitch=5, teeth=t2, mate_teeth=t1, right_handed=true,
2383//       slices=12, anchor="apex", backing=3, spin=180/t2
2384//   );
2385// Example(Anim,Frames=4,VPD=175,NoAxes): Manual Spacing of Pinion and Gear.  Here conical backing has been turned off.  
2386//   t1 = 14; t2 = 28; circ_pitch=5;
2387//   color("lightblue")back(pitch_radius(circ_pitch, t2)) {
2388//     yrot($t*360/t1)
2389//     bevel_gear(
2390//       circ_pitch=circ_pitch, teeth=t1, mate_teeth=t2, shaft_diam=5,
2391//       slices=12, orient=FWD
2392//     );
2393//   }
2394//   down(pitch_radius(circ_pitch, t1)) {
2395//     zrot($t*360/t2)
2396//     bevel_gear(
2397//       circ_pitch=circ_pitch, teeth=t2, mate_teeth=t1, right_handed=true,
2398//       shaft_diam=5, slices=12, backing=3, spin=180/t2, cone_backing=false
2399//     );
2400//   }
2401// Example(NoAxes,VPT=[24.4306,-9.20912,-29.3331],VPD=292.705,VPR=[71.8,0,62.5]): Bevel gears at a non right angle, positioned by aligning the pitch cone apexes.  
2402//   ang=65;
2403//   bevel_gear(mod=3,35,15,ang,spiral=0,backing=5,anchor="apex")   
2404//     cyl(h=48,d=3,$fn=16,anchor=BOT);
2405//   color("lightblue")
2406//   xrot(ang)
2407//     bevel_gear(mod=3,15,35,ang,spiral=0,right_handed=true,anchor="apex")
2408//       cyl(h=65,d=3,$fn=16,anchor=BOT);
2409// Example(VPT=[6.39483,26.2195,8.93229],VPD=192.044,VPR=[76.7,0,63.3],NoAxes): At this extreme 135 degree angle the yellow gear has internal teeth.  This is a rare configuration.  
2410//   ang=135;
2411//   bevel_gear(mod=3,35,15,ang);   
2412//   color("lightblue")
2413//     back(pitch_radius(mod=3,teeth=35)+pitch_radius(mod=3,teeth=15))
2414//     xrot(ang,cp=[0,-pitch_radius(mod=3,teeth=15),0])
2415//         bevel_gear(mod=3,15,35,ang,right_handed=true);       
2416function bevel_gear(
2417    teeth,
2418    mate_teeth,
2419    shaft_angle=90,
2420    backing,thickness,bottom,
2421    face_width,
2422    pressure_angle = 20,
2423    clearance,
2424    backlash = 0.0,
2425    cutter_radius,
2426    spiral = 35,
2427    right_handed = false,
2428    slices = 5,
2429    cone_backing = true,
2430    pitch,
2431    circ_pitch,
2432    diam_pitch,
2433    mod,
2434    anchor = "pitchbase",
2435    spin = 0,
2436    gear_spin = 0, 
2437    orient = UP,
2438    _return_anchors = false
2439) = assert(all_integer([teeth,mate_teeth]) && teeth>=3 && mate_teeth>=3, "Must give teeth and mate_teeth, integers greater than or equal to 3")
2440    assert(all_nonnegative([spiral]), "spiral must be nonnegative")
2441    assert(is_undef(cutter_radius) || all_nonnegative([cutter_radius]), "cutter_radius must be nonnegative")
2442    assert(is_finite(shaft_angle) && shaft_angle>0 && shaft_angle<180,"shaft_angle must be strictly between 0 and 180")  
2443    let(
2444        circ_pitch = _inherit_gear_pitch("bevel_gear()",pitch, circ_pitch, diam_pitch, mod),
2445        PA = _inherit_gear_pa(pressure_angle),
2446        spiral = _inherit_gear_helical(spiral),
2447        slices = cutter_radius==0? 1 : slices,
2448        pitch_angle = posmod(atan(sin(shaft_angle)/((mate_teeth/teeth)+cos(shaft_angle))),180),
2449        pr = pitch_radius(circ_pitch, teeth),
2450        rr = _root_radius(circ_pitch, teeth, clearance),
2451        pitchoff = (pr-rr) * sin(pitch_angle),
2452        ocone_rad = pitch_angle<90 ? opp_ang_to_hyp(pr, pitch_angle)
2453                                   : opp_ang_to_hyp(pitch_radius(circ_pitch,mate_teeth), shaft_angle-pitch_angle),
2454        default_face_width = min(ocone_rad/3, 10*module_value(circ_pitch)),
2455        face_width = _inherit_gear_thickness(face_width,dflt=default_face_width),
2456        icone_rad = ocone_rad - face_width,
2457        
2458        cutter_radius = is_undef(cutter_radius) ? face_width * 2 / cos(spiral)
2459                      : cutter_radius==0? face_width*100
2460                      : cutter_radius,
2461        midpr = (icone_rad + ocone_rad) / 2,
2462        radcp = [0, midpr] + polar_to_xy(cutter_radius, 180+spiral),
2463        angC1 = law_of_cosines(a=cutter_radius, b=norm(radcp), c=ocone_rad),
2464        angC2 = law_of_cosines(a=cutter_radius, b=norm(radcp), c=icone_rad),
2465        radcpang = v_theta(radcp),
2466        sang = radcpang - (180-angC1),
2467        eang = radcpang - (180-angC2),
2468        profile = reverse(_gear_tooth_profile(
2469            circ_pitch = circ_pitch,
2470            teeth = teeth,
2471            pressure_angle = PA,
2472            clearance = clearance,
2473            backlash = backlash,
2474            center = true
2475        )),
2476        verts1 = [
2477            for (v = lerpn(0,1,slices+1)) let(
2478                p = radcp + polar_to_xy(cutter_radius, lerp(sang,eang,v)),
2479                ang = v_theta(p)-90,
2480                dist = norm(p)
2481            ) [
2482                let(
2483                    u = dist / ocone_rad,
2484                    m = up((1-u) * pr / tan(pitch_angle)) *
2485                        up(pitchoff) *
2486                        zrot(ang/sin(pitch_angle)) *
2487                        back(u * pr) *
2488                        xrot(pitch_angle) *
2489                        scale(u)
2490                )
2491                for (tooth=[0:1:teeth-1])
2492                each apply(xflip() * zrot(360*tooth/teeth) * m, path3d(profile))
2493            ]
2494        ],
2495        botz = verts1[0][0].z,      // bottom of center
2496        topz = last(verts1)[0].z,   // top of center
2497        ctr_thickness = topz - botz,  
2498        vertices = [for (x=verts1) reverse(x)],
2499        sides_vnf = vnf_vertex_array(vertices, caps=false, col_wrap=true, reverse=true),
2500        top_verts = last(vertices),
2501        bot_verts = vertices[0],
2502        gear_pts = len(top_verts),
2503        face_pts = gear_pts / teeth,
2504        minbacking = -min(0,ctr_thickness),  
2505        backing = is_def(backing) ?
2506                      assert(all_nonnegative([backing]), "backing must be a non-negative value")
2507                      assert(ctr_thickness>0 || backing>0, "internal gears require backing>0")
2508                      backing-min(0,ctr_thickness)
2509                : is_def(thickness) ?
2510                      let(thick_OK=is_finite(thickness) && (thickness>abs(ctr_thickness) || (thickness==ctr_thickness && ctr_thickness>0)))
2511                      assert(thick_OK, str("thickness is invalid or too small for teeth; thickness must be larger than ",abs(ctr_thickness)))
2512                      thickness-ctr_thickness
2513                : is_def(bottom)?
2514                    assert(is_finite(bottom) && bottom-pitchoff>minbacking,
2515                           str("bottom is invalid or too small for teeth, must exceed ",minbacking+pitchoff))
2516                    bottom-pitchoff
2517                : ctr_thickness>face_width/2 ? 0
2518                : -ctr_thickness+face_width/2,
2519        cpz = (topz + botz - backing) / 2,
2520        teeth_top_faces =[
2521            for (i=[0:1:teeth-1], j=[0:1:(face_pts/2)-1]) each [
2522                [i*face_pts+j, (i+1)*face_pts-j-1, (i+1)*face_pts-j-2],
2523                [i*face_pts+j, (i+1)*face_pts-j-2, i*face_pts+j+1]
2524            ]
2525        ],
2526        flat_top_faces = [    
2527            for (i=[0:1:teeth-1]) each [
2528                [gear_pts, (i+1)*face_pts-1, i*face_pts],
2529                [gear_pts, ((i+1)%teeth)*face_pts, (i+1)*face_pts-1]
2530            ]
2531        ],
2532        backing_vert = backing==0? []
2533                     : !cone_backing ? down(backing,[for(i=[0:1:teeth-1]) each( [bot_verts[i*face_pts], bot_verts[(i+1)*face_pts-1]])])
2534                     : let(
2535                           factor = tan(pitch_angle-90)*backing
2536                       )
2537                       [for(i=[0:1:teeth-1]) let(
2538                           A = bot_verts[i*face_pts],
2539                           B = bot_verts[(i+1)*face_pts-1],
2540                           adjA = point3d(factor*unit(point2d(A)),-backing),
2541                           adjB = point3d(factor*unit(point2d(B)),-backing)
2542                       )
2543                       each [ A+adjA, B+adjB]],
2544        shift = len(bot_verts),
2545        backing_bot_faces = backing==0? flat_top_faces
2546                          :[for (i=idx(backing_vert))
2547                               [shift+len(backing_vert), shift+(i+1)%len(backing_vert),shift+i]
2548                            ],
2549        backing_side_faces = backing==0 ? []
2550                         : [
2551                             for (i=[0:1:teeth-1]) 
2552                               each [
2553                                     [shift+2*i,shift+(2*i+1),(i+1)*face_pts-1],
2554                                     [shift+2*i+1,shift+2*((i+1)%teeth), ((i+1)%teeth)*face_pts],
2555                                     [(i+1)*face_pts-1, i*face_pts, shift+2*i],
2556                                     [((i+1)%teeth)*face_pts, (i+1)*face_pts-1, shift+2*i+1]
2557                               ]              
2558                           ],
2559        vnf1 = vnf_join([
2560            [
2561                [each top_verts, [0,0,top_verts[0].z]],
2562                concat(teeth_top_faces, flat_top_faces)
2563            ],
2564            [
2565                [each bot_verts,each backing_vert, [0,0,bot_verts[0].z-backing]   ],
2566                [for (x=concat(teeth_top_faces,backing_bot_faces,backing_side_faces)) reverse(x)]
2567            ],
2568            sides_vnf
2569        ]),
2570        lvnf = right_handed? vnf1 : xflip(p=vnf1),
2571        vnf = zrot(gear_spin,down(cpz, p=lvnf)),
2572        anchors = [
2573            named_anchor("pitchbase", [0,0,pitchoff-ctr_thickness/2+backing/2]),
2574            named_anchor("flattop", [0,0,ctr_thickness/2+backing/2]),
2575            named_anchor("apex", [0,0,hyp_ang_to_opp(pitch_angle<90?ocone_rad:icone_rad,90-pitch_angle)+pitchoff-ctr_thickness/2+backing/2])
2576        ],
2577        final_vnf = reorient(anchor,spin,orient, vnf=vnf, extent=true, anchors=anchors, p=vnf)
2578    )
2579    _return_anchors==false ? final_vnf
2580                        : [final_vnf, anchors, ctr_thickness+backing];
2581
2582
2583module bevel_gear(
2584    teeth,
2585    mate_teeth,
2586    shaft_angle=90,
2587    bottom,backing,thickness,cone_backing=true,
2588    face_width,
2589    shaft_diam = 0,
2590    pressure_angle = 20,
2591    clearance = undef,
2592    backlash = 0.0,
2593    cutter_radius,
2594    spiral = 35,
2595    right_handed = false,
2596    slices = 5,
2597    pitch,
2598    diam_pitch,
2599    circ_pitch,
2600    mod,
2601    anchor = "pitchbase",
2602    spin = 0,
2603    gear_spin=0, 
2604    orient = UP
2605) {
2606    vnf_anchors = bevel_gear(
2607        circ_pitch = circ_pitch, mod=mod, diam_pitch=diam_pitch, 
2608        teeth = teeth,
2609        mate_teeth = mate_teeth,
2610        shaft_angle=shaft_angle,
2611        bottom=bottom,thickness=thickness,backing=backing,cone_backing=cone_backing,
2612        face_width = face_width,
2613        pressure_angle = pressure_angle,
2614        clearance = clearance,
2615        backlash = backlash,
2616        cutter_radius = cutter_radius,
2617        spiral = spiral,
2618        right_handed = right_handed,
2619        slices = slices,
2620        anchor=CENTER,
2621        gear_spin=gear_spin,
2622        _return_anchors=true
2623    );
2624    vnf=vnf_anchors[0];
2625    anchors=vnf_anchors[1];
2626    thickness = vnf_anchors[2];
2627    attachable(anchor,spin,orient, vnf=vnf, extent=true, anchors=anchors) {        
2628        difference() {
2629           vnf_polyhedron(vnf, convexity=teeth/2);
2630           if (shaft_diam > 0)
2631               cylinder(h=2*thickness, r=shaft_diam/2, center=true, $fn=max(12,segs(shaft_diam/2)));
2632        }
2633        children();
2634    }
2635}
2636
2637
2638// Function&Module: worm()
2639// Synopsis: Creates a worm that will mate with a worm gear.
2640// SynTags: Geom, VNF
2641// Topics: Gears, Parts
2642// See Also: worm(), worm_gear(), rack(), rack2d(), spur_gear(), spur_gear2d(), bevel_pitch_angle(), bevel_gear()
2643// Usage: As a Module
2644//   worm(circ_pitch, d, l, [starts=], [left_handed=], [pressure_angle=], [backlash=], [clearance=]);
2645//   worm(mod=, d=, l=, [starts=], [left_handed=], [pressure_angle=], [backlash=], [clearance=]);
2646// Usage: As a Function
2647//   vnf = worm(circ_pitch, d, l, [starts=], [left_handed=], [pressure_angle=], [backlash=], [clearance=]);
2648//   vnf = worm(mod=, d=, l=, [starts=], [left_handed=], [pressure_angle=], [backlash=], [clearance=]);
2649// Description:
2650//   Creates a worm shape that can be matched to a worm gear.
2651// Arguments:
2652//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.  Default: 5
2653//   d = The diameter of the worm.  Default: 30
2654//   l = The length of the worm.  Default: 100
2655//   starts = The number of lead starts.  Default: 1
2656//   left_handed = If true, the gear returned will have a left-handed spiral.  Default: false
2657//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. Default: 20
2658//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
2659//   clearance = Clearance gap at the bottom of the inter-tooth valleys.  Default: module/4
2660//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
2661//   mod = The module of the gear (pitch diameter / teeth)
2662//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2663//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2664//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2665// Example:
2666//   worm(circ_pitch=8, d=30, l=50, $fn=72);
2667// Example: Multiple Starts.
2668//   worm(circ_pitch=8, d=30, l=50, starts=3, $fn=72);
2669// Example: Left Handed
2670//   worm(circ_pitch=8, d=30, l=50, starts=3, left_handed=true, $fn=72);
2671// Example: Called as Function
2672//   vnf = worm(circ_pitch=8, d=35, l=50, starts=2, left_handed=true, pressure_angle=20, $fn=72);
2673//   vnf_polyhedron(vnf);
2674
2675function worm(
2676    circ_pitch,
2677    d=30, l=100,
2678    starts=1,
2679    left_handed=false,
2680    pressure_angle,
2681    backlash=0,
2682    clearance,
2683    diam_pitch,
2684    mod,
2685    pitch,
2686    gear_spin=0,
2687    anchor=CENTER,
2688    spin=0,
2689    orient=UP
2690) =
2691    let(
2692        circ_pitch = _inherit_gear_pitch("worm()", pitch, circ_pitch, diam_pitch, mod),
2693        PA = _inherit_gear_pa(pressure_angle)
2694    )
2695    assert(is_integer(starts) && starts>0)
2696    assert(is_finite(l) && l>0)
2697    //assert(is_finite(shaft_diam) && shaft_diam>=0)
2698    assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
2699    assert(clearance==undef || (is_finite(clearance) && clearance>=0))
2700    assert(is_finite(backlash) && backlash>=0)
2701    assert(is_bool(left_handed))
2702    assert(is_finite(gear_spin))
2703    let(
2704        helical = asin(starts * circ_pitch / PI / d),
2705        trans_pitch = circ_pitch / cos(helical),
2706        tooth = xflip(
2707            p=select(rack2d(
2708                pitch=circ_pitch,
2709                teeth=1,
2710                pressure_angle=PA,
2711                clearance=clearance,
2712                backlash=backlash,
2713                helical=helical,
2714                profile_shift=0
2715            ), 1, -2)
2716        ),
2717        rack_profile = [
2718            for (t = xcopies(trans_pitch, n=2*ceil(l/trans_pitch)+1))
2719                each apply(t, tooth)
2720        ],
2721        steps = max(36, segs(d/2)),
2722        step = 360 / steps,
2723        zsteps = ceil(l / trans_pitch / starts * steps),
2724        zstep = l / zsteps,
2725        profiles = [
2726            for (j = [0:1:zsteps]) [
2727                for (i = [0:1:steps-1]) let(
2728                    u = i / steps - 0.5,
2729                    ang = 360 * (1 - u) + 90,
2730                    z = j*zstep - l/2,
2731                    zoff = trans_pitch * starts * u,
2732                    h = lookup(z+zoff, rack_profile)
2733                )
2734                cylindrical_to_xyz(d/2+h, ang, z)
2735            ]
2736        ],
2737        vnf1 = vnf_vertex_array(profiles, caps=true, col_wrap=true, style="alt"),
2738        m = product([
2739            zrot(gear_spin),
2740            if (left_handed) xflip(),
2741        ]),
2742        vnf = apply(m, vnf1)
2743    ) reorient(anchor,spin,orient, d=d, l=l, p=vnf);
2744
2745
2746module worm(
2747    circ_pitch,
2748    d=15, l=100,
2749    starts=1,
2750    left_handed=false,
2751    pressure_angle,
2752    backlash=0,
2753    clearance,
2754    pitch,
2755    diam_pitch,
2756    mod,
2757    gear_spin=0,
2758    anchor=CENTER,
2759    spin=0,
2760    orient=UP
2761) {
2762    circ_pitch = _inherit_gear_pitch("worm()", pitch, circ_pitch, diam_pitch, mod);
2763    PA = _inherit_gear_pa(pressure_angle);
2764    checks =
2765        assert(is_integer(starts) && starts>0)
2766        assert(is_finite(l) && l>0)
2767        //assert(is_finite(shaft_diam) && shaft_diam>=0)
2768        assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
2769        assert(clearance==undef || (is_finite(clearance) && clearance>=0))
2770        assert(is_finite(backlash) && backlash>=0)
2771        assert(is_bool(left_handed))
2772        assert(is_finite(gear_spin));
2773    helical = asin(starts * circ_pitch / PI / d);
2774    trans_pitch = circ_pitch / cos(helical);
2775    vnf = worm(
2776        circ_pitch=circ_pitch,
2777        starts=starts,
2778        d=d, l=l,
2779        left_handed=left_handed,
2780        pressure_angle=PA,
2781        backlash=backlash,
2782        clearance=clearance,
2783        mod=mod
2784    );
2785    attachable(anchor,spin,orient, d=d, l=l) {
2786        zrot(gear_spin) vnf_polyhedron(vnf, convexity=ceil(l/trans_pitch)*2);
2787        children();
2788    }
2789}
2790
2791
2792// Function&Module: enveloping_worm()
2793// Synopsis: Creates a double-enveloping worm that will mate with a worm gear.
2794// SynTags: Geom, VNF
2795// Topics: Gears, Parts
2796// See Also: worm(), worm_gear(), rack(), rack2d(), spur_gear(), spur_gear2d(), bevel_pitch_angle(), bevel_gear()
2797// Usage: As a Module
2798//   enveloping_worm(circ_pitch, mate_teeth, d, [left_handed=], [starts=], [arc=], [pressure_angle=]);
2799//   enveloping_worm(mod=, mate_teeth=, d=, [left_handed=], [starts=], [arc=], [pressure_angle=]);
2800//   enveloping_worm(diam_pitch=, mate_teeth=, d=, [left_handed=], [starts=], [arc=], [pressure_angle=]);
2801// Usage: As a Function
2802//   vnf = enveloping_worm(circ_pitch, mate_teeth, d, [left_handed=], [starts=], [arc=], [pressure_angle=]);
2803//   vnf = enveloping_worm(mod=, mate_teeth=, d=, [left_handed=], [starts=], [arc=], [pressure_angle=]);
2804//   vnf = enveloping_worm(diam_pitch=, mate_teeth=, d=, [left_handed=], [starts=], [arc=], [pressure_angle=]);
2805// Description:
2806//   Creates a double-enveloping worm shape that can be matched to a worm gear.
2807// Arguments:
2808//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.  Default: 5
2809//   mate_teeth = The number of teeth in the mated worm gear.
2810//   d = The pitch diameter of the worm at its middle.
2811//   left_handed = If true, the gear returned will have a left-handed spiral.  Default: false
2812//   ---
2813//   starts = The number of lead starts.  Default: 1
2814//   arc = Arc angle of the mated worm gear to envelop.  Default: `2 * pressure_angle`
2815//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. Default: 20
2816//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
2817//   mod = The module of the gear (pitch diameter / teeth)
2818//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2819//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2820//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2821// Example:
2822//   enveloping_worm(circ_pitch=8, mate_teeth=45, d=30, $fn=72);
2823// Example: Multiple Starts.
2824//   enveloping_worm(circ_pitch=8, mate_teeth=33, d=30, starts=3, $fn=72);
2825// Example: Left Handed
2826//   enveloping_worm(circ_pitch=8, mate_teeth=33, d=30, starts=3, left_handed=true, $fn=72);
2827// Example: Called as Function
2828//   vnf = enveloping_worm(circ_pitch=8, mate_teeth=37, d=35, starts=2, left_handed=true, pressure_angle=20, $fn=72);
2829//   vnf_polyhedron(vnf);
2830
2831function enveloping_worm(
2832    circ_pitch,
2833    mate_teeth,
2834    d,
2835    left_handed=false,
2836    starts=1,
2837    arc,
2838    pressure_angle,
2839    gear_spin=0,
2840    rounding=true,
2841    taper=true,
2842    diam_pitch,
2843    mod,
2844    pitch,
2845    anchor=CTR,
2846    spin=0,
2847    orient=UP
2848) =
2849    let(
2850        circ_pitch = _inherit_gear_pitch("worm_gear()", pitch, circ_pitch, diam_pitch, mod),
2851        pressure_angle = _inherit_gear_pa(pressure_angle),
2852        arc = default(arc, 2*pressure_angle)
2853    )
2854    assert(is_integer(mate_teeth) && mate_teeth>10)
2855    assert(is_finite(d) && d>0)
2856    assert(is_bool(left_handed))
2857    assert(is_integer(starts) && starts>0)
2858    assert(is_finite(arc) && arc>10 && arc<=2*pressure_angle)
2859    assert(is_finite(gear_spin))
2860    let(
2861        hsteps = segs(d/2),
2862        vsteps = hsteps,
2863        helical = asin(starts * circ_pitch / PI / d),
2864        pr = pitch_radius(circ_pitch, mate_teeth, helical=helical),
2865        taper_table = taper
2866          ? [
2867                [-180, 0],
2868                [-arc/2, 0],
2869                [-arc/2*0.85, 0.75],
2870                [-arc/2*0.8, 0.93],
2871                [-arc/2*0.75, 1],
2872                [+arc/2*0.75, 1],
2873                [+arc/2*0.8, 0.93],
2874                [+arc/2*0.85, 0.75],
2875                [+arc/2, 0],
2876                [+180, 0],
2877            ]
2878          : [
2879                [-180, 0],
2880                [-arc/2-0.00001, 0],
2881                [-arc/2, 1],
2882                [+arc/2, 1],
2883                [+arc/2+0.00001, 0],
2884                [+180, 0],
2885            ],
2886        tarc = 360 / mate_teeth,
2887        rteeth = quantup(ceil(mate_teeth*arc/360),2)+1+2*starts,
2888        rack_path = select(
2889            rack2d(
2890                circ_pitch, rteeth,
2891                pressure_angle=pressure_angle,
2892                rounding=rounding, spin=90
2893            ),
2894            1,-2
2895        ),
2896        adendum = _adendum(circ_pitch, profile_shift=0),
2897        m1 = yscale(360/(circ_pitch*mate_teeth)) * left(adendum),
2898        rows = [
2899            for (i = [0:1:hsteps-1]) let(
2900                u = i / hsteps,
2901                theta = (1-u) * 360,
2902                m2 = back(circ_pitch*starts*u),
2903                polars = [
2904                    for (p=apply(m1*m2, rack_path))
2905                    if(p.y>=-arc-tarc && p.y<=arc+tarc)
2906                    [pr+p.x*lookup(p.y,taper_table)+adendum, p.y]
2907                ],
2908                rpolars = mirror([-1,1],p=polars)
2909            ) [
2910                for (j = [0:1:vsteps-1]) let(
2911                    v = j / (vsteps-1),
2912                    phi = (v-0.5) * arc,
2913                    minor_r = lookup(phi, rpolars),
2914                    xy = [d/2+pr,0] + polar_to_xy(minor_r,180-phi),
2915                    xyz = xrot(90,p=point3d(xy))
2916                ) zrot(theta, p=xyz)
2917            ]
2918        ],
2919        ys = column(flatten(rows),1),
2920        miny = min(ys),
2921        maxy = max(ys),
2922        vnf1 = vnf_vertex_array(transpose(rows), col_wrap=true, caps=true),
2923        m = product([
2924            zrot(gear_spin),
2925            if (!left_handed) xflip(),
2926            zrot(90),
2927        ]),
2928        vnf = apply(m, vnf1)
2929    ) reorient(anchor,spin,orient, d=d, l=maxy-miny, p=vnf);
2930
2931
2932module enveloping_worm(
2933    circ_pitch,
2934    mate_teeth,
2935    d,
2936    left_handed=false,
2937    starts=1,
2938    arc,
2939    pressure_angle=20,
2940    gear_spin=0,
2941    rounding=true,
2942    taper=true,
2943    diam_pitch,
2944    mod,
2945    pitch,
2946    anchor=CTR,
2947    spin=0,
2948    orient=UP
2949) {
2950    vnf = enveloping_worm(
2951        mate_teeth=mate_teeth,
2952        d=d,
2953        left_handed=left_handed,
2954        starts=starts,
2955        arc=arc,
2956        pressure_angle=pressure_angle,
2957        gear_spin=gear_spin,
2958        rounding=rounding,
2959        taper=taper,
2960        circ_pitch=circ_pitch,
2961        diam_pitch=diam_pitch,
2962        mod=mod,
2963        pitch=pitch
2964    );
2965    bounds = pointlist_bounds(vnf[0]);
2966    delta = bounds[1] - bounds[0];
2967    attachable(anchor,spin,orient, d=max(delta.x,delta.y), l=delta.z) {
2968        vnf_polyhedron(vnf, convexity=mate_teeth);
2969        children();
2970    }
2971}
2972
2973// Function&Module: worm_gear()
2974// Synopsis: Creates a worm gear that will mate with a worm.
2975// SynTags: Geom, VNF
2976// Topics: Gears, Parts
2977// See Also: worm(), worm_gear(), rack(), rack2d(), spur_gear(), spur_gear2d(), bevel_pitch_angle(), bevel_gear()
2978// Usage: As a Module
2979//   worm_gear(circ_pitch, teeth, worm_diam, [worm_starts=], [worm_arc=], [crowning=], [left_handed=], [pressure_angle=], [backlash=], [clearance=], [slices=], [shaft_diam=]) [ATTACHMENTS];
2980//   worm_gear(mod=, teeth=, worm_diam=, [worm_starts=], [worm_arc=], [crowning=], [left_handed=], [pressure_angle=], [backlash=], [clearance=], [slices=], [shaft_diam=]) [ATTACHMENTS];
2981// Usage: As a Function
2982//   vnf = worm_gear(circ_pitch, teeth, worm_diam, [worm_starts=], [worm_arc=], [crowning=], [left_handed=], [pressure_angle=], [backlash=], [clearance=], [slices=]);
2983//   vnf = worm_gear(mod=, teeth=, worm_diam=, [worm_starts=], [worm_arc=], [crowning=], [left_handed=], [pressure_angle=], [backlash=], [clearance=], [slices=]);
2984// Description:
2985//   Creates a worm gear to match with a worm.
2986// Arguments:
2987//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.  Default: 5
2988//   teeth = Total number of teeth along the rack.  Default: 30
2989//   worm_diam = The pitch diameter of the worm gear to match to.  Default: 30
2990//   worm_starts = The number of lead starts on the worm gear to match to.  Default: 1
2991//   worm_arc = The arc of the worm to mate with, in degrees. Default: 45 degrees
2992//   crowning = The amount to oversize the virtual hobbing cutter used to make the teeth, to add a slight crowning to the teeth to make them fit the work easier.  Default: 1
2993//   left_handed = If true, the gear returned will have a left-handed spiral.  Default: false
2994//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. Default: 20
2995//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
2996//   clearance = Clearance gap at the bottom of the inter-tooth valleys.  Default: module/4
2997//   profile_shift = Profile shift factor x.  Default: "auto"
2998//   slices = The number of vertical slices to refine the curve of the worm throat.  Default: 10
2999//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3000//   mod = The module of the gear (pitch diameter / teeth)
3001//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3002//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3003//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3004// Example: Right-Handed
3005//   worm_gear(circ_pitch=5, teeth=36, worm_diam=30, worm_starts=1);
3006// Example: Left-Handed
3007//   worm_gear(circ_pitch=5, teeth=36, worm_diam=30, worm_starts=1, left_handed=true);
3008// Example: Multiple Starts
3009//   worm_gear(circ_pitch=5, teeth=36, worm_diam=30, worm_starts=4);
3010// Example: Metric Worm Gear
3011//   worm_gear(mod=2, teeth=32, worm_diam=30, worm_starts=1);
3012// Example(Anim,Frames=4,FrameMS=125,VPD=220,VPT=[-15,0,0]): Meshing Worm and Gear
3013//   $fn=36;
3014//   circ_pitch = 5; starts = 4;
3015//   worm_diam = 30; worm_length = 50;
3016//   gear_teeth=36;
3017//   right(worm_diam/2)
3018//     yrot($t*360/starts)
3019//       worm(
3020//          d=worm_diam,
3021//          l=worm_length,
3022//          circ_pitch=circ_pitch,
3023//          starts=starts,
3024//          orient=BACK);
3025//   left(pitch_radius(circ_pitch, gear_teeth))
3026//     zrot(-$t*360/gear_teeth)
3027//       worm_gear(
3028//          circ_pitch=circ_pitch,
3029//          teeth=gear_teeth,
3030//          worm_diam=worm_diam,
3031//          worm_starts=starts);
3032// Example: Meshing Worm and Gear Metricly
3033//   $fn = 72;
3034//   modulus = 2; starts = 3;
3035//   worm_diam = 30; worm_length = 50;
3036//   gear_teeth=36;
3037//   right(worm_diam/2)
3038//       worm(d=worm_diam, l=worm_length, mod=modulus, starts=starts, orient=BACK);
3039//   left(pitch_radius(mod=modulus, teeth=gear_teeth))
3040//       worm_gear(mod=modulus, teeth=gear_teeth, worm_diam=worm_diam, worm_starts=starts);
3041// Example: Called as Function
3042//   vnf = worm_gear(circ_pitch=8, teeth=30, worm_diam=30, worm_starts=1);
3043//   vnf_polyhedron(vnf);
3044
3045function worm_gear(
3046    circ_pitch,
3047    teeth,
3048    worm_diam,
3049    worm_starts=1,
3050    worm_arc=45,
3051    crowning=0.1,
3052    left_handed=false,
3053    pressure_angle,
3054    backlash=0,
3055    clearance,
3056    profile_shift="auto",
3057    slices=10,
3058    gear_spin=0,
3059    pitch,
3060    diam_pitch,
3061    mod,
3062    get_thickness=false,
3063    anchor=CTR,
3064    spin=0,
3065    orient=UP
3066) =
3067    let(
3068        circ_pitch = _inherit_gear_pitch("worm_gear()", pitch, circ_pitch, diam_pitch, mod),
3069        PA = _inherit_gear_pa(pressure_angle),
3070        profile_shift = auto_profile_shift(teeth,PA,profile_shift=profile_shift)
3071    )
3072    assert(is_finite(worm_diam) && worm_diam>0)
3073    assert(is_integer(teeth) && teeth>7)
3074    assert(is_finite(worm_arc) && worm_arc>0 && worm_arc <= 60)
3075    assert(is_integer(worm_starts) && worm_starts>0)
3076    assert(is_bool(left_handed))
3077    assert(is_finite(backlash))
3078    assert(is_finite(crowning) && crowning>=0)
3079    assert(clearance==undef || (is_finite(clearance) && clearance>=0))
3080    assert(is_finite(profile_shift))
3081    let(
3082        gear_arc = 2 * PA,
3083        helical = asin(worm_starts * circ_pitch / PI / worm_diam),
3084        full_tooth = apply(
3085            zrot(90) * scale(0.99),
3086            _gear_tooth_profile(
3087                circ_pitch, teeth=teeth,
3088                pressure_angle=PA,
3089                profile_shift=-profile_shift,
3090                clearance=clearance,
3091                helical=helical,
3092                center=true
3093            )
3094        ),
3095        ftl = len(full_tooth),
3096        tooth_half1 = (select(full_tooth, 0, ftl/2-1)),
3097        tooth_half2 = (select(full_tooth, ftl/2, -1)),
3098        tang = 360 / teeth,
3099        rteeth = quantdn(teeth * gear_arc / 360, 2) / 2 + 0.5,
3100        pr = pitch_radius(circ_pitch, teeth, helical=helical),
3101        oslices = slices * 4,
3102        rows = [
3103            for (data = [[tooth_half1,1], [tooth_half2,-1]])
3104            let (
3105                tooth_half = data[0],
3106                dir = data[1]
3107            )
3108            for (pt = tooth_half) [
3109                for (i = [0:1:oslices])
3110                let (
3111                    u = i / oslices,
3112                    w_ang = worm_arc * (u - 0.5),
3113                    g_ang_delta = w_ang/360 * tang * worm_starts * (left_handed?1:-1),
3114                    m = zrot(dir*rteeth*tang+g_ang_delta, cp=[worm_diam/2+pr,0,0]) *
3115                        left(crowning) *
3116                        yrot(w_ang) *
3117                        right(worm_diam/2+crowning) *
3118                        zrot(-dir*rteeth*tang+g_ang_delta, cp=[pr,0,0]) *
3119                        xrot(180)
3120                ) apply(m, point3d(pt))
3121            ]
3122        ],
3123        midrow = len(rows)/2,
3124        goodcols = [
3125            for (i = idx(rows[0]))
3126            let(
3127                p1 = rows[midrow-1][i],
3128                p2 = rows[midrow][i]
3129            )
3130            if (p1.y > p2.y) i
3131        ],
3132        dowarn = goodcols[0]==0? 0 : echo("Worm gear tooth arc reduced to fit."),
3133        truncrows = [for (row = rows) [ for (i=goodcols) row[i] ] ],
3134        zs = column(flatten(truncrows),2),
3135        minz = min(zs),
3136        maxz = max(zs),
3137        zmax = max(abs(minz), abs(maxz))+0.05,
3138        twang1 = v_theta(truncrows[0][0]),
3139        twang2 = v_theta(last(truncrows[0])),
3140        twang = modang(twang1 - twang2) / (maxz-minz),
3141        resampled_rows = [for (row = truncrows) resample_path(row, n=slices, keep_corners=30, closed=false)],
3142        tooth_rows = [
3143            for (row = resampled_rows) [
3144                zrot(twang*(zmax-row[0].z), p=[row[0].x, row[0].y, zmax]),
3145                each row,
3146                zrot(twang*(-zmax-last(row).z), p=[last(row).x, last(row).y, -zmax]),
3147            ],
3148        ]
3149    )
3150    get_thickness? zmax*2 :
3151    let(
3152        gear_rows = [
3153            for (i = [0:1:teeth-1])
3154            let(
3155                m = zrot(i*tang) *
3156                    back(pr) *
3157                    zrot(-90) *
3158                    left(worm_diam/2)
3159            )
3160            for (row = tooth_rows)
3161            apply(m, row)
3162        ],
3163        vnf1 = vnf_vertex_array(transpose(gear_rows), col_wrap=true, caps=true),
3164        vnf = apply(zrot(gear_spin), vnf1)
3165    ) reorient(anchor,spin,orient, r=pr, h=2*zmax, p=vnf);
3166
3167
3168module worm_gear(
3169    circ_pitch,
3170    teeth,
3171    worm_diam,
3172    worm_starts = 1,
3173    worm_arc = 45,
3174    crowning = 0.1,
3175    left_handed = false,
3176    pressure_angle,
3177    backlash = 0,
3178    clearance,
3179    profile_shift="auto",
3180    slices = 10,
3181    shaft_diam = 0,
3182    gear_spin=0,
3183    pitch,
3184    diam_pitch,
3185    mod,
3186    anchor = CENTER,
3187    spin = 0,
3188    orient = UP
3189) {
3190    circ_pitch = _inherit_gear_pitch("worm_gear()", pitch, circ_pitch, diam_pitch, mod);
3191    PA = _inherit_gear_pa(pressure_angle);
3192    profile_shift = auto_profile_shift(teeth,PA,profile_shift=profile_shift);
3193    checks =
3194        assert(is_integer(teeth) && teeth>10)
3195        assert(is_finite(worm_diam) && worm_diam>0)
3196        assert(is_integer(worm_starts) && worm_starts>0)
3197        assert(is_finite(worm_arc) && worm_arc>0 && worm_arc<90)
3198        assert(is_finite(crowning) && crowning>=0)
3199        assert(is_bool(left_handed))
3200        assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
3201        assert(clearance==undef || (is_finite(clearance) && clearance>=0))
3202        assert(is_finite(backlash) && backlash>=0)
3203        assert(is_finite(shaft_diam) && shaft_diam>=0)
3204        assert(slices==undef || (is_integer(slices) && slices>0))
3205        assert(is_finite(profile_shift) && abs(profile_shift)<1)
3206        assert(is_finite(gear_spin));
3207    helical = asin(worm_starts * circ_pitch / PI / worm_diam);
3208    pr = pitch_radius(circ_pitch, teeth, helical);
3209    vnf = worm_gear(
3210        circ_pitch = circ_pitch,
3211        teeth = teeth,
3212        worm_diam = worm_diam,
3213        worm_starts = worm_starts,
3214        worm_arc = worm_arc,
3215        crowning = crowning,
3216        left_handed = left_handed,
3217        pressure_angle = PA,
3218        backlash = backlash,
3219        clearance = clearance,
3220        profile_shift = profile_shift,
3221        slices = slices
3222    );
3223    thickness = pointlist_bounds(vnf[0])[1].z;
3224    attachable(anchor,spin,orient, r=pr, l=thickness) {
3225        zrot(gear_spin)
3226        difference() {
3227            vnf_polyhedron(vnf, convexity=teeth/2);
3228            if (shaft_diam > 0) {
3229                cylinder(h=2*thickness+1, r=shaft_diam/2, center=true, $fn=max(12,segs(shaft_diam/2)));
3230            }
3231        }
3232        children();
3233    }
3234}
3235
3236
3237
3238
3239/// Function: _gear_tooth_profile()
3240/// Usage: As Function
3241///   path = _gear_tooth_profile(pitch, teeth, [pressure_angle], [clearance], [backlash], [internal]);
3242/// Topics: Gears
3243/// See Also: spur_gear2d()
3244/// Description:
3245///   When called as a function, returns the 2D profile path for an individual gear tooth.
3246///   When called as a module, creates the 2D profile shape for an individual gear tooth.
3247/// Arguments:
3248///   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
3249///   teeth = Total number of teeth on the spur gear that this is a tooth for.
3250///   pressure_angle = Pressure Angle.  Controls how straight or bulged the tooth sides are. In degrees.
3251///   clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters)
3252///   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
3253///   internal = If true, create a mask for difference()ing from something else.
3254///   center = If true, centers the pitch circle of the tooth profile at the origin.  Default: false.
3255///   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3256///   mod = The module of the gear (pitch diameter / teeth)
3257/// Example(2D):
3258///   _gear_tooth_profile(circ_pitch=5, teeth=20, pressure_angle=20);
3259/// Example(2D): Metric Gear Tooth
3260///   _gear_tooth_profile(mod=2, teeth=20, pressure_angle=20);
3261/// Example(2D):
3262///   _gear_tooth_profile(
3263///       circ_pitch=5, teeth=20, pressure_angle=20
3264///   );
3265/// Example(2D): As a function
3266///   path = _gear_tooth_profile(
3267///       circ_pitch=5, teeth=20, pressure_angle=20
3268///   );
3269///   stroke(path, width=0.1);
3270
3271function _gear_tooth_profile(
3272    circ_pitch,
3273    teeth,
3274    pressure_angle = 20,
3275    clearance,
3276    backlash = 0.0,
3277    helical = 0,
3278    internal = false,
3279    profile_shift = 0.0,
3280    shorten = 0, 
3281    mod,
3282    diam_pitch,
3283    pitch,
3284    center = false
3285) = let(
3286    // Calculate a point on the involute curve, by angle.
3287    _involute = function(base_r,a)
3288        let(b=a*PI/180) base_r * [cos(a)+b*sin(a), sin(a)-b*cos(a)],
3289
3290    steps = 16,
3291    circ_pitch = circular_pitch(pitch=pitch, circ_pitch=circ_pitch, diam_pitch=diam_pitch, mod=mod),
3292    mod = module_value(circ_pitch=circ_pitch),
3293    clear = default(clearance, 0.25 * mod),
3294
3295    // Calculate the important circle radii
3296    arad = outer_radius(circ_pitch, teeth, helical=helical, profile_shift=profile_shift, internal=internal, shorten=shorten),
3297    prad = pitch_radius(circ_pitch, teeth, helical=helical),
3298    brad = _base_radius(circ_pitch, teeth, pressure_angle, helical=helical),
3299    rrad = _root_radius(circ_pitch, teeth, clearance, helical=helical, profile_shift=profile_shift, internal=internal),
3300    srad = max(rrad,brad),
3301    tthick = circ_pitch/PI / cos(helical) * (PI/2 + 2*profile_shift * tan(pressure_angle)) + (internal?backlash:-backlash),
3302    tang = tthick / prad / 2 * 180 / PI,
3303
3304    // Generate a lookup table for the involute curve angles, by radius
3305    involute_lup = [
3306        for (i=[0:5:arad/PI/brad*360])
3307            let(
3308                xy = _involute(brad,i),
3309                pol = xy_to_polar(xy)
3310            )
3311            if (pol.x <= arad * 1.1)
3312            [pol.x, 90-pol.y]
3313    ],
3314
3315    // Generate reverse lookup table for involute radii, by angle
3316    involute_rlup = mirror([-1,1],p=involute_lup), // swaps X and Y columns.
3317
3318    a_ang = lookup(arad, involute_lup),
3319    p_ang = lookup(prad, involute_lup),
3320    b_ang = lookup(brad, involute_lup),
3321    r_ang = lookup(rrad, involute_lup),
3322    s_ang = lookup(srad, involute_lup),
3323    soff = tang + (b_ang - p_ang),
3324    ma_rad = min(arad, lookup(90-soff+0.05*360/teeth/2, involute_rlup)),
3325    ma_ang = lookup(ma_rad, involute_lup),
3326    cap_steps = ceil((ma_ang + soff - 90) / 5),
3327    cap_step = (ma_ang + soff - 90) / cap_steps,
3328    ax = circ_pitch/4 - ang_adj_to_opp(pressure_angle, circ_pitch/PI),
3329
3330    // Calculate the undercut a meshing rack might carve out of this tooth.
3331    undercut = [
3332        for (a=[atan2(ax,rrad):-1:-90])
3333        let(
3334            bx = -a/360 * 2*PI*prad,
3335            x = bx + ax,
3336            y = prad - circ_pitch/PI + profile_shift*circ_pitch/PI,
3337            pol = xy_to_polar(x,y)
3338        )
3339        if (pol.x < arad*1.05)
3340        [pol.x, pol.y-a+180/teeth]
3341    ],
3342    uc_min = min_index(column(undercut,0)),
3343
3344    // Generate a fast lookup table for the undercut.
3345    undercut_lup = [for (i=idx(undercut)) if (i>=uc_min) undercut[i]],
3346
3347    // The u values to use when generating the tooth.
3348    us = [for (i=[0:1:steps*2]) i/steps/2],
3349
3350    // Find top of undercut.
3351    undercut_max = max([
3352        0,
3353        for (u = us) let(
3354            r = lerp(rrad, ma_rad, u),
3355            a1 = lookup(r, involute_lup) + soff,
3356            a2 = lookup(r, undercut_lup),
3357            a = internal || r < undercut_lup[0].x? a1 : min(a1,a2),
3358            b = internal || r < undercut_lup[0].x? false : a1>a2
3359        ) if(a<90+180/teeth && b) r
3360    ]),
3361
3362    // Generate the left half of the tooth.
3363    tooth_half_raw = deduplicate([
3364        for (u = us)
3365            let(
3366                r = lerp(rrad, ma_rad, u),
3367                a1 = lookup(r, involute_lup) + soff,
3368                a2 = lookup(r, undercut_lup),
3369                a = internal || r < undercut_lup[0].x? a1 : min(a1,a2)
3370            )
3371            if ( internal || r > (rrad+clear) )
3372            if (!internal || r < (ma_rad-clear) )
3373            if (a < 90+180/teeth)
3374            polar_to_xy(r, a),
3375        if (!internal)
3376            for (i=[0:1:cap_steps-1]) let(
3377                a = ma_ang + soff - i * (cap_step-1)
3378            ) polar_to_xy(ma_rad, a),
3379    ]),
3380
3381    // Round out the clearance valley
3382    rcircum = 2 * PI * (internal? ma_rad : rrad),
3383    rpart = (180/teeth-tang)/360,
3384    round_r = min(clear, rcircum*rpart),
3385    line1 = internal
3386          ? select(tooth_half_raw,-2,-1)
3387          : select(tooth_half_raw,0,1),
3388    line2 = internal
3389          ? [[0,ma_rad],[-1,ma_rad]]
3390          : zrot(180/teeth, p=[[0,rrad],[1,rrad]]),
3391    isect_pt = line_intersection(line1,line2),
3392    rcorner = internal
3393      ? [last(line1), isect_pt, line2[0]]
3394      : [line2[0], isect_pt, line1[0]],
3395    rounded_tooth_half = deduplicate([
3396        if (!internal && round_r>0) each arc(n=8, r=round_r, corner=rcorner),
3397        if (!internal && round_r<=0) isect_pt,
3398        each tooth_half_raw,
3399        if (internal && round_r>0) each arc(n=8, r=round_r, corner=rcorner),
3400        if (internal && round_r<=0) isect,
3401    ]),
3402
3403    // Strip "jaggies" if found.
3404    strip_left = function(path,i)
3405        i > len(path)? [] :
3406        norm(path[i]) >= undercut_max? [for (j=idx(path)) if(j>=i) path[j]] :
3407        let(
3408            angs = [
3409                for (j=[i+1:1:len(path)-1]) let(
3410                    p = path[i],
3411                    np = path[j],
3412                    r = norm(np),
3413                    a = v_theta(np-p)
3414                ) if(r<undercut_max) a
3415            ],
3416            mti = !angs? 0 : min_index(angs),
3417            out = concat([path[i]], strip_left(path, i + mti + 1))
3418        ) out,
3419    tooth_half = !undercut_max? rounded_tooth_half :
3420        strip_left(rounded_tooth_half, 0),
3421
3422    // Mirror the tooth to complete it.
3423    full_tooth = deduplicate([
3424        each tooth_half,
3425        each reverse(xflip(tooth_half)),
3426    ]),
3427
3428    // Reduce number of vertices.
3429    tooth = path_merge_collinear(
3430        resample_path(full_tooth, n=ceil(2*steps), keep_corners=30, closed=false)
3431    ),
3432
3433    out = center? fwd(prad, p=tooth) : tooth
3434) out;
3435
3436
3437// Section: Gear Assemblies
3438
3439// Function: planetary_gears()
3440// Synopsis: Calculate teeth counts and angles for planetary gear assembly with specified ratio.
3441// Usage:
3442//   gear_data = planetary_gears(mod=|circ_pitch=|diam_pitch=, n, max_teeth, ring_carrier=|carrier_ring=|sun_carrier=|carrier_sun=|sun_ring=|ring_sun=, [helical=], [gear_spin=]);
3443// Description:
3444//   Calculates a planetary gear assembly that approximates a desired transmission ratio.  A planetary gear assembly can be regarded as having three
3445//   elements: the outer ring gear, the central sun gear, and a carrier that holds several planet gears, which fit between the sun and ring.
3446//   The transmission ratio of a planetary gear assembly depends on which element is fixed and which ones are considered the input and output shafts.
3447//   The fixed element can be the ring gear, the sun gear, or the carrier, and then you specify the desired ratio between the other two.
3448//   You must also specify a maximum number of teeth on the ring gear.  The function calculates the best approximation to your desired
3449//   transmission ratio under that constraint: a large enough increase in the allowed number of teeth will yield a more accurate approximation.  Note that the planet gears
3450//   appear uniformly spaced around the sun gear, but this uniformity is often only approximate.  Exact uniformity occurs when teeth_sun+teeth_ring
3451//   is a multiple of the number of planet gears.
3452//   .
3453//   You specify the desired ratio using one of six parameters that identify which ratio you want to specify, and which is the driven element.
3454//   Each different ratio is limited to certain bounds.  For the case of the fixed carrier system, the sun and ring rotate in opposite directions.
3455//   This is sometimes indicated by a negative transmission ratio.  For these cases you can give a positive or negative value.  
3456//   .
3457//   The return is a list of entries that describe the elements of the planetary assembly.  The list entries are:
3458//   - ["sun", teeth, profile_shift, spin]
3459//   - ["ring", teeth, profile_shift, spin]
3460//   - ["planets", teeth, profile_shift, spins, positions, angles]
3461//   - ["ratio", realized_ratio]
3462//   .
3463//   The sun and ring gear are assumed to be placed at the origin.  The planet gears are placed at the list of positions.  The gears all
3464//   have a spin in degrees.  The planets list also includes the angular position of each planet in the `angles` list.
3465//   One of the planets always appears on the X+ axis when `gear_spin` is zero.  The final list entry gives the realized ratio of
3466//   the assembly, so you can determine how closely it approaches your desired ratio.  This will always be a positive value.  
3467//   .
3468//   The sun gear appears by default with a tooth pointing on the Y+ axis with no spin, so if gear_spin is not used then the sun gear spin will
3469//   always be zero.  If you set `gear_spin` then the drive gear for the ratio you specified will be rotated by the specified angle and all
3470//   of the other gears will be rotated appropriately.
3471//   .
3472//   The computation of planetary gear assembles is about determining the teeth counts on the sun, ring and planet gears,
3473//   and the angular positions of the planet gears.
3474//   The tooth size or helical angle are needed only for determining proper profile shifting and for determining the
3475//   gear positions for the profiled shifted gears.  To control the size of the assembly, do a planetary calculation
3476//   with a module of 1 and then scale the module to produce the required gear dimensions.  Remember, you should never
3477//   use `scale()` on gears; change their size by scaling the module or one of the other tooth size parameters.  
3478// Arguments:
3479//   n = Number of planetary gears
3480//   max_teeth = maximum number of teeth allowed on the ring gear
3481//   ---
3482//   mod = The module of the gear, pitch diameter divided by tooth count. 
3483//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3484//   circ_pitch = distance between teeth centers around the pitch circle.
3485//   ring_carrier = set ring/carrier ratio to this value in a ring driven system, must be between 1 and 2
3486//   carrier_ring = set carrier/ring ratio to this value in a carrier driven system, must be between 1/2 and 1
3487//   sun_carrier = set sun/carrier ratio to this value in a sun driven system, must be larger than 2
3488//   carrier_sun = set carrier/sun ratio to this value in a carrier driven system, must be smaller than 1/2
3489//   ring_sun = set ring/sun ratio to this value in a ring driven system, must have absolute value larger than 1
3490//   sun_ring = set sun/ring ratio to this value in a sun driven system, must have absolute value smaller than 1
3491//   helical = create gears with specified helical angle.  Default: 0
3492//   gear_spin = rotate the driven gear by this number of degrees.  Default:0
3493// Example(2D,NoAxes,Anim,Frames=90,FrameMS=30,VPT=[-0.875705,-0.110537,-66.3877],VPR=[0,0,0],VPD=102,Med): In this example we request a ring/carrier ratio of 1.341 and the system produced has a ratio of 4/3.  The sun is fixed, the input is carried by the ring, and the carrier, shown as the blue triangle, is the output, rotating approximately in accordance with the requested ratio.  
3494//   mod=1;
3495//   gear_data = planetary_gears(mod=mod, n=3, max_teeth=28, ring_carrier=1.341, gear_spin=4/3*360/3*$t);
3496//   ring_gear2d(mod=mod, teeth=gear_data[1][1], profile_shift=gear_data[1][2], gear_spin=gear_data[1][3],backing=2);
3497//   stroke(gear_data[2][4],closed=true,color="blue",width=2);
3498//     spur_gear2d(mod=mod, teeth=gear_data[0][1], profile_shift=gear_data[0][2], gear_spin=gear_data[0][3]);  //sun
3499//   color("red")move_copies(gear_data[2][4])
3500//     spur_gear2d(mod=mod, teeth=gear_data[2][1], profile_shift=gear_data[2][2], gear_spin=gear_data[2][3][$idx]);
3501// Example(2D,Med,NoAxes,Anim,FrameMS=60,Frames=90,VPT=[-0.125033,0.508151,-66.3877],VPR=[0,0,0],VPD=192.044): In this example we request a sun/carrier ratio of 3.6 and get exactly that ratio.  The carrier shown as the blue pentagon moves very slowly as the central sun turns.  The ring is fixed.  
3502//   mod=1;
3503//   gear_data = planetary_gears(mod=mod, n=5, max_teeth=70, sun_carrier=3.6, gear_spin=3.6*360/5*$t);
3504//   ring_gear2d(mod=mod, teeth=gear_data[1][1], profile_shift=gear_data[1][2], gear_spin=gear_data[1][3],backing=2);
3505//   stroke(gear_data[2][4],closed=true,color="blue");
3506//   color("gold")
3507//     spur_gear2d(mod=mod, teeth=gear_data[0][1], profile_shift=gear_data[0][2], gear_spin=gear_data[0][3]);  //sun
3508//   color("red")move_copies(gear_data[2][4])
3509//       spur_gear2d(mod=mod, teeth=gear_data[2][1], profile_shift=gear_data[2][2], gear_spin=gear_data[2][3][$idx]);
3510// Example(3D,Med,NoAxes,Anim,Frames=7,FrameMS=50,VPT=[0.128673,0.24149,0.651451],VPR=[38.5,0,21],VPD=222.648): Here we request a sun/ring ratio of 3 and it is exactly achieved.  The carrier, shown in blue, is fixed.  This example is shown with helical gears.  It is important to remember to flip the sign of the helical angle for the planet gears.  
3511//   $fn=81;
3512//   mod=1;
3513//   helical=25;
3514//   gear_data = planetary_gears(mod=mod, n=4, max_teeth=82, sun_ring=3, helical=helical,gear_spin=360/27*$t);
3515//   ring_gear(mod=mod, teeth=gear_data[1][1], profile_shift=gear_data[1][2], helical=helical, gear_spin=gear_data[1][3],backing=4,thickness=7);
3516//   color("blue"){
3517//       move_copies(gear_data[2][4]) cyl(h=12,d=4);
3518//       down(9)linear_extrude(height=3)scale(1.2)polygon(gear_data[2][4]);
3519//   }    
3520//   spur_gear(mod=mod, teeth=gear_data[0][1], profile_shift=gear_data[0][2], helical=helical, gear_spin=gear_data[0][3]);  //sun
3521//   color("red")move_copies(gear_data[2][4])
3522//       spur_gear(mod=mod, teeth=gear_data[2][1], profile_shift=gear_data[2][2], helical=-helical, gear_spin=gear_data[2][3][$idx]);
3523function planetary_gears(n, max_teeth, helical=0, circ_pitch, mod, diam_pitch,
3524                         ring_carrier, carrier_ring, sun_carrier, carrier_sun, sun_ring, ring_sun,
3525                         gear_spin=0) =
3526    let(
3527        mod = module_value(mod=mod,circ_pitch=circ_pitch,diam_pitch=diam_pitch),
3528        dummy = one_defined([ring_carrier,carrier_ring,sun_carrier,carrier_sun,sun_ring,ring_sun],
3529                            "ring_carrier,carrier_ring,sun_carrier,carrier_sun,sun_ring,ring_sun"),
3530        // ratio is between the sun and ring 
3531        ratio = is_def(ring_carrier) ? assert(is_finite(ring_carrier) && ring_carrier>1 && ring_carrier<2, "ring/carrier ratio must be between 1 and 2")
3532                                       ring_carrier - 1
3533              : is_def(carrier_ring) ? assert(is_finite(carrier_ring) && carrier_ring>1/2 && carrier_ring<1, "carrier/ring ratio must be between 1/2 and 1")
3534                                       1/carrier_ring - 1
3535              : is_def(sun_carrier) ?  assert(is_finite(sun_carrier) && sun_carrier>2, "sun/carrier ratio must be larger than 2")
3536                                       1/(sun_carrier-1)
3537              : is_def(carrier_sun) ?  assert(is_finite(carrier_sun) && carrier_sun<1/2, "carrier/sun ratio must be smaller than 1/2")
3538                                       1/(1/carrier_sun-1)
3539              : is_def(sun_ring) ?     assert(is_finite(sun_ring) && abs(sun_ring)>1, "abs(sun/ring) ratio must be larger than 1")
3540                                       1/abs(sun_ring)
3541              : /*is_def(ring_sun)*/   assert(is_finite(ring_sun) && abs(ring_sun)<1, "abs(ring/sun) ratio must be smaller than 1")
3542                                       abs(ring_sun),
3543        pq = rational_approx(ratio, max_teeth),
3544        factor = floor(max_teeth/pq[1]),
3545        temp_z_sun = factor*pq[0],
3546        temp_z_ring = factor*pq[1],
3547        z_sun = temp_z_sun%2==0 ? temp_z_sun+1 : temp_z_sun,
3548        z_ring = temp_z_ring%2==0 ? min(temp_z_ring+1, max_teeth-(max_teeth%2==0?1:0)) : temp_z_ring,
3549        z_planet = (z_ring-z_sun)/2
3550    )
3551    assert(z_planet==floor(z_planet),"Planets have non-integer teeth count!  Algorithm failed.")
3552    let(
3553        d12 = gear_dist(mod=mod,z_sun,z_planet,helical),
3554        ps_sun = auto_profile_shift(teeth=z_sun,helical=helical),
3555        ps_planet = auto_profile_shift(teeth=z_planet,helical=helical),
3556        ps_ring = ps_sun+2*ps_planet,
3557        ring_spin = ring_sun || ring_carrier ? gear_spin
3558                  : sun_ring ? -gear_spin*z_sun/z_ring
3559                  : carrier_ring ? gear_spin*(z_ring+z_sun)/z_ring
3560                  : 0,
3561        planet_rot = ring_carrier ? gear_spin*z_ring/(z_ring+z_sun)
3562                   : carrier_sun || carrier_ring ? gear_spin
3563                   : sun_carrier ? gear_spin*z_sun/(z_ring+z_sun)
3564                   : carrier_ring ? gear_spin*z_ring/(z_ring+z_sun)
3565                   : 0,
3566        sun_spin = ring_sun ? -gear_spin*z_ring/z_sun
3567                 : sun_ring || sun_carrier ? gear_spin
3568                 : carrier_sun ? (z_ring+z_sun)*gear_spin/z_sun
3569                 : 0,
3570        planet_spin = -sun_spin*z_sun/z_planet,
3571
3572        quant = 360/(z_sun+z_ring),
3573        planet_angles = [for (uang=lerpn(0,360,n,endpoint=false)) quant(uang,quant)+planet_rot],
3574        planet_pos = [for(ang=planet_angles) d12*[cos(ang),sin(ang)]],
3575        planet_spins = [for(ang=planet_angles) (z_sun/z_planet)*(ang-90)+90+ang+360/z_planet/2+planet_spin],
3576
3577        final_ratio = ring_carrier ? 1+z_sun/z_ring
3578                    : carrier_ring ? 1/(1+z_sun/z_ring)
3579                    : sun_carrier ? 1+z_ring/z_sun
3580                    : carrier_sun ? 1/(1+z_ring/z_sun)
3581                    : sun_ring ? z_ring/z_sun
3582                    : /* ring_run */ z_sun/z_ring
3583   )   
3584   [  
3585     ["sun", z_sun, ps_sun, sun_spin],
3586     ["ring", z_ring, ps_ring, 360/z_ring/2 * (1-(z_sun%2))+ring_spin],
3587     ["planets", z_planet, ps_planet, planet_spins, planet_pos, planet_angles],
3588     ["ratio", final_ratio]
3589   ];
3590
3591
3592
3593// Section: Computing Gear Dimensions
3594//   These functions let the user find the derived dimensions of the gear.
3595//   A gear fits within a circle of radius outer_radius, and two gears should have
3596//   their centers separated by the sum of their pitch_radius.
3597
3598
3599// Function: circular_pitch()
3600// Synopsis: Returns tooth density expressed as "circular pitch".
3601// Topics: Gears, Parts
3602// See Also: spur_gear(), diametral_pitch(), circular_pitch(), module_value()
3603// Usage:
3604//   circ_pitch = circular_pitch(circ_pitch);
3605//   circ_pitch = circular_pitch(mod=);
3606//   circ_pitch = circular_pitch(diam_pitch=);
3607// Description:
3608//   Get tooth size expressed as "circular pitch", or the distance between teeth centers around the pitch circle.
3609//   For example, an 11 tooth gear with a pitch circumference of 110 mm has a circular pitch of 110 mm /11, or 10 mm / tooth.
3610//   Note that this calculation is does not depend on units for circ_pitch or mod, but the `diam_pitch` argument is based
3611//   on inches and returns its value in millimeters.  
3612// Arguments:
3613//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
3614//   ---
3615//   mod = The module of the gear (pitch diameter / teeth)
3616//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3617// Example(2D,Med,VPT=[0,31,0],VPR=[0,0,0],VPD=40):
3618//   $fn=144;
3619//   teeth=20;
3620//   circ_pitch = circular_pitch(diam_pitch=8);
3621//   pr = pitch_radius(circ_pitch, teeth);
3622//   stroke(spur_gear2d(circ_pitch, teeth), width=0.1);
3623//   color("cyan")
3624//       dashed_stroke(circle(r=pr), width=0.1);
3625//   color("black") {
3626//       stroke(
3627//           arc(r=pr, start=90+90/teeth, angle=-360/teeth),
3628//           width=0.2, endcaps="arrow");
3629//       back(pr+1) right(3)
3630//          zrot(30) text("Circular Pitch", size=1);
3631//   }
3632// Example:
3633//   circ_pitch1 = circular_pitch(circ_pitch=5);
3634//   circ_pitch2 = circular_pitch(diam_pitch=12);
3635//   circ_pitch3 = circular_pitch(mod=2);
3636
3637function circular_pitch(circ_pitch, mod, pitch, diam_pitch) =
3638    assert(one_defined([pitch, mod, circ_pitch, diam_pitch], "pitch,mod,circ_pitch,diam_pitch"))
3639    pitch != undef? assert(is_finite(pitch) && pitch>0) pitch :
3640    circ_pitch != undef? assert(is_finite(circ_pitch) && circ_pitch>0) circ_pitch :
3641    diam_pitch != undef? assert(is_finite(diam_pitch) && diam_pitch>0) PI / diam_pitch * INCH :
3642    assert(is_finite(mod) && mod>0) mod * PI;
3643
3644
3645// Function: diametral_pitch()
3646// Synopsis: Returns tooth density expressed as "diametral pitch".
3647// Topics: Gears, Parts
3648// See Also: spur_gear(), diametral_pitch(), circular_pitch(), module_value()
3649// Usage:
3650//   dp = diametral_pitch(circ_pitch);
3651//   dp = diametral_pitch(mod=);
3652//   dp = diametral_pitch(diam_pitch=);
3653// Description:
3654//   Returns tooth density expressed as "diametral pitch", the number of teeth per inch of pitch diameter.
3655//   For example, if you have a gear with 30 teeth, with a 1.5 inch pitch diameter, then you have a
3656//   diametral pitch of 20 teeth/inch.
3657// Arguments:
3658//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
3659//   ---
3660//   mod = The module of the gear (pitch diameter / teeth)
3661//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3662// Example:
3663//   diam_pitch1 = diametral_pitch(mod=2);
3664//   diam_pitch2 = diametral_pitch(circ_pitch=8);
3665//   diam_pitch3 = diametral_pitch(diam_pitch=16);
3666
3667function diametral_pitch(circ_pitch, mod, pitch, diam_pitch) =
3668    let( circ_pitch = circular_pitch(pitch, mod, circ_pitch, diam_pitch) )
3669    PI / circ_pitch / INCH;
3670
3671
3672// Function: module_value()
3673// Synopsis: Returns tooth density expressed as "module"
3674// Topics: Gears, Parts
3675// See Also: spur_gear(), diametral_pitch(), circular_pitch(), module_value()
3676// Usage:
3677//   mod = module_value(circ_pitch);
3678//   mod = module_value(mod=);
3679//   mod = module_value(diam_pitch=);
3680// Description:
3681//   Get tooth size expressed as "module".  The module is the pitch
3682//   diameter of the gear divided by the number of teeth on the gear.  For example, a gear with a pitch
3683//   diameter of 40 mm, with 20 teeth on it will have a modulus of 2 mm.  For circ_pitch and mod this
3684//   calculation does not depend on untis.  If you give diametral pitch, which is based on inputs, then
3685//   the module is returned in millimeters.  
3686// Arguments:
3687//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
3688//   ---
3689//   mod = The module of the gear (pitch diameter / teeth)
3690//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3691// Example:
3692//   mod1 = module_value(circ_pitch=8);
3693//   mod2 = module_value(mod=2);
3694//   mod3 = module_value(diam_pitch=16);
3695
3696function module_value(circ_pitch, mod, pitch, diam_pitch) =
3697    let( circ_pitch = circular_pitch(circ_pitch, mod, pitch, diam_pitch) )
3698    circ_pitch / PI;
3699
3700
3701/// Function: _adendum()
3702/// Usage:
3703///   ad = _adendum(circ_pitch, [profile_shift]);
3704///   ad = _adendum(diam_pitch=, [profile_shift=]);
3705///   ad = _adendum(mod=, [profile_shift=]);
3706/// Topics: Gears
3707/// Description:
3708///   The height of the top of a gear tooth above the pitch radius circle.
3709/// Arguments:
3710///   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
3711///   profile_shift = Profile shift factor x.  Default: 0 
3712///   ---
3713///   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3714///   mod = The module of the gear (pitch diameter / teeth)
3715/// Example:
3716///   ad = _adendum(circ_pitch=5);
3717///   ad = _adendum(mod=2);
3718/// Example(2D):
3719///   circ_pitch = 5; teeth = 17;
3720///   pr = pitch_radius(circ_pitch, teeth);
3721///   adn = _adendum(circ_pitch=5);
3722///   #spur_gear2d(circ_pitch=circ_pitch, teeth=teeth);
3723///   color("black") {
3724///       stroke(circle(r=pr),width=0.1,closed=true);
3725///       stroke(circle(r=pr+adn),width=0.1,closed=true);
3726///   }
3727
3728function _adendum(
3729    circ_pitch,
3730    profile_shift=0,
3731    shorten=0,
3732    diam_pitch,
3733    mod,
3734    pitch
3735) =
3736    let( mod = module_value(circ_pitch, mod, pitch, diam_pitch) )
3737    mod * (1 + profile_shift - shorten);
3738
3739
3740
3741/// Function: _dedendum()
3742/// Usage:
3743///   ddn = _dedendum(circ_pitch=, [clearance], [profile_shift]);
3744///   ddn = _dedendum(diam_pitch=, [clearance=], [profile_shift=]);
3745///   ddn = _dedendum(mod=, [clearance=], [profile_shift=]);
3746/// Topics: Gears
3747/// Description:
3748///   The depth of the gear tooth valley, below the pitch radius.
3749/// Arguments:
3750///   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
3751///   clearance = If given, sets the clearance between meshing teeth.  Default: module/4
3752///   profile_shift = Profile shift factor x.  Default: 0
3753///   ---
3754///   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3755///   mod = The module of the gear (pitch diameter / teeth)
3756///   shorten = amount to shorten tip 
3757/// Example:
3758///   ddn = _dedendum(circ_pitch=5);
3759///   ddn = _dedendum(mod=2);
3760/// Example(2D):
3761///   circ_pitch = 5; teeth = 17;
3762///   pr = pitch_radius(circ_pitch, teeth);
3763///   ddn = _dedendum(circ_pitch=5);
3764///   #spur_gear2d(circ_pitch=circ_pitch, teeth=teeth);
3765///   color("black") {
3766///       stroke(circle(r=pr),width=0.1,closed=true);
3767///       stroke(circle(r=pr-ddn),width=0.1,closed=true);
3768///   }
3769
3770function _dedendum(
3771    circ_pitch,
3772    clearance,
3773    profile_shift=0,
3774    diam_pitch,
3775    mod,
3776    pitch
3777) = let(
3778        mod = module_value(circ_pitch, mod, pitch, diam_pitch),
3779        clearance = default(clearance, 0.25 * mod)
3780    )
3781    mod * (1 - profile_shift) + clearance;
3782
3783
3784// Function: pitch_radius()
3785// Synopsis: Returns the pitch radius for a gear.
3786// Topics: Gears, Parts
3787// See Also: spur_gear(), diametral_pitch(), circular_pitch(), module_value(), outer_radius()
3788// Usage:
3789//   pr = pitch_radius(pitch, teeth, [helical]);
3790//   pr = pitch_radius(mod=, teeth=, [helical=]);
3791// Description:
3792//   Calculates the pitch radius for the gear.  Two mated gears will have their centers spaced apart
3793//   by the sum of the two gear's pitch radii.
3794// Arguments:
3795//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
3796//   teeth = The number of teeth on the gear.
3797//   helical = The helical angle (from vertical) of the teeth on the gear.  Default: 0
3798//   ---
3799//   mod = The module of the gear (pitch diameter / teeth)
3800//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3801// Example:
3802//   pr = pitch_radius(circ_pitch=5, teeth=11);
3803//   pr = pitch_radius(circ_pitch=5, teeth=11, helical=30);
3804//   pr = pitch_radius(diam_pitch=10, teeth=11);
3805//   pr = pitch_radius(mod=2, teeth=20);
3806//   pr = pitch_radius(mod=2, teeth=20, helical=30);
3807// Example(2D,Med,NoScales,VPT=[-0.20531,0.133721,0.658081],VPR=[0,0,0],VPD=82.6686):
3808//   $fn=144;
3809//   teeth=17; circ_pitch = 5;
3810//   pr = pitch_radius(circ_pitch, teeth);
3811//   stroke(spur_gear2d(circ_pitch, teeth), width=0.2);
3812//   color("blue") dashed_stroke(circle(r=pr), width=0.2);
3813//   color("black") {
3814//       stroke([[0,0],polar_to_xy(pr,45)],
3815//           endcaps="arrow", width=0.3);
3816//       fwd(1)
3817//           text("Pitch Radius", size=1.5,
3818//               halign="center", valign="top");
3819//   }
3820
3821function pitch_radius(
3822    circ_pitch,
3823    teeth,
3824    helical=0,
3825    mod,
3826    diam_pitch,
3827    pitch
3828) =
3829    let( circ_pitch = circular_pitch(pitch, mod, circ_pitch, diam_pitch) )
3830    assert(is_finite(helical))
3831    assert(is_finite(circ_pitch))
3832    circ_pitch * teeth / PI / 2 / cos(helical);
3833
3834
3835// Function: outer_radius()
3836// Synopsis: Returns the outer radius for a gear.
3837// Topics: Gears, Parts
3838// See Also: spur_gear(), diametral_pitch(), circular_pitch(), module_value(), pitch_radius(), outer_radius()
3839// Usage:
3840//   or = outer_radius(circ_pitch, teeth, [helical=], [clearance=], [internal=], [profile_shift=], [shorten=]);
3841//   or = outer_radius(mod=, teeth=, [helical=], [clearance=], [internal=], [profile_shift=], [shorten=]);
3842//   or = outer_radius(diam_pitch=, teeth=, [helical=], [clearance=], [internal=], [profile_shift=], [shorten=]);
3843// Description:
3844//   Calculates the outer radius for the gear. The gear fits entirely within a cylinder of this radius, unless
3845//   it has been strongly profile shifted, in which case it will be undersized due to tip clipping.
3846// Arguments:
3847//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
3848//   teeth = The number of teeth on the gear.
3849//   ---
3850//   clearance = If given, sets the clearance between meshing teeth.  Default: module/4
3851//   profile_shift = Profile shift factor x.  Default: "auto"
3852//   pressure_angle = Pressure angle.  Default: 20
3853//   helical = The helical angle (from vertical) of the teeth on the gear.  Default: 0
3854//   shorten = Shortening factor, needed to maintain clearance with profile shifting.  Default: 0
3855//   internal = If true, calculate for an internal gear.
3856//   mod = The module of the gear (pitch diameter / teeth)
3857//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3858// Example:
3859//   or = outer_radius(circ_pitch=5, teeth=20);
3860//   or = outer_radius(circ_pitch=5, teeth=20, helical=30);
3861//   or = outer_radius(diam_pitch=10, teeth=17);
3862//   or = outer_radius(mod=2, teeth=16);
3863// Example(2D,Med,NoScales,VPT=[-0.20531,0.133721,0.658081],VPR=[0,0,0],VPD=82.6686):
3864//   $fn=144;
3865//   teeth=17; circ_pitch = 5;
3866//   or = outer_radius(circ_pitch, teeth);
3867//   stroke(spur_gear2d(circ_pitch, teeth), width=0.2);
3868//   color("blue") dashed_stroke(circle(r=or), width=0.2);
3869//   color("black") {
3870//       stroke([[0,0],polar_to_xy(or,45)],
3871//           endcaps="arrow", width=0.3);
3872//       fwd(1)
3873//           text("Outer Radius", size=1.5,
3874//               halign="center", valign="top");
3875//   }
3876
3877function outer_radius(circ_pitch, teeth, clearance, internal=false, helical=0, profile_shift="auto", pressure_angle=20, shorten=0, mod, pitch, diam_pitch) =
3878    let(
3879       circ_pitch = circular_pitch(pitch, mod, circ_pitch, diam_pitch),
3880       profile_shift = auto_profile_shift(teeth, pressure_angle, helical, profile_shift=profile_shift)
3881    )
3882    pitch_radius(circ_pitch, teeth, helical) + (
3883        internal
3884          ? _dedendum(circ_pitch, clearance, profile_shift=-profile_shift)
3885          : _adendum(circ_pitch, profile_shift=profile_shift, shorten=shorten)
3886    );
3887
3888
3889/// Function: _root_radius()
3890/// Usage:
3891///   rr = _root_radius(circ_pitch, teeth, [helical], [clearance=], [internal=], [profile_shift=]);
3892///   rr = _root_radius(diam_pitch=, teeth=, [helical=], [clearance=], [internal=], [profile_shift=]);
3893///   rr = _root_radius(mod=, teeth=, [helical=], [clearance=], [internal=], [profile_shift=]);
3894/// Topics: Gears
3895/// Description:
3896///   Calculates the root radius for the gear, at the base of the dedendum.  Does not apply auto profile shifting. 
3897/// Arguments:
3898///   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
3899///   teeth = The number of teeth on the gear.
3900///   ---
3901///   clearance = If given, sets the clearance between meshing teeth.  Default: module/4
3902///   internal = If true, calculate for an internal gear.
3903///   helical = The helical angle (from vertical) of the teeth on the gear.  Default: 0
3904///   profile_shift = Profile shift factor x.  Default:0
3905///   mod = The module of the gear (pitch diameter / teeth)
3906///   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3907/// Example:
3908///   rr = _root_radius(circ_pitch=5, teeth=11);
3909///   rr = _root_radius(circ_pitch=5, teeth=16, helical=30);
3910///   rr = _root_radius(diam_pitch=10, teeth=11);
3911///   rr = _root_radius(mod=2, teeth=16);
3912/// Example(2D):
3913///   pr = _root_radius(circ_pitch=5, teeth=11);
3914///   #spur_gear2d(pitch=5, teeth=11);
3915///   color("black")
3916///       stroke(circle(r=pr),width=0.1,closed=true);
3917
3918function _root_radius(circ_pitch, teeth, clearance, internal=false, helical=0, profile_shift=0, diam_pitch, mod, pitch) =
3919    let( circ_pitch = circular_pitch(pitch, mod, circ_pitch, diam_pitch) )
3920    pitch_radius(circ_pitch, teeth, helical) - (
3921        internal
3922          ? _adendum(circ_pitch, profile_shift=-profile_shift)
3923          : _dedendum(circ_pitch, clearance, profile_shift=profile_shift)
3924    );
3925
3926
3927/// Function: _base_radius()
3928/// Usage:
3929///   br = _base_radius(circ_pitch, teeth, [pressure_angle], [helical]);
3930///   br = _base_radius(diam_pitch=, teeth=, [pressure_angle=], [helical=]);
3931///   br = _base_radius(mod=, teeth=, [pressure_angle=], [helical=]);
3932/// Topics: Gears
3933/// Description:
3934///   Get the base circle for involute teeth, at the base of the teeth.
3935/// Arguments:
3936///   pitch = The circular pitch, the distance between teeth centers around the pitch circle.
3937///   teeth = The number of teeth on the gear.
3938///   pressure_angle = Pressure angle in degrees.  Controls how straight or bulged the tooth sides are.
3939///   helical = The helical angle (from vertical) of the teeth on the gear.  Default: 0
3940///   ---
3941///   mod = The module of the gear (pitch diameter / teeth)
3942///   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
3943/// Example:
3944///   br = _base_radius(circ_pitch=5, teeth=20, pressure_angle=20);
3945///   br = _base_radius(circ_pitch=5, teeth=20, pressure_angle=20, helical=30);
3946///   br = _base_radius(diam_pitch=10, teeth=20, pressure_angle=20);
3947///   br = _base_radius(mod=2, teeth=18, pressure_angle=20);
3948/// Example(2D):
3949///   pr = _base_radius(circ_pitch=5, teeth=11);
3950///   #spur_gear2d(circ_pitch=5, teeth=11);
3951///   color("black")
3952///       stroke(circle(r=pr),width=0.1,closed=true);
3953
3954function _base_radius(circ_pitch, teeth, pressure_angle=20, helical=0, diam_pitch, mod, pitch) =
3955    let(
3956        circ_pitch = circular_pitch(pitch, mod, circ_pitch, diam_pitch),
3957        trans_pa = atan(tan(pressure_angle)/cos(helical))
3958    )
3959    pitch_radius(circ_pitch, teeth, helical) * cos(trans_pa);
3960
3961
3962// Function: bevel_pitch_angle()
3963// Synopsis: Returns the pitch cone angle for a bevel gear.
3964// Topics: Gears, Parts
3965// See Also: bevel_gear(), pitch_radius(), outer_radius()
3966// Usage:
3967//   ang = bevel_pitch_angle(teeth, mate_teeth, [drive_angle=]);
3968// Description:
3969//   Returns the correct pitch cone angle for a bevel gear with a given number of teeth, that is
3970//   matched to another bevel gear with a (possibly different) number of teeth.
3971// Arguments:
3972//   teeth = Number of teeth that this gear has.
3973//   mate_teeth = Number of teeth that the matching gear has.
3974//   drive_angle = Angle between the drive shafts of each gear.  Default: 90º.
3975// Example:
3976//   ang = bevel_pitch_angle(teeth=18, mate_teeth=30);
3977// Example(2D):
3978//   t1 = 13; t2 = 19; pitch=5;
3979//   pang = bevel_pitch_angle(teeth=t1, mate_teeth=t2, drive_angle=90);
3980//   color("black") {
3981//       zrot_copies([0,pang])
3982//           stroke([[0,0,0], [0,-20,0]],width=0.2);
3983//       stroke(arc(r=3, angle=[270,270+pang]),width=0.2);
3984//   }
3985//   #bevel_gear(
3986//       pitch=5, teeth=t1, mate_teeth=t2,
3987//       spiral=0, cutter_radius=1000,
3988//       slices=12, anchor="apex", orient=BACK
3989//   );
3990
3991function bevel_pitch_angle(teeth, mate_teeth, drive_angle=90) =
3992    atan(sin(drive_angle)/((mate_teeth/teeth)+cos(drive_angle)));
3993
3994
3995// Function: worm_gear_thickness()
3996// Synopsis: Returns the thickness for a worm gear.
3997// Topics: Gears, Parts
3998// See Also: worm(), worm_gear(), pitch_radius(), outer_radius()
3999// Usage:
4000//   thick = worm_gear_thickness(pitch, teeth, worm_diam, [worm_arc=], [crowning=], [clearance=]);
4001//   thick = worm_gear_thickness(mod=, teeth=, worm_diam=, [worm_arc=], [crowning=], [clearance=]);
4002// Description:
4003//   Calculate the thickness of the worm gear.
4004// Arguments:
4005//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.  Default: 5
4006//   teeth = Total number of teeth along the rack.  Default: 30
4007//   worm_diam = The pitch diameter of the worm gear to match to.  Default: 30
4008//   ---
4009//   worm_arc = The arc of the worm to mate with, in degrees. Default: 45 degrees
4010//   pressure_angle = Pressure angle in degrees.  Controls how straight or bulged the tooth sides are.  Default: 20º
4011//   crowning = The amount to oversize the virtual hobbing cutter used to make the teeth, to add a slight crowning to the teeth to make them fit the work easier.  Default: 1
4012//   clearance = Clearance gap at the bottom of the inter-tooth valleys.  Default: module/4
4013//   mod = The module of the gear (pitch diameter / teeth)
4014//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
4015// Example:
4016//   thick = worm_gear_thickness(circ_pitch=5, teeth=36, worm_diam=30);
4017//   thick = worm_gear_thickness(mod=2, teeth=28, worm_diam=25);
4018// Example(2D):
4019//   circ_pitch = 5;
4020//   teeth = 17;
4021//   worm_diam = 30;
4022//   worm_starts = 2;
4023//   worm_arc = 40;
4024//   y = worm_gear_thickness(
4025//       circ_pitch=circ_pitch,
4026//       teeth=teeth,
4027//       worm_diam=worm_diam,
4028//       worm_arc=worm_arc
4029//   );
4030//   #worm_gear(
4031//       circ_pitch=circ_pitch,
4032//       teeth=teeth,
4033//       worm_diam=worm_diam,
4034//       worm_arc=worm_arc,
4035//       worm_starts=worm_starts,
4036//       orient=BACK
4037//   );
4038//   color("black") {
4039//       ycopies(y) stroke([[-25,0],[25,0]], width=0.5);
4040//       stroke([[-20,-y/2],[-20,y/2]],width=0.5,endcaps="arrow");
4041//   }
4042
4043function worm_gear_thickness(
4044    circ_pitch,
4045    teeth,
4046    worm_diam,
4047    worm_arc=45,
4048    pressure_angle=20,
4049    crowning=0.1,
4050    clearance,
4051    diam_pitch,
4052    mod,
4053    pitch
4054) = let(
4055        circ_pitch = circular_pitch(pitch, mod, circ_pitch, diam_pitch),
4056        thickness = worm_gear(
4057            circ_pitch=circ_pitch,
4058            teeth=teeth,
4059            worm_diam=worm_diam,
4060            worm_arc=worm_arc,
4061            crowning=crowning,
4062            pressure_angle=pressure_angle,
4063            clearance=clearance,
4064            get_thickness=true
4065        )
4066    ) thickness;
4067
4068
4069// Function: worm_dist()
4070// Synopsis: Returns the distance between a worm and a worm gear
4071// Topics: Gears, Parts
4072// See Also: worm(), worm_gear(), pitch_radius(), outer_radius()
4073// Usage:
4074//   dist = worm_dist(mod=|diam_pitch=|circ_pitch=, d, starts, teeth, [profile_shift], [pressure_angle=]);
4075// Description:
4076//   Calculate the distance between the centers of a worm and its mating worm gear, taking account
4077//   possible profile shifting of the worm gear.
4078// Arguments:
4079//   d = diameter of worm
4080//   starts = number of starts of worm
4081//   teeth = number of teeth on worm gear
4082//   profile_shift = profile shift of worm gear
4083//   ---
4084//   mod = The module of the gear (pitch diameter / teeth)
4085//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
4086//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
4087//   pressure_angle = The pressure angle of the gear.
4088//   backlash = Add extra space to produce a total of 2*backlash between the two gears. 
4089
4090function worm_dist(d,starts,teeth,mod,profile_shift=0,diam_pitch,circ_pitch,pressure_angle=20,backlash=0) =
4091  let(
4092      mod = module_value(mod=mod,diam_pitch=diam_pitch,circ_pitch=circ_pitch),
4093      lead_angle = asin(mod*starts/d),
4094      pitch_diam = mod*teeth/cos(lead_angle)
4095  )
4096  (d+pitch_diam)/2 + profile_shift*mod
4097//   + backlash * (cos(lead_angle)+cos(90-lead_angle)) / tan(pressure_angle);
4098//    + backlash * cos(45-lead_angle) / tan(pressure_angle);
4099     + backlash * cos(lead_angle) / tan(pressure_angle);
4100
4101
4102
4103// Function: gear_dist()
4104// Synopsis: Returns the distance between two gear centers for spur gears or parallel axis helical gears.
4105// Topics: Gears, Parts
4106// See Also: worm(), worm_gear(), pitch_radius(), outer_radius()
4107// Usage:
4108//   dist = gear_dist(mod=|diam_pitch=|circ_pitch=, teeth1, teeth2, [helical], [profile_shift1], [profile_shift2], [pressure_angle=], [backlash=]);
4109// Description:
4110//   Calculate the distance between the centers of two spur gears gears or helical gears with parallel axes,
4111//   taking into account profile shifting and helical angle.  You can give the helical angle as either positive or negative.  
4112//   If you set one of the tooth counts to zero than that gear will be treated as a rack and the distance returned is the
4113//   distance between the rack's pitch line and the gear's center.  If you set internal1 or internal2 to true then the
4114//   specified gear is a ring gear;  the returned distance is still the distance between the centers of the gears.  Note that
4115//   for a regular gear and ring gear to be compatible the ring gear must have more teeth and at least as much profile shift
4116//   as the regular gear.
4117//   .
4118//   The backlash parameter computes the distance offset that produces a total backlash of `2*backlash` in the
4119//   two gear mesh system.  This is equivalent to giving the same backlash argument to both gears.  
4120// Arguments:
4121//   teeth1 = Total number of teeth in the first gear.  If given 0, we assume this is a rack or worm.
4122//   teeth2 = Total number of teeth in the second gear.  If given 0, we assume this is a rack or worm.
4123//   helical = The value of the helical angle (from vertical) of the teeth on the two gears (either sign).  Default: 0
4124//   profile_shift1 = Profile shift factor x for the first gear.  Default: 0
4125//   profile_shift2 = Profile shift factor x for the second gear.  Default: 0
4126//   --
4127//   mod = The module of the gear (pitch diameter / teeth)
4128//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
4129//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
4130//   internal1 = first gear is an internal (ring) gear.  Default: false
4131//   internal2 = second gear is an internal (ring) gear.  Default: false
4132//   pressure_angle = The pressure angle of the gear.
4133//   backlash = Add extra space to produce a total of 2*backlash between the two gears. 
4134// Example(2D,NoAxes): Spur gears (with automatic profile shifting on both)
4135//   circ_pitch=5; teeth1=7; teeth2=24;
4136//   d = gear_dist(circ_pitch=circ_pitch, teeth1, teeth2);
4137//   spur_gear2d(circ_pitch, teeth1, gear_spin=-90);
4138//   right(d) spur_gear2d(circ_pitch, teeth2, gear_spin=90-180/teeth2);
4139// Example(3D,NoAxes,Med,VPT=[23.9049,5.42594,-4.68026],VPR=[64.8,0,353.5],VPD=140): Helical gears (with auto profile shifting on one of the gears)
4140//   circ_pitch=5; teeth1=7; teeth2=24; helical=37;
4141//   d = gear_dist(circ_pitch=circ_pitch, teeth1, teeth2, helical);
4142//   spur_gear(circ_pitch, teeth1, helical=helical, gear_spin=-90,slices=15);
4143//   right(d) spur_gear(circ_pitch, teeth2, helical=-helical, gear_spin=-90-180/teeth2,slices=9);
4144// Example(2D,NoAxes): Disable Auto Profile Shifting on the smaller gear
4145//   circ_pitch=5; teeth1=7; teeth2=24;
4146//   d = gear_dist(circ_pitch=circ_pitch, teeth1, teeth2, profile_shift1=0);
4147//   spur_gear2d(circ_pitch, teeth1, profile_shift=0, gear_spin=-90);
4148//   right(d) spur_gear2d(circ_pitch, teeth2, gear_spin=90-180/teeth2);
4149// Example(2D,NoAxes): Manual Profile Shifting
4150//   circ_pitch=5; teeth1=7; teeth2=24; ps1 = 0.5; ps2 = -0.2;
4151//   d = gear_dist(circ_pitch=circ_pitch, teeth1, teeth2, profile_shift1=ps1, profile_shift2=ps2);
4152//   spur_gear2d(circ_pitch, teeth1, profile_shift=ps1, gear_spin=-90);
4153//   right(d) spur_gear2d(circ_pitch, teeth2, profile_shift=ps2, gear_spin=90-180/teeth2);
4154// Example(2D,NoAxes): Profile shifted gear and a rack
4155//   mod=3; teeth=8;
4156//   d = gear_dist(mod=mod, teeth, 0);
4157//   rack2d(mod=mod, teeth=5, bottom=9);
4158//   back(d) spur_gear2d(mod=mod, teeth=teeth, gear_spin=180/teeth);
4159// Example(3D,Med,NoAxes,VPT=[-0.0608489,1.3772,-3.68839],VPR=[63.4,0,29.7],VPD=113.336): Profile shifted helical gear and rack 
4160//   mod=3; teeth=8; helical=29;
4161//   d = gear_dist(mod=mod, teeth, 0, helical);
4162//   rack(mod=mod, teeth=5, helical=helical, orient=FWD);
4163//   color("lightblue")
4164//     fwd(d) spur_gear(mod=mod, teeth=teeth, helical=-helical, gear_spin=180/teeth);
4165function gear_dist(
4166    teeth1,
4167    teeth2,
4168    helical=0,
4169    profile_shift1,
4170    profile_shift2,
4171    internal1=false,
4172    internal2=false,
4173    backlash = 0,
4174    pressure_angle=20,
4175    diam_pitch,
4176    circ_pitch,
4177    mod
4178) =
4179    assert(all_nonnegative([teeth1,teeth2]),"Must give nonnegative values for teeth")
4180    assert(teeth1>0 || teeth2>0, "One of the teeth counts must be nonzero")
4181    assert(is_bool(internal1))
4182    assert(is_bool(internal2))
4183    assert(is_finite(helical))
4184    assert(!(internal1&&internal2), "Cannot specify both gears as internal")
4185    assert(!(internal1 || internal2) || (teeth1>0 && teeth2>0), "Cannot specify internal gear with rack (zero tooth count)")
4186    let(
4187        mod = module_value(mod=mod,circ_pitch= circ_pitch, diam_pitch=diam_pitch),
4188        profile_shift1 = auto_profile_shift(teeth1,pressure_angle,helical,profile_shift=profile_shift1),
4189        profile_shift2 = auto_profile_shift(teeth2,pressure_angle,helical,profile_shift=profile_shift2),
4190        teeth1 = internal2? -teeth1 : teeth1,
4191        teeth2 = internal1? -teeth2 : teeth2
4192    )
4193    assert(teeth1+teeth2>0, "Internal gear must have more teeth than the mated external gear")
4194    let(
4195        profile_shift1 = internal2? -profile_shift1 : profile_shift1,
4196        profile_shift2 = internal1? -profile_shift2 : profile_shift2
4197    )
4198    assert(!(internal1||internal2) || profile_shift1+profile_shift2>=0, "Internal gear must have profile shift equal or greater than mated external gear")
4199    teeth1==0 || teeth2==0? pitch_radius(mod=mod, teeth=teeth1+teeth2, helical=helical) + (profile_shift1+profile_shift2)*mod
4200    :
4201    let(
4202        pa_eff = _working_pressure_angle(teeth1,profile_shift1,teeth2,profile_shift2,pressure_angle,helical),
4203        pa_transv = atan(tan(pressure_angle)/cos(helical))
4204    )
4205    mod*(teeth1+teeth2)*cos(pa_transv)/cos(pa_eff)/cos(helical)/2
4206        + (internal1||internal2?-1:1) * backlash*cos(helical)/tan(pressure_angle);
4207
4208function _invol(a) = tan(a) - a*PI/180;
4209
4210function _working_pressure_angle(teeth1,profile_shift1, teeth2, profile_shift2, pressure_angle, helical) =
4211  let(
4212      pressure_angle = atan(tan(pressure_angle)/cos(helical))
4213  )
4214  teeth1==0 || teeth2==0 ? pressure_angle
4215  :
4216  let(
4217      rhs = 2*(profile_shift1+profile_shift2)/(teeth1+teeth2)*cos(helical)*tan(pressure_angle) + _invol(pressure_angle)
4218  )
4219  assert(rhs>0, "Total profile shift is too small, so working pressure angle is negative, and no valid gear separation exists")
4220  let(
4221      pa_eff = root_find(function (x) _invol(x)-rhs, 1, 75)
4222  )
4223  pa_eff;
4224
4225
4226
4227// Function: gear_dist_skew()
4228// Usage:
4229// Synopsis: Returns the distance between two helical gear centers with skew axes.  
4230// Topics: Gears, Parts
4231// See Also: gear_dist(), worm(), worm_gear(), pitch_radius(), outer_radius()
4232// Usage:
4233//   dist = gear_dist_skew(mod=|diam_pitch=|circ_pitch=, teeth1, teeth2, helical1, helical2, [profile_shift1], [profile_shift2], [pressure_angle=]
4234// Description:
4235//   Calculate the distance between two helical gears that mesh with non-parallel axes, taking into account
4236//   profile shift and the helical angles.
4237// Arguments:
4238//   teeth1 = Total number of teeth in the first gear.  If given 0, we assume this is a rack or worm.
4239//   teeth2 = Total number of teeth in the second gear.  If given 0, we assume this is a rack or worm.
4240//   helical1 = The helical angle (from vertical) of the teeth on the first gear. 
4241//   helical1 = The helical angle (from vertical) of the teeth on the second gear.
4242//   profile_shift1 = Profile shift factor x for the first gear.  Default: "auto"
4243//   profile_shift2 = Profile shift factor x for the second gear.  Default: "auto"
4244//   --
4245//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
4246//   mod = The module of the gear (pitch diameter / teeth)
4247//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
4248//   pressure_angle = The pressure angle of the gear.
4249//   backlash = Add extra space to produce a total of 2*backlash between the two gears. 
4250// Example(3D,Med,NoAxes,VPT=[-0.302111,3.7924,-9.252],VPR=[55,0,25],VPD=155.556): Non-parallel Helical Gears (without any profile shifting)
4251//   circ_pitch=5; teeth1=15; teeth2=24; ha1=45; ha2=30; thick=10;
4252//   d = gear_dist_skew(circ_pitch=circ_pitch, teeth1, teeth2, helical1=ha1, helical2=ha2);
4253//   left(d/2) spur_gear(circ_pitch, teeth1, helical=ha1, thickness=thick, gear_spin=-90);
4254//   right(d/2) xrot(ha1+ha2) spur_gear(circ_pitch, teeth2, helical=ha2, thickness=thick, gear_spin=90-180/teeth2);
4255function gear_dist_skew(teeth1,teeth2,helical1,helical2,profile_shift1,profile_shift2,pressure_angle=20,
4256                        mod, circ_pitch, diam_pitch, backlash=0) =
4257  assert(all_nonnegative([teeth1,teeth2]),"Must give nonnegative values for teeth")
4258  assert(teeth1>0 || teeth2>0, "One of the teeth counts must be nonzero")
4259  let(
4260      profile_shift1 = auto_profile_shift(teeth1,pressure_angle,helical1,profile_shift=profile_shift1),
4261      profile_shift2 = auto_profile_shift(teeth2,pressure_angle,helical2,profile_shift=profile_shift2),
4262      mod = module_value(circ_pitch=circ_pitch, diam_pitch=diam_pitch, mod=mod)
4263  )
4264  teeth1==0 || teeth2==0? pitch_radius(mod=mod, teeth=teeth1+teeth2, helical=teeth1?helical1:helical2) + (profile_shift1+profile_shift2)*mod
4265  :
4266  let(
4267      pa_normal_eff = _working_normal_pressure_angle_skew(teeth1,profile_shift1,helical1,teeth2,profile_shift2,helical2,pressure_angle),
4268      dist_adj = 0.5*(teeth1/cos(helical1)^3+teeth2/cos(helical2)^3)*(cos(pressure_angle)/cos(pa_normal_eff)-1)
4269  )
4270  mod*(teeth1/2/cos(helical1)+teeth2/2/cos(helical2)+dist_adj)
4271      // This expression is a guess based on finding the cross section where pressure angles match so that there is a single
4272      // pressure angle to reference the movement by. 
4273      + backlash * cos((helical1-helical2)/2) / tan(pressure_angle);
4274
4275
4276function _working_normal_pressure_angle_skew(teeth1,profile_shift1,helical1, teeth2, profile_shift2, helical2, pressure_angle) = 
4277  let(
4278      inv = function(a) tan(a) + a*PI/180, 
4279      rhs = 2*(profile_shift1+profile_shift2)/(teeth1/cos(helical1)^3+teeth2/cos(helical2)^3)*tan(pressure_angle) + _invol(pressure_angle),
4280      pa_eff_normal = root_find(function (x) _invol(x)-rhs, 5, 75)
4281  )
4282  pa_eff_normal;
4283
4284
4285// Function: gear_skew_angle()
4286// Usage:
4287//   ang = gear_skew_angle(teeth1, teeth2, helical1, helical2, [profile_shift1], [profile_shift2], [pressure_angle=]
4288// Synopsis: Returns corrected skew angle between two profile shifted helical gears.  
4289// Description:
4290//   Compute the correct skew angle between the axes of two profile shifted helical gears.  When profile shifting is zero, or when one of
4291//   the gears is a rack, this angle is simply the sum of the helical angles of the two gears.  But with profile shifted gears, a small
4292//   correction to the skew angle is needed for proper meshing.  
4293// Arguments:
4294//   teeth1 = Total number of teeth in the first gear.  If given 0, we assume this is a rack or worm.
4295//   teeth2 = Total number of teeth in the second gear.  If given 0, we assume this is a rack or worm.
4296//   helical1 = The helical angle (from vertical) of the teeth on the first gear. 
4297//   helical1 = The helical angle (from vertical) of the teeth on the second gear.
4298//   profile_shift1 = Profile shift factor x for the first gear.  Default: "auto"
4299//   profile_shift2 = Profile shift factor x for the second gear.  Default: "auto"
4300//   --
4301//   pressure_angle = The pressure angle of the gear.
4302// Example(3D,Med,NoAxes,VPT=[-2.62091,2.01048,-1.31405],VPR=[55,0,25],VPD=74.4017): These gears are auto profile shifted and as a result, do not mesh at the sum of their helical angles, but at 2.5 degrees more.  
4303//   circ_pitch=5; teeth1=12; teeth2=7; ha1=25; ha2=30; thick=10;
4304//   d = gear_dist_skew(circ_pitch=circ_pitch, teeth1, teeth2, ha1, ha2);
4305//   ang = gear_skew_angle(teeth1, teeth2, helical1=ha1, helical2=ha2);  // Returns 57.7
4306//   left(d/2)
4307//     spur_gear(circ_pitch, teeth1, helical=ha1, thickness=thick, gear_spin=-90);
4308//   right(d/2) color("lightblue")
4309//     xrot(ang) spur_gear(circ_pitch, teeth2, helical=ha2, thickness=thick, gear_spin=90-180/teeth2);
4310
4311function gear_skew_angle(teeth1,teeth2,helical1,helical2,profile_shift1,profile_shift2,pressure_angle=20) =
4312   assert(all_nonnegative([teeth1,teeth2]),"Must give nonnegative values for teeth")
4313   assert(teeth1>0 || teeth2>0, "One of the teeth counts must be nonzero")
4314   let(
4315       mod = 1,  // This is independent of module size
4316       profile_shift1 = auto_profile_shift(teeth1,pressure_angle,helical1,profile_shift=profile_shift1),
4317       profile_shift2 = auto_profile_shift(teeth2,pressure_angle,helical2,profile_shift=profile_shift2)
4318   )
4319   profile_shift1==0 && profile_shift2==0 ? helical1+helical2
4320 : teeth1==0 || teeth2==0 ? helical1+helical2
4321 : let(
4322        a = gear_dist_skew(mod=mod,teeth1,teeth2,helical1,helical2,profile_shift1,profile_shift2,pressure_angle=pressure_angle),
4323        b = gear_dist_skew(mod=mod,teeth1,teeth2,helical1,helical2,0,0,pressure_angle=pressure_angle),
4324        d1 = 2*pitch_radius(mod=mod,teeth=teeth1,helical=helical1),
4325        d2 = 2*pitch_radius(mod=mod,teeth=teeth2,helical=helical2),
4326        dw1 = 2*a*d1/(d1+d2),
4327        dw2 = 2*a*d2/(d1+d2),
4328        beta1 = atan(dw1/d1*tan(helical1)),
4329        beta2 = atan(dw2/d2*tan(helical2))
4330   )
4331   beta1+beta2;
4332
4333
4334// Function: get_profile_shift()
4335// Usage:
4336//   total_shift = get_profile_shift(mod=|diam_pitch=|circ_pitch=, desired, teeth1, teeth2, [helical], [pressure_angle=],
4337// Synopsis: Returns total profile shift needed to achieve a desired spacing between two gears
4338// Description:
4339//   Compute the total profile shift, split between two gears, needed to place those gears with a specified separation.
4340//   If the requested separation is too small, returns NaN.  Note that the profile shift returned may also be impractically
4341//   large or small and does not necessarily lead to a valid gear configuration.  You will need to split the profile shift
4342//   between the two gears.  Note that for helical gears, much more adjustment is available by modifying the helical angle.  
4343// Arguments:
4344//   desired = desired gear center separation
4345//   teeth1 = number of teeth on first gear
4346//   teeth2 = number of teeth on second gear
4347//   helical = The helical angle (from vertical) of the teeth on the gear.  Default: 0
4348//   ---
4349//   mod = The module of the gear (pitch diameter / teeth)
4350//   diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter.  Note that the diametral pitch is a completely different thing than the pitch diameter.
4351//   circ_pitch = The circular pitch, the distance between teeth centers around the pitch circle.
4352//   pressure_angle = normal pressure angle of gear teeth.  Default: 20
4353// Example(2D,Med,NoAxes,VPT=[37.0558,0.626722,9.78411],VPR=[0,0,0],VPD=496): For a pair of module 4 gears with 19, and 37 teeth, the separation without profile shifting is 112.  Suppose we want it instead to be 115.  A positive profile shift, split evenly between the gears, achieves the goal, as shown by the red rectangle, with width 115.  
4354//   teeth1=37;
4355//   teeth2=19;
4356//   mod=4;
4357//   desired=115;
4358//   pshift = get_profile_shift(desired,teeth1,teeth2,mod=mod);  // Returns 0.82
4359//   ps1 = pshift/2;
4360//   ps2 = pshift/2;
4361//   shorten=gear_shorten(teeth1,teeth2,0,ps1,ps2);       // Returns 0.07
4362//   d = gear_dist(mod=mod, teeth1,teeth2,0,ps1,ps2);
4363//   spur_gear2d(mod=mod,teeth=teeth1,profile_shift=ps1,shorten=shorten,gear_spin=-90,shaft_diam=5);
4364//   right(d)
4365//     spur_gear2d(mod=mod,teeth=teeth2,profile_shift=ps2,shorten=shorten,gear_spin=-90,shaft_diam=5);
4366//   stroke([rect([desired,40], anchor=LEFT)],color="red");
4367// Example(2D,Med,NoAxes,VPT=[37.0558,0.626722,9.78411],VPR=[0,0,0],VPD=496): For the same pair of module 4 gears with 19, and 37 teeth, suppose we want a closer spacing of 110 instead of 112.  A positive profile shift does the job, as shown by the red rectangle with width 110.  More of the negative shift is assigned to the large gear, to avoid undercutting the smaller gear.  
4368//   teeth1=37;
4369//   teeth2=19;
4370//   mod=4;
4371//   desired=110;
4372//   pshift = get_profile_shift(desired,teeth1,teeth2,mod=mod);  // Returns -0.46
4373//   ps1 = 0.8*pshift;
4374//   ps2 = 0.2*pshift;
4375//   shorten=gear_shorten(teeth1,teeth2,0,ps1,ps2);  // Returns 0.04
4376//   d = gear_dist(mod=mod, teeth1,teeth2,0,ps1,ps2);
4377//   spur_gear2d(mod=mod,teeth=teeth1,profile_shift=ps1,shorten=shorten,gear_spin=-90,shaft_diam=5);
4378//   right(d)
4379//     spur_gear2d(mod=mod,teeth=teeth2,profile_shift=ps2,shorten=shorten,gear_spin=-90,shaft_diam=5);
4380//   stroke([rect([desired,40], anchor=LEFT)],color="red");
4381function get_profile_shift(desired,teeth1,teeth2,helical=0,pressure_angle=20,mod,diam_pitch,circ_pitch) =
4382  let(
4383       mod = module_value(mod=mod, circ_pitch=circ_pitch, diam_pitch=diam_pitch),
4384       teethsum = teeth1+teeth2,
4385       pressure_angle_trans = atan(tan(pressure_angle)/cos(helical)),
4386       y = desired/mod - teethsum/2/cos(helical),
4387       thing=teethsum*cos(pressure_angle_trans) / (teethsum+2*y*cos(helical)),
4388       pa_eff = acos(teethsum*cos(pressure_angle_trans) / (teethsum+2*y*cos(helical)))
4389  )
4390  teethsum * (_invol(pa_eff)-_invol(pressure_angle_trans))/2/tan(pressure_angle);
4391
4392
4393// Function: auto_profile_shift()
4394// Synopsis: Returns the recommended profile shift for a gear.
4395// Topics: Gears, Parts
4396// See Also: worm(), worm_gear(), pitch_radius(), outer_radius()
4397// Usage:
4398//   x = auto_profile_shift(teeth, [pressure_angle], [helical], [profile_shift=]);
4399//   x = auto_profile_shift(teeth, [pressure_angle], [helical], get_min=);
4400//   x = auto_profile_shift(teeth, min_teeth=);
4401// Description:
4402//   Calculates the recommended profile shift to avoid gear tooth undercutting.  You can set `min_teeth` to a
4403//   value to allow small undercutting, and only activate the profile shift for more extreme cases.  Is is common
4404//   practice to make gears with 15-17 teeth with undercutting with the standard 20 deg pressure angle.
4405//   .
4406//   The `get_min` argument returns the minimum profile shift needed to avoid undercutting for the specified
4407//   number of teeth.  This will be a negative value for gears with a large number of teeth; such gears can
4408//   be given a negative profile shift without undercutting.  
4409// Arguments:
4410//   teeth = Total number of teeth in the gear.
4411//   pressure_angle = The pressure angle of the gear.
4412//   helical = helical angle
4413//   ---
4414//   min_teeth = If given, the minimum number of teeth on a gear that has acceptable undercut.
4415//   get_min = If true then return the minimum profile shift to avoid undercutting, which may be a negative value for large gears.  
4416//   profile_shift = If numerical then just return this value; if "auto" or not given then compute the automatic profile shift.
4417function auto_profile_shift(teeth, pressure_angle=20, helical=0, min_teeth, profile_shift, get_min=false) =
4418    assert(is_undef(profile_shift) || is_finite(profile_shift) || profile_shift=="auto", "Profile shift must be \"auto\" or a number")
4419    is_num(profile_shift) ? profile_shift
4420  : teeth==0 ? 0
4421  : let(
4422        pressure_angle=atan(tan(pressure_angle)/cos(helical)),
4423        min_teeth = default(min_teeth, 2 / sin(pressure_angle)^2)
4424    )
4425    !get_min && teeth > floor(min_teeth)? 0
4426  : (1 - (teeth / min_teeth))/cos(helical);
4427
4428
4429// Function: gear_shorten()
4430// Usage:
4431//    shorten = gear_shorten(teeth1, teeth2, [helical], [profile_shift1], [profile_shift2], [pressure_angle=]);
4432// Synopsis: Returns the tip shortening parameter for profile shifted parallel axis gears.
4433// Description:
4434//    Compute the gear tip shortening factor for gears that have profile shifts.  This factor depends on both
4435//    gears in a pair and when applied, will results in teeth that meet the specified clearance distance.
4436//    Generally if you don't apply it the teeth clearance will be decreased due to the profile shifting.
4437//    Because it operates pairwise, if a gear mates with more than one other gear, you may have to decide
4438//    which shortening factor to use.  The shortening factor is independent of the size of the teeth.
4439// Arguments:
4440//   teeth1 = number of teeth on first gear
4441//   teeth2 = number of teeth on second gear
4442//   helical = The helical angle (from vertical) of the teeth on the gear.  Default: 0
4443//   profile_shift1 = Profile shift factor x for the first gear.  Default: "auto"
4444//   profile_shift2 = Profile shift factor x for the second gear.  Default: "auto"
4445//   ---
4446//   pressure_angle = normal pressure angle of gear teeth.  Default: 20
4447// Example(2D,Med,VPT=[53.9088,1.83058,26.0319],VPR=[0,0,0],VPD=140): Big profile shift eliminates the clearance between the teeth
4448//   teeth1=25;
4449//   teeth2=19;
4450//   mod=4;
4451//   ps1 = 0.75;
4452//   ps2 = 0.75;
4453//   d = gear_dist(mod=mod, teeth1,teeth2,0,ps1,ps2);
4454//   color("lightblue")
4455//     spur_gear2d(mod=mod,teeth=teeth1,profile_shift=ps1,gear_spin=-90);
4456//   right(d)
4457//     spur_gear2d(mod=mod,teeth=teeth2,profile_shift=ps2,gear_spin=-90);
4458// Example(2D,Med,VPT=[53.9088,1.83058,26.0319],VPR=[0,0,0],VPD=140,NoAxes): Applying the correct shortening factor restores the clearance to its normal value.  
4459//   teeth1=25;
4460//   teeth2=19;
4461//   mod=4;
4462//   ps1 = 0.75;
4463//   ps2 = 0.75;
4464//   d = gear_dist(mod=mod, teeth1,teeth2,0,ps1,ps2);
4465//   shorten=gear_shorten(teeth1,teeth2,0,ps1,ps2);
4466//   color("lightblue")
4467//     spur_gear2d(mod=mod,teeth=teeth1,profile_shift=ps1,shorten=shorten,gear_spin=-90);
4468//   right(d)
4469//     spur_gear2d(mod=mod,teeth=teeth2,profile_shift=ps2,shorten=shorten,gear_spin=-90);
4470function gear_shorten(teeth1,teeth2,helical=0,profile_shift1="auto",profile_shift2="auto",pressure_angle=20) =
4471    teeth1==0 || teeth2==0 ? 0
4472  : let(
4473         profile_shift1 = auto_profile_shift(teeth1,pressure_angle,helical,profile_shift=profile_shift1),
4474         profile_shift2 = auto_profile_shift(teeth2,pressure_angle,helical,profile_shift=profile_shift2),
4475         ax = gear_dist(mod=1,teeth1,teeth2,helical,profile_shift1,profile_shift2,pressure_angle=pressure_angle),
4476         y = ax - (teeth1+teeth2)/2/cos(helical)
4477    )
4478    profile_shift1+profile_shift2-y;
4479
4480
4481// Function: gear_shorten_skew()
4482// Usage:
4483//    shorten = gear_shorten_skew(teeth1, teeth2, helical1, helical2, [profile_shift1], [profile_shift2], [pressure_angle=]);
4484// Synopsis: Returns the tip shortening parameter for profile shifted skew axis helical gears.
4485// Description:
4486//    Compute the gear tip shortening factor for skew axis helical gears that have profile shifts.  This factor depends on both
4487//    gears in a pair and when applied, will results in teeth that meet the specified clearance distance.
4488//    Generally if you don't apply it the teeth clearance will be decreased due to the profile shifting.
4489//    Because it operates pairwise, if a gear mates with more than one other gear, you may have to decide
4490//    which shortening factor to use.  The shortening factor is independent of the size of the teeth.
4491// Arguments:
4492//   teeth1 = Total number of teeth in the first gear.  If given 0, we assume this is a rack or worm.
4493//   teeth2 = Total number of teeth in the second gear.  If given 0, we assume this is a rack or worm.
4494//   helical1 = The helical angle (from vertical) of the teeth on the first gear. 
4495//   helical1 = The helical angle (from vertical) of the teeth on the second gear.
4496//   profile_shift1 = Profile shift factor x for the first gear.  Default: "auto"
4497//   profile_shift2 = Profile shift factor x for the second gear.  Default: "auto"
4498//   ---
4499//   pressure_angle = The pressure angle of the gear.
4500function gear_shorten_skew(teeth1,teeth2,helical1,helical2,profile_shift1="auto",profile_shift2="auto",pressure_angle=20) =
4501    let(
4502         profile_shift1 = auto_profile_shift(teeth1,pressure_angle,helical1,profile_shift=profile_shift1),
4503         profile_shift2 = auto_profile_shift(teeth2,pressure_angle,helical2,profile_shift=profile_shift2),
4504         ax = gear_dist(mod=1,teeth1,teeth2,helical,profile_shift1,profile_shift2,pressure_angle=pressure_angle),
4505         y = ax - (teeth1+teeth2)/2/cos(helical)
4506    )
4507    profile_shift1+profile_shift2-y;
4508
4509
4510module _show_gear_tooth_profile(
4511    circ_pitch,
4512    teeth,
4513    pressure_angle=20,
4514    profile_shift,
4515    helical=0,
4516    internal=false,
4517    clearance,
4518    backlash=0,
4519    show_verts=false,
4520    diam_pitch,
4521    mod
4522) {
4523    mod = module_value(circ_pitch=circ_pitch, diam_pitch=diam_pitch, mod=mod);
4524    profile_shift = default(profile_shift, auto_profile_shift(teeth, pressure_angle, helical));
4525    or = outer_radius(mod=mod, teeth=teeth, clearance=clearance, helical=helical, profile_shift=profile_shift, internal=internal);
4526    pr = pitch_radius(mod=mod, teeth=teeth, helical=helical);
4527    rr = _root_radius(mod=mod, teeth=teeth, helical=helical, profile_shift=profile_shift, clearance=clearance, internal=internal);
4528    br = _base_radius(mod=mod, teeth=teeth, helical=helical, pressure_angle=pressure_angle);
4529    tang = 360/teeth;
4530    rang = tang * 1.075;
4531    tsize = (or-rr) / 20;
4532    clear = (1-profile_shift)*mod;
4533    tooth = _gear_tooth_profile(
4534        mod=mod, teeth=teeth,
4535        pressure_angle=pressure_angle,
4536        clearance=clearance,
4537        backlash=backlash,
4538        helical=helical,
4539        internal=internal,
4540        profile_shift=profile_shift
4541    );
4542    $fn=360;
4543    union() {
4544        color("cyan") { // Pitch circle
4545            stroke(arc(r=pr,start=90-rang/2,angle=rang), width=0.05);
4546            zrot(-tang/2*1.10) back(pr) text("pitch", size=tsize, halign="left", valign="center");
4547        }
4548        color("lightgreen") { // Outer and Root circles
4549            stroke(arc(r=or,start=90-rang/2,angle=rang), width=0.05);
4550            stroke(arc(r=rr,start=90-rang/2,angle=rang), width=0.05);
4551            zrot(-tang/2*1.10) back(or) text("tip", size=tsize, halign="left", valign="center");
4552            zrot(-tang/2*1.10) back(rr) text("root", size=tsize, halign="left", valign="center");
4553        }
4554        color("#fcf") { // Base circle
4555            stroke(arc(r=br,start=90-rang/2,angle=rang), width=0.05);
4556            zrot(tang/2*1.10) back(br) text("base", size=tsize, halign="right", valign="center");
4557        }
4558        color("#ddd") { // Clearance area
4559            if (internal) {
4560                dashed_stroke(arc(r=pr+clear, start=90-rang/2, angle=rang), width=0.05);
4561                back((pr+clear+or)/2) text("clearance", size=tsize, halign="center", valign="center");
4562            } else {
4563                dashed_stroke(arc(r=pr-clear, start=90-rang/2, angle=rang), width=0.05);
4564                back((pr-clear+rr)/2) text("clearance", size=tsize, halign="center", valign="center");
4565            }
4566        }
4567        color("#ddd") { // Tooth width markers
4568            stroke([polar_to_xy(min(rr,br)-mod/10,90-180/teeth),polar_to_xy(or+mod/10,90-180/teeth)], width=0.05, closed=true);
4569            stroke([polar_to_xy(min(rr,br)-mod/10,90+180/teeth),polar_to_xy(or+mod/10,90+180/teeth)], width=0.05, closed=true);
4570        }
4571        zrot_copies([0]) { // Tooth profile overlay
4572            stroke(tooth, width=0.1, dots=(show_verts?"dot":false), endcap_color1="green", endcap_color2="red");
4573        }
4574    }
4575}
4576
4577
4578
4579// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap