CADで設計したロボットをシミュレーションする [Fusion360 x Unity]

# 1. はじめに

自分でロボットを設計してラジコンのように動かせたら楽しいですよね。実際のものを作るのは少し大変かもしれませんが、実際に作らなくても、CADで設計さえすれば、Unityで動かすことができます。

この記事は、設計したロボットのデータからスタートし、ロボットをUnity に読み込み、キーボード操作で動かすところまでをまとめたものです。3Dデータはgithub から使用可能です。

自分、まだまだUnityは不慣れで、こうやった方がよい的なところはあるかもしれませんので、ご了承ください。コメントいただけると幸いです。

![fingalone_unity_short2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/98d64a6a-4047-0ddf-e864-d6721a89ae5a.gif)

参考にさせていただいた記事
- https://qiita.com/yanosen_jp/items/3c43474dd8c1ee958a79

#2. Fusion360 で作ったロボットの3Dデータ
[Fusion360](https://www.autodesk.co.jp/campaigns/design-now)は高性能な3DCADソフトです。非商用目的で個人で使う場合には、無料で使用することができます。

下のキャプチャは、Fusion360で作ったロボットのモデルです。3つのサーボ(sg90)で動くことを想定しています。指っぽいのでフィンガロン（Fingalone）と呼ぶことにします。背中の赤と緑の四角は、ここでは、ただの模様だと思ってください。

![fusion.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/b3e9323f-13be-0be8-7e22-deebf860de87.png)


Fusion360 で設計したデータを、Objファイルでエクスポートしました。エクスポートしたファイル名は、Fingalone.obj としました。

![fusion2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/5694dc42-f62b-3c6c-2471-08a041634fc2.png)

このobjデータは[github](https://github.com/itoshin-tech/fingalone/tree/master/obj_data)に置きましたので、（パーツの名前の付け方がぐたぐたですが、ダウンロードして使ってください。Fingalone.obj です。

https://github.com/itoshin-tech/fingalone/tree/master/obj_data

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/c6ae7611-a541-c986-0f1c-df43da59fedc.png)

# 3. CADデータをUnityに取り込んで着色

[Unity](https://unity.com/ja)は、ご存じの通り、2Dや3Dのアプリを開発するソフトです。物理シミュレーターが使えます。これも無料で始めることができます。

まず、Unityを開いて、New を選択、Project name はなんでもよいですが、とりあえず Fingalone とし、Template は 3D　を指定し、Create projectを押します。

作業画面が開いたら、Assets という場所に Fingalone.obj をドラッグし、Unity に反映させます。

![hp01_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/72560ea2-08a5-3c0c-a7c8-ba22e6e5499f.png)

Assets内に現れたFingalone のアイコンを、画面左側のHierarchy にをドラッグします。すると、モデルが画面に現れます。なんと、生まれたてのカブトムシのように真っ白です。

![hp02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/4b54ab20-f1b6-2582-369c-0c8a9fe3ed44.png)

Unity内でのモデルは想定よりも巨大なものになっています（mm が m として解釈されている？）。

とにかくこのままでは操作しずらいので、scale を 0.01 にします。このくらいがちょうどよいでしょう。

![hp03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/71848ced-9393-0969-450b-5ad1e69fde2e.png)

次は、Fingalone のパーツを一緒に動く者同士で4つのグループに分けるため、Fingalone の階層構造を以下の操作でバラバラにします（もしかしたら、この操作は不要なのかもしれません）。

![hp04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/f89ace2f-0f45-08a6-0d53-34864db08ac8.png)

グループの入れ物として、Finger1～4 を作成します。

![hp05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/e700b8a8-bf7f-4459-aa39-8def36adedf9.png)

一緒に動くパーツ同士を同系色でまとめると、グループ分けがしやすいので、色を付けます。まず、以下の操作で、青色のmaterial を作ります。

![hp06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/1367cbfc-cba7-c9a4-d7b6-2c7774262658.png)

作った青のmaterial を目的のパーツまでドラッグすると、色を付けることができます。

![hp07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/855785dd-bbda-0313-13dd-6194fd58c489.png)

こんな感じで、色々な色のmaterial を作って、パーツに色を付けました。同じグループは、同系色でまとめました（モーターはグループに関係なく青にしました）。

![hp08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/0e5aec9f-eb0f-22e6-016b-9b2296dd3151.png)

次に、Hierarchyのパーツを、Finger1-4 にドラッグすることで、グループ分けをしました。ボルトとナットも忘れずに各グループに分けました。

![hp09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/a05aa850-b8b0-aedc-060b-a3734a670ad8.png)

これで、着色とグループ分けは完了です。

![hp10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/02006ebe-c75e-511c-f930-a181efab53e2.png)

# 4. 関節と剛体の特性を定義する

CAD上では関節も定義していましたが、Objファイルにはその情報はないようですので、Unity 上で新たに定義していきます。

まず、Finger1 に Hinji joint を追加します。Hinji joint とは、ドアの蝶番のような構造をもったjointですが、モーターの軸として使用します。

![hp11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/9f709b86-6002-c709-5700-b706fe2e8f2b.png)

Hinji joint の位置と方向を指定します。点線で囲んだ茶色の矢印が関節位置にくるように合わせます。

![hp12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/3b9a1503-1eb4-1238-4b24-b74f43c8fc74.png)

同様に、Finger2 にも Hinji joint を追加。

![hp13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/6aee1d0b-e9f5-5a0c-2d96-90d3d372da2d.png)

Finger3 にもHinji joint を追加です。

![hp14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/6f96b177-8643-4413-f85b-4eb94fe997bb.png)

最後のパーツのFinger4 には、Hinji joint は追加せず、Rigidbody （剛体の性質）を追加します。ちなみに、Finger1～3 は、Hinji joint を追加した時に自動的にRigidbody も追加されています。Hinji を使うには Rigidbodyが必須ということなのでしょう。

![hp15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/83db39a6-0b21-4588-25d9-d54f16a29344.png)

次に、Hinji の繋がり先を指定します。Connected Body の枠の右側に◎のような小さな記号があり、これをクリックしてFinger2を選びます。同様に、Finger2もFinger3も繋がり先を指定します。

![hp16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/c87bd02b-d98e-f43b-b78f-8ef6a3c3766b.png)

次は、衝突範囲の設定です。衝突判定をしてほしい箇所を直方体で指定するために、Finger1にBoxCollider を追加します。

![hp17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/78742482-b9e9-058b-70b1-c4cd340c4d3b.png)

Edit Collider を押して、直方体を以下のように先端部に合わせます。最初に、size に 10 などを入れると初期の直方体が大きくなるのでやりやすくなります。

![hp18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/06c36ab6-d9c3-8aa5-d1a9-8e94e99fcfd4.png)

同様に、Finger2にも図のようにBoxCollider をセットします。

![hp19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/87526edb-b30b-5282-32ee-d58f25fece02.png)

Finger3はこんな感じ。

![hp20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/6e1291b4-a996-c969-05b0-922bd2bd2a4f.png)

最後のFinger4 はこんな感じで。

![hp21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/dff10437-feb6-270d-af0a-d6f2dfa8d77f.png)

これでロボットのメインの定義は終わりですが、このままシミュレーションを開始すると下に落ちてしまいます。床を作る必要がありますので、Cube を作成して大きさと位置を調節します。

![hp22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/f3ae5415-6e55-9cab-4d7f-cc3bc8892eb1.png)

床にも色を付けます。

![hp23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/b6732adb-6908-2f0b-7ef4-e3e5ff563e4b.png)

これで完成です。開始ボタン（上の右向き三角）を押すと、Fingalone が床にドスンと乗るはずです。

![hp24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/debe9e09-f2f6-0a90-0b74-36a15e1f7383.png)

もうちょっと上から見たいので、カメラ位置を調節します。

![hp26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/45d78e90-de6f-657f-2041-11b438436780.png)

Fingalone の物理特性の大雑把な定義はこれで終わりです。あとは動かしながら調整していきます。

# 5. キーボードで動かすためのUnityプログラムを作る

最後に、キーボードでFingalone を動かすためのプログラムを作ります。以下のような操作を想定します。

---------
**動かし方**

- [u, j] 第一関節のモーター
- [i, k] 第二関節のモーター
- [o, l] 第三関節のモーター
- [e, d, s, f]の連打、モーターを使った前後・左右旋回の移動
- [1, 2, 3] 特定のポーズ
- [↑, ↓, ← ,→]前後・旋回移動（モーターを使わない、インチキ移動）
- [space] 浮遊

---------




Assets で右クリック、Create > C# Script を選びます。

![hp25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/961dd48b-1bc9-6f07-8b84-36649ac9dc29.png)

作成されたC# のアイコンをダブルクリックすると、Visual Studio が起動します。キーボードでFingalone を動かすために以下のプログラムを作ってセーブ。
 
<pre class="lang:c# decode:true " title="control_pattern.cs" >
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class control_pattern : MonoBehaviour
{
    public GameObject Gameobject1;
    public GameObject Gameobject2;
    public GameObject Gameobject3;
    public GameObject Gameobject4;

    private HingeJoint joint;

    private Rigidbody rb1;
    private Vector3 init_pos1;
    private Quaternion init_rot1;

    private Rigidbody rb2;
    private Vector3 init_pos2;
    private Quaternion init_rot2;

    private Rigidbody rb3;
    private Vector3 init_pos3;
    private Quaternion init_rot3;

    private Rigidbody rb4;
    private Vector3 init_pos4;
    private Quaternion init_rot4;

    private float dA = 5;

    private HingeJoint hinge1;
    private float A1 = 0;
    private float A1_min = -90;
    private float A1_max = 90;

    private HingeJoint hinge2;
    private float A2 = 0;
    private float A2_min = -90;
    private float A2_max = 90;

    private HingeJoint hinge3;
    private float A3 = 0;
    private float A3_min = -90;
    private float A3_max = 90;

    private float[] pat_f1 = { 0, -120, -150 };
    private float[] pat_f2 = { 0, -40, 90 };
    private float[] pat_f3 = { 0, 0, 0 };
    private int i_f = 0;
    private int n_f = 3;

    private float[] pat_b1 = { -110, -110, 0 };
    private float[] pat_b2 = { 90, 0, 0 };
    private float[] pat_b3 = { 0, 0, 0 };
    private int i_b = 0;
    private int n_b = 3;

    private float[] pat_l1 = { 0, -40, -40, 0 };
    private float[] pat_l2 = { 90, 0, 0, 90 };
    private float[] pat_l3 = { -60, -60, 60, 60 };
    private int i_l = 0;
    private int n_l = 4;

    private float[] pat_r1 = { 0, -40, -40, 0 };
    private float[] pat_r2 = { 90, 0, 0, 90 };
    private float[] pat_r3 = { 60, 60, -60, -60 };
    private int i_r = 0;
    private int n_r = 4;

    // Start is called before the first frame update
    void Start()
    {
        rb1 = Gameobject1.GetComponent&lt;Rigidbody&gt;();
        init_pos1 = rb1.transform.position;
        init_rot1 = rb1.transform.rotation;

        rb2 = Gameobject2.GetComponent&lt;Rigidbody&gt;();
        init_pos2 = rb2.transform.position;
        init_rot2 = rb2.transform.rotation;

        rb3 = Gameobject3.GetComponent&lt;Rigidbody&gt;();
        init_pos3 = rb3.transform.position;
        init_rot3 = rb3.transform.rotation;

        rb4 = Gameobject4.GetComponent&lt;Rigidbody&gt;();
        init_pos4 = rb4.transform.position;
        init_rot4 = rb4.transform.rotation;

        hinge1 = Gameobject1.GetComponent&lt;HingeJoint&gt;();
        hinge2 = Gameobject2.GetComponent&lt;HingeJoint&gt;();
        hinge3 = Gameobject3.GetComponent&lt;HingeJoint&gt;();

    }

    // Update is called once per frame
    void ChangePos()
    {
        JointSpring hingeSpring = hinge1.spring;
        hingeSpring.targetPosition = A1;
        hinge1.spring = hingeSpring;

        JointSpring hingeSpring2 = hinge2.spring;
        hingeSpring2.targetPosition = A2;
        hinge2.spring = hingeSpring2;

        JointSpring hingeSpring3 = hinge3.spring;
        hingeSpring3.targetPosition = A3;
        hinge3.spring = hingeSpring3;
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.E))
        {
            i_f += 1;
            if (i_f &gt;= n_f) i_f = 0;
            A1 = pat_f1[i_f];
            A2 = pat_f2[i_f];
            A3 = pat_f3[i_f];
            ChangePos();
        }

        if (Input.GetKeyDown(KeyCode.D))
        {
            i_b += 1;
            if (i_b &gt;= n_b) i_b = 0;
            A1 = pat_b1[i_b];
            A2 = pat_b2[i_b];
            A3 = pat_b3[i_b];
            ChangePos();
        }

        if (Input.GetKeyDown(KeyCode.S))
        {
            i_l += 1;
            if (i_l &gt;= n_l) i_l = 0;
            A1 = pat_l1[i_l];
            A2 = pat_l2[i_l];
            A3 = pat_l3[i_l];
            ChangePos();
        }

        if (Input.GetKeyDown(KeyCode.F))
        {
            i_r += 1;
            if (i_r &gt;= n_r) i_r = 0;
            A1 = pat_r1[i_r];
            A2 = pat_r2[i_r];
            A3 = pat_r3[i_r];
            ChangePos();
        }

        if (Input.GetKey(KeyCode.Alpha1))
        {
            A1 = 0;
            A2 = 0;
            A3 = 0;
            ChangePos();
        }
        if (Input.GetKey(KeyCode.Alpha2))
        {
            A1 = -90;
            A2 = 90;
            A3 = 0;
            ChangePos();
        }
        if (Input.GetKey(KeyCode.Alpha3))
        {
            A1 = -90;
            A2 = -10;
            A3 = 0;
            ChangePos();
        }

        if (Input.GetKey(KeyCode.U))
        {
            A1 += dA;
            if (A1 &gt; A1_max) A1 = A1_max;
            ChangePos();
        }

        if (Input.GetKey(KeyCode.J))
        {
            A1 -= dA;
            if (A1 &lt; A1_min) A1 = A1_min;
            ChangePos();
        }

        if (Input.GetKey(KeyCode.I))
        {
            A2 += dA;
            if (A2 &gt; A2_max) A2 = A2_max;
            ChangePos();

        }

        if (Input.GetKey(KeyCode.K))
        {
            A2 -= dA;
            if (A2 &lt; A2_min) A2 = A2_min;
            ChangePos();
        }
        if (Input.GetKey(KeyCode.O))
        {
            A3 += dA;
            if (A3 &gt; A3_max) A3 = A3_max;
            ChangePos();

        }

        if (Input.GetKey(KeyCode.L))
        {
            A3 -= dA;
            if (A3 &lt; A3_min) A3 = A3_min;
            ChangePos();
        }


        if (Input.GetKey(KeyCode.Space))
        {
            rb4.velocity = new Vector3(0, 3, 0);
        }

        if (Input.GetKey(KeyCode.UpArrow))
        {
            rb4.transform.Translate(-0.1f, 0.0f, 0.0f);
        }

        if (Input.GetKey(KeyCode.DownArrow))
        {
            rb4.transform.Translate(0.1f, 0.0f, 0.0f);
        }

        if (Input.GetKey(KeyCode.LeftArrow))
        {
            rb4.transform.Rotate(0.0f, -5.0f, 0.0f);
        }

        if (Input.GetKey(KeyCode.RightArrow))
        {
            rb4.transform.Rotate(0.0f, 5.0f, 0.0f);
        }

        Vector3 pos = rb4.transform.position;
        if (pos.y &lt; -10)
        {
            rb1.transform.position = init_pos1;
            rb1.transform.rotation = init_rot1;
            rb1.velocity = new Vector3(0, 0, 0);

            rb2.transform.position = init_pos2;
            rb2.transform.rotation = init_rot2;
            rb2.velocity = new Vector3(0, 0, 0);

            rb3.transform.position = init_pos3;
            rb3.transform.rotation = init_rot3;
            rb3.velocity = new Vector3(0, 0, 0);

            rb4.transform.position = init_pos4;
            rb4.transform.rotation = init_rot4;
            rb4.velocity = new Vector3(0, 0, 0);
        }
    }
}</pre> 

