diff --git a/export_directx_x.py b/export_directx_x.py new file mode 100644 index 0000000000000000000000000000000000000000..627226cba40308d2a6d5113e5e0637e12160cb8a --- /dev/null +++ b/export_directx_x.py @@ -0,0 +1,870 @@ + # ***** 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': '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=467', + '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() \ No newline at end of file