【MotionBuilder】Python SDK 入門 第4回 『MotionBuilder内部の概念』

2024/07/27に公開

この記事は、Python SDK 入門の第4回目の記事です。
今回は、スクリプトを書く上で役立つMotionBuilder内部の概念について説明します。新しくソフトウェアを学習するにあたり、ただ操作を覚えるだけでなく、ソフト特有の固有名詞や概念をおさえると理解しやすくなる場合があります。

Scene

オブジェクトが存在する”空間”のようなもの

In MotionBuilder, the scene is the environment where your models exist.
A project can only contain one scene …
- FBScene Class Reference - Detailed Description

Unityにおける「Scene」、Unreal Engineにおける「Level」と同様の概念です。ただし、MotionBuilderのSceneは一つのfbxファイルにつき一つしか存在しません。このSceneについては簡単なスクリプトであれば普段意識することは特に無く、しばしばドキュメントに登場する「in the scene」という文言は「これはfbxファイル内」と読み替えて大きな問題はないと思います。

ちなみにMotionBuilder左下のこの表示はScene Browserといいます。おおよそ、Scene内に存在するものをツリー状に表示したものです。


Scene Browser

FBSceneクラスは、FBSystemクラスのScene属性でアクセスできます。例えばScene内の全てのマテリアルについて調べたい場合は、以下のようにします。

from pyfbsdk import*
for mat in FBSystem().Scene.Materials:
    print(mat.Name)
>>>
DefaultMaterial
N00_000_00_FaceMouth_00_FACE (Instance)
N00_000_00_EyeIris_00_EYE (Instance)
N00_000_00_EyeHighlight_00_EYE (Instance)
N00_000_00_Face_00_SKIN (Instance)
N00_000_00_FaceBrow_00_FACE (Instance)
N00_000_00_Body_00_SKIN (Instance)
N00_002_03_Tops_01_CLOTH_01 (Instance)
N00_000_00_HairBack_00_HAIR (Instance)
N00_000_Hair_00_HAIR (Instance)


Component

Sceneの構成要素

前章ではMaterialのみを表示していましたが、もっと大きなくくりとしてSceneの構成要素全てを表示することができます。

for comp in FBSystem().Scene.Components:
    print(comp.Name)
>>>
Scene
Core
Thread Manager
Evaluation Manager
Timing Manager
Command Manager
KTimeWarpManager
Profiler
Evaluation
Transport
Timer
Audio
Video
DefaultMaterial
__SYSTEM_DEFAULT_GEOMETRY_CAMERA_FORPICK__FOCUS(10.6772)_ROLL(0.0000)_ZFrustrum(14.9256)_XRadiusFactor(1.4926)_YRadiusFactor(1.4926)
Producer Perspective
(以下略)

上記の実行結果は起動直後のもので、表示されたcomponentは130個あります。また、一つモデルをインポートして再度実行すると、実に1131個のcomponentが表示されました。

FBComponentクラスはSDKのベースとなる、という記述の通り、殆どのクラスがこのFBComponentクラスを継承しています。


FBComponent Class Reference

ComponentがSceneの構成要素ということは、インポートしたモデルを構成するメッシュやボーンもComponentの一つにとなります。具体的には、

  • メッシュ:FBModelクラス
  • ボーン:FBModelSkeletonクラス

もComponentの一つです。Inheritance Diagramを見れば、これらがComponent(FBComponentクラス)に属する(派生する)こともお分かりいただけるでしょう。このページに書かれているスクリプトに登場していたName属性はFBComponentクラスで定義されているので、派生クラスであるFBMaterialクラスでもName属性を使用でき、前章ではこれを利用し各マテリアルの名前を表示させていました。

余談ですが、OpenReality SDKサンプルで頻繁に登場するFBCreate()FBDestroy()はFBComponentクラスで定義されており、上記のドキュメントでは「FBComponentを継承したオブジェクトは必ずFBComponent::FBCreate()FBComponent::FBDestroy()を定義しなければならない」と書かれています。特にMotionBuilderの画面で扱うモデルやコンストレイン等はみなFBBoxクラスを継承しているので、プラグイン開発の際は必ず上記2つの関数を宣言することになります。

Box

MotionBuilderが内部の計算の際に、Componentをノードのように解釈するもの

MotionBuilder's technology is built upon the concept of a 'box', essentially connecting inputs on one side, processing the information (calculations, transformations, etc.), and sending the data on to outputs on the other side.
- Boxes and Operators
A box is a fundamental building block in the application architecture.
All animatable elements are derived in some way from the main box class, either by deriving directly or owning a box.
- FBBox Class Reference

FBBoxクラスはScene内の全てのアニメート可能なオブジェクトの基底クラスです。自分の知る限り、C++においてアニメーションやリアルタイムのシステムを構築する上で必要なようで、Pythonで簡単なスクリプトを書く際に登場することは滅多にないです。

Animation Node

Boxが持つアニメーション評価の入出力の要素

Box全体においては、OpenReality SDKのboxesのサンプルにおいて、boxレベルでのAnimation Nodeがrelation constraintにおけるオペレータの入出力として確認できます。


