"""
Gears - parametric, involute gears of all types
name: gear.py
by: Gumyr
date: July 14nd 2024
This module can be used to create a wide variety of involute spur gears,
either standard ISO (metric) or fully custom. Involute gears have
the property of continually meshing at a specific angle (the pressure angle)
thus avoiding the stutter of non-involute gears as the teeth lose
contact with each other. Imagine a telescope mount: involute gears would
allow the telescope to smoothly follow a star as it moves across the night
sky, while non-involute gears would introduce a shake that would blur the
image of a long exposure.
Gears are art pieces unless they mesh with each other. To ensure two
gears can mesh, follow these guidelines:
- Meshing gears need the same tooth shape and size, so use a common
module (for metric gears) or diametral pitch value (for imperial gears).
For fully custom gears, the base, pitch and outer radii will all
need to be calculated appropriately.
- When positioning two gears to mesh, they need to be separated by the
sum of their pitch radii. For ISO metric gears this is very easy to
do - simply multiply the gear module by the sum of the tooth count and
divide by two (in mm), or: separation = module*(n0 + n1)/2 [mm]
license:
Copyright 2024 Gumyr
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from math import sin, cos, tan, acos, radians, degrees
from typing import Optional, Union
from build123d import *
from OCP.StdFail import StdFail_NotDone
[docs]
class InvoluteToothProfile(BaseLineObject):
"""InvoluteToothProfile
The outline of a single involute tooth.
Args:
module (float): the ratio of the pitch diameter to the number of teeth and
is expressed in millimeters (mm)
tooth_count (int): number of teeth in complete gear
pressure_angle (float): the angle between the line of action (the line along
which the force is transmitted between meshing gear teeth) and the tangent
to the pitch circle. Common values are 14.5 or 20.
root_fillet (float): radius of the fillet at the root of the tooth
addendum (float, optional): the radial distance between the pitch circle and
the top of the gear tooth. Defaults to None (calculated).
dedendum (float, optional): the radial distance between the pitch circle and
the bottom of the gear tooth space. It defines the depth of the space
between gear teeth below the pitch circle. Defaults to None (calculated).
closed (bool, optional): create a closed wire. Defaults to False.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
"""
_applies_to = [BuildLine._tag]
def __init__(
self,
module: float,
tooth_count: int,
pressure_angle: float,
root_fillet: Optional[float] = None,
addendum: Optional[float] = None,
dedendum: Optional[float] = None,
closed: bool = False,
mode: Mode = Mode.ADD,
):
self.module = module
self.tooth_count = tooth_count
self.pitch_radius = module * tooth_count / 2
self.base_radius = self.pitch_radius * cos(radians(pressure_angle))
self.addendum = addendum if addendum is not None else module
self.addendum_radius = self.pitch_radius + self.addendum
self.dedendum = dedendum if dedendum is not None else 1.25 * module
self.root_radius = self.pitch_radius - self.dedendum
half_thick_angle = 90 / tooth_count
half_pitch_angle = half_thick_angle + degrees(
tan(radians(pressure_angle)) - radians(pressure_angle)
)
# # Create the involute curve points
involute_size = self.addendum_radius - self.base_radius
pnts = []
for i in range(11):
r = self.base_radius + involute_size * i / 10
α = acos(self.base_radius / r) # in radians
involute = tan(α) - α
if (rp := r * cos(involute)) > self.root_radius:
pnts.append((rp, r * sin(involute)))
with BuildLine(Plane.XY.rotated((0, 0, -half_pitch_angle))) as tooth:
l1 = Spline(*pnts)
l2 = Line(pnts[0], (self.root_radius, 0))
root = RadiusArc(
l2 @ 1,
Vector(self.root_radius, 0).rotate(Axis.Z, -2 * half_thick_angle),
self.root_radius,
)
top_land = RadiusArc(
l1 @ 1,
Vector(self.addendum_radius, 0),
-self.addendum_radius,
)
if root_fillet is not None:
try:
fillet(tooth.vertices().sort_by(Axis.X)[1], root_fillet)
except StdFail_NotDone as err:
raise ValueError(
"Invalid root radius, try a smaller value"
) from err
mirror(about=Plane.XZ)
close = (
[
Edge.make_line(
tooth.vertices().sort_by(Axis.Y)[-1].to_tuple(),
tooth.vertices().sort_by(Axis.Y)[0].to_tuple(),
)
]
if closed
else []
)
super().__init__(Wire(tooth.edges() + close), mode=mode)
[docs]
class SpurGearPlan(BaseSketchObject):
"""InvoluteToothProfile
The 2D plan of the gear.
Args:
module (float): the ratio of the pitch diameter to the number of teeth and
is expressed in millimeters (mm)
tooth_count (int): number of teeth in complete gear
pressure_angle (float): the angle between the line of action (the line along
which the force is transmitted between meshing gear teeth) and the tangent
to the pitch circle. Common values are 14.5 or 20.
root_fillet (float): radius of the fillet at the root of the tooth
addendum (float, optional): the radial distance between the pitch circle and
the top of the gear tooth. Defaults to None (calculated).
dedendum (float, optional): the radial distance between the pitch circle and
the bottom of the gear tooth space. It defines the depth of the space
between gear teeth below the pitch circle. Defaults to None (calculated).
closed (bool, optional): create a closed wire. Defaults to False.
align (Union[None, Align, tuple[Align, Align]], optional): align min, center,
or max of object. Defaults to (Align.CENTER, Align.CENTER).
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
"""
_applies_to = [BuildSketch._tag]
def __init__(
self,
module: float,
tooth_count: int,
pressure_angle: float,
root_fillet: Optional[float] = None,
addendum: Optional[float] = None,
dedendum: Optional[float] = None,
rotation: float = 0,
align: Union[Align, tuple[Align, Align]] = (Align.CENTER, Align.CENTER),
mode: Mode = Mode.ADD,
):
gear_tooth = InvoluteToothProfile(
module, tooth_count, pressure_angle, root_fillet, addendum, dedendum
)
self.pitch_radius = gear_tooth.pitch_radius
self.base_radius = gear_tooth.base_radius
self.addendum_radius = gear_tooth.addendum_radius
self.root_radius = gear_tooth.root_radius
if self.base_radius < self.root_radius:
raise ValueError("Invalid configuration, try changing the pressure angle")
gear_teeth = PolarLocations(0, tooth_count) * gear_tooth
gear_wire = Wire([e for tooth in gear_teeth for e in tooth.edges()])
gear_face = Face(gear_wire)
if gear_face.normal_at().Z < 0:
gear_face = -gear_face
super().__init__(gear_face, rotation, align, mode)
[docs]
class SpurGear(BasePartObject):
"""InvoluteToothProfile
The 3D representation of the gear.
Args:
module (float): the ratio of the pitch diameter to the number of teeth and
is expressed in millimeters (mm)
tooth_count (int): number of teeth in complete gear
pressure_angle (float): the angle between the line of action (the line along
which the force is transmitted between meshing gear teeth) and the tangent
to the pitch circle. Common values are 14.5 or 20.
root_fillet (float): radius of the fillet at the root of the tooth
thickness (float): gear thickness
addendum (float, optional): the radial distance between the pitch circle and
the top of the gear tooth. Defaults to None (calculated).
dedendum (float, optional): the radial distance between the pitch circle and
the bottom of the gear tooth space. It defines the depth of the space
between gear teeth below the pitch circle. Defaults to None (calculated).
closed (bool, optional): create a closed wire. Defaults to False.
align (Union[None, Align, tuple[Align, Align, Align]], optional): align min,
center, or max of object. Defaults to Align.CENTER.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
"""
_applies_to = [BuildPart._tag]
def __init__(
self,
module: float,
tooth_count: int,
pressure_angle: float,
thickness: float,
root_fillet: Optional[float] = None,
addendum: Optional[float] = None,
dedendum: Optional[float] = None,
rotation: RotationLike = (0, 0, 0),
align: Union[None, Align, tuple[Align, Align, Align]] = Align.CENTER,
mode: Mode = Mode.ADD,
):
gear_plan = SpurGearPlan(
module, tooth_count, pressure_angle, root_fillet, addendum, dedendum
)
self.pitch_radius = gear_plan.pitch_radius
self.base_radius = gear_plan.base_radius
self.addendum_radius = gear_plan.addendum_radius
self.root_radius = gear_plan.root_radius
super().__init__(
extrude(gear_plan, amount=thickness),
rotation,
align,
mode,
)
if __name__ == "__main__":
# from ocp_vscode import show, set_defaults, Camera
# set_defaults(reset_camera=Camera.CENTER)
gear_tooth = InvoluteToothProfile(
module=2,
tooth_count=12,
pressure_angle=14.5,
root_fillet=0.5 * MM,
)
gear_profile = SpurGearPlan(
module=2,
tooth_count=12,
pressure_angle=14.5,
root_fillet=0.5 * MM,
)
spur_gear = SpurGear(
module=2,
tooth_count=12,
pressure_angle=14.5,
root_fillet=0.5 * MM,
thickness=5 * MM,
)
# show(pack([gear_tooth, gear_profile, spur_gear], 5))