[Bf-extensions-cvs] [404e8a7] fbx_io_development: FBX import: Add static armature support (no animation yet).

Bastien Montagne noreply at git.blender.org
Sun Jul 13 18:36:14 CEST 2014


Commit: 404e8a71d202763bf4c5f8ea1d7156d338462b0b
Author: Bastien Montagne
Date:   Sun Jul 13 18:17:32 2014 +0200
https://developer.blender.org/rBA404e8a71d202763bf4c5f8ea1d7156d338462b0b

FBX import: Add static armature support (no animation yet).

Please note following limitations:
* FBX 'Bones' orientation status is currently unclear - they *seem* to be -X oriented,
  wich means they would need to be corrected (in export as well). Could not get this
  workling yet, though, and it does not seems to bother much apps like Unity?
  This is still being investigated.
* We only support 'Deformer' based skinning, not 'BindPose' one. Why FBX keeps two different
  systems here, sometimes mixing them happily? And why BindPose has no weighting system?

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

M	io_scene_fbx/import_fbx.py

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

diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py
index c5a12e0..9210ad0 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -313,6 +313,20 @@ def blen_read_object_transform_do(transform_data):
     )
 
 
+# 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))
+    if vg_indices:
+        for obj in objects:
+            # We replace/override here...
+            vg = obj.vertex_groups.get(vg_name)
+            if vg is None:
+                vg = obj.vertex_groups.new(vg_name)
+            for i, w in zip(vg_indices, vg_weights):
+                vg.add((i,), w, 'REPLACE')
+
+
 def blen_read_object_transform_preprocess(fbx_props, fbx_obj, rot_alt_mat):
     # This is quite involved, 'fbxRNode.cpp' from openscenegraph used as a reference
     const_vector_zero_3d = 0.0, 0.0, 0.0
@@ -390,6 +404,180 @@ def blen_read_object(fbx_tmpl, fbx_obj, object_data):
     return obj
 
 
