FBX SDK Python2020 の使用例1




まずはコードを御覧下さい。





import math
import FbxCommon
from fbx import FbxSkeleton,FbxNode,FbxDocumentInfo,FbxDouble3
from fbx import FbxDocumentInfo
import pprint

def CreateScene(lSdkManager, lScene):
    # Create scene info
    lSceneInfo = FbxDocumentInfo.Create(lSdkManager, "SceneInfo")
    lSceneInfo.mTitle = "Example scene"
    lSceneInfo.mSubject = "Illustrates the creation and animation of a deformed cylinder."
    lSceneInfo.mAuthor = "ExportScene01.exe sample program."
    lSceneInfo.mRevision = "rev. 1.0"
    lSceneInfo.mKeywords = "deformed cylinder"
    lSceneInfo.mComment = "no particular comments required."
    lScene.SetSceneInfo(lSceneInfo)
    lSkeletonRoot = CreateSkeleton(lSdkManager, skname="Skeleton")
    lScene.GetRootNode().AddChild(lSkeletonRoot)
    root_node = lScene.GetRootNode()
    skeleton_info = ExtractSkeletonInfoRecursive(root_node)
    pprint.pprint(skeleton_info)
    return lSkeletonRoot 
    

def CreateSkeleton(lSdkManager, skname=""):
    # Create skeleton root
    lRootName = skname + "Root"
    lSkeletonRootAttribute = FbxSkeleton.Create(lSdkManager, lRootName)
    lSkeletonRootAttribute.SetSkeletonType(FbxSkeleton.EType.eRoot)
    lSkeletonRoot = FbxNode.Create(lSdkManager, lRootName)
    lSkeletonRoot.SetNodeAttribute(lSkeletonRootAttribute)    
    lSkeletonRoot.LclTranslation.Set(FbxDouble3(0.0, -40.0, 0.0))
    # Create skeleton first limb node.
    lLimbNodeName1 = skname + "LimbNode1"
    lSkeletonLimbNodeAttribute1 = FbxSkeleton.Create(lSdkManager, lLimbNodeName1)
    lSkeletonLimbNodeAttribute1.SetSkeletonType(FbxSkeleton.EType.eLimbNode)
    lSkeletonLimbNodeAttribute1.Size.Set(1.0)
    lSkeletonLimbNode1 = FbxNode.Create(lSdkManager, lLimbNodeName1)
    lSkeletonLimbNode1.SetNodeAttribute(lSkeletonLimbNodeAttribute1)    
    lSkeletonLimbNode1.LclTranslation.Set(FbxDouble3(0.0, 40.0, 0.0))
    # Create skeleton second limb node.
    lLimbNodeName2 = skname + "LimbNode2"
    lSkeletonLimbNodeAttribute2 = FbxSkeleton.Create(lSdkManager, lLimbNodeName2)
    lSkeletonLimbNodeAttribute2.SetSkeletonType(FbxSkeleton.EType.eLimbNode)
    lSkeletonLimbNodeAttribute2.Size.Set(1.0)
    lSkeletonLimbNode2 = FbxNode.Create(lSdkManager, lLimbNodeName2)
    lSkeletonLimbNode2.SetNodeAttribute(lSkeletonLimbNodeAttribute2)    
    lSkeletonLimbNode2.LclTranslation.Set(FbxDouble3(0.0, 40.0, 0.0))
    # Build skeleton node hierarchy. 
    lSkeletonRoot.AddChild(lSkeletonLimbNode1)
    lSkeletonLimbNode1.AddChild(lSkeletonLimbNode2)
    return lSkeletonRoot

    
def ExtractNodeInfo(node):
    # ノードの名前を取得する
    node_name = node.GetName()

    # ノードの位置を取得する
    translation = node.LclTranslation.Get()

    # ノードの回転を取得する
    rotation = node.LclRotation.Get()

    # ノードのスケールを取得する
    scale = node.LclScaling.Get()

    # ノードの情報を辞書としてまとめる
    node_info = {
        "name": node_name,
        "translation": translation,
        "rotation": rotation,
        "scale": scale,
        "object":node
    }

    return node_info

def ExtractSkeletonInfoRecursive(node):
    # ノードの情報を取得する
    node_info = ExtractNodeInfo(node)

    # 子ノードの数を取得する
    num_children = node.GetChildCount()

    # 子ノードの情報を格納するリストを初期化する
    children_info = []

    # 子ノードの情報を取得してリストに追加する
    for i in range(num_children):
        child_node = node.GetChild(i)
        child_info = ExtractSkeletonInfoRecursive(child_node)
        children_info.append(child_info)

    # ノード情報に子ノードの情報を追加する
    node_info["children"] = children_info

    return node_info


