【MotionBuilder】Python SDK 入門 第4回 『SDKの予備知識』
この記事は、Python SDK 入門 の第4回目の記事です。
今回は、スクリプトを書く上で役立つ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 と同様のものです。
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 に相当します。
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を使うときがあります。
Scene Browserに表示されたTake
ちなみに、Take は(現在は)fbx形式特有ではなく、MotionBuilder 特有の呼称です。その正体は Animation Stack という FBX SDK の中の一つの概念で、Animation Layer の集まりを一まとめに扱う単位です。
1.5. Character
『モーションソースと使用モデルを仲介する、独自のスケルトン規格の型』
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")
作成された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上で行うことができます。
画面での Mapping(割り当て)作業
ボーンの割り当てを行っても、まだそれはボーンを slot に登録しただけであり、それを確定・有効化させなければ意味がありません。
chara.SetCharacterizeOn(True)
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 に大別される
-
全ての要素は接続可能な ”プラグ” として扱われる
接続可能なのは、ほとんどの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クラスの派生クラスのオブジェクトです。
Camera の Property
Property 欄の記号には以下の意味があります。
- Property名の右にある「A」が薄い灰色の時 Animated、暗い灰色の時 Not Animated
- Property名の右にある「K」が灰色の時はキーフレーム上に無く、赤色の時のフレームがキーフレーム
また、以下の点に注意してください。
- Animate されてない Property にはキーを登録できません
- Animate しない限りその Property にはコピーしたキーをペーストできません
- Animatable でない Property はそもそも Animate させることができません。
2.3. FBBox
『Scene 内の全てのアニメート可能なオブジェクトの基底クラス』
また、『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 の状態は以下の通り。
続いて、まだ 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 の状態は以下の通り変化します。
上記を踏まえて、以下の点を押さえておきましょう。
- Animate していない Property の AnimationNode を得ることはできない
- Animate していない Property は Relation Constraint に表示されない
次回
今回は、MotionBuilderの内部の特有の概念とその関連事項について解説しました。
次回は、簡単なスクリプトやテンプレートを紹介していこうと思います。
それでは、今回はここまで。最後までお読みくださりありがとうございました。
Discussion