浏览代码

Merge pull request #63 from Jason0214/support_constraint_action

Support pose,object constraints
sdfgeoff 7 年之前
父节点
当前提交
5b6767b0c3

+ 149 - 32
io_scene_godot/converters/animation.py

@@ -5,6 +5,7 @@ import math
 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
+    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

文件差异内容过多而无法显示
+ 1 - 1
tests/reference_exports/animation_bone_transform.escn


+ 6 - 6
tests/reference_exports/animation_camera.escn

@@ -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),
@@ -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),
@@ -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),
@@ -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),
@@ -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),
@@ -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)

+ 2 - 2
tests/reference_exports/animation_light_type_change.escn

@@ -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),
@@ -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)

文件差异内容过多而无法显示
+ 1 - 1
tests/reference_exports/animation_object_transform.escn


文件差异内容过多而无法显示
+ 1 - 1
tests/reference_exports/animation_parented_objects.escn


+ 2 - 2
tests/reference_exports/animation_point_light_shadow.escn

@@ -51,7 +51,7 @@ surfaces/0 = {
 step = 0.1
 length = 1.25
 tracks/0/type = "value"
-tracks/0/path = NodePath("Lamp:shadow_color")
+tracks/0/path = NodePath(".:shadow_color")
 tracks/0/interp = 1
 tracks/0/keys = {
 	"times":PoolRealArray(0.0416667, 0.0833333, 0.125, 0.166667, 0.208333, 0.25, 0.291667, 0.333333, 0.375, 0.416667),
@@ -85,7 +85,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(4)

文件差异内容过多而无法显示
+ 1 - 1
tests/reference_exports/animation_rotation_euler.escn


+ 2 - 2
tests/reference_exports/animation_shapekey.escn

@@ -35,7 +35,7 @@ surfaces/0 = {
 step = 0.1
 length = 2.5
 tracks/0/type = "value"
-tracks/0/path = NodePath("Suzanne002:blend_shapes/Key 1")
+tracks/0/path = NodePath(".:blend_shapes/Key 1")
 tracks/0/interp = 1
 tracks/0/keys = {
 	"times":PoolRealArray(0.0416667, 0.0833333, 0.125, 0.166667, 0.208333, 0.25, 0.291667, 0.333333, 0.375, 0.416667, 0.458333, 0.5, 0.541667, 0.583333, 0.625, 0.666667, 0.708333, 0.75, 0.791667, 0.833333, 0.875, 0.916667, 0.958333, 1.0, 1.04167, 1.08333, 1.125, 1.16667, 1.20833, 1.25, 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),
@@ -52,7 +52,7 @@ mesh = SubResource(1)
 visible = true
 transform = Transform(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, -0.0656638, 0.257309, -0.440554)
 
-[node name="Suzanne002Animation" type="AnimationPlayer" parent="."]
+[node name="AnimationPlayer" type="AnimationPlayer" parent="Suzanne002"]
 
 root_node = NodePath("..:")
 anims/Key.002Action = SubResource(2)

+ 4 - 4
tests/reference_exports/animation_spot_light.escn

@@ -33,7 +33,7 @@ surfaces/0 = {
 step = 0.1
 length = 2.5
 tracks/0/type = "value"
-tracks/0/path = NodePath("Lamp:spot_angle")
+tracks/0/path = NodePath(".:spot_angle")
 tracks/0/interp = 1
 tracks/0/keys = {
 	"times":PoolRealArray(1.25, 1.29167, 1.33333, 1.375, 1.41667, 1.45833, 1.5, 1.54167, 1.58333, 1.625, 1.66667),
@@ -42,7 +42,7 @@ tracks/0/keys = {
 	"values":[7.5, 7.66627, 8.16667, 8.98029, 10.0445, 11.25, 12.4555, 13.5197, 14.3333, 14.8337, 15.0]
 }
 tracks/1/type = "value"
-tracks/1/path = NodePath("Lamp:spot_angle_attenuation")
+tracks/1/path = NodePath(".:spot_angle_attenuation")
 tracks/1/interp = 1
 tracks/1/keys = {
 	"times":PoolRealArray(2.08333, 2.125, 2.16667, 2.20833, 2.25, 2.29167, 2.33333, 2.375, 2.41667, 2.45833, 2.5),
@@ -51,7 +51,7 @@ tracks/1/keys = {
 	"values":[1.25, 1.22455, 1.15385, 1.05482, 0.948365, 0.851064, 0.771871, 0.713281, 0.674157, 0.652157, 0.645161]
 }
 tracks/2/type = "value"
-tracks/2/path = NodePath("Lamp:spot_range")
+tracks/2/path = NodePath(".:spot_range")
 tracks/2/interp = 1
 tracks/2/keys = {
 	"times":PoolRealArray(0.0416667, 0.0833333, 0.125, 0.166667, 0.208333, 0.25, 0.291667, 0.333333, 0.375, 0.416667, 0.458333, 0.5, 0.541667, 0.583333, 0.625, 0.666667, 0.708333, 0.75, 0.791667, 0.833333),
@@ -81,7 +81,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)

文件差异内容过多而无法显示
+ 1 - 1
tests/reference_exports/animation_spot_light_transform.escn


+ 5 - 5
tests/reference_exports/animation_sun.escn

@@ -23,7 +23,7 @@ surfaces/0 = {
 step = 0.1
 length = 3.33333
 tracks/0/type = "value"
-tracks/0/path = NodePath("Sun:light_negative")
+tracks/0/path = NodePath(".:light_negative")
 tracks/0/interp = 0
 tracks/0/keys = {
 	"times":PoolRealArray(2.5, 2.91667, 3.33333),
@@ -32,7 +32,7 @@ tracks/0/keys = {
 	"values":[false, true, true]
 }
 tracks/1/type = "value"
-tracks/1/path = NodePath("Sun:light_specular")
+tracks/1/path = NodePath(".:light_specular")
 tracks/1/interp = 0
 tracks/1/keys = {
 	"times":PoolRealArray(1.66667, 2.08333),
@@ -41,7 +41,7 @@ tracks/1/keys = {
 	"values":[1.0, 0.0]
 }
 tracks/2/type = "value"
-tracks/2/path = NodePath("Sun:light_energy")
+tracks/2/path = NodePath(".:light_energy")
 tracks/2/interp = 1
 tracks/2/keys = {
 	"times":PoolRealArray(0.833333, 0.875, 0.916667, 0.958333, 1.0, 1.04167, 1.08333, 1.125, 1.16667, 1.20833, 1.25),
@@ -50,7 +50,7 @@ tracks/2/keys = {
 	"values":[1.0, 1.08868, 1.35556, 1.78949, 2.35705, 3.0, 3.64295, 4.21051, 4.64444, 4.91132, 5.0]
 }
 tracks/3/type = "value"
-tracks/3/path = NodePath("Sun:light_color")
+tracks/3/path = NodePath(".:light_color")
 tracks/3/interp = 1
 tracks/3/keys = {
 	"times":PoolRealArray(0.0416667, 0.0833333, 0.125, 0.166667, 0.208333, 0.25, 0.291667, 0.333333, 0.375, 0.416667),
@@ -77,7 +77,7 @@ transform = Transform(1.0, 0.0, 0.0, 0.0, -4.37114e-08, 1.0, 0.0, -1.0, -4.37114
 shadow_enabled = false
 light_negative = true
 
-[node name="SunAnimation" type="AnimationPlayer" parent="."]
+[node name="AnimationPlayer" type="AnimationPlayer" parent="Sun"]
 
 root_node = NodePath("..:")
 anims/SunAction = SubResource(2)

文件差异内容过多而无法显示
+ 3 - 3
tests/reference_exports/armature_with_non_deform_bone.escn


文件差异内容过多而无法显示
+ 9 - 0
tests/reference_exports/constraint_external_IK.escn


文件差异内容过多而无法显示
+ 9 - 0
tests/reference_exports/constraint_internal_IK.escn


二进制
tests/test_scenes/constraint_external_IK.blend


二进制
tests/test_scenes/constraint_internal_IK.blend


部分文件因为文件数量过多而无法显示