#  ***** GPL LICENSE BLOCK *****
 #
 #  This program is free software: you can redistribute it and/or modify
 #  it under the terms of the GNU General Public License as published by
 #  the Free Software Foundation, either version 3 of the License, or
 #  (at your option) any later version.
 #
 #  This program is distributed in the hope that it will be useful,
 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 #  GNU General Public License for more details.
 #
 #  You should have received a copy of the GNU General Public License
 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #  All rights reserved.
 #  ***** GPL LICENSE BLOCK *****

#One line description for early versions of Blender 2.52.
"Export: DirectX Model Format (.x)"

bl_addon_info = {
    'name': 'Export: DirectX Model Format (.x)',
    'author': 'Chris Foster (Kira Vakaan)',
    'version': '1.1',
    'blender': (2, 5, 3),
    'location': 'File > Export',
    'description': 'Export to the DirectX Model Format',
    'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
        'Scripts/File_I-O/DirectX_Exporter',
    'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
        'func=detail&aid=22795&group_id=153&atid=469',
    'category': 'Import/Export'}
"""
Name: 'DirectX Exporter'
Blender: 252
Group: 'Export'
Tooltip: 'Exports to the DirectX model file format (.x)'
"""

__author__="Chris Foster (Kira Vakaan)"
__url__="www.tobedecided.com"
__version__="1.1"
__bpydoc__="""\
"""

import bpy
from math import radians
from mathutils import *
import os


#Container for the exporter settings
class DirectXExporterSettings:
    def __init__(self,
                 context,
                 FilePath,
                 CoordinateSystem=1,
                 RotateX=True,
                 FlipNormals=False,
                 ApplyModifiers=False,
                 IncludeFrameRate=False,
                 ExportTextures=True,
                 ExportArmatures=False,
                 ExportAnimation=0,
                 ExportMode=1,
                 Verbose=False):
        self.context=context
        self.FilePath=FilePath
        self.CoordinateSystem=int(CoordinateSystem)
        self.RotateX=RotateX
        self.FlipNormals=FlipNormals
        self.ApplyModifiers=ApplyModifiers
        self.IncludeFrameRate=IncludeFrameRate
        self.ExportTextures=ExportTextures
        self.ExportArmatures=ExportArmatures
        self.ExportAnimation=int(ExportAnimation)
        self.ExportMode=int(ExportMode)
        self.Verbose=Verbose

def LegalName(Name):
    NewName=Name.replace(".","_")
    NewName=NewName.replace(" ","_")
    if NewName[0].isdigit() or NewName in ["ARRAY","DWORD","UCHAR","BINARY","FLOAT","ULONGLONG","BINARY_RESOURCE","SDWORD","UNICODE","CHAR","STRING","WORD","CSTRING","SWORD","DOUBLE","TEMPLATE"]:
        NewName="_"+NewName
    return NewName

def ExportDirectX(Config):
    print("----------\nExporting to {}".format(Config.FilePath))
    if Config.Verbose: print("Opening File...",end=" ")
    Config.File=open(Config.FilePath,"w")
    if Config.Verbose: print("Done")
    
    if Config.Verbose: print("Generating Object list for export...",end=" ")
    if Config.ExportMode==1:
        Config.ExportList=[Object for Object in Config.context.scene.objects if Object.type in ("ARMATURE","EMPTY","MESH") and Object.parent==None]
    else:
        ExportList=[Object for Object in Config.context.selected_objects if Object.type in ("ARMATURE","EMPTY","MESH")]
        Config.ExportList=[Object for Object in ExportList if Object.parent not in ExportList]
    if Config.Verbose: print("Done")
    
    if Config.Verbose: print("Setting up...",end=" ")
    Config.SystemMatrix=Matrix()
    if Config.RotateX:
        Config.SystemMatrix*=RotationMatrix(radians(-90),4,"X")
    if Config.CoordinateSystem==1:
        Config.SystemMatrix*=ScaleMatrix(-1,4,Vector((0,1,0)))
    Config.InverseSystemMatrix=Config.SystemMatrix.copy().invert()
    
    if Config.ExportAnimation:
        CurrentFrame=bpy.context.scene.frame_current
        bpy.context.scene.frame_current=bpy.context.scene.frame_current
    if Config.Verbose: print("Done")
    
    if Config.Verbose: print("Writing Header...",end=" ")
    WriteHeader(Config)
    if Config.Verbose: print("Done")
    
    Config.Whitespace=0
    Config.ObjectList=[]
    if Config.Verbose: print("Writing Objects...")
    WriteObjects(Config,Config.ExportList)
    if Config.Verbose: print("Done")
    
    if Config.ExportAnimation:
        if Config.IncludeFrameRate:
            if Config.Verbose: print("Writing Frame Rate...",end=" ")
            Config.File.write("{}AnimTicksPerSecond {{\n".format("  "*Config.Whitespace))
            Config.Whitespace+=1
            Config.File.write("{}{};\n".format("  "*Config.Whitespace,int(bpy.context.scene.render.fps/bpy.context.scene.render.fps_base)))
            Config.Whitespace-=1
            Config.File.write("{}}}\n".format("  "*Config.Whitespace))
            if Config.Verbose: print("Done")
        if Config.Verbose: print("Writing Animation...")
        WriteAnimationSet(Config)
        bpy.context.scene.frame_current=CurrentFrame
        if Config.Verbose: print("Done")
    
    CloseFile(Config)
    print("Finished")

def GetObjectChildren(Parent):
    return [Object for Object in Parent.children if Object.type in ("ARMATURE","EMPTY","MESH")]

def GetMeshVertexCount(Mesh):
    VertexCount=0
    for Face in Mesh.faces:
        VertexCount+=len(Face.verts)
    return VertexCount