C#のプログラム名をcontrol_patternとします。プログラム内でのclass 名とファイル名が一致していないとエラーが出るので注意が必要です。

このプログラムをFinger1にドラッグすると、右側のInspector にcontrol_pattern が追加されます。その欄にGameObject1-4 があるので、◎をクリックして、Finger1-4 を割り当てます。

![hp27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/4d2bfcc7-ffd4-c5d4-2957-0f06207f142a.png)

この状態で、カーソルキーでFingaloneを動かすることはできます。しかし、関節を動かすにはJointにバネの使用を登録する必要があります。

以下のように、Finger1のHinge Joint で、Use Spring にチェックを入れ、バネの強さ Spring 700, バネの遅さ Damper 50 とセット。Finger2もFinger3も同じようにセットします。これで関節が動きます。
  
![hp28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/bdc171bd-74b0-6c55-ab93-fe5a026bc548.png)

ここからは、思うように動くように物理パラメータを微調整していきます。動きの反動が大きいので、Finger4 のRigidbodyのmass を5として重くします。

![hp29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/fc0b5292-1583-7d00-983a-fa36507c6029.png)

指の先端がつるつるして進めないので、Finger1の先端の摩擦を強くします。Assets で右クリック、Create > Physic Material を作成します。"ゴム"と命名します。

