Skip to content
Snippets Groups Projects
export_fbx.py 115 KiB
Newer Older
  • Learn to ignore specific revisions
  •                                     mat = mats[f.material_index]
                                    except:
                                        mat = None
    
                                    materials[mat, tex] = material_mapping_local[mat, tex] = None  # should use sets, wait for blender 2.5
    
    
                        else:
                            for mat in mats:
                                # 2.44 use mat.lib too for uniqueness
                                materials[mat, None] = material_mapping_local[mat, None] = None
                            else:
                                materials[None, None] = None
    
    
                            armob = ob.find_armature()
                            blenParentBoneName = None
    
                            # parent bone - special case
                            if (not armob) and ob.parent and ob.parent.type == 'ARMATURE' and \
                                    ob.parent_type == 'BONE':
                                armob = ob.parent
                                blenParentBoneName = ob.parent_bone
    
                            if armob and armob not in ob_arms:
                                ob_arms.append(armob)
    
                            # Warning for scaled, mesh objects with armatures
                            if abs(ob.scale[0] - 1.0) > 0.05 or abs(ob.scale[1] - 1.0) > 0.05 or abs(ob.scale[1] - 1.0) > 0.05:
    
                                operator.report({'WARNING'}, "Object '%s' has a scale of (%.3f, %.3f, %.3f), " \
                                                             "Armature deformation will not work as expected " \
                                                             "(apply Scale to fix)" % ((ob.name,) + tuple(ob.scale)))
    
    
                        else:
                            blenParentBoneName = armob = None
    
                        my_mesh = my_object_generic(ob, mtx)
    
                        my_mesh.blenData = me
                        my_mesh.origData = origData
                        my_mesh.blenMaterials = list(material_mapping_local.keys())
    
                        my_mesh.blenTextures = list(texture_mapping_local.keys())
    
                        # sort the name so we get predictable output, some items may be NULL
    
                        my_mesh.blenMaterials.sort(key=lambda m: (getattr(m[0], "name", ""), getattr(m[1], "name", "")))
                        my_mesh.blenTextures.sort(key=lambda m: getattr(m, "name", ""))
    
    
                        # if only 1 null texture then empty the list
                        if len(my_mesh.blenTextures) == 1 and my_mesh.blenTextures[0] is None:
                            my_mesh.blenTextures = []
    
    
                        my_mesh.fbxArm = armob  # replace with my_object_generic armature instance later
                        my_mesh.fbxBoneParent = blenParentBoneName  # replace with my_bone instance later
    
                ob_base.dupli_list_clear()
    
            # now we have the meshes, restore the rest arm position
            for i, arm in enumerate(bpy.data.armatures):
                arm.pose_position = ob_arms_orig_rest[i]
    
            if ob_arms_orig_rest:
                for ob_base in bpy.data.objects:
                    if ob_base.type == 'ARMATURE':
    
                        ob_base.update_tag()
    
                # This causes the makeDisplayList command to effect the mesh
                scene.frame_set(scene.frame_current)
    
    
        del tmp_ob_type, context_objects
    
    
        # now we have collected all armatures, add bones
        for i, ob in enumerate(ob_arms):
    
            ob_arms[i] = my_arm = my_object_generic(ob)
    
    
            my_arm.fbxBones = []
            my_arm.blenData = ob.data
    
                my_arm.blenAction = ob.animation_data.action
    
    
            # fbxName, blenderObject, my_bones, blenderActions
            #ob_arms[i] = fbxArmObName, ob, arm_my_bones, (ob.action, [])
    
    
            if use_armature_deform_only:
                # tag non deforming bones that have no deforming children
                deform_map = dict.fromkeys(my_arm.blenData.bones, False)
                for bone in my_arm.blenData.bones:
                    if bone.use_deform:
                        deform_map[bone] = True
                        # tag all parents, even ones that are not deform since their child _is_
                        for parent in bone.parent_recursive:
                            deform_map[parent] = True
    
    
            for bone in my_arm.blenData.bones:
    
    
                if use_armature_deform_only:
                    # if this bone doesnt deform, and none of its children deform, skip it!
                    if not deform_map[bone]:
                        continue
    
    
                my_bone = my_bone_class(bone, my_arm)
    
                my_arm.fbxBones.append(my_bone)
                ob_bones.append(my_bone)
    
        # add the meshes to the bones and replace the meshes armature with own armature class
        #for obname, ob, mtx, me, mats, arm, armname in ob_meshes:
        for my_mesh in ob_meshes:
            # Replace
            # ...this could be sped up with dictionary mapping but its unlikely for
            # it ever to be a bottleneck - (would need 100+ meshes using armatures)
            if my_mesh.fbxArm:
                for my_arm in ob_arms:
                    if my_arm.blenObject == my_mesh.fbxArm:
                        my_mesh.fbxArm = my_arm
                        break
    
            for my_bone in ob_bones:
    
                # The mesh uses this bones armature!
                if my_bone.fbxArm == my_mesh.fbxArm:
                    if my_bone.blenBone.use_deform:
                        my_bone.blenMeshes[my_mesh.fbxName] = me
    
                    # parent bone: replace bone names with our class instances
                    # my_mesh.fbxBoneParent is None or a blender bone name initialy, replacing if the names match.
                    if my_mesh.fbxBoneParent == my_bone.blenName:
                        my_mesh.fbxBoneParent = my_bone
    
    
        bone_deformer_count = 0  # count how many bones deform a mesh
    
        my_bone_blenParent = None
        for my_bone in ob_bones:
            my_bone_blenParent = my_bone.blenBone.parent
            if my_bone_blenParent:
                for my_bone_parent in ob_bones:
                    # Note 2.45rc2 you can compare bones normally
                    if my_bone_blenParent.name == my_bone_parent.blenName and my_bone.fbxArm == my_bone_parent.fbxArm:
                        my_bone.parent = my_bone_parent
                        break
    
            # Not used at the moment
            # my_bone.calcRestMatrixLocal()
            bone_deformer_count += len(my_bone.blenMeshes)
    
        del my_bone_blenParent
    
        # Build blenObject -> fbxObject mapping
        # this is needed for groups as well as fbxParenting
    
    
        # using a list of object names for tagging (Arystan)
    
        tmp_obmapping = {}
        for ob_generic in ob_all_typegroups:
            for ob_base in ob_generic:
                ob_base.blenObject.tag = True
                tmp_obmapping[ob_base.blenObject] = ob_base
    
        # Build Groups from objects we export
        for blenGroup in bpy.data.groups:
            fbxGroupName = None
            for ob in blenGroup.objects:
                if ob.tag:
                    if fbxGroupName is None:
                        fbxGroupName = sane_groupname(blenGroup)
                        groups.append((fbxGroupName, blenGroup))
    
    
                    tmp_obmapping[ob].fbxGroupNames.append(fbxGroupName)  # also adds to the objects fbxGroupNames
    
        groups.sort()  # not really needed
    
    
        # Assign parents using this mapping
        for ob_generic in ob_all_typegroups:
            for my_ob in ob_generic:
                parent = my_ob.blenObject.parent
    
                if parent and parent.tag:  # does it exist and is it in the mapping
    
                    my_ob.fbxParent = tmp_obmapping[parent]
    
        del tmp_obmapping
        # Finished finding groups we use
    
        # == WRITE OBJECTS TO THE FILE ==
        # == From now on we are building the FBX file from the information collected above (JCB)
    
        materials = [(sane_matname(mat_tex_pair), mat_tex_pair) for mat_tex_pair in materials.keys()]
        textures = [(sane_texname(tex), tex) for tex in textures.keys()  if tex]
    
        materials.sort(key=lambda m: m[0])  # sort by name
        textures.sort(key=lambda m: m[0])
    
        camera_count = 8 if 'CAMERA' in object_types else 0
    
        # sanity checks
        try:
            assert(not (ob_meshes and ('MESH' not in object_types)))
            assert(not (materials and ('MESH' not in object_types)))
            assert(not (textures and ('MESH' not in object_types)))
            assert(not (textures and ('MESH' not in object_types)))
    
            assert(not (ob_lights and ('LAMP' not in object_types)))
    
            assert(not (ob_cameras and ('CAMERA' not in object_types)))
        except AssertionError:
            import traceback
            traceback.print_exc()
    
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('''
    
    
    ; Object definitions
    ;------------------------------------------------------------------
    
    Definitions:  {
    
    	Version: 100
    	Count: %i''' % (
            1 + camera_count +
            len(ob_meshes) +
            len(ob_lights) +
            len(ob_cameras) +
            len(ob_arms) +
            len(ob_null) +
            len(ob_bones) +
            bone_deformer_count +
            len(materials) +
    
            (len(textures) * 2)))  # add 1 for global settings
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('''
    
            camera_count +
            len(ob_meshes) +
            len(ob_lights) +
            len(ob_cameras) +
            len(ob_arms) +
            len(ob_null) +
    
            len(ob_bones)))
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('''
    
    	ObjectType: "Geometry" {
    		Count: %i
    	}''' % len(ob_meshes))
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    	ObjectType: "Material" {
    		Count: %i
    	}''' % len(materials))
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    	ObjectType: "Texture" {
    		Count: %i
    	}''' % len(textures))  # add 1 for an empty tex
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    	ObjectType: "Video" {
    		Count: %i
    	}''' % len(textures))  # add 1 for an empty tex
    
    
        tmp = 0
        # Add deformer nodes
        for my_mesh in ob_meshes:
            if my_mesh.fbxArm:
    
    
        # Add subdeformers
        for my_bone in ob_bones:
            tmp += len(my_bone.blenMeshes)
    
        if tmp:
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    Campbell Barton's avatar
    Campbell Barton committed
        # Bind pose is essential for XNA if the 'MESH' is included,
        # but could be removed now?
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('''
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    	ObjectType: "GroupSelection" {
    		Count: %i
    	}''' % len(groups))
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('''
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('''
    
    
    ; Object properties
    ;------------------------------------------------------------------
    
    Objects:  {''')
    
    
        if 'CAMERA' in object_types:
            # To comply with other FBX FILES
            write_camera_switch()
    
        # XNA requires the armature to be a Limb (JCB)
        # Note, 2.58 and previous wrote these as normal empties and it worked mostly (except for XNA)
    
            write_null(my_arm, fbxType="Limb", fbxTypeFlags="Skeleton")
    
    
        for my_cam in ob_cameras:
            write_camera(my_cam)
    
        for my_light in ob_lights:
            write_light(my_light)
    
        for my_mesh in ob_meshes:
            write_mesh(my_mesh)
    
        #for bonename, bone, obname, me, armob in ob_bones:
        for my_bone in ob_bones:
            write_bone(my_bone)
    
    
        if 'CAMERA' in object_types:
            write_camera_default()
    
    
        for matname, (mat, tex) in materials:
    
            write_material(matname, mat)  # We only need to have a material per image pair, but no need to write any image info into the material (dumb fbx standard)
    
    
        # each texture uses a video, odd
        for texname, tex in textures:
            write_video(texname, tex)
        i = 0
        for texname, tex in textures:
            write_texture(texname, tex, i)
    
    
        for groupname, group in groups:
            write_group(groupname)
    
        # NOTE - c4d and motionbuilder dont need normalized weights, but deep-exploration 5 does and (max?) do.
    
        # Write armature modifiers
        # TODO - add another MODEL? - because of this skin definition.
        for my_mesh in ob_meshes:
            if my_mesh.fbxArm:
                write_deformer_skin(my_mesh.fbxName)
    
                # Get normalized weights for temorary use
                if my_mesh.fbxBoneParent:
                    weights = None
                else:
                    weights = meshNormalizedWeights(my_mesh.blenObject, my_mesh.blenData)
    
                #for bonename, bone, obname, bone_mesh, armob in ob_bones:
                for my_bone in ob_bones:
                    if me in iter(my_bone.blenMeshes.values()):
                        write_sub_deformer_skin(my_mesh, my_bone, weights)
    
    
        # Write pose is really weird, only needed when an armature and mesh are used together
        # each by themselves do not need pose data. For now only pose meshes and bones
    
        # Bind pose is essential for XNA if the 'MESH' is included (JCB)
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('''
    
    	Pose: "Pose::BIND_POSES", "BindPose" {
    		Type: "BindPose"
    		Version: 100
    		Properties60:  {
    		}
    		NbPoseNodes: ''')
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw(str(len(pose_items)))
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tPoseNode:  {')
            fw('\n\t\t\tNode: "Model::%s"' % fbxName)
            fw('\n\t\t\tMatrix: %s' % mat4x4str(matrix if matrix else Matrix()))
            fw('\n\t\t}')
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('\n\t}')
    
    
        # Finish Writing Objects
        # Write global settings
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('''
    
    	GlobalSettings:  {
    		Version: 1000
    		Properties60:  {
    			Property: "UpAxis", "int", "",1
    			Property: "UpAxisSign", "int", "",1
    			Property: "FrontAxis", "int", "",2
    			Property: "FrontAxisSign", "int", "",1
    			Property: "CoordAxis", "int", "",0
    			Property: "CoordAxisSign", "int", "",1
    			Property: "UnitScaleFactor", "double", "",1
    		}
    	}
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('}')
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('''
    
    
    ; Object relations
    ;------------------------------------------------------------------
    
    Relations:  {''')
    
    
        # Nulls are likely to cause problems for XNA
    
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tModel: "Model::%s", "Null" {\n\t}' % my_null.fbxName)
    
        # Armature must be a Limb for XNA
        # Note, 2.58 and previous wrote these as normal empties and it worked mostly (except for XNA)
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tModel: "Model::%s", "Limb" {\n\t}' % my_arm.fbxName)
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tModel: "Model::%s", "Mesh" {\n\t}' % my_mesh.fbxName)
    
    
        # TODO - limbs can have the same name for multiple armatures, should prefix.
        #for bonename, bone, obname, me, armob in ob_bones:
        for my_bone in ob_bones:
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tModel: "Model::%s", "Limb" {\n\t}' % my_bone.fbxName)
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tModel: "Model::%s", "Camera" {\n\t}' % my_cam.fbxName)
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tModel: "Model::%s", "Light" {\n\t}' % my_light.fbxName)
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('''
    
    	Model: "Model::Producer Perspective", "Camera" {
    	}
    	Model: "Model::Producer Top", "Camera" {
    	}
    	Model: "Model::Producer Bottom", "Camera" {
    	}
    	Model: "Model::Producer Front", "Camera" {
    	}
    	Model: "Model::Producer Back", "Camera" {
    	}
    	Model: "Model::Producer Right", "Camera" {
    	}
    	Model: "Model::Producer Left", "Camera" {
    	}
    	Model: "Model::Camera Switcher", "CameraSwitcher" {
    	}''')
    
    
        for matname, (mat, tex) in materials:
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tMaterial: "Material::%s", "" {\n\t}' % matname)
    
    
        if textures:
            for texname, tex in textures:
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\tTexture: "Texture::%s", "TextureVideoClip" {\n\t}' % texname)
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\tVideo: "Video::%s", "Clip" {\n\t}' % texname)
    
    
        # deformers - modifiers
        for my_mesh in ob_meshes:
            if my_mesh.fbxArm:
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\tDeformer: "Deformer::Skin %s", "Skin" {\n\t}' % my_mesh.fbxName)
    
    
        #for bonename, bone, obname, me, armob in ob_bones:
        for my_bone in ob_bones:
    
            for fbxMeshObName in my_bone.blenMeshes:  # .keys() - fbxMeshObName
    
                # is this bone effecting a mesh?
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\tDeformer: "SubDeformer::Cluster %s %s", "Cluster" {\n\t}' % (fbxMeshObName, my_bone.fbxName))
    
    Campbell Barton's avatar
    Campbell Barton committed
        # fw('\n\tPose: "Pose::BIND_POSES", "BindPose" {\n\t}')
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tGroupSelection: "GroupSelection::%s", "Default" {\n\t}' % groupname)
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('\n}')
        fw('''
    
    
    ; Object connections
    ;------------------------------------------------------------------
    
    Connections:  {''')
    
    
        # NOTE - The FBX SDK does not care about the order but some importers DO!
    
        # for instance, defining the material->mesh connection
    
        # before the mesh->parent crashes cinema4d
    
        for ob_generic in ob_all_typegroups:  # all blender 'Object's we support
    
                # for deformed meshes, don't have any parents or they can get twice transformed.
                if my_ob.fbxParent and (not my_ob.fbxArm):
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_ob.fbxName, my_ob.fbxParent.fbxName))
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\tConnect: "OO", "Model::%s", "Model::Scene"' % my_ob.fbxName)
    
    
        if materials:
            for my_mesh in ob_meshes:
                # Connect all materials to all objects, not good form but ok for now.
                for mat, tex in my_mesh.blenMaterials:
    
                    mat_name = mat.name if mat else None
                    tex_name = tex.name if tex else None
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\tConnect: "OO", "Material::%s", "Model::%s"' % (sane_name_mapping_mat[mat_name, tex_name], my_mesh.fbxName))
    
    
        if textures:
            for my_mesh in ob_meshes:
                if my_mesh.blenTextures:
    
    Campbell Barton's avatar
    Campbell Barton committed
                    # fw('\n\tConnect: "OO", "Texture::_empty_", "Model::%s"' % my_mesh.fbxName)
    
                    for tex in my_mesh.blenTextures:
                        if tex:
    
    Campbell Barton's avatar
    Campbell Barton committed
                            fw('\n\tConnect: "OO", "Texture::%s", "Model::%s"' % (sane_name_mapping_tex[tex.name], my_mesh.fbxName))
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\tConnect: "OO", "Video::%s", "Texture::%s"' % (texname, texname))
    
        if 'MESH' in object_types:
            for my_mesh in ob_meshes:
                if my_mesh.fbxArm:
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\tConnect: "OO", "Deformer::Skin %s", "Model::%s"' % (my_mesh.fbxName, my_mesh.fbxName))
    
            for my_bone in ob_bones:
                for fbxMeshObName in my_bone.blenMeshes:  # .keys()
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\tConnect: "OO", "SubDeformer::Cluster %s %s", "Deformer::Skin %s"' % (fbxMeshObName, my_bone.fbxName, fbxMeshObName))
    
            # limbs -> deformers
            for my_bone in ob_bones:
                for fbxMeshObName in my_bone.blenMeshes:  # .keys()
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\tConnect: "OO", "Model::%s", "SubDeformer::Cluster %s %s"' % (my_bone.fbxName, fbxMeshObName, my_bone.fbxName))
    
    
        #for bonename, bone, obname, me, armob in ob_bones:
        for my_bone in ob_bones:
            # Always parent to armature now
            if my_bone.parent:
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_bone.fbxName, my_bone.parent.fbxName))
    
            else:
                # the armature object is written as an empty and all root level bones connect to it
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_bone.fbxName, my_bone.fbxArm.fbxName))
    
    
        # groups
        if groups:
            for ob_generic in ob_all_typegroups:
                for ob_base in ob_generic:
                    for fbxGroupName in ob_base.fbxGroupNames:
    
    Campbell Barton's avatar
    Campbell Barton committed
                        fw('\n\tConnect: "OO", "Model::%s", "GroupSelection::%s"' % (ob_base.fbxName, fbxGroupName))
    
        # I think the following always duplicates the armature connection because it is also in ob_all_typegroups above! (JCB)
        # for my_arm in ob_arms:
    
    Campbell Barton's avatar
    Campbell Barton committed
        #     fw('\n\tConnect: "OO", "Model::%s", "Model::Scene"' % my_arm.fbxName)
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('\n}')
    
    
        # Needed for scene footer as well as animation
        render = scene.render
    
        # from the FBX sdk
        #define KTIME_ONE_SECOND        KTime (K_LONGLONG(46186158000))
        def fbx_time(t):
            # 0.5 + val is the same as rounding.
    
            return int(0.5 + ((t / fps) * 46186158000))
    
        start = scene.frame_start
        end = scene.frame_end
        if end < start:
    
    Campbell Barton's avatar
    Campbell Barton committed
            start, end = end, start
    
    
        # comment the following line, otherwise we dont get the pose
    
    
        # animations for these object types
        ob_anim_lists = ob_bones, ob_meshes, ob_null, ob_cameras, ob_lights, ob_arms
    
    
        if use_anim and [tmp for tmp in ob_anim_lists if tmp]:
    
                ANIM_OPTIMIZE_PRECISSION_FLOAT = 0.1 ** anim_optimize_precision
    
    
            # default action, when no actions are avaioable
            tmp_actions = []
            blenActionDefault = None
            action_lastcompat = None
    
            # instead of tagging
            tagged_actions = []
    
    
            # get the current action first so we can use it if we only export one action (JCB)
            for my_arm in ob_arms:
    
    Campbell Barton's avatar
    Campbell Barton committed
                blenActionDefault = my_arm.blenAction
                if blenActionDefault:
                    break
    
                tmp_actions = bpy.data.actions[:]
    
            elif not use_default_take:
                if blenActionDefault:
                    # Export the current action (JCB)
                    tmp_actions.append(blenActionDefault)
    
                # find which actions are compatible with the armatures
                tmp_act_count = 0
                for my_arm in ob_arms:
    
                    arm_bone_names = set([my_bone.blenName for my_bone in my_arm.fbxBones])
    
                    for action in tmp_actions:
    
    
                        if arm_bone_names.intersection(action_bone_names(my_arm.blenObject, action)):  # at least one channel matches.
    
                            my_arm.blenActionList.append(action)
                            tagged_actions.append(action.name)
                            tmp_act_count += 1
    
    
                            # in case there are no actions applied to armatures
    
                            # for example, when a user deletes the current action.
    
                            action_lastcompat = action
    
                if tmp_act_count:
                    # unlikely to ever happen but if no actions applied to armatures, just use the last compatible armature.
                    if not blenActionDefault:
                        blenActionDefault = action_lastcompat
    
            del action_lastcompat
    
    
            if use_default_take:
                tmp_actions.insert(0, None)  # None is the default action
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    ;Takes and animation section
    ;----------------------------------------------------
    
    Takes:  {''')
    
    
            if blenActionDefault and not use_default_take:
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\tCurrent: "%s"' % sane_takename(blenActionDefault))
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\tCurrent: "Default Take"')
    
    
            for blenAction in tmp_actions:
                # we have tagged all actious that are used be selected armatures
                if blenAction:
                    if blenAction.name in tagged_actions:
                        print('\taction: "%s" exporting...' % blenAction.name)
                    else:
                        print('\taction: "%s" has no armature using it, skipping' % blenAction.name)
                        continue
    
                if blenAction is None:
                    # Warning, this only accounts for tmp_actions being [None]
    
                    take_name = sane_name_mapping_take.get(blenAction.name)
                    if take_name is None:
    
                        take_name = sane_takename(blenAction)
    
    
                    act_start, act_end = blenAction.frame_range
                    act_start = int(act_start)
                    act_end = int(act_end)
    
                    # Set the action active
                    for my_arm in ob_arms:
                        if my_arm.blenObject.animation_data and blenAction in my_arm.blenActionList:
                            my_arm.blenObject.animation_data.action = blenAction
    
    
                # Use the action name as the take name and the take filename (JCB)
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\tTake: "%s" {' % take_name)
                fw('\n\t\tFileName: "%s.tak"' % take_name.replace(" ", "_"))
                fw('\n\t\tLocalTime: %i,%i' % (fbx_time(act_start - 1), fbx_time(act_end - 1)))  # ??? - not sure why this is needed
                fw('\n\t\tReferenceTime: %i,%i' % (fbx_time(act_start - 1), fbx_time(act_end - 1)))  # ??? - not sure why this is needed
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('''
    
    		;Models animation
    		;----------------------------------------------------''')
    
                # do this here in case the action changes
    
                '''
                for my_bone in ob_bones:
                    my_bone.flushAnimData()
                '''
                i = act_start
                while i <= act_end:
                    scene.frame_set(i)
                    for ob_generic in ob_anim_lists:
                        for my_ob in ob_generic:
                            #Blender.Window.RedrawAll()
                            if ob_generic == ob_meshes and my_ob.fbxArm:
                                # We cant animate armature meshes!
                                my_ob.setPoseFrame(i, fake=True)
                            else:
                                my_ob.setPoseFrame(i)
    
    
    
                #for bonename, bone, obname, me, armob in ob_bones:
                for ob_generic in (ob_bones, ob_meshes, ob_null, ob_cameras, ob_lights, ob_arms):
    
                    for my_ob in ob_generic:
    
                        if ob_generic == ob_meshes and my_ob.fbxArm:
                            # do nothing,
                            pass
                        else:
    
    
    Campbell Barton's avatar
    Campbell Barton committed
                            fw('\n\t\tModel: "Model::%s" {' % my_ob.fbxName)  # ??? - not sure why this is needed
                            fw('\n\t\t\tVersion: 1.1')
                            fw('\n\t\t\tChannel: "Transform" {')
    
                            context_bone_anim_mats = [(my_ob.getAnimParRelMatrix(frame), my_ob.getAnimParRelMatrixRot(frame)) for frame in range(act_start, act_end + 1)]
    
                            for TX_LAYER, TX_CHAN in enumerate('TRS'):  # transform, rotate, scale
    
                                    context_bone_anim_vecs = [mtx[0].to_translation() for mtx in context_bone_anim_mats]
    
                                    context_bone_anim_vecs = [mtx[0].to_scale() for mtx in context_bone_anim_mats]
    
                                    # Was....
                                    # elif 	TX_CHAN=='R':	context_bone_anim_vecs = [mtx[1].to_euler()			for mtx in context_bone_anim_mats]
                                    #
                                    # ...but we need to use the previous euler for compatible conversion.
                                    context_bone_anim_vecs = []
                                    prev_eul = None
                                    for mtx in context_bone_anim_mats:
    
                                        if prev_eul:
                                            prev_eul = mtx[1].to_euler('XYZ', prev_eul)
                                        else:
                                            prev_eul = mtx[1].to_euler()
    
                                        context_bone_anim_vecs.append(tuple_rad_to_deg(prev_eul))
    
    
    Campbell Barton's avatar
    Campbell Barton committed
                                fw('\n\t\t\t\tChannel: "%s" {' % TX_CHAN)  # translation
    
    
                                for i in range(3):
                                    # Loop on each axis of the bone
    
    Campbell Barton's avatar
    Campbell Barton committed
                                    fw('\n\t\t\t\t\tChannel: "%s" {' % ('XYZ'[i]))  # translation
                                    fw('\n\t\t\t\t\t\tDefault: %.15f' % context_bone_anim_vecs[0][i])
                                    fw('\n\t\t\t\t\t\tKeyVer: 4005')
    
                                        # Just write all frames, simple but in-eficient
    
    Campbell Barton's avatar
    Campbell Barton committed
                                        fw('\n\t\t\t\t\t\tKeyCount: %i' % (1 + act_end - act_start))
                                        fw('\n\t\t\t\t\t\tKey: ')
    
                                        frame = act_start
                                        while frame <= act_end:
    
    Campbell Barton's avatar
    Campbell Barton committed
                                                fw(',')
    
    
                                            # Curve types are 'C,n' for constant, 'L' for linear
                                            # C,n is for bezier? - linear is best for now so we can do simple keyframe removal
    
    Campbell Barton's avatar
    Campbell Barton committed
                                            fw('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(frame - 1), context_bone_anim_vecs[frame - act_start][i]))
    
                                    else:
                                        # remove unneeded keys, j is the frame, needed when some frames are removed.
    
                                        context_bone_anim_keys = [(vec[i], j) for j, vec in enumerate(context_bone_anim_vecs)]
    
    
                                        # last frame to fisrt frame, missing 1 frame on either side.
                                        # removeing in a backwards loop is faster
                                        #for j in xrange( (act_end-act_start)-1, 0, -1 ):
                                        # j = (act_end-act_start)-1
    
                                        j = len(context_bone_anim_keys) - 2
    
                                        while j > 0 and len(context_bone_anim_keys) > 2:
                                            # print j, len(context_bone_anim_keys)
                                            # Is this key the same as the ones next to it?
    
                                            # co-linear horizontal...
    
                                            if		abs(context_bone_anim_keys[j][0] - context_bone_anim_keys[j - 1][0]) < ANIM_OPTIMIZE_PRECISSION_FLOAT and \
                                                    abs(context_bone_anim_keys[j][0] - context_bone_anim_keys[j + 1][0]) < ANIM_OPTIMIZE_PRECISSION_FLOAT:
    
                                                frame_range = float(context_bone_anim_keys[j + 1][1] - context_bone_anim_keys[j - 1][1])
                                                frame_range_fac1 = (context_bone_anim_keys[j + 1][1] - context_bone_anim_keys[j][1]) / frame_range
    
                                                frame_range_fac2 = 1.0 - frame_range_fac1
    
    
                                                if abs(((context_bone_anim_keys[j - 1][0] * frame_range_fac1 + context_bone_anim_keys[j + 1][0] * frame_range_fac2)) - context_bone_anim_keys[j][0]) < ANIM_OPTIMIZE_PRECISSION_FLOAT:
    
    
                                            # keep the index below the list length
    
                                            if j > len(context_bone_anim_keys) - 2:
                                                j = len(context_bone_anim_keys) - 2
    
    
                                        if len(context_bone_anim_keys) == 2 and context_bone_anim_keys[0][0] == context_bone_anim_keys[1][0]:
    
                                            # This axis has no moton, its okay to skip KeyCount and Keys in this case
                                            # pass
    
                                            # better write one, otherwise we loose poses with no animation
    
    Campbell Barton's avatar
    Campbell Barton committed
                                            fw('\n\t\t\t\t\t\tKeyCount: 1')
                                            fw('\n\t\t\t\t\t\tKey: ')
                                            fw('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(start), context_bone_anim_keys[0][0]))
    
                                        else:
                                            # We only need to write these if there is at least one
    
    Campbell Barton's avatar
    Campbell Barton committed
                                            fw('\n\t\t\t\t\t\tKeyCount: %i' % len(context_bone_anim_keys))
                                            fw('\n\t\t\t\t\t\tKey: ')
    
                                            for val, frame in context_bone_anim_keys:
    
                                                if frame != context_bone_anim_keys[0][1]:  # not the first
    
    Campbell Barton's avatar
    Campbell Barton committed
                                                    fw(',')
    
                                                # frame is already one less then blenders frame
    
    Campbell Barton's avatar
    Campbell Barton committed
                                                fw('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(frame), val))
    
    Campbell Barton's avatar
    Campbell Barton committed
                                        fw('\n\t\t\t\t\t\tColor: 1,0,0')
    
    Campbell Barton's avatar
    Campbell Barton committed
                                        fw('\n\t\t\t\t\t\tColor: 0,1,0')
    
    Campbell Barton's avatar
    Campbell Barton committed
                                        fw('\n\t\t\t\t\t\tColor: 0,0,1')
    
    Campbell Barton's avatar
    Campbell Barton committed
                                    fw('\n\t\t\t\t\t}')
                                fw('\n\t\t\t\t\tLayerType: %i' % (TX_LAYER + 1))
                                fw('\n\t\t\t\t}')
    
    Campbell Barton's avatar
    Campbell Barton committed
                            fw('\n\t\t\t}')
                            fw('\n\t\t}')
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\t}')
    
    
                # end action loop. set original actions
    
                # do this after every loop in case actions effect eachother.
    
                for my_arm in ob_arms:
                    if my_arm.blenObject.animation_data:
                        my_arm.blenObject.animation_data.action = my_arm.blenAction
    
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n}')
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n;Takes and animation section')
            fw('\n;----------------------------------------------------')
            fw('\n')
            fw('\nTakes:  {')
            fw('\n\tCurrent: ""')
            fw('\n}')
    
    
        # write meshes animation
        #for obname, ob, mtx, me, mats, arm, armname in ob_meshes:
    
        # Clear mesh data Only when writing with modifiers applied
        for me in meshes_to_clear:
            bpy.data.meshes.remove(me)
    
        # --------------------------- Footer
        if world:
            m = world.mist_settings
            has_mist = m.use_mist
            mist_intense = m.intensity
            mist_start = m.start
            mist_end = m.depth
    
    Campbell Barton's avatar
    Campbell Barton committed
            # mist_height = m.height  # UNUSED
    
            world_hor = world.horizon_color
        else:
    
    Campbell Barton's avatar
    Campbell Barton committed
            has_mist = mist_intense = mist_start = mist_end = 0
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('\n;Version 5 settings')
        fw('\n;------------------------------------------------------------------')
        fw('\n')
        fw('\nVersion5:  {')
        fw('\n\tAmbientRenderSettings:  {')
        fw('\n\t\tVersion: 101')
        fw('\n\t\tAmbientLightColor: %.1f,%.1f,%.1f,0' % tuple(world_amb))
        fw('\n\t}')
        fw('\n\tFogOptions:  {')
    
        fw('\n\t\tFogEnable: %i' % has_mist)
    
    Campbell Barton's avatar
    Campbell Barton committed
        fw('\n\t\tFogMode: 0')
        fw('\n\t\tFogDensity: %.3f' % mist_intense)
        fw('\n\t\tFogStart: %.3f' % mist_start)
        fw('\n\t\tFogEnd: %.3f' % mist_end)
        fw('\n\t\tFogColor: %.1f,%.1f,%.1f,1' % tuple(world_hor))
        fw('\n\t}')
        fw('\n\tSettings:  {')
        fw('\n\t\tFrameRate: "%i"' % int(fps))
        fw('\n\t\tTimeFormat: 1')
        fw('\n\t\tSnapOnFrames: 0')
        fw('\n\t\tReferenceTimeIndex: -1')
        fw('\n\t\tTimeLineStartTime: %i' % fbx_time(start - 1))
        fw('\n\t\tTimeLineStopTime: %i' % fbx_time(end - 1))
        fw('\n\t}')
        fw('\n\tRendererSetting:  {')
        fw('\n\t\tDefaultCamera: "Producer Perspective"')
        fw('\n\t\tDefaultViewingMode: 0')
        fw('\n\t}')
        fw('\n}')
        fw('\n')
    
        for mapping in (sane_name_mapping_ob,
                        sane_name_mapping_ob_unique,
                        sane_name_mapping_mat,
                        sane_name_mapping_tex,
                        sane_name_mapping_take,
                        sane_name_mapping_group,
                        ):
            mapping.clear()
        del mapping
    
        del ob_arms[:]
        del ob_bones[:]
        del ob_cameras[:]
        del ob_lights[:]
        del ob_meshes[:]
        del ob_null[:]
    
        # copy all collected files.
    
        bpy_extras.io_utils.path_reference_copy(copy_set)
    
        print('export finished in %.4f sec.' % (time.clock() - start_time))
        return {'FINISHED'}
    
    
    
    # defaults for applications, currently only unity but could add others.
    def defaults_unity3d():
        return dict(global_matrix=Matrix.Rotation(-math.pi / 2.0, 4, 'X'),
                    use_selection=False,
                    object_types={'ARMATURE', 'EMPTY', 'MESH'},
                    use_mesh_modifiers=True,
    
                    use_anim=True,
                    use_anim_optimize=False,
                    use_anim_action_all=True,
                    batch_mode='OFF',
                    use_default_take=True,
                    )
    
    
    
    Campbell Barton's avatar
    Campbell Barton committed
    def save(operator, context,
    
             filepath="",
             use_selection=False,
             batch_mode='OFF',
             use_batch_own_dir=False,
             **kwargs
             ):
    
    
        if bpy.ops.object.mode_set.poll():
            bpy.ops.object.mode_set(mode='OBJECT')
    
        if batch_mode == 'OFF':
            kwargs_mod = kwargs.copy()
            if use_selection:
                kwargs_mod["context_objects"] = context.selected_objects
            else:
    
                kwargs_mod["context_objects"] = context.scene.objects
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
            return save_single(operator, context.scene, filepath, **kwargs_mod)
        else:
            fbxpath = filepath
    
            prefix = os.path.basename(fbxpath)
            if prefix:
                fbxpath = os.path.dirname(fbxpath)
    
            if not fbxpath.endswith(os.sep):
                fbxpath += os.sep
    
            if batch_mode == 'GROUP':
                data_seq = bpy.data.groups
            else:
                data_seq = bpy.data.scenes
    
            # call this function within a loop with BATCH_ENABLE == False
            # no scene switching done at the moment.
            # orig_sce = context.scene
    
            new_fbxpath = fbxpath  # own dir option modifies, we need to keep an original
            for data in data_seq:  # scene or group
                newname = prefix + bpy.path.clean_name(data.name)
    
    
                    new_fbxpath = fbxpath + newname + os.sep
                    # path may already exist
                    # TODO - might exist but be a file. unlikely but should probably account for it.
    
                    if not os.path.exists(new_fbxpath):
                        os.makedirs(new_fbxpath)