def GetMaterialTexture(Material):
    if Material:
        ImageTextures=[Material.texture_slots[TextureSlot].texture for TextureSlot in Material.texture_slots.keys() if Material.texture_slots[TextureSlot].texture.type=="IMAGE"]
        ImageFiles=[os.path.basename(Texture.image.filename) for Texture in ImageTextures if Texture.image.source=="FILE"]
        if ImageFiles:
            return ImageFiles[0]
    return None

def WriteHeader(Config):
    Config.File.write("xof 0303txt 0032\n\n")
    if Config.ExportArmatures:
        Config.File.write("template XSkinMeshHeader {\n\
  <3cf169ce-ff7c-44ab-93c0-f78f62d172e2>\n\
  WORD nMaxSkinWeightsPerVertex;\n\
  WORD nMaxSkinWeightsPerFace;\n\
  WORD nBones;\n\
}\n\n\
template SkinWeights {\n\
  <6f0d123b-bad2-4167-a0d0-80224f25fabb>\n\
  STRING transformNodeName;\n\
  DWORD nWeights;\n\
  array DWORD vertexIndices[nWeights];\n\
  array float weights[nWeights];\n\
  Matrix4x4 matrixOffset;\n\
}\n\n")

def WriteObjects(Config,ObjectList):
    Config.ObjectList+=ObjectList
    
    for Object in ObjectList:
        if Config.Verbose: print("  Writing Object: {}...".format(Object.name))
        Config.File.write("{}Frame {} {{\n".format("  "*Config.Whitespace,LegalName(Object.name)))
        
        Config.Whitespace+=1
        if Config.Verbose: print("    Writing Local Matrix...",end=" ")
        WriteLocalMatrix(Config,Object)
        if Config.Verbose: print("Done")
        
        if Config.ExportArmatures and Object.type=="ARMATURE":
            Armature=Object.data
            ParentList=[Bone for Bone in Armature.bones if Bone.parent==None]
            if Config.Verbose: print("    Writing Armature Bones...")
            WriteArmatureBones(Config,Object,ParentList)
            if Config.Verbose: print("    Done")
        
        ChildList=GetObjectChildren(Object)
        if Config.Verbose: print("    Writing Children...")
        WriteObjects(Config,ChildList)
        if Config.Verbose: print("    Done Writing Children")
        
        if Object.type=="MESH":
            if Config.Verbose: print("    Generating Mesh...",end=" ")
            Mesh=Object.create_mesh(bpy.context.scene,(Config.ApplyModifiers|Config.ExportArmatures),"PREVIEW")
            if Config.Verbose: print("Done")
            if Config.Verbose: print("    Writing Mesh...")
            WriteMesh(Config,Object,Mesh)
            if Config.Verbose: print("    Done")
            bpy.data.meshes.remove(Mesh)
        
        Config.Whitespace-=1
        Config.File.write("{}}} //End of {}\n".format("  "*Config.Whitespace,LegalName(Object.name)))
        if Config.Verbose: print("  Done Writing Object: {}".format(Object.name))

def WriteLocalMatrix(Config,Object):
    if Object.parent:
        LocalMatrix=Object.parent.matrix_world.copy().invert()
    else:
        LocalMatrix=Matrix()
    LocalMatrix*=Object.matrix_world
    LocalMatrix=Config.SystemMatrix*LocalMatrix*Config.InverseSystemMatrix
    
    Config.File.write("{}FrameTransformMatrix {{\n".format("  "*Config.Whitespace))
    Config.Whitespace+=1
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  "*Config.Whitespace,LocalMatrix[0][0],LocalMatrix[0][1],LocalMatrix[0][2],LocalMatrix[0][3]))
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  "*Config.Whitespace,LocalMatrix[1][0],LocalMatrix[1][1],LocalMatrix[1][2],LocalMatrix[1][3]))
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  "*Config.Whitespace,LocalMatrix[2][0],LocalMatrix[2][1],LocalMatrix[2][2],LocalMatrix[2][3]))
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,LocalMatrix[3][0],LocalMatrix[3][1],LocalMatrix[3][2],LocalMatrix[3][3]))
    Config.Whitespace-=1
    Config.File.write("{}}}\n".format("  "*Config.Whitespace))

def WriteArmatureBones(Config,Object,ChildList):
    PoseBones=Object.pose.bones
    for Bone in ChildList:
        if Config.Verbose: print("      Writing Bone: {}...".format(Bone.name),end=" ")
        Config.File.write("{}Frame {} {{\n".format("  "*Config.Whitespace,LegalName(Object.name)+"_"+LegalName(Bone.name)))
        Config.Whitespace+=1
        
        PoseBone=PoseBones[Bone.name]
        
        if Bone.parent:
            BoneMatrix=(PoseBone.parent.matrix*RotationMatrix(radians(-90),4,"X")).invert()
        else:
            BoneMatrix=Matrix()
        
        BoneMatrix*=PoseBone.matrix*RotationMatrix(radians(-90),4,"X")
        BoneMatrix=Config.SystemMatrix*BoneMatrix*Config.InverseSystemMatrix
        
        Config.File.write("{}FrameTransformMatrix {{\n".format("  "*Config.Whitespace))
        Config.Whitespace+=1
        Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  "*Config.Whitespace,BoneMatrix[0][0],BoneMatrix[0][1],BoneMatrix[0][2],BoneMatrix[0][3]))
        Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  "*Config.Whitespace,BoneMatrix[1][0],BoneMatrix[1][1],BoneMatrix[1][2],BoneMatrix[1][3]))
        Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  "*Config.Whitespace,BoneMatrix[2][0],BoneMatrix[2][1],BoneMatrix[2][2],BoneMatrix[2][3]))
        Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,BoneMatrix[3][0],BoneMatrix[3][1],BoneMatrix[3][2],BoneMatrix[3][3]))
        Config.Whitespace-=1
        Config.File.write("{}}}\n".format("  "*Config.Whitespace))
        
        if Config.Verbose: print("Done")
        WriteArmatureBones(Config,Object,Bone.children)
        Config.Whitespace-=1
        
        Config.File.write("{}}} //End of {}\n".format("  "*Config.Whitespace,LegalName(Object.name)+"_"+LegalName(Bone.name)))

