【MotionBuilder】Python SDK 入門 第5回 『スクリプト例と解説』

2024/12/30に公開

この記事は、Python SDK 入門 の第5回目の記事です。

https://zenn.dev/nadegata_memo/articles/77a0fbce3b3387#記事の構成

今回は様々なスクリプトの簡単な例を示していきます。

0. 書けることを増やすために


type() を用いて扱っているデータの型を適宜確認するのは重要で、

「型(クラス)が分かればドキュメントで検索ができる」
「検索ができればメンバが分かる」
「メンバが分かれば実際に試せる」
「実際に試したらさらにデータが得られる」
「データが得られればその型を調べる」
「型が分かれば…(以下略)」

という過程でSDKの知識やスクリプトを書く経験を増やせるからです。

また、Property についてその種類を確認したいのは、Property = <some value> という記述で Property を編集可能なのか、それとも値を設定する特別な関数が必要なのかが変わるからです。

# Read Only / Read Write Property の例

from pyfbsdk import*
model = FBModelCube("testcube")

# Show Property は Read write Property
model.Show
>>> 
True

model.Show = False # Property は編集可能

# UniqueColorId Property は Read Only Property
model.UniqueColorId
>>>
FBColor(0.643137, 0, 0)

model.UniqueColorId = FBColor(0.2, 0, 0) # Property は編集不可、エラーが出る
>>>
Traceback (most recent call last):
  File "<MotionBuilder>", line 1, in <module>
AttributeError: property of 'FBModelCube' object has no setter



それではスクリプト例を書いていきます。
以下の例では、インポート文(from pyfbsdk import*)は省略しています。

1. 基本的な検索・データの取得

1.1. 名前でモデルを検索し取得

model_name = "target"
model = FBFindModelByLabelName(model_name)
print(model.Name)
>>>
target

FBFindModelByLabelName() は基本のメソッドでよくお世話になります。

1.2. Scene 内の各データの取得

FBScene クラスの各属性を使います。

lScene = FBSystem().Scene
for mat in lScene.Materials:
    print(mat.Name) # 全マテリアル名の表示
for cam in lScene.Cameras:
    print(cam.Name) # 全カメラ名の表示

1.3. Scene 内の全てのモデルの取得

def AccessChildrens(model:FBModel):
    if len(model.Children) > 0:
        for child in model.Children:
            print(child.Name)
            AccessChildrens(child)

AccessChildrens(FBSystem().Scene.RootModel)

FBModel 型を引数にとって、その子オブジェクトがあるのなら全ての子にアクセスする関数 AccessChildrens() を定義。再帰を組めば親子関係の最下位オブジェクトまで探索できるため、実行時の引数には Scene 最上位のモデル FBSystem().Scene.RootModel をとります。この RootModel が返す最上位のモデルは実体が無く、Viewerで確認することはできません。

RootModel

1.4. 可視状態になっているか

Visibility Property が無効になっている場合、Shift + S および Shift + H の表示・非表示(Show Property の True/False)の切り替えに関わらず、Viewer では非表示になります。

bone = FBFindModelByLabelName("LeftArm")
print(bone.Visibility)
>>>
False  # Shift + S 押してもViewerに表示されない

bone.Visibility = True  # Viewerに表示され、Show Propertyで表示状態を切り替え可能になる
bone.Show = False  # 非表示に

FBModel.IsVisible() でも Visibility Property の True/False を確認できます。ドキュメントでは「Public Member Functions」の項目に並べられているのですが、どういう訳か属性のように用いないとエラーが出ます。

bone = FBFindModelByLabelName("Reference")
if bone.IsVisible:
    print("the bone is visible\n")
>>>
the bone is visible

print(bone.IsVisible())
>>>
Traceback (most recent call last):
File "<string>", line 4, in <module>
TypeError: 'bool' object is not callable

1.5. 目的のクラス型なのか

確認する方法は3通りあります。

  • Is(<ClassName>_TypeInfo())
  • Is(<object>.TypeInfo)
  • type(<object>) == pyfbsdk.<ClassName>
model = FBFindModelByLabelName("LeftHandThumb1")

# <class Name>_TypeInfo() function
print(model.Is(FBModel_TypeInfo()))

# FBComponent.TypeInfo attribute
print(model.Is(FBModelSkeleton.TypeInfo))
    
# type() method
print(type(model) == pyfbsdk.FBModel)

>>>
True
True
False

