不思議遊星歯車の転位設計(2)

前回の「不思議遊星歯車の転位設計(1)」では、特定の歯数と既知の中心距離の組み合わせに適用する転位係数の計算法を紹介しました。その後、いろいろ文献を調査した結果、以下の資料に記載された方法が妥当と判断し、その考え方にしたがって歯数設計と転位設計を行うプログラムを開発したので、その内容を紹介します。
「不思議遊星歯車機構減速機の最高効率に関する研究」堀他、日本機械学会論文集C編 60 (579), 3940-3947, 1994

文献の内容

不思議遊星歯車は大減速比が得られるいっぽうで、効率がよくないと言われます。上記論文における最終目標は、その効率を向上させることですが、その前段階として歯数組み合わせや転位設計を系統的に行い、理論効率への影響因子を把握する様子が述べられています。その時の歯数設計、転位設計の考え方を参考にしました。
論文では、さらに以下へ続きますが、ここでは触れません。

  • 現状の最高効率と探索法
  • 最高効率を向上させるかみ合い率因子設計

歯数と転位の設計手順

論文で示された内容は、大まかに以下の手順であると理解しました。

  1. サンギヤZ1と出力リングギヤZ4の歯数が3の倍数であり、Z2をピニオンとするとき、同軸条件(Z4=Z1+2*Z2)を満たす歯数の組み合わせZ1, Z2, Z4を求めます。
  2. 固定リングギヤをZ3とし、その歯数を出力リングギヤより3歯少なくします。すなわち、Z3=Z4-3です。
  3. Z2の歯数を1歯減らします。これにより各歯車間に隙間ができ、どの歯車も正転位が可能になります。論文内では1歯だけに限ってはいませんが、実施例が1歯減であることと、それ以上の歯数減は、歯先が尖って成立しないことを確認したので、ここでは1歯減としました。

(2024.03.31訂正)論文の後半に歯数0.5,1,1.5,2.0減について歯数組み合わせと効率の関係について記載があります。歯数減が1の時最も効率が良いとのことです。

  1. Z2の転位係数を0.324、Z4の転位係数を0とします。

これにより、歯数Z2とZ4の中心距離が得られ、同じ中心距離をZ2とZ3、Z1とZ2に適用して噛み合い圧力角を求め、その結果から転位係数x1, x3を求めることができます。

不思議遊星歯車の歯数探索、転位設計プログラム

論文を参考にして、歯数探索と転位設計のプログラムを開発しました。目標減速比と許容範囲を指定すると、その範囲内で可能な歯数の組み合わせを列挙し、転位係数等の諸元を出力します。計算例を以下に示します。

例:減速比100~110としたときの計算例

入力

プログラムのmain関数にあるtarget_ratioが減速比目標値で、toleranceが許容範囲です。下記の例では105±5内の減速比となる歯数組み合わせを出力します。

def main():
    # Target gear ratio
    target_ratio = 105      # ←ここを変更します
    # Tolerance range
    tolerance = 5             # ←ここを変更します

    # Module
    m = 1                         # モジュール変更時はここを修正します
    # Pressure angle in radians
    al = math.radians(20)  # 圧力角変更時はここを修正します
出力

その範囲内で15の歯数組み合わせが見つかりました。各ケースについて、歯数Z1からZ4、転位係数X1からX4、中心距離a、噛み合い圧力角alw、Z1の歯先の厚み、Z3の歯溝の厚みを出力します。

['Gear Ratio', 'Number of Teeth Z1', 'Number of Teeth Z2', 'Number of Teeth Z3', 'Number of Teeth Z4', 'Profile Shift Coef. Z1', 'Profile Shift Coef. Z2', 'Profile Shift Coef. Z3', 'Profile Shift Coef. Z4', 'Center Distance', 'Actual Pressure Angle', 'Top Land Thickness Z1', 'Top Land Thickness Z3']

1: [107.7, 9, 20, 48, 51, 0.4116, 0.324, 1.7468, 0, 15.1418, 25.9, 0.2309, -0.1025]
2: [105.0, 15, 23, 60, 63, 0.4033, 0.324, 1.7017, 0, 19.6515, 24.7, 0.4389, 0.0501]
3: [107.2, 24, 26, 75, 78, 0.3943, 0.324, 1.6616, 0, 25.6582, 23.7, 0.5712, 0.1723]

(途中省略)

15: [108.3, 60, 26, 111, 114, 0.3792, 0.324, 1.6009, 0, 43.6662, 22.3, 0.7229, 0.3315]

pythonプログラムリスト

プログラムを以下に記載しますので、興味ある方はお試しください。ただし、結果に責任は持てませんけど。

import math