def WriteMesh(Config,Object,Mesh):
    Config.File.write("{}Mesh {{ //{} Mesh\n".format("  "*Config.Whitespace,LegalName(Mesh.name)))
    Config.Whitespace+=1
    
    if Config.Verbose: print("      Writing Mesh Vertices...",end=" ")
    WriteMeshVertices(Config,Mesh)
    if Config.Verbose: print("Done\n      Writing Mesh Normals...",end=" ")
    WriteMeshNormals(Config,Mesh)
    if Config.Verbose: print("Done\n      Writing Mesh Materials...",end=" ")
    WriteMeshMaterials(Config,Mesh)
    if Config.Verbose: print("Done")
    if Mesh.uv_textures:
        if Config.Verbose: print("      Writing Mesh UV Coordinates...",end=" ")
        WriteMeshUVCoordinates(Config,Mesh)
        if Config.Verbose: print("Done")
    if Config.ExportArmatures:
        if Config.Verbose: print("      Writing Mesh Skin Weights...",end=" ")
        WriteMeshSkinWeights(Config,Object,Mesh)
        if Config.Verbose: print("Done")
        
    Config.Whitespace-=1
    Config.File.write("{}}} //End of {} Mesh\n".format("  "*Config.Whitespace,LegalName(Mesh.name)))

def WriteMeshVertices(Config,Mesh):
    Index=0
    VertexCount=GetMeshVertexCount(Mesh)
    Config.File.write("{}{};\n".format("  "*Config.Whitespace,VertexCount))
    
    for Face in Mesh.faces:
        Vertices=list(Face.verts)
        
        if Config.CoordinateSystem==1: Vertices=Vertices[::-1]
        for Vertex in [Mesh.verts[Vertex] for Vertex in Vertices]:
            Position=Config.SystemMatrix*Vertex.co
            Config.File.write("{}{:9f};{:9f};{:9f};".format("  "*Config.Whitespace,Position[0],Position[1],Position[2]))
            Index+=1
            if Index==VertexCount: Config.File.write(";\n")
            else: Config.File.write(",\n")
    
    Index=0
    Config.File.write("{}{};\n".format("  "*Config.Whitespace,len(Mesh.faces)))
    
    for Face in Mesh.faces:
        Config.File.write("{}{};".format("  "*Config.Whitespace,len(Face.verts)))
        for Vertex in Face.verts:
            Config.File.write("{};".format(Index))
            Index+=1
        if Index==VertexCount: Config.File.write(";\n")
        else: Config.File.write(",\n")

def WriteMeshNormals(Config,Mesh):
    Config.File.write("{}MeshNormals {{ //{} Normals\n".format("  "*Config.Whitespace,LegalName(Mesh.name)))
    Config.Whitespace+=1
    
    Index=0
    VertexCount=GetMeshVertexCount(Mesh)
    Config.File.write("{}{};\n".format("  "*Config.Whitespace,VertexCount))
    
    for Face in Mesh.faces:
        Vertices=list(Face.verts)
        
        if Config.CoordinateSystem==1: Vertices=Vertices[::-1]
        for Vertex in [Mesh.verts[Vertex] for Vertex in Vertices]:
            if Face.smooth: Normal=Config.SystemMatrix*Vertex.normal
            else: Normal=Config.SystemMatrix*Face.normal
            if Config.FlipNormals: Normal=-Normal
            Config.File.write("{}{:9f};{:9f};{:9f};".format("  "*Config.Whitespace,Normal[0],Normal[1],Normal[2]))
            Index+=1
            if Index==VertexCount: Config.File.write(";\n")
            else: Config.File.write(",\n")
    
    Index=0
    Config.File.write("{}{};\n".format("  "*Config.Whitespace,len(Mesh.faces)))
    
    for Face in Mesh.faces:
        Config.File.write("{}{};".format("  "*Config.Whitespace,len(Face.verts)))
        for Vertex in Face.verts:
            Config.File.write("{};".format(Index))
            Index+=1
        if Index==VertexCount: Config.File.write(";\n")
        else: Config.File.write(",\n")
    Config.Whitespace-=1
    Config.File.write("{}}} //End of {} Normals\n".format("  "*Config.Whitespace,LegalName(Mesh.name)))

def WriteMeshMaterials(Config,Mesh):
    Config.File.write("{}MeshMaterialList {{ //{} Material List\n".format("  "*Config.Whitespace,LegalName(Mesh.name)))
    Config.Whitespace+=1
    
    Materials=Mesh.materials
    if Materials.keys():
        MaterialIndexes={}
        for Face in Mesh.faces:
            if Materials[Face.material_index] not in MaterialIndexes:
                MaterialIndexes[Materials[Face.material_index]]=len(MaterialIndexes)
        
        FaceCount=len(Mesh.faces)
        Index=0
        Config.File.write("{}{};\n{}{};\n".format("  "*Config.Whitespace,len(MaterialIndexes),"  "*Config.Whitespace,FaceCount))
        for Face in Mesh.faces:
            Config.File.write("{}{}".format("  "*Config.Whitespace,MaterialIndexes[Materials[Face.material_index]]))
            Index+=1
            if Index==FaceCount: Config.File.write(";;\n")
            else: Config.File.write(",\n")
        
        Materials=[Item[::-1] for Item in MaterialIndexes.items()]
        Materials.sort()
        for Material in Materials:
            WriteMaterial(Config,Material[1])
    else:
        Config.File.write("{}1;\n{}1;\n{}0;;\n".format("  "*Config.Whitespace,"  "*Config.Whitespace,"  "*Config.Whitespace))
        WriteMaterial(Config)
    
    Config.Whitespace-=1
    Config.File.write("{}}} //End of {} Material List\n".format("  "*Config.Whitespace,LegalName(Mesh.name)))

