【MotionBuilder】Python SDK 入門 第4回 『SDKの予備知識』

2024/12/24に公開

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

https://zenn.dev/nadegata_memo/articles/77a0fbce3b3387

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


1. 基本事項

1.1. Scene

オブジェクトが存在する”空間”

In MotionBuilder, the scene is the environment where your models exist.

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

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

alt text
Scene Browser

一点注意すべきは、上図にある文字は全て「項目名」であることです。Scene に実際に存在しているのは、これらの項目名のツリーを展開して表示されるもののみです。したがって、例えば上図の状態では、Constraint も Material も Scene 中には存在しません。

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

from pyfbsdk import FBSystem
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)


1.2. 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
(以下略)

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


FBComponent Class Reference より

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

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

も Component で、FBComponentクラスの派生クラスです。この記事中のスクリプトに登場している Name 属性は FBComponentクラスで定義されているので、派生クラスである FBMaterial クラスでも Name 属性を使用でき、前節ではこれを利用して各マテリアルの名前を表示させていました。

余談ですが、OpenReality SDKサンプルで頻繁に登場する FBCreate()FBDestroy() はFBComponentクラスで定義されており、FBComponentを継承したクラスは必ずこの2つのメンバ関数を定義する必要があります。


1.3. Property

Component が持つ性質・情報

座標(移動・回転・寸法)はもちろん、画面における表示設定や、メッシュの場合はそれがもつシェイプキーなども指します。これらは画面右下の Resources Window >> Properties から確認できます。Unity での Inspector, Unreal Engine での Details に相当します。

alt text
Propertiesタブ

スクリプトで 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


1.4. Take

MotionBuilderのアニメーションデータの保存単位

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

alt text
Scene Browserに表示されたTake

ちなみに、Take は(現在は)fbx形式特有ではなく、MotionBuilder 特有の呼称です。その正体は Animation Stack という FBX SDK の中の一つの概念で、Animation Layer の集まりを一まとめに扱う単位です。


1.5. Character

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

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

3Dのソフトウェアの中には、リターゲット等の際の処理・設定を簡便にできるよう独自のリグシステムがあります。Unity では Mechanim Humanoid、Unreal Engine では Control Rig がそれにあたります。そして MotionBuilder(Maya, 3dsMax 含む Autodesk製品) では Human IK がその役割を担います。

リグシステムには大抵ソフト内で統一されたスケルトンの型が存在し、MotionBuilderでは HIK Character がそれに当てはまります。この HIK Character をしばしば略して Character と呼びます。

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

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

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

alt text
作成されたCharacter

繰り返しになりますが、Character は”型”なので「Character が作成された」と言われた時に頭に具体的な”キャラ”が浮かんでしまっては困ります。上画像にはインポートされたモデルが映っていますが、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です。Character という型にボーンを登録した後は、「HipsLink, LeftUpLegLink, LeftLegLink, ...」のそれぞれの Property が保持するデータにアクセスすることで、slot に登録されたボーンにアクセスできます。

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

# Mappingを元に、各ボーンを対応するPropertyに追加する

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])
    prop.append(bone)

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

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

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

chara.SetCharacterizeOn(True)

alt text
Mappingの作成とその有効化

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

The action of activating the defined Character map is called characterizing. When you characterize a model, it is no longer just mesh and a skeleton, MotionBuilder recognizes it as a character that can be animated.
- About Characterizing

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


2. その他

2.1. FBPlug

『Component-Property 間、また Component 間の接続を担うクラス』

  • SDK のオブジェクトは、FBComponent と FBProperty に大別される
    alt text

  • 全ての要素は接続可能な ”プラグ” として扱われる

    接続可能なのは、ほとんどのSDKの要素が FBComponentクラスと FBPropertyクラスから派生しており、これらの基底クラスが FBPlugであるためです。

  • Component 同士の接続は FBPlugクラスのメンバ関数で行う

    以下の使い分けがあるので注意。

    • FBPlug::ConnectSrc() : Child(子)にあたる Component を指定して接続
    • FBPlug::ConnectDst() : Parent(親)にあたる Component を指定して接続

参考


2.2. FBPropertyAnimatable