# 使用例

    
save_filename="test_fbx.fbx"

lSdkManager,lScene = FbxCommon.InitializeSdkObjects()

lSkeletonRoot = CreateScene(lSdkManager,lScene)

lResult = FbxCommon.SaveScene(lSdkManager, lScene, save_filename)





簡単な解説。

  • 1.まず、FbxCommonで、マネージャーとシーンを作成します。

FbxCommon.pyはサンプルに入ってるので、使用すればいいと思います。

  • 2.で、シーンをクリエイトします。

シーンを作った後にシーンにオブジェクトを追加します。
CreateSceneのメソッドです。

  • 3.今回はボーンを追加。

CreateSkeletonで作成して、シーンの子として入れます。
CreateSkeletonで作ったボーンをCreateSceneメソッド内で使用してます。
で、
CreateSkeletonで作ったボーンはbuildしないといけないので、注意を。これも子として挿入していくだけです。
何にAddChildのメソッドを追加していくかか階層構造の基本です。

ボーン2本のFBXが出力されてると思います。


作ったオブジェクトの情報を抽出

で、最後に自分のしたかったことで、
一度作ったオブジェクトの情報を抽出したいことです。

オブジェクトの構造は階層構造なのを注意

味噌が、ツリー状でつまり階層構造なのです。

たんなる集合体ではないので普通のリストなどでは出なく、
つまり、オブジェクトのChildren構造を解析して再帰的に子の構造を抽出しながら全オブジェクトを出さないといけません。

なので、辞書などで管理すると良いでしょう。
出た辞書の結果がこちら。

{'children': [{'children': [{'children': [{'children': [],
                                           'name': 'SkeletonLimbNode2',
                                           'node_instans': <fbx.FbxNode object at 0x00000273AB9EB1C0>,
                                           'rotation': <fbx.FbxDouble3 object at 0x00000273AB9E8CA0>,
                                           'scale': <fbx.FbxDouble3 object at 0x00000273AB9E8940>,
                                           'translation': <fbx.FbxDouble3 object at 0x00000273AB9E88B0>}],
                             'name': 'SkeletonLimbNode1',
                             'node_instans': <fbx.FbxNode object at 0x00000273AB9E8790>,
                             'rotation': <fbx.FbxDouble3 object at 0x00000273AB9E9510>,
                             'scale': <fbx.FbxDouble3 object at 0x00000273AB9E9000>,
                             'translation': <fbx.FbxDouble3 object at 0x00000273AB9E9750>}],
               'name': 'SkeletonRoot',
               'node_instans': <fbx.FbxNode object at 0x00000273AB9EAC20>,
               'rotation': <fbx.FbxDouble3 object at 0x00000273AB9E8820>,
               'scale': <fbx.FbxDouble3 object at 0x00000273AB9E9630>,
               'translation': <fbx.FbxDouble3 object at 0x00000273AB9EA680>}],
 'name': 'RootNode',
 'node_instans': <fbx.FbxNode object at 0x00000273AB9EA9E0>,
 'rotation': <fbx.FbxDouble3 object at 0x00000273AB9EA4D0>,
 'scale': <fbx.FbxDouble3 object at 0x00000273AB9E9900>,
 'translation': <fbx.FbxDouble3 object at 0x00000273AB9E92D0>}
{'children': [{'children': [{'children': [{'children': [],
                                           'name': 'SkeletonLimbNode2',
                                           'node_instans': <class 'fbx.FbxNode'>,
                                           'rotation': <fbx.FbxDouble3 object at 0x00000273AB9E8DC0>,
                                           'scale': <fbx.FbxDouble3 object at 0x00000273AB9EB2E0>,
                                           'translation': <fbx.FbxDouble3 object at 0x00000273AB9E8700>}],
                             'name': 'SkeletonLimbNode1',
                             'node_instans': <class 'fbx.FbxNode'>,
                             'rotation': <fbx.FbxDouble3 object at 0x00000273AB9E92D0>,
                             'scale': <fbx.FbxDouble3 object at 0x00000273AB9E85E0>,
                             'translation': <fbx.FbxDouble3 object at 0x00000273AB9E8CA0>}],
               'name': 'SkeletonRoot',
               'node_instans': <class 'fbx.FbxNode'>,
               'rotation': <fbx.FbxDouble3 object at 0x00000273AB9E8280>,
               'scale': <fbx.FbxDouble3 object at 0x00000273AB9E8820>,
               'translation': <fbx.FbxDouble3 object at 0x00000273AB9EBF40>}],
 'name': 'RootNode',
 'node_instans': <class 'fbx.FbxNode'>,
 'rotation': <fbx.FbxDouble3 object at 0x00000273AB9E9D80>,
 'scale': <fbx.FbxDouble3 object at 0x00000273AB9E89D0>,
 'translation': <fbx.FbxDouble3 object at 0x00000273AB9E8C10>}
{'children': [{'children': [{'children': [{'children': [],
                                           'name': 'SkeletonLimbNode2',
                                           'object': <fbx.FbxNode object at 0x00000273AB9F9F30>,
                                           'rotation': <fbx.FbxDouble3 object at 0x00000273AB9EA3B0>,
                                           'scale': <fbx.FbxDouble3 object at 0x00000273AB9E9870>,
                                           'translation': <fbx.FbxDouble3 object at 0x00000273AB9EAC20>}],
                             'name': 'SkeletonLimbNode1',
                             'object': <fbx.FbxNode object at 0x00000273AB9F9EA0>,
                             'rotation': <fbx.FbxDouble3 object at 0x00000273AB9F9360>,
                             'scale': <fbx.FbxDouble3 object at 0x00000273AB9E91B0>,
                             'translation': <fbx.FbxDouble3 object at 0x00000273AB9F8D30>}],
               'name': 'SkeletonRoot',
               'object': <fbx.FbxNode object at 0x00000273AB9FA290>,
               'rotation': <fbx.FbxDouble3 object at 0x00000273AB9F9B40>,
               'scale': <fbx.FbxDouble3 object at 0x00000273AB9F8E50>,
               'translation': <fbx.FbxDouble3 object at 0x00000273AB9FA440>}],
 'name': 'RootNode',
 'object': <fbx.FbxNode object at 0x00000273AB9FA0E0>,
 'rotation': <fbx.FbxDouble3 object at 0x00000273AB9F9FC0>,
 'scale': <fbx.FbxDouble3 object at 0x00000273AB9F8820>,
 'translation': <fbx.FbxDouble3 object at 0x00000273AB9F8550>}

