[Bf-extensions-cvs] [ab834eb] master: Fix T43141: FBX import issue with rigged character.

Bastien Montagne noreply at git.blender.org
Tue Jan 6 21:20:30 CET 2015


Commit: ab834eb7555a3d26fcd0f1f378ec63afa1a10d3b
Author: Bastien Montagne
Date:   Tue Jan 6 21:11:59 2015 +0100
Branches: master
https://developer.blender.org/rBAab834eb7555a3d26fcd0f1f378ec63afa1a10d3b

Fix T43141: FBX import issue with rigged character.

This report actually showed two different issues.

First, handling of bind poses was basically useless trash. Blender does not
have any real concept of bind pose, it uses parenting instead. So what we really
need to do here is set matrix_parent_inverse according to bind matrices of armature
and mesh.

Second, mighty FBX actually has *two* different transform models (which combine into
a nice monster using 13 matrices...). Added details in (huge) comment, but short story
is FBX uses by default Maya tx model, but can also store Max one, which uses some kind
of local diff additional transform (similar to our dloc & co I think). So had
to add support for that too, which makes our code even more light and beautiful!

Note those 'geometric transformations' might entertain us again, quite not sure I
caught all cases where we have to take them into account. :/

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

M	io_scene_fbx/import_fbx.py

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

diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py
index fa681f8..d26d266 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -270,9 +270,9 @@ from collections import namedtuple
 
 
 FBXTransformData = namedtuple("FBXTransformData", (
-    "loc",
-    "rot", "rot_ofs", "rot_piv", "pre_rot", "pst_rot", "rot_ord", "rot_alt_mat",
-    "sca", "sca_ofs", "sca_piv",
+    "loc", "geom_loc", 
+    "rot", "rot_ofs", "rot_piv", "pre_rot", "pst_rot", "rot_ord", "rot_alt_mat", "geom_rot",
+    "sca", "sca_ofs", "sca_piv", "geom_sca",
 ))
 
 
@@ -334,14 +334,57 @@ def blen_read_custom_properties(fbx_obj, blen_obj, settings):
 
 
 def blen_read_object_transform_do(transform_data):
+    # This is a nightmare. FBX SDK uses Maya way to compute the transformation matrix of a node - utterly simple:
+    #
+    #     WorldTransform = ParentWorldTransform * T * Roff * Rp * Rpre * R * Rpost * Rp-1 * Soff * Sp * S * Sp-1
+    #
+    # Where all those terms are 4 x 4 matrices that contain:
+    #     WorldTransform: Transformation matrix of the node in global space.
+    #     ParentWorldTransform: Transformation matrix of the parent node in global space.
+    #     T: Translation
+    #     Roff: Rotation offset
+    #     Rp: Rotation pivot
+    #     Rpre: Pre-rotation
+    #     R: Rotation
+    #     Rpost: Post-rotation
+    #     Rp-1: Inverse of the rotation pivot
+    #     Soff: Scaling offset
+    #     Sp: Scaling pivot
+    #     S: Scaling
+    #     Sp-1: Inverse of the scaling pivot
+    #
+    # But it was still too simple, and FBX notion of compatibility is... quite specific. So we also have to
+    # support 3DSMax way:
+    #
+    #     WorldTransform = ParentWorldTransform * T * R * S * OT * OR * OS
+    #
+    # Where all those terms are 4 x 4 matrices that contain:
+    #     WorldTransform: Transformation matrix of the node in global space
+    #     ParentWorldTransform: Transformation matrix of the parent node in global space
+    #     T: Translation
+    #     R: Rotation
+    #     S: Scaling
+    #     OT: Geometric transform translation
+    #     OR: Geometric transform rotation
+    #     OS: Geometric transform translation
+    #
+    # Notes:
+    #     Geometric transformations ***are not inherited***: ParentWorldTransform does not contain the OT, OR, OS
+    #     of WorldTransform's parent node.
+    #
+    # Taken from http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/
+    #            index.html?url=WS1a9193826455f5ff1f92379812724681e696651.htm,topicNumber=d0e7429
+
     # translation
     lcl_translation = Matrix.Translation(transform_data.loc)