値を時間変化させられる Property の、基底クラス

Animate は値を時間変化させることを指し、Animate 可能であることを Animatable と呼びます。MotionBuilder においてモデルが動くことは、座標を表す Property が Animatable であり、かつ Property の Data がフレームの遷移に従って変化するということです。この Animatable な Property は全て FBPropertyAnimatableクラスの派生クラスのオブジェクトです。

alt text
Camera の Property

Property 欄の記号には以下の意味があります。

  • Property名の右にある「A」が薄い灰色の時 Animated、暗い灰色の時 Not Animated
  • Property名の右にある「K」が灰色の時はキーフレーム上に無く、赤色の時のフレームがキーフレーム


また、以下の点に注意してください。

  • Animate されてない Property にはキーを登録できません
  • Animate しない限りその Property にはコピーしたキーをペーストできません
  • Animatable でない Property はそもそも Animate させることができません。


2.3. FBBox

Scene 内の全てのアニメート可能なオブジェクトの基底クラス

alt text
FBBox Class Reference より

また、『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


2.4. Animation Node

Box および Animatable な Property が持つアニメーションデータの入出力地点


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

上図において、繋ぐ線ではなくその接続部が Node です。

FBPropertyAnimatable::GetAnimationNode() で Property の AnimationNode を取得できます。さらに、FBAnimationNodeクラスの Nodes 属性で得た Node のリストからは、分岐した先の AnimationNode を取得可能です。

ちなみに、FCurve 属性でその Property の Function Curve が得られます。

上図の関係をスクリプトで確認してみましょう。

from pyfbsdk import*

lCamera = FBCamera("Camera")
lCamera.Show = True
lConstraint = FBConstraintRelation("relation")
lConstraint.ConstrainObject(lCamera)

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

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

この時点での Relation Constraint 内の Box の状態は以下の通り。
alt text

続いて、まだ Animate されていない Property を Animate させます。

for prop in lCamera.PropertyList:
    if prop.IsAnimatable():
        if not prop.IsAnimated():
            prop.SetAnimated(True)
        node = prop.GetAnimationNode()
        if node:
            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
Node Name: Roll  Node Counts: 0
Node Name: FieldOfView  Node Counts: 0
Node Name: FieldOfViewX  Node Counts: 0
Node Name: FieldOfViewY  Node Counts: 0
Node Name: OpticalCenterX  Node Counts: 0
Node Name: OpticalCenterY  Node Counts: 0
Node Name: BackgroundColor  Node Counts: 3
X
Y
Z
Node Name: TurnTable  Node Counts: 0
Node Name: Motion Blur Intensity  Node Counts: 0
Node Name: NearPlane  Node Counts: 0
Node Name: FarPlane  Node Counts: 0
Node Name: BackPlaneOffsetX  Node Counts: 0
Node Name: BackPlaneOffsetY  Node Counts: 0
Node Name: BackPlaneScaleX  Node Counts: 0
Node Name: BackPlaneScaleY  Node Counts: 0
Node Name: FrontPlaneOffsetX  Node Counts: 0
Node Name: FrontPlaneOffsetY  Node Counts: 0
Node Name: FrontPlaneScaleX  Node Counts: 0
Node Name: FrontPlaneScaleY  Node Counts: 0
Node Name: Foreground Opacity  Node Counts: 0
Node Name: 2D Magnifier Zoom  Node Counts: 0
Node Name: 2D Magnifier X  Node Counts: 0
Node Name: 2D Magnifier Y  Node Counts: 0
Node Name: UseDepthOfField  Node Counts: 0
Node Name: FocusAngle  Node Counts: 0
Node Name: FocusDistance  Node Counts: 0

Relation Constraint 内の Box の状態は以下の通り変化します。

alt text

上記を踏まえて、以下の点を押さえておきましょう。

  • Animate していない Property の AnimationNode を得ることはできない
  • Animate していない Property は Relation Constraint に表示されない


次回

今回は、MotionBuilderの内部の特有の概念とその関連事項について解説しました。
次回は、簡単なスクリプトやテンプレートを紹介していこうと思います。

https://zenn.dev/nadegata_memo/articles/7823b8bd555995

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

Discussion