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// Section: Terminology
  14//   The outline of a gear is a smooth circle (the "pitch circle") which has
  15//   mountains and valleys added so it is toothed.  There is an inner
  16//   circle (the "root circle") that touches the base of all the teeth, an
  17//   outer circle that touches the tips of all the teeth, and the invisible
  18//   pitch circle in between them.  There is also a "base circle", which can
  19//   be smaller than all three of the others, which controls the shape of
  20//   the teeth.  The side of each tooth lies on the path that the end of a
  21//   string would follow if it were wrapped tightly around the base circle,
  22//   then slowly unwound.  That shape is an "involute", which gives this
  23//   type of gear its name.
  24
  25
  26// Section: Gears
  27
  28// Function&Module: spur_gear()
  29// Usage: As a Module
  30//   spur_gear(pitch, teeth, thickness, [shaft_diam], [hide=], [pressure_angle=], [clearance=], [backlash=], [helical=], [slices=], [interior=]) [ATTACHMENTS];
  31//   spur_gear(mod=, teeth=, thickness=, [shaft_diam=], ...) [ATTACHMENTS];
  32// Usage: As a Function
  33//   vnf = spur_gear(pitch, teeth, thickness, [shaft_diam], ...);
  34//   vnf = spur_gear(mod=, teeth=, thickness=, [shaft_diam], ...);
  35// Topics: Gears
  36// See Also: rack()
  37// Description:
  38//   Creates a (potentially helical) involute spur gear.  The module `spur_gear()` gives an involute
  39//   spur gear, with reasonable defaults for all the parameters.  Normally, you should just choose the
  40//   first 4 parameters, and let the rest be default values.  The module `spur_gear()` gives a gear in
  41//   the XY plane, centered on the origin, with one tooth centered on the positive Y axis.  The most
  42//   important is `pitch_radius()`, which tells how far apart to space gears that are meshing, and
  43//   `outer_radius()`, which gives the size of the region filled by the gear.  A gear has a "pitch
  44//   circle", which is an invisible circle that cuts through the middle of each tooth (though not the
  45//   exact center). In order for two gears to mesh, their pitch circles should just touch.  So the
  46//   distance between their centers should be `pitch_radius()` for one, plus `pitch_radius()` for the
  47//   other, which gives the radii of their pitch circles.  In order for two gears to mesh, they must
  48//   have the same `pitch` and `pressure_angle` parameters.  `pitch` gives the number of millimeters
  49//   of arc around the pitch circle covered by one tooth and one space between teeth.  The
  50//   `pressure_angle` controls how flat or bulged the sides of the teeth are.  Common values include
  51//   14.5 degrees and 20 degrees, and occasionally 25.  Though I've seen 28 recommended for plastic
  52//   gears. Larger numbers bulge out more, giving stronger teeth, so 28 degrees is the default here.
  53//   The ratio of `teeth` for two meshing gears gives how many times one will make a full revolution
  54//   when the the other makes one full revolution.  If the two numbers are coprime (i.e.  are not both
  55//   divisible by the same number greater than 1), then every tooth on one gear will meet every tooth
  56//   on the other, for more even wear.  So coprime numbers of teeth are good.
  57// Arguments:
  58//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
  59//   teeth = Total number of teeth around the entire perimeter
  60//   thickness = Thickness of gear in mm
  61//   shaft_diam = Diameter of the hole in the center, in mm.  Default: 0 (no shaft hole)
  62//   ---
  63//   hide = Number of teeth to delete to make this only a fraction of a circle
  64//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees.
  65//   clearance = Clearance gap at the bottom of the inter-tooth valleys.
  66//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
  67//   helical = Teeth are slanted around the spur gear at this angle away from the gear axis of rotation.
  68//   slices = Number of vertical layers to divide gear into.  Useful for refining gears with `helical`.
  69//   scale = Scale of top of gear compared to bottom.  Useful for making crown gears.
  70//   interior = If true, create a mask for difference()ing from something else.
  71//   mod = The metric module/modulus of the gear.
  72//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
  73//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
  74//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
  75// Example: Spur Gear
  76//   spur_gear(pitch=5, teeth=20, thickness=8, shaft_diam=5);
  77// Example: Metric Gear
  78//   spur_gear(mod=2, teeth=20, thickness=8, shaft_diam=5);
  79// Example: Helical Gear
  80//   spur_gear(
  81//       pitch=5, teeth=20, thickness=10,
  82//       shaft_diam=5, helical=-30, slices=12,
  83//       $fa=1, $fs=1
  84//   );
  85// Example(Anim,Frames=8,VPT=[0,30,0],VPR=[0,0,0],VPD=300): Assembly of Gears
  86//   n1 = 11; //red gear number of teeth
  87//   n2 = 20; //green gear
  88//   n3 = 5;  //blue gear
  89//   n4 = 16; //orange gear
  90//   n5 = 9;  //gray rack
  91//   pitch = 9; //all meshing gears need the same `pitch` (and the same `pressure_angle`)
  92//   thickness    = 6;
  93//   hole         = 3;
  94//   rack_base    = 12;
  95//   r1 = pitch_radius(pitch,n1);
  96//   r2 = pitch_radius(pitch,n2);
  97//   r3 = pitch_radius(pitch,n3);
  98//   r4 = pitch_radius(pitch,n4);
  99//   r5 = pitch_radius(pitch,n5);
 100//   a1 =  $t * 360 / n1;
 101//   a2 = -$t * 360 / n2 + 180/n2;
 102//   a3 = -$t * 360 / n3;
 103//   a4 = -$t * 360 / n4 - 7.5*180/n4;
 104//   color("#f77")              zrot(a1) spur_gear(pitch,n1,thickness,hole);
 105//   color("#7f7") back(r1+r2)  zrot(a2) spur_gear(pitch,n2,thickness,hole);
 106//   color("#77f") right(r1+r3) zrot(a3) spur_gear(pitch,n3,thickness,hole);
 107//   color("#fc7") left(r1+r4)  zrot(a4) spur_gear(pitch,n4,thickness,hole,hide=n4-3);
 108//   color("#ccc") fwd(r1) right(pitch*$t)
 109//       rack(pitch=pitch,teeth=n5,thickness=thickness,height=rack_base,anchor=CENTER,orient=BACK);
 110function spur_gear(
 111    pitch = 3,
 112    teeth = 11,
 113    thickness = 6,
 114    shaft_diam = 0,
 115    hide = 0,
 116    pressure_angle = 28,
 117    clearance = undef,
 118    backlash = 0.0,
 119    helical = 0,
 120    slices = 2,
 121    interior = false,
 122    mod,
 123    anchor = CENTER,
 124    spin = 0,
 125    orient = UP
 126) =
 127    let(
 128        pitch = is_undef(mod) ? pitch : pitch_value(mod),
 129        p = pitch_radius(pitch, teeth),
 130        c = outer_radius(pitch, teeth, clearance, interior),
 131        r = _root_radius(pitch, teeth, clearance, interior),
 132        twist = atan2(thickness*tan(helical),p),
 133        rgn = [
 134            spur_gear2d(
 135                pitch = pitch,
 136                teeth = teeth,
 137                pressure_angle = pressure_angle,
 138                hide = hide,
 139                clearance = clearance,
 140                backlash = backlash,
 141                interior = interior
 142            ),
 143            if (shaft_diam > 0) circle(d=shaft_diam, $fn=max(12,segs(shaft_diam/2)))
 144        ],
 145        vnf = linear_sweep(rgn, height=thickness, center=true)
 146    ) reorient(anchor,spin,orient, h=thickness, r=p, p=vnf);
 147
 148
 149module spur_gear(
 150    pitch = 3,
 151    teeth = 11,
 152    thickness = 6,
 153    shaft_diam = 0,
 154    hide = 0,
 155    pressure_angle = 28,
 156    clearance = undef,
 157    backlash = 0.0,
 158    helical = 0,
 159    slices = 2,
 160    interior = false,
 161    mod,
 162    anchor = CENTER,
 163    spin = 0,
 164    orient = UP
 165) {
 166    pitch = is_undef(mod) ? pitch : pitch_value(mod);
 167    p = pitch_radius(pitch, teeth);
 168    c = outer_radius(pitch, teeth, clearance, interior);
 169    r = _root_radius(pitch, teeth, clearance, interior);
 170    twist = atan2(thickness*tan(helical),p);
 171    attachable(anchor,spin,orient, r=p, l=thickness) {
 172        difference() {
 173            linear_extrude(height=thickness, center=true, convexity=teeth/2, twist=twist) {
 174                spur_gear2d(
 175                    pitch = pitch,
 176                    teeth = teeth,
 177                    pressure_angle = pressure_angle,
 178                    hide = hide,
 179                    clearance = clearance,
 180                    backlash = backlash,
 181                    interior = interior
 182                );
 183            }
 184            if (shaft_diam > 0) {
 185                cylinder(h=2*thickness+1, r=shaft_diam/2, center=true, $fn=max(12,segs(shaft_diam/2)));
 186            }
 187        }
 188        children();
 189    }
 190}
 191
 192
 193// Function&Module: spur_gear2d()
 194// Usage: As Module
 195//   spur_gear2d(pitch, teeth, [hide=], [pressure_angle=], [clearance=], [backlash=], [interior=]) [ATTACHMENTS];
 196//   spur_gear2d(mod=, teeth=, [hide=], [pressure_angle=], [clearance=], [backlash=], [interior=]) [ATTACHMENTS];
 197// Usage: As Function
 198//   poly = spur_gear2d(pitch, teeth, [hide=], [pressure_angle=], [clearance=], [backlash=], [interior=]);
 199//   poly = spur_gear2d(mod=, teeth=, [hide=], [pressure_angle=], [clearance=], [backlash=], [interior=]);
 200// Topics: Gears
 201// See Also: spur_gear()
 202// Description:
 203//   When called as a module, creates a 2D involute spur gear.  When called as a function, returns a
 204//   2D path for the perimeter of a 2D involute spur gear.  Normally, you should just specify the
 205//   first 2 parameters `pitch` and `teeth`, and let the rest be default values.
 206//   Meshing gears must match in `pitch`, `pressure_angle`, and `helical`, and be separated by
 207//   the sum of their pitch radii, which can be found with `pitch_radius()`.
 208// Arguments:
 209//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
 210//   teeth = Total number of teeth around the spur gear.
 211//   hide = Number of teeth to delete to make this only a fraction of a circle
 212//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees.
 213//   clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters)
 214//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
 215//   interior = If true, create a mask for difference()ing from something else.
 216//   mod = The metric module/modulus of the gear.
 217//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 218//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 219// Example(2D): Typical Gear Shape
 220//   spur_gear2d(pitch=5, teeth=20);
 221// Example(2D): Metric Gear
 222//   spur_gear2d(mod=2, teeth=20);
 223// Example(2D): Lower Pressure Angle
 224//   spur_gear2d(pitch=5, teeth=20, pressure_angle=20);
 225// Example(2D): Partial Gear
 226//   spur_gear2d(pitch=5, teeth=20, hide=15, pressure_angle=20);
 227// Example(2D): Called as a Function
 228//   path = spur_gear2d(pitch=8, teeth=16);
 229//   polygon(path);
 230function spur_gear2d(
 231    pitch = 3,
 232    teeth = 11,
 233    hide = 0,
 234    pressure_angle = 28,
 235    clearance = undef,
 236    backlash = 0.0,
 237    interior = false,
 238    mod,
 239    anchor = CENTER,
 240    spin = 0
 241) = let(
 242    pitch = is_undef(mod) ? pitch : pitch_value(mod),
 243    pr = pitch_radius(pitch=pitch, teeth=teeth),
 244    tooth_profile = _gear_tooth_profile(
 245        pitch = pitch,
 246        teeth = teeth,
 247        pressure_angle = pressure_angle,
 248        clearance = clearance,
 249        backlash = backlash,
 250        interior = interior,
 251        valleys = false
 252    ),
 253    pts = concat(
 254        [for (tooth = [0:1:teeth-hide-1])
 255            each rot(tooth*360/teeth, p=tooth_profile)
 256        ],
 257        hide>0? [[0,0]] : []
 258    )
 259) reorient(anchor,spin, two_d=true, r=pr, p=pts);
 260
 261
 262module spur_gear2d(
 263    pitch = 3,
 264    teeth = 11,
 265    hide = 0,
 266    pressure_angle = 28,
 267    clearance = undef,
 268    backlash = 0.0,
 269    interior = false,
 270    mod,
 271    anchor = CENTER,
 272    spin = 0
 273) {
 274    pitch = is_undef(mod) ? pitch : pitch_value(mod);
 275    path = spur_gear2d(
 276        pitch = pitch,
 277        teeth = teeth,
 278        hide = hide,
 279        pressure_angle = pressure_angle,
 280        clearance = clearance,
 281        backlash = backlash,
 282        interior = interior
 283    );
 284    pr = pitch_radius(pitch=pitch, teeth=teeth);
 285    attachable(anchor,spin, two_d=true, r=pr) {
 286        polygon(path);
 287        children();
 288    }
 289}
 290
 291
 292
 293// Function&Module: rack()
 294// Usage: As a Module
 295//   rack(pitch, teeth, thickness, height, [pressure_angle=], [backlash=]) [ATTACHMENTS];
 296//   rack(mod=, teeth=, thickness=, height=, [pressure_angle=], [backlash]=) [ATTACHMENTS];
 297// Usage: As a Function
 298//   vnf = rack(pitch, teeth, thickness, height, [pressure_angle=], [backlash=]);
 299//   vnf = rack(mod=, teeth=, thickness=, height=, [pressure_angle=], [backlash=]);
 300// Topics: Gears
 301// See Also: spur_gear()
 302// Description:
 303//   This is used to create a 3D rack, which is a linear bar with teeth that a gear can roll along.
 304//   A rack can mesh with any gear that has the same `pitch` and `pressure_angle`.
 305//   When called as a function, returns a 3D [VNF](vnf.scad) for the rack.
 306//   When called as a module, creates a 3D rack shape.
 307// Arguments:
 308//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. Default: 5
 309//   teeth = Total number of teeth along the rack.  Default: 20
 310//   thickness = Thickness of rack in mm (affects each tooth).  Default: 5
 311//   height = Height of rack in mm, from tooth top to back of rack.  Default: 10
 312//   ---
 313//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees.  Default: 28
 314//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
 315//   clearance = Clearance gap at the bottom of the inter-tooth valleys.
 316//   helical = The angle of the rack teeth away from perpendicular to the rack length.  Used to match helical spur gear pinions.  Default: 0
 317//   mod = The metric module/modulus of the gear.
 318//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 319//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 320//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 321// Extra Anchors:
 322//   "adendum" = At the tips of the teeth, at the center of rack.
 323//   "adendum-left" = At the tips of the teeth, at the left end of the rack.
 324//   "adendum-right" = At the tips of the teeth, at the right end of the rack.
 325//   "adendum-back" = At the tips of the teeth, at the back of the rack.
 326//   "adendum-front" = At the tips of the teeth, at the front of the rack.
 327//   "dedendum" = At the base of the teeth, at the center of rack.
 328//   "dedendum-left" = At the base of the teeth, at the left end of the rack.
 329//   "dedendum-right" = At the base of the teeth, at the right end of the rack.
 330//   "dedendum-back" = At the base of the teeth, at the back of the rack.
 331//   "dedendum-front" = At the base of the teeth, at the front of the rack.
 332// Example(VPR=[60,0,325],VPD=130):
 333//   rack(pitch=5, teeth=10, thickness=5, height=5, pressure_angle=20);
 334// Example: Rack for Helical Gear
 335//   rack(pitch=5, teeth=10, thickness=5, height=5, pressure_angle=20, helical=30);
 336// Example: Alternate Helical Gear
 337//   rack(pitch=5, teeth=10, thickness=5, height=5, pressure_angle=20, helical=-30);
 338// Example: Metric Rack
 339//   rack(mod=2, teeth=10, thickness=5, height=5, pressure_angle=20);
 340// Example(Anim,VPT=[0,0,12],VPD=100,Frames=6): Rack and Pinion
 341//   teeth1 = 16; teeth2 = 16;
 342//   pitch = 5; thick = 5; helical = 30;
 343//   pr = pitch_radius(pitch=pitch, teeth=teeth2);
 344//   right(pr*2*PI/teeth2*$t) rack(pitch=pitch, teeth=teeth1, thickness=thick, height=5, helical=helical);
 345//   up(pr) yrot(186.5-$t*360/teeth2)
 346//       spur_gear(pitch=pitch, teeth=teeth2, thickness=thick, helical=helical, shaft_diam=5, orient=BACK);
 347module rack(
 348    pitch = 5,
 349    teeth = 20,
 350    thickness = 5,
 351    height = 10,
 352    pressure_angle = 28,
 353    backlash = 0.0,
 354    clearance,
 355    helical=0,
 356    mod,
 357    anchor = CENTER,
 358    spin = 0,
 359    orient = UP
 360) {
 361    pitch = is_undef(mod) ? pitch : pitch_value(mod);
 362    a = _adendum(pitch);
 363    d = _dedendum(pitch, clearance);
 364    l = teeth * pitch;
 365    anchors = [
 366        named_anchor("adendum",         [0,0,a],             BACK),
 367        named_anchor("adendum-left",    [-l/2,0,a],          LEFT),
 368        named_anchor("adendum-right",   [ l/2,0,a],          RIGHT),
 369        named_anchor("adendum-front",   [0,-thickness/2,a],  DOWN),
 370        named_anchor("adendum-back",    [0, thickness/2,a],  UP),
 371        named_anchor("dedendum",        [0,0,-d],            BACK),
 372        named_anchor("dedendum-left",   [-l/2,0,-d],         LEFT),
 373        named_anchor("dedendum-right",  [ l/2,0,-d],         RIGHT),
 374        named_anchor("dedendum-front",  [0,-thickness/2,-d], DOWN),
 375        named_anchor("dedendum-back",   [0, thickness/2,-d], UP),
 376    ];
 377    attachable(anchor,spin,orient, size=[l, thickness, 2*abs(a-height)], anchors=anchors) {
 378        skew(sxy=tan(helical)) xrot(90) {
 379            linear_extrude(height=thickness, center=true, convexity=teeth*2) {
 380                rack2d(
 381                    pitch = pitch,
 382                    teeth = teeth,
 383                    height = height,
 384                    pressure_angle = pressure_angle,
 385                    backlash = backlash,
 386                    clearance = clearance
 387                );
 388            }
 389        }
 390        children();
 391    }
 392}
 393
 394
 395function rack(
 396    pitch = 5,
 397    teeth = 20,
 398    thickness = 5,
 399    height = 10,
 400    pressure_angle = 28,
 401    backlash = 0.0,
 402    clearance,
 403    helical=0,
 404    mod,
 405    anchor = CENTER,
 406    spin = 0,
 407    orient = UP
 408) =
 409    let(
 410        pitch = is_undef(mod) ? pitch : pitch_value(mod),
 411        a = _adendum(pitch),
 412        d = _dedendum(pitch, clearance),
 413        l = teeth * pitch,
 414        anchors = [
 415            named_anchor("adendum",         [0,0,a],             BACK),
 416            named_anchor("adendum-left",    [-l/2,0,a],          LEFT),
 417            named_anchor("adendum-right",   [ l/2,0,a],          RIGHT),
 418            named_anchor("adendum-front",   [0,-thickness/2,a],  DOWN),
 419            named_anchor("adendum-back",    [0, thickness/2,a],  UP),
 420            named_anchor("dedendum",        [0,0,-d],            BACK),
 421            named_anchor("dedendum-left",   [-l/2,0,-d],         LEFT),
 422            named_anchor("dedendum-right",  [ l/2,0,-d],         RIGHT),
 423            named_anchor("dedendum-front",  [0,-thickness/2,-d], DOWN),
 424            named_anchor("dedendum-back",   [0, thickness/2,-d], UP),
 425        ],
 426        path = rack2d(
 427            pitch = pitch,
 428            teeth = teeth,
 429            height = height,
 430            pressure_angle = pressure_angle,
 431            backlash = backlash,
 432            clearance = clearance
 433        ),
 434        vnf = linear_sweep(path, height=thickness, anchor="origin", orient=FWD),
 435        out = helical==0? vnf : skew(sxy=tan(helical), p=vnf)
 436    ) reorient(anchor,spin,orient, size=[l, thickness, 2*abs(a-height)], anchors=anchors, p=out);
 437
 438
 439
 440
 441// Function&Module: rack2d()
 442// Usage: As a Module
 443//   path = rack2d(pitch, teeth, height, [pressure_angle=], [backlash=]) [ATTACHMENTS];
 444//   path = rack2d(mod=, teeth=, height=, [pressure_angle=], [backlash=]) [ATTACHMENTS];
 445// Usage: As a Function
 446//   path = rack2d(pitch, teeth, height, [pressure_angle=], [backlash=]);
 447//   path = rack2d(mod=, teeth=, height=, [pressure_angle=], [backlash=]);
 448// Topics: Gears
 449// See Also: spur_gear2d()
 450// Description:
 451//   This is used to create a 2D rack, which is a linear bar with teeth that a gear can roll along.
 452//   A rack can mesh with any gear that has the same `pitch` and `pressure_angle`.
 453//   When called as a function, returns a 2D path for the outline of the rack.
 454//   When called as a module, creates a 2D rack shape.
 455// Arguments:
 456//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
 457//   teeth = Total number of teeth along the rack
 458//   height = Height of rack in mm, from tooth top to back of rack.
 459//   ---
 460//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees.
 461//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
 462//   mod = The metric module/modulus of the gear.
 463//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 464//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 465// Extra Anchors:
 466//   "adendum" = At the tips of the teeth, at the center of rack.
 467//   "adendum-left" = At the tips of the teeth, at the left end of the rack.
 468//   "adendum-right" = At the tips of the teeth, at the right end of the rack.
 469//   "dedendum" = At the height of the teeth, at the center of rack.
 470//   "dedendum-left" = At the height of the teeth, at the left end of the rack.
 471//   "dedendum-right" = At the height of the teeth, at the right end of the rack.
 472// Example(2D):
 473//   rack2d(pitch=5, teeth=10, height=10, pressure_angle=20);
 474// Example(2D): Called as a Function
 475//   path = rack2d(pitch=8, teeth=8, height=10, pressure_angle=28);
 476//   polygon(path);
 477function rack2d(
 478    pitch = 5,
 479    teeth = 20,
 480    height = 10,
 481    pressure_angle = 28,
 482    backlash = 0.0,
 483    clearance = undef,
 484    mod,
 485    anchor = CENTER,
 486    spin = 0
 487) =
 488    let(
 489        pitch = is_undef(mod) ? pitch : pitch_value(mod),
 490        a = _adendum(pitch),
 491        d = _dedendum(pitch, clearance)
 492    )
 493    assert(a+d < height)
 494    let(
 495        xa = a * sin(pressure_angle),
 496        xd = d * sin(pressure_angle),
 497        l = teeth * pitch,
 498        anchors = [
 499            named_anchor("adendum",         [   0, a,0],  BACK),
 500            named_anchor("adendum-left",    [-l/2, a,0],  LEFT),
 501            named_anchor("adendum-right",   [ l/2, a,0],  RIGHT),
 502            named_anchor("dedendum",        [   0,-d,0],  BACK),
 503            named_anchor("dedendum-left",   [-l/2,-d,0],  LEFT),
 504            named_anchor("dedendum-right",  [ l/2,-d,0],  RIGHT),
 505        ],
 506        path = [
 507            [-(teeth-1)/2 * pitch + -1/2 * pitch,  a-height],
 508            [-(teeth-1)/2 * pitch + -1/2 * pitch,  -d],
 509            for (i = [0:1:teeth-1]) let(
 510                off = (i-(teeth-1)/2) * pitch
 511            ) each [
 512                [off + -1/4 * pitch + backlash - xd, -d],
 513                [off + -1/4 * pitch + backlash + xa,  a],
 514                [off +  1/4 * pitch - backlash - xa,  a],
 515                [off +  1/4 * pitch - backlash + xd, -d],
 516            ],
 517            [ (teeth-1)/2 * pitch +  1/2 * pitch,  -d],
 518            [ (teeth-1)/2 * pitch +  1/2 * pitch,  a-height],
 519        ]
 520    ) reorient(anchor,spin, two_d=true, size=[l,2*abs(a-height)], anchors=anchors, p=path);
 521
 522
 523module rack2d(
 524    pitch = 5,
 525    teeth = 20,
 526    height = 10,
 527    pressure_angle = 28,
 528    backlash = 0.0,
 529    clearance = undef,
 530    mod,
 531    anchor = CENTER,
 532    spin = 0
 533) {
 534    pitch = is_undef(mod) ? pitch : pitch_value(mod);
 535    a = _adendum(pitch);
 536    d = _dedendum(pitch, clearance);
 537    l = teeth * pitch;
 538    anchors = [
 539        named_anchor("adendum",         [   0, a,0],  BACK),
 540        named_anchor("adendum-left",    [-l/2, a,0],  LEFT),
 541        named_anchor("adendum-right",   [ l/2, a,0],  RIGHT),
 542        named_anchor("dedendum",        [   0,-d,0],  BACK),
 543        named_anchor("dedendum-left",   [-l/2,-d,0],  LEFT),
 544        named_anchor("dedendum-right",  [ l/2,-d,0],  RIGHT),
 545    ];
 546    path = rack2d(
 547        pitch = pitch,
 548        teeth = teeth,
 549        height = height,
 550        pressure_angle = pressure_angle,
 551        backlash  = backlash,
 552        clearance = clearance
 553    );
 554    attachable(anchor,spin, two_d=true, size=[l, 2*abs(a-height)], anchors=anchors) {
 555        polygon(path);
 556        children();
 557    }
 558}
 559
 560
 561
 562
 563// Function&Module: bevel_gear()
 564// Usage: As a Module
 565//   bevel_gear(pitch|mod, teeth, face_width, pitch_angle, [shaft_diam], [hide], [pressure_angle], [clearance], [backlash], [cutter_radius], [spiral_angle], [slices], [interior]);
 566// Usage: As a Function
 567//   vnf = bevel_gear(pitch|mod, teeth, face_width, pitch_angle, [hide], [pressure_angle], [clearance], [backlash], [cutter_radius], [spiral_angle], [slices], [interior]);
 568// Topics: Gears
 569// See Also: bevel_pitch_angle()
 570// Description:
 571//   Creates a (potentially spiral) bevel gear.  The module `bevel_gear()` gives a bevel gear, with
 572//   reasonable defaults for all the parameters.  Normally, you should just choose the first 4
 573//   parameters, and let the rest be default values.  The module `bevel_gear()` gives a gear in the XY
 574//   plane, centered on the origin, with one tooth centered on the positive Y axis.  The various
 575//   functions below it take the same parameters, and return various measurements for the gear.  The
 576//   most important is `pitch_radius()`, which tells how far apart to space gears that are meshing,
 577//   and `outer_radius()`, which gives the size of the region filled by the gear.  A gear has a "pitch
 578//   circle", which is an invisible circle that cuts through the middle of each tooth (though not the
 579//   exact center). In order for two gears to mesh, their pitch circles should just touch.  So the
 580//   distance between their centers should be `pitch_radius()` for one, plus `pitch_radius()` for the
 581//   other, which gives the radii of their pitch circles.  In order for two gears to mesh, they must
 582//   have the same `pitch` and `pressure_angle` parameters.  `pitch` gives the number of millimeters of arc around
 583//   the pitch circle covered by one tooth and one space between teeth.  The `pressure_angle` controls how flat or
 584//   bulged the sides of the teeth are.  Common values include 14.5 degrees and 20 degrees, and
 585//   occasionally 25.  Though I've seen 28 recommended for plastic gears. Larger numbers bulge out
 586//   more, giving stronger teeth, so 28 degrees is the default here.  The ratio of `teeth` for two
 587//   meshing gears gives how many times one will make a full revolution when the the other makes one
 588//   full revolution.  If the two numbers are coprime (i.e.  are not both divisible by the same number
 589//   greater than 1), then every tooth on one gear will meet every tooth on the other, for more even
 590//   wear.  So coprime numbers of teeth are good.
 591// Arguments:
 592//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.  Default: 5
 593//   teeth = Total number of teeth around the entire perimeter.  Default: 20
 594//   face_width = Width of the toothed surface in mm, from inside to outside.  Default: 10
 595//   pitch_angle = Angle of beveled gear face.  Default: 45
 596//   mate_teeth = The number of teeth in the gear that this gear will mate with.  Overrides `pitch_angle` if given.
 597//   shaft_diam = Diameter of the hole in the center, in mm.  Module use only.  Default: 0 (no shaft hole)
 598//   hide = Number of teeth to delete to make this only a fraction of a circle.  Default: 0
 599//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. Default: 28
 600//   clearance = Clearance gap at the bottom of the inter-tooth valleys.
 601//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
 602//   cutter_radius = Radius of spiral arc for teeth.  If 0, then gear will not be spiral.  Default: 0
 603//   spiral_angle = The base angle for spiral teeth.  Default: 0
 604//   left_handed = If true, the gear returned will have a left-handed spiral.  Default: false
 605//   slices = Number of vertical layers to divide gear into.  Useful for refining gears with `spiral`.  Default: 1
 606//   interior = If true, create a mask for difference()ing from something else.
 607//   mod = The metric module/modulus of the gear.
 608//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 609//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 610//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 611// Extra Anchors:
 612//   "apex" = At the pitch cone apex for the bevel gear.
 613//   "pitchbase" = At the natural height of the pitch radius of the beveled gear.
 614//   "flattop" = At the top of the flat top of the bevel gear.
 615// Example: Beveled Gear
 616//   bevel_gear(
 617//       pitch=5, teeth=36, face_width=10, shaft_diam=5,
 618//       pitch_angle=45, spiral_angle=0
 619//   );
 620// Example: Spiral Beveled Gear and Pinion
 621//   t1 = 16; t2 = 28;
 622//   bevel_gear(
 623//       pitch=5, teeth=t1, mate_teeth=t2,
 624//       slices=12, anchor="apex", orient=FWD
 625//   );
 626//   bevel_gear(
 627//       pitch=5, teeth=t2, mate_teeth=t1, left_handed=true,
 628//       slices=12, anchor="apex", spin=180/t2
 629//   );
 630// Example(Anim,Frames=4,VPD=175): Manual Spacing of Pinion and Gear
 631//   t1 = 14; t2 = 28; pitch=5;
 632//   back(pitch_radius(pitch=pitch, teeth=t2)) {
 633//     yrot($t*360/t1)
 634//     bevel_gear(
 635//       pitch=pitch, teeth=t1, mate_teeth=t2, shaft_diam=5,
 636//       slices=12, orient=FWD
 637//     );
 638//   }
 639//   down(pitch_radius(pitch=pitch, teeth=t1)) {
 640//     zrot($t*360/t2)
 641//     bevel_gear(
 642//       pitch=pitch, teeth=t2, mate_teeth=t1, left_handed=true,
 643//       shaft_diam=5, slices=12, spin=180/t2
 644//     );
 645//   }
 646function bevel_gear(
 647    pitch = 5,
 648    teeth = 20,
 649    face_width = 10,
 650    pitch_angle = 45,
 651    mate_teeth,
 652    hide = 0,
 653    pressure_angle = 20,
 654    clearance = undef,
 655    backlash = 0.0,
 656    cutter_radius = 30,
 657    spiral_angle = 35,
 658    left_handed = false,
 659    slices = 5,
 660    interior = false,
 661    mod,
 662    anchor = "pitchbase",
 663    spin = 0,
 664    orient = UP
 665) =
 666    let(
 667        pitch = is_undef(mod) ? pitch : pitch_value(mod),
 668        slices = cutter_radius==0? 1 : slices,
 669        pitch_angle = is_undef(mate_teeth)? pitch_angle : atan(teeth/mate_teeth),
 670        pr = pitch_radius(pitch, teeth),
 671        rr = _root_radius(pitch, teeth, clearance, interior),
 672        pitchoff = (pr-rr) * sin(pitch_angle),
 673        ocone_rad = opp_ang_to_hyp(pr, pitch_angle),
 674        icone_rad = ocone_rad - face_width,
 675        cutter_radius = cutter_radius==0? 1000 : cutter_radius,
 676        midpr = (icone_rad + ocone_rad) / 2,
 677        radcp = [0, midpr] + polar_to_xy(cutter_radius, 180+spiral_angle),
 678        angC1 = law_of_cosines(a=cutter_radius, b=norm(radcp), c=ocone_rad),
 679        angC2 = law_of_cosines(a=cutter_radius, b=norm(radcp), c=icone_rad),
 680        radcpang = v_theta(radcp),
 681        sang = radcpang - (180-angC1),
 682        eang = radcpang - (180-angC2),
 683        profile = _gear_tooth_profile(
 684            pitch = pitch,
 685            teeth = teeth,
 686            pressure_angle = pressure_angle,
 687            clearance = clearance,
 688            backlash = backlash,
 689            interior = interior,
 690            valleys = false,
 691            center = true
 692        ),
 693        verts1 = [
 694            for (v = lerpn(0,1,slices+1)) let(
 695                p = radcp + polar_to_xy(cutter_radius, lerp(sang,eang,v)),
 696                ang = v_theta(p)-90,
 697                dist = norm(p)
 698            ) [
 699                let(
 700                    u = dist / ocone_rad,
 701                    m = up((1-u) * pr / tan(pitch_angle)) *
 702                        up(pitchoff) *
 703                        zrot(ang/sin(pitch_angle)) *
 704                        back(u * pr) *
 705                        xrot(pitch_angle) *
 706                        scale(u)
 707                )
 708                for (tooth=[0:1:teeth-1])
 709                each apply(xflip() * zrot(360*tooth/teeth) * m, path3d(profile))
 710            ]
 711        ],
 712        botz = verts1[0][0].z,
 713        topz = last(verts1)[0].z,
 714        thickness = abs(topz - botz),
 715        cpz = (topz + botz) / 2,
 716        vertices = [for (x=verts1) reverse(x)],
 717        sides_vnf = vnf_vertex_array(vertices, caps=false, col_wrap=true, reverse=true),
 718        top_verts = last(vertices),
 719        bot_verts = vertices[0],
 720        gear_pts = len(top_verts),
 721        face_pts = gear_pts / teeth,
 722        top_faces =[
 723            for (i=[0:1:teeth-1], j=[0:1:(face_pts/2)-1]) each [
 724                [i*face_pts+j, (i+1)*face_pts-j-1, (i+1)*face_pts-j-2],
 725                [i*face_pts+j, (i+1)*face_pts-j-2, i*face_pts+j+1]
 726            ],
 727            for (i=[0:1:teeth-1]) each [
 728                [gear_pts, (i+1)*face_pts-1, i*face_pts],
 729                [gear_pts, ((i+1)%teeth)*face_pts, (i+1)*face_pts-1]
 730            ]
 731        ],
 732        vnf1 = vnf_join([
 733            [
 734                [each top_verts, [0,0,top_verts[0].z]],
 735                top_faces
 736            ],
 737            [
 738                [each bot_verts, [0,0,bot_verts[0].z]],
 739                [for (x=top_faces) reverse(x)]
 740            ],
 741            sides_vnf
 742        ]),
 743        lvnf = left_handed? vnf1 : xflip(p=vnf1),
 744        vnf = down(cpz, p=lvnf),
 745        anchors = [
 746            named_anchor("pitchbase", [0,0,pitchoff-thickness/2]),
 747            named_anchor("flattop", [0,0,thickness/2]),
 748            named_anchor("apex", [0,0,hyp_ang_to_opp(ocone_rad,90-pitch_angle)+pitchoff-thickness/2])
 749        ]
 750    ) reorient(anchor,spin,orient, vnf=vnf, extent=true, anchors=anchors, p=vnf);
 751
 752
 753module bevel_gear(
 754    pitch = 5,
 755    teeth = 20,
 756    face_width = 10,
 757    pitch_angle = 45,
 758    mate_teeth,
 759    shaft_diam = 0,
 760    hide = 0,
 761    pressure_angle = 20,
 762    clearance = undef,
 763    backlash = 0.0,
 764    cutter_radius = 30,
 765    spiral_angle = 35,
 766    left_handed = false,
 767    slices = 5,
 768    interior = false,
 769    mod,
 770    anchor = "pitchbase",
 771    spin = 0,
 772    orient = UP
 773) {
 774    pitch = is_undef(mod) ? pitch : pitch_value(mod);
 775    slices = cutter_radius==0? 1 : slices;
 776    pitch_angle = is_undef(mate_teeth)? pitch_angle : atan(teeth/mate_teeth);
 777    pr = pitch_radius(pitch, teeth);
 778    ipr = pr - face_width*sin(pitch_angle);
 779    rr = _root_radius(pitch, teeth, clearance, interior);
 780    pitchoff = (pr-rr) * sin(pitch_angle);
 781    vnf = bevel_gear(
 782        pitch = pitch,
 783        teeth = teeth,
 784        face_width = face_width,
 785        pitch_angle = pitch_angle,
 786        hide = hide,
 787        pressure_angle = pressure_angle,
 788        clearance = clearance,
 789        backlash = backlash,
 790        cutter_radius = cutter_radius,
 791        spiral_angle = spiral_angle,
 792        left_handed = left_handed,
 793        slices = slices,
 794        interior = interior,
 795        anchor=CENTER
 796    );
 797    axis_zs = [for (p=vnf[0]) if(norm(point2d(p)) < EPSILON) p.z];
 798    thickness = max(axis_zs) - min(axis_zs);
 799    anchors = [
 800        named_anchor("pitchbase", [0,0,pitchoff-thickness/2]),
 801        named_anchor("flattop", [0,0,thickness/2]),
 802        named_anchor("apex", [0,0,adj_ang_to_opp(pr,90-pitch_angle)+pitchoff-thickness/2])
 803    ];
 804    attachable(anchor,spin,orient, r1=pr, r2=ipr, h=thickness, anchors=anchors) {
 805        difference() {
 806            vnf_polyhedron(vnf, convexity=teeth/2);
 807            if (shaft_diam > 0) {
 808                cylinder(h=2*thickness+1, r=shaft_diam/2, center=true, $fn=max(12,segs(shaft_diam/2)));
 809            }
 810        }
 811        children();
 812    }
 813}
 814
 815
 816
 817
 818
 819// Function&Module: worm()
 820// Usage: As a Module
 821//   worm(pitch|mod, d, l, [starts], [left_handed], [pressure_angle], [backlash], [clearance]);
 822// Usage: As a Function
 823//   vnf = worm(pitch|mod, d, l, [starts], [left_handed], [pressure_angle], [backlash], [clearance]);
 824// Topics: Gears
 825// See Also: worm_gear()
 826// Description:
 827//   Creates a worm shape that can be matched to a worm gear.
 828// Arguments:
 829//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.  Default: 5
 830//   d = The diameter of the worm.  Default: 30
 831//   l = The length of the worm.  Default: 100
 832//   starts = The number of lead starts.  Default: 1
 833//   left_handed = If true, the gear returned will have a left-handed spiral.  Default: false
 834//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. Default: 20
 835//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
 836//   clearance = Clearance gap at the bottom of the inter-tooth valleys.
 837//   mod = The metric module/modulus of the gear.
 838//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 839//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 840//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 841// Example:
 842//   worm(pitch=8, d=30, l=50, $fn=72);
 843// Example: Multiple Starts.
 844//   worm(pitch=8, d=30, l=50, starts=3, $fn=72);
 845// Example: Left Handed
 846//   worm(pitch=8, d=30, l=50, starts=3, left_handed=true, $fn=72);
 847// Example: Called as Function
 848//   vnf = worm(pitch=8, d=35, l=50, starts=2, left_handed=true, pressure_angle=20, $fn=72);
 849//   vnf_polyhedron(vnf);
 850function worm(
 851    pitch=5,
 852    d=30, l=100,
 853    starts=1,
 854    left_handed=false,
 855    pressure_angle=20,
 856    backlash=0,
 857    clearance,
 858    mod,
 859    anchor=CENTER,
 860    spin=0,
 861    orient=UP
 862) =
 863    let(
 864        pitch = is_undef(mod) ? pitch : pitch_value(mod),
 865        rack_profile = select(rack2d(
 866            pitch = pitch,
 867            teeth = starts,
 868            height = d,
 869            pressure_angle = pressure_angle,
 870            backlash = backlash,
 871            clearance = clearance
 872        ), 1, -2),
 873        polars = [
 874            for (i=idx(rack_profile)) let(
 875                p = rack_profile[i],
 876                a = 360 * p.x / pitch / starts
 877            ) [a, p.y + d/2]
 878        ],
 879        maxang = 360 / segs(d/2),
 880        refined_polars = [
 881            for (i=idx(polars,e=-2)) let(
 882                delta = polars[i+1].x - polars[i].x,
 883                steps = ceil(delta/maxang),
 884                step = delta/steps
 885            ) for (j = [0:1:steps-1])
 886            [polars[i].x + j*step, lerp(polars[i].y,polars[i+1].y, j/steps)]
 887        ],
 888        cross_sect = [ for (p = refined_polars) polar_to_xy(p.y, p.x) ],
 889        revs = l/pitch/starts,
 890        zsteps = ceil(revs*360/maxang),
 891        zstep = l/zsteps,
 892        astep = revs*360/zsteps,
 893        profiles = [
 894            for (i=[0:1:zsteps]) let(
 895                z = i*zstep - l/2,
 896                a = i*astep - 360*revs/2
 897            )
 898            apply(zrot(a)*up(z), path3d(cross_sect))
 899        ],
 900        rprofiles = [ for (prof=profiles) reverse(prof) ],
 901        vnf1 = vnf_vertex_array(rprofiles, caps=true, col_wrap=true, style="min_edge"),
 902        vnf = left_handed? xflip(p=vnf1) : vnf1
 903    ) reorient(anchor,spin,orient, d=d, l=l, p=vnf);
 904
 905
 906module worm(
 907    pitch=5,
 908    d=15, l=100,
 909    starts=1,
 910    left_handed=false,
 911    pressure_angle=20,
 912    backlash=0,
 913    clearance,
 914    mod,
 915    anchor=CENTER,
 916    spin=0,
 917    orient=UP
 918) {
 919    vnf = worm(
 920        pitch=pitch,
 921        starts=starts,
 922        d=d, l=l,
 923        left_handed=left_handed,
 924        pressure_angle=pressure_angle,
 925        backlash=backlash,
 926        clearance=clearance,
 927        mod=mod
 928    );
 929    attachable(anchor,spin,orient, d=d, l=l) {
 930        vnf_polyhedron(vnf, convexity=ceil(l/pitch)*2);
 931        children();
 932    }
 933}
 934
 935
 936// Function&Module: worm_gear()
 937// Usage: As a Module
 938//   worm_gear(pitch|mod, teeth, worm_diam, [worm_starts], [crowning], [left_handed], [pressure_angle], [backlash], [slices], [clearance], [shaft_diam]);
 939// Usage: As a Function
 940//   vnf = worm_gear(pitch|mod, teeth, worm_diam, [worm_starts], [crowning], [left_handed], [pressure_angle], [backlash], [slices], [clearance]);
 941// Topics: Gears
 942// See Also: worm()
 943// Description:
 944//   Creates a worm gear to match with a worm.
 945// Arguments:
 946//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.  Default: 5
 947//   teeth = Total number of teeth along the rack.  Default: 30
 948//   worm_diam = The pitch diameter of the worm gear to match to.  Default: 30
 949//   worm_starts = The number of lead starts on the worm gear to match to.  Default: 1
 950//   worm_arc = The arc of the worm to mate with, in degrees. Default: 60 degrees
 951//   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 fir the work easier.  Default: 1
 952//   left_handed = If true, the gear returned will have a left-handed spiral.  Default: false
 953//   pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. Default: 20
 954//   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle.  Default: 0
 955//   clearance = Clearance gap at the bottom of the inter-tooth valleys.
 956//   slices = The number of vertical slices to refine the curve of the worm throat.  Default: 10
 957//   mod = The metric module/modulus of the gear.
 958//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 959//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 960//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 961// Example: Right-Handed
 962//   worm_gear(pitch=5, teeth=36, worm_diam=30, worm_starts=1);
 963// Example: Left-Handed
 964//   worm_gear(pitch=5, teeth=36, worm_diam=30, worm_starts=1, left_handed=true);
 965// Example: Multiple Starts
 966//   worm_gear(pitch=5, teeth=36, worm_diam=30, worm_starts=4);
 967// Example: Metric Worm Gear
 968//   worm_gear(mod=2, teeth=32, worm_diam=30, worm_starts=1);
 969// Example(Anim,Frames=4,FrameMS=125,VPD=220,VPT=[-15,0,0]): Meshing Worm and Gear
 970//   $fn=36;
 971//   pitch = 5; starts = 4;
 972//   worm_diam = 30; worm_length = 50;
 973//   gear_teeth=36;
 974//   right(worm_diam/2)
 975//     yrot($t*360/starts)
 976//       worm(d=worm_diam, l=worm_length, pitch=pitch, starts=starts, orient=BACK);
 977//   left(pitch_radius(pitch=pitch, teeth=gear_teeth))
 978//     zrot(-$t*360/gear_teeth)
 979//       worm_gear(pitch=pitch, teeth=gear_teeth, worm_diam=worm_diam, worm_starts=starts);
 980// Example: Meshing Worm and Gear Metricly
 981//   $fn = 72;
 982//   modulus = 2; starts = 3;
 983//   worm_diam = 30; worm_length = 50;
 984//   gear_teeth=36;
 985//   right(worm_diam/2)
 986//       worm(d=worm_diam, l=worm_length, mod=modulus, starts=starts, orient=BACK);
 987//   left(pitch_radius(mod=modulus, teeth=gear_teeth))
 988//       worm_gear(mod=modulus, teeth=gear_teeth, worm_diam=worm_diam, worm_starts=starts);
 989// Example: Called as Function
 990//   vnf = worm_gear(pitch=8, teeth=30, worm_diam=30, worm_starts=1);
 991//   vnf_polyhedron(vnf);
 992function worm_gear(
 993    pitch = 5,
 994    teeth = 36,
 995    worm_diam = 30,
 996    worm_starts = 1,
 997    worm_arc = 60,
 998    crowning = 1,
 999    left_handed = false,
1000    pressure_angle = 20,
1001    backlash = 0,
1002    clearance,
1003    mod,
1004    slices = 10,
1005    anchor = CENTER,
1006    spin = 0,
1007    orient = UP
1008) =
1009    assert(worm_arc >= 10 && worm_arc <= 60)
1010    let(
1011        pitch = is_undef(mod) ? pitch : pitch_value(mod),
1012        p = pitch_radius(pitch, teeth),
1013        circ = 2 * PI * p,
1014        r1 = p + worm_diam/2 + crowning,
1015        r2 = worm_diam/2 + crowning,
1016        thickness = worm_gear_thickness(pitch=pitch, teeth=teeth, worm_diam=worm_diam, worm_arc=worm_arc, crowning=crowning, clearance=clearance),
1017        helical = pitch * worm_starts * worm_arc / 360 * 360 / circ,
1018        tooth_profile = reverse(_gear_tooth_profile(
1019            pitch = pitch,
1020            teeth = teeth,
1021            pressure_angle = pressure_angle,
1022            clearance = clearance,
1023            backlash = backlash,
1024            valleys = false,
1025            center = true
1026        )),
1027        profiles = [
1028            for (slice = [0:1:slices]) let(
1029                u = slice/slices - 0.5,
1030                zang = u * worm_arc,
1031                tp = [0,r1,0] - spherical_to_xyz(r2, 90, 90+zang),
1032                zang2 = u * helical
1033            ) [
1034                for (i = [0:1:teeth-1]) each
1035                apply(
1036                    zrot(-i*360/teeth+zang2) *
1037                        move(tp) *
1038                        xrot(-zang) *
1039                        scale(cos(zang)),
1040                    path3d(tooth_profile)
1041                )
1042            ]
1043        ],
1044        top_verts = last(profiles),
1045        bot_verts = profiles[0],
1046        face_pts = len(tooth_profile),
1047        gear_pts = face_pts * teeth,
1048        top_faces =[
1049            for (i=[0:1:teeth-1], j=[0:1:(face_pts/2)-2]) each [
1050                [i*face_pts+j, (i+1)*face_pts-j-1, (i+1)*face_pts-j-2],
1051                [i*face_pts+j, (i+1)*face_pts-j-2, i*face_pts+j+1]
1052            ],
1053            for (i=[0:1:teeth-1]) each [
1054                [gear_pts, (i+1)*face_pts-1, i*face_pts],
1055                [gear_pts, ((i+1)%teeth)*face_pts, (i+1)*face_pts-1]
1056            ]
1057        ],
1058        sides_vnf = vnf_vertex_array(profiles, caps=false, col_wrap=true, style="min_edge"),
1059        vnf1 = vnf_join([
1060            [
1061                [each top_verts, [0,0,top_verts[0].z]],
1062                [for (x=top_faces) reverse(x)]
1063            ],
1064            [
1065                [each bot_verts, [0,0,bot_verts[0].z]],
1066                top_faces
1067            ],
1068            sides_vnf
1069        ]),
1070        vnf = left_handed? xflip(p=vnf1) : vnf1
1071    ) reorient(anchor,spin,orient, r=p, l=thickness, p=vnf);
1072
1073
1074module worm_gear(
1075    pitch = 5,
1076    teeth = 36,
1077    worm_diam = 30,
1078    worm_starts = 1,
1079    worm_arc = 60,
1080    crowning = 1,
1081    left_handed = false,
1082    pressure_angle = 20,
1083    backlash = 0,
1084    slices = 10,
1085    clearance,
1086    mod,
1087    shaft_diam = 0,
1088    anchor = CENTER,
1089    spin = 0,
1090    orient = UP
1091) {
1092    pitch = is_undef(mod) ? pitch : pitch_value(mod);
1093    p = pitch_radius(pitch, teeth);
1094    vnf = worm_gear(
1095        pitch = pitch,
1096        teeth = teeth,
1097        worm_diam = worm_diam,
1098        worm_starts = worm_starts,
1099        worm_arc = worm_arc,
1100        crowning = crowning,
1101        left_handed = left_handed,
1102        pressure_angle = pressure_angle,
1103        backlash = backlash,
1104        slices = slices,
1105        clearance = clearance
1106    );
1107    thickness = pointlist_bounds(vnf[0])[1].z;
1108    attachable(anchor,spin,orient, r=p, l=thickness) {
1109        difference() {
1110            vnf_polyhedron(vnf, convexity=teeth/2);
1111            if (shaft_diam > 0) {
1112                cylinder(d=shaft_diam, l=worm_diam, center=true);
1113            }
1114        }
1115        children();
1116    }
1117}
1118
1119
1120
1121
1122/// Function&Module: _gear_tooth_profile()
1123/// Usage: As Module
1124///   _gear_tooth_profile(pitch|mod, teeth, [pressure_angle], [clearance], [backlash], [interior], [valleys]);
1125/// Usage: As Function
1126///   path = _gear_tooth_profile(pitch|mod, teeth, [pressure_angle], [clearance], [backlash], [interior], [valleys]);
1127/// Topics: Gears
1128/// See Also: spur_gear2d()
1129/// Description:
1130///   When called as a function, returns the 2D profile path for an individual gear tooth.
1131///   When called as a module, creates the 2D profile shape for an individual gear tooth.
1132/// Arguments:
1133///   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
1134///   teeth = Total number of teeth on the spur gear that this is a tooth for.
1135///   pressure_angle = Pressure Angle.  Controls how straight or bulged the tooth sides are. In degrees.
1136///   clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters)
1137///   backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
1138///   interior = If true, create a mask for difference()ing from something else.
1139///   valleys = If true, add the valley bottoms on either side of the tooth.  Default: true
1140///   center = If true, centers the pitch circle of the tooth profile at the origin.  Default: false.
1141///   mod = The metric module/modulus of the gear.
1142/// Example(2D):
1143///   _gear_tooth_profile(pitch=5, teeth=20, pressure_angle=20);
1144/// Example(2D): Metric Gear Tooth
1145///   _gear_tooth_profile(mod=2, teeth=20, pressure_angle=20);
1146/// Example(2D):
1147///   _gear_tooth_profile(
1148///       pitch=5, teeth=20, pressure_angle=20, valleys=false
1149///   );
1150/// Example(2D): As a function
1151///   path = _gear_tooth_profile(
1152///       pitch=5, teeth=20, pressure_angle=20, valleys=false
1153///   );
1154///   stroke(path, width=0.1);
1155function _gear_tooth_profile(
1156    pitch = 3,
1157    teeth = 11,
1158    pressure_angle = 28,
1159    clearance = undef,
1160    backlash = 0.0,
1161    interior = false,
1162    valleys = true,
1163    center = false,
1164    mod
1165) = let(
1166    pitch = is_undef(mod) ? pitch : pitch_value(mod),
1167    p = pitch_radius(pitch, teeth),
1168    c = outer_radius(pitch, teeth, clearance, interior),
1169    r = _root_radius(pitch, teeth, clearance, interior),
1170    b = _base_radius(pitch, teeth, pressure_angle),
1171    t  = pitch/2-backlash/2,                //tooth thickness at pitch circle
1172    k  = -_gear_iang(b, p) - t/2/p/PI*180,  //angle to where involute meets base circle on each side of tooth
1173    kk = r<b? k : -180/teeth,
1174    isteps = 5,
1175    pts = [
1176        if (valleys) each [
1177            _gear_polar(r-1, 180.1/teeth),
1178            _gear_polar(r, 180.1/teeth),
1179        ],
1180        _gear_polar(r, -kk),
1181        for (i=[0: 1:isteps]) _gear_q7(i/isteps,r,b,c,k,-1),
1182        for (i=[isteps:-1:0]) _gear_q7(i/isteps,r,b,c,k, 1),
1183        _gear_polar(r, kk),
1184        if (valleys) each [
1185            _gear_polar(r, -180.1/teeth),
1186            _gear_polar(r-1, -180.1/teeth),
1187        ]
1188    ],
1189    pts2 = center? fwd(p, p=pts) : pts
1190) pts2;
1191
1192
1193module _gear_tooth_profile(
1194    pitch = 3,
1195    teeth = 11,
1196    pressure_angle = 28,
1197    backlash  = 0.0,
1198    clearance = undef,
1199    interior = false,
1200    valleys = true,
1201    center = false,
1202    mod
1203) {
1204    no_children($children);
1205    pitch = is_undef(mod) ? pitch : pitch_value(mod);
1206    r = _root_radius(pitch, teeth, clearance, interior);
1207    fwd(r)
1208    polygon(
1209        points=_gear_tooth_profile(
1210            pitch = pitch,
1211            teeth = teeth,
1212            pressure_angle = pressure_angle,
1213            backlash = backlash,
1214            clearance = clearance,
1215            interior = interior,
1216            valleys = valleys,
1217            center = center
1218        )
1219    );
1220}
1221
1222
1223// Section: Computing Gear Dimensions
1224//   These functions let the user find the derived dimensions of the gear.
1225//   A gear fits within a circle of radius outer_radius, and two gears should have
1226//   their centers separated by the sum of their pitch_radius.
1227
1228
1229// Function: circular_pitch()
1230// Usage:
1231//   circp = circular_pitch(pitch|mod);
1232// Topics: Gears
1233// Description:
1234//   Get tooth density expressed as "circular pitch".
1235// Arguments:
1236//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
1237//   mod = The metric module/modulus of the gear.
1238// Example:
1239//   circp = circular_pitch(pitch=5);
1240//   circp = circular_pitch(mod=2);
1241function circular_pitch(pitch=5, mod) =
1242    let( pitch = is_undef(mod) ? pitch : pitch_value(mod) )
1243    pitch;
1244
1245
1246// Function: diametral_pitch()
1247// Usage:
1248//   dp = diametral_pitch(pitch|mod);
1249// Topics: Gears
1250// Description:
1251//   Get tooth density expressed as "diametral pitch".
1252// Arguments:
1253//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
1254//   mod = The metric module/modulus of the gear.
1255// Example:
1256//   dp = diametral_pitch(pitch=5);
1257//   dp = diametral_pitch(mod=2);
1258function diametral_pitch(pitch=5, mod) =
1259    let( pitch = is_undef(mod) ? pitch : pitch_value(mod) )
1260    PI / pitch;
1261
1262
1263// Function: pitch_value()
1264// Usage:
1265//   pitch = pitch_value(mod);
1266// Topics: Gears
1267// Description:
1268//   Get circular pitch in mm from module/modulus.  The circular pitch of a gear is the number of
1269//   millimeters per tooth around the pitch radius circle.
1270// Arguments:
1271//   mod = The module/modulus of the gear.
1272function pitch_value(mod) = mod * PI;
1273
1274
1275// Function: module_value()
1276// Usage:
1277//   mod = module_value(pitch);
1278// Topics: Gears
1279// Description:
1280//   Get tooth density expressed as "module" or "modulus" in millimeters.  The module is the pitch
1281//   diameter of the gear divided by the number of teeth on it.  For example, a gear with a pitch
1282//   diameter of 40mm, with 20 teeth on it will have a modulus of 2.
1283// Arguments:
1284//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
1285function module_value(pitch=5) = pitch / PI;
1286
1287
1288/// Function: _adendum()
1289/// Usage:
1290///   ad = _adendum(pitch|mod);
1291/// Topics: Gears
1292/// Description:
1293///   The height of the top of a gear tooth above the pitch radius circle.
1294/// Arguments:
1295///   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
1296///   mod = The metric module/modulus of the gear.
1297/// Example:
1298///   ad = _adendum(pitch=5);
1299///   ad = _adendum(mod=2);
1300/// Example(2D):
1301///   pitch = 5; teeth = 17;
1302///   pr = pitch_radius(pitch=pitch, teeth=teeth);
1303///   adn = _adendum(pitch=5);
1304///   #spur_gear2d(pitch=pitch, teeth=teeth);
1305///   color("black") {
1306///       stroke(circle(r=pr),width=0.1,closed=true);
1307///       stroke(circle(r=pr+adn),width=0.1,closed=true);
1308///   }
1309function _adendum(pitch=5, mod) =
1310    let( pitch = is_undef(mod) ? pitch : pitch_value(mod) )
1311    module_value(pitch) * 1.0;
1312
1313
1314/// Function: _dedendum()
1315/// Usage:
1316///   ddn = _dedendum(pitch|mod, [clearance]);
1317/// Topics: Gears
1318/// Description:
1319///   The depth of the gear tooth valley, below the pitch radius.
1320/// Arguments:
1321///   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
1322///   clearance = If given, sets the clearance between meshing teeth.
1323///   mod = The metric module/modulus of the gear.
1324/// Example:
1325///   ddn = _dedendum(pitch=5);
1326///   ddn = _dedendum(mod=2);
1327/// Example(2D):
1328///   pitch = 5; teeth = 17;
1329///   pr = pitch_radius(pitch=pitch, teeth=teeth);
1330///   ddn = _dedendum(pitch=5);
1331///   #spur_gear2d(pitch=pitch, teeth=teeth);
1332///   color("black") {
1333///       stroke(circle(r=pr),width=0.1,closed=true);
1334///       stroke(circle(r=pr-ddn),width=0.1,closed=true);
1335///   }
1336function _dedendum(pitch=5, clearance, mod) =
1337    let( pitch = is_undef(mod) ? pitch : pitch_value(mod) )
1338    is_undef(clearance)? (1.25 * module_value(pitch)) :
1339    (module_value(pitch) + clearance);
1340
1341
1342// Function: pitch_radius()
1343// Usage:
1344//   pr = pitch_radius(pitch|mod, teeth);
1345// Topics: Gears
1346// Description:
1347//   Calculates the pitch radius for the gear.  Two mated gears will have their centers spaced apart
1348//   by the sum of the two gear's pitch radii.
1349// Arguments:
1350//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
1351//   teeth = The number of teeth on the gear.
1352//   mod = The metric module/modulus of the gear.
1353// Example:
1354//   pr = pitch_radius(pitch=5, teeth=11);
1355//   pr = pitch_radius(mod=2, teeth=20);
1356// Example(2D):
1357//   pr = pitch_radius(pitch=5, teeth=11);
1358//   #spur_gear2d(pitch=5, teeth=11);
1359//   color("black")
1360//       stroke(circle(r=pr),width=0.1,closed=true);
1361function pitch_radius(pitch=5, teeth=11, mod) =
1362    let( pitch = is_undef(mod) ? pitch : pitch_value(mod) )
1363    pitch * teeth / PI / 2;
1364
1365
1366// Function: outer_radius()
1367// Usage:
1368//   or = outer_radius(pitch|mod, teeth, [clearance], [interior]);
1369// Topics: Gears
1370// Description:
1371//   Calculates the outer radius for the gear. The gear fits entirely within a cylinder of this radius.
1372// Arguments:
1373//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
1374//   teeth = The number of teeth on the gear.
1375//   clearance = If given, sets the clearance between meshing teeth.
1376//   interior = If true, calculate for an interior gear.
1377//   mod = The metric module/modulus of the gear.
1378// Example:
1379//   or = outer_radius(pitch=5, teeth=20);
1380//   or = outer_radius(mod=2, teeth=16);
1381// Example(2D):
1382//   pr = outer_radius(pitch=5, teeth=11);
1383//   #spur_gear2d(pitch=5, teeth=11);
1384//   color("black")
1385//       stroke(circle(r=pr),width=0.1,closed=true);
1386function outer_radius(pitch=5, teeth=11, clearance, interior=false, mod) =
1387    let( pitch = is_undef(mod) ? pitch : pitch_value(mod) )
1388    pitch_radius(pitch, teeth) +
1389    (interior? _dedendum(pitch, clearance) : _adendum(pitch));
1390
1391
1392/// Function: _root_radius()
1393/// Usage:
1394///   rr = _root_radius(pitch|mod, teeth, [clearance], [interior]);
1395/// Topics: Gears
1396/// Description:
1397///   Calculates the root radius for the gear, at the base of the dedendum.
1398/// Arguments:
1399///   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
1400///   teeth = The number of teeth on the gear.
1401///   clearance = If given, sets the clearance between meshing teeth.
1402///   interior = If true, calculate for an interior gear.
1403///   mod = The metric module/modulus of the gear.
1404/// Example:
1405///   rr = _root_radius(pitch=5, teeth=11);
1406///   rr = _root_radius(mod=2, teeth=16);
1407/// Example(2D):
1408///   pr = _root_radius(pitch=5, teeth=11);
1409///   #spur_gear2d(pitch=5, teeth=11);
1410///   color("black")
1411///       stroke(circle(r=pr),width=0.1,closed=true);
1412function _root_radius(pitch=5, teeth=11, clearance, interior=false, mod) =
1413    let( pitch = is_undef(mod) ? pitch : pitch_value(mod) )
1414    pitch_radius(pitch, teeth) -
1415    (interior? _adendum(pitch) : _dedendum(pitch, clearance));
1416
1417
1418/// Function: _base_radius()
1419/// Usage:
1420///   br = _base_radius(pitch|mod, teeth, [pressure_angle]);
1421/// Topics: Gears
1422/// Description:
1423///   Get the base circle for involute teeth, at the base of the teeth.
1424/// Arguments:
1425///   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
1426///   teeth = The number of teeth on the gear.
1427///   pressure_angle = Pressure angle in degrees.  Controls how straight or bulged the tooth sides are.
1428///   mod = The metric module/modulus of the gear.
1429/// Example:
1430///   br = _base_radius(pitch=5, teeth=20, pressure_angle=20);
1431///   br = _base_radius(mod=2, teeth=18, pressure_angle=20);
1432/// Example(2D):
1433///   pr = _base_radius(pitch=5, teeth=11);
1434///   #spur_gear2d(pitch=5, teeth=11);
1435///   color("black")
1436///       stroke(circle(r=pr),width=0.1,closed=true);
1437function _base_radius(pitch=5, teeth=11, pressure_angle=28, mod) =
1438    let( pitch = is_undef(mod) ? pitch : pitch_value(mod) )
1439    pitch_radius(pitch, teeth) * cos(pressure_angle);
1440
1441
1442// Function: bevel_pitch_angle()
1443// Usage:
1444//   ang = bevel_pitch_angle(teeth, mate_teeth, [drive_angle]);
1445// Topics: Gears
1446// See Also: bevel_gear()
1447// Description:
1448//   Returns the correct pitch angle for a bevel gear with a given number of tooth, that is
1449//   matched to another bevel gear with a (possibly different) number of teeth.
1450// Arguments:
1451//   teeth = Number of teeth that this gear has.
1452//   mate_teeth = Number of teeth that the matching gear has.
1453//   drive_angle = Angle between the drive shafts of each gear.  Default: 90ยบ.
1454// Example:
1455//   ang = bevel_pitch_angle(teeth=18, mate_teeth=30);
1456// Example(2D):
1457//   t1 = 13; t2 = 19; pitch=5;
1458//   pang = bevel_pitch_angle(teeth=t1, mate_teeth=t2, drive_angle=90);
1459//   color("black") {
1460//       zrot_copies([0,pang])
1461//           stroke([[0,0,0], [0,-20,0]],width=0.2);
1462//       stroke(arc(r=3, angle=[270,270+pang]),width=0.2);
1463//   }
1464//   #bevel_gear(
1465//       pitch=5, teeth=t1, mate_teeth=t2,
1466//       spiral_angle=0, cutter_radius=1000,
1467//       slices=12, anchor="apex", orient=BACK
1468//   );
1469function bevel_pitch_angle(teeth, mate_teeth, drive_angle=90) =
1470    atan(sin(drive_angle)/((mate_teeth/teeth)+cos(drive_angle)));
1471
1472
1473// Function: worm_gear_thickness()
1474// Usage:
1475//   thick = worm_gear_thickness(pitch|mod, teeth, worm_diam, [worm_arc], [crowning], [clearance]);
1476// Topics: Gears
1477// See Also: worm(), worm_gear()
1478// Description:
1479//   Calculate the thickness of the worm gear.
1480// Arguments:
1481//   pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.  Default: 5
1482//   teeth = Total number of teeth along the rack.  Default: 30
1483//   worm_diam = The pitch diameter of the worm gear to match to.  Default: 30
1484//   worm_arc = The arc of the worm to mate with, in degrees. Default: 60 degrees
1485//   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 fir the work easier.  Default: 1
1486//   clearance = Clearance gap at the bottom of the inter-tooth valleys.
1487//   mod = The metric module/modulus of the gear.
1488// Example:
1489//   thick = worm_gear_thickness(pitch=5, teeth=36, worm_diam=30);
1490//   thick = worm_gear_thickness(mod=2, teeth=28, worm_diam=25);
1491// Example(2D):
1492//   pitch = 5;  teeth=17;
1493//   worm_diam = 30; starts=2;
1494//   y = worm_gear_thickness(pitch=pitch, teeth=teeth, worm_diam=worm_diam);
1495//   #worm_gear(
1496//       pitch=pitch, teeth=teeth,
1497//       worm_diam=worm_diam,
1498//       worm_starts=starts,
1499//       orient=BACK
1500//   );
1501//   color("black") {
1502//       ycopies(y) stroke([[-25,0],[25,0]], width=0.5);
1503//       stroke([[-20,-y/2],[-20,y/2]],width=0.5,endcaps="arrow");
1504//   }
1505function worm_gear_thickness(pitch=5, teeth=30, worm_diam=30, worm_arc=60, crowning=1, clearance, mod) =
1506    let(
1507        pitch = is_undef(mod) ? pitch : pitch_value(mod),
1508        r = worm_diam/2 + crowning,
1509        pitch_thick = r * sin(worm_arc/2) * 2,
1510        pr = pitch_radius(pitch, teeth),
1511        rr = _root_radius(pitch, teeth, clearance, false),
1512        pitchoff = (pr-rr) * sin(worm_arc/2),
1513        thickness = pitch_thick + 2*pitchoff
1514    ) thickness;
1515
1516
1517function _gear_polar(r,t) = r*[sin(t),cos(t)];
1518function _gear_iang(r1,r2) = sqrt((r2/r1)*(r2/r1) - 1)/PI*180 - acos(r1/r2);  //unwind a string this many degrees to go from radius r1 to radius r2
1519function _gear_q6(b,s,t,d) = _gear_polar(d,s*(_gear_iang(b,d)+t));            //point at radius d on the involute curve
1520function _gear_q7(f,r,b,r2,t,s) = _gear_q6(b,s,t,(1-f)*max(b,r)+f*r2);        //radius a fraction f up the curved side of the tooth
1521
1522
1523
1524
1525// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap