Museで、自分のPCにある3Dモデルを操作できるか試しました。公式のMuse Gadget SDKをUbuntuに入れ、Blenderでロボットを作成。その後、保存したモデルを開いて片腕を上げるところまで動きました。
途中で詰まったのはBluetoothです。作業用PCにアダプターがなかったので、別のLinux PCのものをUSB/IPとSSHで一時的に借りました。ペアリング後はそれを返し、ネットワーク経由でBlenderを実行できています。
確認日は2026年11月3日です。環境構築と確認にはCodexを使い、Blenderの作成・編集スクリプトはMuseに書かせました。認証やスマホのペアリングは自分で操作しています。
既存の記事と今回の範囲
Qiitaには、すでにRaspberry PiでMuse Gadgetsを使う記事があります。MuseのVMで開発ツールを動かす記事も参考になります。
今回は、手元のUbuntu PCにあるBlenderを動かす構成と、そのPCにBluetoothがない場合の接続を記録します。一般的なMuseの紹介や、登録手順は省きます。
使った環境
| 項目 | 今回の環境 |
|---|---|
| 作業用PC | Ubuntu 24.04、Bluetoothなし |
| Bluetoothを借りたPC | 同じLAN内の別のUbuntu PC |
| アダプター | Intel 8087:0036。BLEと広告機能を確認 |
| Python | 3.12.3 |
| Muse Gadget SDK | 0.1.0 |
| SDKのコミット | d32ddd74fec5fb404c2d5be423044f2ea4895130 |
| Blender | 5.2.2 LTS |
| ペアリング | iPhoneのMuseアプリ |
Blenderはインストール済みのものを使いました。以下のパスは自分の環境に合わせて読み替えてください。
今回使った公式SDKはこちらのLinux版です。Linux用のデスクトップアプリを探すところから始めましたが、この用途にはLinux版Gadget SDKで接続できました。
Muse Codeも先に試していますが、以下のBlender操作に使ったのは一般のMuseとGadget SDKです。Muse Codeの利用枠と、Museの設定画面に出る追加トークンは分けて確認しています。この実験で追加トークンを何トークン使ったかは計測していません。
コマンドを実行するユーザーを分ける
SDKにはsystem.runがあり、Linux上でシェルコマンドを実行できます。普段のユーザーをそのまま渡すのは避けて、muse3dを作りました。
sudo useradd --system --user-group --create-home \
--home-dir /var/lib/muse3d --shell /usr/sbin/nologin muse3d
sudo chmod 700 /var/lib/muse3d
sudo install -d -m 700 -o muse3d -g muse3d /var/lib/muse3d/workspaces
id muse3d
muse3dにはsudo権限を付けていません。SDKのサービスはBluetoothと認証情報を扱うためrootで動き、コマンド実行時にmuse3dへ権限を落とします。
ただし、ユーザーを分けただけで完全なサンドボックスになるわけではありません。muse3dに読めるファイルや、利用できるネットワークへのアクセスは残ります。Blenderだけを許可する設定でもありません。
ペアリングを後回しにしてSDKを入れる
SDKの導入とBluetoothペアリングは分けて進めました。先にSDKだけ準備しています。
使ったコミットを固定して、Linuxのソースを取得します。
git init muse-gadget-sdk
cd muse-gadget-sdk
git remote add origin https://github.com/facebookincubator/muse-gadget-sdk.git
git sparse-checkout init --cone
git sparse-checkout set linux
git fetch --depth 1 --filter=blob:none origin d32ddd74fec5fb404c2d5be423044f2ea4895130
git checkout --detach FETCH_HEAD
linux/install.shを確認した後、次の指定で導入しました。
sudo bash linux/install.sh --from "$PWD/linux" \
--run-as muse3d --no-pair --yes
--no-pairでペアリングを開始せず、--run-asでコマンドを実行するユーザーを指定します。--yesはインストール時の確認に対する指定です。
この時点でmusegadget.serviceは起動します。ただ、サービスがactiveというだけではMuseとの接続完了にはなりません。
SDKトークンは端末から非表示で入力した
SDKトークンは公式のSDK tokens画面で、規約を確認して発行しました。
ここで使うmgst_から始まるトークンは、記事の最後に載せている招待コードとは別物です。SDKトークンは公開しません。
今回は端末でgetpassを使って入力し、/var/lib/musegadget/sdk_tokenへroot所有・権限600で保存しました。トークン自体をコマンドの引数には入れていません。
同じ流れを使う場合の入力用コードです。SDKを上記の場所へ導入済みであることが前提です。
sudo /opt/musegadget/venv/bin/python - <<'PY'
import getpass
import os
import tempfile
from pathlib import Path
from musegadget import config
os.environ[config.SDK_TOKEN_ENV] = getpass.getpass("Muse SDK token: ")
try:
token = config.sdk_token()
finally:
os.environ.pop(config.SDK_TOKEN_ENV, None)
if not token:
raise SystemExit("No token entered; nothing changed.")
directory = config.DEFAULT_STATE_DIR
fd, temporary = tempfile.mkstemp(prefix=".sdk_token-", dir=directory)
try:
with os.fdopen(fd, "w") as stream:
stream.write(token + "\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, directory / config.SDK_TOKEN_FILE)
finally:
Path(temporary).unlink(missing_ok=True)
print("SDK token saved.")
PY
sudo systemctl restart musegadget.service
入力は画面に表示されません。config.sdk_token()はSDKに含まれる形式チェックで、ここではトークンの失効や権限をオンライン確認しているわけではありません。
SDKトークンには専用の利用条件があります。SDKのソースコードのライセンスとは別なので、使う前に確認してください。
Bluetoothは別PCから一時的に借りた
作業用PCにはBluetoothがありませんでした。別のPCにはBLE対応のアダプターがあったので、次の構成にしました。
iPhoneのMuseアプリ
│ Bluetooth
▼
別PCのBluetoothアダプター
│ USB/IPをSSHトンネルに通す
▼
作業用Ubuntu PCのMuse Gadget SDK
スマホを近づける先は、アダプターが実際に付いている別PCです。
USB/IPの共有ポートは、送信側のファイアウォールでループバックからだけ接続できるようにしました。その上で、受信側からSSHのローカル転送を張っています。USB/IPのポートをLAN全体に開放する構成にはしていません。
受信側のコマンドは次の形です。user@bt-pcはBluetoothを持つPCのSSH接続先です。送信側でのUSB/IP共有と、ループバック以外を拒否する設定は済んでいる前提です。
# このSSHは別ターミナルで起動したままにする
ssh -NT -o ExitOnForwardFailure=yes \
-L 127.0.0.1:43240:127.0.0.1:43240 user@bt-pc
# 作業用PC側
sudo modprobe vhci_hcd
sudo usbip --tcp-port 43240 list --remote 127.0.0.1
sudo usbip --tcp-port 43240 attach --remote 127.0.0.1 --busid 1-10
sudo usbip port
1-10は今回のアダプターのbusidです。別の環境では一覧で対象を確認して読み替える必要があります。
送信側は一時スクリプトで共有し、終了時にUSB/IPのbind、起動したデーモン、一時的なファイアウォールルールを戻すようにしました。最初は5分で自動終了する設定でしたが、スマホを操作する時間も必要だったため、再試行では10分に延ばしました。
この共有手順は手間が増えます。手元にBLEと広告機能に対応するアダプターがあれば、作業用PCへ直接接続して進められます。今回は手元の別PCを使う方法が実際に通るか確認しました。
送信側で使った一時共有スクリプトも載せます。usbip、usbipd、iptablesが利用でき、usbip_hostをロードできるLinux環境が前提です。受信側にはusbipとvhci_hcdが必要です。今回のusbip-utilsは2.0でした。
送信側の一時共有スクリプト(10分で終了)
busid=1-10と8087:0036は今回のアダプターに合わせた確認値です。別の機器を扱わないよう、自分の環境の値を確認してから変更してください。/tmp/muse-bt-share.shなどに保存して使います。
#!/usr/bin/env bash
# Temporary export of one Bluetooth adapter through an SSH tunnel.
set -euo pipefail
export PATH=/usr/sbin:/usr/bin:/sbin:/bin
busid=1-10
port=43240
device=/sys/bus/usb/devices/$busid
tag=muse-bt-share-demo
daemon_pid=
reject_added=0
allow_added=0
binding_started=0
allow_rule=(-i lo -p tcp --dport "$port" -m comment --comment "$tag" -j ACCEPT)
reject_rule=(-p tcp --dport "$port" -m comment --comment "$tag" -j REJECT)
[[ $EUID == 0 ]] || { echo 'Run with sudo.' >&2; exit 1; }
[[ $(cat "$device/idVendor") == 8087 && $(cat "$device/idProduct") == 0036 ]] || {
echo 'Expected Bluetooth 8087:0036 at 1-10 was not found.' >&2; exit 1;
}
[[ $(readlink -f "$device/$busid:1.0/driver") == /sys/bus/usb/drivers/btusb ]] || {
echo 'Bluetooth is already using another driver; leaving it unchanged.' >&2; exit 1;
}
[[ -z $(ss -H -ltn "sport = :$port") ]] || {
echo 'Temporary TCP port is already in use.' >&2; exit 1;
}
cleanup() {
trap - EXIT
set +e
if [[ -n $daemon_pid ]]; then
kill "$daemon_pid" 2>/dev/null
wait "$daemon_pid" 2>/dev/null
fi
if (( binding_started )) && [[ -d $device && $(cat "$device/idVendor") == 8087 && $(cat "$device/idProduct") == 0036 ]]; then
usbip unbind --busid="$busid"
if [[ -d $device ]]; then
for interface in "$device/$busid":*; do
if [[ -d $interface && ! -e $interface/driver ]]; then
printf '%s' "${interface##*/}" > /sys/bus/usb/drivers_probe
fi
done
fi
fi
if (( allow_added )); then iptables -w 5 -D INPUT "${allow_rule[@]}"; fi
if (( reject_added )); then iptables -w 5 -D INPUT "${reject_rule[@]}"; fi
echo "Temporary sharing stopped. Bluetooth driver: $(basename "$(readlink -f "$device/$busid:1.0/driver")")"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM HUP
modprobe usbip_host
# usbipd cannot select a listen address; keep this port reachable only on loopback.
iptables -w 5 -I INPUT 1 "${reject_rule[@]}"
reject_added=1
iptables -w 5 -I INPUT 1 "${allow_rule[@]}"
allow_added=1
usbipd -4 --tcp-port "$port" &
daemon_pid=$!
sleep 0.3
kill -0 "$daemon_pid"
binding_started=1
usbip bind --busid="$busid"
echo 'READY: Bluetooth 8087:0036 is shared for ten minutes through SSH only.'
echo 'Keep this terminal open. Ctrl-C restores Bluetooth immediately.'
sleep 600
受信側から送信側へ配置して実行する場合は、次の形です。sudoのパスワードはこのSSHの端末で入力しました。
scp muse-bt-share.sh user@bt-pc:/tmp/muse-bt-share.sh
ssh -t user@bt-pc 'sudo bash /tmp/muse-bt-share.sh'
READYが出た端末は開いたままにし、その後に上記のSSHトンネルとattachを実行します。中断するときは、この端末でCtrl-Cを押すと復元処理が走ります。
「接続中」で一度止まった
作業用PCで次を実行し、スマホのMuseで「設定 → デバイス → 開発者モード」を有効にして、右上の追加ボタンからMuseGadgetXXXXXXを選びます。
sudo musegadget pair
最初はスマホ側が「接続中」のまま進みませんでした。PC側には、スマホが接続してget_device_infoを要求し、SDKが応答したところまでログがありました。次の認証処理には進まず、その回の受付は時間切れになりました。
Museアプリを開き直し、Bluetooth共有とペアリング受付をやり直したところ、2回目は進みました。ログでは次を確認できています。
RX action: pairing_client_hello
TX pairing_ready
pairing confirmed (app consent)
RX action: wifi_scan (encrypted)
RX action: provision_v2 (encrypted)
TX status (encrypted #4): auth_ok
setup complete
Paired.
1回目が止まった原因は特定できていません。アプリを開き直すと必ず直る、という話にはできません。
ネットワーク選択では既存の接続を選びました。このPCはすでにオンラインなので、Wi-Fiパスワードの入力は不要でした。
ペアリング後はBluetoothを返せた
ペアリング完了後、受信側のusbip portで対象ポートを確認し、detachしました。今回の割り当ては0です。
sudo usbip detach --port 0
SSHトンネルと送信側の共有も終了しました。別PCでアダプターがbtusbに戻り、Bluetoothコントローラーが復帰したことまで確認しています。
その後、作業用PCのサービスログにregistered with the Museが出ました。Blenderの作成と編集は、Bluetoothを返した後に実行しています。
sudo musegadget doctorは状態確認に使えます。ただし、今回のようにアダプターを外した後はBluetoothの警告が出ます。また、doctorのサービス起動・トークン形式のチェックだけで、Museがコマンドを実行できるところまで確認したことにはなりません。
ロボットを作らせ、保存したモデルを編集した
実行ファイルの絶対パスと、専用ユーザーが書き込める作業先を渡しました。依頼内容は、おおむね次のとおりです。
登録済みLinuxガジェットのsystem.runとfile.writeを使い、ローカルのBlenderで青緑とオレンジのロボットを作る。丸い頭、目、胴体、両腕、両脚、アンテナ、台座を付ける。Blenderの元ファイル、GLB、プレビュー、生成スクリプトを保存する。
これは依頼内容の要約です。実際の指示では、実行先と出力先に加えて、処理を軽くする設定も渡しています。
- バックグラウンド実行
- CPU 2スレッド、同時実行1プロセス
- Cycles CPU、512×512、16 samples、denoise無効
- 外部素材や追加パッケージは使わない
Museはスクリプトを書き、Blenderを起動して、ファイルを保存しました。続けて「保存したモデルを開いて、片腕を上げる」という編集を依頼しています。
編集ではShoulderRを支点にArmRとHandRを回転させていました。入力のrobot.blendは保持し、別名で保存されています。
作成したモデルです。
同じBlenderファイルを読み込み、片腕を上げた編集版です。
| ファイル | 作成時 | 腕を上げた編集版 |
|---|---|---|
| Blenderファイル | 177,972 bytes | 177,693 bytes |
| GLB | 645,536 bytes | 645,644 bytes |
| プレビューPNG | 356,093 bytes | 358,539 bytes |
作成・編集のどちらも、ガジェット側の実行ログは終了コード0でした。プレビュー画像を開き、編集版で腕が上がっていることも確認しています。GLBはglTF 2.0として読み取れ、アップロード後にダウンロードしたファイルのSHA-256も一致しました。
検証した範囲は、この簡単なロボットの作成とオブジェクトの移動・回転までです。複雑なモデリングやリギングの性能は、まだ試していません。
やってみて分かったこと
Blenderを動かす準備より、最初のBluetooth接続に時間がかかりました。一方、接続後は、ファイル作成から既存モデルの編集までローカルで実行できました。
同じ構成を試すなら、実行ユーザーと作業フォルダを先に用意し、Blenderのパスを明示しておくと、どこで何が実行されたか追いやすくなります。今回はMuseの返答に加えて、サービスログ、実ファイル、画像を確認しました。
招待コード
Museをこれから試す方向けに、私の招待コードを載せておきます。
6D56QR
2026年11月3日に自分の招待画面で確認した条件は、参加後48時間以内に設定でコードを利用すると、利用者と紹介者の双方に10億Museトークンが付く、というものでした。このコードが適用されると、私にも特典が付きます。
登録は公式サイトから進め、利用できる地域、入力期限、特典の条件は、その時点の公式案内とアカウント画面で確認してください。このコードの利用は、上記のSDK手順を試すための必須条件ではありません。

