MotionBuilder Scripts - 階層を保ったモデルのコピー

に公開

目標

Scene内の複数のモデルを、その階層を保ったままスクリプトでコピーするメソッドを作成する。

使用例

alt text
Character のスケルトン構造をコピー

解説

モデルのコピー

MotionBuilder のUI上におけるコピー(Ctrl+C → Ctrl+V)に対応するメソッドとして、FBModel.Clone() が SDK で提供されている。

alt text
公式リファレンスより

FBModelおよびその継承クラス(FBModelNullFBModelSkeleton)でこのメソッドを用いれば、モデルのコピーができる。

model = FBModelCube("test cube")
model_clone = model.Clone() # コピー

print("%s : %s" % (model.Name, model))
print("%s : %s" % (model_clone.Name, model_clone))
# >>>
# test cube : <pyfbsdk.FBModelCube object at 0x0000023548166AC0>
# test cube 1 : <pyfbsdk.FBModel object at 0x00000235481B3990>

上記例では、コピーされたモデルの型が元のモデルから変化しているが、FBModelCube特有のもので動作には問題ないと思われる[1]

また、コピーされたモデルはデフォルトでUIに表示される。

print(model.Show)
print(model_clone.Show)
# >>>
# False
# True


ペアレント化と階層のコピー

FBModel.Parent プロパティはそのモデルの親にあたるモデルを保持する。
alt text
公式リファレンスより

このプロパティは Read Write Property であるから、親子関係を構成したい場合は「子」のモデルの Parentプロパティに「親」のモデルを格納すればよい。

cube_parent = FBModelCube("Parent Cube")
cube_child = FBModelCube("Child Cube")
cube_child.Parent = cube_parent

alt text
作成された親子関係

階層ごとモデルをコピーする方法として、メソッド「親モデルとそのコピーを引数に取り、その子モデルを参照して作成したコピーを親モデルのコピーにペアレント化」を再帰的に実行する方法が考えられる。

# 子モデルを再帰的にコピーする、メソッド
def CloneChildModels(model_parent: FBModel, model_parent_clone: FBModel) -> None:
    if len(model_parent.Children) > 0:
        for model_child in model_parent.Children:

            # Clone およびペアレント化
            model_child_clone = model_child.Clone()
            model_child_clone.Parent = model_parent_clone

            CloneChildModels(model_child, model_child_clone)

model_root = FBFindModelByLabelName("ルートのモデル名")
model_root_clone = model_root.Clone()

CloneChildModels(model_root, model_root_clone)

先程作成した Cube の階層について実行すれば、以下のようにコピーされた階層が得られる。

alt text
コピーされた Cube 階層


Transform のコピー

前節の方法では階層のコピーに成功したが、モデルの座標(Transform)はコピーされない。

モデルの Transfrom を取得・設定するには、FBModel.GetMatrix() および FBModel.SetMatrix() メソッドを使用する。

alt text
公式リファレンスより

alt text
公式リファレンスより

Transform の取得の際は引数に FBMatrix型インスタンスを使用するので、予め作成しておく。Transform 設定時に、再度このインスタンスを引数に取ればよい。

matrix_tr = FBMatrix()
cube_parent.GetMatrix(matrix_tr) # コピー元の Tranform 取得
cube_parent_clone = FBFindModelByLabelName("Parent Cube 1")
cube_parent_clone.SetMatrix(matrix_tr) # Tranform 設定

cube_child.GetMatrix(matrix_tr) # コピー元の Tranform 取得
cube_child_clone = FBFindModelByLabelName("Child Cube 1")
cube_child_clone.SetMatrix(matrix_tr) # Tranform 設定


Namespace 付きのモデルのコピーと名前の処理

namespace を持つモデルを FBModel.Clone() でコピーすると、namespace名に接尾辞が付く。

alt text
namespace を持つモデルを"スクリプトで"コピーした場合

ところで、MotionBuilder におけるコンポーネントの名前は、以下の3種類ある。

名前の種類 概要とSDKでの扱い
Name group と namespace を省いたオブジェクト名(例:「Hips」)
Name プロパティで取得。
LabelName namespace を含めたオブジェクト名(例:「Mia:Hips」)
LongName プロパティで取得。
FullName group と namespace を含めたオブジェクト名(例:「Model::Mia:Hips」)
FullName プロパティで取得。


Scene内モデルの取得でよく使用される(と思う)メソッド FBFindModelByLabelName() の「LabelName」は、まさに上記の LabelName のことである。 namespace 付きのモデルを扱う場合は、上記の使い分けに注意すること。

MotionBuilder のUI上では、モデルの名前を編集する際に namespace を直接指定することはできない。一方、SDK からは LongName プロパティを編集することで、直接 namespace の指定ができる。

model1 = FBFindModelByLabelName("TEST 1:Parent Cube")
model2 = FBFindModelByLabelName("TEST 2:Child Cube")

model1.LongName = "Parent Cube"  # namespace 無し
model2.LongName = "TEST 3:Child Cube" # namespace あり

alt text
スクリプトによる namespace付きのモデル名の処理

新規の namespaceを使って名前を変更した場合は、FBNamespace型のオブジェクトが作成され、Scene に追加される。一方で、namespace無しの名前を設定したとして、その namespace をもつモデルが無くなったとしても、namespace 自体は Scene から削除されない。


Namespace に関するメソッドなど

FBNamespaceクラスおよび FBSceneクラスには、namespace に関する処理を行うメソッドがある。ただし、処理に少々癖がある[2][3]ので、公式リファレンスの説明をよく読んで、実際の動作を確認していただきたい。

今回は、FBComponent.ProcessObjectNamespace() を使用する。

alt text
公式リファレンスより

単純に namespace を更新したい場合は、以下のように用いる。

# モデルの namespace名を返すメソッド
def GetNamespaceStr(model: FBModel) -> str:
    colon_index = model.LongName.find(":")
    if colon_index != -1:
        return model.LongName[0:colon_index]
    else:
        return ""

model = FBFindModelByLabelName("TEST:Parent Cube")
namespace_original = GetNamespaceStr(model) # 元の namespace の取得
namespace_updated = "TEST_updated"

if namespace_original:
    model.ProcessObjectNamespace(
        FBNamespaceAction.kFBReplaceNamespace,
        namespace_original,
        namespace_updated,
        False
    )

第3引数 pReplaceTo に指定した namespace名が Scene に無い場合は、その名前を使って自動で FBNamespaceオブジェクトが作成される。


備考

スクリプトを使わない場合は、該当のモデルを全選択し、「Ctrl+C → Ctrl+V」を実行すれば、階層と Transform を保持したままコピーができる。このとき、namespace も必要以上作成されることは無い。今回の記事で扱ったのは、あくまでスクリプトからこの処理を行いたい場合である。

P.S.もし SDK に同様の処理を行うメソッドが既にあれば教えてください。


ソースコード

https://github.com/Ndgt/Mobu_PluginBase_Python/blob/master/src/Scripts/DeepModelClone.py


脚注
  1. FBFindModelByLabelName()で取得した場合でも FBModel型と返されるため、デフォルトの動作である模様 ↩︎

  2. FBScene.NamespaceDelete() は namespace 単体を削除するのではなく、それを持つモデル自体も削除するメソッド ↩︎

  3. 使用されていないはずの namespace について、FBNamespace.GetContetCount() が0より大きい値を返す場合がある ↩︎

Discussion