瀏覽代碼

rewrite constraint baking

Jason0214 6 年之前
父節點
當前提交
c5c0bffd41

+ 108 - 33
io_scene_godot/converters/animation/action.py

@@ -9,6 +9,7 @@ import copy
 import bpy
 import bpy
 import mathutils
 import mathutils
 from .serializer import FloatTrack, TransformTrack, ColorTrack, TransformFrame
 from .serializer import FloatTrack, TransformTrack, ColorTrack, TransformFrame
+from .constraint_baking import check_object_constraint
 from ...structures import (NodePath, fix_bone_attachment_location)
 from ...structures import (NodePath, fix_bone_attachment_location)
 
 
 
 
@@ -35,7 +36,7 @@ def get_strip_frame_range(strip):
 class ActionStrip:
 class ActionStrip:
     """Abstract of blender action strip, it may override attributes
     """Abstract of blender action strip, it may override attributes
     of an action object"""
     of an action object"""
-    def __init__(self, action_or_strip, action_override=None):
+    def __init__(self, action_or_strip):
         self.action = None
         self.action = None
         self.frame_range = (0, 0)
         self.frame_range = (0, 0)
 
 
@@ -47,21 +48,18 @@ class ActionStrip:
 
 
         if isinstance(action_or_strip, bpy.types.NlaStrip):
         if isinstance(action_or_strip, bpy.types.NlaStrip):
             strip = action_or_strip
             strip = action_or_strip
-            if action_override:
-                self.action = action_override
-            else:
-                self.action = strip.action
+            self.action = strip.action
             self._fk = (
             self._fk = (
                 (strip.frame_end - strip.frame_start) /
                 (strip.frame_end - strip.frame_start) /
                 (self.action.frame_range[1] - self.action.frame_range[0])
                 (self.action.frame_range[1] - self.action.frame_range[0])
             )
             )
             self._fb = self.action.frame_range[1] - self._fk * strip.frame_end
             self._fb = self.action.frame_range[1] - self._fk * strip.frame_end
             self.frame_range = get_strip_frame_range(strip)
             self.frame_range = get_strip_frame_range(strip)
-        else:
-            if action_override:
-                assert False
+        elif isinstance(action_or_strip, bpy.types.Action):
             self.action = action_or_strip
             self.action = action_or_strip
             self.frame_range = get_action_frame_range(self.action)
             self.frame_range = get_action_frame_range(self.action)
+        else:  # action_or_strip is None
+            self.frame_range = (0, 190)
 
 
     def evaluate_fcurve(self, fcurve, frame):
     def evaluate_fcurve(self, fcurve, frame):
         """Evaluate a value of fcurve, DO NOT use fcurve.evalute, as
         """Evaluate a value of fcurve, DO NOT use fcurve.evalute, as
@@ -93,12 +91,96 @@ def split_fcurve_data_path(data_path):
     return path_list[0], path_list[1]
     return path_list[0], path_list[1]
 
 
 
 
