3Dマウスは高いので自作する。【Blender】【ラズパイPico／Arduino】

# はじめに
近年、VR/ARを活かしたビジネスからYouTubeを始めとしたエンタメ方面まで3Dモデリングの需要が急激に高まっています。
家庭用3Dプリンタも低価格で購入でき、個人宅で可能なものづくりの幅もかなり広がりました。
[Blender](https://www.blender.org/)などフリーソフトですら機能が大変充実し、[Fusion360](https://www.autodesk.co.jp/products/fusion-360)などの高機能な3DCADソフトウェアも学生・非商用利用では無料で使える時代です。
これだけ一気に需要が高まり、入門のハードルが下がった時代ですので、個人で／趣味で3Dを勉強し始めた方も少なくないでしょう。

そんな3D製作において、視点操作は作業時間の多くを占めていると思います。
それもそのはずで、その理由として2Dのモニタ上で3Dのデータを正しく認識し制御するには、同時に操作できる視点の動きの自由度があまりにも低いことが挙げられます。
マウスとキーボードの組み合わせでは平行移動（2次元）・拡大縮小（1次元）・回転（3次元）の動作をそれぞれ個別に行うことしかできません。
任意の視点に到達するまでに単純計算で全て同時に操作できた場合の3倍、細かな微調整を考えるとそれ以上の時間を費やすこともあるかもしれません。


この問題を解決する道具として、3Dマウスというものが存在します。
6DoF、つまり上記の平行移動(2)+拡大縮小(1)+回転(3)の合計6自由度を同時に操作可能という優れものです。
しかし3Dマウスは、[3Dconnexion 社](https://3dconnexion.com/jp/)が実質独占的に販売しており、
更に値段も[2万円弱～](https://3dconnexion.com/jp/shop/)と、個人で利用する入力機器としてはかなりの高額であるという問題があります。

なら自分で作ってしまいましょう。**（買え）無いなら作（創）る。DIYの基本です。**

### 余談
私自身は普段3Dconnexion 社の3Dマウスを使用しており、自作を試みて却って本家の完成度を実感させられました。
お金に余裕がありましたら是非購入を検討してみてください。慣れるまでは苦戦するかもしれませんが、良い製品です。
スカルプトなど左手ショートカットを頻繁に使用しない場合は小さいモデルで十分ですが、ボリゴンモデリング等ではショートカットボタンのたくさんついたモデルをおすすめします。

# どうやって作るの？
HID（Human Interface Device）として開発可能なマイコン（マイコンボード）を使用します。
Arduinoの一部のモデル（Micro等）やESP32、RaspberryPi Picoなどで可能です。

これらを使った自作キーボードづくりも非常に楽しいので一度試してみてください。
想像以上に簡単に作ることができて満足度も高いです。

## ライブラリって便利
自作キーボードや自作マウスつくる場合、ネット上には多くのサンプルコードやライブラリが存在します。
特にライブラリを使用すれば、簡単な左手デバイス程度なら20行に満たないプログラムで作成できます。
試しにArduinoIDEを使用して1ボタンショートカットキーボードを作成する例を示します。

```cpp: undo.ino
#include "Keyboard.h"

void setup() {
  pinMode(2, INPUT_PULLUP);
  Keyboard.begin();
}

void loop() {
  if (digitalRead(2) == LOW) {
    Keyboard.press(KEY_LEFT_CTRL);
    Keyboard.press('Z');
    while(digitalRead(2) == LOW) delay(100);
    Keyboard.releaseAll();
  }
}
```
このコードを書き込んで、マイコンボードの2番ピンとGNDの間にスイッチを接続するだけで「取り消し」ボタンの出来上がりです。

このように非常に簡単に好みの機能を搭載することが可能なのですが、これは1行目で読み込んでいる`Keyboard.h`というモジュールが裏で面倒事を一手に引き受けてくれているためです。

しかし私の知る限りでは、3Dマウスとして機能してくれるライブラリは存在しないため、もう少し踏み込んだ実装が必要になります。

## PCに3Dマウスとして認識させる

踏み込んだ実装と言っても、PCとの具体的な通信はライブラリ側で行ってくれるため、それほど難しいことを行う必要はありません。
PCに「私はこういうデバイスです」という紹介文と、それに準じた情報を送信するだけです。

以下がUSB-HIDの世界の”紹介文”（3Dマウスなど多次元デバイスのもの（https://pastebin.com/gQxUrScV より引用））です。
デバイスから送信する情報をどのように解釈・処理すればよいかといった内容がフォーマットに従って並べられています。
権利関係で何かあると恐いので（そちらは詳しくないので余計に）、途中省略しています。お試しの際は上記リンクより補完してください。

```
0x05, 0x01,           //  Usage Page (Generic Desktop)
0x09, 0x08,           //  0x08: Usage (Multi-Axis)
0xa1, 0x01,           //  Collection (Application)
0xa1, 0x00,           //  Collection (Physical)
0x85, 0x01,           //  Report ID
0x16, 0x00, 0x80,     //  logical minimum (-500)
0x26, 0xff, 0x7f,     //  logical maximum (500)
0x36, 0x00, 0x80,     //  Physical Minimum (-32768)
0x46, 0xff, 0x7f,     //  Physical Maximum (32767)
0x09, 0x30,           //  Usage (X)
0x09, 0x31,           //  Usage (Y)

        ・
        ・
        ・

0x25, 0x01,           //  Logical Maximum (1)
0x75, 0x01,           //  Report Size (1)
0x95, 32,             //  Report Count (24)
0x05, 0x09,           //  Usage Page (Button)
0x19, 1,              //  Usage Minimum (Button #1)
0x29, 32,             //  Usage Maximum (Button #24)
0x81, 0x02,           //  Input (variable,absolute)
0xC0,
0xC0
```

この”紹介文”には、多次元デバイスであること、X, Y, Z, ROTATION_X, ROTATION_Y, ROTATION_Z（各16bit） + ボタン（1bit × 24）の機能を有することなどが記述されているため、PCと通信が開始されるタイミングで送信することでPCに3Dマウスであると認識させることが可能となります。
後は、必要なタイミングで入力情報を送信するだけです。

## 具体的な実装
先のリンクでArduinoでの実装例は存在するため、ここではラズパイPicoを使用した場合を示します。
ラズパイPicoではPythonを使用できるため、より気軽にプログラムの書き込み・実行が行なえ、特に初心者におすすめです。値段も770円（Amazonでは1,100円）とお手軽に試すことができます。

先ず、ラズパイPicoにCircuitPythonをインストールします。
[ここ](https://circuitpython.org/board/raspberry_pi_pico/) からファームウェアをダウンロードし、「BOOTSELボタン」を押しながらPCに接続したラズパイPicoのルートディレクトリにドロップするだけでインストールは完了です。

次にプログラムを書き込みます。
`boot.py`はPCと通信が開始される前に実行され、通信が確立された後は`code.py`が実行されます。
`boot.py`で”紹介文”を準備しましょう。ここで準備した情報がPCとの通信開始時に送信されます。

```python: boot.py
import usb_hid

PROGMEM = (
    0x05, 0x01,           #  Usage Page (Generic Desktop)
    0x09, 0x08,           #  0x08: Usage (Multi-Axis)
    0xa1, 0x01,           #  Collection (Application)
    0xa1, 0x00,           #  Collection (Physical)
    0x85, 0x01,           #  Report ID
    0x16, 0x00, 0x80,     #  logical minimum (-500)
    0x26, 0xff, 0x7f,     #  logical maximum (500)
    0x36, 0x00, 0x80,     #  Physical Minimum (-32768)
    0x46, 0xff, 0x7f,     #  Physical Maximum (32767)
    0x09, 0x30,           #  Usage (X)
    0x09, 0x31,           #  Usage (Y)

        ・
        ・
        ・

    0x15, 0x00,           #  Logical Minimum (0)
    0x25, 0x01,           #  Logical Maximum (1)
    0x75, 0x01,           #  Report Size (1)
    0x95, 32,             #  Report Count (24)
    0x05, 0x09,           #  Usage Page (Button)
    0x19, 1,              #  Usage Minimum (Button #1)
    0x29, 32,             #  Usage Maximum (Button #24)
    0x81, 0x02,           #  Input (variable,absolute)
    0xC0,
    0xC0
)

# デバイス情報を作成
device = usb_hid.Device(
    report_descriptor=bytes(PROGMEM), # "紹介文"はbyte型に変換
    usage_page=0x05,
    usage=0x09,
    report_ids=(1, 2, 3),
    in_report_lengths=(6, 6, 1),
    out_report_lengths=(0, 0, 0),
)
usb_hid.disable() # 一度デフォルトで登録されているusb_hid.Deviceをリセット
usb_hid.enable((device,)) # 作成したインスタンスを登録

```

次に動作の設定を`code.py`に記述します。

```python: code.py
import usb_hid
import digitalio
from board import *
import time
import math


def send_command(device, rx, ry, rz, x, y, z):
    trans = [i & 0xFF for i in (x, x >> 8, y, y >> 8, z, z >> 8)]
    device.send_report(bytes(trans), 1) # 第2引数はレポートID
    rot = [i & 0xFF for i in (rx, rx >> 8, ry, ry >> 8, rz, rz >> 8)]
    device.send_report(bytes(rot), 2)


def setup():
    global device, button
    button = digitalio.DigitalInOut(GP28)
    button.direction = digitalio.Direction.INPUT
    button.pull = digitalio.Pull.DOWN
    device = usb_hid.devices[-1]


def loop():
    if something:
        x  = ...
        y  = ...
        z  = ...
        rx = ...
        ry = ...
        rz = ...
        send_command(device, x, y, z, rx, ry, xz)
    time.sleep(1e-3)


setup()
while True:
    loop()

```

たったこれだけでラズパイPicoを自作3Dマウスとして使用することができます。
`boot.py`と`send_command()`さえ用意すれば後はライブラリを使用した場合と大差ない簡単さです。

ただし、**Fusion360など一部のソフトウェアでは3Dconnexion社の3Dマウスでないと動作しないようなフィルターが施されているため、このままでは使用できない**点にご注意ください。**Blender**では使用可能です。
ここでは紹介しませんが、どうしてもFusion360などで使用したい場合は、PIDやVIDを偽装して無理やり3Dconnexion社の3Dマウスとして認識させることで使用が可能です。（ESP32ではできましたが、ラズパイPicoでできるかは分かりません。）

# 使用例

こちらは手軽に用意できる6DoFデバイスとして、VRコントローラを3Dマウスとして使用した例です。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Quest2コントローラ → ヘッドセット → AirLink<br>→ Unity → シリアル通信 → ラズパイピコ<br>→ HID(多次元入力) → Blender<br><br>すごい遠回りしてVRコントローラを3Dマウスとして使用してみた。<br>この精度で操作できれば何かしら用途がありそう。 <a href="https://t.co/8dn5cKnME6">pic.twitter.com/8dn5cKnME6</a></p>&mdash; みたき (@_mitaki_) <a href="https://twitter.com/_mitaki_/status/1608208352357781504?ref_src=twsrc%5Etfw">December 28, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# おわりに
ぜひ皆さんも自由な発想で自作3Dマウスを作って作業効率化に挑んでみてください。
自作デバイス界隈の賑わわんことを。