def WriteMaterial(Config,Material=None):
    if Material:
        Config.File.write("{}Material {} {{\n".format("  "*Config.Whitespace,LegalName(Material.name)))
        Config.Whitespace+=1
        
        Diffuse=list(Material.diffuse_color)
        Diffuse.append(Material.alpha)
        Specularity=Material.specular_intensity
        Specular=list(Material.specular_color)
        
        Config.File.write("{}{:9f};{:9f};{:9f};{:9f};;\n".format("  "*Config.Whitespace,Diffuse[0],Diffuse[1],Diffuse[2],Diffuse[3]))
        Config.File.write("{}{:9f};\n".format("  "*Config.Whitespace,2*(1.0-Specularity)))
        Config.File.write("{}{:9f};{:9f};{:9f};;\n".format("  "*Config.Whitespace,Specular[0],Specular[1],Specular[2]))
    else:
        Config.File.write("{}Material Default_Material {{\n".format("  "*Config.Whitespace))
        Config.Whitespace+=1
        Config.File.write("{} 1.000000; 1.000000; 1.000000; 1.000000;;\n".format("  "*Config.Whitespace))
        Config.File.write("{} 1.500000;\n".format("  "*Config.Whitespace))
        Config.File.write("{} 1.000000; 1.000000; 1.000000;;\n".format("  "*Config.Whitespace))
    Config.File.write("{} 0.000000; 0.000000; 0.000000;;\n".format("  "*Config.Whitespace))
    if Config.ExportTextures:
        Texture=GetMaterialTexture(Material)
        if Texture: Config.File.write("{}TextureFilename {{\"{}\";}}\n".format("  "*Config.Whitespace,Texture))
    Config.Whitespace-=1
    Config.File.write("{}}}\n".format("  "*Config.Whitespace))

def WriteMeshUVCoordinates(Config,Mesh):
    Config.File.write("{}MeshTextureCoords {{ //{} UV Coordinates\n".format("  "*Config.Whitespace,LegalName(Mesh.name)))
    Config.Whitespace+=1
    
    UVCoordinates=None
    for UV in Mesh.uv_textures:
        if UV.active_render:
            UVCoordinates=UV.data
            break
    
    Index=0
    VertexCount=GetMeshVertexCount(Mesh)
    Config.File.write("{}{};\n".format("  "*Config.Whitespace,VertexCount))
    
    for Face in UVCoordinates:
        Vertices=[]
        for Vertex in Face.uv:
            Vertices.append(tuple(Vertex))
        if Config.CoordinateSystem==1:
            Vertices=Vertices[::-1]
        for Vertex in Vertices:
            Config.File.write("{}{:9f};{:9f};".format("  "*Config.Whitespace,Vertex[0],1-Vertex[1]))
            Index+=1
            if Index==VertexCount: Config.File.write(";\n")
            else: Config.File.write(",\n")
    Config.Whitespace-=1
    Config.File.write("{}}} //End of {} UV Coordinates\n".format("  "*Config.Whitespace,LegalName(Mesh.name)))