def calculate_ratio(z1, z3, z4):
    """
    Calculate the gear ratio based on z1, z3, and z4.
    """
    return (1 / z1 + 1 / z3) / (1 / z3 - 1 / z4)

def is_valid_combination(z1, z4):
    """
    Check if z1 and z4 create a valid combination for z2 and z3.
    """
    z2_float = (z4 - z1) / 2
    return (z4 - z1) % 2 == 0 and z2_float > 11

def find_gear_sets(
        target_ratio, tolerance, z1_min, z1_max, 
        z4_min, z4_max, m, al, step_size=3
        ):
    gearset_list = []

    # Adjust the range of tooth numbers to multiples of step_size
    z1_min = math.ceil(z1_min / step_size) * step_size
    z4_min = math.ceil(z4_min / step_size) * step_size
    z1_range = range(z1_min, z1_max + 1, step_size)
    z4_range = range(z4_min, z4_max + 1, step_size)

    # Set the target range
    min_ratio = target_ratio - tolerance
    max_ratio = target_ratio + tolerance

    # Try combinations of tooth numbers
    for z1 in z1_range:
        for z4 in z4_range:
            if is_valid_combination(z1, z4):
                z2 = (z4 - z1) // 2 - 1
                z3 = z4 - 3

                # Calculate the gear ratio
                i = calculate_ratio(z1, z3, z4)
                
                # Check if the ratio is within the target range
                if min_ratio <= i <= max_ratio:
                    gearset_list.append((z1, z2, z3, z4, m, al, round(i, 1)))

    return gearset_list



class MechanicalParadoxGearSystem:
    def __init__(self, number_of_teeth_1, number_of_teeth_2, 
                 number_of_teeth_3, number_of_teeth_4,
                module, pressure_angle, ratio, 
                profile_shift_coef_2=0.324, profile_shift_coef_4=0.0):
        self.number_of_teeth_1 = number_of_teeth_1
        self.number_of_teeth_2 = number_of_teeth_2
        self.number_of_teeth_3 = number_of_teeth_3
        self.number_of_teeth_4 = number_of_teeth_4
        self.module = module
        self.pressure_angle = pressure_angle
        self.ratio = ratio
        self.profile_shift_coef_2 = profile_shift_coef_2
        self.profile_shift_coef_4 = profile_shift_coef_4

        # Calculate the center distance between gear 2 and gear 4
        self.center_distance = self.calculate_center_distance()

        # Dictionary to store calculated coefficients
        self.profile_shift_values = {}
        self.calculate_coefficients()
        self.sa_list = self.calculate_top_land_thickness()

    def calculate_center_distance(self):
        # Calculate the center distance between gear 2 and 
        # gear 4 using profile shift coefficients
        inverse_pressure_angle = (
            self.calculate_involute(self.pressure_angle) 
            + 2 * math.tan(self.pressure_angle) 
            *  (self.profile_shift_coef_4 - self.profile_shift_coef_2) 
            /  (self.number_of_teeth_4 - self.number_of_teeth_2)
        )
        actual_pressure_angle = self.calculate_reverse_involute(
            inverse_pressure_angle
            )
        return (
            (self.number_of_teeth_4 - self.number_of_teeth_2) 
            / 2 * math.cos(self.pressure_angle) 
            / math.cos(actual_pressure_angle)
        )

    def calculate_involute(self, angle):
        # Involute function
        return math.tan(angle) - angle

    def calculate_reverse_involute(self, value):
        # Reverse involute function (approximation)
        # https://opeo.jp/library/onepoint/mech_elem/gear/inv_involute/

        return (
            3 ** (1 / 3) * value ** (1 / 3)
            - (2 / 5) * value
            + (9 / 175) * 3 ** (2 / 3) * value ** (5 / 3)
            - (2 / 175) * 3 ** (1 / 3) * value ** (7 / 3)
        )

    def calculate_coefficients(self):
        # Calculate profile shift coefficients for each gear pair
        gear_pairs = [
            (self.number_of_teeth_1, self.number_of_teeth_2, "external"),
            (self.number_of_teeth_2, self.number_of_teeth_3, "internal"),
            (self.number_of_teeth_2, self.number_of_teeth_4, "internal"),
        ]

        for Z1, Z2, gear_type in gear_pairs:
            tooth_difference = Z1 + Z2 if gear_type == "external" else abs(Z1 - Z2)

            acos_argument = (
                tooth_difference * self.module 
                / (2 * self.center_distance) * math.cos(self.pressure_angle)
            )
            # Check if the argument of acos is within the valid range
            if not -1 <= acos_argument <= 1:
                raise ValueError(f"Invalid argument for acos: {acos_argument}")

            actual_pressure_angle = math.acos(acos_argument)
            x_sum = (
                (self.calculate_involute(actual_pressure_angle) 
                 - self.calculate_involute(self.pressure_angle))
                / (2 * math.tan(self.pressure_angle))
                * tooth_difference
            )
            self.profile_shift_values[(Z1, Z2)] = {
                "actual_pressure_angle": actual_pressure_angle,
                  "x_sum": x_sum
                  }

        # Calculate profile shift coefficients for each gear
        self.profile_shift_coef_Z4 = 0
        self.profile_shift_coef_Z2 = -self.profile_shift_values[
            (self.number_of_teeth_2, self.number_of_teeth_4)
            ]["x_sum"]
        self.profile_shift_coef_Z1 = self.profile_shift_values[
            (self.number_of_teeth_1, self.number_of_teeth_2)
            ]["x_sum"] - self.profile_shift_coef_Z2
        self.profile_shift_coef_Z3 = self.profile_shift_values[
            (self.number_of_teeth_2, self.number_of_teeth_3)
            ]["x_sum"] + self.profile_shift_coef_Z2


    def calculate_top_land_thickness(self):
        # Calculate the top land tooth thickness
        # (specifically the bottom groove width for internal gears)
        sa_values = []
        for gear_number, addendum_factor, profile_shift_coef \
            in zip([1, 3], [1, 1.25], [self.profile_shift_coef_Z1, self.profile_shift_coef_Z3]):
            number_of_teeth = getattr(self, f"number_of_teeth_{gear_number}")
            # Pitch circle diameter
            pitch_diameter = number_of_teeth * self.module
            # Root circle diameter
            root_diameter = (
                pitch_diameter + 2 * self.module 
                * (addendum_factor + profile_shift_coef)
            )
            # Base circle diameter
            base_diameter = pitch_diameter * math.cos(self.pressure_angle)
            # Pressure angle at the root (for internal gears, at the bottom)
            root_pressure_angle = math.acos(base_diameter / root_diameter)
            # Half angle at the root (for internal gears, half angle of the groove)
            psf = (
                math.pi / (2 * number_of_teeth)
                + 2 * profile_shift_coef * math.tan(self.pressure_angle)
                 / number_of_teeth
                + (self.calculate_involute(self.pressure_angle) 
                   - self.calculate_involute(root_pressure_angle))
            )
            # Tooth thickness at the root 
            #(for internal gears, groove width at the bottom)
            sa = psf * root_diameter
            sa_values.append(sa)
        return sa_values

    def result(self):
        output_set = [
            self.ratio,
            self.number_of_teeth_1,
            self.number_of_teeth_2,
            self.number_of_teeth_3,
            self.number_of_teeth_4,
            round(self.profile_shift_coef_Z1, 4),
            round(self.profile_shift_coef_Z2, 4),
            round(self.profile_shift_coef_Z3, 4),
            round(self.profile_shift_coef_Z4, 4),
            round(self.center_distance, 4),
            round(math.degrees(
                self.profile_shift_values[
                    (self.number_of_teeth_1, self.number_of_teeth_2)
                ]["actual_pressure_angle"]
            ), 1),
            round(self.sa_list[0], 4),
            round(self.sa_list[1], 4),
        ]
        return output_set