boxvectorサンプルにおけるAnimation Nodeのinput側とoutput側の作成


作成されたオペレータ

Box内のアニメート可能なProperty(次章を参照)においては、以下のような関係があります。


Alexさんのチュートリアルより。これはCameraの場合

この関係はスクリプトでも確認できます。

# FBModelCubeの場合
from pyfbsdk import*

model = FBFindModelByLabelName("Cube")

for prop in model.PropertyList:
    if prop.IsAnimatable():
        if prop.IsAnimated() == False:
            prop.SetAnimated(True)

        node = prop.GetAnimationNode()
        print("Node Name:",node.Name, " Node Counts:", len(node.Nodes))
        for node in node.Nodes:
            print(node.Name)

>>>
Node Name: Visibility  Node Counts: 0
Node Name: Lcl Translation  Node Counts: 3
X
Y
Z
Node Name: Lcl Rotation  Node Counts: 3
X
Y
Z
Node Name: Lcl Scaling  Node Counts: 3
X
Y
Z


Property

Componentが持つ性質・情報

MotionBuidlerで普段扱うオブジェクトもComponentの一つなので「オブジェクトが持つ情報」と読み替えても問題ないと思います。座標(移動・回転・寸法)はもちろん、画面における表示設定や、メッシュの場合はそれがもつシェイプキーなども指します。

これらは画面右下のResources Window >> Propertiesから確認することができます。

PropertyiesタブはUnityのInspectorに対応します

スクリプトでPropertyにアクセスする手段として代表的なのは、FBComponentクラスのPropertyList属性を用いる方法です。全てのPropertyが得られます。また、各Propertyが保持するデータは一部を除いてData変数を用いてアクセスすることができます。

from pyfbsdk import*

# 名前でオブジェクトを探すときのお決まりの関数
model = FBFindModelByLabelName("Face")

for prop in model.PropertyList:
    try:
        print("{:<26}".format(prop.GetName()), prop.Data)
    except:
        print("{:<26}".format(prop.GetName()), "-----")
>>>
MultiTake                  1
DefaultKeyingGroup         0
DefaultKeyingGroupEnum     0
ManipulationMode           0
QuaternionInterpolate      False
Visibility                 True
Visibility Inheritance     True
Lcl Translation            FBVector3d(0, 0, 0)
Lcl Rotation               FBVector3d(-90, -180, -0)
Lcl Scaling                FBVector3d(100, 100, 100)
Primary Visibility         True
Casts Shadows              True
# (中略)
RotationMinZ               False
RotationMaxX               False
RotationMaxY               False
RotationMaxZ               False
RotationStiffnessX         0.0
RotationStiffnessY         0.0
RotationStiffnessZ         0.0
MinDampRangeX              0.0
MinDampRangeY              0.0
MinDampRangeZ              0.0
MaxDampRangeX              0.0
MaxDampRangeY              0.0
MaxDampRangeZ              0.0
MinDampStrengthX           0.0
MinDampStrengthY           0.0
MinDampStrengthZ           0.0
MaxDampStrengthX           0.0
MaxDampStrengthY           0.0
MaxDampStrengthZ           0.0
PreferedAngleX             0.0
PreferedAngleY             0.0
PreferedAngleZ             0.0
SetPreferedAngle           -----
InheritType                1
ScalingActive              False
ScalingMin                 FBVector3d(0, 0, 0)
ScalingMax                 FBVector3d(1, 1, 1)
ScalingMinX                False
ScalingMinY                False
ScalingMinZ                False
ScalingMaxX                False
ScalingMaxY                False
ScalingMaxZ                False
GeometricTranslation       FBVector3d(0, 0, 0)
GeometricRotation          FBVector3d(0, 0, 0)
GeometricScaling           FBVector3d(1, 1, 1)
PivotsVisibility           1
RotationLimitsVisibility   False
LocalTranslationRefVisibility False
RotationRefVisibility      False
RotationAxisVisibility     False
ScalingRefVisibility       False
HierarchicalCenterVisibility False
GeometricCenterVisibility  False
ReferentialSize            12.0
LookAtProperty             -----
UpVectorProperty           -----
Show                       True
Pickable                   True
Transformable              True
CullingMode                0
ShowTrajectories           False
# (中略)
Fcl_MTH_A                  0.0
Fcl_MTH_I                  0.0
Fcl_MTH_U                  0.0
Fcl_MTH_E                  0.0
Fcl_MTH_O                  0.0
Fcl_HA_Hide                0.0
Fcl_HA_Fung1               0.0
Fcl_HA_Fung1_Low           0.0
Fcl_HA_Fung1_Up            0.0
Fcl_HA_Fung2               0.0
Fcl_HA_Fung2_Low           0.0
Fcl_HA_Fung2_Up            0.0
Fcl_HA_Fung3               0.0
Fcl_HA_Fung3_Up            0.0
Fcl_HA_Fung3_Low           0.0
Fcl_HA_Short               0.0
Fcl_HA_Short_Up            0.0
Fcl_HA_Short_Low           0.0