def WriteMeshSkinWeights(Config,Object,Mesh):
    ArmatureList=[Modifier for Modifier in Object.modifiers if Modifier.type=="ARMATURE"]
    if ArmatureList:
        ArmatureObject=ArmatureList[0].object
        Armature=ArmatureObject.data
        
        PoseBones=ArmatureObject.pose.bones
        
        MaxInfluences=0
        UsedBones=set()
        VertexGroups={}
        
        for Vertex in Mesh.verts:
            BoneInfluences=[PoseBones[Object.vertex_groups[Group.group].name] for Group in Vertex.groups if Object.vertex_groups[Group.group].name in PoseBones]
            if len(BoneInfluences)>MaxInfluences: MaxInfluences=len(BoneInfluences)
            for Bone in BoneInfluences:
                UsedBones.add(Bone)
                if Bone not in VertexGroups:
                    VertexGroups[Bone]=[Vertex]
                else:
                    VertexGroups[Bone].append(Vertex)
        BoneCount=len(UsedBones)
        
        Config.File.write("{}XSkinMeshHeader {{\n".format("  "*Config.Whitespace))
        Config.Whitespace+=1
        Config.File.write("{}{};\n{}{};\n{}{};\n".format("  "*Config.Whitespace,MaxInfluences,"  "*Config.Whitespace,MaxInfluences*3,"  "*Config.Whitespace,BoneCount))
        Config.Whitespace-=1
        Config.File.write("{}}}\n".format("  "*Config.Whitespace))
        
        for Bone in UsedBones:
            VertexCount=0
            VertexIndexes=[Vertex.index for Vertex in VertexGroups[Bone]]
            for Face in Mesh.faces:
                for Vertex in Face.verts:
                    if Vertex in VertexIndexes: VertexCount+=1
            
            Config.File.write("{}SkinWeights {{\n".format("  "*Config.Whitespace))
            Config.Whitespace+=1
            Config.File.write("{}\"{}\";\n{}{};\n".format("  "*Config.Whitespace,LegalName(ArmatureObject.name)+"_"+LegalName(Bone.name),"  "*Config.Whitespace,VertexCount))
            
            VertexWeights=[]
            Index=0
            WrittenIndexes=0
            for Face in Mesh.faces:
                FaceVertices=list(Face.verts)
                if Config.CoordinateSystem==1: FaceVertices=FaceVertices[::-1]
                for Vertex in FaceVertices:
                    if Vertex in VertexIndexes:
                        Config.File.write("{}{}".format("  "*Config.Whitespace,Index))
                        
                        GroupIndexes={Object.vertex_groups[Group.group].name:Index for Index,Group in enumerate(Mesh.verts[Vertex].groups) if Object.vertex_groups[Group.group].name in PoseBones}
                        
                        WeightTotal=0.0
                        for Weight in [Group.weight for Group in Mesh.verts[Vertex].groups if Object.vertex_groups[Group.group].name in PoseBones]:
                            WeightTotal+=Weight
                        
                        if WeightTotal:
                            VertexWeights.append(Mesh.verts[Vertex].groups[GroupIndexes[Bone.name]].weight/WeightTotal)
                        else:
                            VertexWeights.append(0.0)
                        
                        WrittenIndexes+=1
                        if WrittenIndexes==VertexCount: Config.File.write(";\n")
                        else: Config.File.write(",\n")
                    Index+=1
            
            for Index,Weight in enumerate(VertexWeights):
                Config.File.write("{}{:8f}".format("  "*Config.Whitespace,Weight))
                if Index==(VertexCount-1): Config.File.write(";\n")
                else: Config.File.write(",\n")
            
            PoseBone=PoseBones[Bone.name]
            
            BoneMatrix=(PoseBone.matrix*RotationMatrix(radians(-90),4,"X")).invert()
            BoneMatrix*=ArmatureObject.matrix_world.copy().invert()
            
            if Object.parent and Object.parent!=ArmatureObject:
                BoneMatrix*=Object.parent.matrix_world.copy().invert()
            
            BoneMatrix*=Object.matrix_world
            
            BoneMatrix=Config.SystemMatrix*BoneMatrix*Config.InverseSystemMatrix
            
            Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  "*Config.Whitespace,BoneMatrix[0][0],BoneMatrix[0][1],BoneMatrix[0][2],BoneMatrix[0][3]))
            Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  "*Config.Whitespace,BoneMatrix[1][0],BoneMatrix[1][1],BoneMatrix[1][2],BoneMatrix[1][3]))
            Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  "*Config.Whitespace,BoneMatrix[2][0],BoneMatrix[2][1],BoneMatrix[2][2],BoneMatrix[2][3]))
            Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,BoneMatrix[3][0],BoneMatrix[3][1],BoneMatrix[3][2],BoneMatrix[3][3]))
            Config.Whitespace-=1
            Config.File.write("{}}}  //End of {} Skin Weights\n".format("  "*Config.Whitespace,LegalName(ArmatureObject.name)+"_"+LegalName(Bone.name)))
                            

