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

Character のスケルトン構造をコピー
解説
モデルのコピー
MotionBuilder のUI上におけるコピー(Ctrl+C → Ctrl+V)に対応するメソッドとして、FBModel.Clone() が SDK で提供されている。

公式リファレンスより
FBModelおよびその継承クラス(FBModelNullやFBModelSkeleton)でこのメソッドを用いれば、モデルのコピーができる。
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 プロパティはそのモデルの親にあたるモデルを保持する。

公式リファレンスより
このプロパティは Read Write Property であるから、親子関係を構成したい場合は「子」のモデルの Parentプロパティに「親」のモデルを格納すればよい。
cube_parent = FBModelCube("Parent Cube")
cube_child = FBModelCube("Child Cube")
cube_child.Parent = cube_parent

作成された親子関係
階層ごとモデルをコピーする方法として、メソッド「親モデルとそのコピーを引数に取り、その子モデルを参照して作成したコピーを親モデルのコピーにペアレント化」を再帰的に実行する方法が考えられる。
# 子モデルを再帰的にコピーする、メソッド
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 の階層について実行すれば、以下のようにコピーされた階層が得られる。

コピーされた Cube 階層
Transform のコピー
前節の方法では階層のコピーに成功したが、モデルの座標(Transform)はコピーされない。
モデルの Transfrom を取得・設定するには、FBModel.GetMatrix() および FBModel.SetMatrix() メソッドを使用する。

公式リファレンスより

公式リファレンスより
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名に接尾辞が付く。

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 あり

スクリプトによる namespace付きのモデル名の処理
新規の namespaceを使って名前を変更した場合は、FBNamespace型のオブジェクトが作成され、Scene に追加される。一方で、namespace無しの名前を設定したとして、その namespace をもつモデルが無くなったとしても、namespace 自体は Scene から削除されない。
Namespace に関するメソッドなど
FBNamespaceクラスおよび FBSceneクラスには、namespace に関する処理を行うメソッドがある。ただし、処理に少々癖がある[2][3]ので、公式リファレンスの説明をよく読んで、実際の動作を確認していただきたい。
今回は、FBComponent.ProcessObjectNamespace() を使用する。

公式リファレンスより
単純に 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 に同様の処理を行うメソッドが既にあれば教えてください。
ソースコード
Discussion