├── .gitattributes ├── Phong.osl ├── Phong.oso ├── README.md ├── __init__.py ├── export_x.py └── import_x.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Phong.osl: -------------------------------------------------------------------------------- 1 | 2 | 3 | shader phong(color faceDiffuseColor = color(1.0, 1.0, 1.0), 4 | int faceShininess = 20, 5 | color faceSpecularColor = color(1.0, 1.0, 1.0), 6 | color faceEmissiveColor = color(0.0, 0.0, 0.0), 7 | color texture = color(1.0, 1.0, 1.0), 8 | color ambientLightColor = color(0.25, 0.25, 0.25), 9 | vector lightDirection = vector(1.0, -0.25, 0.0), 10 | color directionalLightColor = color(1.0, 1.0, 1.0), 11 | output color BSDF = 0) 12 | { 13 | // Grab the EyePosition 14 | point EyePos = point("camera", 0, 0, 0); 15 | 16 | // Compute the vector from the vertex to the eye position 17 | vector toEye = normalize(EyePos - P); 18 | 19 | // Normalize lightDirection 20 | vector lightDir = normalize(lightDirection); 21 | 22 | // Blender uses xyz not xyz so swap Y and Z here 23 | float temp = lightDir[1]; 24 | lightDir[1] = lightDir[2]; 25 | lightDir[2] = temp; 26 | 27 | // Calculate diffuse co-efficient 28 | float s = max(dot(-lightDir, N), 0.0); 29 | 30 | // Compute the reflection Vector 31 | vector reflectionVec = normalize(reflect(lightDir, N)); 32 | 33 | // Determine how much (if any) specular light makes it to the eye 34 | float t = pow(max(dot(reflectionVec, toEye), 0.0), faceShininess); 35 | 36 | // Calculate DiffuseLight 37 | color diffuseLight = (faceDiffuseColor * directionalLightColor) * s; 38 | 39 | // Calculate SpecularLight 40 | color specularLight = (faceSpecularColor * directionalLightColor) * t; 41 | 42 | // Set the final Color 43 | BSDF = faceEmissiveColor + ((ambientLightColor * diffuseLight) * texture + specularLight); 44 | } -------------------------------------------------------------------------------- /Phong.oso: -------------------------------------------------------------------------------- 1 | OpenShadingLanguage 1.00 2 | # Compiled by oslc 1.9.9 3 | # options: -o C:\Program Files (x86)\blender-2.80.0-git.faecac0b5e14-windows64\2.80\scripts\addons\io_scene_directx\Phong.oso -IC:\Program Files (x86)\blender-2.80.0-git.faecac0b5e14-windows64\2.80\scripts\addons\cycles\shader 4 | shader phong 5 | param color faceDiffuseColor 1 1 1 %read{21,21} %write{2147483647,-1} 6 | param int faceShininess 20 %read{19,19} %write{2147483647,-1} 7 | param color faceSpecularColor 1 1 1 %read{23,23} %write{2147483647,-1} 8 | param color faceEmissiveColor 0 0 0 %read{28,28} %write{2147483647,-1} 9 | param color texture 1 1 1 %read{26,26} %write{2147483647,-1} 10 | param color ambientLightColor 0.25 0.25 0.25 %read{25,25} %write{2147483647,-1} 11 | param vector lightDirection 1 -0.25 0 %read{3,3} %write{2147483647,-1} 12 | param color directionalLightColor 1 1 1 %read{21,23} %write{2147483647,-1} 13 | oparam color BSDF 0 0 0 %read{2147483647,-1} %write{28,28} 14 | global point P %read{1,1} %write{2147483647,-1} 15 | global normal N %read{9,14} %write{2147483647,-1} 16 | local point EyePos %read{1,1} %write{0,0} 17 | local vector toEye %read{17,17} %write{2,2} 18 | local vector lightDir %read{4,15} %write{3,7} 19 | local float temp %read{7,7} %write{4,4} 20 | local float s %read{22,22} %write{10,10} 21 | local vector reflectionVec %read{17,17} %write{16,16} 22 | local float t %read{24,24} %write{20,20} 23 | local color diffuseLight %read{25,25} %write{22,22} 24 | local color specularLight %read{27,27} %write{24,24} 25 | const string $const1 "camera" %read{0,0} %write{2147483647,-1} 26 | const float $const3 0 %read{0,18} %write{2147483647,-1} 27 | temp vector $tmp1 %read{2,2} %write{1,1} 28 | const int $const4 1 %read{4,6} %write{2147483647,-1} 29 | const int $const5 2 %read{5,7} %write{2147483647,-1} 30 | temp float $tmp2 %read{6,6} %write{5,5} 31 | temp float $tmp3 %read{10,10} %write{9,9} 32 | temp vector $tmp4 %read{9,9} %write{8,8} 33 | temp vector $tmp5 %read{16,16} %write{15,15} 34 | const string $const6 "reflect" %read{11,11} %write{2147483647,-1} 35 | temp float $tmp6 %read{13,13} %write{12,12} 36 | temp float $tmp7 %read{14,14} %write{13,13} 37 | const float $const7 2 %read{13,13} %write{2147483647,-1} 38 | temp vector $tmp8 %read{15,15} %write{14,14} 39 | temp float $tmp9 %read{20,20} %write{18,18} 40 | temp float $tmp10 %read{18,18} %write{17,17} 41 | temp float $tmp11 %read{20,20} %write{19,19} 42 | temp color $tmp12 %read{22,22} %write{21,21} 43 | temp color $tmp13 %read{24,24} %write{23,23} 44 | temp color $tmp14 %read{26,26} %write{25,25} 45 | temp color $tmp15 %read{27,27} %write{26,26} 46 | temp color $tmp16 %read{28,28} %write{27,27} 47 | code ___main___ 48 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:15 49 | # 50 | point EyePos $const1 $const3 $const3 $const3 %filename{"2.80\scripts\addons\io_scene_directx\Phong.osl"} %line{15} %argrw{"wrrrr"} 51 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:18 52 | # 53 | sub $tmp1 EyePos P %line{18} %argrw{"wrr"} 54 | normalize toEye $tmp1 %argrw{"wr"} 55 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:21 56 | # 57 | normalize lightDir lightDirection %line{21} %argrw{"wr"} 58 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:24 59 | # lightDir[1] = lightDir[2]; 60 | compref temp lightDir $const4 %line{24} %argrw{"wrr"} 61 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:25 62 | # lightDir[2] = temp; 63 | compref $tmp2 lightDir $const5 %line{25} %argrw{"wrr"} 64 | compassign lightDir $const4 $tmp2 %argrw{"wrr"} 65 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:26 66 | # 67 | compassign lightDir $const5 temp %line{26} %argrw{"wrr"} 68 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:29 69 | # 70 | neg $tmp4 lightDir %line{29} %argrw{"wr"} 71 | dot $tmp3 $tmp4 N %argrw{"wrr"} 72 | max s $tmp3 $const3 %argrw{"wrr"} 73 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:32 74 | # 75 | functioncall $const6 16 %line{32} %argrw{"r"} 76 | # 2.80\scripts\addons\cycles\shader\stdosl.h:192 77 | # vector reflect (vector I, vector N) { return I - 2*dot(N,I)*N; } 78 | dot $tmp6 N lightDir %filename{"2.80\scripts\addons\cycles\shader\stdosl.h"} %line{192} %argrw{"wrr"} 79 | mul $tmp7 $const7 $tmp6 %argrw{"wrr"} 80 | mul $tmp8 $tmp7 N %argrw{"wrr"} 81 | sub $tmp5 lightDir $tmp8 %argrw{"wrr"} 82 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:32 83 | # 84 | normalize reflectionVec $tmp5 %filename{"2.80\scripts\addons\io_scene_directx\Phong.osl"} %line{32} %argrw{"wr"} 85 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:35 86 | # 87 | dot $tmp10 reflectionVec toEye %line{35} %argrw{"wrr"} 88 | max $tmp9 $tmp10 $const3 %argrw{"wrr"} 89 | assign $tmp11 faceShininess %argrw{"wr"} 90 | pow t $tmp9 $tmp11 %argrw{"wrr"} 91 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:38 92 | # 93 | mul $tmp12 faceDiffuseColor directionalLightColor %line{38} %argrw{"wrr"} 94 | mul diffuseLight $tmp12 s %argrw{"wrr"} 95 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:41 96 | # 97 | mul $tmp13 faceSpecularColor directionalLightColor %line{41} %argrw{"wrr"} 98 | mul specularLight $tmp13 t %argrw{"wrr"} 99 | # 2.80\scripts\addons\io_scene_directx\Phong.osl:44 100 | # } 101 | mul $tmp14 ambientLightColor diffuseLight %line{44} %argrw{"wrr"} 102 | mul $tmp15 $tmp14 texture %argrw{"wrr"} 103 | add $tmp16 $tmp15 specularLight %argrw{"wrr"} 104 | add BSDF faceEmissiveColor $tmp16 %argrw{"wrr"} 105 | end 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # io_scene_directx 2 | A DirectX Model Exporter for Blender 2.8+ 3 | 4 | To use this project locate your blender folder on your hard disk and save this project into that folder. Restart / Open Blender and then the option to export as a *.x model will be available with the other export models. 5 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN ZLIB LICENSE BLOCK ##### 2 | 3 | # Copyright (c) <2019> 4 | 5 | # This software is provided 'as-is', without any express or implied 6 | # warranty. In no event will the authors be held liable for any damages 7 | # arising from the use of this software. 8 | # 9 | # Permission is granted to anyone to use this software for any purpose, 10 | # including commercial applications, and to alter it and redistribute it 11 | # freely, subject to the following restrictions: 12 | # 13 | # 1. The origin of this software must not be misrepresented; you must not 14 | # claim that you wrote the original software. If you use this software 15 | # in a product, an acknowledgment in the product documentation would be 16 | # appreciated but is not required. 17 | # 2. Altered source versions must be plainly marked as such, and must not be 18 | # misrepresented as being the original software. 19 | # 3. This notice may not be removed or altered from any source distribution. 20 | 21 | # ##### END ZLIB LICENSE BLOCK ##### 22 | 23 | # NOTE: Plugins need to pass through a pep8 checker. The following line 24 | # sets the formatting requirements for this python file. 25 | # 26 | 27 | # The bl_info is a meta field used by Blender to describe this addon 28 | bl_info = { 29 | "name": "DirectX X Format", 30 | "author": "Dodgee Software", 31 | "version": (0, 0, 1), 32 | "blender": (2, 80, 0), 33 | "location": "File > Export > DirectX (.x)", 34 | "description": "Export mesh vertices, UV's, materials, textures, " 35 | "vertex colors, armatures, empties, and actions.", 36 | "wiki_url": "https://dodgeesoftware.com.au", 37 | "category": "Import-Export"} 38 | 39 | from .import_x import * 40 | from .export_x import * 41 | 42 | import bpy 43 | from bpy.props import BoolProperty 44 | from bpy.props import EnumProperty 45 | from bpy.props import StringProperty 46 | from bpy.props import ( 47 | StringProperty, 48 | BoolProperty, 49 | FloatProperty, 50 | EnumProperty, 51 | ) 52 | from bpy_extras.io_utils import ( 53 | ImportHelper, 54 | ExportHelper, 55 | orientation_helper, 56 | path_reference_mode, 57 | axis_conversion, 58 | ) 59 | 60 | @orientation_helper(axis_forward='-Z', axis_up='Y') 61 | class ImportX(bpy.types.Operator, ImportHelper): 62 | """Load a X file""" 63 | bl_idname = "import_scene.x" 64 | bl_label = "Import X" 65 | bl_options = {'UNDO', 'PRESET'} 66 | 67 | directory: StringProperty() 68 | 69 | filename_ext = ".x" 70 | filter_glob: StringProperty(default="*.x", options={'HIDDEN'}) 71 | 72 | def draw(self, context): 73 | layout = self.layout 74 | 75 | def execute(self, context): 76 | return ImportFile(self.filepath) 77 | 78 | @orientation_helper(axis_forward='-Z', axis_up='Y') 79 | class ExportX(bpy.types.Operator, ExportHelper): 80 | """Write a X file""" 81 | bl_idname = "export_scene.x" 82 | bl_label = "Export X" 83 | bl_options = {'UNDO', 'PRESET'} 84 | 85 | filename_ext = ".x" 86 | filter_glob: StringProperty(default="*.x", options={'HIDDEN'}) 87 | 88 | def draw(self, context): 89 | layout = self.layout 90 | 91 | @property 92 | def check_extension(self): 93 | return True 94 | 95 | def execute(self, context): 96 | return ExportFile(self.filepath) 97 | 98 | def menu_func_import(self, context): 99 | self.layout.operator(ImportX.bl_idname, text="DirectX X (.x)") 100 | 101 | def menu_func_export(self, context): 102 | self.layout.operator(ExportX.bl_idname, text="DirectX X (.x)") 103 | 104 | classes = ( 105 | ExportX, 106 | ImportX 107 | ) 108 | 109 | def register(): 110 | print("Registering plugin: io_scene_directx") 111 | for cls in classes: 112 | bpy.utils.register_class(cls) 113 | bpy.types.TOPBAR_MT_file_import.append(menu_func_import) 114 | bpy.types.TOPBAR_MT_file_export.append(menu_func_export) 115 | 116 | def unregister(): 117 | print("Deregistering plugin: io_scene_directx") 118 | for cls in classes: 119 | bpy.utils.unregister_class(cls) 120 | bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) 121 | bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) 122 | 123 | if __name__ == "__main__": 124 | register() 125 | -------------------------------------------------------------------------------- /export_x.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN ZLIB LICENSE BLOCK ##### 2 | 3 | # Copyright (c) <2020> 4 | 5 | # This software is provided 'as-is', without any express or implied 6 | # warranty. In no event will the authors be held liable for any damages 7 | # arising from the use of this software. 8 | # 9 | # Permission is granted to anyone to use this software for any purpose, 10 | # including commercial applications, and to alter it and redistribute it 11 | # freely, subject to the following restrictions: 12 | # 13 | # 1. The origin of this software must not be misrepresented; you must not 14 | # claim that you wrote the original software. If you use this software 15 | # in a product, an acknowledgment in the product documentation would be 16 | # appreciated but is not required. 17 | # 2. Altered source versions must be plainly marked as such, and must not be 18 | # misrepresented as being the original software. 19 | # 3. This notice may not be removed or altered from any source distribution. 20 | 21 | # ##### END ZLIB LICENSE BLOCK ##### 22 | 23 | # NOTE: Plugins need to through a pep8 checker. The following line 24 | # sets the formatting requirements for this python file. 25 | # 26 | 27 | import math 28 | from math import radians 29 | import mathutils 30 | import bpy 31 | from pathlib import Path 32 | 33 | # TODO: Support for vertex colours via mesh.vertex_colors 34 | # TODO: Do I need to figure out how to do parented meshes (nested transforms) 35 | 36 | def ExportFile(filepath): 37 | bpy.ops.object.mode_set(mode="OBJECT") 38 | 39 | # Send a message to the console 40 | print("Exporting File: " + filepath) 41 | # Open the file for export 42 | f = open(filepath, "w", encoding="utf8", newline="\n") 43 | # Write the File Header to the file 44 | WriteHeader(f) 45 | # Write all the Template Boiler plate to the file 46 | WriteBoilerPlate(f) 47 | # Go through all the objects in the scene 48 | for object in bpy.data.objects: 49 | # If the object type isn't a mesh then goto the next object 50 | if object.type != 'MESH': 51 | continue 52 | # Grab the Mesh from the Object 53 | mesh = object.data 54 | 55 | # WRITE MESH FRAME 56 | # Write the Object Name 57 | f.write("# " + object.name + "\n") 58 | f.write("Frame\n") 59 | f.write("{\n") 60 | 61 | # WRITE FRAMETRANSFORMATIONMATRIX 62 | f.write("FrameTransformMatrix\n") 63 | f.write("{\n") 64 | # TODO: Try and replace this with a reusable function 65 | # Translation Matrix 66 | translationMatrix = mathutils.Matrix.Translation((object.location[0], object.location[2], object.location[1])) 67 | # Rotation about the X Axis Matrix 68 | #rotationXMatrix = mathutils.Matrix.Rotation((object.rotation_euler[0]), 4, 'X') 69 | rotationXMatrix = mathutils.Matrix.Identity(4) 70 | rotationXMatrix[1][1] = math.cos(-object.rotation_euler[0]) 71 | rotationXMatrix[1][2] = -math.sin(-object.rotation_euler[0]) 72 | rotationXMatrix[2][1] = math.sin(-object.rotation_euler[0]) 73 | rotationXMatrix[2][2] = math.cos(-object.rotation_euler[0]) 74 | # Rotation about the Y Axis Matrix 75 | #rotationYMatrix = mathutils.Matrix.Rotation((object.rotation_euler[2]), 4, 'Y') 76 | rotationYMatrix = mathutils.Matrix.Identity(4) 77 | rotationYMatrix[0][0] = math.cos(-object.rotation_euler[2]) 78 | rotationYMatrix[0][2] = math.sin(-object.rotation_euler[2]) 79 | rotationYMatrix[2][0] = -math.sin(-object.rotation_euler[2]) 80 | rotationYMatrix[2][2] = math.cos(-object.rotation_euler[2]) 81 | # Rotation about the Z Axis Matrix 82 | #rotationZMatrix = mathutils.Matrix.Rotation((object.rotation_euler[1]), 4, 'Z') 83 | rotationZMatrix = mathutils.Matrix.Identity(4) 84 | rotationZMatrix[0][0] = math.cos(-object.rotation_euler[1]) 85 | rotationZMatrix[0][1] = -math.sin(-object.rotation_euler[1]) 86 | rotationZMatrix[1][0] = math.sin(-object.rotation_euler[1]) 87 | rotationZMatrix[1][1] = math.cos(-object.rotation_euler[1]) 88 | # Scale Matrix 89 | scaleXMatrix = mathutils.Matrix.Scale(object.scale[0], 4, (1.0, 0.0, 0.0)) 90 | scaleYMatrix = mathutils.Matrix.Scale(object.scale[2], 4, (0.0, 1.0, 0.0)) 91 | scaleZMatrix = mathutils.Matrix.Scale(object.scale[1], 4, (0.0, 0.0, 1.0)) 92 | # Compute the final Model transformation matrix 93 | finalMatrix = mathutils.Matrix(translationMatrix @ rotationYMatrix @ rotationZMatrix @ rotationXMatrix @scaleYMatrix @ scaleZMatrix @ scaleXMatrix) 94 | # Compute the matrix to transform the normals 95 | normalMatrix = mathutils.Matrix(rotationYMatrix @ rotationZMatrix @ rotationXMatrix) 96 | # The DirectX format stores matrices 97 | # in row major format so we transpose the 98 | # matrix here before writing 99 | finalMatrix.transpose() 100 | # Write the Matrix 101 | for j in range(0, 4): 102 | for i in range(0, 4): 103 | f.write(str('%.6f' % finalMatrix[j][i])) 104 | if j == 3 and i == 3: 105 | f.write(";;") 106 | else: 107 | f.write(",") 108 | f.write("\n") 109 | f.write("}\n") 110 | 111 | # WRITE THE MESH 112 | f.write("Mesh " + mesh.name + "\n{" + "\n") 113 | # Grab the Number of Vertices 114 | mesh_verts = mesh.vertices[:] 115 | # Grab the Number of Polygons 116 | mesh_polygons = mesh.polygons[:] 117 | # Count the Number of vertices 118 | vertexCount = 0 119 | for polygon in mesh_polygons: 120 | for vertex in polygon.vertices: 121 | vertexCount = vertexCount + 1 122 | # Write the Vertex Count 123 | f.write(str(vertexCount) + ";\n") 124 | # Go through all the polygons in the mesh 125 | subscriptOffset = 0 126 | for polygon in mesh_polygons: 127 | # Go through all the vertices in the polygon 128 | for i in range(len(polygon.vertices)): 129 | # Grab the vertex 130 | vertex = mesh_verts[polygon.vertices[i]] 131 | # Here I swap the Y and Z Axis 132 | f.write(str('%.6f' % vertex.co[0]) + ";" + str('%.6f' % vertex.co[2]) + ";" + str('%.6f' % (vertex.co[1]))) 133 | # if we are at the last polygon and vertex write a double semicolon 134 | if polygon == mesh_polygons[-1] and i == (len(polygon.vertices) - 1): 135 | f.write(str(len(mesh_verts)) + ";;\n") 136 | else: 137 | f.write(str(len(mesh_verts)) + ";,\n") 138 | # Increment our subscripts 139 | subscriptOffset += len(polygon.vertices) 140 | f.write("\n") 141 | 142 | # WRITE POLYGON INDICES 143 | # Write the Number of Polygons 144 | f.write(str(len(mesh_polygons)) +";\n") 145 | # Write the Polygons 146 | subscriptOffset = 0 147 | for polygon in mesh_polygons: 148 | f.write(str(len(polygon.vertices)) + ";") 149 | for index in range(0, len(polygon.vertices)): 150 | indice = (subscriptOffset + (len(polygon.vertices) - 1) - index) 151 | f.write(str(indice)) 152 | if index == len(polygon.vertices) - 1: 153 | f.write(";") 154 | else: 155 | f.write(",") 156 | if polygon == mesh_polygons[-1]: 157 | f.write(";") 158 | else: 159 | f.write(",") 160 | subscriptOffset = subscriptOffset + len(polygon.vertices) 161 | f.write("\n") 162 | f.write("\n") 163 | 164 | # WRITE NORMALS FOR THE MESH 165 | # TODO: Need to figure out how to provide support for per face vertex normals. 166 | # there must be away to create them in blender then detect them in script 167 | # and save them here when they exist. At the moment all polygons in a mesh 168 | # are hard edges and this is wrong 169 | # NOTE: We do NOT need to transform the normals. 170 | # It seems that the frametransform is applied automatically 171 | # to the normals 172 | f.write("MeshNormals \n{\n") 173 | # Calculate the Number of normals 174 | numNormals = 0 175 | for polygon in mesh_polygons: 176 | for vertex in polygon.vertices: 177 | numNormals = numNormals + 1 178 | # Write the Number of normals 179 | f.write(str(numNormals) + ";\n") 180 | for polygon in mesh_polygons: 181 | for vertex in polygon.vertices: 182 | if mesh.use_auto_smooth == False: 183 | normal = polygon.normal 184 | else: 185 | normal = mesh.vertices[vertex].normal 186 | #normal = polygon.normal 187 | f.write(str('%.6f' % normal.x) + ";") 188 | f.write(str('%.6f' % normal.z) + ";") 189 | f.write(str('%.6f' % normal.y) + ";") 190 | if polygon == mesh_polygons[-1]: 191 | if vertex == polygon.vertices[-1]: 192 | f.write(";") 193 | else: 194 | f.write(",") 195 | else: 196 | f.write(",") 197 | f.write("\n") 198 | f.write("\n") 199 | 200 | # WRITE THE POLYGONS 201 | # Write the Number of Polygons 202 | f.write(str(len(mesh_polygons)) +";\n") 203 | # Write the Polygons 204 | subscriptOffset = 0 205 | for polygon in mesh_polygons: 206 | f.write(str(len(polygon.vertices)) + ";") 207 | for index in range(0, len(polygon.vertices)): 208 | indice = (subscriptOffset + (len(polygon.vertices) - 1) - index) 209 | f.write(str(indice)) 210 | if index == len(polygon.vertices) - 1: 211 | f.write(";") 212 | else: 213 | f.write(",") 214 | if polygon == mesh_polygons[-1]: 215 | f.write(";") 216 | else: 217 | f.write(",") 218 | subscriptOffset = subscriptOffset + len(polygon.vertices) 219 | f.write("\n") 220 | f.write("}\n") 221 | 222 | # WRITE UVS (IF ANY) 223 | # Do we have uv data? 224 | if len(mesh.uv_layers) > 0: 225 | # NOTE: There can only be one active UVMap per mesh. This 226 | # is set by the user in the interface. 227 | uvs = mesh.uv_layers.active.data[:] 228 | # Write the Name of the UVMap 229 | f.write("# " + str(mesh.uv_layers.active.name) + "\n") 230 | f.write("MeshTextureCoords \n{\n") 231 | # Write the UV coords for faces 232 | f.write(str(len(mesh.uv_layers.active.data[:])) + ";\n") 233 | # Write the UVs for each face 234 | subscriptOffset = 0 235 | for polygon in mesh_polygons: 236 | for index in range(0, len(polygon.vertices)): 237 | indice = (subscriptOffset + index) 238 | meshUVLoop = mesh.uv_layers.active.data[indice] 239 | f.write(str('%.6f' % meshUVLoop.uv[0])) 240 | f.write(";") 241 | f.write(str('%.6f' % (1.0 - meshUVLoop.uv[1]))) 242 | f.write(";") 243 | if polygon == mesh_polygons[-1] and index == len(polygon.vertices) - 1: 244 | f.write(";") 245 | else: 246 | f.write(",") 247 | f.write("\n") 248 | subscriptOffset = subscriptOffset + len(polygon.vertices) 249 | f.write("}\n") 250 | 251 | # WRITE MATERIALS LIST 252 | # Grab the Materials used by this mesh 253 | mesh_materials = mesh.materials[:] 254 | # Write the MeshMaterial List 255 | f.write("MeshMaterialList\n{\n") 256 | # Write the number of materials used by this mesh 257 | f.write(str(len(mesh_materials)) + ";\n") 258 | f.write(str(len(mesh_polygons)) +";\n") 259 | for index in range(0, len(mesh_polygons), 1): 260 | if index == len(mesh_polygons) - 1: 261 | f.write(str(mesh_polygons[index].material_index) +";\n") 262 | else: 263 | f.write(str(mesh_polygons[index].material_index) +",\n") 264 | 265 | for material in mesh_materials: 266 | f.write("Material "+ material.name + "\n{\n") 267 | if material.use_nodes == False: 268 | f.write("# This material doesn't use nodes. Doing best to export properties anyway. \n") 269 | # Write Diffuse Colour 270 | f.write(str('%.6f' % material.diffuse_color[0]) + ";" + str('%.6f' % material.diffuse_color[1]) + ";" + str('%.6f' % material.diffuse_color[2]) + ";" + str('%.6f' % material.diffuse_color[3]) + ";;\n") 271 | # Write specular cooeffiencnt 272 | f.write(str('%.6f' % material.specular_intensity) + ";\n") 273 | # Non-node materials in Blender have no specular colour write a default one here (white) 274 | f.write(str('%.6f' % 1.0) + ";" + str('%.6f' % 1.0) + ";" + str('%.6f' % 1.0) + ";;\n") 275 | # Non-node materials in Blender have no emissive colour write a default one here (black) 276 | f.write(str('%.6f' % 0.0) + ";" + str('%.6f' % 0.0) + ";" + str('%.6f' % 0.0) + ";;\n") 277 | else: 278 | f.write("# Exporter deliberately and only supports the Specular Material Node in the Shader Graph \n") 279 | #specularNode = node for node in material.node_tree.nodes if node.type == "" 280 | faceColor = [1.0, 1.0, 1.0, 1.0] 281 | power = 200.0 282 | specularColor = [1.0, 1.0, 1.0, 1.0] 283 | emissiveColor = [0.0, 0.0, 0.0, 1.0] 284 | filenameandpath = "" 285 | for node in material.node_tree.nodes: 286 | if node.type == 'SCRIPT': 287 | # GRAB THE FACE COLOR 288 | colorSocket = node.inputs[0] 289 | faceColor[0] = colorSocket.default_value[0] 290 | faceColor[1] = colorSocket.default_value[1] 291 | faceColor[2] = colorSocket.default_value[2] 292 | faceColor[3] = colorSocket.default_value[3] 293 | # GRAB THE SPECULAR POWER 294 | floatSocket = node.inputs[1] 295 | # Convert the Roughness into specular cooefficient 296 | power = floatSocket.default_value 297 | # Specular power must be greater than 1 298 | if power < 1.0: 299 | power = 1.0 300 | if power > 800.0: 301 | power = 800.0 302 | # GRAB THE SPECULAR COLOR 303 | colorSocket = node.inputs[2] 304 | specularColor[0] = colorSocket.default_value[0] 305 | specularColor[1] = colorSocket.default_value[1] 306 | specularColor[2] = colorSocket.default_value[2] 307 | specularColor[3] = colorSocket.default_value[3] 308 | # GRAB THE EMISSIVE COLOR 309 | colorSocket = node.inputs[3] 310 | emissiveColor[0] = colorSocket.default_value[0] 311 | emissiveColor[1] = colorSocket.default_value[1] 312 | emissiveColor[2] = colorSocket.default_value[2] 313 | emissiveColor[3] = colorSocket.default_value[3] 314 | if node.type == 'EEVEE_SPECULAR': 315 | # Grab Diffuse colour 316 | colorSocket = node.inputs[0] 317 | faceColor[0] = colorSocket.default_value[0] 318 | faceColor[1] = colorSocket.default_value[1] 319 | faceColor[2] = colorSocket.default_value[2] 320 | faceColor[3] = colorSocket.default_value[3] 321 | colorSocket = node.inputs[1] 322 | # Grab Specular colour 323 | specularColor[0] = colorSocket.default_value[0] 324 | specularColor[1] = colorSocket.default_value[1] 325 | specularColor[2] = colorSocket.default_value[2] 326 | specularColor[3] = colorSocket.default_value[3] 327 | floatSocket = node.inputs[2] 328 | # Convert the Roughness into specular cooefficient 329 | power = (1.0 - floatSocket.default_value) * 800.0 330 | # Specular power must be greater than 1 331 | if power < 1.0: 332 | power = 1.0 333 | if power > 800.0: 334 | power = 800.0 335 | colorSocket = node.inputs[3] 336 | # Grab Emissive colour 337 | emissiveColor[0] = colorSocket.default_value[0] 338 | emissiveColor[1] = colorSocket.default_value[1] 339 | emissiveColor[2] = colorSocket.default_value[2] 340 | emissiveColor[3] = colorSocket.default_value[3] 341 | # If there is a texture grab the filenameandpath 342 | if node.type == 'TEX_IMAGE': 343 | if node.outputs[0].is_linked == True: 344 | image = node.image 345 | if image != None: 346 | filenameandpath = image.filepath 347 | # Write the Diffuse Colour 348 | f.write(str('%.6f' % faceColor[0]) + ";" + str('%.6f' % faceColor[1]) + ";" + str('%.6f' % faceColor[2]) + ";" + str('%.6f' % faceColor[3]) + ";;\n") 349 | # Write the Specular Cooefficient 350 | f.write(str('%.6f' % power) + ";\n") 351 | # Write the Specular Colour 352 | f.write(str('%.6f' % specularColor[0]) + ";" + str('%.6f' % specularColor[1]) + ";" + str('%.6f' % specularColor[2]) + ";;\n") 353 | # Write the Emissive Colour 354 | f.write(str('%.6f' % emissiveColor[0]) + ";" + str('%.6f' % emissiveColor[1]) + ";" + str('%.6f' % emissiveColor[2]) + ";;\n") 355 | # If there is a texture write the TexutreFilename node to the file 356 | if len(filenameandpath): 357 | f.write("TextureFilename\n{\n") 358 | #f.write("\"" + Path(filenameandpath).name + "\"" + ";\n") 359 | f.write("\"" + ExtractFilenameFromPath(filenameandpath) + "\"" + ";\n") 360 | f.write("}\n") 361 | f.write("}\n") 362 | # If there are no materials then use a default one 363 | # the file format must define at least one material 364 | # being material 0 365 | if len(mesh_materials) == 0: 366 | # Create the default Material Node give it a name TODO: Cannot have spaces investigate valid names 367 | f.write("Material "+ "DefaultMaterial" + "\n{\n") 368 | # Write the Diffuse Colour 369 | f.write(str('%.6f' % 1.0) + ";" + str('%.6f' % 1.0) + ";" + str('%.6f' % 1.0) + ";" + str('%.6f' % 1.0) + ";;\n") 370 | # Write the Specular Cooefficient 371 | f.write(str('%.6f' % 2.0) + ";\n") 372 | # Write the Specular Colour 373 | f.write(str('%.6f' % 1.0) + ";" + str('%.6f' % 1.0) + ";" + str('%.6f' % 1.0) + ";;\n") 374 | # Write the Emissive Colour 375 | f.write(str('%.6f' % 0.0) + ";" + str('%.6f' % 0.0) + ";" + str('%.6f' % 0.0) + ";;\n") 376 | f.write("}\n") 377 | f.write("}" + "\n") 378 | 379 | # if there is an armature modifier then write 380 | # skeletal data to the file 381 | if object.modifiers.find("Armature") is not -1: 382 | # Grab the Armature object from the armature modifier 383 | armature = object.modifiers["Armature"].object.data 384 | boneCount = len(armature.bones.items()) 385 | # Write the XSkinMeshHeader to file 386 | f.write("XSkinMeshHeader\n") 387 | f.write("{\n") 388 | f.write(str(boneCount) + ";" + " #nMaxSkinWeightsPerVertex \n") 389 | f.write(str(boneCount) + ";" + " #nMaxSkinWeightsPerFace \n") 390 | f.write(str(boneCount) + ";" + " #nBones \n") 391 | f.write("}\n") 392 | 393 | for vertexGroup in object.vertex_groups: 394 | f.write("SkinWeights\n") 395 | f.write("{\n") 396 | # WARNING: VertexGroup name isn't the same as Bone Name its the name in the heirachy which can be changed 397 | # the name of the vertex group should never be different from the joint name 398 | f.write("\"" + vertexGroup.name + "\"; # name of the bone \n"); 399 | 400 | # Count the verts in this skin 401 | vertSkinCount = 0 402 | # Go through each polygon in the Mesh 403 | for polygon in mesh_polygons: 404 | # Go through all the vertices in the polygon 405 | for i in range(len(polygon.vertices)): 406 | try: 407 | vertexGroup.weight(polygon.vertices[i]) 408 | vertSkinCount += 1 409 | except RuntimeError: 410 | # vertex is not in the group 411 | pass 412 | # Write the number of vertices in the skin 413 | f.write(str(vertSkinCount) + "; #verts in this skin \n") 414 | 415 | # Create a dictionary for the weights 416 | skinIndices = list() 417 | skinWeights = list() 418 | # Go through each polygon in the Mesh 419 | for polygon in mesh_polygons: 420 | # Go through all the vertices in the polygon 421 | for i in range(len(polygon.vertices)): 422 | try: 423 | skinWeights.append(vertexGroup.weight(polygon.vertices[i])) 424 | skinIndices.append(polygon.vertices[i]) 425 | except RuntimeError: 426 | # vertex is not in the group 427 | pass 428 | 429 | f.write("# list of indices \n") 430 | for i in range(len(skinIndices)): 431 | f.write(str(skinIndices[i])) 432 | if i < len(skinIndices) - 1: 433 | f.write(",\n") 434 | else: 435 | f.write(";\n") 436 | 437 | f.write("# list of weights \n") 438 | for i in range(len(skinWeights)): 439 | f.write(str('%.6f' % skinWeights[i])) 440 | if i < len(skinWeights) - 1: 441 | f.write(",\n") 442 | else: 443 | f.write(";\n") 444 | 445 | f.write("# offset matrix \n") 446 | # From official Documentation: 447 | # The matrix matrixOffset transforms the mesh vertices to the space of the bone. 448 | # When concatenated to the bone's transform, this provides the world space coordinates of the mesh as affected by the bone 449 | bone = armature.bones[vertexGroup.name] 450 | 451 | # 8888888888888888888888888888 452 | 453 | boneMatrix = bone.matrix_local 454 | boneMatrix = boneMatrix.inverted() 455 | boneMatrix = object.modifiers["Armature"].object.matrix_world.inverted() @ boneMatrix 456 | boneMatrix = object.matrix_world @ boneMatrix 457 | 458 | # Grab bone location rotation and scale 459 | boneLocation = [ boneMatrix[0][3], boneMatrix[1][3], boneMatrix[2][3] ] 460 | myQuaternion = boneMatrix.to_quaternion() 461 | myEuler = myQuaternion.to_euler() 462 | boneRotation = [ myEuler[0], myEuler[1], myEuler[2] ] 463 | boneScale = [ boneMatrix[0][0], boneMatrix[1][2], boneMatrix[2][1] ] 464 | # Create translation Matrix 465 | tMatrix = mathutils.Matrix.Translation((boneLocation[0], boneLocation[2], boneLocation[1])) 466 | # Rotation about the X Axis Matrix 467 | rXMatrix = mathutils.Matrix.Identity(4) 468 | rXMatrix[1][1] = math.cos(-boneRotation[0]) 469 | rXMatrix[1][2] = -math.sin(-boneRotation[0]) 470 | rXMatrix[2][1] = math.sin(-boneRotation[0]) 471 | rXMatrix[2][2] = math.cos(-boneRotation[0]) 472 | # Rotation about the Y Axis Matrix 473 | rYMatrix = mathutils.Matrix.Identity(4) 474 | rYMatrix[0][0] = math.cos(-boneRotation[2]) 475 | rYMatrix[0][2] = math.sin(-boneRotation[2]) 476 | rYMatrix[2][0] = -math.sin(-boneRotation[2]) 477 | rYMatrix[2][2] = math.cos(-boneRotation[2]) 478 | # Rotation about the Z Axis Matrix 479 | rZMatrix = mathutils.Matrix.Identity(4) 480 | rZMatrix[0][0] = math.cos(-boneRotation[1]) 481 | rZMatrix[0][1] = -math.sin(-boneRotation[1]) 482 | rZMatrix[1][0] = math.sin(-boneRotation[1]) 483 | rZMatrix[1][1] = math.cos(-boneRotation[1]) 484 | # Create the Scale Matrices 485 | sXMatrix = mathutils.Matrix.Scale(boneScale[0], 4, (1.0, 0.0, 0.0)) 486 | sYMatrix = mathutils.Matrix.Scale(boneScale[2], 4, (0.0, 1.0, 0.0)) 487 | sZMatrix = mathutils.Matrix.Scale(boneScale[1], 4, (0.0, 0.0, 1.0)) 488 | # Compute the final Model transformation matrix 489 | fMatrix = mathutils.Matrix(tMatrix @ rYMatrix @ rZMatrix @ rXMatrix @sYMatrix @ sZMatrix @ sXMatrix) 490 | # Tranpose before writing 491 | fMatrix.transpose() 492 | # Write the matrix 493 | for j in range(0, 4): 494 | for i in range(0, 4): 495 | f.write(str('%.6f' % fMatrix[j][i])) 496 | if i == 3 and j == 3: 497 | f.write("; ") 498 | else: 499 | f.write(", ") 500 | if i == 3: 501 | f.write("\n") 502 | 503 | 504 | 505 | # 8888888888888888888888888888 506 | 507 | ## Write the Matrix 508 | #for j in range(0, 4): 509 | #for i in range(0, 4): 510 | #f.write(str('%.6f' % boneMatrix[j][i])) 511 | #if j == 3 and i == 3: 512 | #f.write(";;") 513 | #else: 514 | #f.write(",") 515 | #f.write("\n") 516 | #f.write("\n") 517 | #f.write("1.000000, 0.000000, 0.000000, 0.000000,\n") 518 | #f.write("0.000000, 0.000000, 1.000000, 0.000000,\n") 519 | #f.write("0.000000, 1.000000, 0.000000, 0.000000,\n") 520 | #f.write("0.000000, 0.000000, 0.000000, 1.000000;;\n") 521 | f.write("}\n") 522 | 523 | # Go through all bones looking for root bones 524 | for rootBone in armature.bones: 525 | # if bone is a root bone 526 | if rootBone.parent is None: 527 | WriteBoneAndChildren(f, rootBone) 528 | 529 | f.write("}\n") 530 | f.write("}\n") 531 | f.write("\n") 532 | 533 | if object.modifiers.find("Armature") is not -1: 534 | # Grab the Scene 535 | scene = bpy.context.scene 536 | # Write some interesting information into the file 537 | f.write("# Total Frames: " + str(scene.frame_end - scene.frame_start + 1) + "\n") 538 | f.write("# FPS: " + str(bpy.context.scene.render.fps) + "\n") 539 | f.write("# FPS Base: " + str(bpy.context.scene.render.fps_base) + "\n") 540 | f.write("AnimationSet\n") 541 | f.write("{\n") 542 | # Cache the current frame so we can store it later 543 | cacheCurrentFrame = scene.frame_current 544 | # OLD CODE WAS (When confident remove it) 545 | ## Grab the Armature 546 | #armature = object.modifiers["Armature"].object.data 547 | ## Grab the Bones from the Armature 548 | #bones = armature.bones 549 | 550 | # Grab the name of the armature 551 | armatureName = object.modifiers["Armature"].object.name 552 | # Grab the armature 553 | armature = bpy.data.objects[armatureName] 554 | # Grab the Bones from the Armature 555 | bones = armature.pose.bones 556 | 557 | # Calculate frame count 558 | frameCount = (scene.frame_end - scene.frame_start) + 1 559 | # Go through the bones one by one 560 | for bone in bones: 561 | f.write("Animation\n") 562 | f.write("{\n") 563 | f.write("AnimationKey\n") 564 | f.write("{\n") 565 | # TODO: Need to reconstruct the matrix for each frame here 566 | # so that Y is up and that the rotations are correct. Since this happens 567 | # a fair bit we need a function for it 568 | f.write("4; # keytype (4 is matrix type) \n") 569 | f.write(str(frameCount) +";" + "# numberofkeys\n") 570 | # Go through the scene one frame at a time scrubbing through the timeline 571 | for frame in range(scene.frame_start, scene.frame_end + 1, 1): 572 | # Set the frame for the animation 573 | scene.frame_set(frame) 574 | # Grab bone location rotation and scale 575 | boneLocation = bone.location 576 | boneRotation = bone.rotation_euler 577 | boneScale = bone.scale 578 | tMatrix = mathutils.Matrix.Translation((boneLocation[0], boneLocation[2], boneLocation[1])) 579 | # Rotation about the X Axis Matrix 580 | rXMatrix = mathutils.Matrix.Identity(4) 581 | rXMatrix[1][1] = math.cos(-boneRotation[0]) 582 | rXMatrix[1][2] = -math.sin(-boneRotation[0]) 583 | rXMatrix[2][1] = math.sin(-boneRotation[0]) 584 | rXMatrix[2][2] = math.cos(-boneRotation[0]) 585 | # Rotation about the Y Axis Matrix 586 | rYMatrix = mathutils.Matrix.Identity(4) 587 | rYMatrix[0][0] = math.cos(-boneRotation[2]) 588 | rYMatrix[0][2] = math.sin(-boneRotation[2]) 589 | rYMatrix[2][0] = -math.sin(-boneRotation[2]) 590 | rYMatrix[2][2] = math.cos(-boneRotation[2]) 591 | # Rotation about the Z Axis Matrix 592 | rZMatrix = mathutils.Matrix.Identity(4) 593 | rZMatrix[0][0] = math.cos(-boneRotation[1]) 594 | rZMatrix[0][1] = -math.sin(-boneRotation[1]) 595 | rZMatrix[1][0] = math.sin(-boneRotation[1]) 596 | rZMatrix[1][1] = math.cos(-boneRotation[1]) 597 | # Calculate Scale 598 | sXMatrix = mathutils.Matrix.Scale(boneScale[0], 4, (1.0, 0.0, 0.0)) 599 | sYMatrix = mathutils.Matrix.Scale(boneScale[2], 4, (0.0, 1.0, 0.0)) 600 | sZMatrix = mathutils.Matrix.Scale(boneScale[1], 4, (0.0, 0.0, 1.0)) 601 | # Compute the final Model transformation matrix 602 | boneMatrix = mathutils.Matrix(tMatrix @ rYMatrix @ rZMatrix @ rXMatrix @sYMatrix @ sZMatrix @ sXMatrix) 603 | # Tranpose before writing 604 | boneMatrix.transpose() 605 | # Write the FrameNumber, NumberOfelementsIn4x4Matrix(16) and then the elements in the matrix 606 | f.write(str(frame) + ";" + "16" + ";") 607 | f.write("\n") 608 | # Write the bone Matrix 609 | for j in range(0, 4): 610 | for i in range(0, 4): 611 | f.write(str('%.6f' % boneMatrix[j][i])) 612 | if j == 3 and i == 3: 613 | f.write(";;") 614 | else: 615 | f.write(",") 616 | f.write("\n") 617 | if frame < scene.frame_end: 618 | f.write(",") 619 | else: 620 | f.write(";") 621 | f.write("\n") 622 | f.write("}\n") 623 | f.write("{" + bone.name + "}\n") 624 | f.write("}\n") 625 | # Restore the current frame 626 | scene.frame_set(cacheCurrentFrame) 627 | f.write("}\n") 628 | f.write("\n") 629 | # Close the file 630 | f.close() 631 | # Complete the Export 632 | return {'FINISHED'} 633 | 634 | def ExtractFilenameFromPath(filenameandpath): 635 | indexOfBeginingOfFilename = 0; 636 | for index in range(len(filenameandpath)): 637 | i = (len(filenameandpath) - 1) - index 638 | if filenameandpath[i] == '\\' or filenameandpath[i] == '/': 639 | indexOfBeginingOfFilename = i + 1 640 | break 641 | return filenameandpath[indexOfBeginingOfFilename : len(filenameandpath) : 1] 642 | 643 | def WriteHeader(f): 644 | f.write("xof 0302txt 0032\n") 645 | #f.write("Header {1; 0; 1;}\n") # TODO: This really isn't necessary should we remove this? 646 | #f.write("\n") 647 | f.write("# Created by DodgeeSoftware's DirectX Model Exporter\n") 648 | f.write("# Website: www.dodgeesoftware.com\n") 649 | f.write("# Email: dodgeesoftware@gmail.com\n") 650 | f.write("\n") 651 | 652 | def WriteBoilerPlate(f): 653 | f.write("template Header\n") 654 | f.write("{\n") 655 | f.write(" <3D82AB43-62DA-11cf-AB39-0020AF71E433>\n") 656 | f.write(" WORD major;\n") 657 | f.write(" WORD minor;\n") 658 | f.write(" DWORD flags;\n") 659 | f.write("}\n\n") 660 | 661 | f.write("template Vector\n") 662 | f.write("{\n") 663 | f.write(" <3D82AB5E-62DA-11cf-AB39-0020AF71E433>\n") 664 | f.write(" FLOAT x;\n") 665 | f.write(" FLOAT y;\n") 666 | f.write(" FLOAT z;\n") 667 | f.write("}\n\n") 668 | 669 | f.write("template Coords2d\n") 670 | f.write("{\n") 671 | f.write(" \n") 672 | f.write(" FLOAT u;\n") 673 | f.write(" FLOAT v;\n") 674 | f.write("}\n\n") 675 | 676 | f.write("template Matrix4x4\n") 677 | f.write("{\n") 678 | f.write(" \n") 679 | f.write(" array FLOAT matrix[16];\n") 680 | f.write("}\n\n") 681 | 682 | f.write("template ColorRGBA\n") 683 | f.write("{\n") 684 | f.write(" <35FF44E0-6C7C-11cf-8F52-0040333594A3>\n") 685 | f.write(" FLOAT red;\n") 686 | f.write(" FLOAT green;\n") 687 | f.write(" FLOAT blue;\n") 688 | f.write(" FLOAT alpha;\n") 689 | f.write("}\n\n") 690 | 691 | f.write("template ColorRGB\n") 692 | f.write("{\n") 693 | f.write(" \n") 694 | f.write(" FLOAT red;\n") 695 | f.write(" FLOAT green;\n") 696 | f.write(" FLOAT blue;\n") 697 | f.write("}\n\n") 698 | 699 | f.write("template TextureFilename\n") 700 | f.write("{\n") 701 | f.write(" \n") 702 | f.write(" STRING filename;\n") 703 | f.write("}\n\n") 704 | 705 | f.write("template Material\n") 706 | f.write("{\n") 707 | f.write(" <3D82AB4D-62DA-11cf-AB39-0020AF71E433>\n") 708 | f.write(" ColorRGBA faceColor;\n") 709 | f.write(" FLOAT power;\n") 710 | f.write(" ColorRGB specularColor;\n") 711 | f.write(" ColorRGB emissiveColor;\n") 712 | f.write(" [...]\n") 713 | f.write("}\n\n") 714 | 715 | f.write("template MeshFace\n") 716 | f.write("{\n") 717 | f.write(" <3D82AB5F-62DA-11cf-AB39-0020AF71E433>\n") 718 | f.write(" DWORD nFaceVertexIndices;\n") 719 | f.write(" array DWORD faceVertexIndices[nFaceVertexIndices];\n") 720 | f.write("}\n\n") 721 | 722 | f.write("template MeshTextureCoords\n") 723 | f.write("{\n") 724 | f.write(" \n") 725 | f.write(" DWORD nTextureCoords;\n") 726 | f.write(" array Coords2d textureCoords[nTextureCoords];\n") 727 | f.write("}\n\n") 728 | 729 | f.write("template MeshMaterialList\n") 730 | f.write("{\n") 731 | f.write(" \n") 732 | f.write(" DWORD nMaterials;\n") 733 | f.write(" DWORD nFaceIndexes;\n") 734 | f.write(" array DWORD faceIndexes[nFaceIndexes];\n") 735 | f.write(" [Material]\n") 736 | f.write("}\n\n") 737 | 738 | f.write("template MeshNormals\n") 739 | f.write("{\n") 740 | f.write(" \n") 741 | f.write(" DWORD nNormals;\n") 742 | f.write(" array Vector normals[nNormals];\n") 743 | f.write(" DWORD nFaceNormals;\n") 744 | f.write(" array MeshFace faceNormals[nFaceNormals];\n") 745 | f.write("}\n\n") 746 | 747 | f.write("template Mesh\n") 748 | f.write("{\n") 749 | f.write(" <3D82AB44-62DA-11cf-AB39-0020AF71E433>\n") 750 | f.write(" DWORD nVertices;\n") 751 | f.write(" array Vector vertices[nVertices];\n") 752 | f.write(" DWORD nFaces;\n") 753 | f.write(" array MeshFace faces[nFaces];\n") 754 | f.write(" [...]\n") 755 | f.write("}\n\n") 756 | 757 | f.write("template FrameTransformMatrix\n") 758 | f.write("{\n") 759 | f.write(" \n") 760 | f.write(" Matrix4x4 frameMatrix;\n") 761 | f.write("}\n\n") 762 | 763 | f.write("template Frame\n") 764 | f.write("{\n") 765 | f.write(" <3D82AB46-62DA-11cf-AB39-0020AF71E433>\n") 766 | f.write(" [...]\n") 767 | f.write("}\n\n") 768 | 769 | f.write("template FloatKeys\n") 770 | f.write("{\n") 771 | f.write(" <10DD46A9-775B-11cf-8F52-0040333594A3>\n") 772 | f.write(" DWORD nValues;\n") 773 | f.write(" array FLOAT values[nValues];\n") 774 | f.write("}\n\n") 775 | 776 | f.write("template TimedFloatKeys\n") 777 | f.write("{\n") 778 | f.write(" \n") 779 | f.write(" DWORD time;\n") 780 | f.write(" FloatKeys tfkeys;\n") 781 | f.write("}\n\n") 782 | 783 | f.write("template AnimationKey\n") 784 | f.write("{\n") 785 | f.write(" <10DD46A8-775B-11cf-8F52-0040333594A3>\n") 786 | f.write(" DWORD keyType;\n") 787 | f.write(" DWORD nKeys;\n") 788 | f.write(" array TimedFloatKeys keys[nKeys];\n") 789 | f.write("}\n\n") 790 | 791 | f.write("template AnimationOptions\n") 792 | f.write("{\n") 793 | f.write(" \n") 794 | f.write(" DWORD openclosed;\n") 795 | f.write(" DWORD positionquality;\n") 796 | f.write("}\n\n") 797 | 798 | f.write("template Animation\n") 799 | f.write("{\n") 800 | f.write(" <3D82AB4F-62DA-11cf-AB39-0020AF71E433>\n") 801 | f.write(" [...]\n") 802 | f.write("}\n\n") 803 | 804 | f.write("template AnimationSet\n") 805 | f.write("{\n") 806 | f.write(" <3D82AB50-62DA-11cf-AB39-0020AF71E433>\n") 807 | f.write(" [Animation]\n") 808 | f.write("}\n\n") 809 | 810 | f.write("template XSkinMeshHeader\n") 811 | f.write("{\n") 812 | f.write(" <3cf169ce-ff7c-44ab-93c0-f78f62d172e2>\n") 813 | f.write(" WORD nMaxSkinWeightsPerVertex;\n") 814 | f.write(" WORD nMaxSkinWeightsPerFace;\n") 815 | f.write(" WORD nBones;\n") 816 | f.write("}\n\n") 817 | 818 | f.write("template VertexDuplicationIndices\n") 819 | f.write("{\n") 820 | f.write(" \n") 821 | f.write(" DWORD nIndices;\n") 822 | f.write(" DWORD nOriginalVertices;\n") 823 | f.write(" array DWORD indices[nIndices];\n") 824 | f.write("}\n\n") 825 | 826 | f.write("template SkinWeights\n") 827 | f.write("{\n") 828 | f.write(" <6f0d123b-bad2-4167-a0d0-80224f25fabb>\n") 829 | f.write(" STRING transformNodeName;\n") 830 | f.write(" DWORD nWeights;\n") 831 | f.write(" array DWORD vertexIndices[nWeights];\n") 832 | f.write(" array FLOAT weights[nWeights];\n") 833 | f.write(" Matrix4x4 matrixOffset;\n") 834 | f.write("}\n\n") 835 | 836 | 837 | # ***************************************** 838 | # * FUNCTIONS WHICH WRITE A SINGLE OBJECT * 839 | # ***************************************** 840 | 841 | def WriteBool(f, value): 842 | print("not implemented yet") 843 | 844 | def WriteBool2D(f, value): 845 | print("not implemented yet") 846 | 847 | def WriteInt(f, value): 848 | print("not implemented yet") 849 | 850 | def WriteFloat(f, value): 851 | print("not implemented yet") 852 | 853 | def WriteString(f, text): 854 | print("not implemented yet") 855 | 856 | def WriteVector(f, vector): 857 | print("not implemented yet") 858 | 859 | def WriteVertex(f, vertex): 860 | print("not implemented yet") 861 | 862 | def WriteColourRGB(f, colour): 863 | print("not implemented yet") 864 | 865 | def WriteColourRGBA(f, colour): 866 | print("not implemented yet") 867 | 868 | def WriteColourIndexed(f, colour): 869 | print("not implemented yet") 870 | 871 | def WriteMatrix4x4(f, matrix): 872 | print("not implemented yet") 873 | 874 | def WriteQuaternion(f, quaternion): 875 | print("not implemented yet") 876 | 877 | def WriteBone(f, bone): 878 | print("not implemented yet") 879 | 880 | def WriteMeshFace(f, face): 881 | print("not implemented yet") 882 | 883 | def WriteMeshTextureCoords(f, textureCoords): 884 | print("not implemented yet") 885 | 886 | def WriteMeshMaterialList(f, materialList): 887 | print("not implemented yet") 888 | 889 | def WriteMeshNormals(f, meshNormals): 890 | print("not implemented yet") 891 | 892 | def WriteVertextColours(f, meshVertexColours): 893 | print("not implemented yet") 894 | 895 | def WriteTextureFilename(f, textureFilename): 896 | print("not implemented yet") 897 | 898 | def WriteMaterial(f, material): 899 | print("not implemented yet") 900 | 901 | def WriteMesh(f, mesh): 902 | print("not implemented yet") 903 | 904 | def WriteFrameTransformMatrix(f, frameTransformMatrix): 905 | print("not implemented yet") 906 | 907 | def WriteFrame(f, frame): 908 | print("not implemented yet") 909 | 910 | def WriteFloatKeys(f, floatKeys): 911 | print("not implemented yet") 912 | 913 | def WriteTimedFloatKeys(f, timedFloatKeys): 914 | print("not implemented yet") 915 | 916 | def WriteAnimationKey(f, animationKey): 917 | print("not implemented yet") 918 | 919 | def WriteAnimationOptions(f, animationOptions): 920 | print("not implemented yet") 921 | 922 | def WriteAnimation(f, animation): 923 | print("not implemented yet") 924 | 925 | def WriteAnimationSet(f, animationSet): 926 | print("not implemented yet") 927 | 928 | def WriteBoneAndChildren(f, bone): 929 | # write its frame node 930 | f.write("Frame " + bone.name + "\n") 931 | f.write("{\n") 932 | # write its transform 933 | f.write("FrameTransformMatrix\n") 934 | f.write("{\n") 935 | # Grab the Bone Matrix relative to its parent 936 | boneMatrix = bone.matrix_local 937 | # Grab bone location rotation and scale 938 | boneLocation = [ boneMatrix[0][3], boneMatrix[1][3], boneMatrix[2][3] ] 939 | myQuaternion = boneMatrix.to_quaternion() 940 | myEuler = myQuaternion.to_euler() 941 | boneRotation = [ myEuler[0], myEuler[1], myEuler[2] ] # [ 0.0, 0.0, 0.0 ] 942 | boneScale = [ boneMatrix[0][0], boneMatrix[1][2], boneMatrix[2][1] ] 943 | # Create translation Matrix 944 | tMatrix = mathutils.Matrix.Translation((boneLocation[0], boneLocation[2], boneLocation[1])) 945 | # Rotation about the X Axis Matrix 946 | rXMatrix = mathutils.Matrix.Identity(4) 947 | rXMatrix[1][1] = math.cos(-boneRotation[0]) 948 | rXMatrix[1][2] = -math.sin(-boneRotation[0]) 949 | rXMatrix[2][1] = math.sin(-boneRotation[0]) 950 | rXMatrix[2][2] = math.cos(-boneRotation[0]) 951 | # Rotation about the Y Axis Matrix 952 | rYMatrix = mathutils.Matrix.Identity(4) 953 | rYMatrix[0][0] = math.cos(-boneRotation[2]) 954 | rYMatrix[0][2] = math.sin(-boneRotation[2]) 955 | rYMatrix[2][0] = -math.sin(-boneRotation[2]) 956 | rYMatrix[2][2] = math.cos(-boneRotation[2]) 957 | # Rotation about the Z Axis Matrix 958 | rZMatrix = mathutils.Matrix.Identity(4) 959 | rZMatrix[0][0] = math.cos(-boneRotation[1]) 960 | rZMatrix[0][1] = -math.sin(-boneRotation[1]) 961 | rZMatrix[1][0] = math.sin(-boneRotation[1]) 962 | rZMatrix[1][1] = math.cos(-boneRotation[1]) 963 | # Create the Scale Matrices 964 | sXMatrix = mathutils.Matrix.Scale(boneScale[0], 4, (1.0, 0.0, 0.0)) 965 | sYMatrix = mathutils.Matrix.Scale(boneScale[2], 4, (0.0, 1.0, 0.0)) 966 | sZMatrix = mathutils.Matrix.Scale(boneScale[1], 4, (0.0, 0.0, 1.0)) 967 | # Compute the final Model transformation matrix 968 | finalMatrix = mathutils.Matrix(tMatrix @ rYMatrix @ rZMatrix @ rXMatrix @sYMatrix @ sZMatrix @ sXMatrix) 969 | # Tranpose before writing 970 | finalMatrix.transpose() 971 | # Write the matrix 972 | for j in range(0, 4): 973 | for i in range(0, 4): 974 | f.write(str('%.6f' % finalMatrix[j][i])) 975 | if i == 3 and j == 3: 976 | f.write("; ") 977 | else: 978 | f.write(", ") 979 | if i == 3: 980 | f.write("\n") 981 | f.write("}\n") 982 | # Write the child bones 983 | for childBone in bone.children: 984 | WriteBoneAndChildren(f, childBone) 985 | f.write("}\n") 986 | 987 | # TODO: Review this function, does this also convert righthand to left hand? 988 | def ConvertMatrixToYAxisUp(matrix): 989 | # Decompose the Matrix into component parts 990 | location, rotation, scale = matrix.decompose() # TODO: decompose is inaccurate need a better method 991 | 992 | # Translation Matrix 993 | translationMatrix = mathutils.Matrix.Translation((location.x, location.z, location.y)) 994 | # Rotation about the X Axis Matrix 995 | #rotationXMatrix = mathutils.Matrix.Rotation((object.rotation_euler[0]), 4, 'X') 996 | rotationXMatrix = mathutils.Matrix.Identity(4) 997 | rotationXMatrix[1][1] = math.cos(-rotation.x) 998 | rotationXMatrix[1][2] = -math.sin(-rotation.x) 999 | rotationXMatrix[2][1] = math.sin(-rotation.x) 1000 | rotationXMatrix[2][2] = math.cos(-rotation.x) 1001 | # Rotation about the Y Axis Matrix 1002 | #rotationYMatrix = mathutils.Matrix.Rotation((object.rotation_euler[2]), 4, 'Y') 1003 | rotationYMatrix = mathutils.Matrix.Identity(4) 1004 | rotationYMatrix[0][0] = math.cos(-rotation.z) 1005 | rotationYMatrix[0][2] = math.sin(-rotation.z) 1006 | rotationYMatrix[2][0] = -math.sin(-rotation.z) 1007 | rotationYMatrix[2][2] = math.cos(-rotation.z) 1008 | # Rotation about the Z Axis Matrix 1009 | #rotationZMatrix = mathutils.Matrix.Rotation((object.rotation_euler[1]), 4, 'Z') 1010 | rotationZMatrix = mathutils.Matrix.Identity(4) 1011 | rotationZMatrix[0][0] = math.cos(-rotation.y) 1012 | rotationZMatrix[0][1] = -math.sin(-rotation.y) 1013 | rotationZMatrix[1][0] = math.sin(-rotation.y) 1014 | rotationZMatrix[1][1] = math.cos(-rotation.y) 1015 | # Scale Matrix 1016 | scaleXMatrix = mathutils.Matrix.Scale(scale.x, 4, (1.0, 0.0, 0.0)) 1017 | scaleYMatrix = mathutils.Matrix.Scale(scale.z, 4, (0.0, 1.0, 0.0)) 1018 | scaleZMatrix = mathutils.Matrix.Scale(scale.y, 4, (0.0, 0.0, 1.0)) 1019 | 1020 | # Compute the final Model transformation matrix 1021 | finalMatrix = mathutils.Matrix.Identity(4) 1022 | finalMatrix = mathutils.Matrix(translationMatrix @ rotationYMatrix @ rotationZMatrix @ rotationXMatrix @scaleYMatrix @ scaleZMatrix @ scaleXMatrix) 1023 | return finalMatrix -------------------------------------------------------------------------------- /import_x.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN ZLIB LICENSE BLOCK ##### 2 | 3 | # Copyright (c) <2019> 4 | 5 | # This software is provided 'as-is', without any express or implied 6 | # warranty. In no event will the authors be held liable for any damages 7 | # arising from the use of this software. 8 | # 9 | # Permission is granted to anyone to use this software for any purpose, 10 | # including commercial applications, and to alter it and redistribute it 11 | # freely, subject to the following restrictions: 12 | # 13 | # 1. The origin of this software must not be misrepresented; you must not 14 | # claim that you wrote the original software. If you use this software 15 | # in a product, an acknowledgment in the product documentation would be 16 | # appreciated but is not required. 17 | # 2. Altered source versions must be plainly marked as such, and must not be 18 | # misrepresented as being the original software. 19 | # 3. This notice may not be removed or altered from any source distribution. 20 | 21 | # ##### END ZLIB LICENSE BLOCK ##### 22 | 23 | # NOTE: Plugins need to pass through a pep8 checker. The following line 24 | # sets the formatting requirements for this python file. 25 | # 26 | 27 | def ImportFile(filepath): 28 | f = open(filepath,"r") 29 | contents = f.read() 30 | print(contents) 31 | f.close() 32 | return {'FINISHED'} --------------------------------------------------------------------------------