1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

デジタルツインの誕生:Fusion 360の設計データを物理エンジンに召喚する【Day 4】

Last updated at Posted at 2025-12-03

1. はじめに:絵に描いた餅を、食べられる餅にする

前回の [Day 3] では、Fusion 360を使ってパラメトリック設計による「100mmキューブ・サンドイッチ構造」の筐体を作り上げました。

しかし、これはまだPCの中にあるただの**「絵(3Dモデル)」**に過ぎません。重さもなければ、摩擦もありません。このままでは、いくらコードを書いても何も動きません。

本日のゴールは、この設計データを物理シミュレータ (PyBullet) の中に 「デジタルツイン(物理的実体を持った分身)」 として召喚することです。

2. 戦略:プラグインを使わない「手組みURDF」

Fusion 360からロボット定義ファイル (URDF) を自動で書き出すプラグインはいくつか存在します。
しかし、今回はあえてそれらを使いません。

なぜ手書きなのか?

自動変換ツールは便利ですが、座標変換がブラックボックスになりがちです。
「シミュレータ上では動くのに、実機だと逆に動く」「なぜか空中に固定される」といったバグに遭遇したとき、中身を理解していないと詰みます。

今回の手法

  1. Fusion 360からパーツごとの形状データ (STL) を書き出す。
  2. XML形式の URDFファイル を自分の手で書き、構造(関節の位置、質量の中心など)を定義する。

この「泥臭い」アプローチこそが、Sim2Real(実機への転移)を成功させるための急がば回れルートです。

3. 実装:STLエクスポートとURDFの記述

3.1 STLの準備

Fusion 360から、動かないベース部分 (body.stl) と、回転するフライホイール部分 (flywheel.stl) をそれぞれ書き出します。

image.png

3.2 URDFの魔術(ここがハマりポイント)

ロボットの構成図である my_cubli.urdf を作成します。
URDFでは、剛体を表す <link> と、関節を表す <joint> を組み合わせて記述します。

ここで最も重要、かつハマりやすいのが 「見た目 (Visual)」 と 「物理 (Inertial)」 の分離 です。

直面した課題:物理エンジンの発狂

最初、Fusion 360の設計座標をそのままURDFに入力しました。
するとシミュレータを起動した瞬間、フライホイールが筐体の外にテレポートし、地面に激突してガタガタと荒ぶる現象(通称:物理エンジンの発狂)が発生しました。

原因は、STLファイルの原点と、物理演算上の重心位置(Center of Mass)の定義ズレです。

解決策:重心オフセット

試行錯誤の結果、見た目の位置合わせはそのままで、物理的な重心定義だけをずらすことで解決しました。

