活動日誌の写真

【Fusion360 API】人力飛行機のフェアリング設計を自動化するPythonスクリプト

お久しぶりです。芝浦工業大学TBTフェアリング設計のきゃっぷです。
本日は、Fusion360 APIを使ったフェアリング設計の自動化について紹介したいと思います。
フェアリング製作が本格化していく中で、少しでも作業を楽にするために作ったスクリプトを解説します。

フェアリング設計は、空力やベンチレーションなどを考えるのはとても楽しい反面、時間がかかって面倒な工程も多くあります。
たとえば、翼型の配置を繰り返したり、ロフトを作成したり、発泡マスターやリブマスターを作ったりといった作業です。
これらは単調で時間を浪費しやすい工程ですが、避けては通れません。

そこで登場するのが Fusion API です。
Fusion APIとは、3D設計ソフト「Fusion 360」を自動で操作したり、自分専用の機能を作成したりできる仕組みのこと。
API(アプリケーション・プログラミング・インターフェース)とは、ソフトをプログラムから操作するための“入口”のようなものです。
Fusion APIを活用することで、繰り返し作業の自動化設計支援ツールの自作が可能になります。

「そんなことに時間をかけている暇はない!」
ということで、今回は作業の自動化に挑戦しました。

1:翼型の配置およびロフト


フェアリング設計でまず面倒なのが、翼型を平面ごとに配置し、スケーリングしてロフトを作成する作業です。
平面を作って翼型を配置、サイズを調整、次の平面を作ってまた配置……これを手動で繰り返していては、膨大な時間を費やしてしまいます。

そこで、この一連の流れを自動化するプログラムを作成しました。
指定した位置やスケールに応じて、翼型を自動で配置・ロフトしてくれるので、手作業の繰り返しを大幅に削減できます。

翼型の配置およびロフト自動化スクリプト

このスクリプトは、Fusion360 APIを用いて「前縁・後縁カーブから翼型を自動配置し、ロフトでフェアリング形状を作成する」ためのものです。
手動では何十回も行う「平面作成→翼型配置→スケーリング→ロフト」を、数分で自動実行できるようにします。



メイン処理の流れ

def run(context):
    app = adsk.core.Application.get()
    ui = app.userInterface
    design = app.activeProduct
    root = design.rootComponent
    sketches = root.sketches
    planes = root.constructionPlanes

    num_sections = 20
    circle_path = os.path.join(os.getcwd(), 'circle.dat')
    circle_pts = read_circle_dat(circle_path)

Fusion360のアクティブな設計を取得し、操作対象を初期化します。
断面数は20に設定(多すぎるとロフトが不安定になるため)。
circle.dat が存在すれば、それを翼型テンプレートとして読み込みます。


前縁・後縁カーブの選択と軸設定

ui.messageBox("まず『前縁』のスケッチ曲線を選択してください。")
leading = ui.selectEntity("Select Leading curve", "SketchCurves").entity
ui.messageBox("次に『後縁』のスケッチ曲線を選択してください。")
trailing = ui.selectEntity("Select Trailing curve", "SketchCurves").entity

# 軸の自動推定と選択
ctrl = eval_world_points(leading, samples=20) + eval_world_points(trailing, samples=20)
min_x, max_x = min(p.x for p in ctrl), max(p.x for p in ctrl)
min_y, max_y = min(p.y for p in ctrl), max(p.y for p in ctrl)
min_z, max_z = min(p.z for p in ctrl), max(p.z for p in ctrl)
ui.messageBox(f"スパン情報:\nX軸:{max_x-min_x}\nY軸:{max_y-min_y}\nZ軸:{max_z-min_z}")
axis_choice = ui.inputBox("軸を選択してください (X, Y, Z):", "軸選択", "Y")[0]

ユーザーに前縁・後縁スケッチを選ばせ、その座標分布からスパン方向(X/Y/Z)を判定します。
自動で範囲を表示し、最も伸びている方向を軸として選択するのが基本的な使い方です。
私はblenderとの併用もしているためスパン方向はY軸が多くなります。


断面生成と翼型配置

