diff options
author | Chris Foster <cdbfoster@gmail.com> | 2013-03-17 23:27:11 +0400 |
---|---|---|
committer | Chris Foster <cdbfoster@gmail.com> | 2013-03-17 23:27:11 +0400 |
commit | f3366818c0796ba057361abb26caa157b4d16453 (patch) | |
tree | cbcf413c9627720ca92a3b545efcd520392bb4db | |
parent | e56284b89d7a3c68f234a8c7c12aa7f1e620404c (diff) |
- A ground-up rewrite of the DirectX .x exporter for better design and extensibility.
- Optimized vertex, normal, and skin weight export where possible.
- Largely decoupled data gathering and data writing. This will make it much easier to provide binary file support in the future.
- Added animation option to export each action of each object as a separate AnimationSet and another option to export unused actions as if used by the first armature object.
- Added vertex color export support.
-rw-r--r-- | io_export_directx_x.py | 1299 | ||||
-rw-r--r-- | io_scene_x/__init__.py | 167 | ||||
-rw-r--r-- | io_scene_x/export_x.py | 1272 |
3 files changed, 1439 insertions, 1299 deletions
diff --git a/io_export_directx_x.py b/io_export_directx_x.py deleted file mode 100644 index 4532600c..00000000 --- a/io_export_directx_x.py +++ /dev/null @@ -1,1299 +0,0 @@ -# ***** 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, 3), - "blender": (2, 63, 0), - "location": "File > Export > DirectX (.x)", - "description": "Export DirectX Model Format (.x)", - "warning": "", - "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/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): - - 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 - 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... (Root parents only)") - if Config.ExportMode == 1: - Config.ExportList = [Object for Object in Config.context.scene.objects - if Object.type in {'ARMATURE', 'EMPTY', 'MESH'} - and Object.parent is None] - 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.Verbose: - print("Objects Exported: {}".format(Config.ExportList)) - - 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 Polygon in Mesh.polygons: - VertexCount += len(Polygon.vertices) - return VertexCount - -#Returns the file path of first image texture from Material. -def GetMaterialTextureFileName(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") - - if Config.IncludeFrameRate: - Config.File.write("template AnimTicksPerSecond {\n\ - <9E415A43-7BA6-4a73-8743-B73D47E88476>\n\ - DWORD AnimTicksPerSecond;\n\ -}\n\n") - - if Config.ExportArmatures: - Config.File.write("template XSkinMeshHeader {\n\ - <3cf169ce-ff7c-44ab-93c0-f78f62d172e2>\n\ - WORD nMaxSkinWeightsPerVertex;\n\ - WORD nMaxSkinWeightsPerFace;\n\ - WORD nBones;\n\ -}\n\n\ -template SkinWeights {\n\ - <6f0d123b-bad2-4167-a0d0-80224f25fabb>\n\ - STRING transformNodeName;\n\ - DWORD nWeights;\n\ - array DWORD vertexIndices[nWeights];\n\ - array float weights[nWeights];\n\ - Matrix4x4 matrixOffset;\n\ -}\n\n") - -def WriteRootFrame(Config): - Config.File.write("{}Frame Root {{\n".format(" " * Config.Whitespace)) - Config.Whitespace += 1 - - Config.File.write("{}FrameTransformMatrix {{\n".format(" " * Config.Whitespace)) - Config.Whitespace += 1 - Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, Config.SystemMatrix[0][0], Config.SystemMatrix[1][0], Config.SystemMatrix[2][0], Config.SystemMatrix[3][0])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, Config.SystemMatrix[0][1], Config.SystemMatrix[1][1], Config.SystemMatrix[2][1], Config.SystemMatrix[3][1])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, Config.SystemMatrix[0][2], Config.SystemMatrix[1][2], Config.SystemMatrix[2][2], Config.SystemMatrix[3][2])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, Config.SystemMatrix[0][3], Config.SystemMatrix[1][3], Config.SystemMatrix[2][3], Config.SystemMatrix[3][3])) - Config.Whitespace -= 1 - Config.File.write("{}}}\n".format(" " * Config.Whitespace)) - -def WriteObjects(Config, ObjectList): - Config.ObjectList += ObjectList - - for Object in ObjectList: - if Config.Verbose: - print(" Writing Object: {}...".format(Object.name)) - Config.File.write("{}Frame {} {{\n".format(" " * Config.Whitespace, LegalName(Object.name))) - - Config.Whitespace += 1 - if Config.Verbose: - 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...") - 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[1][0], LocalMatrix[2][0], LocalMatrix[3][0])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, LocalMatrix[0][1], LocalMatrix[1][1], LocalMatrix[2][1], LocalMatrix[3][1])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, LocalMatrix[0][2], LocalMatrix[1][2], LocalMatrix[2][2], LocalMatrix[3][2])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, LocalMatrix[0][3], LocalMatrix[1][3], LocalMatrix[2][3], LocalMatrix[3][3])) - Config.Whitespace -= 1 - Config.File.write("{}}}\n".format(" " * Config.Whitespace)) - - -def WriteArmatureBones(Config, Object, ChildList): - PoseBones = Object.pose.bones - for Bone in ChildList: - if Config.Verbose: - print(" Writing Bone: {}...".format(Bone.name)) - Config.File.write("{}Frame {} {{\n".format(" " * Config.Whitespace, LegalName(Object.name) + "_" + LegalName(Bone.name))) - Config.Whitespace += 1 - - PoseBone = PoseBones[Bone.name] - - if Bone.parent: - BoneMatrix = PoseBone.parent.matrix.inverted() - 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[1][0], BoneMatrix[2][0], BoneMatrix[3][0])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, BoneMatrix[0][1], BoneMatrix[1][1], BoneMatrix[2][1], BoneMatrix[3][1])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, BoneMatrix[0][2], BoneMatrix[1][2], BoneMatrix[2][2], BoneMatrix[3][2])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, BoneMatrix[0][3], BoneMatrix[1][3], BoneMatrix[2][3], BoneMatrix[3][3])) - Config.Whitespace -= 1 - Config.File.write("{}}}\n".format(" " * Config.Whitespace)) - - if Config.Verbose: - 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 Polygon in Mesh.polygons: - Vertices = list(Polygon.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.polygons))) - - for Polygon in Mesh.polygons: - Config.File.write("{}{};".format(" " * Config.Whitespace, len(Polygon.vertices))) - for Vertex in Polygon.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 Polygon in Mesh.polygons: - Vertices = list(Polygon.vertices) - - if Config.CoordinateSystem == 1: - Vertices = Vertices[::-1] - for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]: - if Polygon.use_smooth: - Normal = Vertex.normal - else: - Normal = Polygon.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.polygons))) - - for Polygon in Mesh.polygons: - Config.File.write("{}{};".format(" " * Config.Whitespace, len(Polygon.vertices))) - for Vertex in Polygon.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 Polygon in Mesh.polygons: - if Materials[Polygon.material_index] not in MaterialIndexes: - MaterialIndexes[Materials[Polygon.material_index]] = len(MaterialIndexes) - - PolygonCount = len(Mesh.polygons) - Index = 0 - Config.File.write("{}{};\n{}{};\n".format(" " * Config.Whitespace, len(MaterialIndexes), " " * Config.Whitespace, PolygonCount)) - for Polygon in Mesh.polygons: - Config.File.write("{}{}".format(" " * Config.Whitespace, MaterialIndexes[Materials[Polygon.material_index]])) - Index += 1 - if Index == PolygonCount: - 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: - TextureFileName = GetMaterialTextureFileName(Material) - if TextureFileName: - Config.File.write("{}TextureFilename {{\"{}\";}}\n".format(" " * Config.Whitespace, TextureFileName)) - 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 = Mesh.uv_layers.active.data - - Index = 0 - VertexCount = GetMeshVertexCount(Mesh) - Config.File.write("{}{};\n".format(" " * Config.Whitespace, VertexCount)) - - for Polygon in Mesh.polygons: - Vertices = [] - for Vertex in [UVCoordinates[Vertex] for Vertex in Polygon.loop_indices]: - Vertices.append(tuple(Vertex.uv)) - 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 = {} - ObjectVertexGroups = {i: Group.name for (i, Group) in enumerate(Object.vertex_groups)} - for Vertex in Mesh.vertices: - #BoneInfluences contains the bones of the armature that affect the current vertex - BoneInfluences = [PoseBone for Group in Vertex.groups - for PoseBone in (PoseBones.get(ObjectVertexGroups.get(Group.group, "")), ) - if PoseBone is not None - ] - - 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 Polygon in Mesh.polygons: - for Vertex in Polygon.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 Polygon in Mesh.polygons: - PolygonVertices = list(Polygon.vertices) - if Config.CoordinateSystem == 1: - PolygonVertices = PolygonVertices[::-1] - for Vertex in PolygonVertices: - if Vertex in VertexIndexes: - Config.File.write("{}{}".format(" " * Config.Whitespace, Index)) - - GroupIndexes = {ObjectVertexGroups.get(Group.group): Index - for Index, Group in enumerate(Mesh.vertices[Vertex].groups) - if ObjectVertexGroups.get(Group.group, "") in PoseBones} - - WeightTotal = 0.0 - for Weight in (Group.weight for Group in Mesh.vertices[Vertex].groups if ObjectVertexGroups.get(Group.group, "") 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[1][0], BoneMatrix[2][0], BoneMatrix[3][0])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, BoneMatrix[0][1], BoneMatrix[1][1], BoneMatrix[2][1], BoneMatrix[3][1])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, BoneMatrix[0][2], BoneMatrix[1][2], BoneMatrix[2][2], BoneMatrix[3][2])) - Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, BoneMatrix[0][3], BoneMatrix[1][3], BoneMatrix[2][3], BoneMatrix[3][3])) - Config.Whitespace -= 1 - Config.File.write("{}}} //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 = ( - ("1", "Left-Handed", ""), - ("2", "Right-Handed", ""), - ) - - -AnimationModes = ( - ("0", "None", ""), - ("1", "Keyframes Only", ""), - ("2", "Full Animation", ""), - ) - -ExportModes = ( - ("1", "All Objects", ""), - ("2", "Selected Objects", ""), - ) - - -from bpy.props import StringProperty, EnumProperty, BoolProperty - - -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 - FilePath = bpy.path.ensure_ext(self.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): - if not self.filepath: - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".x") - WindowManager = context.window_manager - WindowManager.fileselect_add(self) - return {"RUNNING_MODAL"} - - -def menu_func(self, context): - self.layout.operator(DirectXExporter.bl_idname, text="DirectX (.x)") - - -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() diff --git a/io_scene_x/__init__.py b/io_scene_x/__init__.py new file mode 100644 index 00000000..f1fa1c4c --- /dev/null +++ b/io_scene_x/__init__.py @@ -0,0 +1,167 @@ +# ##### BEGIN 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. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +bl_info = { + "name": "DirectX X Format", + "author": "Chris Foster", + "version": (3, 0, 0), + "blender": (2, 66, 0), + "location": "File > Export > DirectX (.x)", + "description": "Export mesh vertices, UV's, materials, textures, "\ + "vertex colors, armatures, empties, and actions.", + "warning": "This script is a WIP!", + "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\ + "Scripts/Import-Export/DirectX_Exporter", + "tracker_url": "https://projects.blender.org/tracker/index.php?"\ + "func=detail&aid=22795", + "category": "Import-Export"} + +import bpy +from bpy.props import BoolProperty +from bpy.props import EnumProperty +from bpy.props import StringProperty + + +class ExportDirectX(bpy.types.Operator): + """Export selection to DirectX""" + + bl_idname = "export_scene.x" + bl_label = "Export DirectX" + + filepath = StringProperty(subtype='FILE_PATH') + + # Export options + + SelectedOnly = BoolProperty( + name="Export Selected Objects Only", + description="Export only selected objects", + default=True) + + ExportMeshes = BoolProperty( + name="Export Meshes", + description="Export mesh objects", + default=True) + + ExportNormals = BoolProperty( + name=" Export Normals", + description="Export mesh normals", + default=True) + + ExportUVCoordinates = BoolProperty( + name=" Export UV Coordinates", + description="Export mesh UV coordinates, if any", + default=True) + + ExportMaterials = BoolProperty( + name=" Export Materials", + description="Export material properties and reference image textures", + default=True) + + ExportVertexColors = BoolProperty( + name=" Export Vertex Colors", + description="Export mesh vertex colors, if any", + default=False) + + ExportSkinWeights = BoolProperty( + name=" Export Skin Weights", + description="Bind mesh vertices to armature bones", + default=False) + + ApplyModifiers = BoolProperty( + name=" Apply Modifiers", + description="Apply the effects of object modifiers before export", + default=False) + + ExportArmatureBones = BoolProperty( + name="Export Armature Bones", + description="Export armatures bones", + default=False) + + ExportRestBone = BoolProperty( + name=" Export Rest Position", + description="Export bones in their rest position (recommended for "\ + "animation)", + default=False) + + ExportAnimation = BoolProperty( + name="Export Animations", + description="Export object and bone animations. Data is exported for "\ + "every frame", + 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) + + ExportActionsAsSets = BoolProperty( + name=" Export Actions as AnimationSets", + description="Export each action of each object as a separate "\ + "AnimationSet. Otherwise all current actions are lumped "\ + "together into a single set", + default=False) + + AttachToFirstArmature = BoolProperty( + name=" Attach Unused Actions to First Armature", + description="Export each unused action as if used by the first "\ + "armature object", + default=False) + + Verbose = BoolProperty( + name="Verbose", + description="Run the exporter in debug mode. Check the console for "\ + "output", + default=False) + + def execute(self, context): + self.filepath = bpy.path.ensure_ext(self.filepath, ".x") + + import export_x + Exporter = export_x.DirectXExporter(self, context) + Exporter.Export() + return {'FINISHED'} + + def invoke(self, context, event): + if not self.filepath: + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".x") + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + + +def menu_func(self, context): + self.layout.operator(ExportDirectX.bl_idname, text="DirectX (.x)") + + +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() + diff --git a/io_scene_x/export_x.py b/io_scene_x/export_x.py new file mode 100644 index 00000000..3aa2b909 --- /dev/null +++ b/io_scene_x/export_x.py @@ -0,0 +1,1272 @@ +# ##### BEGIN 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. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +from math import radians + +import bpy +from mathutils import * + + +class DirectXExporter: + def __init__(self, Config, context): + self.Config = Config + self.context = context + + self.Log("Begin verbose logging ----------\n") + + self.File = File(self.Config.filepath) + + self.Log("Setting up coordinate system...") + # SystemMatrix converts from right-handed, z-up to left-handed, y-up + self.SystemMatrix = (Matrix.Scale(-1, 4, Vector((0, 0, 1))) * + Matrix.Rotation(radians(-90), 4, 'X')) + self.Log("Done") + + self.Log("Generating object lists for export...") + if self.Config.SelectedOnly: + ExportList = list(self.context.selected_objects) + else: + ExportList = list(self.context.scene.objects) + + # ExportMap maps Blender objects to ExportObjects + ExportMap = {} + for Object in ExportList: + if Object.type == 'EMPTY': + ExportMap[Object] = EmptyExportObject(self.Config, self, Object) + elif Object.type == 'MESH': + ExportMap[Object] = MeshExportObject(self.Config, self, + Object) + elif Object.type == 'ARMATURE': + ExportMap[Object] = ArmatureExportObject(self.Config, self, + Object) + + # Find the objects who do not have a parent or whose parent we are + # not exporting + self.RootExportList = [Object for Object in ExportMap.values() + if Object.BlenderObject.parent not in ExportList] + self.RootExportList = Util.SortByNameField(self.RootExportList) + + self.ExportList = Util.SortByNameField(ExportMap.values()) + + # Determine each object's children from the pool of ExportObjects + for Object in ExportMap.values(): + Children = Object.BlenderObject.children + Object.Children = [] + for Child in Children: + if Child in ExportMap: + Object.Children.append(ExportMap[Child]) + self.Log("Done") + + self.AnimationWriter = None + if self.Config.ExportAnimation: + self.Log("Gathering animation data...") + + # Collect all animated object data + AnimationGenerators = self.__GatherAnimationGenerators() + + # Split the data up into animation sets based on user options + if self.Config.ExportActionsAsSets: + self.AnimationWriter = SplitSetAnimationWriter(self.Config, + self, AnimationGenerators) + else: + self.AnimationWriter = JoinedSetAnimationWriter(self.Config, + self, AnimationGenerators) + self.Log("Done") + + # "Public" Interface + + def Export(self): + self.Log("Exporting to {}".format(self.File.FilePath), + MessageVerbose=False) + + # Export everything + self.Log("Opening file...") + self.File.Open() + self.Log("Done") + + self.Log("Writing header...") + self.__WriteHeader() + self.Log("Done") + + self.Log("Opening Root frame...") + self.__OpenRootFrame() + self.Log("Done") + + self.Log("Writing objects...") + for Object in self.RootExportList: + Object.Write() + self.Log("Done writing objects") + + self.Log("Closing Root frame...") + self.__CloseRootFrame() + self.Log("Done") + + if self.AnimationWriter is not None: + self.Log("Writing animation set(s)...") + self.AnimationWriter.WriteAnimationSets() + self.Log("Done writing animation set(s)") + + self.Log("Closing file...") + self.File.Close() + self.Log("Done") + + def Log(self, String, MessageVerbose=True): + if self.Config.Verbose is True or MessageVerbose == False: + print(String) + + # "Private" Methods + + def __WriteHeader(self): + self.File.Write("xof 0303txt 0032\n\n") + + # Write the headers that are required by some engines as needed + + if self.Config.IncludeFrameRate: + self.File.Write("template AnimTicksPerSecond {\n\ + <9E415A43-7BA6-4a73-8743-B73D47E88476>\n\ + DWORD AnimTicksPerSecond;\n\ +}\n\n") + if self.Config.ExportSkinWeights: + self.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") + + # Start the Root frame and write its transform matrix + def __OpenRootFrame(self): + self.File.Write("Frame Root {\n") + self.File.Indent() + + self.File.Write("FrameTransformMatrix {\n") + self.File.Indent() + + # Write the matrix that will convert Blender's coordinate space into + # DirectX's. + Util.WriteMatrix(self.File, self.SystemMatrix) + + self.File.Unindent() + self.File.Write("}\n") + + def __CloseRootFrame(self): + self.File.Unindent() + self.File.Write("} // End of Root\n") + + def __GatherAnimationGenerators(self): + Generators = [] + + # If all animation data is to be lumped into one AnimationSet, + if not self.Config.ExportActionsAsSets: + # Build the appropriate generators for each object's type + for Object in self.ExportList: + if Object.BlenderObject.type == 'ARMATURE': + Generators.append(ArmatureAnimationGenerator(self.Config, + None, Object)) + else: + Generators.append(GenericAnimationGenerator(self.Config, + None, Object)) + # Otherwise, + else: + # Keep track of which objects have no action. These will be + # lumped together in a Default_Action AnimationSet. + ActionlessObjects = [] + + for Object in self.ExportList: + if Object.BlenderObject.animation_data is None: + ActionlessObjects.append(Object) + continue + else: + if Object.BlenderObject.animation_data.action is None: + ActionlessObjects.append(Object) + continue + + # If an object has an action, build its appropriate generator + if Object.BlenderObject.type == 'ARMATURE': + Generators.append(ArmatureAnimationGenerator(self.Config, + Util.SafeName( + Object.BlenderObject.animation_data.action.name), + Object)) + else: + Generators.append(GenericAnimationGenerator(self.Config, + Util.SafeName( + Object.BlenderObject.animation_data.action.name), + Object)) + + # If we should export unused actions as if the first armature was + # using them, + if self.Config.AttachToFirstArmature: + # Find the first armature + FirstArmature = None + for Object in self.ExportList: + if Object.BlenderObject.type == 'ARMATURE': + FirstArmature = Object + break + + if FirstArmature is not None: + # Determine which actions are not used + UsedActions = [BlenderObject.animation_data.action + for BlenderObject in bpy.data.objects + if BlenderObject.animation_data is not None] + FreeActions = [Action for Action in bpy.data.actions + if Action not in UsedActions] + + # If the first armature has no action, remove it from the + # actionless objects so it doesn't end up in Default_Action + if FirstArmature in ActionlessObjects and len(FreeActions): + ActionlessObjects.remove(FirstArmature) + + # Keep track of the first armature's animation data so we + # can restore it after export + OldAction = None + NoData = False + if FirstArmature.BlenderObject.animation_data is not None: + OldAction = \ + FirstArmature.BlenderObject.animation_data.action + else: + NoData = True + FirstArmature.BlenderObject.animation_data_create() + + # Build a generator for each unused action + for Action in FreeActions: + FirstArmature.BlenderObject.animation_data.action = \ + Action + + Generators.append(ArmatureAnimationGenerator( + self.Config, Util.SafeName(Action.name), + FirstArmature)) + + # Restore old animation data + FirstArmature.BlenderObject.animation_data.action = \ + OldAction + + if NoData: + FirstArmature.BlenderObject.animation_data_clear() + + # Build a special generator for all actionless objects + if len(ActionlessObjects): + Generators.append(GroupAnimationGenerator(self.Config, + "Default_Action", ActionlessObjects)) + + return Generators + +# This class wraps a Blender object and writes its data to the file +class ExportObject: # Base class, do not use + def __init__(self, Config, Exporter, BlenderObject): + self.Config = Config + self.Exporter = Exporter + self.BlenderObject = BlenderObject + + self.name = self.BlenderObject.name # Simple alias + self.SafeName = Util.SafeName(self.BlenderObject.name) + self.Children = [] + + def __repr__(self): + return "[ExportObject: {}]".format(self.BlenderObject.name) + + # "Public" Interface + + def Write(self): + self.Exporter.Log("Opening frame for {}".format(self)) + self._OpenFrame() + + self.Exporter.Log("Writing children of {}".format(self)) + self._WriteChildren() + + self._CloseFrame() + self.Exporter.Log("Closed frame of {}".format(self)) + + # "Protected" Interface + + def _OpenFrame(self): + self.Exporter.File.Write("Frame {} {{\n".format(self.SafeName)) + self.Exporter.File.Indent() + + self.Exporter.File.Write("FrameTransformMatrix {\n") + self.Exporter.File.Indent() + Util.WriteMatrix(self.Exporter.File, self.BlenderObject.matrix_local) + self.Exporter.File.Unindent() + self.Exporter.File.Write("}\n") + + def _CloseFrame(self): + self.Exporter.File.Unindent() + self.Exporter.File.Write("}} // End of {}\n".format(self.SafeName)) + + def _WriteChildren(self): + for Child in Util.SortByNameField(self.Children): + Child.Write() + +# Simple decorator implemenation for ExportObject. Used by empty objects +class EmptyExportObject(ExportObject): + def __init__(self, Config, Exporter, BlenderObject): + ExportObject.__init__(self, Config, Exporter, BlenderObject) + + def __repr__(self): + return "[EmptyExportObject: {}]".format(self.name) + +# Mesh object implementation of ExportObject +class MeshExportObject(ExportObject): + def __init__(self, Config, Exporter, BlenderObject): + ExportObject.__init__(self, Config, Exporter, BlenderObject) + + def __repr__(self): + return "[MeshExportObject: {}]".format(self.name) + + # "Public" Interface + + def Write(self): + self.Exporter.Log("Opening frame for {}".format(self)) + self._OpenFrame() + + if self.Config.ExportMeshes: + self.Exporter.Log("Generating mesh for export...") + # Generate the export mesh + Mesh = None + if self.Config.ApplyModifiers: + # Certain modifiers shouldn't be applied in some cases + # Deactivate them until after mesh generation is complete + + DeactivatedModifierList = [] + + # If we're exporting armature data, we shouldn't apply + # armature modifiers to the mesh + if self.Config.ExportSkinWeights: + DeactivatedModifierList = [Modifier + for Modifier in self.BlenderObject.modifiers + if Modifier.type == 'ARMATURE' and \ + Modifier.show_viewport] + + for Modifier in DeactivatedModifierList: + Modifier.show_viewport = False + + Mesh = self.BlenderObject.to_mesh(self.Exporter.context.scene, + True, 'PREVIEW') + + # Restore the deactivated modifiers + for Modifier in DeactivatedModifierList: + Modifier.show_viewport = True + else: + Mesh = self.BlenderObject.to_mesh(self.Exporter.context.scene, + False, 'PREVIEW') + self.Exporter.Log("Done") + + self.__WriteMesh(Mesh) + + # Cleanup + bpy.data.meshes.remove(Mesh) + + self.Exporter.Log("Writing children of {}".format(self)) + self._WriteChildren() + + self._CloseFrame() + self.Exporter.Log("Closed frame of {}".format(self)) + + # "Protected" + + # This class provides a general system for indexing a mesh, depending on + # exporter needs. For instance, some options require us to duplicate each + # vertex of each face, some can reuse vertex data. For those we'd use + # _UnrolledFacesMeshEnumerator and _OneToOneMeshEnumerator respectively. + class _MeshEnumerator: + def __init__(self, Mesh): + self.Mesh = Mesh + + # self.vertices and self.PolygonVertexIndexes relate to the + # original mesh in the following way: + + # Mesh.vertices[Mesh.polygons[x].vertices[y]] == + # self.vertices[self.PolygonVertexIndexes[x][y]] + + self.vertices = None + self.PolygonVertexIndexes = None + + # Represents the mesh as it is inside Blender + class _OneToOneMeshEnumerator(_MeshEnumerator): + def __init__(self, Mesh): + MeshExportObject._MeshEnumerator.__init__(self, Mesh) + + self.vertices = Mesh.vertices + + self.PolygonVertexIndexes = tuple(tuple(Polygon.vertices) + for Polygon in Mesh.polygons) + + # Duplicates each vertex for each face + class _UnrolledFacesMeshEnumerator(_MeshEnumerator): + def __init__(self, Mesh): + MeshExportObject._MeshEnumerator.__init__(self, Mesh) + + self.vertices = tuple() + for Polygon in Mesh.polygons: + self.vertices += tuple(Mesh.vertices[VertexIndex] + for VertexIndex in Polygon.vertices) + + self.PolygonVertexIndexes = [] + Index = 0 + for Polygon in Mesh.polygons: + self.PolygonVertexIndexes.append(tuple(range(Index, + Index + len(Polygon.vertices)))) + Index += len(Polygon.vertices) + + # "Private" Methods + + def __WriteMesh(self, Mesh): + self.Exporter.Log("Writing mesh vertices...") + self.Exporter.File.Write("Mesh {{ // {} mesh\n".format(self.SafeName)) + self.Exporter.File.Indent() + + # Create the mesh enumerator based on options + MeshEnumerator = None + if (self.Config.ExportUVCoordinates and Mesh.uv_textures) or \ + (self.Config.ExportVertexColors and Mesh.vertex_colors): + MeshEnumerator = MeshExportObject._UnrolledFacesMeshEnumerator(Mesh) + else: + MeshEnumerator = MeshExportObject._OneToOneMeshEnumerator(Mesh) + + # Write vertex positions + VertexCount = len(MeshEnumerator.vertices) + self.Exporter.File.Write("{};\n".format(VertexCount)) + for Index, Vertex in enumerate(MeshEnumerator.vertices): + Position = Vertex.co + self.Exporter.File.Write("{:9f};{:9f};{:9f};".format( + Position[0], Position[1], Position[2])) + + if Index == VertexCount - 1: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + # Write face definitions + PolygonCount = len(MeshEnumerator.PolygonVertexIndexes) + self.Exporter.File.Write("{};\n".format(PolygonCount)) + for Index, PolygonVertexIndexes in \ + enumerate(MeshEnumerator.PolygonVertexIndexes): + + self.Exporter.File.Write("{};".format(len(PolygonVertexIndexes))) + + PolygonVertexIndexes = PolygonVertexIndexes[::-1] + + for VertexIndex in PolygonVertexIndexes: + self.Exporter.File.Write("{};".format(VertexIndex), + Indent=False) + + if Index == PolygonCount - 1: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + self.Exporter.Log("Done") + + # Write the other mesh components + + if self.Config.ExportNormals: + self.Exporter.Log("Writing mesh normals...") + self.__WriteMeshNormals(Mesh) + self.Exporter.Log("Done") + + if self.Config.ExportUVCoordinates: + self.Exporter.Log("Writing mesh UV coordinates...") + self.__WriteMeshUVCoordinates(Mesh) + self.Exporter.Log("Done") + + if self.Config.ExportMaterials: + self.Exporter.Log("Writing mesh materials...") + self.__WriteMeshMaterials(Mesh) + self.Exporter.Log("Done") + + if self.Config.ExportVertexColors: + self.Exporter.Log("Writing mesh vertex colors...") + self.__WriteMeshVertexColors(Mesh, MeshEnumerator=MeshEnumerator) + self.Exporter.Log("Done") + + if self.Config.ExportSkinWeights: + self.Exporter.Log("Writing mesh skin weights...") + self.__WriteMeshSkinWeights(Mesh, MeshEnumerator=MeshEnumerator) + self.Exporter.Log("Done") + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}} // End of {} mesh\n".format(self.SafeName)) + + def __WriteMeshNormals(self, Mesh, MeshEnumerator=None): + # Since mesh normals only need their face counts and vertices per face + # to match up with the other mesh data, we can optimize export with + # this enumerator. Exports each vertex's normal when a face is shaded + # smooth, and exports the face normal only once when a face is shaded + # flat. + class _NormalsMeshEnumerator(MeshExportObject._MeshEnumerator): + def __init__(self, Mesh): + MeshExportObject._MeshEnumerator(Mesh) + + self.vertices = [] + self.PolygonVertexIndexes = [] + + Index = 0 + for Polygon in Mesh.polygons: + if not Polygon.use_smooth: + self.vertices.append(Polygon) + self.PolygonVertexIndexes.append( + tuple(len(Polygon.vertices) * [Index])) + Index += 1 + else: + for Vertex in Polygon.vertices: + self.vertices.append(Vertex) + self.PolygonVertexIndexes.append( + tuple(range(Index, Index + len(Polygon.vertices)))) + Index += len(Polygon.vertices) + + if MeshEnumerator is None: + MeshEnumerator = _NormalsMeshEnumerator(Mesh) + + self.Exporter.File.Write("MeshNormals {{ // {} normals\n".format( + self.SafeName)) + self.Exporter.File.Indent() + + NormalCount = len(MeshEnumerator.vertices) + self.Exporter.File.Write("{};\n".format(NormalCount)) + + # Write mesh normals. + for Index, Vertex in enumerate(MeshEnumerator.vertices): + Normal = Vertex.normal + self.Exporter.File.Write("{:9f};{:9f};{:9f};".format(Normal[0], + Normal[1], Normal[2])) + + if Index == NormalCount - 1: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + # Write face definitions. + FaceCount = len(MeshEnumerator.PolygonVertexIndexes) + self.Exporter.File.Write("{};\n".format(FaceCount)) + for Index, Polygon in enumerate(MeshEnumerator.PolygonVertexIndexes): + self.Exporter.File.Write("{};".format(len(Polygon))) + + # Reverse the winding order + for VertexIndex in Polygon[::-1]: + self.Exporter.File.Write("{};".format(VertexIndex), + Indent=False) + + if Index == FaceCount - 1: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}} // End of {} normals\n".format( + self.SafeName)) + + def __WriteMeshUVCoordinates(self, Mesh): + if not Mesh.uv_textures: + return + + self.Exporter.File.Write("MeshTextureCoords {{ // {} UV coordinates\n" \ + .format(self.SafeName)) + self.Exporter.File.Indent() + + UVCoordinates = Mesh.uv_layers.active.data + + VertexCount = 0 + for Polygon in Mesh.polygons: + VertexCount += len(Polygon.vertices) + + # Gather and write UV coordinates + Index = 0 + self.Exporter.File.Write("{};\n".format(VertexCount)) + for Polygon in Mesh.polygons: + Vertices = [] + for Vertex in [UVCoordinates[Vertex] for Vertex in + Polygon.loop_indices]: + Vertices.append(tuple(Vertex.uv)) + for Vertex in Vertices: + self.Exporter.File.Write("{:9f};{:9f};".format(Vertex[0], + Vertex[1])) + Index += 1 + if Index == VertexCount: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}} // End of {} UV coordinates\n".format( + self.SafeName)) + + def __WriteMeshMaterials(self, Mesh): + def WriteMaterial(Exporter, Material): + def GetMaterialTextureFileName(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 to only image file names if applicable + ImageFiles = [bpy.path.basename(Texture.image.filepath) + for Texture in ImageTextures + if getattr(Texture.image, "source", "") == 'FILE'] + if ImageFiles: + return ImageFiles[0] + return None + + Exporter.File.Write("Material {} {{\n".format( + Util.SafeName(Material.name))) + Exporter.File.Indent() + + Diffuse = list(Vector(Material.diffuse_color) * + Material.diffuse_intensity) + Diffuse.append(Material.alpha) + # Map Blender's range of 1 - 511 to 0 - 1000 + Specularity = 1000 * (Material.specular_hardness - 1.0) / 510.0 + Specular = list(Vector(Material.specular_color) * + Material.specular_intensity) + + Exporter.File.Write("{:9f};{:9f};{:9f};{:9f};;\n".format(Diffuse[0], + Diffuse[1], Diffuse[2], Diffuse[3])) + Exporter.File.Write(" {:9f};\n".format(Specularity)) + Exporter.File.Write("{:9f};{:9f};{:9f};;\n".format(Specular[0], + Specular[1], Specular[2])) + Exporter.File.Write(" 0.000000; 0.000000; 0.000000;;\n") + + TextureFileName = GetMaterialTextureFileName(Material) + if TextureFileName: + Exporter.File.Write("TextureFilename {{\"{}\";}}\n".format( + TextureFileName)) + + Exporter.File.Unindent() + Exporter.File.Write("}\n"); + + Materials = Mesh.materials + # Do not write materials if there are none + if not Materials.keys(): + return + + self.Exporter.File.Write("MeshMaterialList {{ // {} material list\n". + format(self.SafeName)) + self.Exporter.File.Indent() + + self.Exporter.File.Write("{};\n".format(len(Materials))) + self.Exporter.File.Write("{};\n".format(len(Mesh.polygons))) + # Write a material index for each face + for Index, Polygon in enumerate(Mesh.polygons): + self.Exporter.File.Write("{}".format(Polygon.material_index)) + if Index == len(Mesh.polygons) - 1: + self.Exporter.File.Write(";;\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + for Material in Materials: + WriteMaterial(self.Exporter, Material) + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}} // End of {} material list\n".format( + self.SafeName)) + + def __WriteMeshVertexColors(self, Mesh, MeshEnumerator=None): + # If there are no vertex colors, don't write anything + if len(Mesh.vertex_colors) == 0: + return + + # Blender stores vertex color information per vertex per face, so we + # need to pass in an _UnrolledFacesMeshEnumerator. Otherwise, + if MeshEnumerator is None: + MeshEnumerator = _UnrolledFacesMeshEnumerator(Mesh) + + # Gather the colors of each vertex + VertexColorLayer = Mesh.vertex_colors.active + VertexColors = [VertexColorLayer.data[Index].color for Index in + range(0,len(MeshEnumerator.vertices))] + VertexColorCount = len(VertexColors) + + self.Exporter.File.Write("MeshVertexColors {{ // {} vertex colors\n" \ + .format(self.SafeName)) + self.Exporter.File.Indent() + self.Exporter.File.Write("{};\n".format(VertexColorCount)) + + # Write the vertex colors for each vertex index. + for Index, Color in enumerate(VertexColors): + self.Exporter.File.Write("{};{:9f};{:9f};{:9f};{:9f};;".format( + Index, Color[0], Color[1], Color[2], 1.0)) + + if Index == VertexColorCount - 1: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}} // End of {} vertex colors\n".format( + self.SafeName)) + + def __WriteMeshSkinWeights(self, Mesh, MeshEnumerator=None): + # This contains vertex indexes and weights for the vertices that belong + # to this bone's group. Also calculates the bone skin matrix. + class _BoneVertexGroup: + def __init__(self, BlenderObject, ArmatureObject, BoneName): + self.BoneName = BoneName + self.SafeName = Util.SafeName(ArmatureObject.name) + "_" + \ + Util.SafeName(BoneName) + + self.Indexes = [] + self.Weights = [] + + # 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 + # This way, when BoneMatrix is transformed by the bone's + # Frame matrix, the vertices will be in their final world + # position. + + self.BoneMatrix = ArmatureObject.data.bones[BoneName] \ + .matrix_local.inverted() + self.BoneMatrix *= ArmatureObject.matrix_world.inverted() + self.BoneMatrix *= BlenderObject.matrix_world + + def AddVertex(self, Index, Weight): + self.Indexes.append(Index) + self.Weights.append(Weight) + + # Skin weights work well with vertex reuse per face. Use a + # _OneToOneMeshEnumerator if possible. + if MeshEnumerator is None: + MeshEnumerator = MeshExportObject._OneToOneMeshEnumerator(Mesh) + + ArmatureModifierList = [Modifier + for Modifier in self.BlenderObject.modifiers + if Modifier.type == 'ARMATURE' and Modifier.show_viewport] + + if not ArmatureModifierList: + return + + # Although multiple armature objects are gathered, support for + # multiple armatures per mesh is not complete + ArmatureObjects = [Modifier.object for Modifier in ArmatureModifierList] + + for ArmatureObject in ArmatureObjects: + # Determine the names of the bone vertex groups + PoseBoneNames = [Bone.name for Bone in ArmatureObject.pose.bones] + VertexGroupNames = [Group.name for Group + in self.BlenderObject.vertex_groups] + UsedBoneNames = set(PoseBoneNames).intersection(VertexGroupNames) + + # Create a _BoneVertexGroup for each group name + BoneVertexGroups = [_BoneVertexGroup(self.BlenderObject, + ArmatureObject, BoneName) for BoneName in UsedBoneNames] + + # Maps Blender's internal group indexing to our _BoneVertexGroups + GroupIndexToBoneVertexGroups = {Group.index : BoneVertexGroup + for Group in self.BlenderObject.vertex_groups + for BoneVertexGroup in BoneVertexGroups + if Group.name == BoneVertexGroup.BoneName} + + MaximumInfluences = 0 + + for Index, Vertex in enumerate(MeshEnumerator.vertices): + VertexWeightTotal = 0.0 + VertexInfluences = 0 + + # Sum up the weights of groups that correspond + # to armature bones. + for VertexGroup in Vertex.groups: + BoneVertexGroup = GroupIndexToBoneVertexGroups.get( + VertexGroup.group) + if BoneVertexGroup is not None: + VertexWeightTotal += VertexGroup.weight + VertexInfluences += 1 + + if VertexInfluences > MaximumInfluences: + MaximumInfluences = VertexInfluences + + # Add the vertex to the bone vertex groups it belongs to, + # normalizing each bone's weight. + for VertexGroup in Vertex.groups: + BoneVertexGroup = GroupIndexToBoneVertexGroups.get( + VertexGroup.group) + if BoneVertexGroup is not None: + Weight = VertexGroup.weight / VertexWeightTotal + BoneVertexGroup.AddVertex(Index, Weight) + + self.Exporter.File.Write("XSkinMeshHeader {\n") + self.Exporter.File.Indent() + self.Exporter.File.Write("{};\n".format(MaximumInfluences)) + self.Exporter.File.Write("{};\n".format(3 * MaximumInfluences)) + self.Exporter.File.Write("{};\n".format(len(BoneVertexGroups))) + self.Exporter.File.Unindent() + self.Exporter.File.Write("}\n") + + for BoneVertexGroup in BoneVertexGroups: + self.Exporter.File.Write("SkinWeights {\n") + self.Exporter.File.Indent() + self.Exporter.File.Write("\"{}\";\n".format( + BoneVertexGroup.SafeName)) + + GroupVertexCount = len(BoneVertexGroup.Indexes) + self.Exporter.File.Write("{};\n".format(GroupVertexCount)) + + # Write the indexes of the vertices this bone affects. + for Index, VertexIndex in enumerate(BoneVertexGroup.Indexes): + self.Exporter.File.Write("{}".format(VertexIndex)) + + if Index == GroupVertexCount - 1: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + # Write the weights of the affected vertices. + for Index, VertexWeight in enumerate(BoneVertexGroup.Weights): + self.Exporter.File.Write("{:9f}".format(VertexWeight)) + + if Index == GroupVertexCount - 1: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + # Write the bone's matrix. + Util.WriteMatrix(self.Exporter.File, BoneVertexGroup.BoneMatrix) + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}} // End of {} skin weights\n" \ + .format(BoneVertexGroup.SafeName)) + +# Armature object implementation of ExportObject +class ArmatureExportObject(ExportObject): + def __init__(self, Config, Exporter, BlenderObject): + ExportObject.__init__(self, Config, Exporter, BlenderObject) + + def __repr__(self): + return "[ArmatureExportObject: {}]".format(self.name) + + # "Public" Interface + + def Write(self): + self.Exporter.Log("Opening frame for {}".format(self)) + self._OpenFrame() + + if self.Config.ExportArmatureBones: + Armature = self.BlenderObject.data + RootBones = [Bone for Bone in Armature.bones if Bone.parent is None] + self.Exporter.Log("Writing frames for armature bones...") + self.__WriteBones(RootBones) + self.Exporter.Log("Done") + + self.Exporter.Log("Writing children of {}".format(self)) + self._WriteChildren() + + self._CloseFrame() + self.Exporter.Log("Closed frame of {}".format(self)) + + # "Private" Methods + + def __WriteBones(self, Bones): + # Simply export the frames for each bone. Export in rest position or + # posed position depending on options. + for Bone in Bones: + BoneMatrix = Matrix() + + if self.Config.ExportRestBone: + if Bone.parent: + BoneMatrix = Bone.parent.matrix_local.inverted() + BoneMatrix *= Bone.matrix_local + else: + PoseBone = self.BlenderObject.pose.bones[Bone.name] + if Bone.parent: + BoneMatrix = PoseBone.parent.matrix.inverted() + BoneMatrix *= PoseBone.matrix + + BoneSafeName = self.SafeName + "_" + \ + Util.SafeName(Bone.name) + self.__OpenBoneFrame(BoneSafeName, BoneMatrix) + + self.__WriteBoneChildren(Bone) + + self.__CloseBoneFrame(BoneSafeName) + + + def __OpenBoneFrame(self, BoneSafeName, BoneMatrix): + self.Exporter.File.Write("Frame {} {{\n".format(BoneSafeName)) + self.Exporter.File.Indent() + + self.Exporter.File.Write("FrameTransformMatrix {\n") + self.Exporter.File.Indent() + Util.WriteMatrix(self.Exporter.File, BoneMatrix) + self.Exporter.File.Unindent() + self.Exporter.File.Write("}\n") + + def __CloseBoneFrame(self, BoneSafeName): + self.Exporter.File.Unindent() + self.Exporter.File.Write("}} // End of {}\n".format(BoneSafeName)) + + def __WriteBoneChildren(self, Bone): + self.__WriteBones(Util.SortByNameField(Bone.children)) + + +# Container for animation data +class Animation: + def __init__(self, SafeName): + self.SafeName = SafeName + + self.RotationKeys = [] + self.ScaleKeys = [] + self.PositionKeys = [] + + # "Public" Interface + + def GetKeyCount(self): + return len(self.RotationKeys) + + +# Creates a list of Animation objects based on the animation needs of the +# ExportObject passed to it +class AnimationGenerator: # Base class, do not use + def __init__(self, Config, SafeName, ExportObject): + self.Config = Config + self.SafeName = SafeName + self.ExportObject = ExportObject + + self.Animations = [] + + +# Creates one Animation object that contains the rotation, scale, and position +# of the ExportObject +class GenericAnimationGenerator(AnimationGenerator): + def __init__(self, Config, SafeName, ExportObject): + AnimationGenerator.__init__(self, Config, SafeName, ExportObject) + + self._GenerateKeys() + + # "Protected" Interface + + def _GenerateKeys(self): + Scene = bpy.context.scene # Convenience alias + BlenderCurrentFrame = Scene.frame_current + + CurrentAnimation = Animation(self.ExportObject.SafeName) + BlenderObject = self.ExportObject.BlenderObject + + for Frame in range(Scene.frame_start, Scene.frame_end + 1): + Scene.frame_set(Frame) + + Rotation = BlenderObject.rotation_euler.to_quaternion() + Scale = BlenderObject.matrix_local.to_scale() + Position = BlenderObject.matrix_local.to_translation() + + CurrentAnimation.RotationKeys.append(Rotation) + CurrentAnimation.ScaleKeys.append(Scale) + CurrentAnimation.PositionKeys.append(Position) + + self.Animations.append(CurrentAnimation) + Scene.frame_set(BlenderCurrentFrame) + + +# Creates one Animation object for each of the ExportObjects it gets passed. +# Essentially a bunch of GenericAnimationGenerators lumped into one. +class GroupAnimationGenerator(AnimationGenerator): + def __init__(self, Config, SafeName, ExportObjects): + AnimationGenerator.__init__(self, Config, SafeName, None) + self.ExportObjects = ExportObjects + + self._GenerateKeys() + + # "Protected" Interface + + def _GenerateKeys(self): + for Object in self.ExportObjects: + if Object.BlenderObject.type == 'ARMATURE': + TemporaryGenerator = ArmatureAnimationGenerator(self.Config, + None, Object) + self.Animations += TemporaryGenerator.Animations + else: + TemporaryGenerator = GenericAnimationGenerator(self.Config, + None, Object) + self.Animations += TemporaryGenerator.Animations + + +# Creates an Animation object for the ArmatureExportObject it gets passed and +# an Animation object for each bone in the armature (if options allow) +class ArmatureAnimationGenerator(GenericAnimationGenerator): + def __init__(self, Config, SafeName, ArmatureExportObject): + GenericAnimationGenerator.__init__(self, Config, SafeName, + ArmatureExportObject) + + if self.Config.ExportArmatureBones: + self._GenerateBoneKeys() + + # "Protected" Interface + + def _GenerateBoneKeys(self): + from itertools import zip_longest as zip + + Scene = bpy.context.scene # Convenience alias + BlenderCurrentFrame = Scene.frame_current + + ArmatureObject = self.ExportObject.BlenderObject + ArmatureSafeName = self.ExportObject.SafeName + + # Create Animation objects for each bone + BoneAnimations = [Animation(ArmatureSafeName + "_" + \ + Util.SafeName(Bone.name)) for Bone in ArmatureObject.pose.bones] + + for Frame in range(Scene.frame_start, Scene.frame_end + 1): + Scene.frame_set(Frame) + + for Bone, BoneAnimation in \ + zip(ArmatureObject.pose.bones, BoneAnimations): + + Rotation = ArmatureObject.data.bones[Bone.name] \ + .matrix.to_quaternion() * \ + Bone.rotation_quaternion + + PoseMatrix = Matrix() + if Bone.parent: + PoseMatrix = Bone.parent.matrix.inverted() + PoseMatrix *= Bone.matrix + + Scale = PoseMatrix.to_scale() + Position = PoseMatrix.to_translation() + + BoneAnimation.RotationKeys.append(Rotation) + BoneAnimation.ScaleKeys.append(Scale) + BoneAnimation.PositionKeys.append(Position) + + self.Animations += BoneAnimations + Scene.frame_set(BlenderCurrentFrame) + + +# Container for all AnimationGenerators that belong in a single AnimationSet +class AnimationSet: + def __init__(self, SafeName, AnimationGenerators): + self.SafeName = SafeName + self.AnimationGenerators = AnimationGenerators + + +# Writes all animation data to file. Implementations will control the +# separation of AnimationGenerators into distinct AnimationSets. +class AnimationWriter: + def __init__(self, Config, Exporter, AnimationGenerators): + self.Config = Config + self.Exporter = Exporter + self.AnimationGenerators = AnimationGenerators + + self.AnimationSets = [] + + # "Public" Interface + + # Writes all AnimationSets. Implementations probably won't have to override + # this method. + def WriteAnimationSets(self): + if self.Config.IncludeFrameRate: + self.Exporter.Log("Writing frame rate...") + self.__WriteFrameRate() + self.Exporter.Log("Done") + + for Set in self.AnimationSets: + self.Exporter.Log("Writing animation set {}".format(Set.SafeName)) + self.Exporter.File.Write("AnimationSet {} {{\n".format( + Set.SafeName)) + self.Exporter.File.Indent() + + # Write each animation of each generator + for Generator in Set.AnimationGenerators: + for CurrentAnimation in Generator.Animations: + self.Exporter.Log("Writing animation of {}".format( + CurrentAnimation.SafeName)) + self.Exporter.File.Write("Animation {\n") + self.Exporter.File.Indent() + self.Exporter.File.Write("{{{}}}\n".format( + CurrentAnimation.SafeName)) + + KeyCount = CurrentAnimation.GetKeyCount() + + # Write rotation keys + self.Exporter.File.Write("AnimationKey { // Rotation\n"); + self.Exporter.File.Indent() + self.Exporter.File.Write("0;\n") + self.Exporter.File.Write("{};\n".format(KeyCount)) + + for Frame, Key in enumerate(CurrentAnimation.RotationKeys): + self.Exporter.File.Write( + "{};4;{:9f},{:9f},{:9f},{:9f};;".format( + Frame, -Key[0], Key[1], Key[2], Key[3])) + + if Frame == KeyCount - 1: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}\n") + + # Write scale keys + self.Exporter.File.Write("AnimationKey { // Scale\n"); + self.Exporter.File.Indent() + self.Exporter.File.Write("1;\n") + self.Exporter.File.Write("{};\n".format(KeyCount)) + + for Frame, Key in enumerate(CurrentAnimation.ScaleKeys): + self.Exporter.File.Write( + "{};3;{:9f},{:9f},{:9f};;".format( + Frame, Key[0], Key[1], Key[2])) + + if Frame == KeyCount - 1: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}\n") + + # Write position keys + self.Exporter.File.Write("AnimationKey { // Position\n"); + self.Exporter.File.Indent() + self.Exporter.File.Write("2;\n") + self.Exporter.File.Write("{};\n".format(KeyCount)) + + for Frame, Key in enumerate(CurrentAnimation.PositionKeys): + self.Exporter.File.Write( + "{};3;{:9f},{:9f},{:9f};;".format( + Frame, Key[0], Key[1], Key[2])) + + if Frame == KeyCount - 1: + self.Exporter.File.Write(";\n", Indent=False) + else: + self.Exporter.File.Write(",\n", Indent=False) + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}\n") + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}\n") + self.Exporter.Log("Done") + + self.Exporter.File.Unindent() + self.Exporter.File.Write("}} // End of AnimationSet {}\n".format( + Set.SafeName)) + self.Exporter.Log("Done writing animation set {}".format( + Set.SafeName)) + + # "Private" Methods + + def __WriteFrameRate(self): + Scene = bpy.context.scene # Convenience alias + + # Calculate the integer frame rate + FrameRate = int(Scene.render.fps / Scene.render.fps_base) + + self.Exporter.File.Write("AnimTicksPerSecond {\n"); + self.Exporter.File.Indent() + self.Exporter.File.Write("{};\n".format(FrameRate)) + self.Exporter.File.Unindent() + self.Exporter.File.Write("}\n") + +# Implementation of AnimationWriter that sticks all generators into a +# single AnimationSet +class JoinedSetAnimationWriter(AnimationWriter): + def __init__(self, Config, Exporter, AnimationGenerators): + AnimationWriter.__init__(self, Config, Exporter, AnimationGenerators) + + self.AnimationSets = [AnimationSet("Global", self.AnimationGenerators)] + +# Implementation of AnimationWriter that puts each generator into its +# own AnimationSet +class SplitSetAnimationWriter(AnimationWriter): + def __init__(self, Config, Exporter, AnimationGenerators): + AnimationWriter.__init__(self, Config, Exporter, AnimationGenerators) + + self.AnimationSets = [AnimationSet(Generator.SafeName, [Generator]) + for Generator in AnimationGenerators] + + +# Interface to the file. Supports automatic whitespace indenting. +class File: + def __init__(self, FilePath): + self.FilePath = FilePath + self.File = None + self.__Whitespace = 0 + + def Open(self): + if not self.File: + self.File = open(self.FilePath, 'w') + + def Close(self): + self.File.close() + self.File = None + + def Write(self, String, Indent=True): + if Indent: + # Escape any formatting braces + String = String.replace("{", "{{") + String = String.replace("}", "}}") + self.File.write(("{}" + String).format(" " * self.__Whitespace)) + else: + self.File.write(String) + + def Indent(self, Levels=1): + self.__Whitespace += Levels + + def Unindent(self, Levels=1): + self.__Whitespace -= Levels + if self.__Whitespace < 0: + self.__Whitespace = 0 + + +# Static utilities +class Util: + @staticmethod + def SafeName(Name): + # Replaces each character in OldSet with NewChar + 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", + "FLOAT", "ULONGLONG", "BINARY_RESOURCE", "SDWORD", "UNICODE", + "CHAR", "STRING", "WORD", "CSTRING", "SWORD", "DOUBLE", "TEMPLATE"]: + NewName = "_" + NewName + return NewName + + @staticmethod + def WriteMatrix(File, Matrix): + File.Write("{:9f},{:9f},{:9f},{:9f},\n".format(Matrix[0][0], + Matrix[1][0], Matrix[2][0], Matrix[3][0])) + File.Write("{:9f},{:9f},{:9f},{:9f},\n".format(Matrix[0][1], + Matrix[1][1], Matrix[2][1], Matrix[3][1])) + File.Write("{:9f},{:9f},{:9f},{:9f},\n".format(Matrix[0][2], + Matrix[1][2], Matrix[2][2], Matrix[3][2])) + File.Write("{:9f},{:9f},{:9f},{:9f};;\n".format(Matrix[0][3], + Matrix[1][3], Matrix[2][3], Matrix[3][3])) + + # Used on lists of blender objects and lists of ExportObjects, both of + # which have a name field + @staticmethod + def SortByNameField(List): + def SortKey(x): + return x.name + + return sorted(List, key=SortKey) |