<?xml version="1.0"?>
<robot name="my_cubli">

  <material name="white">
    <color rgba="0.9 0.9 0.9 1"/>
  </material>
  <material name="gold">
    <color rgba="0.8 0.6 0.2 1"/>
  </material>

  <link name="base_link">
    <visual>
      <geometry>
        <mesh filename="meshes/body.stl" scale="0.001 0.001 0.001"/>
      </geometry>
      <material name="white"/>
    </visual>
    
    <collision>
      <geometry>
        <mesh filename="meshes/body.stl" scale="0.001 0.001 0.001"/>
      </geometry>
    </collision>
    
    <inertial>
      <origin xyz="0.05 0.05 0.05" rpy="0 0 0"/>
      <mass value="0.4"/>
      <inertia ixx="0.0006" ixy="0" ixz="0" iyy="0.0006" iyz="0" izz="0.0006"/>
    </inertial>
  </link>

  <link name="wheel_x">
    <visual>
      <geometry>
        <mesh filename="meshes/flywheel.stl" scale="0.001 0.001 0.001"/>
      </geometry>
      <material name="gold"/>
    </visual>
    
    <inertial>
      <origin xyz="0 0 -0.05" rpy="0 0 0"/>
      <mass value="0.1"/>
      <inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.0001"/>
    </inertial>
  </link>

  <joint name="joint_x" type="continuous">
    <parent link="base_link"/>
    <child link="wheel_x"/>
    <origin xyz="0.0 0.045 0.045" rpy="0 1.5708 0"/> 
    <axis xyz="0 0 1"/> 
  </joint>

  <link name="wheel_y">
    <visual>
      <geometry>
        <mesh filename="meshes/flywheel.stl" scale="0.001 0.001 0.001"/>
      </geometry>
      <material name="gold"/>
    </visual>

    <inertial>
      <origin xyz="0 0 -0.05" rpy="0 0 0"/>
      <mass value="0.1"/>
      <inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.0001"/>
    </inertial>
  </link>

  <joint name="joint_y" type="continuous">
    <parent link="base_link"/>
    <child link="wheel_y"/>
    <origin xyz="0.045 0.09 0.045" rpy="1.5708 0 0"/> 
    <axis xyz="0 0 1"/>
  </joint>

  <link name="wheel_z">
    <visual>
      <geometry>
        <mesh filename="meshes/flywheel.stl" scale="0.001 0.001 0.001"/>
      </geometry>
      <material name="gold"/>
    </visual>

    <inertial>
      <origin xyz="0 0 -0.05" rpy="0 0 0"/>
      <mass value="0.1"/>
      <inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.0001"/>
    </inertial>
  </link>

  <joint name="joint_z" type="continuous">
    <parent link="base_link"/>
    <child link="wheel_z"/>
    <origin xyz="0.045 0.045 -0.0" rpy="0 0 0"/> 
    <axis xyz="0 0 1"/>
  </joint>

</robot>

この 「見た目は変えず、重心だけ操る」 テクニックにより、物理的に安定したモデルを作ることができました。

4. 召喚の儀(デモ動画)

準備は整いました。
Fusion 360から書き出し、手書きで修正したURDFを読み込むPythonスクリプト (sim_cubli.py) を作成します。

ここでは、単に表示するだけでなく、「フライホイールにトルク(回転力)を与えて、筐体がどう反応するか」 をテストします。

実装コード: sim_cubli.py

import pybullet as p
import pybullet_data
import time
import math

# GUI起動
try:
    physicsClient = p.connect(p.GUI)
except Exception as e:
    print("Failed to connect to PyBullet GUI:", e)
    exit()

p.setAdditionalSearchPath(pybullet_data.getDataPath())
p.setGravity(0, 0, -9.81)
planeId = p.loadURDF("plane.urdf")

# カメラ設定 (接写)
p.resetDebugVisualizerCamera(
    cameraDistance=0.2, 
    cameraYaw=0, 
    cameraPitch=-30, 
    cameraTargetPosition=[0,0,0.05]
)

boxId = None
startPos = [0, 0, 0.1]
angle_45deg = math.pi / 4

# 少し傾けた状態で出現させる
startOrientation = p.getQuaternionFromEuler([angle_45deg*3, angle_45deg, angle_45deg])

# ★ フラグ管理
is_paused = False
is_motor_on = False # モーター回転フラグ

print("------------------------------------------------")
print(" [a] 出現 / リセット")
print(" [p] 一時停止 / 再開")
print(" [m] モーター回転 ON/OFF (Motor)")
print(" [SPACE] ジャンプ")
print("------------------------------------------------")