def main():
    # Target gear ratio
    target_ratio = 105      # ←ここを変更します
    # Tolerance range
    tolerance = 5             # ←ここを変更します

    # Module
    m = 1                         # モジュール変更時はここを修正します
    # Pressure angle in radians
    al = math.radians(20)  # 圧力角変更時はここを修正します

    # Search range
    z1_min = 9  # Minimum sun gear teeth count (multiple of 3)
    z1_max = 60  # Maximum sun gear teeth count
    z4_min = 42  # Minimum ring gear teeth count (multiple of 3)
    z4_max = 300  # Maximum ring gear teeth count

    # List up gear sets within the target range
    gear_sets = find_gear_sets(
        target_ratio, tolerance, z1_min, z1_max, z4_min, z4_max, m, al
    )

    header = [
        "Gear Ratio",
        "Number of Teeth Z1",
        "Number of Teeth Z2",
        "Number of Teeth Z3",
        "Number of Teeth Z4",
        "Profile Shift Coef. Z1",
        "Profile Shift Coef. Z2",
        "Profile Shift Coef. Z3",
        "Profile Shift Coef. Z4",
        "Center Distance",
        "Actual Pressure Angle",
        "Top Land Thickness Z1",
        "Top Land Thickness Z3"
    ]
    print(header)
    for i, gear_set in enumerate(gear_sets):

        gear_system = MechanicalParadoxGearSystem(*gear_set)
        output_set = gear_system.result()
        print(f"{i+1}:", output_set)

if __name__ == "__main__":
    main()

次回は、この結果をもとに諸元計算して3Dモデルを作ってみます。