1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BluetoothのないUbuntuで、MuseにBlenderのモデル作成・編集を任せてみた

1
Posted at

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は保持し、別名で保存されています。

作成したモデルです。

Museが手元のBlenderで作成したロボット

同じ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手順を試すための必須条件ではありません。

参考

1
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?