歯車の形に興味のある人に

FreeCADで歯車創成図を作成する(スクリプト編)

(2024.05.04)転位時のスクリプトにミスがありましたので、修正しました。

これまで、いくつかのCADソフトウェア(Autodesk Fusion、LibreCAD、QCAD)を用いて歯車の創成図の手操作による作図方法を紹介してきました。今回は、無料の3D-CADソフトウェアである「FreeCAD」を試したので、その結果をお見せします。
FreeCADでマクロ言語として組み込まれているPythonを利用しています。結果をいくつかまとめて下図に示します。

図1.歯数、転位係数と創成図

入力画面はありませんので、諸元の変更は直接Pythonスクリプト中の数字を書き換える必要があります。


処理内容の紹介

プログラムでは、「入力諸元からラック形状を作成」し、次に「ラックの移動と回転繰り返し」を実行します。その内容を簡単に説明します。

ラック形状作成

入力諸元に基づき、以下の形状を自動で作成します。ラックの中心(歯形の中心線とデータム線の交点)を原点にとっています。

図2.ラック定義

点aからeおよび点pの座標を以下の表に示します。この図形は鏡対称であるため、右半分に位置する点fからjおよび点qの座標は、左側の対応する点の座標の符号を反転させて得られます。

x y
a -π/2 \cdot m -h_f \cdot m
b -(π/4 + h_f \cdot \tan(pa)  + r_a \cdot \cos(pa)) \cdot m -h_f  \cdot m
c -(π/4 + \tan(pa)) \cdot m -1.0 \cdot m
d  -(π/4 - h_a \cdot \tan(pa)) \cdot m h_a \cdot m
e 0 h_a \cdot m
p -(π/4 + \tan(pa)  + r_a \cdot \cos(pa)) \cdot m (-h_f + r_a) \cdot m

ここで
pa:圧力角=20 deg
m:モジュール
hf:歯すえのたけ係数=1
ha:歯元のたけ係数=1.25
ra:ラック刃先の丸み係数=0.38

ラック移動、回転

ラックカッターが歯切りする時の姿勢変化(A→B→C→D)を、図3に示します。

図3.ラックの移動と回転
A→B→C→Dの動き
A-B ラックを原点から、ピッチ線まで移動させます。転位時は転位量を加算します。
B-C 歯車素材がθ回転したとき、ラックは水平方向にRθ動きます
C-D 歯車素材固定として考えて、oを中心にラックを-θ回転させます

0から最大角度まで区間分割し、角度増分しながらDの姿勢を重ねていくと、創成図が完成します。

以上の内容は、以前の記事でAutodesk Fusionを使用して述べていることと同一です。
involutegearsoft.hatenablog.com

Pythonスクリプト

実行方法

後述するスクリプトの全文を選択し、[Ctrl+C]でコピーします。
FreeCADで「マクロ」「マクロ」メニューから「作成」をクリックすると、ファイル名を入力する画面が表示されます。
図4.マクロ設定
ファイル名を付けたら、エディターが開くので、ここに先ほどコピーしたスクリプトを[Ctrl+V]でペーストします。
「マクロ」メニューから「マクロの実行」をクリックするか、画面上部のツールバーにある緑の三角形のボタンをクリックします。

実行結果

マクロを実行すると、下図のような結果ページが表示されます。画面下にあるのはラックで、上が創成図です。

図5.実行結果

諸元変更方法

スクリプトをエディターに貼り付け後、95行目から108行目に記載の諸元を書き換えてください。

    # 歯数
    z = 20
    # モジュール
    m = 2
    # 圧力角
    pa = 20
    # 転位係数
    x = 0
    # 歯すえのたけ
    ha = 1
    # 歯元のたけ
    hf = 1.25

    # ラック刃先の丸み係数
    ra = 0.38

スクリプト全文

import FreeCAD as App
import FreeCADGui as Gui
import Part
import math
import Draft

def setup_document():
    """ドキュメントを初期設定し、必要に応じて新規作成します。"""
    doc = App.activeDocument()
    if not doc:
    	doc = App.newDocument('GearGeneration')
    Gui.activateWorkbench("SketcherWorkbench")
    return doc

def create_sketch(doc):
    """新しいスケッチをドキュメントに追加し、編集モードを有効にします。"""
    sketch = doc.addObject('Sketcher::SketchObject', 'RackSketch')
    doc.recompute()
    Gui.activeDocument().setEdit(sketch.Name)
    return sketch

def add_polyline(sketch, points):
    """ラックの座標点を基にポリラインを作ってスケッチに追加します。"""
    for i in range(len(points) - 1):
        sketch.addGeometry(Part.LineSegment(points[i], points[i+1]), False)