![hp30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/2a60b440-958a-7af9-9ba4-e63599ea7911.png)

作成した"ゴム"のアイコンを選択し、Dynamic Friction = 1、Static Friction = 1とセットして摩擦を最大にします。

![hp31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/051fd6b5-9d2b-c540-0235-826f6818b1ca.png)

Finger1 の BoxCollider のMaterial に、◎をクリックしてさっき作った"ゴム"を選択します。

![hp32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/97368444-a967-c88b-9500-3ee2b663fae7.png)

一方Finger4の部分は、よく滑るように、摩擦を小さくします。

![hp33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/0ebfbd79-bc27-148a-cc79-d25862a010eb.png)

以上の設定で上の実行ボタン（右向き三角）を押すと、キーボード操作で自由にFingalone が動きます。

![hp35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/d1681a01-3519-eaf4-b9b0-ccd3bc84e95b.png)

以下はおまけです。消しゴムロボットも作って対戦遊びをしているところです。

![fingalone_vs_keshigomu.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/86fb1253-3de9-0bf5-57ed-014e769c8601.gif)

実体にしてラズパイで動かしたものはこちらでまとめています。
http://itoshi.main.jp/tech/fingalone/intro/

![fingalone_QR2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/454bc05f-fac7-28ae-4310-08abe6ddfd41.gif)