-
-
Notifications
You must be signed in to change notification settings - Fork 133
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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()) | ||
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""" | ||
|
@@ -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) | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably not be only for blender 2.79! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, yes, it should be place outside, I am so careless.. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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""" | ||
|
@@ -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() | ||
|
@@ -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() | ||
|
@@ -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 |
Large diffs are not rendered by default.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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