MotionBuilder のスケルトンを強制的にTポーズにさせるスクリプト

この記事は Akatsuki Games Advent Calendar 2023 の19日目の記事です。

昨日の記事はぐんそうさんの エンジニアチームの成長を目指した、輪読会の取り組み ~どんな形式で、どんな書籍を読んできたか?~ でした。 チームみんなで輪読会で、技術的な知識の向上だけでなく、チーム全体の共通の認識を作る行う取り組り、書籍の選定から輪読会、振り返りのサイクルをうまく回してる素敵なチームですね。

はじめに

MotionBuilder でキャラクタライズをする際に スケルトンを Tポーズにする必要があります。Character Controls を見ると Bone が正しく Tポーズになっていないと、警告が表示されます。

これを解決するには、 Bone を1つ1つTポーズになるように回転させる必要があります。数体であれば気合いの手動でやることもできますが、モーションキャプチャーで撮影したモーションデータをキャラクタライズする場合など、数十体行う必要がある場合手動では到底無理です。そのため、選択したスケルトンを 強制的に Tポーズにするスクリプトを作りました。

MotionBuilder SDK

MotionBuilder SDK として c++python が提供されています。 c++python で提供されている API が若干異なり、c++ で提供されている API の方がよりネイティブな API になっています。

毎フレーム実行されるような処理を書くのであれば性能的に c++ を採用することになりますが、ちょっとした自動化ツールを作るのであれば、python で十分です。 c++ の場合毎回ビルドする必要がありますが、python の場合はスクリプトを書いて実行するだけなので、開発効率が良いです。

python を使用する際の注意点ですが、MotionBuilder のバージョンによって組み込まれている python のバージョンが異なります。MotionBuilder 2022 以降は python 3系ですが、それ以前は python 2系だったりします。

Python パッケージのインストール

bone の回転角度などを設定ファイルとして外部に定義していくのですが、設定ファイルを xml で書いていくのは辛いので yaml で書けるようにします。そのため、YAMLパーサーとして PyYAML を使用するのですが、MotionBuilder に組み込まれている python の pip を使用してインストールする必要があります。

MotionBuilder に python パッケージを入れる方法は 公式ドキュメント に記載されいますが、管理者権限で

mobupy -m pip install pyyaml

として python パッケージをインストールする必要があります。(今回は PyYAML ですが、他のパッケージをインストールする歳も同様 です。)

ただ、毎回 MotionBuilder のパスに移動してコマンドを実行するのは面倒なので、bat ファイルを作りました。

https://github.com/nasshu2916/MotionBuilderTools/blob/842c35f8916e6c48c92eb3896b5b7fca85e5b17f/setup.bat

Tポーズにするスクリプト

強制的にTポーズにさせるスクリプトの処理の流れは以下の感じになります。

  1. Tポーズにさせるスケルトンを取得する
  2. ケルトンの root bone を取得する
  3. root bone から再帰的に children を取得する
  4. children に対して、config でした角度になるように回転させる

MotionBuilder SDK に関しての情報はネットに少なく、個人的な感想になりますが、公式ドキュメントも読みやすいとは言い難いので、スクリプトを書いてる時間の大部分は MotionBuilder と仲良くなる時間になりました。

1. Tポーズにさせるスケルトンを取得する

MotionBuilder SDK で現在選択している node を取得する方法は以下の様になります。 FBModelList で空のリストを作成し、FBGetSelectedModels に渡すことで選択している node を取得します。

from pyfbsdk import *

models = FBModelList()
FBGetSelectedModels(models)

2. スケルトンの root bone を取得する

スクリプトを実行する node を全取得する都合上、スケルトンの root bone を取得する必要があります。 スクリプトの成約として、root bone(Hips) を選択し実行するルールにしても良いのですが、毎回 root bone を選択するのは面倒なので、スクリプトを実行すると自動的に root bone を取得するようにします。

現在の node の親に関する情報はFBModel の Parent() で取得できるので、 現在の model の parent を再帰的に辿っていくと取得できます。