type() は継承関係を考慮しません。

1.6. ベクトルの取得

属性で直接得る場合とPropertyListを介して得る場合の2通りあります。

bone = FBFindModelByLabelName("Mia:LeftArmRoll")

# 属性から得る
print(bone.Rotation)
>>>

# PropertyListから得る
rotationProp = bone.PropertyList.Find("Lcl Rotation")
print(rotationProp)
>>>

ただし、この方法で得るモデルの座標はローカル座標です。グローバル座標は GetVector() を使います。このメソッドは FBVector3d 型変数を引数にとり、その変数に取得した座標を保存します。したがって、ユーザーは事前にその変数を宣言しておく必要があります。

model = FBFindModelByLabelName("Mia:LeftArmRoll")
vec = FBVector3d()

model.GetVector(vec, FBModelTransformationType.kModelRotation) # Global Rotation
print(vec)
model.GetVector(vec, FBModelTransformationType.kModelTranslation) # Global Translation
print(vec)
model.GetVector(vec, FBModelTransformationType.kModelTransformation)
print(vec)

>>>
FBVector3d(-89.998, 0.00234162, -0.00645417)
FBVector3d(30.4288, 144.19, -2.56136)
FBVector3d(30.4288, 144.19, -2.56136)

kModelTranslationkModelTransformation の違いはよく知りません。
また、第2引数に False を指定するとローカル座標が得られます(デフォルトは True です)。

model.GetVector(vec, FBModelTransformationType.kModelRotation, False)
print(vec)
model.GetVector(vec, FBModelTransformationType.kModelTranslation, False)
print(vec)
model.GetVector(vec, FBModelTransformationType.kModelTransformation, False)
print(vec)

>>> 
FBVector3d(-0.824261, 2.57693e-05, 7.33129e-06)
FBVector3d(33.0882, 9.90851e-06, 1.47323e-05)
FBVector3d(33.0882, 9.90851e-06, 1.47323e-05)


2. Character 関連

2.1. Characterの取得

2通りあります。

# Scene内のCharacterのリストを得て、イテレータで指定
chara = FBSystem().Scene.Characters[0]

# 現在アクティブ状態(≒選択状態)のCharacterを得る
chara = FBApplication().CurrentCharacter

後者については、Character Controls Window が表示されていない場合(Scripting Layout など)、Scene Browser 等で character が選択状態になっていないと FBCharacter型オブジェクトを返してくれないので注意。characterが得られたかどうかの条件分岐については以下の通り。

# Charactersを用いた場合
charaList = FBSystem().Scene.Characters
if len(charaList) == 0:
    print("cannot get character")

# CurrentCharacterを用いた場合
chara = FBApplication().CurrentCharacter
if chara == None:
    print("cannot get character")

Scene 内に character が一つも無い場合、Characters[0] と書くと index out of range エラーが出てしまうので、まずはイテレータを使わずに character のリストを取得し、その要素数が0かどうかの判断を入れるとよいかと思います。

2.2. キャラクタライズ

キャラクタライズを実行するにはまずボーンの Mapping(割り当て)の作成が必要です。FBCharacter型のPropertyListより、「~Link」Property に対応する slot へボーンのモデルを登録します。

# Character作成
chara = FBCharacter("test")

# Mappingの作成
skeletonMap = {
    # Property名:str とボーン名:str の辞書
}

# 各Propertyへのボーンの登録
for key in skeletonMap.keys():
    LinkProp = chara.PropertyList.Find(key)
    bone = FBFindModelByLabelName(skeletonMap[key])
    LinkProp.append(bone)

キャラクタライズを実行します。

# キャラクタライズ実行
chara.SetCharacterizeOn(True)

# キャラクタライズ結果の判断
if chara.GetCharacterizeError() == "":
    FBMessageBox("Message", "Characterize succeeded!", "OK")
else:
    FBMessageBox("Caution", chara.GetCharacterizeError(), "OK")

GetCharacterizeError() は直前のキャラクタライズ処理のエラー結果(文字列)を返します。キャラクタライズに成功した場合はエラーが無いので空文字列が返されます。

2.3. Characterに登録されたボーンの取得

