Skip to content
Snippets Groups Projects
export_x.py 51.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation, either version 3
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    #  All rights reserved.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    # <pep8 compliant>
    
    from math import radians
    
    import bpy
    from mathutils import *
    
    
    class DirectXExporter:
        def __init__(self, Config, context):
            self.Config = Config
            self.context = context
    
            self.Log("Begin verbose logging ----------\n")
    
            self.File = File(self.Config.filepath)
    
            self.Log("Setting up coordinate system...")
    
            
            # SystemMatrix converts from right-handed, z-up to the target coordinate system
            self.SystemMatrix = Matrix()
            
            if self.Config.CoordinateSystem == 'LEFT_HANDED':
                self.SystemMatrix *= Matrix.Scale(-1, 4, Vector((0, 0, 1)))
            
            if self.Config.UpAxis == 'Y':
                self.SystemMatrix *= Matrix.Rotation(radians(-90), 4, 'X')
                
    
    47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
            self.Log("Done")
    
            self.Log("Generating object lists for export...")
            if self.Config.SelectedOnly:
                ExportList = list(self.context.selected_objects)
            else:
                ExportList = list(self.context.scene.objects)
    
            # ExportMap maps Blender objects to ExportObjects
            ExportMap = {}
            for Object in ExportList:
                if Object.type == 'EMPTY':
                    ExportMap[Object] = EmptyExportObject(self.Config, self, Object)
                elif Object.type == 'MESH':
                    ExportMap[Object] = MeshExportObject(self.Config, self,
                        Object)
                elif Object.type == 'ARMATURE':
                    ExportMap[Object] = ArmatureExportObject(self.Config, self,
                        Object)
    
            # Find the objects who do not have a parent or whose parent we are
            # not exporting
            self.RootExportList = [Object for Object in ExportMap.values()
                if Object.BlenderObject.parent not in ExportList]
            self.RootExportList = Util.SortByNameField(self.RootExportList)
            
            self.ExportList = Util.SortByNameField(ExportMap.values())
    
            # Determine each object's children from the pool of ExportObjects
            for Object in ExportMap.values():
                Children = Object.BlenderObject.children
                Object.Children = []
                for Child in Children:
                    if Child in ExportMap:
                        Object.Children.append(ExportMap[Child])
            self.Log("Done")
            
            self.AnimationWriter = None
            if self.Config.ExportAnimation:
                self.Log("Gathering animation data...")
                
                # Collect all animated object data
                AnimationGenerators = self.__GatherAnimationGenerators()
                
                # Split the data up into animation sets based on user options
                if self.Config.ExportActionsAsSets:
                    self.AnimationWriter = SplitSetAnimationWriter(self.Config,
                        self, AnimationGenerators)
                else:
                    self.AnimationWriter = JoinedSetAnimationWriter(self.Config,
                        self, AnimationGenerators)
                self.Log("Done")
    
        # "Public" Interface
    
        def Export(self):
            self.Log("Exporting to {}".format(self.File.FilePath),
                MessageVerbose=False)
    
            # Export everything
            self.Log("Opening file...")
            self.File.Open()
            self.Log("Done")
    
            self.Log("Writing header...")
            self.__WriteHeader()
            self.Log("Done")
    
            self.Log("Opening Root frame...")
            self.__OpenRootFrame()
            self.Log("Done")
    
            self.Log("Writing objects...")
            for Object in self.RootExportList:
                Object.Write()
            self.Log("Done writing objects")
    
            self.Log("Closing Root frame...")
            self.__CloseRootFrame()
            self.Log("Done")
            
            if self.AnimationWriter is not None:
                self.Log("Writing animation set(s)...")
                self.AnimationWriter.WriteAnimationSets()
                self.Log("Done writing animation set(s)")
    
            self.Log("Closing file...")
            self.File.Close()
            self.Log("Done")
    
        def Log(self, String, MessageVerbose=True):
            if self.Config.Verbose is True or MessageVerbose == False:
                print(String)
    
        # "Private" Methods
    
        def __WriteHeader(self):
            self.File.Write("xof 0303txt 0032\n\n")
    
            # Write the headers that are required by some engines as needed
    
            if self.Config.IncludeFrameRate:
                self.File.Write("template AnimTicksPerSecond {\n\
      <9E415A43-7BA6-4a73-8743-B73D47E88476>\n\
      DWORD AnimTicksPerSecond;\n\
    }\n\n")
            if self.Config.ExportSkinWeights:
                self.File.Write("template XSkinMeshHeader {\n\
      <3cf169ce-ff7c-44ab-93c0-f78f62d172e2>\n\
      WORD nMaxSkinWeightsPerVertex;\n\
      WORD nMaxSkinWeightsPerFace;\n\
      WORD nBones;\n\
    }\n\n\
    template SkinWeights {\n\
      <6f0d123b-bad2-4167-a0d0-80224f25fabb>\n\
      STRING transformNodeName;\n\
      DWORD nWeights;\n\
      array DWORD vertexIndices[nWeights];\n\
      array float weights[nWeights];\n\
      Matrix4x4 matrixOffset;\n\
    }\n\n")
    
        # Start the Root frame and write its transform matrix
        def __OpenRootFrame(self):
            self.File.Write("Frame Root {\n")
            self.File.Indent()
    
            self.File.Write("FrameTransformMatrix {\n")
            self.File.Indent()
            
            # Write the matrix that will convert Blender's coordinate space into
            # DirectX's.
            Util.WriteMatrix(self.File, self.SystemMatrix)
            
            self.File.Unindent()
            self.File.Write("}\n")
    
        def __CloseRootFrame(self):
            self.File.Unindent()
            self.File.Write("} // End of Root\n")
        
        def __GatherAnimationGenerators(self):
            Generators = []
            
            # If all animation data is to be lumped into one AnimationSet,
            if not self.Config.ExportActionsAsSets:
                # Build the appropriate generators for each object's type
                for Object in self.ExportList:
                    if Object.BlenderObject.type == 'ARMATURE':
                        Generators.append(ArmatureAnimationGenerator(self.Config, 
                            None, Object))
                    else:
                        Generators.append(GenericAnimationGenerator(self.Config,
                            None, Object))
            # Otherwise,
            else:
                # Keep track of which objects have no action.  These will be
                # lumped together in a Default_Action AnimationSet.
                ActionlessObjects = []
                
                for Object in self.ExportList:
                    if Object.BlenderObject.animation_data is None:
                        ActionlessObjects.append(Object)
                        continue
                    else:
                        if Object.BlenderObject.animation_data.action is None:
                            ActionlessObjects.append(Object)
                            continue
                    
                    # If an object has an action, build its appropriate generator
                    if Object.BlenderObject.type == 'ARMATURE':
                        Generators.append(ArmatureAnimationGenerator(self.Config,
                            Util.SafeName(
                                Object.BlenderObject.animation_data.action.name),
                            Object))
                    else:
                        Generators.append(GenericAnimationGenerator(self.Config,
                            Util.SafeName(
                                Object.BlenderObject.animation_data.action.name),
                            Object))
                
                # If we should export unused actions as if the first armature was
                # using them,
                if self.Config.AttachToFirstArmature:
                    # Find the first armature
                    FirstArmature = None
                    for Object in self.ExportList:
                        if Object.BlenderObject.type == 'ARMATURE':
                            FirstArmature = Object
                            break
                        
                    if FirstArmature is not None:
                        # Determine which actions are not used
                        UsedActions = [BlenderObject.animation_data.action
                            for BlenderObject in bpy.data.objects
                            if BlenderObject.animation_data is not None]
                        FreeActions = [Action for Action in bpy.data.actions
                            if Action not in UsedActions]
                        
                        # If the first armature has no action, remove it from the
                        # actionless objects so it doesn't end up in Default_Action
                        if FirstArmature in ActionlessObjects and len(FreeActions):
                            ActionlessObjects.remove(FirstArmature)
                        
                        # Keep track of the first armature's animation data so we
                        # can restore it after export
                        OldAction = None
                        NoData = False
                        if FirstArmature.BlenderObject.animation_data is not None:
                            OldAction = \
                                FirstArmature.BlenderObject.animation_data.action
                        else:
                            NoData = True
                            FirstArmature.BlenderObject.animation_data_create()
                        
                        # Build a generator for each unused action
                        for Action in FreeActions:
                            FirstArmature.BlenderObject.animation_data.action = \
                                Action
                            
                            Generators.append(ArmatureAnimationGenerator(
                                self.Config, Util.SafeName(Action.name),
                                FirstArmature))
                        
                        # Restore old animation data
                        FirstArmature.BlenderObject.animation_data.action = \
                            OldAction
                            
                        if NoData:
                            FirstArmature.BlenderObject.animation_data_clear()
                
                # Build a special generator for all actionless objects
                if len(ActionlessObjects):
                    Generators.append(GroupAnimationGenerator(self.Config,
                        "Default_Action", ActionlessObjects))
    
            return Generators        
    
    # This class wraps a Blender object and writes its data to the file
    class ExportObject: # Base class, do not use
        def __init__(self, Config, Exporter, BlenderObject):
            self.Config = Config
            self.Exporter = Exporter
            self.BlenderObject = BlenderObject
    
            self.name = self.BlenderObject.name # Simple alias
            self.SafeName = Util.SafeName(self.BlenderObject.name)
            self.Children = []
    
        def __repr__(self):
            return "[ExportObject: {}]".format(self.BlenderObject.name)
    
        # "Public" Interface
    
        def Write(self):
            self.Exporter.Log("Opening frame for {}".format(self))
            self._OpenFrame()
    
            self.Exporter.Log("Writing children of {}".format(self))
            self._WriteChildren()
    
            self._CloseFrame()
            self.Exporter.Log("Closed frame of {}".format(self))
    
        # "Protected" Interface
    
        def _OpenFrame(self):
            self.Exporter.File.Write("Frame {} {{\n".format(self.SafeName))
            self.Exporter.File.Indent()
    
            self.Exporter.File.Write("FrameTransformMatrix {\n")
            self.Exporter.File.Indent()
            Util.WriteMatrix(self.Exporter.File, self.BlenderObject.matrix_local)
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}\n")
    
        def _CloseFrame(self):
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}} // End of {}\n".format(self.SafeName))
    
        def _WriteChildren(self):
            for Child in Util.SortByNameField(self.Children):
                Child.Write()
    
    # Simple decorator implemenation for ExportObject.  Used by empty objects
    class EmptyExportObject(ExportObject):
        def __init__(self, Config, Exporter, BlenderObject):
            ExportObject.__init__(self, Config, Exporter, BlenderObject)
    
        def __repr__(self):
            return "[EmptyExportObject: {}]".format(self.name)
        
    # Mesh object implementation of ExportObject
    class MeshExportObject(ExportObject):
        def __init__(self, Config, Exporter, BlenderObject):
            ExportObject.__init__(self, Config, Exporter, BlenderObject)
    
        def __repr__(self):
            return "[MeshExportObject: {}]".format(self.name)
    
        # "Public" Interface
    
        def Write(self):
            self.Exporter.Log("Opening frame for {}".format(self))
            self._OpenFrame()
    
            if self.Config.ExportMeshes:
                self.Exporter.Log("Generating mesh for export...")
                # Generate the export mesh
                Mesh = None
                if self.Config.ApplyModifiers:
                    # Certain modifiers shouldn't be applied in some cases
                    # Deactivate them until after mesh generation is complete
                    
                    DeactivatedModifierList = []
                    
                    # If we're exporting armature data, we shouldn't apply
                    # armature modifiers to the mesh
                    if self.Config.ExportSkinWeights:
                        DeactivatedModifierList = [Modifier
                            for Modifier in self.BlenderObject.modifiers
                            if Modifier.type == 'ARMATURE' and \
                            Modifier.show_viewport]
                    
                    for Modifier in DeactivatedModifierList:
                        Modifier.show_viewport = False
                            
                    Mesh = self.BlenderObject.to_mesh(self.Exporter.context.scene,
                        True, 'PREVIEW')
                    
                    # Restore the deactivated modifiers
                    for Modifier in DeactivatedModifierList:
                        Modifier.show_viewport = True   
                else:
                    Mesh = self.BlenderObject.to_mesh(self.Exporter.context.scene,
                        False, 'PREVIEW')
                self.Exporter.Log("Done")
                        
                self.__WriteMesh(Mesh)
    
                # Cleanup
                bpy.data.meshes.remove(Mesh)
    
            self.Exporter.Log("Writing children of {}".format(self))
            self._WriteChildren()
    
            self._CloseFrame()
            self.Exporter.Log("Closed frame of {}".format(self))
    
        # "Protected"
        
        # This class provides a general system for indexing a mesh, depending on
        # exporter needs.  For instance, some options require us to duplicate each
        # vertex of each face, some can reuse vertex data.  For those we'd use
        # _UnrolledFacesMeshEnumerator and _OneToOneMeshEnumerator respectively.
        class _MeshEnumerator:
            def __init__(self, Mesh):
                self.Mesh = Mesh
                
                # self.vertices and self.PolygonVertexIndexes relate to the
                # original mesh in the following way:
                
                # Mesh.vertices[Mesh.polygons[x].vertices[y]] == 
                # self.vertices[self.PolygonVertexIndexes[x][y]]
                
                self.vertices = None 
                self.PolygonVertexIndexes = None
        
        # Represents the mesh as it is inside Blender
        class _OneToOneMeshEnumerator(_MeshEnumerator):
            def __init__(self, Mesh):
                MeshExportObject._MeshEnumerator.__init__(self, Mesh)
                
                self.vertices = Mesh.vertices
                
                self.PolygonVertexIndexes = tuple(tuple(Polygon.vertices)
                    for Polygon in Mesh.polygons)
    
        # Duplicates each vertex for each face
        class _UnrolledFacesMeshEnumerator(_MeshEnumerator):
            def __init__(self, Mesh):
                MeshExportObject._MeshEnumerator.__init__(self, Mesh)
                
                self.vertices = tuple()
                for Polygon in Mesh.polygons:
                    self.vertices += tuple(Mesh.vertices[VertexIndex]
                        for VertexIndex in Polygon.vertices)
                
                self.PolygonVertexIndexes = []
                Index = 0
                for Polygon in Mesh.polygons:
                    self.PolygonVertexIndexes.append(tuple(range(Index, 
                        Index + len(Polygon.vertices))))
                    Index += len(Polygon.vertices)
                
        # "Private" Methods
    
        def __WriteMesh(self, Mesh):
            self.Exporter.Log("Writing mesh vertices...")
            self.Exporter.File.Write("Mesh {{ // {} mesh\n".format(self.SafeName))
            self.Exporter.File.Indent()
            
            # Create the mesh enumerator based on options
            MeshEnumerator = None
            if (self.Config.ExportUVCoordinates and Mesh.uv_textures) or \
    
                (self.Config.ExportVertexColors and Mesh.vertex_colors) or \
                (self.Config.ExportSkinWeights):
    
                MeshEnumerator = MeshExportObject._UnrolledFacesMeshEnumerator(Mesh)
            else:
                MeshEnumerator = MeshExportObject._OneToOneMeshEnumerator(Mesh)
            
            # Write vertex positions
            VertexCount = len(MeshEnumerator.vertices)
            self.Exporter.File.Write("{};\n".format(VertexCount))
            for Index, Vertex in enumerate(MeshEnumerator.vertices):
                Position = Vertex.co
                self.Exporter.File.Write("{:9f};{:9f};{:9f};".format(
                            Position[0], Position[1], Position[2]))
                
                if Index == VertexCount - 1:
                    self.Exporter.File.Write(";\n", Indent=False)
                else:
                    self.Exporter.File.Write(",\n", Indent=False)
            
            # Write face definitions
            PolygonCount = len(MeshEnumerator.PolygonVertexIndexes)
            self.Exporter.File.Write("{};\n".format(PolygonCount))
            for Index, PolygonVertexIndexes in \
                enumerate(MeshEnumerator.PolygonVertexIndexes):
                
                self.Exporter.File.Write("{};".format(len(PolygonVertexIndexes)))
                
    
                for VertexCountIndex, VertexIndex in \
                    enumerate(PolygonVertexIndexes):
    
                    if VertexCountIndex == len(PolygonVertexIndexes) - 1:
                        self.Exporter.File.Write("{};".format(VertexIndex),
                            Indent=False)
                    else:
                        self.Exporter.File.Write("{},".format(VertexIndex),
                            Indent=False)
    
                
                if Index == PolygonCount - 1:
                    self.Exporter.File.Write(";\n", Indent=False)
                else:
                    self.Exporter.File.Write(",\n", Indent=False)
            self.Exporter.Log("Done")
            
            # Write the other mesh components
                
            if self.Config.ExportNormals:
                self.Exporter.Log("Writing mesh normals...")
                self.__WriteMeshNormals(Mesh)
                self.Exporter.Log("Done")
                
            if self.Config.ExportUVCoordinates:
                self.Exporter.Log("Writing mesh UV coordinates...")
                self.__WriteMeshUVCoordinates(Mesh)
                self.Exporter.Log("Done")
    
            if self.Config.ExportMaterials:
                self.Exporter.Log("Writing mesh materials...")
                self.__WriteMeshMaterials(Mesh)
                self.Exporter.Log("Done")
            
            if self.Config.ExportVertexColors:
                self.Exporter.Log("Writing mesh vertex colors...")
                self.__WriteMeshVertexColors(Mesh, MeshEnumerator=MeshEnumerator)
                self.Exporter.Log("Done")
            
            if self.Config.ExportSkinWeights:
                self.Exporter.Log("Writing mesh skin weights...")
                self.__WriteMeshSkinWeights(Mesh, MeshEnumerator=MeshEnumerator)
                self.Exporter.Log("Done")
    
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}} // End of {} mesh\n".format(self.SafeName))
    
        def __WriteMeshNormals(self, Mesh, MeshEnumerator=None):
            # Since mesh normals only need their face counts and vertices per face
            # to match up with the other mesh data, we can optimize export with
            # this enumerator.  Exports each vertex's normal when a face is shaded
            # smooth, and exports the face normal only once when a face is shaded
            # flat.
            class _NormalsMeshEnumerator(MeshExportObject._MeshEnumerator):
                def __init__(self, Mesh):
                    MeshExportObject._MeshEnumerator(Mesh)
                    
                    self.vertices = []
                    self.PolygonVertexIndexes = []
                    
                    Index = 0
                    for Polygon in Mesh.polygons:
                        if not Polygon.use_smooth:
                            self.vertices.append(Polygon)
                            self.PolygonVertexIndexes.append(
                                tuple(len(Polygon.vertices) * [Index]))
                            Index += 1
                        else:
    
                            for VertexIndex in Polygon.vertices:
                                self.vertices.append(Mesh.vertices[VertexIndex])
    
                            self.PolygonVertexIndexes.append(
                                tuple(range(Index, Index + len(Polygon.vertices))))
                            Index += len(Polygon.vertices)            
            
            if MeshEnumerator is None:
                MeshEnumerator = _NormalsMeshEnumerator(Mesh)
            
            self.Exporter.File.Write("MeshNormals {{ // {} normals\n".format(
                self.SafeName))
            self.Exporter.File.Indent()
            
            NormalCount = len(MeshEnumerator.vertices)
            self.Exporter.File.Write("{};\n".format(NormalCount))
            
            # Write mesh normals.
            for Index, Vertex in enumerate(MeshEnumerator.vertices):
                Normal = Vertex.normal
    
                if self.Config.FlipNormals:
                    Normal = -1.0 * Vertex.normal
                
    
                self.Exporter.File.Write("{:9f};{:9f};{:9f};".format(Normal[0],
                    Normal[1], Normal[2]))
                
                if Index == NormalCount - 1:
                    self.Exporter.File.Write(";\n", Indent=False)
                else:
                    self.Exporter.File.Write(",\n", Indent=False)
            
            # Write face definitions.
            FaceCount = len(MeshEnumerator.PolygonVertexIndexes)
            self.Exporter.File.Write("{};\n".format(FaceCount))
            for Index, Polygon in enumerate(MeshEnumerator.PolygonVertexIndexes):
                self.Exporter.File.Write("{};".format(len(Polygon)))
                
    
                for VertexCountIndex, VertexIndex in enumerate(Polygon):
    
                    if VertexCountIndex == len(Polygon) - 1:
                        self.Exporter.File.Write("{};".format(VertexIndex),
                            Indent=False)
                    else:
                        self.Exporter.File.Write("{},".format(VertexIndex),
                            Indent=False)
    
                
                if Index == FaceCount - 1:
                    self.Exporter.File.Write(";\n", Indent=False)
                else:
                    self.Exporter.File.Write(",\n", Indent=False)
    
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}} // End of {} normals\n".format(
                self.SafeName))
         
        def __WriteMeshUVCoordinates(self, Mesh):
            if not Mesh.uv_textures:
                return
            
            self.Exporter.File.Write("MeshTextureCoords {{ // {} UV coordinates\n" \
                .format(self.SafeName))
            self.Exporter.File.Indent()
            
            UVCoordinates = Mesh.uv_layers.active.data
            
            VertexCount = 0
            for Polygon in Mesh.polygons:
                VertexCount += len(Polygon.vertices)
            
            # Gather and write UV coordinates
            Index = 0
            self.Exporter.File.Write("{};\n".format(VertexCount))
            for Polygon in Mesh.polygons:
                Vertices = []
                for Vertex in [UVCoordinates[Vertex] for Vertex in
                    Polygon.loop_indices]:
                    Vertices.append(tuple(Vertex.uv))
                for Vertex in Vertices:
                    self.Exporter.File.Write("{:9f};{:9f};".format(Vertex[0],
    
    624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
                    Index += 1
                    if Index == VertexCount:
                        self.Exporter.File.Write(";\n", Indent=False)
                    else:
                        self.Exporter.File.Write(",\n", Indent=False)
                        
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}} // End of {} UV coordinates\n".format(
                self.SafeName))
    
        def __WriteMeshMaterials(self, Mesh):
            def WriteMaterial(Exporter, Material):
                def GetMaterialTextureFileName(Material):
                    if Material:
                        # Create a list of Textures that have type 'IMAGE'
                        ImageTextures = [Material.texture_slots[TextureSlot].texture
                            for TextureSlot in Material.texture_slots.keys()
                            if Material.texture_slots[TextureSlot].texture.type ==
                            'IMAGE']
                        # Refine to only image file names if applicable
                        ImageFiles = [bpy.path.basename(Texture.image.filepath)
                            for Texture in ImageTextures
                            if getattr(Texture.image, "source", "") == 'FILE']
                        if ImageFiles:
                            return ImageFiles[0]
                    return None
                
                Exporter.File.Write("Material {} {{\n".format(
                    Util.SafeName(Material.name)))
                Exporter.File.Indent()
                
                Diffuse = list(Vector(Material.diffuse_color) *
                    Material.diffuse_intensity)
                Diffuse.append(Material.alpha)
                # Map Blender's range of 1 - 511 to 0 - 1000
                Specularity = 1000 * (Material.specular_hardness - 1.0) / 510.0
                Specular = list(Vector(Material.specular_color) *
                    Material.specular_intensity)
                
                Exporter.File.Write("{:9f};{:9f};{:9f};{:9f};;\n".format(Diffuse[0],
                    Diffuse[1], Diffuse[2], Diffuse[3]))
                Exporter.File.Write(" {:9f};\n".format(Specularity))
                Exporter.File.Write("{:9f};{:9f};{:9f};;\n".format(Specular[0],
                    Specular[1], Specular[2]))
                Exporter.File.Write(" 0.000000; 0.000000; 0.000000;;\n")
                
                TextureFileName = GetMaterialTextureFileName(Material)
                if TextureFileName:
                    Exporter.File.Write("TextureFilename {{\"{}\";}}\n".format(
                        TextureFileName))
                
                Exporter.File.Unindent()
                Exporter.File.Write("}\n");
            
            Materials = Mesh.materials
            # Do not write materials if there are none
            if not Materials.keys():
                return
            
            self.Exporter.File.Write("MeshMaterialList {{ // {} material list\n".
                format(self.SafeName))
            self.Exporter.File.Indent()
            
            self.Exporter.File.Write("{};\n".format(len(Materials)))
            self.Exporter.File.Write("{};\n".format(len(Mesh.polygons)))
            # Write a material index for each face
            for Index, Polygon in enumerate(Mesh.polygons):
                self.Exporter.File.Write("{}".format(Polygon.material_index))
                if Index == len(Mesh.polygons) - 1:
                    self.Exporter.File.Write(";;\n", Indent=False)
                else:
                    self.Exporter.File.Write(",\n", Indent=False)
            
            for Material in Materials:
                WriteMaterial(self.Exporter, Material)
            
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}} // End of {} material list\n".format(
                self.SafeName))
        
        def __WriteMeshVertexColors(self, Mesh, MeshEnumerator=None):
            # If there are no vertex colors, don't write anything
            if len(Mesh.vertex_colors) == 0:
                return
            
            # Blender stores vertex color information per vertex per face, so we
            # need to pass in an _UnrolledFacesMeshEnumerator.  Otherwise,
            if MeshEnumerator is None:
                MeshEnumerator = _UnrolledFacesMeshEnumerator(Mesh)
            
            # Gather the colors of each vertex
            VertexColorLayer = Mesh.vertex_colors.active
            VertexColors = [VertexColorLayer.data[Index].color for Index in
                range(0,len(MeshEnumerator.vertices))]
            VertexColorCount = len(VertexColors)
            
            self.Exporter.File.Write("MeshVertexColors {{ // {} vertex colors\n" \
                .format(self.SafeName))
            self.Exporter.File.Indent()
            self.Exporter.File.Write("{};\n".format(VertexColorCount))
            
            # Write the vertex colors for each vertex index.
            for Index, Color in enumerate(VertexColors):
                self.Exporter.File.Write("{};{:9f};{:9f};{:9f};{:9f};;".format(
                    Index, Color[0], Color[1], Color[2], 1.0))
                
                if Index == VertexColorCount - 1:
                    self.Exporter.File.Write(";\n", Indent=False)
                else:
                    self.Exporter.File.Write(",\n", Indent=False)
            
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}} // End of {} vertex colors\n".format(
                self.SafeName))
        
        def __WriteMeshSkinWeights(self, Mesh, MeshEnumerator=None):
            # This contains vertex indexes and weights for the vertices that belong
            # to this bone's group.  Also calculates the bone skin matrix.
            class _BoneVertexGroup:
                    def __init__(self, BlenderObject, ArmatureObject, BoneName):
                        self.BoneName = BoneName
                        self.SafeName = Util.SafeName(ArmatureObject.name) + "_" + \
                            Util.SafeName(BoneName)
                        
                        self.Indexes = []
                        self.Weights = []
                        
                        # BoneMatrix transforms mesh vertices into the
                        # space of the bone.
                        # Here are the final transformations in order:
                        #  - Object Space to World Space
                        #  - World Space to Armature Space
                        #  - Armature Space to Bone Space
                        # This way, when BoneMatrix is transformed by the bone's
                        # Frame matrix, the vertices will be in their final world
                        # position.
                        
                        self.BoneMatrix = ArmatureObject.data.bones[BoneName] \
                            .matrix_local.inverted()
                        self.BoneMatrix *= ArmatureObject.matrix_world.inverted()
                        self.BoneMatrix *= BlenderObject.matrix_world
                    
                    def AddVertex(self, Index, Weight):
                        self.Indexes.append(Index)
                        self.Weights.append(Weight)
            
            # Skin weights work well with vertex reuse per face.  Use a
            # _OneToOneMeshEnumerator if possible.
            if MeshEnumerator is None:
                MeshEnumerator = MeshExportObject._OneToOneMeshEnumerator(Mesh)
            
            ArmatureModifierList = [Modifier 
                for Modifier in self.BlenderObject.modifiers
                if Modifier.type == 'ARMATURE' and Modifier.show_viewport]
            
            if not ArmatureModifierList:
                return
            
            # Although multiple armature objects are gathered, support for
            # multiple armatures per mesh is not complete
            ArmatureObjects = [Modifier.object for Modifier in ArmatureModifierList]
            
            for ArmatureObject in ArmatureObjects:
                # Determine the names of the bone vertex groups
                PoseBoneNames = [Bone.name for Bone in ArmatureObject.pose.bones]
                VertexGroupNames = [Group.name for Group
                    in self.BlenderObject.vertex_groups]
                UsedBoneNames = set(PoseBoneNames).intersection(VertexGroupNames)
                
                # Create a _BoneVertexGroup for each group name
                BoneVertexGroups = [_BoneVertexGroup(self.BlenderObject,
                    ArmatureObject, BoneName) for BoneName in UsedBoneNames]
                
                # Maps Blender's internal group indexing to our _BoneVertexGroups
                GroupIndexToBoneVertexGroups = {Group.index : BoneVertexGroup
                    for Group in self.BlenderObject.vertex_groups
                    for BoneVertexGroup in BoneVertexGroups
                    if Group.name == BoneVertexGroup.BoneName}
                
                MaximumInfluences = 0
                
                for Index, Vertex in enumerate(MeshEnumerator.vertices):
                    VertexWeightTotal = 0.0
                    VertexInfluences = 0
                    
                    # Sum up the weights of groups that correspond
                    # to armature bones.
                    for VertexGroup in Vertex.groups:
                        BoneVertexGroup = GroupIndexToBoneVertexGroups.get(
                            VertexGroup.group)
                        if BoneVertexGroup is not None:
                            VertexWeightTotal += VertexGroup.weight
                            VertexInfluences += 1
                    
                    if VertexInfluences > MaximumInfluences:
                        MaximumInfluences = VertexInfluences
                    
                    # Add the vertex to the bone vertex groups it belongs to,
                    # normalizing each bone's weight.
                    for VertexGroup in Vertex.groups:
                        BoneVertexGroup = GroupIndexToBoneVertexGroups.get(
                            VertexGroup.group)
                        if BoneVertexGroup is not None:
                            Weight = VertexGroup.weight / VertexWeightTotal
                            BoneVertexGroup.AddVertex(Index, Weight)
                
                self.Exporter.File.Write("XSkinMeshHeader {\n")
                self.Exporter.File.Indent()
                self.Exporter.File.Write("{};\n".format(MaximumInfluences))
                self.Exporter.File.Write("{};\n".format(3 * MaximumInfluences))
                self.Exporter.File.Write("{};\n".format(len(BoneVertexGroups)))
                self.Exporter.File.Unindent()
                self.Exporter.File.Write("}\n")
                
                for BoneVertexGroup in BoneVertexGroups:
                    self.Exporter.File.Write("SkinWeights {\n")
                    self.Exporter.File.Indent()
                    self.Exporter.File.Write("\"{}\";\n".format(
                        BoneVertexGroup.SafeName))
                    
                    GroupVertexCount = len(BoneVertexGroup.Indexes)
                    self.Exporter.File.Write("{};\n".format(GroupVertexCount))
                    
                    # Write the indexes of the vertices this bone affects.
                    for Index, VertexIndex in enumerate(BoneVertexGroup.Indexes):
                        self.Exporter.File.Write("{}".format(VertexIndex))
                        
                        if Index == GroupVertexCount - 1:
                            self.Exporter.File.Write(";\n", Indent=False)
                        else:
                            self.Exporter.File.Write(",\n", Indent=False)
                    
                    # Write the weights of the affected vertices.
                    for Index, VertexWeight in enumerate(BoneVertexGroup.Weights):
                        self.Exporter.File.Write("{:9f}".format(VertexWeight))
                        
                        if Index == GroupVertexCount - 1:
                            self.Exporter.File.Write(";\n", Indent=False)
                        else:
                            self.Exporter.File.Write(",\n", Indent=False)
                    
                    # Write the bone's matrix.
                    Util.WriteMatrix(self.Exporter.File, BoneVertexGroup.BoneMatrix)
                
                    self.Exporter.File.Unindent()
                    self.Exporter.File.Write("}} // End of {} skin weights\n" \
                        .format(BoneVertexGroup.SafeName))
                
    # Armature object implementation of ExportObject            
    class ArmatureExportObject(ExportObject):
        def __init__(self, Config, Exporter, BlenderObject):
            ExportObject.__init__(self, Config, Exporter, BlenderObject)
    
        def __repr__(self):
            return "[ArmatureExportObject: {}]".format(self.name)
        
        # "Public" Interface
    
        def Write(self):
            self.Exporter.Log("Opening frame for {}".format(self))
            self._OpenFrame()
            
            if self.Config.ExportArmatureBones:
                Armature = self.BlenderObject.data
                RootBones = [Bone for Bone in Armature.bones if Bone.parent is None]
                self.Exporter.Log("Writing frames for armature bones...")
                self.__WriteBones(RootBones)
                self.Exporter.Log("Done")
    
            self.Exporter.Log("Writing children of {}".format(self))
            self._WriteChildren()
    
            self._CloseFrame()
            self.Exporter.Log("Closed frame of {}".format(self))
        
        # "Private" Methods
        
        def __WriteBones(self, Bones):
            # Simply export the frames for each bone.  Export in rest position or
            # posed position depending on options.
            for Bone in Bones:
                BoneMatrix = Matrix()
                
                if self.Config.ExportRestBone:
                    if Bone.parent:
                        BoneMatrix = Bone.parent.matrix_local.inverted()
                    BoneMatrix *= Bone.matrix_local
                else:
                    PoseBone = self.BlenderObject.pose.bones[Bone.name]
                    if Bone.parent:
                        BoneMatrix = PoseBone.parent.matrix.inverted()
                    BoneMatrix *= PoseBone.matrix
                
                BoneSafeName = self.SafeName + "_" + \
                    Util.SafeName(Bone.name)
                self.__OpenBoneFrame(BoneSafeName, BoneMatrix)
                
                self.__WriteBoneChildren(Bone)
                
                self.__CloseBoneFrame(BoneSafeName)
                
        
        def __OpenBoneFrame(self, BoneSafeName, BoneMatrix):
            self.Exporter.File.Write("Frame {} {{\n".format(BoneSafeName))
            self.Exporter.File.Indent()
    
            self.Exporter.File.Write("FrameTransformMatrix {\n")
            self.Exporter.File.Indent()
            Util.WriteMatrix(self.Exporter.File, BoneMatrix)
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}\n")
        
        def __CloseBoneFrame(self, BoneSafeName):
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}} // End of {}\n".format(BoneSafeName))
        
        def __WriteBoneChildren(self, Bone):
            self.__WriteBones(Util.SortByNameField(Bone.children))
    
    
    # Container for animation data
    class Animation:
        def __init__(self, SafeName):
            self.SafeName = SafeName
            
            self.RotationKeys = []
            self.ScaleKeys = []
            self.PositionKeys = []
            
        # "Public" Interface
        
        def GetKeyCount(self):
            return len(self.RotationKeys)
    
    
    # Creates a list of Animation objects based on the animation needs of the
    # ExportObject passed to it
    class AnimationGenerator: # Base class, do not use
        def __init__(self, Config, SafeName, ExportObject):
            self.Config = Config
            self.SafeName = SafeName
            self.ExportObject = ExportObject
            
            self.Animations = []
    
    
    # Creates one Animation object that contains the rotation, scale, and position
    # of the ExportObject
    class GenericAnimationGenerator(AnimationGenerator):
        def __init__(self, Config, SafeName, ExportObject):
            AnimationGenerator.__init__(self, Config, SafeName, ExportObject)
            
            self._GenerateKeys()
        
        # "Protected" Interface
        
        def _GenerateKeys(self):
            Scene = bpy.context.scene # Convenience alias
            BlenderCurrentFrame = Scene.frame_current
            
            CurrentAnimation = Animation(self.ExportObject.SafeName)
            BlenderObject = self.ExportObject.BlenderObject
            
            for Frame in range(Scene.frame_start, Scene.frame_end + 1):
                Scene.frame_set(Frame)
                
                Rotation = BlenderObject.rotation_euler.to_quaternion()
                Scale = BlenderObject.matrix_local.to_scale()
                Position = BlenderObject.matrix_local.to_translation()
                
                CurrentAnimation.RotationKeys.append(Rotation)
                CurrentAnimation.ScaleKeys.append(Scale)
                CurrentAnimation.PositionKeys.append(Position)
            
            self.Animations.append(CurrentAnimation)
            Scene.frame_set(BlenderCurrentFrame)