parent の FBModel type が FBModelSkeleton でなくなったら、その bone が root bone になるので、再帰を終了します。

from pyfbsdk import *

def get_root_bone(bone):
    "再帰的に親をたどって root bone を取得する"

    if is_skeleton(bone):
        return __do_root_bone(bone)
    else:
        return None

def __do_root_bone(bone):
    parent = bone.Parent
    if is_skeleton(parent):
        return __do_root_bone(parent)
    else:
        return bone

def is_skeleton(model):
    return type(model) == FBModelSkeleton

3. root bone から再帰的に children を取得する

FBModel 子要素に関しては Children で取得できるので、引数で指定した bone の children を再帰的に取得していきます。

from pyfbsdk import *

def get_children(bone):
    "再帰的に node の children を取得する"

    children = []
    for child in bone.Children:
        children.append(child)
        children.extend(get_children(child))
    return children

4. children に対して、config でした角度になるように回転させる

  1. で取得した children に対して、config で指定した角度になるように回転させます。

bone の rotation を変更する方法ですが、回転させる bone(FBModel) の SetVector を実行することで回転させることができます。このとき、第2引数に FBModelTransformationType.kModelRotation を指定することで rotation の変更になります。デフォルトで FBModelTransformationType.kModelTranslation になっており、transform が変更されます。また、第3引数は global 座標か local 座標かを指定します。今回は global 座標に変更するので True を指定します。

逆に bone の rotation を取得する場合は GetVector を使用します。
始めに FBVector3d を作成し、GetVector に渡すことで、現在の rotation を取得することができます。SetVector と同様に第2引数に FBModelTransformationType.kModelRotation を指定し、第3引数に取得する座標が global 座標か local 座標かを指定します。 FBModel の中に GetVector の他に Rotation がありますが、こちらは local 座標で取得することになります。

current_rotation = FBVector3d()
# bone は回転させる FBModel
# 現在の回転角度(global 座標)を取得する
bone.GetVector(current_rotation, FBModelTransformationType.kModelRotation, True)
# 新しい rotation を定義する
rotation = FBVector3d( x座標, y座標, z座標)
# bone の rotation を global 座標に設定する
bone.SetVector(rotation, FBModelTransformationType.kModelRotation, True)

bone 名に関しては FBModel の Name で取得できるのですが、(Left|Right)Arm や (left|right)_arm、Arm(L|R) など、使用するモデルによって命名規則が様々です。 プロジェクト内で命名規則を定めていた場合は別ですが、命名規則ごとに config を作成するのは大変です。

今回作成したスクリプトでは全てパスカルケースに変換し文字列の比較を行い、キャメルケースやスネークケースの命名規則の問題を回避しています。

また、config に左右両方の bone 定義するのがめんどくさいので、左右どちらかの bone の rotation を設定し、もう片方は反転させるようにしています。

その他(UI など)

MotionBuilder の UI 作成は癖があり、説明するのが難しいので今回は割愛します(詳細はスクリプトを参考にしてください)。ボタン類に関しては定義している UI に対して、コールバックを設定していく形になります。

今回作成したスクリプトの UI に関するスクリプトは以下の部分です。

https://github.com/nasshu2916/MotionBuilderTools/blob/842c35f8916e6c48c92eb3896b5b7fca85e5b17f/Scripts/MBTools/set_bone_angle.py#L122-L193

また Python スクリプトの実行方法として MotionBuilder の Python エディタから実行できるのですが、毎回開く必要があります。 下記のリポジトリの PythonStartup/mbtools_memu_setup.py を MotionBuilder の PythonStartup フォルダに配置することで、起動時に自動的に上部メニューバーから実行できるようになります。

github.com


明日の Akatsuki Games Advent Calendar 2023 は Yuji Sugiyama さんの「難解プログラミング言語「FRACTRAN(フラクトラン)」を紐解く」です。お楽しみに!

最後に、アカツキゲームスでは一緒に働くエンジニアを募集しています。 カジュアル面談もやっていますので、気軽にご応募ください。

games.aktsk.jp