diff options
author | Bastien Montagne <montagne29@wanadoo.fr> | 2015-01-06 23:11:59 +0300 |
---|---|---|
committer | Bastien Montagne <montagne29@wanadoo.fr> | 2015-01-06 23:11:59 +0300 |
commit | ab834eb7555a3d26fcd0f1f378ec63afa1a10d3b (patch) | |
tree | 68061d1a2391239a7c6e0f7472c58e0b888f618d /io_scene_fbx | |
parent | 2b1026150c5943c02c80422f1f33976a80977c36 (diff) |
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. :/
Diffstat (limited to 'io_scene_fbx')
-rw-r--r-- | io_scene_fbx/import_fbx.py | 122 |
1 files changed, 95 insertions, 27 deletions
diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index fa681f8b..d26d2665 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 + if mesh.matrix_geom: + mmat = mmat * mesh.matrix_geom - arm.matrix_basis = amat - me_mat_back = mesh.bl_obj.matrix_basis.copy() - mesh.bl_obj.matrix_basis = mmat + # Now that we have armature and mesh in there (global) bind 'state' (matrix), + # we can compute inverse parenting matrix of the mesh. + me_obj.matrix_parent_inverse = amat.inverted_safe() * mmat * me_obj.matrix_basis.inverted_safe() mod = mesh.bl_obj.modifiers.new(elem_name_utf8, 'ARMATURE') mod.object = arm - mesh.bl_obj.matrix_basis = me_mat_back - arm.matrix_basis = arm_mat_back - # Add bone weights to the deformers for child in self.children: if child.ignore: @@ -2242,7 +2309,7 @@ def load(operator, context, filepath="", tx_bone = array_to_matrix4(tx_bone_elem.props[0]) if tx_bone_elem else None tx_arm_elem = elem_find_first(fbx_cluster, b'TransformAssociateModel', default=None) - tx_arm = array_to_matrix4(tx_arm_elem.props[0]) if tx_arm_elem else Matrix() + tx_arm = array_to_matrix4(tx_arm_elem.props[0]) if tx_arm_elem else None mesh_matrix = tx_mesh armature_matrix = tx_arm @@ -2271,10 +2338,11 @@ def load(operator, context, filepath="", mesh_node = fbx_helper_nodes[object_uuid] if mesh_node: # ---- - # 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 we get a valid mesh matrix (in bone space), store armature and + # mesh global matrices, we need them to compute mesh's matrix_parent_inverse + # 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! mesh_node.armature_setup = (mesh_matrix, armature_matrix) meshes.add(mesh_node) |