2通りあります。

  1. FBBodyNodeId を用いる方法

    # FBBodyNodeIdのvalues属性はenumとIdの辞書を返す
    for id in FBBodyNodeId.values.values():
        bone = chara.GetModel(id)
        if bone != None:
            print(bone.Name)
    

    よく似た名前のIdに FBSkeletonNodeIdFBBodyPartId がありますが、FBSkeletonNodeId は Actor の各部へのアクセスと MarkerSet への登録、FBBodyPartId はControl Rig 各部へのアクセスに使います。

  2. Property を用いる方法

    for prop in chara.PropertyList:
        if prop.Name.endswith("Link") and len(prop) > 0:
            print(prop[0].Name)
    

2.4. characterにリンクされたメッシュの取得

より正確には、character に登録されたボーンに対してウェイトが塗られたメッシュを取得します。

chara = FBCharacter("test")
meshList = FBModelList()
chara.GetSkinModelList(meshList) 

GetSkinModelList() は引数の FBModelList 型インスタンスに対して、character のスケルトンに結びついたメッシュモデルを登録していきます。


3. その他

3.1. アニメート関連

以下の3つを使います。

  • IsAnimatable() :アニメート可能か
  • IsAnimated() :アニメートされているか
  • SetAnimated(bool) :アニメートの切り替え
model = FBFindModelByLabelName("Face")
for prop in model.PropertyList:
    if prop.IsAnimatable():
        if not prop.IsAnimated():
            prop.SetAnimated(True)

SetAnimated(True) が「アニメートする」であるのに対し、SetAnimated(False) は "remove curves"、すなわち「FCurves を取り除く」という処理です。UI上で『A』ボタンを押した時に表示される警告にあるように、SetAnimated(False) はその Property のアニメーションデータを全て失うので注意。

3.2. グループ化

例えば、FBGetSelectedModels() で現在選択中のモデルを全て取得してみます。

mList = FBModelList()
FBGetSelectedModels(mList)

for model in mList:
    print(model.Name)

>>> 
Mia_right_leg
Mia_left_leg
Mia_head
Mia_body
Mia_left_pad1
Mia_left_pad2
Mia_left_pad3
Mia_right_pad1
Mia_right_pad2
Mia_right_pad4
Mia Hair
Mia_R_eye
Mia_L_eye

続いて、グループを作成し、モデルを登録します。
この時 第4回 で触れた ConnectSrc() を使います。

grp = FBGroup("meshes")
for model in mList:
    grp.ConnectSrc(model)  # グループへ追加

for elm in grp.Items:  # Items でグループ内要素のリストを得る
    print(elm.Name)

>>> 
Mia_right_leg
Mia_left_leg
Mia_head
Mia_body
Mia_left_pad1
Mia_left_pad2
Mia_left_pad3
Mia_right_pad1
Mia_right_pad2
Mia_right_pad4
Mia Hair
Mia_R_eye
Mia_L_eye

3.3. シェイプキーの取得

メッシュの Geometry 属性から取得します。

meshList = FBModelList()
chara.GetSkinModelList(meshList)
for mesh in meshList:    
    geo = mesh.Geometry
    for i in range(geo.ShapeGetCount()):
        name = geo.ShapeGetName(i)
        prop = mesh.PropertyList.Find(name)
        prop.SetAnimated(True)

シェイプキーに対応する Property をインデックスで指定して取得する FBGeometryクラスのメンバ関数はありません(名前で取得するものはありますが)。幸いシェイプキー名はそのまま Property名でもあるので、メッシュの PropertyList で Find() 関数を用いればシェイプキーの Property が取得できます。

3.4. Asset Browserよりアセット作成

FBCreateObject() を使います。

def ApplyShaders():
    # get all mesh of character
    chara = FBApplication().CurrentCharacter
    meshList = FBModelList()
    chara.GetSkinModelList(meshList)

    for mesh in meshList:    
        shader = FBCreateObject('Browsing/Templates/Shading Elements/Shaders', 'Flat', 'myShader')    
        mesh.ShadingMode = FBModelShadingMode.kFBModelShadingFlat 
        mesh.Shaders.append(shader) 

このスクリプトは character に結び付いたメッシュを全て取得し、Flat シェーダーを適用するものです。


次回

今回は、簡単なスクリプトの解説と使用例を紹介しました。ここで紹介しきれなかったスクリプト例については、bin/config/Scripts ディレクトリにある多くのサンプルを参照ください。

次回は一度 SDK の話題から離れて、便利な VScode の拡張機能について紹介します。

https://zenn.dev/nadegata_memo/articles/88804345399f7a

それでは、今回はここまで。最後までお読みくださりありがとうございました。

Discussion