Take

fbxファイル内のアニメーションデータの保存単位

およそ映画やドラマの撮影の”テイク”とほぼ同じ意味です。収録で繰り返しモーキャプを行った場合や、ゲームのモーションなどのとあるキャラクターのモーションについて複数のバリエーションがある場合にTakeを使うときがあります。


Scene Browserに表示されたTake

Character

モーションソースと使用モデルの仲介役となる独自のスケルトン規格の型


helpより。Characterとその15個の必須ノード

Characterは、Unityにおける「Humanoid」と同様の概念です。発音上は「キャラクター」ですが、一般的な言葉の意味(何らかの物語の登場人物の意)とは全く違います。ここでは外部モデルをインポートした状況を例に順番に解説していきます。

Characterというものを扱うにはFBCharacterクラスを使います。FBCharacter()コンストラクタを宣言するとCharacterが作成されますが、得られるのはただの”型”であり、特に働きをもちません。

from pyfbsdk import*
chara = FBCharacter("character_test")


作成されたCharacter

繰り返しになりますが、Characterは”型”なので「Characterが作成された」と言われた時に頭に具体的な”キャラ”が浮かんでしまっては困ります。上画像にはインポートされたモデルが映っていますが、MotionBuilder外の文脈では何らかの”キャラ”である可能性はあるものの、MotionBuilderからするとただのメッシュとボーンの集合体でしかありません。

作成されたCharacterのPropertyを調べると、Linkで終わるものがあることに気付きます。

for prop in chara.PropertyList:
    if prop.Name.endswith("Link"):
        print(prop.GetName())
>>>
ReferenceLink
HipsLink
LeftUpLegLink
LeftLegLink
LeftFootLink
RightUpLegLink
RightLegLink
RightFootLink
SpineLink
LeftArmLink
LeftForeArmLink
LeftHandLink
RightArmLink
RightForeArmLink
RightHandLink
HeadLink
LeftToeBaseLink
RightToeBaseLink
LeftShoulderLink
RightShoulderLink
NeckLink
LeftFingerBaseLink
RightFingerBaseLink
Spine1Link
Spine2Link
Spine3Link
# (以下略)

Characterにはslotと呼ばれる「ボーンの登録先」があり、この登録先を指定するのに用いるのが、「~Link」で終わるPropertyです。つまり「HipsLink, LeftUpLegLink, LeftLegLink, ...」のそれぞれのPropertyが保持するデータにアクセスすることは、すなわちslotに登録されたボーンにアクセスすることと同義です。

このCharacter内の各部位の名称に対応したPropertyを用いると、「インポートしたモデル中のとあるボーンがCharacter内のどの部位にあたるか」を指すMappingを作成することができます。

SkeletonMap = {
    # Property Name      :  Bone Name
    "HipsLink"           :  "Hips",
    "LeftUpLegLink"      :  "LeftUpLeg",
    "LeftLegLink"        :  "LeftLeg",
    "LeftFootLink"       :  "LeftFoot",
    "RightUpLegLink"     :  "RightUpLeg",
    "RightLegLink"       :  "RightLeg",
    "RightFootLink"      :  "RightFoot",
    "SpineLink"          :  "Spine",
    "LeftArmLink"        :  "LeftArm",
    "LeftForeArmLink"    :  "LeftForeArm",
    "LeftHandLink"       :  "LeftHand",
    "RightArmLink"       :  "RightArm",
    "RightForeArmLink"   :  "RightForeArm",
    "RightHandLink"      :  "RightHand",
    "HeadLink"           :  "Head",
}

for LinkPropName in SkeletonMap.keys():
    prop = chara.PropertyList.Find(LinkPropName)
    bone = FBFindModelByLabelName(SkeletonMap[LinkPropName])
    # Mappingを元に、各ボーンを対応するPropertyに追加する
    prop.append(bone)

もちろんこのボーンの割り当ての作業はUIで行うことができます。


画面でのMapping(割り当て)作業

ボーンの割り当てを行っても、まだそれはボーンをslotに登録しただけであり、それを確定・有効化させなければ意味がありません。

chara.SetCharacterizeOn(True)


Mappingの作成とその有効化

このMapping(ボーンの割り当て)を有効化することが、まさしくキャラクタライズと呼ばれる行為です。

定義したキャラクタ マップを有効にする動作をキャラクタライズと呼びます。モデルをキャラクタライズすると、モデルは単なるメッシュやスケルトンでなくなり、MotionBuilder でアニメートできるキャラクタと認識されます。
- キャラクタライズについて

上記のスクリプトではキャラクタライズに必須のボーンだけを割り当てていましたが、この必須のボーンが割り当てられていれば、他のLink(NeckやShoulder, ToeBase, Fingerなど)についても対応するボーンを割り当ててキャラクタライズを行うことができます。


次回

今回は、MotionBuilderの内部の特有の概念とその関連事項について解説しました。次回はよく使うクラスの解説と使用例を説明します。普段自身が書いているスクリプトやテンプレートを紹介していこうと思います。

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

Discussion