fix for various corner cases from testing more sample files.
[blender-addons.git] / io_scene_x / export_x.py
blobf8ccdef2c63775926c4a5ae424b15eded19071be
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation, either version 3
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # All rights reserved.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 from math import radians
23 import bpy
24 from mathutils import *
27 class DirectXExporter:
28 def __init__(self, Config, context):
29 self.Config = Config
30 self.context = context
32 self.Log("Begin verbose logging ----------\n")
34 self.File = File(self.Config.filepath)
36 self.Log("Setting up coordinate system...")
37 # SystemMatrix converts from right-handed, z-up to left-handed, y-up
38 self.SystemMatrix = (Matrix.Scale(-1, 4, Vector((0, 0, 1))) *
39 Matrix.Rotation(radians(-90), 4, 'X'))
40 self.Log("Done")
42 self.Log("Generating object lists for export...")
43 if self.Config.SelectedOnly:
44 ExportList = list(self.context.selected_objects)
45 else:
46 ExportList = list(self.context.scene.objects)
48 # ExportMap maps Blender objects to ExportObjects
49 ExportMap = {}
50 for Object in ExportList:
51 if Object.type == 'EMPTY':
52 ExportMap[Object] = EmptyExportObject(self.Config, self, Object)
53 elif Object.type == 'MESH':
54 ExportMap[Object] = MeshExportObject(self.Config, self,
55 Object)
56 elif Object.type == 'ARMATURE':
57 ExportMap[Object] = ArmatureExportObject(self.Config, self,
58 Object)
60 # Find the objects who do not have a parent or whose parent we are
61 # not exporting
62 self.RootExportList = [Object for Object in ExportMap.values()
63 if Object.BlenderObject.parent not in ExportList]
64 self.RootExportList = Util.SortByNameField(self.RootExportList)
66 self.ExportList = Util.SortByNameField(ExportMap.values())
68 # Determine each object's children from the pool of ExportObjects
69 for Object in ExportMap.values():
70 Children = Object.BlenderObject.children
71 Object.Children = []
72 for Child in Children:
73 if Child in ExportMap:
74 Object.Children.append(ExportMap[Child])
75 self.Log("Done")
77 self.AnimationWriter = None
78 if self.Config.ExportAnimation:
79 self.Log("Gathering animation data...")
81 # Collect all animated object data
82 AnimationGenerators = self.__GatherAnimationGenerators()
84 # Split the data up into animation sets based on user options
85 if self.Config.ExportActionsAsSets:
86 self.AnimationWriter = SplitSetAnimationWriter(self.Config,
87 self, AnimationGenerators)
88 else:
89 self.AnimationWriter = JoinedSetAnimationWriter(self.Config,
90 self, AnimationGenerators)
91 self.Log("Done")
93 # "Public" Interface
95 def Export(self):
96 self.Log("Exporting to {}".format(self.File.FilePath),
97 MessageVerbose=False)
99 # Export everything
100 self.Log("Opening file...")
101 self.File.Open()
102 self.Log("Done")
104 self.Log("Writing header...")
105 self.__WriteHeader()
106 self.Log("Done")
108 self.Log("Opening Root frame...")
109 self.__OpenRootFrame()
110 self.Log("Done")
112 self.Log("Writing objects...")
113 for Object in self.RootExportList:
114 Object.Write()
115 self.Log("Done writing objects")
117 self.Log("Closing Root frame...")
118 self.__CloseRootFrame()
119 self.Log("Done")
121 if self.AnimationWriter is not None:
122 self.Log("Writing animation set(s)...")
123 self.AnimationWriter.WriteAnimationSets()
124 self.Log("Done writing animation set(s)")
126 self.Log("Closing file...")
127 self.File.Close()
128 self.Log("Done")
130 def Log(self, String, MessageVerbose=True):
131 if self.Config.Verbose is True or MessageVerbose == False:
132 print(String)
134 # "Private" Methods
136 def __WriteHeader(self):
137 self.File.Write("xof 0303txt 0032\n\n")
139 # Write the headers that are required by some engines as needed
141 if self.Config.IncludeFrameRate:
142 self.File.Write("template AnimTicksPerSecond {\n\
143 <9E415A43-7BA6-4a73-8743-B73D47E88476>\n\
144 DWORD AnimTicksPerSecond;\n\
145 }\n\n")
146 if self.Config.ExportSkinWeights:
147 self.File.Write("template XSkinMeshHeader {\n\
148 <3cf169ce-ff7c-44ab-93c0-f78f62d172e2>\n\
149 WORD nMaxSkinWeightsPerVertex;\n\
150 WORD nMaxSkinWeightsPerFace;\n\
151 WORD nBones;\n\
152 }\n\n\
153 template SkinWeights {\n\
154 <6f0d123b-bad2-4167-a0d0-80224f25fabb>\n\
155 STRING transformNodeName;\n\
156 DWORD nWeights;\n\
157 array DWORD vertexIndices[nWeights];\n\
158 array float weights[nWeights];\n\
159 Matrix4x4 matrixOffset;\n\
160 }\n\n")
162 # Start the Root frame and write its transform matrix
163 def __OpenRootFrame(self):
164 self.File.Write("Frame Root {\n")
165 self.File.Indent()
167 self.File.Write("FrameTransformMatrix {\n")
168 self.File.Indent()
170 # Write the matrix that will convert Blender's coordinate space into
171 # DirectX's.
172 Util.WriteMatrix(self.File, self.SystemMatrix)
174 self.File.Unindent()
175 self.File.Write("}\n")
177 def __CloseRootFrame(self):
178 self.File.Unindent()
179 self.File.Write("} // End of Root\n")
181 def __GatherAnimationGenerators(self):
182 Generators = []
184 # If all animation data is to be lumped into one AnimationSet,
185 if not self.Config.ExportActionsAsSets:
186 # Build the appropriate generators for each object's type
187 for Object in self.ExportList:
188 if Object.BlenderObject.type == 'ARMATURE':
189 Generators.append(ArmatureAnimationGenerator(self.Config,
190 None, Object))
191 else:
192 Generators.append(GenericAnimationGenerator(self.Config,
193 None, Object))
194 # Otherwise,
195 else:
196 # Keep track of which objects have no action. These will be
197 # lumped together in a Default_Action AnimationSet.
198 ActionlessObjects = []
200 for Object in self.ExportList:
201 if Object.BlenderObject.animation_data is None:
202 ActionlessObjects.append(Object)
203 continue
204 else:
205 if Object.BlenderObject.animation_data.action is None:
206 ActionlessObjects.append(Object)
207 continue
209 # If an object has an action, build its appropriate generator
210 if Object.BlenderObject.type == 'ARMATURE':
211 Generators.append(ArmatureAnimationGenerator(self.Config,
212 Util.SafeName(
213 Object.BlenderObject.animation_data.action.name),
214 Object))
215 else:
216 Generators.append(GenericAnimationGenerator(self.Config,
217 Util.SafeName(
218 Object.BlenderObject.animation_data.action.name),
219 Object))
221 # If we should export unused actions as if the first armature was
222 # using them,
223 if self.Config.AttachToFirstArmature:
224 # Find the first armature
225 FirstArmature = None
226 for Object in self.ExportList:
227 if Object.BlenderObject.type == 'ARMATURE':
228 FirstArmature = Object
229 break
231 if FirstArmature is not None:
232 # Determine which actions are not used
233 UsedActions = [BlenderObject.animation_data.action
234 for BlenderObject in bpy.data.objects
235 if BlenderObject.animation_data is not None]
236 FreeActions = [Action for Action in bpy.data.actions
237 if Action not in UsedActions]
239 # If the first armature has no action, remove it from the
240 # actionless objects so it doesn't end up in Default_Action
241 if FirstArmature in ActionlessObjects and len(FreeActions):
242 ActionlessObjects.remove(FirstArmature)
244 # Keep track of the first armature's animation data so we
245 # can restore it after export
246 OldAction = None
247 NoData = False
248 if FirstArmature.BlenderObject.animation_data is not None:
249 OldAction = \
250 FirstArmature.BlenderObject.animation_data.action
251 else:
252 NoData = True
253 FirstArmature.BlenderObject.animation_data_create()
255 # Build a generator for each unused action
256 for Action in FreeActions:
257 FirstArmature.BlenderObject.animation_data.action = \
258 Action
260 Generators.append(ArmatureAnimationGenerator(
261 self.Config, Util.SafeName(Action.name),
262 FirstArmature))
264 # Restore old animation data
265 FirstArmature.BlenderObject.animation_data.action = \
266 OldAction
268 if NoData:
269 FirstArmature.BlenderObject.animation_data_clear()
271 # Build a special generator for all actionless objects
272 if len(ActionlessObjects):
273 Generators.append(GroupAnimationGenerator(self.Config,
274 "Default_Action", ActionlessObjects))
276 return Generators
278 # This class wraps a Blender object and writes its data to the file
279 class ExportObject: # Base class, do not use
280 def __init__(self, Config, Exporter, BlenderObject):
281 self.Config = Config
282 self.Exporter = Exporter
283 self.BlenderObject = BlenderObject
285 self.name = self.BlenderObject.name # Simple alias
286 self.SafeName = Util.SafeName(self.BlenderObject.name)
287 self.Children = []
289 def __repr__(self):
290 return "[ExportObject: {}]".format(self.BlenderObject.name)
292 # "Public" Interface
294 def Write(self):
295 self.Exporter.Log("Opening frame for {}".format(self))
296 self._OpenFrame()
298 self.Exporter.Log("Writing children of {}".format(self))
299 self._WriteChildren()
301 self._CloseFrame()
302 self.Exporter.Log("Closed frame of {}".format(self))
304 # "Protected" Interface
306 def _OpenFrame(self):
307 self.Exporter.File.Write("Frame {} {{\n".format(self.SafeName))
308 self.Exporter.File.Indent()
310 self.Exporter.File.Write("FrameTransformMatrix {\n")
311 self.Exporter.File.Indent()
312 Util.WriteMatrix(self.Exporter.File, self.BlenderObject.matrix_local)
313 self.Exporter.File.Unindent()
314 self.Exporter.File.Write("}\n")
316 def _CloseFrame(self):
317 self.Exporter.File.Unindent()
318 self.Exporter.File.Write("}} // End of {}\n".format(self.SafeName))
320 def _WriteChildren(self):
321 for Child in Util.SortByNameField(self.Children):
322 Child.Write()
324 # Simple decorator implemenation for ExportObject. Used by empty objects
325 class EmptyExportObject(ExportObject):
326 def __init__(self, Config, Exporter, BlenderObject):
327 ExportObject.__init__(self, Config, Exporter, BlenderObject)
329 def __repr__(self):
330 return "[EmptyExportObject: {}]".format(self.name)
332 # Mesh object implementation of ExportObject
333 class MeshExportObject(ExportObject):
334 def __init__(self, Config, Exporter, BlenderObject):
335 ExportObject.__init__(self, Config, Exporter, BlenderObject)
337 def __repr__(self):
338 return "[MeshExportObject: {}]".format(self.name)
340 # "Public" Interface
342 def Write(self):
343 self.Exporter.Log("Opening frame for {}".format(self))
344 self._OpenFrame()
346 if self.Config.ExportMeshes:
347 self.Exporter.Log("Generating mesh for export...")
348 # Generate the export mesh
349 Mesh = None
350 if self.Config.ApplyModifiers:
351 # Certain modifiers shouldn't be applied in some cases
352 # Deactivate them until after mesh generation is complete
354 DeactivatedModifierList = []
356 # If we're exporting armature data, we shouldn't apply
357 # armature modifiers to the mesh
358 if self.Config.ExportSkinWeights:
359 DeactivatedModifierList = [Modifier
360 for Modifier in self.BlenderObject.modifiers
361 if Modifier.type == 'ARMATURE' and \
362 Modifier.show_viewport]
364 for Modifier in DeactivatedModifierList:
365 Modifier.show_viewport = False
367 Mesh = self.BlenderObject.to_mesh(self.Exporter.context.scene,
368 True, 'PREVIEW')
370 # Restore the deactivated modifiers
371 for Modifier in DeactivatedModifierList:
372 Modifier.show_viewport = True
373 else:
374 Mesh = self.BlenderObject.to_mesh(self.Exporter.context.scene,
375 False, 'PREVIEW')
376 self.Exporter.Log("Done")
378 self.__WriteMesh(Mesh)
380 # Cleanup
381 bpy.data.meshes.remove(Mesh)
383 self.Exporter.Log("Writing children of {}".format(self))
384 self._WriteChildren()
386 self._CloseFrame()
387 self.Exporter.Log("Closed frame of {}".format(self))
389 # "Protected"
391 # This class provides a general system for indexing a mesh, depending on
392 # exporter needs. For instance, some options require us to duplicate each
393 # vertex of each face, some can reuse vertex data. For those we'd use
394 # _UnrolledFacesMeshEnumerator and _OneToOneMeshEnumerator respectively.
395 class _MeshEnumerator:
396 def __init__(self, Mesh):
397 self.Mesh = Mesh
399 # self.vertices and self.PolygonVertexIndexes relate to the
400 # original mesh in the following way:
402 # Mesh.vertices[Mesh.polygons[x].vertices[y]] ==
403 # self.vertices[self.PolygonVertexIndexes[x][y]]
405 self.vertices = None
406 self.PolygonVertexIndexes = None
408 # Represents the mesh as it is inside Blender
409 class _OneToOneMeshEnumerator(_MeshEnumerator):
410 def __init__(self, Mesh):
411 MeshExportObject._MeshEnumerator.__init__(self, Mesh)
413 self.vertices = Mesh.vertices
415 self.PolygonVertexIndexes = tuple(tuple(Polygon.vertices)
416 for Polygon in Mesh.polygons)
418 # Duplicates each vertex for each face
419 class _UnrolledFacesMeshEnumerator(_MeshEnumerator):
420 def __init__(self, Mesh):
421 MeshExportObject._MeshEnumerator.__init__(self, Mesh)
423 self.vertices = tuple()
424 for Polygon in Mesh.polygons:
425 self.vertices += tuple(Mesh.vertices[VertexIndex]
426 for VertexIndex in Polygon.vertices)
428 self.PolygonVertexIndexes = []
429 Index = 0
430 for Polygon in Mesh.polygons:
431 self.PolygonVertexIndexes.append(tuple(range(Index,
432 Index + len(Polygon.vertices))))
433 Index += len(Polygon.vertices)
435 # "Private" Methods
437 def __WriteMesh(self, Mesh):
438 self.Exporter.Log("Writing mesh vertices...")
439 self.Exporter.File.Write("Mesh {{ // {} mesh\n".format(self.SafeName))
440 self.Exporter.File.Indent()
442 # Create the mesh enumerator based on options
443 MeshEnumerator = None
444 if (self.Config.ExportUVCoordinates and Mesh.uv_textures) or \
445 (self.Config.ExportVertexColors and Mesh.vertex_colors) or \
446 (self.Config.ExportSkinWeights):
447 MeshEnumerator = MeshExportObject._UnrolledFacesMeshEnumerator(Mesh)
448 else:
449 MeshEnumerator = MeshExportObject._OneToOneMeshEnumerator(Mesh)
451 # Write vertex positions
452 VertexCount = len(MeshEnumerator.vertices)
453 self.Exporter.File.Write("{};\n".format(VertexCount))
454 for Index, Vertex in enumerate(MeshEnumerator.vertices):
455 Position = Vertex.co
456 self.Exporter.File.Write("{:9f};{:9f};{:9f};".format(
457 Position[0], Position[1], Position[2]))
459 if Index == VertexCount - 1:
460 self.Exporter.File.Write(";\n", Indent=False)
461 else:
462 self.Exporter.File.Write(",\n", Indent=False)
464 # Write face definitions
465 PolygonCount = len(MeshEnumerator.PolygonVertexIndexes)
466 self.Exporter.File.Write("{};\n".format(PolygonCount))
467 for Index, PolygonVertexIndexes in \
468 enumerate(MeshEnumerator.PolygonVertexIndexes):
470 self.Exporter.File.Write("{};".format(len(PolygonVertexIndexes)))
472 PolygonVertexIndexes = PolygonVertexIndexes[::-1]
474 for VertexCountIndex, VertexIndex in \
475 enumerate(PolygonVertexIndexes):
477 if VertexCountIndex == len(PolygonVertexIndexes) - 1:
478 self.Exporter.File.Write("{};".format(VertexIndex),
479 Indent=False)
480 else:
481 self.Exporter.File.Write("{},".format(VertexIndex),
482 Indent=False)
484 if Index == PolygonCount - 1:
485 self.Exporter.File.Write(";\n", Indent=False)
486 else:
487 self.Exporter.File.Write(",\n", Indent=False)
488 self.Exporter.Log("Done")
490 # Write the other mesh components
492 if self.Config.ExportNormals:
493 self.Exporter.Log("Writing mesh normals...")
494 self.__WriteMeshNormals(Mesh)
495 self.Exporter.Log("Done")
497 if self.Config.ExportUVCoordinates:
498 self.Exporter.Log("Writing mesh UV coordinates...")
499 self.__WriteMeshUVCoordinates(Mesh)
500 self.Exporter.Log("Done")
502 if self.Config.ExportMaterials:
503 self.Exporter.Log("Writing mesh materials...")
504 self.__WriteMeshMaterials(Mesh)
505 self.Exporter.Log("Done")
507 if self.Config.ExportVertexColors:
508 self.Exporter.Log("Writing mesh vertex colors...")
509 self.__WriteMeshVertexColors(Mesh, MeshEnumerator=MeshEnumerator)
510 self.Exporter.Log("Done")
512 if self.Config.ExportSkinWeights:
513 self.Exporter.Log("Writing mesh skin weights...")
514 self.__WriteMeshSkinWeights(Mesh, MeshEnumerator=MeshEnumerator)
515 self.Exporter.Log("Done")
517 self.Exporter.File.Unindent()
518 self.Exporter.File.Write("}} // End of {} mesh\n".format(self.SafeName))
520 def __WriteMeshNormals(self, Mesh, MeshEnumerator=None):
521 # Since mesh normals only need their face counts and vertices per face
522 # to match up with the other mesh data, we can optimize export with
523 # this enumerator. Exports each vertex's normal when a face is shaded
524 # smooth, and exports the face normal only once when a face is shaded
525 # flat.
526 class _NormalsMeshEnumerator(MeshExportObject._MeshEnumerator):
527 def __init__(self, Mesh):
528 MeshExportObject._MeshEnumerator(Mesh)
530 self.vertices = []
531 self.PolygonVertexIndexes = []
533 Index = 0
534 for Polygon in Mesh.polygons:
535 if not Polygon.use_smooth:
536 self.vertices.append(Polygon)
537 self.PolygonVertexIndexes.append(
538 tuple(len(Polygon.vertices) * [Index]))
539 Index += 1
540 else:
541 for VertexIndex in Polygon.vertices:
542 self.vertices.append(Mesh.vertices[VertexIndex])
543 self.PolygonVertexIndexes.append(
544 tuple(range(Index, Index + len(Polygon.vertices))))
545 Index += len(Polygon.vertices)
547 if MeshEnumerator is None:
548 MeshEnumerator = _NormalsMeshEnumerator(Mesh)
550 self.Exporter.File.Write("MeshNormals {{ // {} normals\n".format(
551 self.SafeName))
552 self.Exporter.File.Indent()
554 NormalCount = len(MeshEnumerator.vertices)
555 self.Exporter.File.Write("{};\n".format(NormalCount))
557 # Write mesh normals.
558 for Index, Vertex in enumerate(MeshEnumerator.vertices):
559 Normal = Vertex.normal
560 if self.Config.FlipNormals:
561 Normal = -1.0 * Vertex.normal
563 self.Exporter.File.Write("{:9f};{:9f};{:9f};".format(Normal[0],
564 Normal[1], Normal[2]))
566 if Index == NormalCount - 1:
567 self.Exporter.File.Write(";\n", Indent=False)
568 else:
569 self.Exporter.File.Write(",\n", Indent=False)
571 # Write face definitions.
572 FaceCount = len(MeshEnumerator.PolygonVertexIndexes)
573 self.Exporter.File.Write("{};\n".format(FaceCount))
574 for Index, Polygon in enumerate(MeshEnumerator.PolygonVertexIndexes):
575 self.Exporter.File.Write("{};".format(len(Polygon)))
577 # Reverse the winding order
578 for VertexCountIndex, VertexIndex in enumerate(Polygon[::-1]):
579 if VertexCountIndex == len(Polygon) - 1:
580 self.Exporter.File.Write("{};".format(VertexIndex),
581 Indent=False)
582 else:
583 self.Exporter.File.Write("{},".format(VertexIndex),
584 Indent=False)
586 if Index == FaceCount - 1:
587 self.Exporter.File.Write(";\n", Indent=False)
588 else:
589 self.Exporter.File.Write(",\n", Indent=False)
591 self.Exporter.File.Unindent()
592 self.Exporter.File.Write("}} // End of {} normals\n".format(
593 self.SafeName))
595 def __WriteMeshUVCoordinates(self, Mesh):
596 if not Mesh.uv_textures:
597 return
599 self.Exporter.File.Write("MeshTextureCoords {{ // {} UV coordinates\n" \
600 .format(self.SafeName))
601 self.Exporter.File.Indent()
603 UVCoordinates = Mesh.uv_layers.active.data
605 VertexCount = 0
606 for Polygon in Mesh.polygons:
607 VertexCount += len(Polygon.vertices)
609 # Gather and write UV coordinates
610 Index = 0
611 self.Exporter.File.Write("{};\n".format(VertexCount))
612 for Polygon in Mesh.polygons:
613 Vertices = []
614 for Vertex in [UVCoordinates[Vertex] for Vertex in
615 Polygon.loop_indices]:
616 Vertices.append(tuple(Vertex.uv))
617 for Vertex in Vertices:
618 self.Exporter.File.Write("{:9f};{:9f};".format(Vertex[0],
619 1.0 - Vertex[1]))
620 Index += 1
621 if Index == VertexCount:
622 self.Exporter.File.Write(";\n", Indent=False)
623 else:
624 self.Exporter.File.Write(",\n", Indent=False)
626 self.Exporter.File.Unindent()
627 self.Exporter.File.Write("}} // End of {} UV coordinates\n".format(
628 self.SafeName))
630 def __WriteMeshMaterials(self, Mesh):
631 def WriteMaterial(Exporter, Material):
632 def GetMaterialTextureFileName(Material):
633 if Material:
634 # Create a list of Textures that have type 'IMAGE'
635 ImageTextures = [Material.texture_slots[TextureSlot].texture
636 for TextureSlot in Material.texture_slots.keys()
637 if Material.texture_slots[TextureSlot].texture.type ==
638 'IMAGE']
639 # Refine to only image file names if applicable
640 ImageFiles = [bpy.path.basename(Texture.image.filepath)
641 for Texture in ImageTextures
642 if getattr(Texture.image, "source", "") == 'FILE']
643 if ImageFiles:
644 return ImageFiles[0]
645 return None
647 Exporter.File.Write("Material {} {{\n".format(
648 Util.SafeName(Material.name)))
649 Exporter.File.Indent()
651 Diffuse = list(Vector(Material.diffuse_color) *
652 Material.diffuse_intensity)
653 Diffuse.append(Material.alpha)
654 # Map Blender's range of 1 - 511 to 0 - 1000
655 Specularity = 1000 * (Material.specular_hardness - 1.0) / 510.0
656 Specular = list(Vector(Material.specular_color) *
657 Material.specular_intensity)
659 Exporter.File.Write("{:9f};{:9f};{:9f};{:9f};;\n".format(Diffuse[0],
660 Diffuse[1], Diffuse[2], Diffuse[3]))
661 Exporter.File.Write(" {:9f};\n".format(Specularity))
662 Exporter.File.Write("{:9f};{:9f};{:9f};;\n".format(Specular[0],
663 Specular[1], Specular[2]))
664 Exporter.File.Write(" 0.000000; 0.000000; 0.000000;;\n")
666 TextureFileName = GetMaterialTextureFileName(Material)
667 if TextureFileName:
668 Exporter.File.Write("TextureFilename {{\"{}\";}}\n".format(
669 TextureFileName))
671 Exporter.File.Unindent()
672 Exporter.File.Write("}\n");
674 Materials = Mesh.materials
675 # Do not write materials if there are none
676 if not Materials.keys():
677 return
679 self.Exporter.File.Write("MeshMaterialList {{ // {} material list\n".
680 format(self.SafeName))
681 self.Exporter.File.Indent()
683 self.Exporter.File.Write("{};\n".format(len(Materials)))
684 self.Exporter.File.Write("{};\n".format(len(Mesh.polygons)))
685 # Write a material index for each face
686 for Index, Polygon in enumerate(Mesh.polygons):
687 self.Exporter.File.Write("{}".format(Polygon.material_index))
688 if Index == len(Mesh.polygons) - 1:
689 self.Exporter.File.Write(";;\n", Indent=False)
690 else:
691 self.Exporter.File.Write(",\n", Indent=False)
693 for Material in Materials:
694 WriteMaterial(self.Exporter, Material)
696 self.Exporter.File.Unindent()
697 self.Exporter.File.Write("}} // End of {} material list\n".format(
698 self.SafeName))
700 def __WriteMeshVertexColors(self, Mesh, MeshEnumerator=None):
701 # If there are no vertex colors, don't write anything
702 if len(Mesh.vertex_colors) == 0:
703 return
705 # Blender stores vertex color information per vertex per face, so we
706 # need to pass in an _UnrolledFacesMeshEnumerator. Otherwise,
707 if MeshEnumerator is None:
708 MeshEnumerator = _UnrolledFacesMeshEnumerator(Mesh)
710 # Gather the colors of each vertex
711 VertexColorLayer = Mesh.vertex_colors.active
712 VertexColors = [VertexColorLayer.data[Index].color for Index in
713 range(0,len(MeshEnumerator.vertices))]
714 VertexColorCount = len(VertexColors)
716 self.Exporter.File.Write("MeshVertexColors {{ // {} vertex colors\n" \
717 .format(self.SafeName))
718 self.Exporter.File.Indent()
719 self.Exporter.File.Write("{};\n".format(VertexColorCount))
721 # Write the vertex colors for each vertex index.
722 for Index, Color in enumerate(VertexColors):
723 self.Exporter.File.Write("{};{:9f};{:9f};{:9f};{:9f};;".format(
724 Index, Color[0], Color[1], Color[2], 1.0))
726 if Index == VertexColorCount - 1:
727 self.Exporter.File.Write(";\n", Indent=False)
728 else:
729 self.Exporter.File.Write(",\n", Indent=False)
731 self.Exporter.File.Unindent()
732 self.Exporter.File.Write("}} // End of {} vertex colors\n".format(
733 self.SafeName))
735 def __WriteMeshSkinWeights(self, Mesh, MeshEnumerator=None):
736 # This contains vertex indexes and weights for the vertices that belong
737 # to this bone's group. Also calculates the bone skin matrix.
738 class _BoneVertexGroup:
739 def __init__(self, BlenderObject, ArmatureObject, BoneName):
740 self.BoneName = BoneName
741 self.SafeName = Util.SafeName(ArmatureObject.name) + "_" + \
742 Util.SafeName(BoneName)
744 self.Indexes = []
745 self.Weights = []
747 # BoneMatrix transforms mesh vertices into the
748 # space of the bone.
749 # Here are the final transformations in order:
750 # - Object Space to World Space
751 # - World Space to Armature Space
752 # - Armature Space to Bone Space
753 # This way, when BoneMatrix is transformed by the bone's
754 # Frame matrix, the vertices will be in their final world
755 # position.
757 self.BoneMatrix = ArmatureObject.data.bones[BoneName] \
758 .matrix_local.inverted()
759 self.BoneMatrix *= ArmatureObject.matrix_world.inverted()
760 self.BoneMatrix *= BlenderObject.matrix_world
762 def AddVertex(self, Index, Weight):
763 self.Indexes.append(Index)
764 self.Weights.append(Weight)
766 # Skin weights work well with vertex reuse per face. Use a
767 # _OneToOneMeshEnumerator if possible.
768 if MeshEnumerator is None:
769 MeshEnumerator = MeshExportObject._OneToOneMeshEnumerator(Mesh)
771 ArmatureModifierList = [Modifier
772 for Modifier in self.BlenderObject.modifiers
773 if Modifier.type == 'ARMATURE' and Modifier.show_viewport]
775 if not ArmatureModifierList:
776 return
778 # Although multiple armature objects are gathered, support for
779 # multiple armatures per mesh is not complete
780 ArmatureObjects = [Modifier.object for Modifier in ArmatureModifierList]
782 for ArmatureObject in ArmatureObjects:
783 # Determine the names of the bone vertex groups
784 PoseBoneNames = [Bone.name for Bone in ArmatureObject.pose.bones]
785 VertexGroupNames = [Group.name for Group
786 in self.BlenderObject.vertex_groups]
787 UsedBoneNames = set(PoseBoneNames).intersection(VertexGroupNames)
789 # Create a _BoneVertexGroup for each group name
790 BoneVertexGroups = [_BoneVertexGroup(self.BlenderObject,
791 ArmatureObject, BoneName) for BoneName in UsedBoneNames]
793 # Maps Blender's internal group indexing to our _BoneVertexGroups
794 GroupIndexToBoneVertexGroups = {Group.index : BoneVertexGroup
795 for Group in self.BlenderObject.vertex_groups
796 for BoneVertexGroup in BoneVertexGroups
797 if Group.name == BoneVertexGroup.BoneName}
799 MaximumInfluences = 0
801 for Index, Vertex in enumerate(MeshEnumerator.vertices):
802 VertexWeightTotal = 0.0
803 VertexInfluences = 0
805 # Sum up the weights of groups that correspond
806 # to armature bones.
807 for VertexGroup in Vertex.groups:
808 BoneVertexGroup = GroupIndexToBoneVertexGroups.get(
809 VertexGroup.group)
810 if BoneVertexGroup is not None:
811 VertexWeightTotal += VertexGroup.weight
812 VertexInfluences += 1
814 if VertexInfluences > MaximumInfluences:
815 MaximumInfluences = VertexInfluences
817 # Add the vertex to the bone vertex groups it belongs to,
818 # normalizing each bone's weight.
819 for VertexGroup in Vertex.groups:
820 BoneVertexGroup = GroupIndexToBoneVertexGroups.get(
821 VertexGroup.group)
822 if BoneVertexGroup is not None:
823 Weight = VertexGroup.weight / VertexWeightTotal
824 BoneVertexGroup.AddVertex(Index, Weight)
826 self.Exporter.File.Write("XSkinMeshHeader {\n")
827 self.Exporter.File.Indent()
828 self.Exporter.File.Write("{};\n".format(MaximumInfluences))
829 self.Exporter.File.Write("{};\n".format(3 * MaximumInfluences))
830 self.Exporter.File.Write("{};\n".format(len(BoneVertexGroups)))
831 self.Exporter.File.Unindent()
832 self.Exporter.File.Write("}\n")
834 for BoneVertexGroup in BoneVertexGroups:
835 self.Exporter.File.Write("SkinWeights {\n")
836 self.Exporter.File.Indent()
837 self.Exporter.File.Write("\"{}\";\n".format(
838 BoneVertexGroup.SafeName))
840 GroupVertexCount = len(BoneVertexGroup.Indexes)
841 self.Exporter.File.Write("{};\n".format(GroupVertexCount))
843 # Write the indexes of the vertices this bone affects.
844 for Index, VertexIndex in enumerate(BoneVertexGroup.Indexes):
845 self.Exporter.File.Write("{}".format(VertexIndex))
847 if Index == GroupVertexCount - 1:
848 self.Exporter.File.Write(";\n", Indent=False)
849 else:
850 self.Exporter.File.Write(",\n", Indent=False)
852 # Write the weights of the affected vertices.
853 for Index, VertexWeight in enumerate(BoneVertexGroup.Weights):
854 self.Exporter.File.Write("{:9f}".format(VertexWeight))
856 if Index == GroupVertexCount - 1:
857 self.Exporter.File.Write(";\n", Indent=False)
858 else:
859 self.Exporter.File.Write(",\n", Indent=False)
861 # Write the bone's matrix.
862 Util.WriteMatrix(self.Exporter.File, BoneVertexGroup.BoneMatrix)
864 self.Exporter.File.Unindent()
865 self.Exporter.File.Write("}} // End of {} skin weights\n" \
866 .format(BoneVertexGroup.SafeName))
868 # Armature object implementation of ExportObject
869 class ArmatureExportObject(ExportObject):
870 def __init__(self, Config, Exporter, BlenderObject):
871 ExportObject.__init__(self, Config, Exporter, BlenderObject)
873 def __repr__(self):
874 return "[ArmatureExportObject: {}]".format(self.name)
876 # "Public" Interface
878 def Write(self):
879 self.Exporter.Log("Opening frame for {}".format(self))
880 self._OpenFrame()
882 if self.Config.ExportArmatureBones:
883 Armature = self.BlenderObject.data
884 RootBones = [Bone for Bone in Armature.bones if Bone.parent is None]
885 self.Exporter.Log("Writing frames for armature bones...")
886 self.__WriteBones(RootBones)
887 self.Exporter.Log("Done")
889 self.Exporter.Log("Writing children of {}".format(self))
890 self._WriteChildren()
892 self._CloseFrame()
893 self.Exporter.Log("Closed frame of {}".format(self))
895 # "Private" Methods
897 def __WriteBones(self, Bones):
898 # Simply export the frames for each bone. Export in rest position or
899 # posed position depending on options.
900 for Bone in Bones:
901 BoneMatrix = Matrix()
903 if self.Config.ExportRestBone:
904 if Bone.parent:
905 BoneMatrix = Bone.parent.matrix_local.inverted()
906 BoneMatrix *= Bone.matrix_local
907 else:
908 PoseBone = self.BlenderObject.pose.bones[Bone.name]
909 if Bone.parent:
910 BoneMatrix = PoseBone.parent.matrix.inverted()
911 BoneMatrix *= PoseBone.matrix
913 BoneSafeName = self.SafeName + "_" + \
914 Util.SafeName(Bone.name)
915 self.__OpenBoneFrame(BoneSafeName, BoneMatrix)
917 self.__WriteBoneChildren(Bone)
919 self.__CloseBoneFrame(BoneSafeName)
922 def __OpenBoneFrame(self, BoneSafeName, BoneMatrix):
923 self.Exporter.File.Write("Frame {} {{\n".format(BoneSafeName))
924 self.Exporter.File.Indent()
926 self.Exporter.File.Write("FrameTransformMatrix {\n")
927 self.Exporter.File.Indent()
928 Util.WriteMatrix(self.Exporter.File, BoneMatrix)
929 self.Exporter.File.Unindent()
930 self.Exporter.File.Write("}\n")
932 def __CloseBoneFrame(self, BoneSafeName):
933 self.Exporter.File.Unindent()
934 self.Exporter.File.Write("}} // End of {}\n".format(BoneSafeName))
936 def __WriteBoneChildren(self, Bone):
937 self.__WriteBones(Util.SortByNameField(Bone.children))
940 # Container for animation data
941 class Animation:
942 def __init__(self, SafeName):
943 self.SafeName = SafeName
945 self.RotationKeys = []
946 self.ScaleKeys = []
947 self.PositionKeys = []
949 # "Public" Interface
951 def GetKeyCount(self):
952 return len(self.RotationKeys)
955 # Creates a list of Animation objects based on the animation needs of the
956 # ExportObject passed to it
957 class AnimationGenerator: # Base class, do not use
958 def __init__(self, Config, SafeName, ExportObject):
959 self.Config = Config
960 self.SafeName = SafeName
961 self.ExportObject = ExportObject
963 self.Animations = []
966 # Creates one Animation object that contains the rotation, scale, and position
967 # of the ExportObject
968 class GenericAnimationGenerator(AnimationGenerator):
969 def __init__(self, Config, SafeName, ExportObject):
970 AnimationGenerator.__init__(self, Config, SafeName, ExportObject)
972 self._GenerateKeys()
974 # "Protected" Interface
976 def _GenerateKeys(self):
977 Scene = bpy.context.scene # Convenience alias
978 BlenderCurrentFrame = Scene.frame_current
980 CurrentAnimation = Animation(self.ExportObject.SafeName)
981 BlenderObject = self.ExportObject.BlenderObject
983 for Frame in range(Scene.frame_start, Scene.frame_end + 1):
984 Scene.frame_set(Frame)
986 Rotation = BlenderObject.rotation_euler.to_quaternion()
987 Scale = BlenderObject.matrix_local.to_scale()
988 Position = BlenderObject.matrix_local.to_translation()
990 CurrentAnimation.RotationKeys.append(Rotation)
991 CurrentAnimation.ScaleKeys.append(Scale)
992 CurrentAnimation.PositionKeys.append(Position)
994 self.Animations.append(CurrentAnimation)
995 Scene.frame_set(BlenderCurrentFrame)
998 # Creates one Animation object for each of the ExportObjects it gets passed.
999 # Essentially a bunch of GenericAnimationGenerators lumped into one.
1000 class GroupAnimationGenerator(AnimationGenerator):
1001 def __init__(self, Config, SafeName, ExportObjects):
1002 AnimationGenerator.__init__(self, Config, SafeName, None)
1003 self.ExportObjects = ExportObjects
1005 self._GenerateKeys()
1007 # "Protected" Interface
1009 def _GenerateKeys(self):
1010 for Object in self.ExportObjects:
1011 if Object.BlenderObject.type == 'ARMATURE':
1012 TemporaryGenerator = ArmatureAnimationGenerator(self.Config,
1013 None, Object)
1014 self.Animations += TemporaryGenerator.Animations
1015 else:
1016 TemporaryGenerator = GenericAnimationGenerator(self.Config,
1017 None, Object)
1018 self.Animations += TemporaryGenerator.Animations
1021 # Creates an Animation object for the ArmatureExportObject it gets passed and
1022 # an Animation object for each bone in the armature (if options allow)
1023 class ArmatureAnimationGenerator(GenericAnimationGenerator):
1024 def __init__(self, Config, SafeName, ArmatureExportObject):
1025 GenericAnimationGenerator.__init__(self, Config, SafeName,
1026 ArmatureExportObject)
1028 if self.Config.ExportArmatureBones:
1029 self._GenerateBoneKeys()
1031 # "Protected" Interface
1033 def _GenerateBoneKeys(self):
1034 from itertools import zip_longest as zip
1036 Scene = bpy.context.scene # Convenience alias
1037 BlenderCurrentFrame = Scene.frame_current
1039 ArmatureObject = self.ExportObject.BlenderObject
1040 ArmatureSafeName = self.ExportObject.SafeName
1042 # Create Animation objects for each bone
1043 BoneAnimations = [Animation(ArmatureSafeName + "_" + \
1044 Util.SafeName(Bone.name)) for Bone in ArmatureObject.pose.bones]
1046 for Frame in range(Scene.frame_start, Scene.frame_end + 1):
1047 Scene.frame_set(Frame)
1049 for Bone, BoneAnimation in \
1050 zip(ArmatureObject.pose.bones, BoneAnimations):
1052 Rotation = ArmatureObject.data.bones[Bone.name] \
1053 .matrix.to_quaternion() * \
1054 Bone.rotation_quaternion
1056 PoseMatrix = Matrix()
1057 if Bone.parent:
1058 PoseMatrix = Bone.parent.matrix.inverted()
1059 PoseMatrix *= Bone.matrix
1061 Scale = PoseMatrix.to_scale()
1062 Position = PoseMatrix.to_translation()
1064 BoneAnimation.RotationKeys.append(Rotation)
1065 BoneAnimation.ScaleKeys.append(Scale)
1066 BoneAnimation.PositionKeys.append(Position)
1068 self.Animations += BoneAnimations
1069 Scene.frame_set(BlenderCurrentFrame)
1072 # Container for all AnimationGenerators that belong in a single AnimationSet
1073 class AnimationSet:
1074 def __init__(self, SafeName, AnimationGenerators):
1075 self.SafeName = SafeName
1076 self.AnimationGenerators = AnimationGenerators
1079 # Writes all animation data to file. Implementations will control the
1080 # separation of AnimationGenerators into distinct AnimationSets.
1081 class AnimationWriter:
1082 def __init__(self, Config, Exporter, AnimationGenerators):
1083 self.Config = Config
1084 self.Exporter = Exporter
1085 self.AnimationGenerators = AnimationGenerators
1087 self.AnimationSets = []
1089 # "Public" Interface
1091 # Writes all AnimationSets. Implementations probably won't have to override
1092 # this method.
1093 def WriteAnimationSets(self):
1094 if self.Config.IncludeFrameRate:
1095 self.Exporter.Log("Writing frame rate...")
1096 self.__WriteFrameRate()
1097 self.Exporter.Log("Done")
1099 for Set in self.AnimationSets:
1100 self.Exporter.Log("Writing animation set {}".format(Set.SafeName))
1101 self.Exporter.File.Write("AnimationSet {} {{\n".format(
1102 Set.SafeName))
1103 self.Exporter.File.Indent()
1105 # Write each animation of each generator
1106 for Generator in Set.AnimationGenerators:
1107 for CurrentAnimation in Generator.Animations:
1108 self.Exporter.Log("Writing animation of {}".format(
1109 CurrentAnimation.SafeName))
1110 self.Exporter.File.Write("Animation {\n")
1111 self.Exporter.File.Indent()
1112 self.Exporter.File.Write("{{{}}}\n".format(
1113 CurrentAnimation.SafeName))
1115 KeyCount = CurrentAnimation.GetKeyCount()
1117 # Write rotation keys
1118 self.Exporter.File.Write("AnimationKey { // Rotation\n");
1119 self.Exporter.File.Indent()
1120 self.Exporter.File.Write("0;\n")
1121 self.Exporter.File.Write("{};\n".format(KeyCount))
1123 for Frame, Key in enumerate(CurrentAnimation.RotationKeys):
1124 self.Exporter.File.Write(
1125 "{};4;{:9f},{:9f},{:9f},{:9f};;".format(
1126 Frame, -Key[0], Key[1], Key[2], Key[3]))
1128 if Frame == KeyCount - 1:
1129 self.Exporter.File.Write(";\n", Indent=False)
1130 else:
1131 self.Exporter.File.Write(",\n", Indent=False)
1133 self.Exporter.File.Unindent()
1134 self.Exporter.File.Write("}\n")
1136 # Write scale keys
1137 self.Exporter.File.Write("AnimationKey { // Scale\n");
1138 self.Exporter.File.Indent()
1139 self.Exporter.File.Write("1;\n")
1140 self.Exporter.File.Write("{};\n".format(KeyCount))
1142 for Frame, Key in enumerate(CurrentAnimation.ScaleKeys):
1143 self.Exporter.File.Write(
1144 "{};3;{:9f},{:9f},{:9f};;".format(
1145 Frame, Key[0], Key[1], Key[2]))
1147 if Frame == KeyCount - 1:
1148 self.Exporter.File.Write(";\n", Indent=False)
1149 else:
1150 self.Exporter.File.Write(",\n", Indent=False)
1152 self.Exporter.File.Unindent()
1153 self.Exporter.File.Write("}\n")
1155 # Write position keys
1156 self.Exporter.File.Write("AnimationKey { // Position\n");
1157 self.Exporter.File.Indent()
1158 self.Exporter.File.Write("2;\n")
1159 self.Exporter.File.Write("{};\n".format(KeyCount))
1161 for Frame, Key in enumerate(CurrentAnimation.PositionKeys):
1162 self.Exporter.File.Write(
1163 "{};3;{:9f},{:9f},{:9f};;".format(
1164 Frame, Key[0], Key[1], Key[2]))
1166 if Frame == KeyCount - 1:
1167 self.Exporter.File.Write(";\n", Indent=False)
1168 else:
1169 self.Exporter.File.Write(",\n", Indent=False)
1171 self.Exporter.File.Unindent()
1172 self.Exporter.File.Write("}\n")
1174 self.Exporter.File.Unindent()
1175 self.Exporter.File.Write("}\n")
1176 self.Exporter.Log("Done")
1178 self.Exporter.File.Unindent()
1179 self.Exporter.File.Write("}} // End of AnimationSet {}\n".format(
1180 Set.SafeName))
1181 self.Exporter.Log("Done writing animation set {}".format(
1182 Set.SafeName))
1184 # "Private" Methods
1186 def __WriteFrameRate(self):
1187 Scene = bpy.context.scene # Convenience alias
1189 # Calculate the integer frame rate
1190 FrameRate = int(Scene.render.fps / Scene.render.fps_base)
1192 self.Exporter.File.Write("AnimTicksPerSecond {\n");
1193 self.Exporter.File.Indent()
1194 self.Exporter.File.Write("{};\n".format(FrameRate))
1195 self.Exporter.File.Unindent()
1196 self.Exporter.File.Write("}\n")
1198 # Implementation of AnimationWriter that sticks all generators into a
1199 # single AnimationSet
1200 class JoinedSetAnimationWriter(AnimationWriter):
1201 def __init__(self, Config, Exporter, AnimationGenerators):
1202 AnimationWriter.__init__(self, Config, Exporter, AnimationGenerators)
1204 self.AnimationSets = [AnimationSet("Global", self.AnimationGenerators)]
1206 # Implementation of AnimationWriter that puts each generator into its
1207 # own AnimationSet
1208 class SplitSetAnimationWriter(AnimationWriter):
1209 def __init__(self, Config, Exporter, AnimationGenerators):
1210 AnimationWriter.__init__(self, Config, Exporter, AnimationGenerators)
1212 self.AnimationSets = [AnimationSet(Generator.SafeName, [Generator])
1213 for Generator in AnimationGenerators]
1216 # Interface to the file. Supports automatic whitespace indenting.
1217 class File:
1218 def __init__(self, FilePath):
1219 self.FilePath = FilePath
1220 self.File = None
1221 self.__Whitespace = 0
1223 def Open(self):
1224 if not self.File:
1225 self.File = open(self.FilePath, 'w')
1227 def Close(self):
1228 self.File.close()
1229 self.File = None
1231 def Write(self, String, Indent=True):
1232 if Indent:
1233 # Escape any formatting braces
1234 String = String.replace("{", "{{")
1235 String = String.replace("}", "}}")
1236 self.File.write(("{}" + String).format(" " * self.__Whitespace))
1237 else:
1238 self.File.write(String)
1240 def Indent(self, Levels=1):
1241 self.__Whitespace += Levels
1243 def Unindent(self, Levels=1):
1244 self.__Whitespace -= Levels
1245 if self.__Whitespace < 0:
1246 self.__Whitespace = 0
1249 # Static utilities
1250 class Util:
1251 @staticmethod
1252 def SafeName(Name):
1253 # Replaces each character in OldSet with NewChar
1254 def ReplaceSet(String, OldSet, NewChar):
1255 for OldChar in OldSet:
1256 String = String.replace(OldChar, NewChar)
1257 return String
1259 import string
1261 NewName = ReplaceSet(Name, string.punctuation + " ", "_")
1262 if NewName[0].isdigit() or NewName in ["ARRAY", "DWORD", "UCHAR",
1263 "FLOAT", "ULONGLONG", "BINARY_RESOURCE", "SDWORD", "UNICODE",
1264 "CHAR", "STRING", "WORD", "CSTRING", "SWORD", "DOUBLE", "TEMPLATE"]:
1265 NewName = "_" + NewName
1266 return NewName
1268 @staticmethod
1269 def WriteMatrix(File, Matrix):
1270 File.Write("{:9f},{:9f},{:9f},{:9f},\n".format(Matrix[0][0],
1271 Matrix[1][0], Matrix[2][0], Matrix[3][0]))
1272 File.Write("{:9f},{:9f},{:9f},{:9f},\n".format(Matrix[0][1],
1273 Matrix[1][1], Matrix[2][1], Matrix[3][1]))
1274 File.Write("{:9f},{:9f},{:9f},{:9f},\n".format(Matrix[0][2],
1275 Matrix[1][2], Matrix[2][2], Matrix[3][2]))
1276 File.Write("{:9f},{:9f},{:9f},{:9f};;\n".format(Matrix[0][3],
1277 Matrix[1][3], Matrix[2][3], Matrix[3][3]))
1279 # Used on lists of blender objects and lists of ExportObjects, both of
1280 # which have a name field
1281 @staticmethod
1282 def SortByNameField(List):
1283 def SortKey(x):
1284 return x.name
1286 return sorted(List, key=SortKey)