profiles = adsk.core.ObjectCollection.create()
for i in range(num_sections + 1):
    t = i / float(num_sections)
    coord = coord_min + (coord_max - coord_min) * t

    # 前縁後縁の交点を取得
    lead_pts = eval_world_points(leading, samples=400)
    trail_pts = eval_world_points(trailing, samples=400)
    hitsL = intersections_at_coordinate(lead_pts, axis, coord)
    hitsT = intersections_at_coordinate(trail_pts, axis, coord)

    pL = pick_front_back_point(hitsL, axis, 'front')
    pT = pick_front_back_point(hitsT, axis, 'back')
    if not pL or not pT: continue

    # 翼弦長と中心を計算
    radius = dist3(pL, pT) * 0.5
    mid = adsk.core.Point3D.create((pL.x+pT.x)/2, (pL.y+pT.y)/2, (pL.z+pT.z)/2)

    # 平面を生成しそこに翼型を描画
    pin = planes.createInput()
    if axis == 'Y':
        pin.setByOffset(root.xZConstructionPlane, adsk.core.ValueInput.createByReal(coord))
    sec_plane = planes.add(pin)
    sk = sketches.add(sec_plane)

    # 翼型を配置circle.datがあればスプラインなければ円
    if circle_pts:
        sp_objs = adsk.core.ObjectCollection.create()
        chord_length = radius * 2.0
        for (x, y) in circle_pts:
            px = (x - 0.5) * chord_length
            py = y * chord_length
            sp_objs.add(sk.sketchPoints.add(adsk.core.Point3D.create(px, py, 0)))
        spline = sk.sketchCurves.sketchFittedSplines.add(sp_objs)
    else:
        sk.sketchCurves.sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0, 0, 0), radius)

    if sk.profiles.count > 0:
        profiles.add(sk.profiles.item(0))

ここがスクリプトの中心部です。
選択した軸方向に一定間隔で断面平面を作り、各位置で前縁・後縁を結ぶように翼型を配置します。
もし circle.dat があれば、その形状をスケーリングしてスプラインで描きます。
データがない場合は、単純な円で代用します。

断面ごとにスケッチを作成し、得られたプロファイルを profiles コレクションに追加していきます。


ロフト処理と完了

