Source code for gearbox.transmission
"""A pair of meshing gears plus the operating conditions needed for strength calculations."""
from __future__ import annotations
from dataclasses import dataclass
from functools import cached_property
from math import copysign, cos, pi, radians, sin, sqrt, tan
from gearbox.enums import DynamicFactorMethod, FlankModification, GearingType
from gearbox.mathutils import inverse_involute, involute
from gearbox.models import Gear, Lubricant
[docs]
@dataclass(frozen=True, kw_only=True)
class Transmission:
"""Gear pair (``pinion`` drives ``wheel``) with its operating conditions.
The pinion is always an external gear; the wheel can be external or internal
(``Gear.internal``). For an internal wheel the signs follow ISO 6336-1: ``z_2``, ``u``, the
centre distance and every diameter of the wheel are negative.
Attributes:
pinion: Driving gear (gear 1).
wheel: Driven gear (gear 2).
lubricant: Lubricant used in the mesh.
pinion_speed_rpm: Rotational speed of the pinion [rpm].
power_kw: Transmitted power [kW].
design_life_hours: Required design life [h].
application_factor: Application (overload) factor ``K_A``.
min_safety_factor_pitting: Minimum required safety factor against pitting, ``S_Hmin``.
min_safety_factor_bending: Minimum required safety factor against tooth breakage,
``S_Fmin``.
gearing_type: Gear unit quality class, used by the AGMA mesh alignment factor.
temperature_factor: AGMA temperature factor ``K_T`` (1.0 up to 250 degF / 120 degC).
reliability_factor: AGMA reliability factor ``K_R``; see
:data:`gearbox.standards.agma2101.RELIABILITY_FACTORS`.
limited_pitting_allowed: Whether limited pitting is acceptable (ISO 6336-2 clause 4). It
selects the life factor curve ``Z_NT`` of through-hardened and case-hardened steels.
mesh_load_factor: Mesh load factor ``K_gamma`` of ISO 6336-1 (uneven load sharing between
several load paths); 1.0 for a single mesh.
dynamic_factor_method: ISO 6336-1 method for ``K_v`` (B by default).
measured_dynamic_factor: ``K_v`` obtained by Method A (measurement or a dynamic analysis);
when given it replaces the calculated one.
agma_quality_number: Transmission accuracy level ``Q_v`` of AGMA 2001-D04 (5 to 11, higher
is more accurate) for the dynamic factor. ``None`` estimates it from the ISO 1328 single
pitch deviation of the gears.
flank_modification: Flank modifications of a helical pair (ISO 6336-2 table 3). Only used
for helical gears with ``eps_beta >= 1`` or a partial overlap; the default is the
conservative case without modifications.
"""
pinion: Gear
wheel: Gear
lubricant: Lubricant
pinion_speed_rpm: float
power_kw: float
design_life_hours: float
application_factor: float = 1.0
min_safety_factor_pitting: float = 1.0
min_safety_factor_bending: float = 1.0
gearing_type: GearingType = GearingType.COMMERCIAL_ENCLOSED
flank_modification: FlankModification = FlankModification.NONE
agma_quality_number: int | None = None
temperature_factor: float = 1.0
reliability_factor: float = 1.0
limited_pitting_allowed: bool = False
mesh_load_factor: float = 1.0
dynamic_factor_method: DynamicFactorMethod = DynamicFactorMethod.METHOD_B
measured_dynamic_factor: float | None = None
def __post_init__(self) -> None:
if self.pinion.module != self.wheel.module:
raise ValueError("The normal module of both gears must be equal")
if self.pinion.pressure_angle != self.wheel.pressure_angle:
raise ValueError("The pressure angle of both gears must be equal")
if self.pinion.helix_angle != self.wheel.helix_angle:
raise ValueError("The helix angle of both gears must be equal (opposite hand)")
if self.pinion.double_helical != self.wheel.double_helical:
raise ValueError("Both gears must be double helical, or neither")
if self.pinion.internal:
raise ValueError("The pinion must be an external gear; only the wheel can be internal")
if self.wheel.internal and self.wheel.number_of_teeth <= self.pinion.number_of_teeth:
raise ValueError(
"An internal wheel needs more teeth than its pinion "
f"({self.wheel.number_of_teeth} <= {self.pinion.number_of_teeth})"
)
for name in ("pinion_speed_rpm", "power_kw", "design_life_hours"):
if not getattr(self, name) > 0:
raise ValueError(f"Transmission.{name} must be positive")
object.__setattr__(self, "gearing_type", GearingType(self.gearing_type))
object.__setattr__(
self, "dynamic_factor_method", DynamicFactorMethod(self.dynamic_factor_method)
)
if self.mesh_load_factor <= 0:
raise ValueError("Transmission.mesh_load_factor must be positive")
if self.measured_dynamic_factor is not None and self.measured_dynamic_factor < 1:
raise ValueError("Transmission.measured_dynamic_factor must be at least 1")
object.__setattr__(self, "flank_modification", FlankModification(self.flank_modification))
# --- kinematics ---------------------------------------------------------------------------
@property
def module(self) -> float:
"""Common normal module [mm]."""
return self.pinion.module
@property
def pressure_angle(self) -> float:
"""Common normal pressure angle [deg]."""
return self.pinion.pressure_angle
@property
def gear_ratio(self) -> float:
"""``u = z2 / z1``: >= 1 for an external pair, negative with an internal wheel."""
return self.wheel.signed_number_of_teeth / self.pinion.number_of_teeth
@property
def wheel_speed_rpm(self) -> float:
"""Rotational speed of the wheel [rpm]."""
return self.pinion_speed_rpm / abs(self.gear_ratio)
@property
def net_face_width(self) -> float:
"""Face width of the narrower gear [mm] (``F`` in AGMA 2001).
For double helical gears it is the width ``b_B`` of one helix.
"""
return min(self.pinion.face_width, self.wheel.face_width)
@property
def double_helical(self) -> bool:
"""``True`` if both gears are double helical."""
return self.pinion.double_helical
@property
def total_face_width(self) -> float:
"""Face width ``b`` of the load-carrying teeth [mm]: ``2 b_B`` for double helical gears."""
return 2 * self.net_face_width if self.double_helical else self.net_face_width
@property
def profile_shift_sum(self) -> float:
"""``x1 + x2``."""
return self.pinion.profile_shift_coefficient + self.wheel.profile_shift_coefficient
# --- mesh geometry ------------------------------------------------------------------------
@cached_property
def working_pressure_angle(self) -> float:
"""Transverse working pressure angle ``alpha_wt`` [deg]."""
involute_alpha_wt = involute(self.pinion.transverse_pressure_angle) + 2 * (
self.profile_shift_sum
) / (self.pinion.signed_number_of_teeth + self.wheel.signed_number_of_teeth) * tan(
radians(self.pinion.pressure_angle)
)
return inverse_involute(involute_alpha_wt)
@property
def reference_center_distance(self) -> float:
"""``a``, centre distance without profile shift [mm]; negative for an internal wheel."""
return (
(self.pinion.signed_number_of_teeth + self.wheel.signed_number_of_teeth) * self.module
) / (2 * cos(radians(self.pinion.helix_angle)))
@property
def working_center_distance(self) -> float:
"""``a_w`` [mm]; negative for an internal wheel."""
return (
self.reference_center_distance
* cos(radians(self.pinion.transverse_pressure_angle))
/ cos(radians(self.working_pressure_angle))
)
@cached_property
def transverse_contact_ratio(self) -> float:
"""``epsilon_alpha`` (ISO 6336-2 formula 32, without the active root diameter limit).
With the signed quantities of an internal wheel (negative ``d_b2`` and ``a_w``) the line of
action is ``r_b1 (tan a_a1 - tan a_wt) + r_b2 (tan a_a2 - tan a_wt)``, as in formulae (32),
(36) and (38).
"""
p, w = self.pinion, self.wheel
alpha_wt = radians(self.working_pressure_angle)
base_radius1, base_radius2 = p.base_diameter / 2, w.base_diameter / 2
# Parts of the line of action on each side of the pitch point (formulae 36-38).
pinion_tip_part = copysign(sqrt(p.tip_diameter**2 - p.base_diameter**2), 1) / 2 - (
base_radius1 * tan(alpha_wt)
)
wheel_tip_part = copysign(
sqrt(w.tip_diameter**2 - w.base_diameter**2), base_radius2
) / 2 - (base_radius2 * tan(alpha_wt))
if not w.internal:
# Formula (33): the roll angle of each flank is limited by its base circle (the
# tangent point of the line of action); the active root diameter limits of (34)-(35)
# need ISO 21771 data and are not applied.
pinion_tip_part = min(pinion_tip_part, base_radius2 * tan(alpha_wt))
wheel_tip_part = min(wheel_tip_part, base_radius1 * tan(alpha_wt))
line_of_action = pinion_tip_part + wheel_tip_part
transverse_base_pitch = (
pi
* self.module
* cos(radians(p.transverse_pressure_angle))
/ cos(radians(p.helix_angle))
)
return line_of_action / transverse_base_pitch
@property
def overlap_ratio(self) -> float:
"""``epsilon_beta`` (ISO 6336-2 formula 40, with ``b_B`` for double helical gears)."""
return self.net_face_width * sin(radians(self.pinion.helix_angle)) / (self.module * pi)
@property
def total_contact_ratio(self) -> float:
"""``epsilon_gamma = epsilon_alpha + epsilon_beta``."""
return self.transverse_contact_ratio + self.overlap_ratio
# --- loads --------------------------------------------------------------------------------
@property
def pitch_line_velocity(self) -> float:
"""Tangential speed at the reference circle of the pinion [m/s]."""
return self.pinion_speed_rpm * self.pinion.reference_diameter * pi / 60000
@property
def nominal_tangential_force(self) -> float:
"""``F_t`` at the reference circle [N]."""
return (
1000.0
* self.power_kw
* 60000
/ (pi * self.pinion.reference_diameter * self.pinion_speed_rpm)
)
@property
def external_load_factor(self) -> float:
"""``K_A K_gamma``, the factors applied to the nominal load before the internal ones."""
return self.application_factor * self.mesh_load_factor
@property
def line_load(self) -> float:
"""``F_t K_A / b``, the load per unit face width as used by ISO 6336-1 method B [N/mm].
``b = 2 b_B`` for double helical gears.
"""
return self.external_load_factor * self.nominal_tangential_force / self.total_face_width