Skip to content
Snippets Groups Projects
export_fbx.py 114 KiB
Newer Older
  • Learn to ignore specific revisions
  •                                 tex = uf.image
                                    textures[tex] = texture_mapping_local[tex] = None
    
    
                                    try:
                                        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
    
                        if EXP_ARMATURE:
                            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())
    
    
                        # 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
    
            if ob_base.dupli_list:
                ob_base.free_dupli_list()
    
    
        if EXP_ARMATURE:
            # 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()
                # This causes the makeDisplayList command to effect the mesh
                scene.frame_set(scene.frame_current)
    
        del tmp_ob_type, tmp_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
    
            else:
                my_arm.blenAction = None
    # 		my_arm.blenAction =		ob.action
    
    
            # fbxName, blenderObject, my_bones, blenderActions
            #ob_arms[i] = fbxArmObName, ob, arm_my_bones, (ob.action, [])
    
            for bone in my_arm.blenData.bones:
                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
    
    
        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()  # sort by name
    
        textures.sort()
    
        camera_count = 8
        file.write('''
    
    ; Object definitions
    ;------------------------------------------------------------------
    
    Definitions:  {
        Version: 100
        Count: %i''' % (\
    
            1 + 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 the root model 1 for global settings
    
    
        del bone_deformer_count
    
        file.write('''
        ObjectType: "Model" {
            Count: %i
        }''' % (\
    
            1 + camera_count + \
            len(ob_meshes) + \
            len(ob_lights) + \
            len(ob_cameras) + \
            len(ob_arms) + \
            len(ob_null) + \
            len(ob_bones)))  # add 1 for the root model
    
    
        file.write('''
        ObjectType: "Geometry" {
            Count: %i
        }''' % len(ob_meshes))
    
        if materials:
            file.write('''
        ObjectType: "Material" {
            Count: %i
        }''' % len(materials))
    
        if textures:
            file.write('''
        ObjectType: "Texture" {
            Count: %i
    
        }''' % len(textures))  # add 1 for an empty tex
    
            file.write('''
        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:
            file.write('''
        ObjectType: "Deformer" {
            Count: %i
        }''' % tmp)
        del tmp
    
        # we could avoid writing this possibly but for now just write it
    
        file.write('''
        ObjectType: "Pose" {
            Count: 1
        }''')
    
        if groups:
            file.write('''
        ObjectType: "GroupSelection" {
            Count: %i
        }''' % len(groups))
    
        file.write('''
        ObjectType: "GlobalSettings" {
            Count: 1
        }
    }''')
    
        file.write('''
    
    ; Object properties
    ;------------------------------------------------------------------
    
    Objects:  {''')
    
        # To comply with other FBX FILES
        write_camera_switch()
    
        # Write the null object
    
        write_null(None, 'blend_root')  # , GLOBAL_MATRIX)
    
    
        for my_null in ob_null:
            write_null(my_null)
    
        for my_arm in ob_arms:
            write_null(my_arm)
    
        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)
    
        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's really weired, only needed when an armature and mesh are used together
        # each by themselves dont need pose data. for now only pose meshes and bones
    
        file.write('''
        Pose: "Pose::BIND_POSES", "BindPose" {
            Type: "BindPose"
            Version: 100
            Properties60:  {
            }
            NbPoseNodes: ''')
        file.write(str(len(pose_items)))
    
        for fbxName, matrix in pose_items:
            file.write('\n\t\tPoseNode:  {')
    
            file.write('\n\t\t\tNode: "Model::%s"' % fbxName)
    
            file.write('\n\t\t\tMatrix: %s' % mat4x4str(matrix if matrix else Matrix()))
            file.write('\n\t\t}')
    
        file.write('\n\t}')
    
        # Finish Writing Objects
        # Write global settings
        file.write('''
        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", "",100
            }
        }
    ''')
        file.write('}')
    
        file.write('''
    
    ; Object relations
    ;------------------------------------------------------------------
    
    Relations:  {''')
    
        file.write('\n\tModel: "Model::blend_root", "Null" {\n\t}')
    
        for my_null in ob_null:
            file.write('\n\tModel: "Model::%s", "Null" {\n\t}' % my_null.fbxName)
    
        for my_arm in ob_arms:
            file.write('\n\tModel: "Model::%s", "Null" {\n\t}' % my_arm.fbxName)
    
        for my_mesh in ob_meshes:
            file.write('\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:
            file.write('\n\tModel: "Model::%s", "Limb" {\n\t}' % my_bone.fbxName)
    
        for my_cam in ob_cameras:
            file.write('\n\tModel: "Model::%s", "Camera" {\n\t}' % my_cam.fbxName)
    
        for my_light in ob_lights:
            file.write('\n\tModel: "Model::%s", "Light" {\n\t}' % my_light.fbxName)
    
        file.write('''
        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:
            file.write('\n\tMaterial: "Material::%s", "" {\n\t}' % matname)
    
        if textures:
            for texname, tex in textures:
                file.write('\n\tTexture: "Texture::%s", "TextureVideoClip" {\n\t}' % texname)
            for texname, tex in textures:
                file.write('\n\tVideo: "Video::%s", "Clip" {\n\t}' % texname)
    
        # deformers - modifiers
        for my_mesh in ob_meshes:
            if my_mesh.fbxArm:
                file.write('\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?
                file.write('\n\tDeformer: "SubDeformer::Cluster %s %s", "Cluster" {\n\t}' % (fbxMeshObName, my_bone.fbxName))
    
        # This should be at the end
        # file.write('\n\tPose: "Pose::BIND_POSES", "BindPose" {\n\t}')
    
        for groupname, group in groups:
            file.write('\n\tGroupSelection: "GroupSelection::%s", "Default" {\n\t}' % groupname)
    
        file.write('\n}')
        file.write('''
    
    ; Object connections
    ;------------------------------------------------------------------
    
    Connections:  {''')
    
        # NOTE - The FBX SDK dosnt care about the order but some importers DO!
        # for instance, defining the material->mesh connection
        # before the mesh->blend_root crashes cinema4d
    
        # write the fake root node
        file.write('\n\tConnect: "OO", "Model::blend_root", "Model::Scene"')
    
    
        for ob_generic in ob_all_typegroups:  # all blender 'Object's we support
    
            for my_ob in ob_generic:
                if my_ob.fbxParent:
                    file.write('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_ob.fbxName, my_ob.fbxParent.fbxName))
                else:
                    file.write('\n\tConnect: "OO", "Model::%s", "Model::blend_root"' % 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
    
    
                    file.write('\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:
                    # file.write('\n\tConnect: "OO", "Texture::_empty_", "Model::%s"' % my_mesh.fbxName)
                    for tex in my_mesh.blenTextures:
                        if tex:
                            file.write('\n\tConnect: "OO", "Texture::%s", "Model::%s"' % (sane_name_mapping_tex[tex.name], my_mesh.fbxName))
    
            for texname, tex in textures:
                file.write('\n\tConnect: "OO", "Video::%s", "Texture::%s"' % (texname, texname))
    
        for my_mesh in ob_meshes:
            if my_mesh.fbxArm:
                file.write('\n\tConnect: "OO", "Deformer::Skin %s", "Model::%s"' % (my_mesh.fbxName, 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()
    
                file.write('\n\tConnect: "OO", "SubDeformer::Cluster %s %s", "Deformer::Skin %s"' % (fbxMeshObName, my_bone.fbxName, fbxMeshObName))
    
        # limbs -> deformers
        # for bonename, bone, obname, me, armob in ob_bones:
        for my_bone in ob_bones:
    
            for fbxMeshObName in my_bone.blenMeshes:  # .keys()
    
                file.write('\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:
    
                file.write('\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
    
                file.write('\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:
                        file.write('\n\tConnect: "OO", "Model::%s", "GroupSelection::%s"' % (ob_base.fbxName, fbxGroupName))
    
        for my_arm in ob_arms:
            file.write('\n\tConnect: "OO", "Model::%s", "Model::blend_root"' % my_arm.fbxName)
    
        file.write('\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:
            start, end = end, st
    
    
        # comment the following line, otherwise we dont get the pose
        # if start==end: ANIM_ENABLE = False
    
        # animations for these object types
        ob_anim_lists = ob_bones, ob_meshes, ob_null, ob_cameras, ob_lights, ob_arms
    
        if ANIM_ENABLE and [tmp for tmp in ob_anim_lists if tmp]:
    
            frame_orig = scene.frame_current
    
            if ANIM_OPTIMIZE:
                ANIM_OPTIMIZE_PRECISSION_FLOAT = 0.1 ** ANIM_OPTIMIZE_PRECISSION
    
            # default action, when no actions are avaioable
            tmp_actions = []
            blenActionDefault = None
            action_lastcompat = None
    
            # instead of tagging
            tagged_actions = []
    
            if ANIM_ACTION_ALL:
    # 			bpy.data.actions.tag = False
                tmp_actions = bpy.data.actions[:]
    
                # find which actions are compatible with the armatures
                # blenActions is not yet initialized so do it now.
                tmp_act_count = 0
                for my_arm in ob_arms:
    
                    # get the default name
                    if not blenActionDefault:
                        blenActionDefault = my_arm.blenAction
    
                    arm_bone_names = set([my_bone.blenName for my_bone in my_arm.fbxBones])
    
                    for action in tmp_actions:
    
    
                        action_chan_names = arm_bone_names.intersection(set([g.name for g in action.groups]))
    
    # 					action_chan_names = arm_bone_names.intersection( set(action.getChannelNames()) )
    
    
                        if action_chan_names:  # at least one channel matches.
    
                            my_arm.blenActionList.append(action)
                            tagged_actions.append(action.name)
    # 						action.tag = True
                            tmp_act_count += 1
    
                            # incase there is no actions applied to armatures
                            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
    
    
            tmp_actions.insert(0, None)  # None is the default action
    
    
            file.write('''
    ;Takes and animation section
    ;----------------------------------------------------
    
    Takes:  {''')
    
            if blenActionDefault:
                file.write('\n\tCurrent: "%s"' % sane_takename(blenActionDefault))
            else:
                file.write('\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:
    # 				if blenAction.tag:
                        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]
                    file.write('\n\tTake: "Default Take" {')
    
                    if blenAction == blenActionDefault:  # have we already got the name
    
                        file.write('\n\tTake: "%s" {' % sane_name_mapping_take[blenAction.name])
                    else:
                        file.write('\n\tTake: "%s" {' % 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
                            # print('\t\tSetting Action!', blenAction)
                    # scene.update(1)
    
    
                file.write('\n\t\tFileName: "Default_Take.tak"')  # ??? - not sure why this is needed
                file.write('\n\t\tLocalTime: %i,%i' % (fbx_time(act_start - 1), fbx_time(act_end - 1)))  # ??? - not sure why this is needed
                file.write('\n\t\tReferenceTime: %i,%i' % (fbx_time(act_start - 1), fbx_time(act_end - 1)))  # ??? - not sure why this is needed
    
    
                file.write('''
    
            ;Models animation
            ;----------------------------------------------------''')
    
                # set pose data for all bones
                # do this here incase 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:
    
    
                            file.write('\n\t\tModel: "Model::%s" {' % my_ob.fbxName)  # ??? - not sure why this is needed
    
                            file.write('\n\t\t\tVersion: 1.1')
                            file.write('\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
    
                                if TX_CHAN == 'T':
                                    context_bone_anim_vecs = [mtx[0].translation_part() for mtx in context_bone_anim_mats]
                                elif	TX_CHAN == 'S':
                                    context_bone_anim_vecs = [mtx[0].scale_part() for mtx in context_bone_anim_mats]
                                elif	TX_CHAN == 'R':
    
                                    # 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))
    
    
                                file.write('\n\t\t\t\tChannel: "%s" {' % TX_CHAN)  # translation
    
    
                                for i in range(3):
                                    # Loop on each axis of the bone
    
                                    file.write('\n\t\t\t\t\tChannel: "%s" {' % ('XYZ'[i]))  # translation
                                    file.write('\n\t\t\t\t\t\tDefault: %.15f' % context_bone_anim_vecs[0][i])
    
                                    file.write('\n\t\t\t\t\t\tKeyVer: 4005')
    
                                    if not ANIM_OPTIMIZE:
                                        # Just write all frames, simple but in-eficient
                                        file.write('\n\t\t\t\t\t\tKeyCount: %i' % (1 + act_end - act_start))
                                        file.write('\n\t\t\t\t\t\tKey: ')
                                        frame = act_start
                                        while frame <= act_end:
    
                                                file.write(',')
    
                                            # 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
    
                                            file.write('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(frame - 1), context_bone_anim_vecs[frame - act_start][i]))
                                            frame += 1
    
                                    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
                                            file.write('\n\t\t\t\t\t\tKeyCount: 1')
                                            file.write('\n\t\t\t\t\t\tKey: ')
    
                                            file.write('\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
                                            file.write('\n\t\t\t\t\t\tKeyCount: %i' % len(context_bone_anim_keys))
                                            file.write('\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
    
                                                    file.write(',')
                                                # frame is already one less then blenders frame
    
                                                file.write('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(frame), val))
    
                                    if i == 0:
                                        file.write('\n\t\t\t\t\t\tColor: 1,0,0')
                                    elif i == 1:
                                        file.write('\n\t\t\t\t\t\tColor: 0,1,0')
                                    elif i == 2:
                                        file.write('\n\t\t\t\t\t\tColor: 0,0,1')
    
                                file.write('\n\t\t\t\t\tLayerType: %i' % (TX_LAYER + 1))
    
                                file.write('\n\t\t\t\t}')
    
                            # ---------------
    
                            file.write('\n\t\t\t}')
                            file.write('\n\t\t}')
    
                # end the take
                file.write('\n\t}')
    
                # end action loop. set original actions
                # do this after every loop incase actions effect eachother.
                for my_arm in ob_arms:
                    if my_arm.blenObject.animation_data:
                        my_arm.blenObject.animation_data.action = my_arm.blenAction
    
            file.write('\n}')
    
            scene.frame_set(frame_orig)
    
        else:
            # no animation
            file.write('\n;Takes and animation section')
            file.write('\n;----------------------------------------------------')
            file.write('\n')
            file.write('\nTakes:  {')
            file.write('\n\tCurrent: ""')
            file.write('\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
            mist_height = m.height
            world_hor = world.horizon_color
        else:
            has_mist = mist_intense = mist_start = mist_end = mist_height = 0
            world_hor = 0, 0, 0
    
        file.write('\n;Version 5 settings')
        file.write('\n;------------------------------------------------------------------')
        file.write('\n')
        file.write('\nVersion5:  {')
        file.write('\n\tAmbientRenderSettings:  {')
        file.write('\n\t\tVersion: 101')
        file.write('\n\t\tAmbientLightColor: %.1f,%.1f,%.1f,0' % tuple(world_amb))
        file.write('\n\t}')
        file.write('\n\tFogOptions:  {')
        file.write('\n\t\tFlogEnable: %i' % has_mist)
        file.write('\n\t\tFogMode: 0')
        file.write('\n\t\tFogDensity: %.3f' % mist_intense)
        file.write('\n\t\tFogStart: %.3f' % mist_start)
        file.write('\n\t\tFogEnd: %.3f' % mist_end)
        file.write('\n\t\tFogColor: %.1f,%.1f,%.1f,1' % tuple(world_hor))
        file.write('\n\t}')
        file.write('\n\tSettings:  {')
        file.write('\n\t\tFrameRate: "%i"' % int(fps))
        file.write('\n\t\tTimeFormat: 1')
        file.write('\n\t\tSnapOnFrames: 0')
        file.write('\n\t\tReferenceTimeIndex: -1')
    
        file.write('\n\t\tTimeLineStartTime: %i' % fbx_time(start - 1))
        file.write('\n\t\tTimeLineStopTime: %i' % fbx_time(end - 1))
    
        file.write('\n\t}')
        file.write('\n\tRendererSetting:  {')
        file.write('\n\t\tDefaultCamera: "Producer Perspective"')
        file.write('\n\t\tDefaultViewingMode: 0')
        file.write('\n\t}')
        file.write('\n}')
        file.write('\n')
    
        # XXX, shouldnt be global!
        sane_name_mapping_ob.clear()
        sane_name_mapping_mat.clear()
        sane_name_mapping_tex.clear()
        sane_name_mapping_take.clear()
        sane_name_mapping_group.clear()
    
    
        ob_arms[:] = []
        ob_bones[:] = []
        ob_cameras[:] = []
        ob_lights[:] = []
        ob_meshes[:] = []
        ob_null[:] = []
    
    
        # copy images if enabled
    # 	if EXP_IMAGE_COPY:
    # # 		copy_images( basepath,  [ tex[1] for tex in textures if tex[1] != None ])
    # 		bpy.util.copy_images( [ tex[1] for tex in textures if tex[1] != None ], basepath)
        file.close()
    
        print('export finished in %.4f sec.' % (time.clock() - start_time))
        return {'FINISHED'}
    
    
    # NOTES (all line numbers correspond to original export_fbx.py (under release/scripts)
    # - Draw.PupMenu alternative in 2.5?, temporarily replaced PupMenu with print
    # - get rid of bpy.path.clean_name somehow
    # + fixed: isinstance(inst, bpy.types.*) doesn't work on RNA objects: line 565
    # + get rid of BPyObject_getObjectArmature, move it in RNA?
    # - BATCH_ENABLE and BATCH_GROUP options: line 327
    # - implement all BPyMesh_* used here with RNA
    # - getDerivedObjects is not fully replicated with .dupli* funcs
    # - talk to Campbell, this code won't work? lines 1867-1875
    # - don't know what those colbits are, do we need them? they're said to be deprecated in DNA_object_types.h: 1886-1893
    # - no hq normals: 1900-1901
    
    # TODO
    
    # - bpy.data.remove_scene: line 366
    # - bpy.sys.time move to bpy.sys.util?
    # - new scene creation, activation: lines 327-342, 368
    # - uses bpy.path.abspath, *.relpath - replace at least relpath