if profiles.count >= 2:
    loftFeats = root.features.loftFeatures
    loftInput = loftFeats.createInput(adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
    for idx in range(profiles.count):
        loftInput.loftSections.add(profiles.item(idx))
    loftInput.isSolid = True
    loftInput.isClosed = False
    try:
        loft_feature = loftFeats.add(loftInput)
        msg = f"ロフト作成成功: {profiles.count} 断面"
    except:
        # 失敗時は断面を半分に減らして再試行
        loftInput2 = loftFeats.createInput(adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
        for idx in range(0, profiles.count, 2):
            loftInput2.loftSections.add(profiles.item(idx))
        loft_feature = loftFeats.add(loftInput2)
        msg = f"ロフト再試行成功: {loftInput2.loftSections.count} 断面"
else:
    msg = "断面が不足しています。"

ui.messageBox(msg)

最後に、収集した断面スケッチをロフトして立体形状を生成します。
ロフトが自己交差などで失敗した場合は、断面を間引いて再試行する安全機構を持っています。
処理が完了すると、結果(成功・失敗・断面数など)がメッセージで表示されます。


まとめ

このスクリプトの処理内容を一言でまとめると、
**「前縁・後縁を入力すると、自動で翼型を等間隔に配置し、ロフトで3D形状を作る」**というものです。

従来の手作業フロー:

  1. 平面を作る
  2. 翼型を読み込んで配置
  3. スケール・位置を調整
  4. 次の断面へ移動
  5. すべて終わったらロフト

この一連の工程をすべて自動で行い、設計時間を大幅に短縮します。
circle.dat の形状を入れ替えるだけで、任意の翼型にも対応可能です。

2:発砲スチロールマスター設計の自動化

設計が終わっても、設計者の仕事はまだ続きます。
設計通りに形を再現するためには、マスター(治具)を作る必要があります。

フェアリングは発泡スチロールを削って成形するため、マスターがなければ正確な形を出すことはできません。
私たちが作っているのは、球体のように単純な形ではなく、ラムネ瓶のように場所ごとに曲率が違う複雑な形状です。
そんなものを感覚だけで作るのは無理があります。

だからこそ、フェアリングを輪切りにして考えるんです。
「この位置はこんな形、この断面はこうなっている」という情報を示してくれるのがマスターです。
つまり、フェアリングをスライスして地図のように見せてくれる存在です。

ここではまず、Fusion360 APIのコアライブラリ adsk.core とモデリング関連の adsk.fusion を読み込みます。
API操作のベースとなる関数群がこれらに含まれています。
traceback は、エラー発生時にスタックトレースを表示するための標準ライブラリです。


メイン関数の開始

def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui = app.userInterface
        design = app.activeProduct
        if not design:
            ui.messageBox('アクティブなデザインがありません。')
            return

Fusionスクリプトのエントリーポイント。
run() 関数はFusion360でスクリプトを実行した際に呼ばれます。
ここではアプリケーションやUI、アクティブなデザイン情報を取得しています。
もしデザインが開かれていない場合は処理を中断します。


初期設定

<mark class="has-inline-color has-black-color">  </mark>      rootComp = design.rootComponent
        sketches = rootComp.sketches
        unitsMgr = design.unitsManager

ルートコンポーネント(設計の最上位階層)とスケッチ群、単位管理オブジェクトを取得します。
unitsMgr は、入力値(mmなど)をFusion内部単位(cm)に変換するのに使います。


ユーザー入力(ボディ選択)

        selection = ui.selectEntity('断面を抽出するボディを選択してください', 'Bodies')
        if selection:
            targetBody = selection.entity
        else:
            return

ここで、ユーザーが断面を切り出す対象のボディを選択します。
Fusion360のGUIで対象ボディをクリックすると、その情報が取得されます。


分割方向の設定

        (axis_str, is_cancelled) = ui.inputBox('分割方向の軸を入力してください (X, Y, または Z)', '分割方向', 'Z')

ユーザーに「どの軸方向でスライスするか(X/Y/Z)」を入力させます。
入力値は大文字化して selectedAxis に保存します。
もし無効な文字(例:A)が入っていたらメッセージを出して終了します。


始点位置の入力

        (start_pos_str, is_cancelled) = ui.inputBox(f'{selectedAxis_upper}軸方向の「開始位置 (mm)」を入力してください', '開始位置', '0')
        start_position = unitsMgr.evaluateExpression(start_pos_str, "mm")



スライスを開始する位置Z=0mm からスタートを数値で指定できるようになりました
これにより、「Z=200mm からスライスを開始といった柔軟な設定が可能になっています

スライス間隔・数・配置間隔の入力

        (interval_str, is_cancelled) = ui.inputBox('分割間隔 (mm) を入力してください', '分割間隔', '100')
        (count_str, is_cancelled) = ui.inputBox('分割数を入力してください', '分割数', '10')
        (spacing_str, is_cancelled) = ui.inputBox('マスター配置の間隔 (mm) を入力してください', '配置間隔', '100')

それぞれ、

  • 分割間隔 … 断面を切る間隔(例:100mmごと)
  • 分割数 … いくつ断面を取るか
  • 配置間隔 … 並べる際の距離(スケッチ上でのズレ幅)
    を設定します。

基準平面と移動ベクトルの設定

        if selectedAxis == 'X軸':
            basePlane = rootComp.yZConstructionPlane
            moveVector = adsk.core.Vector3D.create(0.0, spacing, 0.0)
        elif selectedAxis == 'Y軸':
            basePlane = rootComp.xZConstructionPlane
            moveVector = adsk.core.Vector3D.create(spacing, 0.0, 0.0)
        else:
            basePlane = rootComp.xYConstructionPlane
            moveVector = adsk.core.Vector3D.create(spacing, 0.0, 0.0)

選択された軸に応じて、どの基準平面からスライスを開始するかを決定します。
また、スケッチ上でマスター断面を横にずらして並べる方向(moveVector)も設定します。


メインループ:断面の生成と配置

        for i in range(count):
            current_offset = start_position + (i * interval)
            offset_distance = adsk.core.ValueInput.createByReal(current_offset)
            
            planes = rootComp.constructionPlanes
            planeInput = planes.createInput()
            planeInput.setByOffset(basePlane, offset_distance)
            offsetPlane = planes.add(planeInput)

ここがスクリプトの心臓部です。
指定した開始位置 + i × 間隔 の位置に新しいスライス平面を生成します。
つまり、Z=0mm → 100mm → 200mm → …のように自動で輪切りを作っていきます。


断面スケッチの作成とコピー

            tempSketch = sketches.add(offsetPlane)
            intersectedCurves = tempSketch.projectCutEdges(targetBody)

各平面上にスケッチを作り、その位置でボディを「切った断面線」を取得します。
これがいわゆる「輪切りの形」です。

            transform = adsk.core.Matrix3D.create()
            currentMoveVector = moveVector.copy()
            currentMoveVector.scaleBy(i)
            transform.translation = currentMoveVector

            masterSketch.copy(curvesToCopy, transform)

抽出した断面を、1枚の「Master_Profiles」スケッチに横並びでコピーします。
これにより、複数の断面を一望できるマスターが自動生成されます。


結果

このスクリプトを使えば、これまで数時間かかっていた「フェアリングの輪切り→断面配置→マスター作成」が、
たった数分、クリック数回で完了します。

まとめ

今回紹介した2つのスクリプトにより、フェアリング設計の中でも特に時間のかかる翼型配置とマスター製作を自動化することができました。
Fusion APIを活用することで、繰り返し作業を削減し、設計者は注力すべき空力設計や形状最適化など、より設計の重要な部分に時間を割けるようになります。