見やすくする

translationやrotationは単純に出力するとタプルですので、
下記のように展開してやれば

    node_info = {
        "name": node_name,
        "translation": tuple(translation),
        "rotation": tuple(rotation),
        "scale": tuple(scale),
        "object":root_node
    }


このように内部が出ます。



{'children': [{'children': [{'children': [{'children': [],
                                           'name': 'SkeletonLimbNode2',
                                           'object': <fbx.FbxNode object at 0x00000273AB9C4820>,
                                           'rotation': (0.0, 0.0, 0.0),
                                           'scale': (1.0, 1.0, 1.0),
                                           'translation': (0.0, 40.0, 0.0)}],
                             'name': 'SkeletonLimbNode1',
                             'object': <fbx.FbxNode object at 0x00000273AB9C5C60>,
                             'rotation': (0.0, 0.0, 0.0),
                             'scale': (1.0, 1.0, 1.0),
                             'translation': (0.0, 40.0, 0.0)}],
               'name': 'SkeletonRoot',
               'object': <fbx.FbxNode object at 0x00000273AB9C43A0>,
               'rotation': (0.0, 0.0, 0.0),
               'scale': (1.0, 1.0, 1.0),
               'translation': (0.0, -40.0, 0.0)}],
 'name': 'RootNode',
 'object': <fbx.FbxNode object at 0x00000273AB9C5480>,
 'rotation': (0.0, 0.0, 0.0),
 'scale': (1.0, 1.0, 1.0),
 'translation': (0.0, 0.0, 0.0)}


簡単に書き込みしてみる

どうやらset関数書き込みもできるようで、
スケールを
root_node.LclScaling.Set(FbxDouble3(0.5,0.5,0.5))
で設定してみます。
値はFbxDouble3などのオブジェクトじゃないと認識しないので気をつけて下さい。

辞書作成前に値を変更してみましょう。



def ExtractNodeInfo(root_node):
    # ノードの名前を取得する
    node_name = root_node.GetName()

    # ノードの位置を取得する
    translation = root_node.LclTranslation.Get()

    # ノードの回転を取得する
    rotation = root_node.LclRotation.Get()

    # ノードのスケールを取得する
    root_node.LclScaling.Set(FbxDouble3(0.5,0.5,0.5))
    scale = root_node.LclScaling.Get()
    

    # ノードの情報を辞書としてまとめる
    node_info = {
        "name": node_name,
        "translation": tuple(translation),
        "rotation": tuple(rotation),
        "scale": tuple(scale),
        "object":root_node
    }

    return node_info,root_node


ちゃんとセーブしよう

2つ目の戻り値で設定したroot_nodeを

lResult = FbxCommon.SaveScene(lSdkManager, root_node, save_filename)

でセーブしないと出力されませんので気をつけて下さい。

情報を書き込みされてます。

