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 #####
21 from math
import radians
24 from mathutils
import *
27 class DirectXExporter
:
28 def __init__(self
, Config
, context
):
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'))
42 self
.Log("Generating object lists for export...")
43 if self
.Config
.SelectedOnly
:
44 ExportList
= list(self
.context
.selected_objects
)
46 ExportList
= list(self
.context
.scene
.objects
)
48 # ExportMap maps Blender objects to ExportObjects
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
,
56 elif Object
.type == 'ARMATURE':
57 ExportMap
[Object
] = ArmatureExportObject(self
.Config
, self
,
60 # Find the objects who do not have a parent or whose parent we are
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
72 for Child
in Children
:
73 if Child
in ExportMap
:
74 Object
.Children
.append(ExportMap
[Child
])
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
)
89 self
.AnimationWriter
= JoinedSetAnimationWriter(self
.Config
,
90 self
, AnimationGenerators
)
96 self
.Log("Exporting to {}".format(self
.File
.FilePath
),
100 self
.Log("Opening file...")
104 self
.Log("Writing header...")
108 self
.Log("Opening Root frame...")
109 self
.__OpenRootFrame
()
112 self
.Log("Writing objects...")
113 for Object
in self
.RootExportList
:
115 self
.Log("Done writing objects")
117 self
.Log("Closing Root frame...")
118 self
.__CloseRootFrame
()
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...")
130 def Log(self
, String
, MessageVerbose
=True):
131 if self
.Config
.Verbose
is True or MessageVerbose
== False:
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\
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\
153 template SkinWeights {\n\
154 <6f0d123b-bad2-4167-a0d0-80224f25fabb>\n\
155 STRING transformNodeName;\n\
157 array DWORD vertexIndices[nWeights];\n\
158 array float weights[nWeights];\n\
159 Matrix4x4 matrixOffset;\n\
162 # Start the Root frame and write its transform matrix
163 def __OpenRootFrame(self
):
164 self
.File
.Write("Frame Root {\n")
167 self
.File
.Write("FrameTransformMatrix {\n")
170 # Write the matrix that will convert Blender's coordinate space into
172 Util
.WriteMatrix(self
.File
, self
.SystemMatrix
)
175 self
.File
.Write("}\n")
177 def __CloseRootFrame(self
):
179 self
.File
.Write("} // End of Root\n")
181 def __GatherAnimationGenerators(self
):
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
,
192 Generators
.append(GenericAnimationGenerator(self
.Config
,
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
)
205 if Object
.BlenderObject
.animation_data
.action
is None:
206 ActionlessObjects
.append(Object
)
209 # If an object has an action, build its appropriate generator
210 if Object
.BlenderObject
.type == 'ARMATURE':
211 Generators
.append(ArmatureAnimationGenerator(self
.Config
,
213 Object
.BlenderObject
.animation_data
.action
.name
),
216 Generators
.append(GenericAnimationGenerator(self
.Config
,
218 Object
.BlenderObject
.animation_data
.action
.name
),
221 # If we should export unused actions as if the first armature was
223 if self
.Config
.AttachToFirstArmature
:
224 # Find the first armature
226 for Object
in self
.ExportList
:
227 if Object
.BlenderObject
.type == 'ARMATURE':
228 FirstArmature
= Object
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
248 if FirstArmature
.BlenderObject
.animation_data
is not None:
250 FirstArmature
.BlenderObject
.animation_data
.action
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
= \
260 Generators
.append(ArmatureAnimationGenerator(
261 self
.Config
, Util
.SafeName(Action
.name
),
264 # Restore old animation data
265 FirstArmature
.BlenderObject
.animation_data
.action
= \
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
))
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
):
282 self
.Exporter
= Exporter
283 self
.BlenderObject
= BlenderObject
285 self
.name
= self
.BlenderObject
.name
# Simple alias
286 self
.SafeName
= Util
.SafeName(self
.BlenderObject
.name
)
290 return "[ExportObject: {}]".format(self
.BlenderObject
.name
)
295 self
.Exporter
.Log("Opening frame for {}".format(self
))
298 self
.Exporter
.Log("Writing children of {}".format(self
))
299 self
._WriteChildren
()
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
):
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
)
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
)
338 return "[MeshExportObject: {}]".format(self
.name
)
343 self
.Exporter
.Log("Opening frame for {}".format(self
))
346 if self
.Config
.ExportMeshes
:
347 self
.Exporter
.Log("Generating mesh for export...")
348 # Generate the export mesh
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
,
370 # Restore the deactivated modifiers
371 for Modifier
in DeactivatedModifierList
:
372 Modifier
.show_viewport
= True
374 Mesh
= self
.BlenderObject
.to_mesh(self
.Exporter
.context
.scene
,
376 self
.Exporter
.Log("Done")
378 self
.__WriteMesh
(Mesh
)
381 bpy
.data
.meshes
.remove(Mesh
)
383 self
.Exporter
.Log("Writing children of {}".format(self
))
384 self
._WriteChildren
()
387 self
.Exporter
.Log("Closed frame of {}".format(self
))
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
):
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]]
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
= []
430 for Polygon
in Mesh
.polygons
:
431 self
.PolygonVertexIndexes
.append(tuple(range(Index
,
432 Index
+ len(Polygon
.vertices
))))
433 Index
+= len(Polygon
.vertices
)
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
)
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
):
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)
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
),
481 self
.Exporter
.File
.Write("{},".format(VertexIndex
),
484 if Index
== PolygonCount
- 1:
485 self
.Exporter
.File
.Write(";\n", Indent
=False)
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
526 class _NormalsMeshEnumerator(MeshExportObject
._MeshEnumerator
):
527 def __init__(self
, Mesh
):
528 MeshExportObject
._MeshEnumerator
(Mesh
)
531 self
.PolygonVertexIndexes
= []
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
]))
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(
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)
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
),
583 self
.Exporter
.File
.Write("{},".format(VertexIndex
),
586 if Index
== FaceCount
- 1:
587 self
.Exporter
.File
.Write(";\n", Indent
=False)
589 self
.Exporter
.File
.Write(",\n", Indent
=False)
591 self
.Exporter
.File
.Unindent()
592 self
.Exporter
.File
.Write("}} // End of {} normals\n".format(
595 def __WriteMeshUVCoordinates(self
, Mesh
):
596 if not Mesh
.uv_textures
:
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
606 for Polygon
in Mesh
.polygons
:
607 VertexCount
+= len(Polygon
.vertices
)
609 # Gather and write UV coordinates
611 self
.Exporter
.File
.Write("{};\n".format(VertexCount
))
612 for Polygon
in Mesh
.polygons
:
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],
621 if Index
== VertexCount
:
622 self
.Exporter
.File
.Write(";\n", Indent
=False)
624 self
.Exporter
.File
.Write(",\n", Indent
=False)
626 self
.Exporter
.File
.Unindent()
627 self
.Exporter
.File
.Write("}} // End of {} UV coordinates\n".format(
630 def __WriteMeshMaterials(self
, Mesh
):
631 def WriteMaterial(Exporter
, Material
):
632 def GetMaterialTextureFileName(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 ==
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']
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
)
668 Exporter
.File
.Write("TextureFilename {{\"{}\";}}\n".format(
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():
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)
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(
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:
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)
729 self
.Exporter
.File
.Write(",\n", Indent
=False)
731 self
.Exporter
.File
.Unindent()
732 self
.Exporter
.File
.Write("}} // End of {} vertex colors\n".format(
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
)
747 # BoneMatrix transforms mesh vertices into the
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
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
:
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
805 # Sum up the weights of groups that correspond
807 for VertexGroup
in Vertex
.groups
:
808 BoneVertexGroup
= GroupIndexToBoneVertexGroups
.get(
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(
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)
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)
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
)
874 return "[ArmatureExportObject: {}]".format(self
.name
)
879 self
.Exporter
.Log("Opening frame for {}".format(self
))
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
()
893 self
.Exporter
.Log("Closed frame of {}".format(self
))
897 def __WriteBones(self
, Bones
):
898 # Simply export the frames for each bone. Export in rest position or
899 # posed position depending on options.
901 BoneMatrix
= Matrix()
903 if self
.Config
.ExportRestBone
:
905 BoneMatrix
= Bone
.parent
.matrix_local
.inverted()
906 BoneMatrix
*= Bone
.matrix_local
908 PoseBone
= self
.BlenderObject
.pose
.bones
[Bone
.name
]
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
942 def __init__(self
, SafeName
):
943 self
.SafeName
= SafeName
945 self
.RotationKeys
= []
947 self
.PositionKeys
= []
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
):
960 self
.SafeName
= SafeName
961 self
.ExportObject
= ExportObject
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
)
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
,
1014 self
.Animations
+= TemporaryGenerator
.Animations
1016 TemporaryGenerator
= GenericAnimationGenerator(self
.Config
,
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()
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
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
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(
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)
1131 self
.Exporter
.File
.Write(",\n", Indent
=False)
1133 self
.Exporter
.File
.Unindent()
1134 self
.Exporter
.File
.Write("}\n")
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)
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)
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(
1181 self
.Exporter
.Log("Done writing animation set {}".format(
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
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.
1218 def __init__(self
, FilePath
):
1219 self
.FilePath
= FilePath
1221 self
.__Whitespace
= 0
1225 self
.File
= open(self
.FilePath
, 'w')
1231 def Write(self
, String
, Indent
=True):
1233 # Escape any formatting braces
1234 String
= String
.replace("{", "{{")
1235 String
= String
.replace("}", "}}")
1236 self
.File
.write(("{}" + String
).format(" " * self
.__Whitespace
))
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
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
)
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
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
1282 def SortByNameField(List
):
1286 return sorted(List
, key
=SortKey
)