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 *****
"name": "DirectX Model Format (.x)",
"version": (2, 1, 2),
"blender": (2, 5, 8),
"api": 37702,
"location": "File > Export > DirectX (.x)",
"description": "Export DirectX Model Format (.x)",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
"tracker_url": "https://projects.blender.org/tracker/index.php?"\
from math import radians
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 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))
print("Opening File...")
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")
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))
print("Setting up...")
Config.SystemMatrix = Matrix()
Config.SystemMatrix *= Matrix.Rotation(radians(-90), 4, "X")
if Config.CoordinateSystem == 1:
Config.SystemMatrix *= Matrix.Scale(-1, 4, Vector((0, 1, 0)))
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...")
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:
print("Writing Frame Rate...")
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):
#Returns the file path of first image texture from Material.
def GetMaterialTexture(Material):
if 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")
Chris Foster
committed
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[0][1], Config.SystemMatrix[0][2], Config.SystemMatrix[0][3]))
Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, Config.SystemMatrix[1][0], Config.SystemMatrix[1][1], Config.SystemMatrix[1][2], Config.SystemMatrix[1][3]))
Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, Config.SystemMatrix[2][0], Config.SystemMatrix[2][1], Config.SystemMatrix[2][2], Config.SystemMatrix[2][3]))
Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, Config.SystemMatrix[3][0], Config.SystemMatrix[3][1], Config.SystemMatrix[3][2], Config.SystemMatrix[3][3]))
Config.Whitespace -= 1
Config.File.write("{}}}\n".format(" " * Config.Whitespace))
def WriteObjects(Config, ObjectList):
Config.ObjectList += 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...")
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 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:
print(" Generating Mesh...")
Chris Foster
committed
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")
Chris Foster
committed
else:
Mesh = Object.to_mesh(bpy.context.scene, True, "PREVIEW")
Chris Foster
committed
else:
Mesh = Object.to_mesh(bpy.context.scene, False, "PREVIEW")
print(" Done")
print(" Writing Mesh...")
WriteMesh(Config, Object, Mesh)
if Config.Verbose:
print(" Done")
Chris Foster
committed
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):
LocalMatrix = Object.matrix_local
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
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]
BoneMatrix = PoseBone.parent.matrix.inverted()
BoneMatrix *= PoseBone.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[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...")
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:
print(" Done")
print(" Writing Mesh UV Coordinates...")
WriteMeshUVCoordinates(Config, Mesh)
if Config.Verbose:
print(" Done")
print(" Writing Mesh Skin Weights...")
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))
if Config.CoordinateSystem == 1:
Vertices = Vertices[::-1]
for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
Position = 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)))
Config.File.write("{}{};".format(" " * Config.Whitespace, len(Face.vertices)))
for Vertex in Face.vertices:
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))
if Config.CoordinateSystem == 1:
Vertices = Vertices[::-1]
for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
Normal = Vertex.normal
Normal = 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)))
Config.File.write("{}{};".format(" " * Config.Whitespace, len(Face.vertices)))
for Vertex in Face.vertices:
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
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))
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])
Config.File.write("{}1;\n{}1;\n{}0;;\n".format(" " * Config.Whitespace, " " * Config.Whitespace, " " * Config.Whitespace))
Config.Whitespace -= 1
Config.File.write("{}}} //End of {} Material List\n".format(" " * Config.Whitespace, LegalName(Mesh.name)))
def WriteMaterial(Config, Material=None):
Config.File.write("{}Material {} {{\n".format(" " * Config.Whitespace, LegalName(Material.name)))
Config.Whitespace += 1
Diffuse = list(Vector(Material.diffuse_color) * Material.diffuse_intensity)
Diffuse.append(Material.alpha)
Specularity = 1000 * (Material.specular_hardness - 1.0) / (511.0 - 1.0) # Map Blender's range of 1 - 511 to 0 - 1000
Specular = list(Vector(Material.specular_color) * Material.specular_intensity)
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, Specularity))
Config.File.write("{}{:9f};{:9f};{:9f};;\n".format(" " * Config.Whitespace, Specular[0], Specular[1], Specular[2]))
Config.File.write("{}Material Default_Material {{\n".format(" " * Config.Whitespace))
Config.Whitespace += 1
Config.File.write("{} 0.800000; 0.800000; 0.800000; 0.800000;;\n".format(" " * Config.Whitespace))
Config.File.write("{} 96.078431;\n".format(" " * Config.Whitespace)) # 1000 * (50 - 1) / (511 - 1)
Config.File.write("{} 0.500000; 0.500000; 0.500000;;\n".format(" " * Config.Whitespace))
Config.File.write("{} 0.000000; 0.000000; 0.000000;;\n".format(" " * Config.Whitespace))
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:
Index = 0
VertexCount = GetMeshVertexCount(Mesh)
Config.File.write("{}{};\n".format(" " * Config.Whitespace, VertexCount))
for Vertex in Face.uv:
Vertices.append(tuple(Vertex))
Loading
Loading full blame...