{'children': [({'children': [({'children': [({'children': [],
                                              'name': 'SkeletonLimbNode2',
                                              'object': <fbx.FbxNode object at 0x00000273AB9F9900>,
                                              'rotation': (0.0, 0.0, 0.0),
                                              'scale': (0.5, 0.5, 0.5),
                                              'translation': (0.0, 40.0, 0.0)},
                                             <fbx.FbxNode object at 0x00000273AB9F9900>)],
                               'name': 'SkeletonLimbNode1',
                               'object': <fbx.FbxNode object at 0x00000273AB9F95A0>,
                               'rotation': (0.0, 0.0, 0.0),
                               'scale': (0.5, 0.5, 0.5),
                               'translation': (0.0, 40.0, 0.0)},
                              <fbx.FbxNode object at 0x00000273AB9F95A0>)],
                'name': 'SkeletonRoot',
                'object': <fbx.FbxNode object at 0x00000273AB9F8940>,
                'rotation': (0.0, 0.0, 0.0),
                'scale': (0.5, 0.5, 0.5),
                'translation': (0.0, -40.0, 0.0)},
               <fbx.FbxNode object at 0x00000273AB9F8940>)],
 'name': 'RootNode',
 'object': <fbx.FbxNode object at 0x00000273AB9F9090>,
 'rotation': (0.0, 0.0, 0.0),
 'scale': (0.5, 0.5, 0.5),
 'translation': (0.0, 0.0, 0.0)}
Info: Deleted 3 object(s)


ボーンの回転などの情報編集は甘くない


一応、こんな感じで特定のオブジェクトだけ名前などや情報を変えられるらしいのですが、ボーンの構造などの回転に関しては難しくそんな単純ではないので、もうちょい勉強が必要みたいです。

    if node_name=='SkeletonLimbNode2':
        root_node.SetName("test")
        root_node.LclScaling.Set(FbxDouble3(1,1,1))
        rotation = FbxVector4()
        FbxVector4.AxisAlignmentInEulerAngle(FbxVector4(1, 0, 0), FbxVector4(0, 0, 1), FbxVector4(0, 1, 0), rotation)
        
        root_node.SetPostRotation(FbxNode.EPivotSet.eSourcePivot, rotation)
    # ノードの情報を辞書としてまとめる
    node_info = {
        "name": root_node.GetName(),
        "translation": tuple(translation),
        "rotation": tuple(rotation),
        "scale": tuple(scale),
        "object":root_node
    }


ネットで出回ってるのはSDK2019や2017なので結構間違ってるのでご注意を

例えば、
SetPostRotationにいれる引数は
FbxNode.EPivotSet.eSourcePivot, rotationなんですけど、
ネットやAIだと
FbxNode.eSourcePivotと書いてあったりします。

また、
lSkeletonRoot.SetPivotState(FbxNode.EPivotSet.eSourcePivot, FbxNode.EPivotState.ePivotActive)
を入れないとボーンの回転は正しくセットできないのですが、
FbxNode.EPivotState.ePivotActiveも

FbxNode.ePivotActiveと

書いてあったります。

書いてる人のを参考にしたりとか、C++やUNITYのコードを参考にしながら作って行ったほうが良いみたいです。

具体的な例はこちら

参考
https://forums.autodesk.com/t5/fbx-forum/how-to-align-joint-axis-joint-orientation/td-p/7264079


    # Align vectors bones lSkeletonRoot.SetPivotState(self FbxNode.EPivotSet FbxNode.EPivotState)
    rotation = FbxVector4()
    if FbxVector4.AxisAlignmentInEulerAngle(FbxVector4(0, 0, 0), FbxVector4(0, 0, 1), FbxVector4(0, 1, 0), rotation):
        lSkeletonRoot.SetPivotState(FbxNode.EPivotSet.eSourcePivot, FbxNode.EPivotState.ePivotActive)
        lSkeletonRoot.SetRotationActive(True)
        lSkeletonRoot.SetPostRotation(FbxNode.EPivotSet.eSourcePivot, rotation)

特にSetPivotStateの命令が謎なんですが、下記のURLC++のマニュアルから検索してみてみると、


要するに、SetPivotStateは変更状態にするという意味らしいです。
これをいれて、なおかつSetRotationActiveの命令も出さないと、回転は正常にできません。
しかもボーンの回転方向ってどうやるん?という問題も…。

まとめ

  1. 作る場合はクリエイト

  2. 見る場合はゲット

  3. 編集する場合はセット

  4. セットの場合はいれる値を一度指定の形でオブジェクト化しないと行けない(例えば、FbxDouble3など)

  5. シーンのセーブを忘れずに

  6. クラスやメソッドの情報でわからないときで、Pythonの情報は当てにしない方が良い。c++などの情報から考えて書いたほう良いです。