+# --------
+# Armature
+
+def blen_read_armatures_add_bone(bl_obj, bl_arm, bones, b_uuid, matrices, fbx_tmpl_model):
+    from mathutils import Matrix, Vector
+
+    b_item, bsize, p_uuid, clusters = bones[b_uuid]
+    fbx_bdata, bl_bname = b_item
+    if bl_bname is not None:
+        return bl_arm.edit_bones[bl_bname]  # Have already been created...
+
+    p_ebo = None
+    if p_uuid is not None:
+        # Recurse over parents!
+        p_ebo = blen_read_armatures_add_bone(bl_obj, bl_arm, bones, p_uuid, matrices, fbx_tmpl_model)
+
+    if clusters:
+        # Note in some cases, one bone can have several clusters (kind of LoD?), in Blender we'll always
+        # use only the first, for now.
+        fbx_cdata, meshes, objects = clusters[0]
+        objects = {blen_o for fbx_o, blen_o in objects}
+
+        # We assume matrices in cluster are rest pose of bones (they are in Global space!).
+        # TransformLink is matrix of bone, in global space.
+        # TransformAssociateModel is matrix of armature, in global space (at bind time).
+        elm = elem_find_first(fbx_cdata, b'Transform', default=None)
+        mmat_bone = array_to_matrix4(elm.props[0]) if elm is not None else None
+        elm = elem_find_first(fbx_cdata, b'TransformLink', default=None)
+        bmat_glob = array_to_matrix4(elm.props[0]) if elm is not None else Matrix()
+        elm = elem_find_first(fbx_cdata, b'TransformAssociateModel', default=None)
+        amat_glob = array_to_matrix4(elm.props[0]) if elm is not None else Matrix()
+
+        mmat_glob = bmat_glob * mmat_bone
+
+        # We seek for matrix of bone in armature space...
+        bmat_arm = amat_glob.inverted() * bmat_glob
+
+        # Bone correction, works here...
+        bmat_loc = (p_ebo.matrix.inverted() * bmat_arm) if p_ebo else bmat_arm
+        bmat_loc = bmat_loc * MAT_CONVERT_BONE
+        bmat_arm = (p_ebo.matrix * bmat_loc) if p_ebo else bmat_loc
+    else:
+        # Armature bound to no mesh...
+        fbx_cdata, meshes, objects = (None, (), ())
+        mmat_bone = None
+        amat_glob = bl_obj.matrix_world
+
+        fbx_props = (elem_find_first(fbx_bdata, b'Properties70'),
+                     elem_find_first(fbx_tmpl_model, b'Properties70', fbx_elem_nil))
+        assert(fbx_props[0] is not None)
+
+        # Bone correction, works here...
+        transform_data = blen_read_object_transform_preprocess(fbx_props, fbx_bdata, MAT_CONVERT_BONE)
+        bmat_loc = blen_read_object_transform_do(transform_data)
+        # Bring back matrix in armature space.
+        bmat_arm = (p_ebo.matrix * bmat_loc) if p_ebo else bmat_loc
+
+    # ----
+    # Now, create the (edit)bone.
+    bone_name = elem_name_ensure_class(fbx_bdata, b'Model')
+
+    ebo = bl_arm.edit_bones.new(name=bone_name)
+    bone_name = ebo.name  # Might differ from FBX bone name!
+    b_item[1] = bone_name  # since ebo is only valid in Edit mode... :/
+
+    # So that our bone gets its final length, but still Y-aligned in armature space.
+    ebo.tail = Vector((0.0, 1.0, 0.0)) * bsize
+    # And rotate/move it to its final "rest pose".
+    ebo.matrix = bmat_arm.normalized()
+
+    # Connection to parent.
+    if p_ebo is not None:
+        ebo.parent = p_ebo
+        if similar_values_iter(p_ebo.tail, ebo.head):
+            ebo.use_connect = True
+
+    if fbx_cdata is not None:
+        # ----
+        # Add a new vgroup to the meshes (their objects, actually!).
+        # Quite obviously, only one mesh is expected...
+        indices = elem_prop_first(elem_find_first(fbx_cdata, b'Indexes', default=None), default=())
+        weights = elem_prop_first(elem_find_first(fbx_cdata, b'Weights', default=None), default=())
+        add_vgroup_to_objects(indices, weights, bone_name, objects)
+
+    # ----
+    # If we get a valid mesh matrix (in bone space), store armature and mesh global matrices, we need to set temporarily
+    # both objects to those matrices when actually binding them via the modifier.
+    # Note we assume all bones were bound with the same mesh/armature (global) matrix, we do not support otherwise
+    # in Blender anyway!
+    if mmat_bone is not None:
+        for obj in objects:
+            if obj in matrices:
+                continue
+            matrices[obj] = (amat_glob, mmat_glob)
+
+    return ebo
+
+
+def blen_read_armatures(fbx_tmpl, armatures, fbx_bones_to_fake_object, scene, global_matrix):
+    from mathutils import Matrix
+
+    if global_matrix is None:
+        global_matrix = Matrix()
+
+    for a_item, bones in armatures:
+        fbx_adata, bl_adata = a_item
+        matrices = {}
+
+        # ----
+        # Armature data.
+        elem_name_utf8 = elem_name_ensure_class(fbx_adata, b'Model')
+        bl_arm = bpy.data.armatures.new(name=elem_name_utf8)
+
+        # Need to create the object right now, since we can only add bones in Edit mode... :/
+        assert(a_item[1] is None)
+
+        if fbx_adata.props[2] in {b'LimbNode', b'Root'}:
+            # rootbone-as-armature case...
+            fbx_bones_to_fake_object[fbx_adata.props[0]] = bl_adata = blen_read_object(fbx_tmpl, fbx_adata, bl_arm)
+            # reset transform.
+            bl_adata.matrix_basis = Matrix()
+        else:
+            bl_adata = a_item[1] = blen_read_object(fbx_tmpl, fbx_adata, bl_arm)
+
+        # Instantiate in scene.
+        obj_base = scene.objects.link(bl_adata)
+        obj_base.select = True
+
+        # Switch to Edit mode.
+        scene.objects.active = bl_adata
+        bpy.ops.object.mode_set(mode='EDIT')
+
+        for b_uuid in bones:
+            blen_read_armatures_add_bone(bl_adata, bl_arm, bones, b_uuid, matrices, fbx_tmpl)
+
+        bpy.ops.object.mode_set(mode='OBJECT')
+
+        # Bind armature to objects.
+        arm_mat_back = bl_adata.matrix_basis.copy()
+        for ob_me, (amat, mmat) in matrices.items():
+            # bring global armature & mesh matrices into *Blender* global space.
+            amat = global_matrix * amat
+            mmat = global_matrix * mmat
+
+            bl_adata.matrix_basis = amat
+            me_mat_back = ob_me.matrix_basis.copy()
+            ob_me.matrix_basis = mmat
+
+            mod = ob_me.modifiers.new(elem_name_utf8, 'ARMATURE')
+            mod.object = bl_adata
+
+            ob_me.parent = bl_adata
+            ob_me.matrix_basis = me_mat_back
+        bl_adata.matrix_basis = arm_mat_back
+
+        # Set Pose transformations...
+        for b_item, _b_size, _p_uuid, _clusters in bones.values():
+            fbx_bdata, bl_bname = b_item
+            fbx_props = (elem_find_first(fbx_bdata, b'Properties70'),
+                         elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
+            assert(fbx_props[0] is not None)
+
+            pbo = b_item[1] = bl_adata.pose.bones[bl_bname]
+            transform_data = object_tdata_cache.get(pbo)
+            if transform_data is None:
+                # Bone correction, gives a mess as result. :(
+                transform_data = blen_read_object_transform_preprocess(fbx_props, fbx_bdata, MAT_CONVERT_BONE)
+                object_tdata_cache[pbo] = transform_data
+            mat = blen_read_object_transform_do(transform_data)
+            if pbo.parent:
+                # Bring back matrix in armature space.
+                mat = pbo.parent.matrix * mat
+            pbo.matrix = mat
+
 
 # ----
 # Mesh
@@ -1232,14 +1420,133 @@ def load(operator, context, filepath="",
     def connection_filter_reverse(fbx_uuid, fbx_id):
         return connection_filter_ex(fbx_uuid, fbx_id, fbx_connection_map_reverse)
 
+    # Armatures pre-processing!
+    fbx_objects_ignore = set()
+    fbx_objects_parent_ignore = set()
+    # Arg! In some case, root bone is used as armature as well, in Blender we have to 'insert'
+    # an armature object between them, so to handle possible parents of root bones we need a mapping
+    # from root bone uuid to Blender's object...
+    fbx_bones_to_fake_object = dict()
+    armatures = []
+    def _():
+        nonlocal fbx_objects_ignore, fbx_objects_parent_ignore
+        for a_uuid, a_item in fbx_table_nodes.items():
+            root_bone = False
+            fbx_adata, bl_adata = a_item = fbx_table_nodes.get(a_uuid, (None, None))
+            if fbx_adata is None or fbx_adata.id != b'Model':
+                continue
+            elif fbx_adata.props[2] != b'Null':
+                if fbx_adata.props[2] not in {b'LimbNode', b'Root'}:
+                    continue
+                # In some cases, armatures have no root 'Null' object, we have to consider all root bones
+                # as armatures in this case. :/
+                root_bone = True
+                for p_uuid, p_ctype in fbx_connection_map.get(a_uuid, ()):
+                    if p_ctype.props[0] != b'OO':
+                        continue
+                    fbx_pdata, bl_pdata = p_item = fbx_table_nodes.get(p_uuid, (None, None))
+                    if (fbx_pdata and fbx_pdata.id == b'Model' and fbx_pdata.props[2] in {b'LimbNode', b'Root

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list