Skip to content
Snippets Groups Projects
io_export_directx_x.py 61.3 KiB
Newer Older
#  ***** 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 *****
bl_info = {
    "name": "DirectX Model Format (.x)",
    "author": "Chris Foster (Kira Vakaan)",
Chris Foster's avatar
Chris Foster committed
    "version": (2, 1, 3),
    "blender": (2, 6, 3),
    "location": "File > Export > DirectX (.x)",
    "description": "Export DirectX Model Format (.x)",
    "warning": "",
    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\
Luca Bonavita's avatar
Luca Bonavita committed
        "Scripts/Import-Export/DirectX_Exporter",
Luca Bonavita's avatar
Luca Bonavita committed
    "tracker_url": "https://projects.blender.org/tracker/index.php?"\
        "func=detail&aid=22795",
Luca Bonavita's avatar
Luca Bonavita committed
    "category": "Import-Export"}
import bpy
from mathutils import *

#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):
    
    def ReplaceSet(String, OldSet, NewChar):
        for OldChar in OldSet:
            String = String.replace(OldChar, NewChar)
        return String
    
    import string
    
    NewName = ReplaceSet(Name, string.punctuation + " ", "_")
    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
def ExportDirectX(Config):
    print("----------\nExporting to {}".format(Config.FilePath))
    if Config.Verbose:
    Config.File = open(Config.FilePath, "w")
    if Config.Verbose:
        print("Done")

    if Config.Verbose:
        print("Generating Object list for export... (Root parents only)")
    if Config.ExportMode == 1:
        Config.ExportList = [Object for Object in Config.context.scene.objects
                             if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}
                             and Object.parent is None]
        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("  List: {}\nDone".format(Config.ExportList))
    Config.SystemMatrix = Matrix()
    if Config.RotateX:
        Config.SystemMatrix *= Matrix.Rotation(radians(-90), 4, "X")
    if Config.CoordinateSystem == 1:
        Config.SystemMatrix *= Matrix.Scale(-1, 4, Vector((0, 1, 0)))
    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:
    WriteHeader(Config)
    if Config.Verbose:
        print("Done")

    Config.Whitespace = 0
    if Config.Verbose:
        print("Writing Root Frame...")
    WriteRootFrame(Config)
    if Config.Verbose:
        print("Done")
    
    Config.ObjectList = []
    if Config.Verbose:
        print("Writing Objects...")
    WriteObjects(Config, Config.ExportList)
    if Config.Verbose:
        print("Done")
    
    Config.Whitespace -= 1
    Config.File.write("{}}} //End of Root Frame\n".format("  " * Config.Whitespace))
    
    if Config.Verbose:
        print("Objects Exported: {}".format(Config.ExportList))
    if Config.ExportAnimation:
        if Config.IncludeFrameRate:
            if Config.Verbose:
            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...")
        if Config.ExportAnimation==1:
            WriteKeyedAnimationSet(Config)
        else:
            WriteFullAnimationSet(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'}]
#Returns the vertex count of Mesh, counting each vertex for every face.
def GetMeshVertexCount(Mesh):
Chris Foster's avatar
Chris Foster committed
    for Polygon in Mesh.polygons:
        VertexCount += len(Polygon.vertices)
#Returns the file path of first image texture from Material.
Chris Foster's avatar
Chris Foster committed
def GetMaterialTextureFileName(Material):
        #Create a list of Textures that have type "IMAGE"
        ImageTextures = [Material.texture_slots[TextureSlot].texture for TextureSlot in Material.texture_slots.keys() if Material.texture_slots[TextureSlot].texture.type == "IMAGE"]
        #Refine a new list with only image textures that have a file source
        ImageFiles = [bpy.path.basename(Texture.image.filepath) for Texture in ImageTextures if getattr(Texture.image, "source", "") == "FILE"]
        if ImageFiles:
            return ImageFiles[0]
    return None

def WriteHeader(Config):
    Config.File.write("xof 0303txt 0032\n\n")
    
    if Config.IncludeFrameRate:
        Config.File.write("template AnimTicksPerSecond {\n\
  <9E415A43-7BA6-4a73-8743-B73D47E88476>\n\
  DWORD AnimTicksPerSecond;\n\
}\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 WriteRootFrame(Config):
    Config.File.write("{}Frame Root {{\n".format("  " * Config.Whitespace))
    Config.Whitespace += 1
    
    Config.File.write("{}FrameTransformMatrix {{\n".format("  " * Config.Whitespace))
    Config.Whitespace += 1
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  " * Config.Whitespace, Config.SystemMatrix[0][0], Config.SystemMatrix[1][0], Config.SystemMatrix[2][0], Config.SystemMatrix[3][0]))
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  " * Config.Whitespace, Config.SystemMatrix[0][1], Config.SystemMatrix[1][1], Config.SystemMatrix[2][1], Config.SystemMatrix[3][1]))
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  " * Config.Whitespace, Config.SystemMatrix[0][2], Config.SystemMatrix[1][2], Config.SystemMatrix[2][2], Config.SystemMatrix[3][2]))
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format("  " * Config.Whitespace, Config.SystemMatrix[0][3], Config.SystemMatrix[1][3], Config.SystemMatrix[2][3], Config.SystemMatrix[3][3]))
    Config.Whitespace -= 1
    Config.File.write("{}}}\n".format("  " * Config.Whitespace))

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:
        WriteLocalMatrix(Config, Object)
        if Config.Verbose:

        if Config.ExportArmatures and Object.type == "ARMATURE":
            Armature = Object.data
            ParentList = [Bone for Bone in Armature.bones if Bone.parent is None]
            if Config.Verbose:
                print("    Writing Armature Bones...")
            WriteArmatureBones(Config, Object, ParentList)
            if Config.Verbose:
                print("    Done")
        ChildList = GetObjectChildren(Object)
        if Config.ExportMode == 2: #Selected Objects Only
            ChildList = [Child for Child in ChildList
                         if Child in Config.context.selected_objects]
        if Config.Verbose:
            print("    Writing Children...")
        WriteObjects(Config, ChildList)
        if Config.Verbose:
            print("    Done Writing Children")

        if Object.type == "MESH":
            if Config.Verbose:
            if Config.ApplyModifiers:
                if Config.ExportArmatures:
                    #Create a copy of the object and remove all armature modifiers so an unshaped
                    #mesh can be created from it.
                    Object2 = Object.copy()
                    for Modifier in [Modifier for Modifier in Object2.modifiers if Modifier.type == "ARMATURE"]:
                        Object2.modifiers.remove(Modifier)
                    Mesh = Object2.to_mesh(bpy.context.scene, True, "PREVIEW")
                    Mesh = Object.to_mesh(bpy.context.scene, True, "PREVIEW")
                Mesh = Object.to_mesh(bpy.context.scene, False, "PREVIEW")
            if Config.Verbose:
                print("    Writing Mesh...")
            WriteMesh(Config, Object, Mesh)
            if Config.Verbose:
                print("    Done")
            if Config.ApplyModifiers and Config.ExportArmatures:
                bpy.data.objects.remove(Object2)
            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):

    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[1][0], LocalMatrix[2][0], LocalMatrix[3][0]))
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  " * Config.Whitespace, LocalMatrix[0][1], LocalMatrix[1][1], LocalMatrix[2][1], LocalMatrix[3][1]))
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  " * Config.Whitespace, LocalMatrix[0][2], LocalMatrix[1][2], LocalMatrix[2][2], LocalMatrix[3][2]))
    Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format("  " * Config.Whitespace, LocalMatrix[0][3], LocalMatrix[1][3], LocalMatrix[2][3], 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))
        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.inverted()
            BoneMatrix = Matrix()

        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[1][0], BoneMatrix[2][0], BoneMatrix[3][0]))
        Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  " * Config.Whitespace, BoneMatrix[0][1], BoneMatrix[1][1], BoneMatrix[2][1], BoneMatrix[3][1]))
        Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format("  " * Config.Whitespace, BoneMatrix[0][2], BoneMatrix[1][2], BoneMatrix[2][2], BoneMatrix[3][2]))
        Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format("  " * Config.Whitespace, BoneMatrix[0][3], BoneMatrix[1][3], BoneMatrix[2][3], BoneMatrix[3][3]))
        Config.Whitespace -= 1
        Config.File.write("{}}}\n".format("  " * Config.Whitespace))

        if Config.Verbose:
        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:
    WriteMeshVertices(Config, Mesh)
    if Config.Verbose:
        print("      Done\n      Writing Mesh Normals...")
    WriteMeshNormals(Config, Mesh)
    if Config.Verbose:
        print("      Done\n      Writing Mesh Materials...")
    WriteMeshMaterials(Config, Mesh)
    if Config.Verbose:
    if Mesh.uv_textures:
        if Config.Verbose:
            print("      Writing Mesh UV Coordinates...")
        WriteMeshUVCoordinates(Config, Mesh)
        if Config.Verbose:
    if Config.ExportArmatures:
        if Config.Verbose:
            print("      Writing Mesh Skin Weights...")
        WriteMeshSkinWeights(Config, Object, Mesh)
        if Config.Verbose:

    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))

Chris Foster's avatar
Chris Foster committed
    for Polygon in Mesh.polygons:
        Vertices = list(Polygon.vertices)

        if Config.CoordinateSystem == 1:
            Vertices = Vertices[::-1]
Chris Foster's avatar
Chris Foster committed

        for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
Loading
Loading full blame...