Let's make 3D animation only with Python without using Blender! [FBX SDK Python]

20191201_moving_circle_cube.gif

This is the 7th day article of Python Advent Calendar 2019. Yesterday was [Introduction of options for python unit test library Nose-19 types-] by @ ko-he-8 (https://qiita.com/ko-he-8/items/92f95fe3ee696b334713).

I want to make a 3D animation with just Python code

If you use 3D animation software like Blender, you can create 3D animation files with a convenient GUI!

so! If you are a creator / animator, use Blender! !! (Well, Maya is good? Then Maya)

However! I think most of Qiita's readers are ** programmers **!

** If you're a programmer, you want to create 3D animations by programming! ** **

No, it should be made by programming!

For you, there is ** FBX SDK Python **!

If you have this! ** You can generate 3D animation file FBX with your favorite Python! ** **

e? Don't you like Python? Can Unity make FBX in C #?

・ ・ ・

**Now let's get started! ** **

This goal

The goal of this time is to create an animation that makes the cube move in a circular motion as shown below!

20191201_moving_circle_cube.gif

Preparation: Install FBX SDK Python

First, let's install the FBX SDK Python!

I wrote some articles before, so please prepare according to your own environment.

-Install on Windows -Install on Mac -Install on Google Colaboratory

Let's move the sample code for the time being

First, let's actually move the sample code!

I have put the sample code below, so please download it.

GitHub / segurvita / fbx_sdk_python_sample

After downloading, hit the following command!

python generate_fbx/circle_anim.py resources/cube_ascii.fbx resources/moving_circle_cube_ascii.fbx

You should have created a file called moving_circle_cube_ascii.fbx in the resources folder.

This is the deliverable of this time!

If you open it with the software Autodesk FBX Review, you can see the cube moving in a circular motion like the previous video. I will! Please try!

Command explanation

Well, I haven't explained Python at all yet.

First of all, I will explain from the following command.

python generate_fbx/circle_anim.py resources/cube_ascii.fbx resources/moving_circle_cube_ascii.fbx

generate_fbx / circle_anim.py is the Python source code used this time. Then there are two arguments. This is each

--Input file: resources / cube_ascii.fbx

--Output file: resources / moving_circle_cube_ascii.fbx

It has become.

Python commentary

Next, let's take a look at generate_fbx / circle_anim.py!

The whole picture looks like this!

circle_anim.py


import sys
import math
from fbx import *

fps = 30.0
rps = 0.5

def generate_anim_stack(scene):
    #function
def generate_anim_layer(scene, anim_stack, node):
    #function
def prot_circle(degree, time, curve_t_x, curve_t_y, curve_r_z):
    #function
def move_circle(node, anim_base_layer):
    #function
def get_ascii_format_id(manager):
    #function
def main(obj_path, fbx_path):
    #function

if __name__ == '__main__':
    # get argument
    args = sys.argv

    if len(args) < 2:
        print('Arguments are too short')
    else:
        main(args[1], args[2])

Since there are many functions, this time I will explain only the part related to 3D animation.

Take a look at the main function

First is the main function. The part related to animation is like this.

def main(obj_path, fbx_path):
    #Create various instances
    manager = FbxManager.Create()
    scene = FbxScene.Create(manager, "fbxScene")
    importer = FbxImporter.Create(manager, "")
    exporter = FbxExporter.Create(manager, "")

    #Load the file into the scene
    importer.Initialize(obj_path, -1)
    importer.Import(scene)

    #Create an animation stack in your scene
    anim_stack = generate_anim_stack(scene)

    #Get the root node (the highest node in the scene)
    root_node = scene.GetRootNode()
    
    #If there is a root node
    if (root_node):
        #Loop for the number of child nodes directly under the root
        for i in range(root_node.GetChildCount()):
            #Get a child node
            node = root_node.GetChild(i)

            #Create an animation layer
            anim_base_layer = generate_anim_layer(scene, anim_stack, node)

            #Make an animation curve
            move_circle(node, anim_base_layer)

    #Write the scene to a file
    exporter.Initialize(fbx_path, get_ascii_format_id(manager))
    exporter.Export(scene)

    #Destroy the instance
    exporter.Destroy()
    importer.Destroy()
    scene.Destroy()
    manager.Destroy()

There are some terms that you can't hear.

Scene: Scene

A scene is like the huge space that an FBX file has.

Various data such as animations and meshes are stored in this scene.

(Blender and Unity also have the concept of scenes, but you can use almost the same image as them!)

You can generate it with this code.

#Make a scene
scene = FbxScene.Create(manager, "fbxScene")

Node: Node

There is a lot of data in the scene. Each one is a node.

Nodes have a parent-child relationship.

For example, everyone, try turning your shoulders.

When you turn your shoulders, the position of your elbows changes, right?

In other words, the ** elbow node is a child of the shoulder node **. Child nodes are affected by the parent node.

On the contrary, turning only the elbow does not change the position of the shoulder, right?

The parent node is not affected by the child node.

In the case of FBX, joints such as elbows and shoulders are also nodes, and animations and meshes are all nodes.

Root Node: Root node

If you follow the parent of such a node, you will finally reach the root node.

It's the top node in the scene!

You can get it with this code.

#Get the root node (the highest node in the scene)
root_node = scene.GetRootNode()

You can get the child nodes in order by writing as follows.

#Loop for the number of child nodes directly under the root
for i in range(root_node.GetChildCount()):
    #Get a child node
    node = root_node.GetChild(i)

Make an animation stack

It's getting harder and harder ...

An animation stack is a node that organizes data related to animation.

You can't animate without it!

For the time being, let's make one.

In the sample code, it is created by a function called generate_anim_stack.

def generate_anim_stack(scene):
    #Make an animation stack
    anim_stack = FbxAnimStack.Create(scene, "stack")

    return anim_stack

You can make it with this!

Let's make an animation layer

Another mysterious term ... I will explain.

There are various movements in 3D animation, right?

Run / walk / jump ...

Each of these movements is called ** animation layer ** in FBX terminology. (I'm sorry if I made a mistake)

This time we will just make a circular motion, so let's make one for the time being!

def generate_anim_layer(scene, anim_stack, node):
    #Get the name of a node (cube)
    node_name = node.GetName()

    #Set the position and rotation of the node (cube) to 0
    node.LclTranslation.Set(FbxDouble3(0.0, 0.0, 0.0))
    node.LclRotation.Set(FbxDouble3(0.0, 0.0, 0.0))

    #Create an animation layer (name the node so that it can be distinguished)
    anim_base_layer = FbxAnimLayer.Create(scene, "layer_" + node_name)
    
    #Add an animation layer under the animation stack
    anim_stack.AddMember(anim_base_layer)

    #Create an animation curve node
    anim_curve_node = node.LclTranslation.GetCurveNode(
        anim_base_layer, True
    )
    
    #Return the animation layer
    return anim_base_layer

Now you can create an animation layer!

・ ・ ・

Somehow, the mysterious term animation curve node appeared on the way ...

This is the data needed to animate the information about the position and rotation of a node (in this case, a cube). I need it for the time being, so let's make it. (I don't really understand either. I'm sorry.)

Let's make an animation curve

Ahh! Another mysterious term!

To tell the truth, if you animate a node (cube),

--Move the position --Rotate --Expand and contract the size

There are various animations, right?

For example, if you want to make a circular motion like this time,

--Move the x coordinate of the node position --Move the y coordinate of the node position --Move the z coordinate in the rotation direction of the node

You will move three coordinates like this.

Each of these is a ** animation curve **!

(The concept is almost the same as the animation curve of Blender and Unity)

This time, there are three animation curves!

So here is the sample code.

def move_circle(node, anim_base_layer):
    #Create an animation curve of the x coordinate of the node position
    curve_t_x = node.LclTranslation.GetCurve(
        anim_base_layer, "X", True
    )
    
    #Create an animation curve of the y coordinate of the node position
    curve_t_y = node.LclTranslation.GetCurve(
        anim_base_layer, "Y", True
    )
    
    #Create an animation curve of the z coordinate in the rotation direction of the node
    curve_r_z = node.LclRotation.GetCurve(
        anim_base_layer, "Z", True
    )

    #Prepare variables for time recording
    time = FbxTime()

    #Start recording animation curves
    curve_t_x.KeyModifyBegin()
    curve_t_y.KeyModifyBegin()
    curve_r_z.KeyModifyBegin()

    # fps =30 (30 frames in 1 second)
    # rps = 0.5 (Half a circle in 1 second)
    # fps/rps =60 (1 round of circular motion in 60 frames)
    #Process 60 frames frame by frame.
    for frame in range(int(fps / rps)):
        #Calculate the elapsed time. Number of frames/fps
        sec = float(frame) / fps
        
        #Record elapsed time
        time.SetSecondDouble(sec)
        
        #Calculate the rotation angle. rps x elapsed time x 360 °
        degree = rps * sec * 360.0
        
        #Plot the value according to the rotation angle on the animation curve
        prot_circle(degree, time, curve_t_x, curve_t_y, curve_r_z)

    #End recording of animation curve
    curve_t_x.KeyModifyEnd()
    curve_t_y.KeyModifyEnd()
    curve_r_z.KeyModifyEnd()

There are various detailed calculation formulas, but you can see that the elapsed time is recorded for each frame and the rotation angle of the circular motion is calculated.

The place to actually record the value in the animation curve is described in the function prot_circle, so let's take a look.

Add keys to animation curves

Recording values on the animation curve is like ** adding a key **.

Below is the code for the key addition process.

def prot_circle(degree, time, curve_t_x, curve_t_y, curve_r_z):
    #Convert rotation angle to radians
    radian = math.radians(degree)

    #The x coordinate of the position is cos(radian)Calculate with and add the key
    key_index, key_last = curve_t_x.KeyAdd(time)
    curve_t_x.KeySet(
        key_index, time, math.cos(radian), FbxAnimCurveDef.eInterpolationLinear
    )

    #The y coordinate of the position is sin(radian)Calculate with and add the key
    key_index, key_last = curve_t_y.KeyAdd(time)
    curve_t_y.KeySet(
        key_index, time, math.sin(radian), FbxAnimCurveDef.eInterpolationLinear
    )

    #Add a key while keeping the z coordinate in the rotation direction as a frequency instead of a radian
    key_index, key_last = curve_r_z.KeyAdd(time)
    curve_r_z.KeySet(
        key_index, time, degree, FbxAnimCurveDef.eInterpolationLinear
    )

By calling this function frame by frame, keys will be added frame by frame to the three animation curves.

This is where the key is added.

key_index, key_last = curve_t_x.KeyAdd(time)

This will add the key.

For those unfamiliar with Python, just in case, there are two return values for the function KeyAdd, which is the code assigned to key_index and key_last respectively.

Writing two variables side by side in this way is sometimes read as a tuple.

This time, the key number is assigned to key_index. I don't use key_last, so ignore it.

The following code sets the actual value for this key_index.

curve_t_x.KeySet(
    key_index, time, math.cos(radian), FbxAnimCurveDef.eInterpolationLinear
)

That concludes the explanation of Python! Thank you for your support!

at the end

Thank you for reading.

Now you can create 3D animations in Python alone, without using Blender! !!

In creating this article, I referred to the following pages.

Recommended Posts

Let's make 3D animation only with Python without using Blender! [FBX SDK Python]
Let's make a GUI with python.
Let's make a web chat using WebSocket with AWS serverless (Python)!
Let's make a shiritori game with Python
Working with OpenStack using the Python SDK
Let's make a voice slowly with Python
Let's make a web framework with Python! (1)
Let's make a Twitter Bot with Python!
Let's make a web framework with Python! (2)
[Blender x Python] Let's get started with Blender Python !!
[Blender x Python] How to make an animation
[Blender x Python] How to make vertex animation
Let's make a module for Python using SWIG
[Let's play with Python] Make a household account book
Let's make a simple game with Python 3 and iPhone
Make Python segfault on one line without using ctypes
[Super easy] Let's make a LINE BOT with Python.
Run Blender with python
Let's make a websocket client with Python. (Access token authentication)
[Blender Python] Display images on 3D View using OpenGL (bgl)
How to transpose a 2D array using only python [Note]
Let's play with 4D 4th
[S3] CRUD with S3 using Python [Python]
Let's run Excel with Python
Using Quaternion with Python ~ numpy-quaternion ~
Create 3d gif with python3
[Python] Using OpenCV with Python (Basic)
Let's make Othello with wxPython
Make a fortune with Python
Let's write python with cinema4d.
Using OpenCV with Python @Mac
Send using Python with Gmail
I tried to make a todo application using bottle with python