[Bf-extensions-cvs] [af0507a] fbx_io_development: FBX export: add shapes anim support.

Bastien Montagne noreply at git.blender.org
Thu Jul 10 21:41:44 CEST 2014


Commit: af0507aeaaf4f047d16c5144604b21ab218a1e34
Author: Bastien Montagne
Date:   Thu Jul 10 21:40:57 2014 +0200
https://developer.blender.org/rBAaf0507aeaaf4f047d16c5144604b21ab218a1e34

FBX export: add shapes anim support.

Code is a bit ugly/messy, but for now will do the trick!

===================================================================

M	io_scene_fbx/import_fbx.py

===================================================================

diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py
index 2e7708d..d4a5378 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -313,7 +313,7 @@ def blen_read_object_transform_do(transform_data):
     )
 
 
-# XXX This might be week, now that we can add vgroups from both bones and shapes, name collisions become
+# XXX This might be weak, now that we can add vgroups from both bones and shapes, name collisions become
 #     more likely, will have to make this more robust!!!
 def add_vgroup_to_objects(vg_indices, vg_weights, vg_name, objects):
     assert(len(vg_indices) == len(vg_weights))
@@ -628,7 +628,7 @@ def blen_read_animations_curves_iter(curves, blen_start_offset, fbx_start_offset
             break
 
 
-def blen_read_animations_action(action, ob, obpath, grpname, cnodes, global_matrix, fps):
+def blen_read_animations_action_object(action, ob, obpath, grpname, cnodes, global_matrix, fps):
     """
     'Bake' loc/rot/scale into the action, taking into account global_matrix if no parent is present.
     """
@@ -704,6 +704,33 @@ def blen_read_animations_action(action, ob, obpath, grpname, cnodes, global_matr
         fc.update()
 
 
+def blen_read_animations_action_shapes(action, kb, cnodes, fps):
+    """
+    'Bake' shapekeys' value into the action.
+    """
+    fbx_curves = []
+    kbpath = kb.path_from_id("value")
+
+    blen_curves = [action.fcurves.new(kbpath, 0, "Key")]
+    for acn_uuid, (curves, fbxprop) in cnodes.items():
+        for ac_uuid, ((fbx_acdata, _blen_data), channel) in curves.items():
+            fbx_curves.append((fbxprop, channel, fbx_acdata))
+
+    # We assume for now blen init point is frame 1.0, while FBX ktime init point is 0.
+    for frame, values in blen_read_animations_curves_iter(fbx_curves, 1.0, 0, fps):
+        value = 0.0
+        for v, (fbxprop, channel, _fbx_acdata) in zip(values, fbx_curves):
+            assert(fbxprop == b'DeformPercent')
+            value = v / 100.0
+
+        for fc, v in zip(blen_curves, tuple(value)):
+            fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
+
+    # Since we inserted our keyframes in 'FAST' mode, we have to update the fcurves now.
+    for fc in blen_curves:
+        fc.update()
+
+
 def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, global_matrix):
     """
     Recreate an action per stack/layer/object combinations.
@@ -712,10 +739,9 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, global
     actions = {}
     for as_uuid, ((fbx_asdata, _blen_data), alayers) in stacks.items():
         stack_name = elem_name_ensure_class(fbx_asdata, b'AnimStack')
-        for al_uuid, ((fbx_aldata, _blen_data), objects) in alayers.items():
+        for al_uuid, ((fbx_aldata, _blen_data), objects, shapes) in alayers.items():
             layer_name = elem_name_ensure_class(fbx_aldata, b'AnimLayer')
             for ob, cnodes in objects.items():
-                # We want to create actions for objects, but for bones we 'reuse' armatures' actions!
                 id_data = ob.id_data
                 key = (as_uuid, al_uuid, id_data)
                 action = actions.get(key)
@@ -723,14 +749,26 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, global
                     action_name = "|".join((id_data.name, stack_name, layer_name))
                     actions[key] = action = bpy.data.actions.new(action_name)
                     action.use_fake_user = True
+                # We want to create actions for objects, but for bones we 'reuse' armatures' actions!
                 if id_data == ob:
                     obpath = ""
                     grpname = None
                 else:
                     obpath = ob.path_from_id() + "."
                     grpname = ob.name
-                blen_read_animations_action(action, ob, obpath, grpname, cnodes, global_matrix, scene.render.fps)
-
+                blen_read_animations_action_object(action, ob, obpath, grpname, cnodes, global_matrix, scene.render.fps)
+
+            print(shapes)
+            for keyblocks, cnodes in shapes.values():
+                for kb in keyblocks:
+                    id_data = kb.id_data
+                    key = (as_uuid, al_uuid, id_data)
+                    action = actions.get(key)
+                    if action is None:
+                        action_name = "|".join((id_data.name, stack_name, layer_name))
+                        actions[key] = action = bpy.data.actions.new(action_name)
+                        action.use_fake_user = True
+                    blen_read_animations_action_shapes(action, kb, cnodes, scene.render.fps)
 
 # ----
 # Mesh
@@ -1129,6 +1167,8 @@ def blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene, global_matri
     assert(len(vgweights) == len(indices) == len(dvcos))
     create_vg = bool(set(vgweights) - {1.0})
 
+    keyblocks = []
+
     for me, objects in meshes:
         vcos = tuple((idx, me.vertices[idx].co + Vector(dvco)) for idx, dvco in zip(indices, dvcos))
         objects = list({blen_o for fbx_o, blen_o in objects})
@@ -1149,6 +1189,10 @@ def blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene, global_matri
             add_vgroup_to_objects(indices, vgweights, elem_name_utf8, objects)
             kb.vertex_group = elem_name_utf8
 
+        keyblocks.append(kb)
+
+    return keyblocks
+
 
 # --------
 # Material
@@ -1732,7 +1776,7 @@ def load(operator, context, filepath="",
     # Now that we have objects...
 
     # I) We can handle shapes.
-    shapes = {}  # No used yet, will be needed for skeys animations...
+    blend_shape_channels = {}  # We do not need Shapes themselves, but BlendShapeChannels, for anim.
     def _():
         fbx_tmpl = fbx_template_get((b'Geometry', b'KFbxShape'))
 
@@ -1742,7 +1786,6 @@ def load(operator, context, filepath="",
                 continue
 
             # shape -> blendshapechannel -> blendshape -> mesh.
-            blend_channels = []
             for bc_uuid, bc_ctype in fbx_connection_map.get(s_uuid, ()):
                 if bc_ctype.props[0] != b'OO':
                     continue
@@ -1777,10 +1820,10 @@ def load(operator, context, filepath="",
                             objects.append(o_item)
                         meshes.append((bl_mdata, objects))
                     # BlendShape deformers are only here to connect BlendShapeChannels to meshes, nothing else to do.
-                blend_channels.append((fbx_bcdata, meshes))
 
-                blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene, global_matrix)
-            shapes[s_uuid] = blend_channels
+                # keyblocks is a list of tuples (mesh, keyblock) matching that shape/blendshapechannel, for animation.
+                keyblocks = blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene, global_matrix)
+                blend_shape_channels[bc_uuid] = keyblocks
     _(); del _
 
     # II) We can finish armatures processing.
@@ -1840,6 +1883,8 @@ def load(operator, context, filepath="",
         fbx_tmpl_alayer = fbx_template_get((b'AnimationLayer', b'FbxAnimLayer'))
         stacks = {}
 
+        print(blend_shape_channels)
+
         # AnimationStacks.
         for as_uuid, fbx_asitem in fbx_table_nodes.items():
             fbx_asdata, _blen_data = fbx_asitem
@@ -1863,7 +1908,7 @@ def load(operator, context, filepath="",
                 continue
             for as_uuid in get_astacks_from_alayer(al_uuid):
                 _fbx_asitem, alayers = stacks[as_uuid]
-                alayers[al_uuid] = (fbx_alitem, {})
+                alayers[al_uuid] = (fbx_alitem, {}, {})  # objects and shapes.
 
         # AnimationCurveNodes (also the ones linked to actual animated data!).
         curvenodes = {}
@@ -1873,16 +1918,21 @@ def load(operator, context, filepath="",
                 continue
             cnode = curvenodes[acn_uuid] = {}
             objs = []
-            for ob_uuid, ob_ctype in fbx_connection_map.get(acn_uuid, ()):
-                if ob_ctype.props[0] != b'OP':
-                    continue
-                lnk_prop = ob_ctype.props[3]
-                if lnk_prop not in {b'Lcl Translation', b'Lcl Rotation', b'Lcl Scaling'}:
+            shps = []
+            for n_uuid, n_ctype in fbx_connection_map.get(acn_uuid, ()):
+                if n_ctype.props[0] != b'OP':
                     continue
-                ob = fbx_table_nodes[ob_uuid][1]
-                if ob is None:
-                    continue
-                objs.append((ob, lnk_prop))
+                lnk_prop = n_ctype.props[3]
+                if lnk_prop in {b'Lcl Translation', b'Lcl Rotation', b'Lcl Scaling'}:
+                    ob = fbx_table_nodes[n_uuid][1]
+                    if ob is None:
+                        continue
+                    objs.append((ob, lnk_prop))
+                elif lnk_prop == b'DeformPercent':  # Shape keys.
+                    keyblocks = blend_shape_channels.get(n_uuid)
+                    if keyblocks is None:
+                        continue
+                    shps.append(keyblocks)
             for al_uuid, al_ctype in fbx_connection_map.get(acn_uuid, ()):
                 if al_ctype.props[0] != b'OO':
                     continue
@@ -1890,11 +1940,13 @@ def load(operator, context, filepath="",
                 if fbx_aldata is None or fbx_aldata.id != b'AnimationLayer' or fbx_aldata.props[2] != b'':
                     continue
                 for as_uuid in get_astacks_from_alayer(al_uuid):
-                    _fbx_alitem, objects = stacks[as_uuid][1][al_uuid]
+                    _fbx_alitem, objects, shapes = stacks[as_uuid][1][al_uuid]
                     assert(_fbx_alitem == fbx_alitem)
                     for ob, ob_prop in objs:
                         # No need to keep curvenode FBX data here, contains nothing useful for us.
                         objects.setdefault(ob, {})[acn_uuid] = (cnode, ob_prop)
+                    print(shps)
+                    shapes[acn_uuid] = [(keyblocks, cnode) for keyblocks in shps]
 
         # AnimationCurves (real animation data).
     

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list