def WriteAnimationSet(Config):
    Config.File.write("{}AnimationSet {{\n".format("  "*Config.Whitespace))
    Config.Whitespace+=1
    for Object in [Object for Object in Config.ObjectList if Object.animation_data]:
        if Config.Verbose: print("  Writing Animation Data for Object: {}".format(Object.name))
        Action=Object.animation_data.action
        if Action:
            PositionFCurves=[None,None,None]
            RotationFCurves=[None,None,None]
            ScaleFCurves=[None,None,None]
            for FCurve in Action.fcurves:
                if FCurve.data_path=="location":
                    PositionFCurves[FCurve.array_index]=FCurve
                elif FCurve.data_path=="rotation_euler":
                    RotationFCurves[FCurve.array_index]=FCurve
                elif FCurve.data_path=="scale":
                    ScaleFCurves[FCurve.array_index]=FCurve
            if [FCurve for FCurve in PositionFCurves+RotationFCurves+ScaleFCurves if FCurve]:
                Config.File.write("{}Animation {{\n".format("  "*Config.Whitespace))
                Config.Whitespace+=1
                Config.File.write("{}{{{}}}\n".format("  "*Config.Whitespace,LegalName(Object.name)))
                
                #Position
                if Config.Verbose: print("    Writing Position...",end=" ")
                AllKeyframes=set()
                for Index,FCurve in enumerate(PositionFCurves):
                    if FCurve:
                        Keyframes=[]
                        for Keyframe in FCurve.keyframe_points:
                            Keyframes.append(Keyframe.co)
                            AllKeyframes.add(int(Keyframe.co[0]))
                        PositionFCurves[Index]={int(Keyframe):Value for Keyframe,Value in Keyframes}
                Config.File.write("{}AnimationKey {{ //Position\n".format("  "*Config.Whitespace))
                Config.Whitespace+=1
                AllKeyframes=list(AllKeyframes)
                AllKeyframes.sort()
                if len(AllKeyframes):
                    Config.File.write("{}2;\n{}{};\n".format("  "*Config.Whitespace,"  "*Config.Whitespace,len(AllKeyframes)))
                    for Keyframe in AllKeyframes:
                        bpy.context.scene.set_frame(Keyframe)
                        Position=Vector()
                        Position[0]=((PositionFCurves[0][Keyframe] if Keyframe in PositionFCurves[0] else Object.location[0]) if PositionFCurves[0] else Object.location[0])
                        Position[1]=((PositionFCurves[1][Keyframe] if Keyframe in PositionFCurves[1] else Object.location[1]) if PositionFCurves[1] else Object.location[1])
                        Position[2]=((PositionFCurves[2][Keyframe] if Keyframe in PositionFCurves[2] else Object.location[2]) if PositionFCurves[2] else Object.location[2])
                        Position=Config.SystemMatrix*Position
                        Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,(str(Keyframe-bpy.context.scene.frame_start)+";3;").ljust(8),Position[0],Position[1],Position[2]))
                else:
                    Config.File.write("{}2;\n{}1;\n".format("  "*Config.Whitespace,"  "*Config.Whitespace))
                    bpy.context.scene.set_frame(bpy.context.scene.frame_start)
                    Position=Config.SystemMatrix*Object.location
                    Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,("0;3;").ljust(8),Position[0],Position[1],Position[2]))
                Config.Whitespace-=1
                Config.File.write("{}}}\n".format("  "*Config.Whitespace))
                if Config.Verbose: print("Done")
                
                #Rotation
                if Config.Verbose: print("    Writing Rotation...",end=" ")
                AllKeyframes=set()
                for Index,FCurve in enumerate(RotationFCurves):
                    if FCurve:
                        Keyframes=[]
                        for Keyframe in FCurve.keyframe_points:
                            Keyframes.append(Keyframe.co)
                            AllKeyframes.add(int(Keyframe.co[0]))
                        RotationFCurves[Index]={int(Keyframe):Value for Keyframe,Value in Keyframes}
                Config.File.write("{}AnimationKey {{ //Rotation\n".format("  "*Config.Whitespace))
                Config.Whitespace+=1
                AllKeyframes=list(AllKeyframes)
                AllKeyframes.sort()
                if len(AllKeyframes):
                    Config.File.write("{}0;\n{}{};\n".format("  "*Config.Whitespace,"  "*Config.Whitespace,len(AllKeyframes)))
                    for Keyframe in AllKeyframes:
                        bpy.context.scene.set_frame(Keyframe)
                        Rotation=Euler()
                        Rotation[0]=((RotationFCurves[0][Keyframe] if Keyframe in RotationFCurves[0] else Object.rotation_euler[0]) if RotationFCurves[0] else Object.rotation_euler[0])
                        Rotation[1]=((RotationFCurves[1][Keyframe] if Keyframe in RotationFCurves[1] else Object.rotation_euler[1]) if RotationFCurves[1] else Object.rotation_euler[1])
                        Rotation[2]=((RotationFCurves[2][Keyframe] if Keyframe in RotationFCurves[2] else Object.rotation_euler[2]) if RotationFCurves[2] else Object.rotation_euler[2])
                        Rotation=(Config.SystemMatrix*(Rotation.to_matrix().to_4x4())*Config.InverseSystemMatrix).to_quat()
                        Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,(str(Keyframe-bpy.context.scene.frame_start)+";4;").ljust(8),-Rotation[0],Rotation[1],Rotation[2],Rotation[3]))
                else:
                    Config.File.write("{}0;\n{}1;\n".format("  "*Config.Whitespace,"  "*Config.Whitespace))
                    bpy.context.scene.set_frame(bpy.context.scene.frame_start)
                    Rotation=(Config.SystemMatrix*(Object.rotation_euler.to_matrix().to_4x4())*Config.InverseSystemMatrix).to_quat()
                    Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,("0;4;").ljust(8),-Rotation[0],Rotation[1],Rotation[2],Rotation[3]))
                Config.Whitespace-=1
                Config.File.write("{}}}\n".format("  "*Config.Whitespace))
                if Config.Verbose: print("Done")
                
                #Scale
                if Config.Verbose: print("    Writing Scale...",end=" ")
                AllKeyframes=set()
                for Index,FCurve in enumerate(ScaleFCurves):
                    if FCurve:
                        Keyframes=[]
                        for Keyframe in FCurve.keyframe_points:
                            Keyframes.append(Keyframe.co)
                            AllKeyframes.add(int(Keyframe.co[0]))
                        ScaleFCurves[Index]={int(Keyframe):Value for Keyframe,Value in Keyframes}
                Config.File.write("{}AnimationKey {{ //Scale\n".format("  "*Config.Whitespace))
                Config.Whitespace+=1
                AllKeyframes=list(AllKeyframes)
                AllKeyframes.sort()
                if len(AllKeyframes):
                    Config.File.write("{}1;\n{}{};\n".format("  "*Config.Whitespace,"  "*Config.Whitespace,len(AllKeyframes)))
                    for Keyframe in AllKeyframes:
                        bpy.context.scene.set_frame(Keyframe)
                        Scale=Vector()
                        Scale[0]=((ScaleFCurves[0][Keyframe] if Keyframe in ScaleFCurves[0] else Object.scale[0]) if ScaleFCurves[0] else Object.scale[0])
                        Scale[1]=((ScaleFCurves[1][Keyframe] if Keyframe in ScaleFCurves[1] else Object.scale[1]) if ScaleFCurves[1] else Object.scale[1])
                        Scale[2]=((ScaleFCurves[2][Keyframe] if Keyframe in ScaleFCurves[2] else Object.scale[2]) if ScaleFCurves[2] else Object.scale[2])
                        Scale=Config.SystemMatrix*Scale
                        Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,(str(Keyframe-bpy.context.scene.frame_start)+";3;").ljust(8),Scale[0],Scale[1],Scale[2]))
                else:
                    Config.File.write("{}1;\n{}1;\n".format("  "*Config.Whitespace,"  "*Config.Whitespace))
                    bpy.context.scene.set_frame(bpy.context.scene.frame_start)
                    Scale=Config.SystemMatrix*Object.scale
                    Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,("0;3;").ljust(8),Scale[0],Scale[1],Scale[2]))
                Config.Whitespace-=1
                Config.File.write("{}}}\n".format("  "*Config.Whitespace))
                if Config.Verbose: print("Done")
                
                Config.Whitespace-=1
                Config.File.write("{}}}\n".format("  "*Config.Whitespace))
            else:
                if Config.Verbose: print("    Object has no useable animation data.")
            
            if Config.ExportArmatures and Object.type=="ARMATURE":
                if Config.Verbose: print("    Writing Armature Bone Animation Data...")
                PoseBones=Object.pose.bones
                for Bone in PoseBones:
                    if Config.Verbose: print("      Writing Bone: {}...".format(Bone.name))
                    PositionFCurves=[None,None,None]
                    RotationFCurves=[None,None,None,None]
                    ScaleFCurves=[None,None,None]
                    for FCurve in Action.fcurves:
                        if FCurve.data_path=="pose.bones[\"{}\"].location".format(Bone.name):
                            PositionFCurves[FCurve.array_index]=FCurve
                        elif FCurve.data_path=="pose.bones[\"{}\"].rotation_quaternion".format(Bone.name):
                            RotationFCurves[FCurve.array_index]=FCurve
                        elif FCurve.data_path=="pose.bones[\"{}\"].scale".format(Bone.name):
                            ScaleFCurves[FCurve.array_index]=FCurve
                    if not [FCurve for FCurve in PositionFCurves+RotationFCurves+ScaleFCurves if FCurve]:
                        if Config.Verbose: print("        Bone has no useable animation data.\n      Done")
                        continue
                    
                    Config.File.write("{}Animation {{\n".format("  "*Config.Whitespace))
                    Config.Whitespace+=1
                    Config.File.write("{}{{{}}}\n".format("  "*Config.Whitespace,LegalName(Object.name)+"_"+LegalName(Bone.name)))
                    
                    #Position
                    if Config.Verbose: print("        Writing Position...",end=" ")
                    AllKeyframes=set()
                    for Index,FCurve in enumerate(PositionFCurves):
                        if FCurve:
                            Keyframes=[]
                            for Keyframe in FCurve.keyframe_points:
                                Keyframes.append(Keyframe.co)
                                AllKeyframes.add(int(Keyframe.co[0]))
                            PositionFCurves[Index]={int(Keyframe):Value for Keyframe,Value in Keyframes}
                    Config.File.write("{}AnimationKey {{ //Position\n".format("  "*Config.Whitespace))
                    Config.Whitespace+=1
                    AllKeyframes=list(AllKeyframes)
                    AllKeyframes.sort()
                    if not len(AllKeyframes):
                        AllKeyframes=[bpy.context.scene.frame_start]    
                    Config.File.write("{}2;\n{}{};\n".format("  "*Config.Whitespace,"  "*Config.Whitespace,len(AllKeyframes)))
                    for Keyframe in AllKeyframes:
                        bpy.context.scene.set_frame(Keyframe)
                        
                        if Bone.parent:
                            PoseMatrix=(Bone.parent.matrix*RotationMatrix(radians(-90),4,"X")).invert()
                        else:
                            PoseMatrix=Matrix()
                        PoseMatrix*=Bone.matrix*RotationMatrix(radians(-90),4,"X")
                        
                        PoseMatrix=Config.SystemMatrix*PoseMatrix*Config.InverseSystemMatrix    
                        
                        Position=PoseMatrix.translation_part()
                        Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,(str(Keyframe-bpy.context.scene.frame_start)+";3;").ljust(8),Position[0],Position[1],Position[2]))
                    Config.Whitespace-=1
                    Config.File.write("{}}}\n".format("  "*Config.Whitespace))
                    if Config.Verbose: print("Done")
                    
                    #Rotation
                    if Config.Verbose: print("        Writing Rotation...",end=" ")
                    AllKeyframes=set()
                    for Index,FCurve in enumerate(RotationFCurves):
                        if FCurve:
                            Keyframes=[]
                            for Keyframe in FCurve.keyframe_points:
                                Keyframes.append(Keyframe.co)
                                AllKeyframes.add(int(Keyframe.co[0]))
                            RotationFCurves[Index]={int(Keyframe):Value for Keyframe,Value in Keyframes}
                    Config.File.write("{}AnimationKey {{ //Rotation\n".format("  "*Config.Whitespace))
                    Config.Whitespace+=1
                    AllKeyframes=list(AllKeyframes)
                    AllKeyframes.sort()
                    if not len(AllKeyframes):
                        AllKeyframes=[bpy.context.scene.frame_start]    
                    Config.File.write("{}0;\n{}{};\n".format("  "*Config.Whitespace,"  "*Config.Whitespace,len(AllKeyframes)))
                    for Keyframe in AllKeyframes:
                        bpy.context.scene.set_frame(Keyframe)
                        
                        if Bone.parent:
                            PoseMatrix=(Bone.parent.matrix*RotationMatrix(radians(-90),4,"X")).invert()
                        else:
                            PoseMatrix=Matrix()
                        PoseMatrix*=Bone.matrix*RotationMatrix(radians(-90),4,"X")
                        
                        PoseMatrix=Config.SystemMatrix*PoseMatrix*Config.InverseSystemMatrix    
                        
                        Rotation=PoseMatrix.rotation_part().to_quat()
                        Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,(str(Keyframe-bpy.context.scene.frame_start)+";4;").ljust(8),-Rotation[0],Rotation[1],Rotation[2],Rotation[3]))
                    Config.Whitespace-=1
                    Config.File.write("{}}}\n".format("  "*Config.Whitespace))
                    if Config.Verbose: print("Done")
                    
                    #Scale
                    if Config.Verbose: print("        Writing Scale...",end=" ")
                    AllKeyframes=set()
                    for Index,FCurve in enumerate(ScaleFCurves):
                        if FCurve:
                            Keyframes=[]
                            for Keyframe in FCurve.keyframe_points:
                                Keyframes.append(Keyframe.co)
                                AllKeyframes.add(int(Keyframe.co[0]))
                            ScaleFCurves[Index]={int(Keyframe):Value for Keyframe,Value in Keyframes}
                    Config.File.write("{}AnimationKey {{ //Scale\n".format("  "*Config.Whitespace))
                    Config.Whitespace+=1
                    AllKeyframes=list(AllKeyframes)
                    AllKeyframes.sort()
                    if not len(AllKeyframes):
                        AllKeyframes=[bpy.context.scene.frame_start]    
                    Config.File.write("{}1;\n{}{};\n".format("  "*Config.Whitespace,"  "*Config.Whitespace,len(AllKeyframes)))
                    for Keyframe in AllKeyframes:
                        bpy.context.scene.set_frame(Keyframe)
                        
                        if Bone.parent:
                            PoseMatrix=(Bone.parent.matrix*RotationMatrix(radians(-90),4,"X")).invert()
                        else:
                            PoseMatrix=Matrix()
                        PoseMatrix*=Bone.matrix*RotationMatrix(radians(-90),4,"X")
                        
                        PoseMatrix=Config.SystemMatrix*PoseMatrix*Config.InverseSystemMatrix    
                        
                        Scale=PoseMatrix.scale_part()
                        Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format("  "*Config.Whitespace,(str(Keyframe-bpy.context.scene.frame_start)+";3;").ljust(8),Scale[0],Scale[1],Scale[2]))
                    Config.Whitespace-=1
                    Config.File.write("{}}}\n".format("  "*Config.Whitespace))
                    if Config.Verbose: print("Done")
                    
                    Config.Whitespace-=1
                    Config.File.write("{}}}\n".format("  "*Config.Whitespace))
                    if Config.Verbose: print("      Done")
                if Config.Verbose: print("    Done")
        if Config.Verbose: print("  Done")
            
    Config.Whitespace-=1
    Config.File.write("{}}} //End of AnimationSet\n".format("  "*Config.Whitespace))

