mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-16 15:35:05 +00:00
1241 lines
60 KiB
Python
1241 lines
60 KiB
Python
# ***** 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)",
|
|
"version": (2, 1),
|
|
"blender": (2, 5, 7),
|
|
"api": 36302,
|
|
"location": "File > Export > DirectX (.x)",
|
|
"description": "Export DirectX Model Format (.x)",
|
|
"warning": "",
|
|
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
|
|
"Scripts/Import-Export/DirectX_Exporter",
|
|
"tracker_url": "https://projects.blender.org/tracker/index.php?"\
|
|
"func=detail&aid=22795",
|
|
"category": "Import-Export"}
|
|
|
|
import os
|
|
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 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...")
|
|
Config.File = open(Config.FilePath, "w")
|
|
if Config.Verbose:
|
|
print("Done")
|
|
|
|
if Config.Verbose:
|
|
print("Generating Object list for export...")
|
|
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]
|
|
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(" List: {}\nDone".format(Config.ExportList))
|
|
|
|
if Config.Verbose:
|
|
print("Setting up...")
|
|
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:
|
|
print("Writing Header...")
|
|
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.ExportAnimation:
|
|
if Config.IncludeFrameRate:
|
|
if Config.Verbose:
|
|
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):
|
|
VertexCount = 0
|
|
for Face in Mesh.faces:
|
|
VertexCount += len(Face.vertices)
|
|
return VertexCount
|
|
|
|
#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 = [os.path.basename(Texture.image.filepath) 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.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
|
|
|
|
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...")
|
|
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")
|
|
|
|
if Config.ExportMode == 1:
|
|
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...")
|
|
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")
|
|
else:
|
|
Mesh = Object.to_mesh(bpy.context.scene, True, "PREVIEW")
|
|
else:
|
|
Mesh = Object.to_mesh(bpy.context.scene, False, "PREVIEW")
|
|
if Config.Verbose:
|
|
print(" Done")
|
|
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):
|
|
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
|
|
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()
|
|
else:
|
|
BoneMatrix = Matrix()
|
|
|
|
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")
|
|
if Mesh.uv_textures:
|
|
if Config.Verbose:
|
|
print(" Writing Mesh UV Coordinates...")
|
|
WriteMeshUVCoordinates(Config, Mesh)
|
|
if Config.Verbose:
|
|
print(" Done")
|
|
if Config.ExportArmatures:
|
|
if Config.Verbose:
|
|
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))
|
|
|
|
for Face in Mesh.faces:
|
|
Vertices = list(Face.vertices)
|
|
|
|
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)))
|
|
|
|
for Face in 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))
|
|
|
|
for Face in Mesh.faces:
|
|
Vertices = list(Face.vertices)
|
|
|
|
if Config.CoordinateSystem == 1:
|
|
Vertices = Vertices[::-1]
|
|
for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
|
|
if Face.use_smooth:
|
|
Normal = Vertex.normal
|
|
else:
|
|
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)))
|
|
|
|
for Face in 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
|
|
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(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]))
|
|
else:
|
|
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))
|
|
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
|
|
ArmatureBones = ArmatureObject.data.bones
|
|
|
|
PoseBones = ArmatureObject.pose.bones
|
|
|
|
MaxInfluences = 0
|
|
UsedBones = set()
|
|
#Maps bones to a list of vertices they affect
|
|
VertexGroups = {}
|
|
|
|
for Vertex in Mesh.vertices:
|
|
#BoneInfluences contains the bones of the armature that affect the current vertex
|
|
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.vertices:
|
|
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.vertices)
|
|
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.vertices[Vertex].groups) if Object.vertex_groups[Group.group].name in PoseBones}
|
|
|
|
WeightTotal = 0.0
|
|
for Weight in [Group.weight for Group in Mesh.vertices[Vertex].groups if Object.vertex_groups[Group.group].name in PoseBones]:
|
|
WeightTotal += Weight
|
|
|
|
if WeightTotal:
|
|
VertexWeights.append(Mesh.vertices[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")
|
|
|
|
RestBone = ArmatureBones[Bone.name]
|
|
|
|
#BoneMatrix transforms mesh vertices into the space of the bone.
|
|
#Here are the final transformations in order:
|
|
# - Object Space to World Space
|
|
# - World Space to Armature Space
|
|
# - Armature Space to Bone Space (The bone matrix needs to be rotated 90 degrees to align with Blender's world axes)
|
|
#This way, when BoneMatrix is transformed by the bone's Frame matrix, the vertices will be in their final world position.
|
|
|
|
BoneMatrix = RestBone.matrix_local.inverted()
|
|
BoneMatrix *= ArmatureObject.matrix_world.inverted()
|
|
BoneMatrix *= Object.matrix_world
|
|
|
|
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 WriteKeyedAnimationSet(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...")
|
|
AllKeyframes = set()
|
|
for Index, FCurve in enumerate(PositionFCurves):
|
|
if FCurve:
|
|
Keyframes = []
|
|
for Keyframe in FCurve.keyframe_points:
|
|
if Keyframe.co[0] < bpy.context.scene.frame_start:
|
|
AllKeyframes.add(bpy.context.scene.frame_start)
|
|
elif Keyframe.co[0] > bpy.context.scene.frame_end:
|
|
AllKeyframes.add(bpy.context.scene.frame_end)
|
|
else:
|
|
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.frame_set(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])
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
|
|
if Keyframe == AllKeyframes[-1]:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
|
|
else:
|
|
Config.File.write("{}2;\n{}1;\n".format(" " * Config.Whitespace, " " * Config.Whitespace))
|
|
bpy.context.scene.frame_set(bpy.context.scene.frame_start)
|
|
Position = Object.matrix_local.to_translation()
|
|
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...")
|
|
AllKeyframes = set()
|
|
for Index, FCurve in enumerate(RotationFCurves):
|
|
if FCurve:
|
|
Keyframes = []
|
|
for Keyframe in FCurve.keyframe_points:
|
|
if Keyframe.co[0] < bpy.context.scene.frame_start:
|
|
AllKeyframes.add(bpy.context.scene.frame_start)
|
|
elif Keyframe.co[0] > bpy.context.scene.frame_end:
|
|
AllKeyframes.add(bpy.context.scene.frame_end)
|
|
else:
|
|
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.frame_set(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 = Rotation.to_quaternion()
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";4;").ljust(8), -Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
|
|
if Keyframe == AllKeyframes[-1]:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
else:
|
|
Config.File.write("{}0;\n{}1;\n".format(" " * Config.Whitespace, " " * Config.Whitespace))
|
|
bpy.context.scene.frame_set(bpy.context.scene.frame_start)
|
|
Rotation = Object.rotation_euler.to_quaternion()
|
|
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...")
|
|
AllKeyframes = set()
|
|
for Index, FCurve in enumerate(ScaleFCurves):
|
|
if FCurve:
|
|
Keyframes = []
|
|
for Keyframe in FCurve.keyframe_points:
|
|
if Keyframe.co[0] < bpy.context.scene.frame_start:
|
|
AllKeyframes.add(bpy.context.scene.frame_start)
|
|
elif Keyframe.co[0] > bpy.context.scene.frame_end:
|
|
AllKeyframes.add(bpy.context.scene.frame_end)
|
|
else:
|
|
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.frame_set(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])
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
|
|
if Keyframe == AllKeyframes[-1]:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
else:
|
|
Config.File.write("{}1;\n{}1;\n".format(" " * Config.Whitespace, " " * Config.Whitespace))
|
|
bpy.context.scene.frame_set(bpy.context.scene.frame_start)
|
|
Scale = Object.matrix_local.to_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...")
|
|
AllKeyframes = set()
|
|
for Index, FCurve in enumerate(PositionFCurves):
|
|
if FCurve:
|
|
Keyframes = []
|
|
for Keyframe in FCurve.keyframe_points:
|
|
if Keyframe.co[0] < bpy.context.scene.frame_start:
|
|
AllKeyframes.add(bpy.context.scene.frame_start)
|
|
elif Keyframe.co[0] > bpy.context.scene.frame_end:
|
|
AllKeyframes.add(bpy.context.scene.frame_end)
|
|
else:
|
|
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.frame_set(Keyframe)
|
|
|
|
if Bone.parent:
|
|
PoseMatrix = Bone.parent.matrix.inverted()
|
|
else:
|
|
PoseMatrix = Matrix()
|
|
PoseMatrix *= Bone.matrix
|
|
|
|
Position = PoseMatrix.to_translation()
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
|
|
if Keyframe == AllKeyframes[-1]:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
Config.Whitespace -= 1
|
|
Config.File.write("{}}}\n".format(" " * Config.Whitespace))
|
|
if Config.Verbose:
|
|
print(" Done")
|
|
|
|
#Rotation
|
|
if Config.Verbose:
|
|
print(" Writing Rotation...")
|
|
AllKeyframes = set()
|
|
for Index, FCurve in enumerate(RotationFCurves):
|
|
if FCurve:
|
|
Keyframes = []
|
|
for Keyframe in FCurve.keyframe_points:
|
|
if Keyframe.co[0] < bpy.context.scene.frame_start:
|
|
AllKeyframes.add(bpy.context.scene.frame_start)
|
|
elif Keyframe.co[0] > bpy.context.scene.frame_end:
|
|
AllKeyframes.add(bpy.context.scene.frame_end)
|
|
else:
|
|
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.frame_set(Keyframe)
|
|
|
|
if Bone.parent:
|
|
PoseMatrix = Bone.parent.matrix.inverted()
|
|
else:
|
|
PoseMatrix = Matrix()
|
|
PoseMatrix *= Bone.matrix
|
|
|
|
Rotation = PoseMatrix.to_3x3().to_quaternion()
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";4;").ljust(8), -Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
|
|
if Keyframe == AllKeyframes[-1]:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
Config.Whitespace -= 1
|
|
Config.File.write("{}}}\n".format(" " * Config.Whitespace))
|
|
if Config.Verbose:
|
|
print(" Done")
|
|
|
|
#Scale
|
|
if Config.Verbose:
|
|
print(" Writing Scale...")
|
|
AllKeyframes = set()
|
|
for Index, FCurve in enumerate(ScaleFCurves):
|
|
if FCurve:
|
|
Keyframes = []
|
|
for Keyframe in FCurve.keyframe_points:
|
|
if Keyframe.co[0] < bpy.context.scene.frame_start:
|
|
AllKeyframes.add(bpy.context.scene.frame_start)
|
|
elif Keyframe.co[0] > bpy.context.scene.frame_end:
|
|
AllKeyframes.add(bpy.context.scene.frame_end)
|
|
else:
|
|
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.frame_set(Keyframe)
|
|
|
|
if Bone.parent:
|
|
PoseMatrix = Bone.parent.matrix.inverted()
|
|
else:
|
|
PoseMatrix = Matrix()
|
|
PoseMatrix *= Bone.matrix
|
|
|
|
Scale = PoseMatrix.to_scale()
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
|
|
if Keyframe == AllKeyframes[-1]:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
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") #Done with Armature Bone
|
|
if Config.Verbose:
|
|
print(" Done") #Done with Armature Bone data
|
|
if Config.Verbose:
|
|
print(" Done") #Done with Object
|
|
|
|
Config.Whitespace -= 1
|
|
Config.File.write("{}}} //End of AnimationSet\n".format(" " * Config.Whitespace))
|
|
|
|
def WriteFullAnimationSet(Config):
|
|
Config.File.write("{}AnimationSet {{\n".format(" " * Config.Whitespace))
|
|
Config.Whitespace += 1
|
|
|
|
KeyframeCount = bpy.context.scene.frame_end - bpy.context.scene.frame_start + 1
|
|
|
|
for Object in Config.ObjectList:
|
|
if Config.Verbose:
|
|
print(" Writing Animation Data for Object: {}".format(Object.name))
|
|
|
|
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...")
|
|
Config.File.write("{}AnimationKey {{ //Position\n".format(" " * Config.Whitespace))
|
|
Config.Whitespace += 1
|
|
Config.File.write("{}2;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, KeyframeCount))
|
|
for Frame in range(0, KeyframeCount):
|
|
bpy.context.scene.frame_set(Frame + bpy.context.scene.frame_start)
|
|
Position = Object.matrix_local.to_translation()
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
|
|
if Frame == KeyframeCount-1:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
Config.Whitespace -= 1
|
|
Config.File.write("{}}}\n".format(" " * Config.Whitespace))
|
|
if Config.Verbose:
|
|
print(" Done")
|
|
|
|
#Rotation
|
|
if Config.Verbose:
|
|
print(" Writing Rotation...")
|
|
Config.File.write("{}AnimationKey {{ //Rotation\n".format(" " * Config.Whitespace))
|
|
Config.Whitespace += 1
|
|
Config.File.write("{}0;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, KeyframeCount))
|
|
for Frame in range(0, KeyframeCount):
|
|
bpy.context.scene.frame_set(Frame + bpy.context.scene.frame_start)
|
|
Rotation = Object.rotation_euler.to_quaternion()
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";4;").ljust(8), -Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
|
|
if Frame == KeyframeCount-1:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
Config.Whitespace -= 1
|
|
Config.File.write("{}}}\n".format(" " * Config.Whitespace))
|
|
if Config.Verbose:
|
|
print(" Done")
|
|
|
|
#Scale
|
|
if Config.Verbose:
|
|
print(" Writing Scale...")
|
|
Config.File.write("{}AnimationKey {{ //Scale\n".format(" " * Config.Whitespace))
|
|
Config.Whitespace += 1
|
|
Config.File.write("{}1;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, KeyframeCount))
|
|
for Frame in range(0, KeyframeCount):
|
|
bpy.context.scene.frame_set(Frame + bpy.context.scene.frame_start)
|
|
Scale = Object.matrix_local.to_scale()
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
|
|
if Frame == KeyframeCount-1:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
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.ExportArmatures and Object.type == "ARMATURE":
|
|
if Config.Verbose:
|
|
print(" Writing Armature Bone Animation Data...")
|
|
PoseBones = Object.pose.bones
|
|
Bones = Object.data.bones
|
|
for Bone in PoseBones:
|
|
if Config.Verbose:
|
|
print(" Writing Bone: {}...".format(Bone.name))
|
|
|
|
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...")
|
|
Config.File.write("{}AnimationKey {{ //Position\n".format(" " * Config.Whitespace))
|
|
Config.Whitespace += 1
|
|
Config.File.write("{}2;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, KeyframeCount))
|
|
for Frame in range(0, KeyframeCount):
|
|
bpy.context.scene.frame_set(Frame + bpy.context.scene.frame_start)
|
|
|
|
if Bone.parent:
|
|
PoseMatrix = Bone.parent.matrix.inverted()
|
|
else:
|
|
PoseMatrix = Matrix()
|
|
PoseMatrix *= Bone.matrix
|
|
|
|
Position = PoseMatrix.to_translation()
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
|
|
if Frame == KeyframeCount-1:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
Config.Whitespace -= 1
|
|
Config.File.write("{}}}\n".format(" " * Config.Whitespace))
|
|
if Config.Verbose:
|
|
print(" Done")
|
|
|
|
#Rotation
|
|
if Config.Verbose:
|
|
print(" Writing Rotation...")
|
|
Config.File.write("{}AnimationKey {{ //Rotation\n".format(" " * Config.Whitespace))
|
|
Config.Whitespace += 1
|
|
Config.File.write("{}0;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, KeyframeCount))
|
|
for Frame in range(0, KeyframeCount):
|
|
bpy.context.scene.frame_set(Frame + bpy.context.scene.frame_start)
|
|
|
|
Rotation = Bones[Bone.name].matrix.to_quaternion() * Bone.rotation_quaternion
|
|
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";4;").ljust(8), -Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
|
|
if Frame == KeyframeCount-1:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
Config.Whitespace -= 1
|
|
Config.File.write("{}}}\n".format(" " * Config.Whitespace))
|
|
if Config.Verbose:
|
|
print(" Done")
|
|
|
|
#Scale
|
|
if Config.Verbose:
|
|
print(" Writing Scale...")
|
|
Config.File.write("{}AnimationKey {{ //Scale\n".format(" " * Config.Whitespace, KeyframeCount))
|
|
Config.Whitespace += 1
|
|
Config.File.write("{}1;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, KeyframeCount))
|
|
for Frame in range(0, KeyframeCount):
|
|
bpy.context.scene.frame_set(Frame + bpy.context.scene.frame_start)
|
|
|
|
if Bone.parent:
|
|
PoseMatrix = Bone.parent.matrix.inverted()
|
|
else:
|
|
PoseMatrix = Matrix()
|
|
PoseMatrix *= Bone.matrix
|
|
|
|
Scale = PoseMatrix.to_scale()
|
|
Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
|
|
if Frame == KeyframeCount-1:
|
|
Config.File.write(";\n")
|
|
else:
|
|
Config.File.write(",\n")
|
|
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") #Done with Armature Bone
|
|
if Config.Verbose:
|
|
print(" Done") #Done with Armature Bone data
|
|
if Config.Verbose:
|
|
print(" Done") #Done with Object
|
|
|
|
Config.Whitespace -= 1
|
|
Config.File.write("{}}} //End of AnimationSet\n".format(" " * Config.Whitespace))
|
|
|
|
|
|
def CloseFile(Config):
|
|
if Config.Verbose:
|
|
print("Closing File...")
|
|
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(subtype='FILE_PATH')
|
|
|
|
#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 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.", default=False)
|
|
ExportAnimation = EnumProperty(name="Animations", description="Select the type of animations to export. Only object and armature bone animations can be exported. Full Animation exports every frame.", 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.filepath
|
|
if not FilePath.lower().endswith(".x"):
|
|
FilePath += ".x"
|
|
|
|
Config = DirectXExporterSettings(context,
|
|
FilePath,
|
|
CoordinateSystem=self.CoordinateSystem,
|
|
RotateX=self.RotateX,
|
|
FlipNormals=self.FlipNormals,
|
|
ApplyModifiers=self.ApplyModifiers,
|
|
IncludeFrameRate=self.IncludeFrameRate,
|
|
ExportTextures=self.ExportTextures,
|
|
ExportArmatures=self.ExportArmatures,
|
|
ExportAnimation=self.ExportAnimation,
|
|
ExportMode=self.ExportMode,
|
|
Verbose=self.Verbose)
|
|
ExportDirectX(Config)
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
WindowManager = context.window_manager
|
|
WindowManager.fileselect_add(self)
|
|
return {"RUNNING_MODAL"}
|
|
|
|
|
|
def menu_func(self, context):
|
|
default_path = os.path.splitext(bpy.data.filepath)[0] + ".x"
|
|
self.layout.operator(DirectXExporter.bl_idname, text="DirectX (.x)").filepath = default_path
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_module(__name__)
|
|
|
|
bpy.types.INFO_MT_file_export.append(menu_func)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_module(__name__)
|
|
|
|
bpy.types.INFO_MT_file_export.remove(menu_func)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register() |