+def has_obj_fcurves(action_strip):
+    """Check whether action has object transform information"""
+    if action_strip.action is None:
+        return False
+    for fcurve in action_strip.action.fcurves:
+        obj_path, attribute = split_fcurve_data_path(fcurve.data_path)
+        if obj_path == '':
+            return True
+    return False
+
+
+def export_constrained_xform_action(godot_node, animation_player,
+                                    blender_object, action_strip,
+                                    animation_resource):
+    """Export transform animation of any object has constraints,
+    it use frame_set to traversal each frame, so it's costly"""
+    first_frame, last_frame = action_strip.frame_range
+
+    obj_xform_mats = list()
+    pbone_xform_mats = collections.OrderedDict()
+
+    scene = bpy.context.scene
+    frame_backup = scene.frame_current
+    for frame in range(first_frame, last_frame):
+        scene.frame_set(frame)
+        obj_xform_mats.append(blender_object.matrix_local.copy())
+        if blender_object.pose is not None:
+            for pbone in blender_object.pose.bones:
+                if pbone.name not in pbone_xform_mats:
+                    pbone_xform_mats[pbone.name] = list()
+                pbone_xform_mats[pbone.name].append(
+                    blender_object.convert_space(
+                        pose_bone=pbone, matrix=pbone.matrix,
+                        from_space='POSE', to_space='LOCAL'
+                    )
+                )
+    scene.frame_set(frame_backup)
+
+    if (check_object_constraint(blender_object) or
+            has_obj_fcurves(action_strip)):
+        xform_frames_list = [
+            TransformFrame.factory(mat)
+            for mat in obj_xform_mats
+        ]
+
+        track_path = NodePath(
+            animation_player.parent.get_path(),
+            godot_node.get_path()
+        )
+
+        if godot_node.parent.get_type() == 'BoneAttachment':
+            xform_frames_list = [
+                fix_bone_attachment_location(blender_object, x.location)
+                for x in xform_frames_list
+            ]
+
+        animation_resource.add_obj_xform_track(
+            godot_node.get_type(), track_path,
+            xform_frames_list, action_strip.frame_range,
+            # no need for parent_inverse, as it is directly access matrix_local
+        )
+
+    for pbone_name, pbone_xform_mat_list in pbone_xform_mats.items():
+        if godot_node.find_bone_id(pbone_name) != -1:
+            pbone_xform_frames_list = [
+                TransformFrame.factory(mat)
+                for mat in pbone_xform_mat_list
+            ]
+
+            track_path = NodePath(
+                animation_player.parent.get_path(),
+                godot_node.get_path(),
+                godot_node.find_bone_name(pbone_name),
+            )
+
+            animation_resource.add_track(
+                TransformTrack(
+                    track_path,
+                    frames_iter=range(first_frame, last_frame),
+                    values_iter=pbone_xform_frames_list,
+                )
+            )
+
+
 def export_transform_action(godot_node, animation_player, blender_object,
 def export_transform_action(godot_node, animation_player, blender_object,
                             action_strip, animation_resource):
                             action_strip, animation_resource):
     """Export a action with bone and object transform"""
     """Export a action with bone and object transform"""
-    def init_transform_frame_values(object_path, blender_object, godot_node,
-                                    first_frame, last_frame):
-        """Initialize a list of TransformFrame for every animated object"""
+    def init_transform_frames_list(object_path, blender_object, godot_node,
+                                   first_frame, last_frame):
+        """Initialize a list of TransformFrame for an animated object"""
         if object_path.startswith('pose'):
         if object_path.startswith('pose'):
             bone_name = blender_path_to_bone_name(object_path)
             bone_name = blender_path_to_bone_name(object_path)
 
 
@@ -136,7 +218,7 @@ def export_transform_action(godot_node, animation_player, blender_object,
         ]
         ]
 
 
     first_frame, last_frame = action_strip.frame_range
     first_frame, last_frame = action_strip.frame_range
-    transform_frame_values_map = collections.OrderedDict()
+    xform_frames_list_map = collections.OrderedDict()
     for fcurve in action_strip.action.fcurves:
     for fcurve in action_strip.action.fcurves:
         # fcurve data are seperated into different channels,
         # fcurve data are seperated into different channels,
         # for example a transform action would have several fcurves
         # for example a transform action would have several fcurves
