Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support pose,object constraints #63

Merged
merged 2 commits into from
Aug 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 149 additions & 32 deletions io_scene_godot/converters/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import copy
from functools import partial
import bpy
import bpy_extras.anim_utils
import mathutils
from . import armature
from ..structures import (NodeTemplate, NodePath, fix_directional_transform,
Expand Down Expand Up @@ -141,15 +142,16 @@ class AnimationPlayer(NodeTemplate):
def __init__(self, name, parent):
super().__init__(name, "AnimationPlayer", parent)
# use parent node as the animation root node
self['root_node'] = NodePath(self.get_path(), self.parent.get_path())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious as to why parent and self.parent are different. From what I can see of NodeTemplate, they should be the same?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it should be the same. While I develop on this PR, I removed it first and then add it back, so caused some inconsistence, I will fix it

self['root_node'] = NodePath(self.get_path(), parent.get_path())
# blender actions not in nla_tracks are treated as default
self.default_animation = None

def add_default_animation_resource(self, escn_file, action):
"""Default animation resource may hold animation from children
objects"""
objects, parameter action is used as hash key of resource"""
self.default_animation = self.create_animation_resource(
escn_file, action)
escn_file, action
)

def create_animation_resource(self, escn_file, action):
"""Create a new animation resource and add it into escn file"""
Expand Down Expand Up @@ -241,8 +243,8 @@ def get_animation_player(escn_file, export_settings, godot_node):

if animation_player is None:
animation_player = AnimationPlayer(
godot_node.get_name() + 'Animation',
godot_node.parent,
name='AnimationPlayer',
parent=godot_node,
)

escn_file.add_node(animation_player)
Expand Down Expand Up @@ -311,6 +313,75 @@ def build_linear_interp_value_track(track_path, map_func, fcurve):
return track


def has_object_constraint(blender_object):
"""Return bool indicate if object has constraint"""
if isinstance(blender_object, bpy.types.Object):
return True if blender_object.constraints else False
return False


def has_pose_constraint(blender_object):
"""Return bool indicate if object has pose constraint"""
if (isinstance(blender_object, bpy.types.Object) and
isinstance(blender_object.data, bpy.types.Armature)):
for pose_bone in blender_object.pose.bones:
if pose_bone.constraints:
return True
return False


def bake_constraint_to_action(blender_object, base_action,
bake_type, in_place):
"""Bake pose or object constrainst (e.g. IK) to action"""
if base_action is not None:
blender_object.animation_data.action = base_action
frame_range = get_action_frame_range(base_action)
else:
frame_range = (1, 250) # default, can be improved

# if action_bake_into is None, it would create a new one
# and baked into it
if in_place:
action_bake_into = base_action
else:
action_bake_into = None

do_pose = bake_type == "POSE"
do_object = not do_pose

if bpy.app.version <= (2, 79, 0):
active_obj_backup = bpy.context.scene.objects.active

# the object to bake is the current active object
bpy.context.scene.objects.active = blender_object
baked_action = bpy_extras.anim_utils.bake_action(
frame_start=frame_range[0],
frame_end=frame_range[1],
frame_step=1,
only_selected=False,
action=action_bake_into,
do_pose=do_pose,
do_object=do_object,
do_visual_keying=True,
)

bpy.context.scene.objects.active = active_obj_backup
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably not be only for blender 2.79!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, it should be place outside, I am so careless..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after think about a bit, I moved the active_obj_backup into the if block instead of move this line outside

else:
baked_action = bpy_extras.anim_utils.bake_action(
obj=blender_object,
frame_start=frame_range[0],
frame_end=frame_range[1],
frame_step=1,
only_selected=False,
action=action_bake_into,
do_pose=do_pose,
do_object=do_object,
do_visual_keying=True,
)

return baked_action


def export_transform_action(godot_node, animation_player,
blender_object, action, animation_resource):
"""Export a action with bone and object transform"""
Expand Down Expand Up @@ -493,9 +564,6 @@ def export_shapekey_action(godot_node, animation_player,
def export_light_action(light_node, animation_player,
blender_lamp, action, animation_resource):
"""Export light(lamp in Blender) action"""
if blender_lamp.animation_data is None:
return

first_frame, last_frame = get_action_frame_range(action)
base_node_path = NodePath(
animation_player.parent.get_path(), light_node.get_path()
Expand Down Expand Up @@ -553,9 +621,6 @@ def export_light_action(light_node, animation_player,
def export_camera_action(camera_node, animation_player,
blender_cam, action, animation_resource):
"""Export camera action"""
if blender_cam.animation_data is None:
return

first_frame, last_frame = get_action_frame_range(action)
base_node_path = NodePath(
animation_player.parent.get_path(), camera_node.get_path()
Expand Down Expand Up @@ -634,37 +699,89 @@ def export_animation_data(escn_file, export_settings, godot_node,
"""Export the action and nla_tracks in blender_object.animation_data,
it will further call the action exporting function in AnimationDataExporter
given by `func_name`"""
if (blender_object.animation_data is None or
not export_settings['use_export_animation']):
if not export_settings['use_export_animation']:
return
animation_player = get_animation_player(
escn_file, export_settings, godot_node)
has_obj_cst = has_object_constraint(blender_object)
has_pose_cst = has_pose_constraint(blender_object)
need_bake = action_type == 'transform' and (has_obj_cst or has_pose_cst)

def action_baker(action_to_bake):
"""A quick call to bake OBJECT and POSE action"""
# note it used variable outside its scope
if has_obj_cst:
action_baked = bake_constraint_to_action(
blender_object, action_to_bake, "OBJECT", False)
if has_pose_cst:
if has_obj_cst:
action_baked = bake_constraint_to_action(
blender_object, action_baked, "POSE", True)
else:
action_baked = bake_constraint_to_action(
blender_object, action_to_bake, "POSE", False)
return action_baked

exporter_func = ACTION_EXPORTER_MAP[action_type]
if blender_object.animation_data is None and not need_bake:
return

animation_player = get_animation_player(
escn_file, export_settings, godot_node
)
exporter_func = ACTION_EXPORTER_MAP[action_type]
# avoid duplicated export, same actions may exist in different nla_strip
exported_actions = set()

action = blender_object.animation_data.action
if action is not None:
if animation_player.default_animation is None:
# choose a arbitrary action as the hash key for animation resource
animation_player.add_default_animation_resource(
escn_file, action)

exported_actions.add(action)
# back up active action to reset back after finish exporting
if blender_object.animation_data:
active_action_bakeup = blender_object.animation_data.action
else:
active_action_bakeup = None

# ---- export active action
action_active = active_action_bakeup
if need_bake:
action_active = action_baker(action_active)

# must be put after active action being baked, because action_active
# may be None before baking
if animation_player.default_animation is None:
animation_player.add_default_animation_resource(
escn_file, action_active
)
# export active action
exporter_func(godot_node, animation_player, blender_object,
action_active, animation_player.default_animation)

if need_bake:
bpy.data.actions.remove(action_active)

# ---- export actions in nla tracks
def export_action(action_to_export):
"""Export an action"""
if need_bake:
# action_to_export is new created, need to be removed later
action_to_export = action_baker(action_to_export)

anim_resource = animation_player.create_animation_resource(
escn_file, action_to_export
)

exporter_func(godot_node, animation_player, blender_object,
action, animation_player.default_animation)
action_to_export, anim_resource)

exported_actions.add(action_to_export)

if need_bake:
# remove baked action
bpy.data.actions.remove(action_to_export)

# export actions in nla_tracks, each exported to seperate
# animation resources
for nla_track in blender_object.animation_data.nla_tracks:
for nla_strip in nla_track.strips:
# make sure no duplicate action exported
if nla_strip.action not in exported_actions:
exported_actions.add(nla_strip.action)
anim_resource = animation_player.create_animation_resource(
escn_file, nla_strip.action
)
exporter_func(godot_node, animation_player, blender_object,
nla_strip.action, anim_resource)
if (nla_strip.action is not None and
nla_strip.action not in exported_actions):
export_action(nla_strip.action)

if active_action_bakeup is not None:
blender_object.animation_data.action = active_action_bakeup
6 changes: 3 additions & 3 deletions tests/reference_exports/animation_bone_transform.escn

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions tests/reference_exports/animation_camera.escn
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ surfaces/0 = {
step = 0.1
length = 2.91667
tracks/0/type = "value"
tracks/0/path = NodePath("Camera:far")
tracks/0/path = NodePath(".:far")
tracks/0/interp = 1
tracks/0/keys = {
"times":PoolRealArray(0.541667, 0.583333, 0.625, 0.666667, 0.708333, 0.75, 0.791667, 0.833333),
Expand All @@ -42,7 +42,7 @@ tracks/0/keys = {
"values":[100.0, 95.9174, 83.8392, 65.4366, 44.5634, 26.1609, 14.0826, 10.0]
}
tracks/1/type = "value"
tracks/1/path = NodePath("Camera:near")
tracks/1/path = NodePath(".:near")
tracks/1/interp = 1
tracks/1/keys = {
"times":PoolRealArray(0.0416667, 0.0833333, 0.125, 0.166667, 0.208333, 0.25, 0.291667, 0.333333, 0.375, 0.416667),
Expand All @@ -51,7 +51,7 @@ tracks/1/keys = {
"values":[0.1, 1.46695, 5.56822, 12.1535, 20.5309, 29.5691, 37.9465, 44.5318, 48.633, 50.0]
}
tracks/2/type = "value"
tracks/2/path = NodePath("Camera:size")
tracks/2/path = NodePath(".:size")
tracks/2/interp = 1
tracks/2/keys = {
"times":PoolRealArray(2.5, 2.54167, 2.58333, 2.625, 2.66667, 2.70833, 2.75, 2.79167, 2.83333, 2.875, 2.91667),
Expand All @@ -60,7 +60,7 @@ tracks/2/keys = {
"values":[7.31429, 7.1743, 6.75302, 6.06803, 5.17209, 4.15714, 3.14219, 2.24626, 1.56127, 1.13998, 1.0]
}
tracks/3/type = "value"
tracks/3/path = NodePath("Camera:projection")
tracks/3/path = NodePath(".:projection")
tracks/3/interp = 0
tracks/3/keys = {
"times":PoolRealArray(0.0416667, 2.5),
Expand All @@ -69,7 +69,7 @@ tracks/3/keys = {
"values":[0, 1]
}
tracks/4/type = "value"
tracks/4/path = NodePath("Camera:fov")
tracks/4/path = NodePath(".:fov")
tracks/4/interp = 1
tracks/4/keys = {
"times":PoolRealArray(0.0416667, 1.29167, 1.33333, 1.375, 1.41667, 1.45833, 1.5, 1.54167, 1.58333, 1.625, 1.66667, 1.70833, 1.75, 1.79167, 1.83333, 1.875, 1.91667, 1.95833, 2.0, 2.04167, 2.08333, 2.125, 2.16667, 2.20833, 2.25, 2.29167, 2.33333, 2.375, 2.41667, 2.45833, 2.5, 2.54167, 2.58333, 2.625, 2.66667, 2.70833, 2.75, 2.79167, 2.83333, 2.875, 2.91667),
Expand All @@ -95,7 +95,7 @@ projection = 0
fov = 49.1343
transform = Transform(0.685921, -0.324014, 0.651558, 0.0, 0.895396, 0.445271, -0.727676, -0.305421, 0.61417, 7.48113, 5.34367, 6.50764)

[node name="CameraAnimation" type="AnimationPlayer" parent="."]
[node name="AnimationPlayer" type="AnimationPlayer" parent="Camera"]

root_node = NodePath("..:")
anims/CameraAction = SubResource(3)
4 changes: 2 additions & 2 deletions tests/reference_exports/animation_light_type_change.escn
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ surfaces/0 = {
step = 0.1
length = 2.91667
tracks/0/type = "value"
tracks/0/path = NodePath("Lamp:light_color")
tracks/0/path = NodePath(".:light_color")
tracks/0/interp = 1
tracks/0/keys = {
"times":PoolRealArray(0.0833333, 0.458333, 0.5, 0.541667, 0.583333, 0.625, 0.666667, 0.708333, 0.75, 0.791667, 0.833333),
Expand All @@ -60,7 +60,7 @@ transform = Transform(-0.290865, -0.771101, 0.566393, -0.0551891, 0.604525, 0.79
shadow_enabled = true
light_negative = false

[node name="LampAnimation" type="AnimationPlayer" parent="."]
[node name="AnimationPlayer" type="AnimationPlayer" parent="Lamp"]

root_node = NodePath("..:")
anims/LampAction = SubResource(3)
Loading