Skip to content
Snippets Groups Projects
fbx_utils.py 49.2 KiB
Newer Older
  • Learn to ignore specific revisions
  •                     return ObjectWrapper(bo_par, self.bdata.parent)
                    else:  # Fallback to mere object parenting.
                        return ObjectWrapper(self.bdata.parent)
                else:
                    # Mere object parenting.
                    return ObjectWrapper(self.bdata.parent)
    
            elif self._tag == 'DP':
    
                return ObjectWrapper(self._ref)
    
            else:  # self._tag == 'BO'
                return ObjectWrapper(self.bdata.parent, self._ref) or ObjectWrapper(self._ref)
        parent = property(get_parent)
    
    
        def get_bdata_pose_bone(self):
            if self._tag == 'BO':
                return self._ref.pose.bones[self.bdata.name]
            return None
        bdata_pose_bone = property(get_bdata_pose_bone)
    
    
        def get_matrix_local(self):
            if self._tag == 'OB':
                return self.bdata.matrix_local.copy()
            elif self._tag == 'DP':
    
                return self._ref.matrix_world.inverted_safe() @ self._dupli_matrix
    
            else:  # 'BO', current pose
                # PoseBone.matrix is in armature space, bring in back in real local one!
                par = self.bdata.parent
    
                par_mat_inv = self._ref.pose.bones[par.name].matrix.inverted_safe() if par else Matrix()
    
                return par_mat_inv @ self._ref.pose.bones[self.bdata.name].matrix
    
        matrix_local = property(get_matrix_local)
    
        def get_matrix_global(self):
            if self._tag == 'OB':
                return self.bdata.matrix_world.copy()
            elif self._tag == 'DP':
                return self._dupli_matrix
            else:  # 'BO', current pose
    
                return self._ref.matrix_world @ self._ref.pose.bones[self.bdata.name].matrix
    
        matrix_global = property(get_matrix_global)
    
        def get_matrix_rest_local(self):
            if self._tag == 'BO':
                # Bone.matrix_local is in armature space, bring in back in real local one!
                par = self.bdata.parent
    
                par_mat_inv = par.matrix_local.inverted_safe() if par else Matrix()
    
                return par_mat_inv @ self.bdata.matrix_local
    
                return self.matrix_local.copy()
    
        matrix_rest_local = property(get_matrix_rest_local)
    
        def get_matrix_rest_global(self):
            if self._tag == 'BO':
    
                return self._ref.matrix_world @ self.bdata.matrix_local
    
                return self.matrix_global.copy()
    
        matrix_rest_global = property(get_matrix_rest_global)
    
    
        # #### Transform and helpers
    
        def has_valid_parent(self, objects):
            par = self.parent
            if par in objects:
                if self._tag == 'OB':
                    par_type = self.bdata.parent_type
                    if par_type in {'OBJECT', 'BONE'}:
                        return True
                    else:
                        print("Sorry, “{}” parenting type is not supported".format(par_type))
                        return False
                return True
            return False
    
        def use_bake_space_transform(self, scene_data):
    
            # NOTE: Only applies to object types supporting this!!! Currently, only meshes and the like...
    
            # TODO: Check whether this can work for bones too...
    
            return (scene_data.settings.bake_space_transform and self._tag in {'OB', 'DP'} and
    
                    self.bdata.type in BLENDER_OBJECT_TYPES_MESHLIKE | {'EMPTY'})
    
    
        def fbx_object_matrix(self, scene_data, rest=False, local_space=False, global_space=False):
            """
            Generate object transform matrix (*always* in matching *FBX* space!).
            If local_space is True, returned matrix is *always* in local space.
            Else if global_space is True, returned matrix is always in world space.
            If both local_space and global_space are False, returned matrix is in parent space if parent is valid,
            else in world space.
            Note local_space has precedence over global_space.
            If rest is True and object is a Bone, returns matching rest pose transform instead of current pose one.
            Applies specific rotation to bones, lamps and cameras (conversion Blender -> FBX).
            """
            # Objects which are not bones and do not have any parent are *always* in global space
            # (unless local_space is True!).
            is_global = (not local_space and
                         (global_space or not (self._tag in {'DP', 'BO'} or self.has_valid_parent(scene_data.objects))))
    
    
            # Objects (meshes!) parented to armature are not parented to anything in FBX, hence we need them
            # in global space, which is their 'virtual' local space...
            is_global = is_global or self.parented_to_armature
    
    
            # Since we have to apply corrections to some types of object, we always need local Blender space here...
            matrix = self.matrix_rest_local if rest else self.matrix_local
            parent = self.parent
    
            # Bones, lamps and cameras need to be rotated (in local space!).
    
            if self._tag == 'BO':
    
                # If we have a bone parent we need to undo the parent correction.
                if not is_global and scene_data.settings.bone_correction_matrix_inv and parent and parent.is_bone:
    
                    matrix = scene_data.settings.bone_correction_matrix_inv @ matrix
    
                # Apply the bone correction.
                if scene_data.settings.bone_correction_matrix:
    
                    matrix = matrix @ scene_data.settings.bone_correction_matrix
    
            elif self.bdata.type == 'LIGHT':
    
                matrix = matrix @ MAT_CONVERT_LIGHT
    
                matrix = matrix @ MAT_CONVERT_CAMERA
    
            if self._tag in {'DP', 'OB'} and parent:
                if parent._tag == 'BO':
                    # In bone parent case, we get transformation in **bone tip** space (sigh).
                    # Have to bring it back into bone root, which is FBX expected value.
    
                    matrix = Matrix.Translation((0, (parent.bdata.tail - parent.bdata.head).length, 0)) @ matrix
    
            # Our matrix is in local space, time to bring it in its final desired space.
            if parent:
                if is_global:
                    # Move matrix to global Blender space.
    
                    matrix = (parent.matrix_rest_global if rest else parent.matrix_global) @ matrix
    
                elif parent.use_bake_space_transform(scene_data):
                    # Blender's and FBX's local space of parent may differ if we use bake_space_transform...
                    # Apply parent's *Blender* local space...
    
                    matrix = (parent.matrix_rest_local if rest else parent.matrix_local) @ matrix
    
                    # ...and move it back into parent's *FBX* local space.
                    par_mat = parent.fbx_object_matrix(scene_data, rest=rest, local_space=True)
    
                    matrix = par_mat.inverted_safe() @ matrix
    
    
            if self.use_bake_space_transform(scene_data):
                # If we bake the transforms we need to post-multiply inverse global transform.
                # This means that the global transform will not apply to children of this transform.
    
                matrix = matrix @ scene_data.settings.global_matrix_inv
    
            if is_global:
                # In any case, pre-multiply the global matrix to get it in FBX global space!
    
                matrix = scene_data.settings.global_matrix @ matrix
    
    
            return matrix
    
        def fbx_object_tx(self, scene_data, rest=False, rot_euler_compat=None):
            """
            Generate object transform data (always in local space when possible).
            """
            matrix = self.fbx_object_matrix(scene_data, rest=rest)
            loc, rot, scale = matrix.decompose()
            matrix_rot = rot.to_matrix()
            # quat -> euler, we always use 'XYZ' order, use ref rotation if given.
            if rot_euler_compat is not None:
                rot = rot.to_euler('XYZ', rot_euler_compat)
            else:
                rot = rot.to_euler('XYZ')
            return loc, rot, scale, matrix, matrix_rot
    
    
        # #### _tag dependent...
    
        def get_is_object(self):
            return self._tag == 'OB'
        is_object = property(get_is_object)
    
        def get_is_dupli(self):
            return self._tag == 'DP'
        is_dupli = property(get_is_dupli)
    
        def get_is_bone(self):
            return self._tag == 'BO'
        is_bone = property(get_is_bone)
    
        def get_type(self):
            if self._tag in {'OB', 'DP'}:
                return self.bdata.type
            return ...
        type = property(get_type)
    
        def get_armature(self):
            if self._tag == 'BO':
                return ObjectWrapper(self._ref)
            return None
        armature = property(get_armature)
    
        def get_bones(self):
            if self._tag == 'OB' and self.bdata.type == 'ARMATURE':
                return (ObjectWrapper(bo, self.bdata) for bo in self.bdata.data.bones)
            return ()
        bones = property(get_bones)
    
        def get_material_slots(self):
            if self._tag in {'OB', 'DP'}:
                return self.bdata.material_slots
            return ()
        material_slots = property(get_material_slots)
    
    
        def is_deformed_by_armature(self, arm_obj):
            if not (self.is_object and self.type == 'MESH'):
                return False
    
            if self.parent == arm_obj and self.bdata.parent_type == 'ARMATURE':
    
                if mod.type == 'ARMATURE' and mod.object in {arm_obj.bdata, arm_obj.bdata.proxy}:
    
        # #### Duplis...
    
        def dupli_list_gen(self, depsgraph):
    
            if self._tag == 'OB' and self.bdata.is_instancer:
    
                return (ObjectWrapper(dup) for dup in depsgraph.object_instances
                                           if dup.parent and ObjectWrapper(dup.parent.original) == self)
    
            return ()
    
    
    def fbx_name_class(name, cls):
        return FBX_NAME_CLASS_SEP.join((name, cls))
    
    
    
    # ##### Top-level FBX data container. #####
    
    
    # Helper sub-container gathering all exporter settings related to media (texture files).
    
    FBXExportSettingsMedia = namedtuple("FBXExportSettingsMedia", (
    
        "path_mode", "base_src", "base_dst", "subdir",
    
        "embed_textures", "copy_set", "embedded_set",
    
    ))
    
    # Helper container gathering all exporter settings.
    
    FBXExportSettings = namedtuple("FBXExportSettings", (
    
        "report", "to_axes", "global_matrix", "global_scale", "apply_unit_scale", "unit_scale",
    
        "bake_space_transform", "global_matrix_inv", "global_matrix_inv_transposed",
    
        "context_objects", "object_types", "use_mesh_modifiers", "use_mesh_modifiers_render",
    
        "mesh_smooth_type", "use_subsurf", "use_mesh_edges", "use_tspace",
    
        "armature_nodetype", "use_armature_deform_only", "add_leaf_bones",
        "bone_correction_matrix", "bone_correction_matrix_inv",
    
        "bake_anim", "bake_anim_use_all_bones", "bake_anim_use_nla_strips", "bake_anim_use_all_actions",
    
        "bake_anim_step", "bake_anim_simplify_factor", "bake_anim_force_startend_keying",
    
        "use_metadata", "media_settings", "use_custom_props",
    
    ))
    
    # Helper container gathering some data we need multiple times:
    #     * templates.
    #     * settings, scene.
    #     * objects.
    #     * object data.
    #     * skinning data (binding armature/mesh).
    #     * animations.
    
    FBXExportData = namedtuple("FBXExportData", (
    
        "templates", "templates_users", "connections",
    
        "settings", "scene", "depsgraph", "objects", "animations", "animated", "frame_start", "frame_end",
    
        "data_empties", "data_lights", "data_cameras", "data_meshes", "mesh_material_indices",
    
        "data_bones", "data_leaf_bones", "data_deformers_skin", "data_deformers_shape",
    
        "data_world", "data_materials", "data_textures", "data_videos",
    ))
    
    
    # Helper container gathering all importer settings.
    FBXImportSettings = namedtuple("FBXImportSettings", (
        "report", "to_axes", "global_matrix", "global_scale",
    
        "bake_space_transform", "global_matrix_inv", "global_matrix_inv_transposed",
    
        "use_custom_normals", "use_image_search",
    
        "use_custom_props", "use_custom_props_enum_as_string",
    
        "nodal_material_wrap_map", "image_cache",
    
        "ignore_leaf_bones", "force_connect_children", "automatic_bone_orientation", "bone_correction_matrix",
        "use_prepost_rot",