def add_fillets(sketch, m, ha=1, hf=1.25, pa=20, x=0, ra=0.378981):
    """ラックにフィレットを追加します。"""
    if ra <=0 :
        return
    pa_radians = math.radians(pa)
    x1 = math.pi/4 *m + math.tan(pa_radians) * m
    y1 = -1.0 * m
    x2 = math.pi/4 * m + math.tan(pa_radians) * m + ra * math.cos(pa_radians) * m
    y2 = -hf  * m
    fillet_data = [
        (4, 5, App.Vector(x1, y1, 0), App.Vector(x2, y2, 0), ra * m),
        (0, 1, App.Vector(-x2, y2, 0), App.Vector(-x1, y1, 0), ra * m),
    ]
    for idx1, idx2, start, end, radius in fillet_data:
        sketch.fillet(idx1, idx2, start, end, radius, True, True)

def setup_points(m, ha, hf, pa):
    """入力諸元からからラックを構成する点の座標を設定します。"""
    pa_radians = math.radians(pa)
    x1 = math.pi/2 * m
    y1 =  -hf * m
    x2 = math.pi/4 * m + hf * math.tan(pa_radians) * m
    y2 =  -hf * m
    x3 = math.pi/4 * m - math.tan(pa_radians) * m
    y3 =  ha * m
    xlist = [-x1, -x2, -x3, 0, x3, x2, x1]
    ylist = [y1, y2, y3, y3, y3, y2, y1]

    return [App.Vector(x,y,0) for x,y in zip(xlist,ylist)]


def finish_editing(doc):
    """スケッチの編集を終了し、ドキュメントを再計算します。"""
    Gui.activeDocument().resetEdit()
    doc.recompute()
    Gui.SendMsgToActiveView("ViewFit")

def add_body_copy(doc, sketch, z, m, x):
    """選択されたボディのコピーを作成し、新しいベースオブジェクトとして設定します。"""
    body0 = sketch
    base = Part.Shape(body0.Shape)
    base_obj = doc.addObject("Part::Feature", f"Rack z{z} m{m} x{x}")
    base_obj.Shape = base
    return base_obj

def rotate_and_move(doc, base_obj, body0, R, xshift, rotation_angle, repetitions):
    """指定された回転角と回数に基づきボディを回転させ、移動させます。"""
    rotation_rad = math.radians(rotation_angle)
    # 繰り返し数の中央を0にする。ex repetitions=41のとき、-20~20のリスト作成
    index_list = [idx - int(repetitions / 2) for idx in range(repetitions)]
    for i in index_list:
        body_obj = doc.addObject("Part::Feature", "TempBody")
        body_obj.Shape = Part.Shape(body0.Shape)
        body_obj.Placement.Base += App.Vector(-R * rotation_rad * i, R+xshift, 0)
        body_obj.Placement.rotate(App.Vector(R * rotation_rad * i, -R-xshift, 0), App.Vector(0, 0, 1), -rotation_angle * i)
        base_obj.Shape = base_obj.Shape.fuse(Part.Shape(body_obj.Shape))
        doc.removeObject('TempBody')


def main():
    """メイン関数:ドキュメントの設定、スケッチの作成、フィレットの追加、ボディの回転・移動を実行します。"""
    doc = setup_document()
    sketch = create_sketch(doc)

    # 諸元定義

    # 歯数
    z = 20
    # モジュール
    m = 2
    # 圧力角
    pa = 20
    # 転位係数
    x = 0.5
    # 歯すえのたけ
    ha = 1
    # 歯元のたけ
    hf = 1.25

    # ラック刃先の丸み係数
    ra = 0.38

    print('創成図作成開始')
    App.ActiveDocument.Label = f"GearGeneration_z{z}_m{m}"
    
    # ラック回転最大角度
    ala = math.acos((m*(z-2*hf)) / (m*(z+2*ha))) 
    ala_deg = int(math.degrees(ala*1.2))

    # ラック頂点座標作成
    points = setup_points(m, ha, hf, pa)

    # ラック輪郭作成
    add_polyline(sketch, points)

    # ラックにフィレット追加 
    add_fillets(sketch, m, ha, hf, pa, ra)  # フィレット操作の追加

    finish_editing(doc)

    # 新しいベースオブジェクトを作成(最初のベースとして選択したボディの形状をコピー)
    base_obj = add_body_copy(doc, sketch, z, m, x)

    # 回転半径を、ピッチ半径に設定
    R = m * z / 2
    # 繰り返し数(奇数)
    repitation = 41
    # 回転きざみ
    rotation_angle = ala_deg / (repitation/2)
    print('回転きざみ:', rotation_angle)

    # 移動と回転実行
    rotate_and_move(doc, base_obj, Gui.Selection.getSelection()[0], R, x*m, rotation_angle, repitation)
    finish_editing(doc)
    print('終了しました')

main()

次回は、歯元のトロコイド曲線部分を取り出すスクリプトを紹介しようと思います。
以上