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