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