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).
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! ** **
The goal of this time is to create an animation that makes the cube move in a circular motion as shown below!
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
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!
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.
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.
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.
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")
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.
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)
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!
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.)
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.
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!
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