while True:
    # キーボード入力の取得
    keys = p.getKeyboardEvents()

    # ------------------------------------------------
    # ★ 'p' キーで一時停止切り替え
    # ------------------------------------------------
    if ord('p') in keys and keys[ord('p')] & p.KEY_WAS_TRIGGERED:
        is_paused = not is_paused
        print("Paused!" if is_paused else "Resumed!")

    # ------------------------------------------------
    # ★ 'm' キーでモーター回転 ON/OFF 切り替え
    # ------------------------------------------------
    if ord('m') in keys and keys[ord('m')] & p.KEY_WAS_TRIGGERED:
        is_motor_on = not is_motor_on
        state_str = "ON (Spinning!)" if is_motor_on else "OFF (Coasting)"
        print(f"Motor Torque: {state_str}")

    # ------------------------------------------------
    # ★ 'a' キーで出現
    # ------------------------------------------------
    if ord('a') in keys and keys[ord('a')] & p.KEY_WAS_TRIGGERED:
        if boxId is not None:
            p.removeBody(boxId)
        
        # ロボット読み込み
        boxId = p.loadURDF("my_cubli.urdf", startPos, startOrientation, globalScaling=1.0)
        
        # 重心の可視化
        p.setDebugObjectColor(boxId, -1, objectDebugColorRGB=[1,1,0])

        # ブレーキ解除(速度制御を無効化)
        p.setJointMotorControl2(boxId, 0, p.VELOCITY_CONTROL, force=0)
        p.setJointMotorControl2(boxId, 1, p.VELOCITY_CONTROL, force=0)
        p.setJointMotorControl2(boxId, 2, p.VELOCITY_CONTROL, force=0)
        
        # リセット時はモーターOFFに戻す
        is_motor_on = False
        print("Spawned!")

    # ------------------------------------------------
    # ★ スペースキーでジャンプ
    # ------------------------------------------------
    if 32 in keys and keys[32] & p.KEY_WAS_TRIGGERED:
        if boxId is not None:
            print("Jump!")
            p.applyExternalForce(boxId, -1, forceObj=[150, 150, 150], posObj=[0, 0, 0], flags=p.LINK_FRAME)
            p.applyExternalTorque(boxId, -1, torqueObj=[5, 5, 0.1], flags=p.LINK_FRAME)

    # ------------------------------------------------
    # ★ シミュレーションステップ
    # ------------------------------------------------
    if not is_paused:
        
        if boxId is not None:
            # フラグに応じてトルクを切り替え
            # ONなら 0.15Nm, OFFなら 0Nm
            target_torque = 0.15 if is_motor_on else 0.0
            
            p.setJointMotorControl2(boxId, 0, p.TORQUE_CONTROL, force=target_torque) # X軸
            p.setJointMotorControl2(boxId, 1, p.TORQUE_CONTROL, force=target_torque) # Y軸
            p.setJointMotorControl2(boxId, 2, p.TORQUE_CONTROL, force=target_torque) # Z軸

        p.stepSimulation()

    # GUI更新ウェイト
    time.sleep(1./240.)

p.disconnect()

実行結果

このスクリプトを実行すると、デジタルツインが召喚されます。

  • 出現: 何もない空間で Enter キーを押すと、上空からCubliが「ストン」と落ちてきます。
  • 静止: 荒ぶることなくピタリと着地しました。
  • 回転: mキーを押してモーターにトルク指令を送ると、金色のホイールがギュイーンと回り出します。

成功の証:反作用トルク

動画をよーく見てください。
ホイールが回転し始めた瞬間、白いボディが逆方向(ホイールと反対向き)に回転しようとしているのが分かりますか?

これぞ 「作用・反作用の法則」
物理エンジンが正しく機能し、回転トルクがボディに伝わっている証拠です。この反動を利用して姿勢を制御するのが、倒立振子の原理そのものです。

5. まとめと次回予告

本日の成果

  • Fusion 360のデザインデータを、物理法則を持つロボットとしてPyBullet内に召喚しました。
  • STLの座標ズレと重心問題を解決し、安定したシミュレーション環境が整いました。

次回予告 [Day 5]

器(ハードウェア)は完成しました。次は魂(ソフト)を入れます。
いよいよ 「制御理論」 の出番です。

明日は、このロボットを、 PID制御コード でピタリと直立させてみせます!


アドベントカレンダー参加中
STM32×AIで「3軸倒立振子」を作る25日間(ひとりアドカレ)Advent Calendar 2025

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?