+    geom_loc = Matrix.Translation(transform_data.geom_loc)
 
     # rotation
     to_rot = lambda rot, rot_ord: Euler(convert_deg_to_rad_iter(rot), rot_ord).to_matrix().to_4x4()
     lcl_rot = to_rot(transform_data.rot, transform_data.rot_ord) * transform_data.rot_alt_mat
     pre_rot = to_rot(transform_data.pre_rot, transform_data.rot_ord)
     pst_rot = to_rot(transform_data.pst_rot, transform_data.rot_ord)
+    geom_rot = to_rot(transform_data.geom_rot, transform_data.rot_ord)
 
     rot_ofs = Matrix.Translation(transform_data.rot_ofs)
     rot_piv = Matrix.Translation(transform_data.rot_piv)
@@ -351,8 +394,10 @@ def blen_read_object_transform_do(transform_data):
     # scale
     lcl_scale = Matrix()
     lcl_scale[0][0], lcl_scale[1][1], lcl_scale[2][2] = transform_data.sca
+    geom_scale = Matrix();
+    geom_scale[0][0], geom_scale[1][1], geom_scale[2][2] = transform_data.geom_sca
 
-    return (
+    base_mat = (
         lcl_translation *
         rot_ofs *
         rot_piv *
@@ -365,6 +410,9 @@ def blen_read_object_transform_do(transform_data):
         lcl_scale *
         sca_piv.inverted_safe()
     )
+    geom_mat = geom_loc * geom_rot * geom_scale
+    # We return mat without 'geometric transforms' too, because it is to be used for children, sigh...
+    return (base_mat * geom_mat, base_mat, geom_mat)
 
 
 # XXX This might be weak, now that we can add vgroups from both bones and shapes, name collisions become
@@ -390,6 +438,10 @@ def blen_read_object_transform_preprocess(fbx_props, fbx_obj, rot_alt_mat, use_p
     rot = list(elem_props_get_vector_3d(fbx_props, b'Lcl Rotation', const_vector_zero_3d))
     sca = list(elem_props_get_vector_3d(fbx_props, b'Lcl Scaling', const_vector_one_3d))
 
+    geom_loc = list(elem_props_get_vector_3d(fbx_props, b'GeometricTranslation', const_vector_zero_3d))
+    geom_rot = list(elem_props_get_vector_3d(fbx_props, b'GeometricRotation', const_vector_zero_3d))
+    geom_sca = list(elem_props_get_vector_3d(fbx_props, b'GeometricScaling', const_vector_one_3d))
+
     rot_ofs = elem_props_get_vector_3d(fbx_props, b'RotationOffset', const_vector_zero_3d)
     rot_piv = elem_props_get_vector_3d(fbx_props, b'RotationPivot', const_vector_zero_3d)
     sca_ofs = elem_props_get_vector_3d(fbx_props, b'ScalingOffset', const_vector_zero_3d)
@@ -418,9 +470,9 @@ def blen_read_object_transform_preprocess(fbx_props, fbx_obj, rot_alt_mat, use_p
         pst_rot = const_vector_zero_3d
         rot_ord = 'XYZ'
 
-    return FBXTransformData(loc,
-                            rot, rot_ofs, rot_piv, pre_rot, pst_rot, rot_ord, rot_alt_mat,
-                            sca, sca_ofs, sca_piv)
+    return FBXTransformData(loc, geom_loc,
+                            rot, rot_ofs, rot_piv, pre_rot, pst_rot, rot_ord, rot_alt_mat, geom_rot,
+                            sca, sca_ofs, sca_piv, geom_sca)
 
 
 # ---------
@@ -547,7 +599,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps):
                     transform_data.rot[channel] = v
                 elif fbxprop == b'Lcl Scaling':
                     transform_data.sca[channel] = v
-            mat = blen_read_object_transform_do(transform_data)
+            mat, _, _ = blen_read_object_transform_do(transform_data)
 
             # compensate for changes in the local matrix during processing
             if item.anim_compensation_matrix:
@@ -1259,9 +1311,10 @@ class FbxImportHelperNode:
     It tries to keep the correction data in one place so it can be applied consistently to the imported data.
     """
 
-    __slots__ = ('_parent', 'anim_compensation_matrix', 'armature_setup', 'bind_matrix', 'bl_bone', 'bl_data', 'bl_obj', 'bone_child_matrix',
-                 'children', 'clusters', 'fbx_elem', 'fbx_name', 'fbx_transform_data', 'fbx_type', 'has_bone_children', 'ignore', 'is_armature',
-                 'is_bone', 'is_root', 'matrix', 'meshes', 'post_matrix', 'pre_matrix')
+    __slots__ = ('_parent', 'anim_compensation_matrix', 'armature_setup', 'bind_matrix',
+                 'bl_bone', 'bl_data', 'bl_obj', 'bone_child_matrix', 'children', 'clusters',
+                 'fbx_elem', 'fbx_name', 'fbx_transform_data', 'fbx_type', 'has_bone_children', 'ignore', 'is_armature',
+                 'is_bone', 'is_root', 'matrix', 'matrix_as_parent', 'matrix_geom', 'meshes', 'post_matrix', 'pre_matrix')
 
     def __init__(self, fbx_elem, bl_data, fbx_transform_data, is_bone):
         self.fbx_name = elem_name_ensure_class(fbx_elem, b'Model') if fbx_elem else 'Unknown'
@@ -1278,7 +1331,10 @@ class FbxImportHelperNode:
         self.ignore = False                     # True for leaf-bones added to the end of some bone chains to set the lengths.
         self.pre_matrix = None                  # correction matrix that needs to be applied before the FBX transform
         self.bind_matrix = None                 # for bones this is the matrix used to bind to the skin
-        self.matrix = blen_read_object_transform_do(fbx_transform_data) if fbx_transform_data else None
+        if fbx_transform_data:
+            self.matrix, self.matrix_as_parent, self.matrix_geom = blen_read_object_transform_do(fbx_transform_data)
+        else:
+            self.matrix, self.matrix_as_parent, self.matrix_geom = (None, None, None)
         self.post_matrix = None                 # correction matrix that needs to be applied after the FBX transform
         self.bone_child_matrix = None           # Objects attached to a bone end not the beginning, this matrix corrects for that
         self.anim_compensation_matrix = None    # a mesh moved in the hierarchy may have a different local matrix. This compensates animations for this.
@@ -1495,8 +1551,14 @@ class FbxImportHelperNode:
         for child in self.children:
             child.find_fake_bones(in_armature)
 
+    def get_world_matrix_as_parent(self):
+        matrix = self.parent.get_world_matrix_as_parent() if self.parent else Matrix()
+        if self.matrix_as_parent:
+            matrix = matrix * self.matrix_as_parent
+        return matrix
+
     def get_world_matrix(self):
-        matrix = self.parent.get_world_matrix() if self.parent else Matrix()
+        matrix = self.parent.get_world_matrix_as_parent() if self.parent else Matrix()
         if self.matrix:
             matrix = matrix * self.matrix
         return matrix
@@ -1794,24 +1856,29 @@ class FbxImportHelperNode:
 
             # Add armature modifiers to the meshes
             if self.meshes:
-                arm_mat_back = arm.matrix_basis.copy()
                 for mesh in self.meshes:
-                    (amat, mmat) = mesh.armature_setup
+                    (mmat, amat) = mesh.armature_setup
+                    me_obj = mesh.bl_obj
 
                     # bring global armature & mesh matrices into *Blender* global space.
-                    amat = settings.global_matrix * amat
+                    # Note: Usage of matrix_geom (local 'diff' transform) here is quite brittle.
+                    #       Among other things, why in hell isn't it taken into account by bindpose & co???
+                    #       Probably because org app (max) handles it completely aside from any parenting stuff,
+                    #       which we obviously cannot do in Blender. :/
+                    amat = settings.global_matrix * (amat if amat is not None else self.bind_matrix)
+                    if self.matrix_geom:
+                        amat = amat * self.matrix_geom
                     mmat = settings.global_matrix * mmat
+                   

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list