def CloseFile(Config):
    if Config.Verbose: print("Closing File...",end=" ")
    Config.File.close()
    if Config.Verbose: print("Done")

CoordinateSystems=[]
CoordinateSystems.append(("1","Left-Handed",""))
CoordinateSystems.append(("2","Right-Handed",""))

AnimationModes=[]
AnimationModes.append(("0","None",""))
AnimationModes.append(("1","Keyframes Only",""))
#AnimationModes.append(("2","Full Animation",""))

ExportModes=[]
ExportModes.append(("1","All Objects",""))
ExportModes.append(("2","Selected Objects",""))

from bpy.props import *

class DirectXExporter(bpy.types.Operator):
    """Export to the DirectX model format (.x)"""
    
    bl_idname="export.directx"
    bl_label="Export DirectX"
    
    filepath=StringProperty()
    filename=StringProperty()
    directory=StringProperty()
    
    #Coordinate System
    CoordinateSystem=EnumProperty(name="System",description="Select a coordinate system to export to",items=CoordinateSystems,default="1")
    
    #General Options
    RotateX=BoolProperty(name="Rotate X 90 Degrees",description="Rotate the entire scene 90 degrees around the X axis so Y is up",default=True)
    FlipNormals=BoolProperty(name="Flip Normals",description="",default=False)
    ApplyModifiers=BoolProperty(name="Apply Modifiers",description="Apply all object modifiers before export.",default=False)
    IncludeFrameRate=BoolProperty(name="Include Frame Rate",description="Include the AnimTicksPerSecond template which is used by some engines to control animation speed.",default=False)
    ExportTextures=BoolProperty(name="Export Textures",description="Reference external image files to be used by the model",default=True)
    ExportArmatures=BoolProperty(name="Export Armatures",description="Export the bones of any armatures to deform meshes.  Warning: This option also applies all modifiers.",default=False)
    ExportAnimation=EnumProperty(name="Animations",description="Select the type of animations to export.  Only object and armature bone animations can be exported.",items=AnimationModes,default="0")
    
    #Export Mode
    ExportMode=EnumProperty(name="Export",description="Select which objects to export.  Only Mesh, Empty, and Armature objects will be exported.",items=ExportModes,default="1")
    
    Verbose=BoolProperty(name="Verbose",description="Run the exporter in debug mode.  Check the console for output.",default=False)
    
    def execute(self,context):
        #Append .x if needed
        FilePath=self.properties.filepath
        if not FilePath.lower().endswith(".x"):
            FilePath+=".x"
        
        Config=DirectXExporterSettings(context,
                                       FilePath,
                                       CoordinateSystem=self.properties.CoordinateSystem,
                                       RotateX=self.properties.RotateX,
                                       FlipNormals=self.properties.FlipNormals,
                                       ApplyModifiers=self.properties.ApplyModifiers,
                                       IncludeFrameRate=self.properties.IncludeFrameRate,
                                       ExportTextures=self.properties.ExportTextures,
                                       ExportArmatures=self.properties.ExportArmatures,
                                       ExportAnimation=self.properties.ExportAnimation,
                                       ExportMode=self.properties.ExportMode,
                                       Verbose=self.properties.Verbose)
        ExportDirectX(Config)
        return {"FINISHED"}
    
    def invoke(self,context,event):
        WindowManager=context.manager
        WindowManager.add_fileselect(self)
        return {"RUNNING_MODAL"}

def menu_func(self,context):
    DefaultPath=bpy.data.filepath
    if DefaultPath.endswith(".blend"):
        DefaultPath=DefaultPath[:-6]+".x"
    self.layout.operator(DirectXExporter.bl_idname,text="DirectX (.x)").filepath=DefaultPath

def register():
    bpy.types.register(DirectXExporter)
    bpy.types.INFO_MT_file_export.append(menu_func)

def unregister():
    bpy.types.unregister(DirectXExporter)
    bpy.types.INFO_MT_file_export.remove(menu_func)

if __name__=="__main__":
    register()