@@ -145,9 +227,9 @@ def export_transform_action(godot_node, animation_player, blender_object,
         object_path, attribute = split_fcurve_data_path(fcurve.data_path)
         object_path, attribute = split_fcurve_data_path(fcurve.data_path)
 
 
         if attribute in TransformFrame.ATTRIBUTES:
         if attribute in TransformFrame.ATTRIBUTES:
-            if object_path not in transform_frame_values_map:
+            if object_path not in xform_frames_list_map:
 
 
-                frame_values = init_transform_frame_values(
+                frame_values = init_transform_frames_list(
                     object_path, blender_object,
                     object_path, blender_object,
                     godot_node, first_frame, last_frame
                     godot_node, first_frame, last_frame
                 )
                 )
@@ -156,20 +238,18 @@ def export_transform_action(godot_node, animation_player, blender_object,
                 if not frame_values:
                 if not frame_values:
                     continue
                     continue
 
 
-                transform_frame_values_map[object_path] = frame_values
+                xform_frames_list_map[object_path] = frame_values
 
 
             for frame in range(first_frame, last_frame):
             for frame in range(first_frame, last_frame):
-                transform_frame_values_map[
-                    object_path][frame - first_frame].update(
-                        attribute,
-                        fcurve.array_index,
-                        action_strip.evaluate_fcurve(fcurve, frame)
-                    )
+                xform_frames_list_map[object_path][frame - first_frame].update(
+                    attribute,
+                    fcurve.array_index,
+                    action_strip.evaluate_fcurve(fcurve, frame)
+                )
 
 
-    for object_path, frame_value_list in transform_frame_values_map.items():
+    for object_path, frame_value_list in xform_frames_list_map.items():
         if object_path == '':
         if object_path == '':
-            # object_path equals '' represents node itself
-
+            # empty object_path represents transform of object itself
             track_path = NodePath(
             track_path = NodePath(
                 animation_player.parent.get_path(),
                 animation_player.parent.get_path(),
                 godot_node.get_path()
                 godot_node.get_path()
@@ -181,16 +261,11 @@ def export_transform_action(godot_node, animation_player, blender_object,
                     for x in frame_value_list
                     for x in frame_value_list
                 ]
                 ]
 
 
-            track = TransformTrack(
-                track_path,
-                frames_iter=range(first_frame, last_frame),
-                values_iter=frame_value_list,
+            animation_resource.add_obj_xform_track(
+                godot_node.get_type(), track_path,
+                frame_value_list, action_strip.frame_range,
+                blender_object.matrix_parent_inverse
             )
             )
-            track.set_parent_inverse(blender_object.matrix_parent_inverse)
-            if godot_node.get_type() in ("SpotLight", "DirectionalLight",
-                                         "Camera", "CollisionShape"):
-                track.is_directional = True
-            animation_resource.add_track(track)
 
 
         elif object_path.startswith('pose'):
         elif object_path.startswith('pose'):
             track_path = NodePath(
             track_path = NodePath(

+ 25 - 41
io_scene_godot/converters/animation/animation_data.py

@@ -2,19 +2,16 @@
 AnimationPlayer as well as distribute Blender action into various
 AnimationPlayer as well as distribute Blender action into various
 action exporting functions"""
 action exporting functions"""
 
 
-import bpy
 from .action import (
 from .action import (
     export_camera_action,
     export_camera_action,
     export_shapekey_action,
     export_shapekey_action,
     export_light_action,
     export_light_action,
+    export_constrained_xform_action,
     export_transform_action
     export_transform_action
 )
 )
 from .constraint_baking import (
 from .constraint_baking import (
-    bake_constraint_to_action,
     check_object_constraint,
     check_object_constraint,
     check_pose_constraint,
     check_pose_constraint,
-    action_baking_finalize,
-    action_baking_initialize
 )
 )
 from .serializer import get_animation_player
 from .serializer import get_animation_player
 from .action import ActionStrip
 from .action import ActionStrip
@@ -35,7 +32,6 @@ class ObjectAnimationExporter:
         self.godot_node = godot_node
         self.godot_node = godot_node
         self.blender_object = blender_object
         self.blender_object = blender_object
 
 
-        self.action_exporter_func = ACTION_EXPORTER_MAP[action_type]
         self.animation_player = None
         self.animation_player = None
 
 
         self.need_baking = False
         self.need_baking = False
@@ -46,6 +42,11 @@ class ObjectAnimationExporter:
         self.check_baking_condition(action_type)
         self.check_baking_condition(action_type)
         self.preprocess_nla_tracks(blender_object)
         self.preprocess_nla_tracks(blender_object)
 
 
+        if not self.need_baking:
+            self.action_exporter_func = ACTION_EXPORTER_MAP[action_type]
+        else:
+            self.action_exporter_func = export_constrained_xform_action
+
     def check_baking_condition(self, action_type):
     def check_baking_condition(self, action_type):
         """Check whether the animated object has any constraint and
         """Check whether the animated object has any constraint and
         thus need to do baking, if needs, some states would be set"""
         thus need to do baking, if needs, some states would be set"""
@@ -68,44 +69,31 @@ class ObjectAnimationExporter:
                 else:
                 else:
                     self.mute_nla_tracks.append(nla_track)
                     self.mute_nla_tracks.append(nla_track)
 
 
-    def bake_to_new_action(self, action_to_bake):
-        """Baking object and pose constraint altogether.
-
-        Note that it accept a action to bake (which would not be modified)
-        and always return a new created baked actiony"""
-        return bake_constraint_to_action(
-            self.blender_object, action_to_bake, False
-        )
-
     def export_active_action(self, escn_file, active_action):
     def export_active_action(self, escn_file, active_action):
         """Export the active action, if needed, would call bake.
         """Export the active action, if needed, would call bake.
-
         Note that active_action maybe None, which would happen when object has
         Note that active_action maybe None, which would happen when object has
         some constraint (so even no action it is still animated)"""
         some constraint (so even no action it is still animated)"""
-        if self.need_baking:
-            action_baking_initialize(active_action)
-            action_active_to_export = self.bake_to_new_action(active_action)
+        if active_action is None:
+            # object has constraints on other objects
+            assert self.need_baking
+            anim_rsc_name = self.blender_object.name + 'Action'
         else:
         else:
-            action_active_to_export = active_action
+            anim_rsc_name = active_action.name
 
 
         if self.animation_player.active_animation is None:
         if self.animation_player.active_animation is None:
             self.animation_player.add_active_animation_resource(
             self.animation_player.add_active_animation_resource(
-                escn_file, action_active_to_export.name
+                escn_file, anim_rsc_name
             )
             )
 
 
         self.action_exporter_func(
         self.action_exporter_func(
             self.godot_node,
             self.godot_node,
             self.animation_player,
             self.animation_player,
             self.blender_object,
             self.blender_object,
-            ActionStrip(action_active_to_export),
+            ActionStrip(active_action),
             self.animation_player.active_animation
             self.animation_player.active_animation
         )
         )
 
 
-        if self.need_baking:
-            # remove new created action
-            bpy.data.actions.remove(action_active_to_export)
-            action_baking_finalize(active_action)
-        else:
+        if not self.need_baking:
             # here export unmuted nla_tracks into animation resource,
             # here export unmuted nla_tracks into animation resource,
             # this is not needed for baking, as baking has applied to
             # this is not needed for baking, as baking has applied to
             # active action
             # active action
@@ -142,7 +130,6 @@ class ObjectAnimationExporter:
     def export_stashed_track(self, escn_file, stashed_track):
     def export_stashed_track(self, escn_file, stashed_track):
         """Export a muted nla_track, track with all its contained action
         """Export a muted nla_track, track with all its contained action
         is exported to a single animation_resource.
         is exported to a single animation_resource.
-
         It works as an action lib"""
         It works as an action lib"""
         if not stashed_track.strips:
         if not stashed_track.strips:
             return
             return
@@ -158,28 +145,22 @@ class ObjectAnimationExporter:
             escn_file, anim_name
             escn_file, anim_name
         )
         )
 
 
+        if self.need_baking:
+            stashed_track.mute = False
+
         for strip in stashed_track.strips:
         for strip in stashed_track.strips:
             if strip.action:
             if strip.action:
-                if self.need_baking:
-                    action_baking_initialize(strip.action)
-                    action_to_export = self.bake_to_new_action(strip.action)
-                else:
-                    action_to_export = strip.action
-
                 self.action_exporter_func(
                 self.action_exporter_func(
                     self.godot_node,
                     self.godot_node,
                     self.animation_player,
                     self.animation_player,
                     self.blender_object,
                     self.blender_object,
-                    ActionStrip(strip, action_to_export),
+                    ActionStrip(strip),
                     anim_resource
                     anim_resource
                 )
                 )
 
 
-                if self.need_baking:
-                    # remove baked action
-                    bpy.data.actions.remove(action_to_export)
-                    action_baking_finalize(strip.action)
-
-        if not self.need_baking:
+        if self.need_baking:
+            stashed_track.mute = True
+        else:  # not self.need_baking:
             # if baking, nla_tracks are already baked into strips
             # if baking, nla_tracks are already baked into strips
             for nla_track in self.unmute_nla_tracks:
             for nla_track in self.unmute_nla_tracks:
                 for strip in nla_track.strips:
                 for strip in nla_track.strips:
@@ -228,8 +209,11 @@ def export_animation_data(escn_file, export_settings, godot_node,
     # export actions in nla_tracks, each exported to seperate
     # export actions in nla_tracks, each exported to seperate
     # animation resources
     # animation resources
     if export_settings['use_stashed_action']:
     if export_settings['use_stashed_action']:
+        if blender_object.animation_data:
+            # clear active action, isolate NLA track
+            blender_object.animation_data.action = None
         for stashed_track in anim_exporter.mute_nla_tracks:
         for stashed_track in anim_exporter.mute_nla_tracks:
             anim_exporter.export_stashed_track(escn_file, stashed_track)
             anim_exporter.export_stashed_track(escn_file, stashed_track)
 
 
-    if active_action is not None:
+    if blender_object.animation_data is not None:
         blender_object.animation_data.action = active_action
         blender_object.animation_data.action = active_action

+ 0 - 55
io_scene_godot/converters/animation/constraint_baking.py

@@ -1,24 +1,5 @@
 """Collection of helper functions to baking constraints into action"""
 """Collection of helper functions to baking constraints into action"""
-
 import bpy
 import bpy
-import bpy_extras.anim_utils
-
-# a suffix append to action need baking to avoid name collision
-# with baked action's name
-BAKING_SUFFIX = '--being-baking'
-
-
-def action_baking_initialize(action):
-    """Intialize steps before an action going to baking"""
-    if action is not None:
-        action.name = action.name + BAKING_SUFFIX
-
-
-def action_baking_finalize(action):
-    """Clear up some baking information for an action having
-    going through baking"""
-    if action is not None:
-        action.name = action.name[:-len(BAKING_SUFFIX)]
 
 
 
 
 def check_object_constraint(blender_object):
 def check_object_constraint(blender_object):
@@ -36,39 +17,3 @@ def check_pose_constraint(blender_object):
             if pose_bone.constraints:
             if pose_bone.constraints:
                 return True
                 return True
     return False
     return False
-
-
-def bake_constraint_to_action(blender_object, base_action, 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 = range(
-            int(base_action.frame_range[0]),
-            int(base_action.frame_range[1]) + 1)
-    else:
-        frame_range = range(1, 251)  # 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
-
-    baked_action = bpy_extras.anim_utils.bake_action_objects(
-        object_action_pairs=[(blender_object, action_bake_into)],
-        frames=frame_range,
-        only_selected=False,
-        do_pose=True,
-        do_object=True,
-        do_visual_keying=True,
-    )[0]
-
-    if in_place:
-        return action_bake_into
-
-    if base_action is not None:
-        baked_action.name = base_action.name[:-len(BAKING_SUFFIX)]
-    else:
-        baked_action.name = blender_object.name + 'Action'
-    return baked_action

+ 30 - 14
io_scene_godot/converters/animation/serializer.py

@@ -52,13 +52,13 @@ class TransformFrame:
     matrix."""
     matrix."""
     ATTRIBUTES = {'location', 'scale', 'rotation_quaternion', 'rotation_euler'}
     ATTRIBUTES = {'location', 'scale', 'rotation_quaternion', 'rotation_euler'}
 
 
-    def __init__(self):
+    def __init__(self, rotation_mode='QUATERNION'):
         self.location = mathutils.Vector((0, 0, 0))
         self.location = mathutils.Vector((0, 0, 0))
         self.scale = mathutils.Vector((1, 1, 1))
         self.scale = mathutils.Vector((1, 1, 1))
 
 
-        self.rotation_mode = 'QUATERNION'
+        self.rotation_mode = rotation_mode
         self.rotation_euler = mathutils.Euler((0, 0, 0))
         self.rotation_euler = mathutils.Euler((0, 0, 0))
-        self.rotation_quaternion = mathutils.Quaternion()
+        self.rotation_quaternion = mathutils.Quaternion((1.0, 0.0, 0.0, 0.0))
 
 
     def __eq__(self, other):
     def __eq__(self, other):
         """Overrides the default implementation"""
         """Overrides the default implementation"""
@@ -69,21 +69,20 @@ class TransformFrame:
         return False
         return False
 
 
     @classmethod
     @classmethod
-    def factory(cls, trans_mat, rotation_mode):
-        """Factory function, create cls from a transform matrix"""
-        ret = cls()
-        ret.location = trans_mat.to_translation()
+    def factory(cls, xform_matrix, rotation_mode='QUATERNION'):
+        """Factory method, return an instance created from transform matrix"""
+        xform_frame = cls()
+        xform_frame.rotation_mode = rotation_mode
+        xform_frame.location = xform_matrix.to_translation()
+        xform_frame.rotation_quaternion = xform_matrix.to_quaternion()
         # FIXME: lose negative scale
         # FIXME: lose negative scale
-        ret.scale = trans_mat.to_scale()
+        xform_frame.scale = xform_matrix.to_scale()
 
 
-        # quaternion and euler fcurves may both exist in fcurves
-        ret.rotation_mode = rotation_mode
-        ret.rotation_quaternion = trans_mat.to_quaternion()
         if rotation_mode == 'QUATERNION':
         if rotation_mode == 'QUATERNION':
-            ret.rotation_euler = trans_mat.to_euler()
+            xform_frame.rotation_euler = xform_matrix.to_euler()
         else:
         else:
-            ret.rotation_euler = trans_mat.to_euler(rotation_mode)
-        return ret
+            xform_frame.rotation_euler = xform_matrix.to_euler(rotation_mode)
+        return xform_frame
 
 
     def update(self, attribute, array_index, value):
     def update(self, attribute, array_index, value):
         """Use fcurve data to update the frame"""
         """Use fcurve data to update the frame"""
@@ -459,6 +458,23 @@ class AnimationResource(InternalResource):
             self[track_id_str + '/interp'] = track.interp
             self[track_id_str + '/interp'] = track.interp
             self[track_id_str + '/keys'] = track
             self[track_id_str + '/keys'] = track
 
 
+    # pylint: disable-msg=too-many-arguments
+    def add_obj_xform_track(self, node_type, track_path,
+                            xform_frames_list, frame_range,
+                            parent_mat_inverse=mathutils.Matrix.Identity(4)):
+        """Add a object transform track to AnimationResource"""
+        track = TransformTrack(
+            track_path,
+            frames_iter=range(frame_range[0], frame_range[1]),
+            values_iter=xform_frames_list,
+        )
+        track.set_parent_inverse(parent_mat_inverse)
+        if node_type in ("SpotLight", "DirectionalLight",
+                         "Camera", "CollisionShape"):
+            track.is_directional = True
+
+        self.add_track(track)
+
     def add_attribute_track(self, action_strip, fcurve,
     def add_attribute_track(self, action_strip, fcurve,
                             converter, node_path):
                             converter, node_path):
         """Add a track into AnimationResource, the track is a
         """Add a track into AnimationResource, the track is a

文件差異過大導致無法顯示
+ 0 - 0
tests/reference_exports/action_with_constraint/bone_attachment_ik.escn


文件差異過大導致無法顯示
+ 1 - 1
tests/reference_exports/action_with_constraint/constraint_external_IK.escn


文件差異過大導致無法顯示
+ 0 - 0
tests/reference_exports/action_with_constraint/constraint_internal_IK.escn


文件差異過大導致無法顯示
+ 2 - 2
tests/reference_exports/action_with_constraint/stashed_constraint.escn


部分文件因文件數量過多而無法顯示