Skip to content
Snippets Groups Projects
io_export_marmalade.py 61.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ***** GPL LICENSE BLOCK *****
    #
    # This program is free software: you can redistribute it and/or modify
    # it under the terms of the GNU General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this program.  If not, see <http://www.gnu.org/licenses/>.
    # All rights reserved.
    # ***** GPL LICENSE BLOCK *****
    
    # Marmalade SDK is not responsible in any case of the following code.
    # This Blender add-on is freely shared for the Blender and Marmalade user communities.
    
    
    bl_info = {
        "name": "Marmalade Cross-platform Apps (.group)",
        "author": "Benoit Muller",
    
        "blender": (2, 63, 0),
    
        "location": "File > Export > Marmalade cross-platform Apps (.group)",
        "description": "Export Marmalade Format files (.group)",
        "warning": "",
    
        "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
    
            "Scripts/Import-Export/Marmalade_Exporter",
    
        "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
    
        "category": "Import-Export"}
    
    import os
    import shutil
    from math import radians
    
    import bpy
    from mathutils import Matrix
    
    import mathutils
    import math
    
    import datetime
    
    import subprocess
    
    
    #Container for the exporter settings
    class MarmaladeExporterSettings:
    
        def __init__(self,
                     context,
                     FilePath,
                     CoordinateSystem=1,
                     FlipNormals=False,
                     ApplyModifiers=False,
                     Scale=100,
                     AnimFPS=30,
                     ExportVertexColors=True,
                     ExportMaterialColors=True,
                     ExportTextures=True,
                     CopyTextureFiles=True,
                     ExportArmatures=False,
    
                     ExportAnimationFrames=0,
                     ExportAnimationActions=0,
    
                     ExportMode=1,
                     MergeModes=0,
                     Verbose=False):
            self.context = context
            self.FilePath = FilePath
            self.CoordinateSystem = int(CoordinateSystem)
            self.FlipNormals = FlipNormals
            self.ApplyModifiers = ApplyModifiers
            self.Scale = Scale
            self.AnimFPS = AnimFPS
            self.ExportVertexColors = ExportVertexColors
            self.ExportMaterialColors = ExportMaterialColors
            self.ExportTextures = ExportTextures
            self.CopyTextureFiles = CopyTextureFiles
            self.ExportArmatures = ExportArmatures
    
            self.ExportAnimationFrames = int(ExportAnimationFrames)
            self.ExportAnimationActions = int(ExportAnimationActions)
    
            self.ExportMode = int(ExportMode)
            self.MergeModes = int(MergeModes)
            self.Verbose = Verbose
    
    
    
    def ExportMadeWithMarmaladeGroup(Config):
        print("----------\nExporting to {}".format(Config.FilePath))
        if Config.Verbose:
            print("Opening File...")
        Config.File = open(Config.FilePath, "w")
    
        if Config.Verbose:
            print("Done")
    
        if Config.Verbose:
            print("writing group header")
    
        Config.File.write('// Marmalade group file exported from : %s\n' % bpy.data.filepath)
        Config.File.write('// Exported %s\n' % str(datetime.datetime.now()))
        Config.File.write("CIwResGroup\n{\n\tname \"%s\"\n" % bpy.path.display_name_from_filepath(Config.FilePath))
    
        if Config.Verbose:
            print("Generating Object list for export... (Root parents only)")
        if Config.ExportMode == 1:
            Config.ExportList = [Object for Object in Config.context.scene.objects
                                 if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}
                                 and Object.parent is None]
        else:
            ExportList = [Object for Object in Config.context.selected_objects
                          if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}]
            Config.ExportList = [Object for Object in ExportList
                                 if Object.parent not in ExportList]
        if Config.Verbose:
            print("  List: {}\nDone".format(Config.ExportList))
    
        if Config.Verbose:
            print("Setting up...")
    
    
            if Config.Verbose:
                print(bpy.context.scene)
                print(bpy.context.scene.frame_current)
            CurrentFrame = bpy.context.scene.frame_current
        if Config.Verbose:
            print("Done")
    
        Config.ObjectList = []
        if Config.Verbose:
            print("Writing Objects...")
        WriteObjects(Config, Config.ExportList)
        if Config.Verbose:
            print("Done")
    
        if Config.Verbose:
            print("Objects Exported: {}".format(Config.ExportList))
    
    
            if Config.Verbose:
                print("Writing Animation...")
    
            WriteKeyedAnimationSet(Config, bpy.context.scene)
            bpy.context.scene.frame_current = CurrentFrame
    
            if Config.Verbose:
                print("Done")
        Config.File.write("}\n")
        CloseFile(Config)
        print("Finished")
    
    
    def GetObjectChildren(Parent):
        return [Object for Object in Parent.children
                if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}]
    
    
    #Returns the file path of first image texture from Material.
    def GetMaterialTextureFullPath(Config, Material):
        if Material:
            #Create a list of Textures that have type "IMAGE"
            ImageTextures = [Material.texture_slots[TextureSlot].texture for TextureSlot in Material.texture_slots.keys() if Material.texture_slots[TextureSlot].texture.type == "IMAGE"]
            #Refine a new list with only image textures that have a file source
            TexImages = [Texture.image for Texture in ImageTextures if getattr(Texture.image, "source", "") == "FILE"]
            ImageFiles = [Texture.image.filepath for Texture in ImageTextures if getattr(Texture.image, "source", "") == "FILE"]
            if TexImages:
                filepath = TexImages[0].filepath
                if TexImages[0].packed_file:
                    TexImages[0].unpack()
                if not os.path.exists(filepath):
                    #try relative path to the blend file
                    filepath = os.path.dirname(bpy.data.filepath) + filepath
                #Marmalade doesn't like jpeg/tif so try to convert in png on the fly
                if (TexImages[0].file_format == 'JPEG' or TexImages[0].file_format == 'TIFF') and os.path.exists(filepath):
                    marmaladeConvert = os.path.expandvars("%S3E_DIR%\\..\\tools\\ImageMagick\\win32\\convert.exe")
                    if (os.path.exists(marmaladeConvert)):
                        srcImagefilepath = filepath
                        filepath = os.path.splitext(filepath)[0] + '.png'
                        if Config.Verbose:
                            print("  /!\\ Converting Texture %s in PNG: %s{}..." % (TexImages[0].file_format, filepath))
                            print('"%s" "%s" "%s"' % (marmaladeConvert, srcImagefilepath, filepath))
                        subprocess.call([marmaladeConvert, srcImagefilepath, filepath])
                return filepath
        return None
    
    
    def WriteObjects(Config, ObjectList, geoFile=None, mtlFile=None, GeoModel=None,  bChildObjects=False):
        Config.ObjectList += ObjectList
    
        if bChildObjects == False and Config.MergeModes > 0:
            if geoFile == None:
                #we merge objects, so use name of group file for the name of Geo
                geoFile, mtlFile = CreateGeoMtlFiles(Config, bpy.path.display_name_from_filepath(Config.FilePath))
                GeoModel = CGeoModel(bpy.path.display_name_from_filepath(Config.FilePath))
    
        for Object in ObjectList:
            if Config.Verbose:
                print("  Writing Object: {}...".format(Object.name))
    
    
            if Config.ExportArmatures and Object.type == "ARMATURE":
    
                Armature = Object.data
                ParentList = [Bone for Bone in Armature.bones if Bone.parent is None]
                if Config.Verbose:
                    print("    Writing Armature Bones...")
                #Create the skel file
    
                skelfullname = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "%s.skel" % (StripName(Object.name))
    
                ensure_dir(skelfullname)
                if Config.Verbose:
                    print("      Creating skel file %s" % (skelfullname))
    
                skelFile = open(skelfullname, "w")
    
                skelFile.write('// skel file exported from : %r\n' % os.path.basename(bpy.data.filepath))
    
                skelFile.write("CIwAnimSkel\n")
                skelFile.write("{\n")
                skelFile.write("\tnumBones %d\n" % (len(Armature.bones)))
                Config.File.write("\t\".\models\%s.skel\"\n" % (StripName(Object.name)))
    
                WriteArmatureParentRootBones(Config, Object, ParentList, skelFile)
    
                skelFile.write("}\n")
                skelFile.close()
                if Config.Verbose:
                    print("    Done")
    
            ChildList = GetObjectChildren(Object)
            if Config.ExportMode == 2:  # Selected Objects Only
                ChildList = [Child for Child in ChildList
                             if Child in Config.context.selected_objects]
            if Config.Verbose:
                print("    Writing Children...")
            WriteObjects(Config, ChildList, geoFile, mtlFile, GeoModel, True)
            if Config.Verbose:
                print("    Done Writing Children")
    
            if Object.type == "MESH":
                if Config.Verbose:
                    print("    Generating Mesh...")
                if Config.ApplyModifiers:
                    if Config.ExportArmatures:
                        #Create a copy of the object and remove all armature modifiers so an unshaped
                        #mesh can be created from it.
                        Object2 = Object.copy()
                        for Modifier in [Modifier for Modifier in Object2.modifiers if Modifier.type == "ARMATURE"]:
                            Object2.modifiers.remove(Modifier)
                        Mesh = Object2.to_mesh(bpy.context.scene, True, "PREVIEW")
                    else:
                        Mesh = Object.to_mesh(bpy.context.scene, True, "PREVIEW")
                else:
                    Mesh = Object.to_mesh(bpy.context.scene, False, "PREVIEW")
                if Config.Verbose:
                    print("    Done")
                    print("    Writing Mesh...")
    
                # Flip ZY axis (Blender Z up: Marmalade: Y up) ans Scale appropriately
                X_ROT = mathutils.Matrix.Rotation(-math.pi / 2, 4, 'X')
    
                if Config.MergeModes == 0:
                    # No merge, so all objects are exported in MODEL SPACE and not in world space
                    # Calculate Scale of the Export
                    meshScale = Object.matrix_world.to_scale()  # Export is working, even if user doesn't have use apply scale in Edit mode.
    
                    scalematrix = Matrix()
                    scalematrix[0][0] = meshScale.x * Config.Scale
                    scalematrix[1][1] = meshScale.y * Config.Scale
                    scalematrix[2][2] = meshScale.z * Config.Scale
    
    
                    meshRot = Object.matrix_world.to_quaternion()  # Export is working, even if user doesn't have use apply Rotation in Edit mode.
                    Mesh.transform(X_ROT * meshRot.to_matrix().to_4x4() * scalematrix)
    
                else:
                    # In Merge mode, we need to keep relative postion of each objects, so we export in WORLD SPACE
                    SCALE_MAT = mathutils.Matrix.Scale(Config.Scale, 4)
    
                    Mesh.transform(SCALE_MAT * X_ROT * Object.matrix_world)
    
    
                 # manage merge options
    
                if Config.MergeModes == 0:
                    #one geo per Object, so use name of Object for the Geo file
                    geoFile, mtlFile = CreateGeoMtlFiles(Config, StripName(Object.name))
    
                    GeoModel = CGeoModel(StripName(Object.name))
    
                # Write the Mesh in the Geo file
    
                WriteMesh(Config, Object, Mesh, geoFile, mtlFile, GeoModel)
    
                if Config.MergeModes == 0:
                    # no merge so finalize the file, and discard the file and geo class
                    FinalizeGeoMtlFiles(Config, geoFile, mtlFile)
                    geoFile = None
                    mtlFile = None
                    GeoModel = None
                elif Config.MergeModes == 1:
                    # merge in one Mesh, so keep the Geo class and prepare to change object
    
                elif Config.MergeModes == 2:
                    # merge several Meshes in one file: so clear the mesh data that we just written in the file,
                    # but keep Materials info that need to be merged across objects
                    GeoModel.ClearAllExceptMaterials()
    
                if Config.Verbose:
                    print("    Done")
    
                if Config.ApplyModifiers and Config.ExportArmatures:
                    bpy.data.objects.remove(Object2)
                bpy.data.meshes.remove(Mesh)
    
            if Config.Verbose:
                print("  Done Writing Object: {}".format(Object.name))
    
        if bChildObjects == False:
            # we have finish to do all objects
            if GeoModel:
                if Config.MergeModes == 1:
                    # we have Merges all objects in one Mesh, so time to write this big mesh in the file
                    GeoModel.PrintGeoMesh(geoFile)
                    # time to write skinfile if any
                    if len(GeoModel.useBonesDict) > 0:
    
                        # some mesh was not modified by the armature. so we must skinned the merged mesh.
                        # So unskinned vertices from unarmatured meshes, are assigned to the root bone of the armature
                        for i in range(0, len(GeoModel.vList)):
                            if not i in GeoModel.skinnedVertices:
                                GeoModel.skinnedVertices.append(i)
                                useBonesKey = pow(2, GeoModel.armatureRootBoneIndex)
                                vertexGroupIndices = list((GeoModel.armatureRootBoneIndex,))
    
                                if useBonesKey not in GeoModel.useBonesDict:
    
                                    GeoModel.mapVertexGroupNames[GeoModel.armatureRootBoneIndex] = StripBoneName(GeoModel.armatureRootBone.name)
                                    VertexList = []
                                    VertexList.append("\t\tvertWeights { %d, 1.0}" % i)
                                    GeoModel.useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
                                else:
                                    pair_ListGroupIndices_ListAssignedVertices = GeoModel.useBonesDict[useBonesKey]
                                    pair_ListGroupIndices_ListAssignedVertices[1].append("\t\tvertWeights { %d, 1.0}" % i)
                                    GeoModel.useBonesDict[useBonesKey] = pair_ListGroupIndices_ListAssignedVertices
                        # now generates the skin file
    
                        PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, GeoModel.name)
                if Config.MergeModes > 0:
                    WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel)
                    FinalizeGeoMtlFiles(Config, geoFile, mtlFile)
            geoFile = None
            mtlFile = None
            GeoModel = None
    
    
    def CreateGeoMtlFiles(Config, Name):
        #Create the geo file
    
        geofullname = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "%s.geo" % Name
    
        ensure_dir(geofullname)
        if Config.Verbose:
    
            print("      Creating geo file %s" % (geofullname))
    
        geoFile = open(geofullname, "w")
        geoFile.write('// geo file exported from : %r\n' % os.path.basename(bpy.data.filepath))
        geoFile.write("CIwModel\n")
        geoFile.write("{\n")
        geoFile.write("\tname \"%s\"\n" % Name)
        # add it to the group
        Config.File.write("\t\".\models\%s.geo\"\n" % Name)
    
        # Create the mtl file
    
        mtlfullname = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "%s.mtl" % Name
    
        ensure_dir(mtlfullname)
        if Config.Verbose:
            print("      Creating mtl file %s" % (mtlfullname))
        mtlFile = open(mtlfullname, "w")
    
        mtlFile.write('// mtl file exported from : %r\n' % os.path.basename(bpy.data.filepath))
    
        return geoFile, mtlFile
    
    
    def FinalizeGeoMtlFiles(Config, geoFile, mtlFile):
        if Config.Verbose:
    
        geoFile.write("}\n")
        geoFile.close()
        if Config.Verbose:
    
        mtlFile.close()
    
    
    def WriteMesh(Config, Object, Mesh,  geoFile=None, mtlFile=None, GeoModel=None):
        if geoFile == None or mtlFile == None:
            print (" ERROR not geo file arguments in WriteMesh method")
            return
    
    
        if GeoModel == None:
            print (" ERROR not GeoModel arguments in WriteMesh method")
            return
    
        BuildOptimizedGeo(Config, Object, Mesh, GeoModel)
        if Config.MergeModes == 0 or Config.MergeModes == 2:
            #if we don't merge, or if we write several meshes into one file ... write the mesh everytime we do an object
            GeoModel.PrintGeoMesh(geoFile)
    
        if Config.Verbose:
            print("      Done\n      Writing Mesh Materials...")
    
        if Config.MergeModes == 0:
            #No merge, so we can diretly write the Mtl file associated to this object
    
            WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel)
    
    
        if Config.Verbose:
            print("      Done")
    
        if Config.ExportArmatures:
            if Config.Verbose:
    
                print("      Writing Mesh Weights...")
            WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel)
    
            if Config.Verbose:
                print("      Done")
    
    
    ###### optimized version fo Export, can be used also to merge several object in one single geo File ######
    
    # CGeoModel
    #  -> List Vertices
    #  -> List Normales
    #  -> List uv 0
    #  -> List uv 1
    #  -> List Vertex Colors
    #  -> List Materials
    #       -> Material name
    #       -> Blender Material Object
    #       -> List Tris -> Stream Indices v,vn,uv0,uv1,vc
    #       -> List Quads -> Stream Indices v,vn,uv0,uv1,vc
    
    
    #############
    #Store one Point of a Quad or Tri in marmalade geo format: //index-list is: { <int> <int> <int> <int> <int> }   //v,vn,uv0,uv1,vc
    
    class CGeoIndexList:
    
        __slots__ = "v", "vn", "uv0", "uv1", "vc"
    
        def __init__(self, v, vn, uv0, uv1, vc):
            self.v = v
            self.vn = vn
            self.uv0 = uv0
            self.uv1 = uv1
            self.vc = vc
    
    
    #############
    #Store a Quad or a Tri in marmalade geo format : 3 or 4 CIndexList depending it is a Tri or a Quad
    
        __slots__ = "pointsList",
    
        def __init__(self):
    
    
        def AddPoint(self, v, vn, uv0, uv1, vc):
            self.pointsList.append( CGeoIndexList(v, vn, uv0, uv1, vc))
    
        def PointsCount(self):
            return len(self.pointsList)
    
        def PrintPoly(self, geoFile):
            if len(self.pointsList) == 3:
                geoFile.write("\t\t\t\tt ")
            if len(self.pointsList) == 4:
                geoFile.write("\t\t\t\tq ")
            for point in self.pointsList:
                geoFile.write(" {%d, %d, %d, %d, %d}" % (point.v, point.vn, point.uv0, point.uv1, point.vc))
            geoFile.write("\n")
    
    
    #############
    
    #Store all the poly (tri or quad) assigned to a Material in marmalade geo format
    
    class CGeoMaterialPolys:
    
        __slots__ = "name", "material", "quadList", "triList", "currentPoly"
    
        def __init__(self, name, material=None):
            self.name = name
            self.material = material
    
            self.quadList = []
            self.triList = []
    
            self.currentPoly = None
    
        def BeginPoly(self):
            self.currentPoly = CGeoPoly()
    
        def AddPoint(self, v, vn, uv0, uv1, vc):
    
        def EndPoly(self):
            if (self.currentPoly.PointsCount() == 3):
                self.triList.append(self.currentPoly)
            if (self.currentPoly.PointsCount() == 4):
                self.quadList.append(self.currentPoly)
            self.currentPoly = None
    
        def ClearPolys(self):
    
            self.quadList = []
            self.triList = []
    
            self.currentPoly = None
    
        def PrintMaterialPolys(self, geoFile):
            geoFile.write("\t\tCSurface\n")
            geoFile.write("\t\t{\n")
            geoFile.write("\t\t\tmaterial \"%s\"\n" % self.name)
    
                geoFile.write("\t\t\tCTris\n")
                geoFile.write("\t\t\t{\n")
                geoFile.write("\t\t\t\tnumTris %d\n" % (len(self.triList)))
                for poly in self.triList:
                    poly.PrintPoly(geoFile)
                geoFile.write("\t\t\t}\n")
    
    
            if self.quadList:
    
                geoFile.write("\t\t\tCQuads\n")
                geoFile.write("\t\t\t{\n")
                geoFile.write("\t\t\t\tnumQuads %d\n" % (len(self.quadList)))
                for poly in self.quadList:
                    poly.PrintPoly(geoFile)
                geoFile.write("\t\t\t}\n")
            geoFile.write("\t\t}\n")
    
    
    #############
    #Store all the information on a Model/Mesh (vertices, normal, certcies color, uv0, uv1, TRI, QUAD) in marmalade geo format
    
        __slots__ = ("name", "MaterialsDict", "vList", "vnList", "vcList", "uv0List", "uv1List",
                    "currentMaterialPolys", "vbaseIndex","vnbaseIndex", "uv0baseIndex", "uv1baseIndex",
    
                    "armatureObjectName", "useBonesDict", "mapVertexGroupNames", "armatureRootBone", "armatureRootBoneIndex", "skinnedVertices")
    
        def __init__(self, name):
            self.name = name
    
            self.MaterialsDict = {}
            self.vList = []
            self.vnList = []
            self.vcList = []
            self.uv0List = []
            self.uv1List = []
    
            self.currentMaterialPolys = None
            #used xx baseIndex are used when merging several blender objects into one Mesh in the geo file (internal offset)
            self.vbaseIndex = 0
            self.vnbaseIndex = 0
            self.uv0baseIndex = 0
            self.uv1baseIndex = 0
    
            # Store some information for skin management , when we merge several object in one big mesh (MergeModes 1)
            # can only work if in the object list only one is rigged with an armature... and if it is located in 0,0,0
            self.armatureObjectName = ""
            #useBonesKey : bit field, where each bit is a VertexGroup.Index): Sum(2^VertGroupIndex).
            #useBonesDict[useBonesKey] = tuple(VertexGroups.group, list(Vertex))
    
            self.useBonesDict = {}
            self.mapVertexGroupNames = {}
    
            self.armatureRootBone = None
            self.armatureRootBoneIndex = 0
            self.skinnedVertices = []
    
    
    
    
        def AddVertex(self, vertex):
            self.vList.append(vertex.copy())
    
        def AddVertexNormal(self, vertexN):
            self.vnList.append(vertexN.copy())
    
        # add a uv coordiantes and return the current Index in the stream (index is local to the object, when we merge several object into a one Mesh)
        def AddVertexUV0(self, u, v):
            self.uv0List.append((u, v))
    
            return len(self.uv0List) - 1 - self.uv0baseIndex
    
    
        def AddVertexUV1(self, u, v):
            self.uv1List.append((u, v))
    
            return len(self.uv1List) - 1 - self.uv1baseIndex
    
    
        # add a vertexcolor if it doesn't already exist and return the current Index in the stream (index is global to all objects, when we merge several object into a one Mesh)
        def AddVertexColor(self, r, g, b, a):
            for i in range(0, len(self.vcList)):
                col = self.vcList[i]
                if col[0] == r and col[1] == g and col[2] == b and col[3] == a:
                    return i
    
            self.vcList.append((r, g, b, a))
            return len(self.vcList)-1
    
        def BeginPoly(self, MaterialName, material=None):
            if MaterialName not in self.MaterialsDict:
                self.currentMaterialPolys = CGeoMaterialPolys(MaterialName, material)
            else:
                self.currentMaterialPolys = self.MaterialsDict[MaterialName]
            self.currentMaterialPolys.BeginPoly()
    
        def AddPoint(self, v, vn, uv0, uv1, vc):
            if v != -1:
                v += self.vbaseIndex
            if vn != -1:
                vn += self.vnbaseIndex
            if uv0 != -1:
                uv0 += self.uv0baseIndex
            if uv1 != -1:
                uv1 += self.uv1baseIndex
    
    
            self.currentMaterialPolys.AddPoint(v, vn, uv0, uv1, vc)
    
    
        def EndPoly(self):
            self.currentMaterialPolys.EndPoly()
            self.MaterialsDict[self.currentMaterialPolys.name] = self.currentMaterialPolys
            self.currentMaterialPolys = None
    
        def NewObject(self):
            #used in Merge mode 1: allows to merge several blender objects into one Mesh.
            self.vbaseIndex = len(self.vList)
            self.vnbaseIndex = len(self.vnList)
            self.uv0baseIndex = len(self.uv0List)
            self.uv1baseIndex = len(self.uv1List)
    
        def ClearAllExceptMaterials(self):
            #used in Merge mode 2: one geo with several mesh
    
            self.vList = []
            self.vnList = []
            self.vcList = []
            self.uv0List = []
            self.uv1List = []
    
            self.currentMaterialPolys = None
            self.vbaseIndex = 0
            self.vnbaseIndex = 0
            self.uv0baseIndex = 0
            self.uv1baseIndex = 0
            for GeoMaterialPolys in self.MaterialsDict.values():
                GeoMaterialPolys.ClearPolys()
    
            self.useBonesDict = {}
            self.mapVertexGroupNames = {}
    
            self.armatureObjectName = ""
    
            self.armatureRootBone = None
            self.armatureRootBoneIndex = 0
            self.skinnedVertices = []
    
    
        def PrintGeoMesh(self, geoFile):
            geoFile.write("\tCMesh\n")
            geoFile.write("\t{\n")
            geoFile.write("\t\tname \"%s\"\n" % (StripName(self.name)))
    
    
                geoFile.write("\t\tCVerts\n")
                geoFile.write("\t\t{\n")
                geoFile.write("\t\t\tnumVerts %d\n" % len(self.vList))
                for vertex in self.vList:
    
                    geoFile.write("\t\t\tv { %.9f, %.9f, %.9f }\n" % (vertex[0], vertex[1], vertex[2]))
    
                geoFile.write("\t\t}\n")
    
    
                geoFile.write("\t\tCVertNorms\n")
                geoFile.write("\t\t{\n")
                geoFile.write("\t\t\tnumVertNorms  %d\n" % len(self.vnList))
                for vertexn in self.vnList:
    
                    geoFile.write("\t\t\tvn { %.9f, %.9f, %.9f }\n" % (vertexn[0], vertexn[1], vertexn[2]))
    
                geoFile.write("\t\t}\n")
    
    
                geoFile.write("\t\tCVertCols\n")
                geoFile.write("\t\t{\n")
                geoFile.write("\t\t\tnumVertCols %d\n" % len(self.vcList))
                for color in self.vcList:
    
                    geoFile.write("\t\t\tcol { %.6f, %.6f, %.6f, %.6f }\n" % (color[0], color[1], color[2], color[3])) #alpha is not supported on blender for vertex colors
    
                geoFile.write("\t\t}\n")
    
    
                geoFile.write("\t\tCUVs\n")
                geoFile.write("\t\t{\n")
                geoFile.write("\t\t\tsetID 0\n")
                geoFile.write("\t\t\tnumUVs %d\n" % len(self.uv0List))
                for uv in self.uv0List:
    
                     geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (uv[0], uv[1]))
    
                geoFile.write("\t\t}\n")
    
    
                geoFile.write("\t\tCUVs\n")
                geoFile.write("\t\t{\n")
                geoFile.write("\t\t\tsetID 1\n")
                geoFile.write("\t\t\tnumUVs %d\n" % len(self.uv1List))
                for uv in self.uv1List:
    
                     geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (uv[0], uv[1]))
    
                geoFile.write("\t\t}\n")
    
            for GeoMaterialPolys in self.MaterialsDict.values():
                GeoMaterialPolys.PrintMaterialPolys(geoFile)
            geoFile.write("\t}\n")
    
        def GetMaterialList(self):
    
            return list(self.MaterialsDict.keys())
    
    
        def GetMaterialByName(self, name):
            if name in self.MaterialsDict:
                return self.MaterialsDict[name].material
            else:
    
    
    
    
    #############
    # iterates faces, vertices ... and store the information in the GeoModel container
    def BuildOptimizedGeo(Config, Object, Mesh, GeoModel):
        if GeoModel == None:
            GeoModel = CGeoModel(filename, Object.name)
    
    
        #Ensure tessfaces data are here
        Mesh.update (calc_tessface=True)
    
        #Store Vertex stream, and Normal stream (use directly the order from blender collection
        for Vertex in Mesh.vertices:
            GeoModel.AddVertex(Vertex.co)
            Normal = Vertex.normal
            if Config.FlipNormals:
                Normal = -Normal
            GeoModel.AddVertexNormal(Normal)
        #Check if some colors have been defined
    
        vertexColors = None
    
        if Config.ExportVertexColors and (len(Mesh.vertex_colors) > 0):
    
            vertexColors = Mesh.tessface_vertex_colors[0].data
    
    
        #Check if some uv coordinates have been defined
        UVCoordinates = None
        if Config.ExportTextures and (len(Mesh.uv_textures) > 0):
    
            for UV in Mesh.tessface_uv_textures:
    
                if UV.active_render:
                    UVCoordinates = UV.data
                    break
    
        #Iterate on Faces and Store the poly (quad or tri) and the associate colors,UVs
    
        for Face in Mesh.tessfaces:
    
            # stream for vertex (we use the same for normal)
            Vertices = list(Face.vertices)
            if Config.CoordinateSystem == 1:
                Vertices = Vertices[::-1]
            # stream for vertex colors
    
            if vertexColors:
                MeshColor = vertexColors[Face.index]
    
                if len(Vertices) == 3:
                    FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3))
                else:
                    FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3, MeshColor.color4))
                if Config.CoordinateSystem == 1:
                    FaceColors = FaceColors[::-1]
    
                for color in FaceColors:
                    index = GeoModel.AddVertexColor(color[0], color[1], color[2], 1)  #rgba => no alpha on vertex color in Blender so use 1
                    colorIndex.append(index)
            else:
                colorIndex = list((-1,-1,-1,-1))
    
            # stream for UV0 coordinates
            if UVCoordinates:
                uvFace = UVCoordinates[Face.index]
                uvVertices = []
                for uvVertex in uvFace.uv:
                    uvVertices.append(tuple(uvVertex))
                if Config.CoordinateSystem == 1:
                    uvVertices = uvVertices[::-1]
    
                for uvVertex in uvVertices:
    
                    index = GeoModel.AddVertexUV0(uvVertex[0], 1 - uvVertex[1])
    
                    uv0Index.append(index)
            else:
                uv0Index = list((-1, -1, -1, -1))
    
            # stream for UV1 coordinates
            uv1Index = list((-1, -1, -1, -1))
    
            mat = None
            # find the associated material
            if Face.material_index < len(Mesh.materials):
                mat = Mesh.materials[Face.material_index]
            if mat:
                matName =  mat.name
            else:
    
                matName = "NoMaterialAssigned"  # There is no material assigned in blender !!!, exporter have generated a default one
    
    
            # now on the material, generates the tri/quad in v,vn,uv0,uv1,vc stream index
            GeoModel.BeginPoly(matName, mat)
    
            for i in range(0, len(Vertices)):
                GeoModel.AddPoint(Vertices[i], Vertices[i], uv0Index[i], uv1Index[i], colorIndex[i])
    
            GeoModel.EndPoly()
    
    
    #############
    # Get the list of Material in use by the CGeoModel
    def WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel):
        for matName in GeoModel.GetMaterialList():
            Material = GeoModel.GetMaterialByName(matName)
            WriteMaterial(Config, mtlFile, Material)
    
    
    def WriteMaterial(Config, mtlFile, Material=None):
        mtlFile.write("CIwMaterial\n")
        mtlFile.write("{\n")
        if Material:
            mtlFile.write("\tname \"%s\"\n" % Material.name)
    
            if Config.ExportMaterialColors:
                #if bpy.context.scene.world:
                #    MatAmbientColor = Material.ambient * bpy.context.scene.world.ambient_color
                MatAmbientColor = Material.ambient * Material.diffuse_color
    
                mtlFile.write("\tcolAmbient {%.2f,%.2f,%.2f,%.2f} \n" % (min(255, MatAmbientColor[0] * 255), min(255, MatAmbientColor[1] * 255), min(255, MatAmbientColor[2] * 255), min(255, Material.alpha * 255)))
                MatDiffuseColor = 255 * Material.diffuse_intensity * Material.diffuse_color
                MatDiffuseColor = min((255, 255, 255)[:],MatDiffuseColor[:])
                mtlFile.write("\tcolDiffuse  {%.2f,%.2f,%.2f} \n" % (MatDiffuseColor[:]))
                MatSpecularColor = 255 * Material.specular_intensity * Material.specular_color
                MatSpecularColor = min((255, 255, 255)[:],MatSpecularColor[:])
                mtlFile.write("\tcolSpecular  {%.2f,%.2f,%.2f} \n" % (MatSpecularColor[:]))
    
                # EmitColor = Material.emit * Material.diffuse_color
    
                # mtlFile.write("\tcolEmissive {%.2f,%.2f,%.2f} \n" % (EmitColor* 255)[:])
    
        else:
            mtlFile.write("\tname \"NoMaterialAssigned\" // There is no material assigned in blender !!!, exporter have generated a default one\n")
    
        #Copy texture
        if Config.ExportTextures:
            Texture = GetMaterialTextureFullPath(Config, Material)
            if Texture:
                mtlFile.write("\ttexture0 .\\textures\\%s\n" % (bpy.path.basename(Texture)))
    
                if Config.CopyTextureFiles:
                    if not os.path.exists(Texture):
                        #try relative path to the blend file
                        Texture = os.path.dirname(bpy.data.filepath) + Texture
                    if os.path.exists(Texture):
    
                        textureDest = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "textures" + os.sep + ("%s" % bpy.path.basename(Texture))
    
                        ensure_dir(textureDest)
                        if Config.Verbose:
                            print("      Copying the texture file %s ---> %s" % (Texture, textureDest))
                        shutil.copy(Texture, textureDest)
                    else:
                        if Config.Verbose:
                            print("      CANNOT Copy texture file (not found) %s" % (Texture))
        mtlFile.write("}\n")
    
    
    def GetFirstRootBone(ArmatureObject):
        ArmatureBones = ArmatureObject.data.bones
        ParentBoneList = [Bone for Bone in ArmatureBones if Bone.parent is None]
        if ParentBoneList:
            return ParentBoneList[0]
        return None
    
    
    def GetVertexGroupFromBone(Object, Bone):
        if Bone:
    
            vertexGroupList = [VertexGroup for VertexGroup in Object.vertex_groups  if VertexGroup.name == Bone.name]
            if vertexGroupList:
                return vertexGroupList[0]
    
    def GetBoneListNames(Bones):
        boneList = []
        for Bone in Bones:
            boneList.append(Bone.name)
            boneList += GetBoneListNames(Bone.children)
        return boneList
    
    
    
    def FindUniqueIndexForRootBone(Object, RootVertexGroup):
        if RootVertexGroup:
            return RootVertexGroup.index
        else:
            #If there is not VertexGroup associated to the root bone name, we don't have a vertex index.
            #so use the next available free index
            return len(Object.vertex_groups)
    
    
    def WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel):
        ArmatureList = [Modifier for Modifier in Object.modifiers if Modifier.type == "ARMATURE"]
        if ArmatureList:
            ArmatureObject = ArmatureList[0].object
            if ArmatureObject is None:
                return
    
            RootBone = GetFirstRootBone(ArmatureObject)
            RootVertexGroup = GetVertexGroupFromBone(Object, RootBone)
    
            BoneNames = GetBoneListNames(ArmatureObject.data.bones)
    
    
            GeoModel.armatureObjectName = StripName(ArmatureObject.name)
    
            if RootBone:
                GeoModel.armatureRootBone = RootBone
                GeoModel.armatureRootBoneIndex = FindUniqueIndexForRootBone(Object, RootVertexGroup)
    
            # Marmalade need to declare a vertex per list of affected bones
            # so first we have to get all the combinations of affected bones that exist in the mesh
    
            # to build thoses groups, we build a unique key (like a bit field, where each bit is a VertexGroup.Index): Sum(2^VertGroupIndex)... so we have a unique Number per combinations
    
            for Vertex in Mesh.vertices:
                VertexIndex = Vertex.index + GeoModel.vbaseIndex
    
                AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, VertexIndex, RootBone, RootVertexGroup, BoneNames)
    
                GeoModel.skinnedVertices.append(VertexIndex)
    
    
            if Config.MergeModes != 1:
                # write skin file directly
                PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, StripName(Object.name))
    
    
    
    def PrintSkinWeights(Config, ArmatureObjectName, useBonesDict, mapVertexGroupNames, GeoName):
    
            #Create the skin file
    
            skinfullname = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "%s.skin" % GeoName
    
            ensure_dir(skinfullname)
            if Config.Verbose:
                print("      Creating skin file %s" % (skinfullname))
            skinFile = open(skinfullname, "w")
    
            skinFile.write('// skin file exported from : %r\n' % os.path.basename(bpy.data.filepath))
    
            skinFile.write("CIwAnimSkin\n")
            skinFile.write("{\n")
            skinFile.write("\tskeleton \"%s\"\n" % ArmatureObjectName)
            skinFile.write("\tmodel \"%s\"\n" % GeoName)
    
            # now we have Bones grouped in the dictionary , along with the associated influenced vertex weighting
            # So simply iterate the dictionary
            Config.File.write("\t\".\models\%s.skin\"\n" % GeoName)
            for pair_ListGroupIndices_ListAssignedVertices in useBonesDict.values():
                skinFile.write("\tCIwAnimSkinSet\n")
                skinFile.write("\t{\n")
                skinFile.write("\t\tuseBones {")
                for vertexGroupIndex in pair_ListGroupIndices_ListAssignedVertices[0]:
                    skinFile.write(" %s" % mapVertexGroupNames[vertexGroupIndex])
                skinFile.write(" }\n")
                skinFile.write("\t\tnumVerts %d\n" % len(pair_ListGroupIndices_ListAssignedVertices[1]))
                for VertexWeightString in pair_ListGroupIndices_ListAssignedVertices[1]:
                    skinFile.write(VertexWeightString)
                skinFile.write("\t}\n")
    
            skinFile.write("}\n")
            skinFile.close()
    
    
    
    def AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, useBonesDict, mapVertexGroupNames, VertexIndex, RootBone, RootVertexGroup, BoneNames):
    
        #build useBones
        useBonesKey = 0
    
        vertexGroupIndices = []
    
        weightTotal = 0.0
        if (len(Vertex.groups)) > 4:
            print ("ERROR Vertex %d is influenced by more than 4 bones\n" % (VertexIndex))
        for VertexGroup in Vertex.groups:
    
                groupName = Object.vertex_groups[VertexGroup.group].name
                if groupName in BoneNames:
                    mapVertexGroupNames[VertexGroup.group] = StripBoneName(groupName)
                    if (len(vertexGroupIndices))<4:  #ignore if more 4 bones are influencing the vertex
                        useBonesKey = useBonesKey + pow(2, VertexGroup.group)
                        vertexGroupIndices.append(VertexGroup.group)
                        weightTotal = weightTotal + VertexGroup.weight
    
        if (weightTotal == 0):
    
            bWeightTotZero = True  #avoid divide by zero later on
            if (RootBone):
                if Config.Verbose:
                    print(" Warning Weight is ZERO for vertex %d => Add it to the root bone" % (VertexIndex))
                RootBoneGroupIndex = FindUniqueIndexForRootBone(Object, RootVertexGroup)
                mapVertexGroupNames[RootBoneGroupIndex] = StripBoneName(RootBone.name)
                useBonesKey = pow(2, RootBoneGroupIndex)
                vertexGroupIndices = list((RootBoneGroupIndex,))
    
                weightTotal = 1
    
        else:
            bWeightTotZero = False
    
        if len(vertexGroupIndices) > 0:
            vertexGroupIndices.sort();
    
            #build the vertex weight string: vertex indices, followed by influence weight for each bone
            VertexWeightString = "\t\tvertWeights { %d" % (VertexIndex)
            for vertexGroupIndex in vertexGroupIndices:
                #get the weight of this specific VertexGroup (aka bone)
                boneWeight = 1
                for VertexGroup in Vertex.groups:
                    if VertexGroup.group == vertexGroupIndex:
                        boneWeight = VertexGroup.weight
                #calculate the influence of this bone compared to the total of weighting applied to this Vertex
                if not bWeightTotZero:
                    VertexWeightString += ", %.7f" % (boneWeight / weightTotal)
                else:
                    VertexWeightString += ", %.7f" % (1.0 / len(vertexGroupIndices))
            VertexWeightString += "}"
            if bWeightTotZero:
    
                VertexWeightString += " // total weight was zero in blender , export assign it to the RootBone with weight 1."
    
            if (len(Vertex.groups)) > 4:
                VertexWeightString += " // vertex is associated to more than 4 bones in blender !! skip some bone association (was associated to %d bones)." % (len(Vertex.groups))
            VertexWeightString += "\n"
    
            #store in dictionnary information
            if useBonesKey not in useBonesDict:
    
                VertexList.append(VertexWeightString)
                useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
            else:
                pair_ListGroupIndices_ListAssignedVertices = useBonesDict[useBonesKey]
                pair_ListGroupIndices_ListAssignedVertices[1].append(VertexWeightString)
                useBonesDict[useBonesKey] = pair_ListGroupIndices_ListAssignedVertices
        else:
    
            print ("ERROR Vertex %d is not skinned (it doesn't belong to any vertex group\n" % (VertexIndex))
    
    ############# ARMATURE: Bone export, and Bone animation export
    
    
    
    def WriteArmatureParentRootBones(Config, Object, RootBonesList, skelFile):
    
        if len(RootBonesList) > 1:
            print(" /!\\  WARNING ,Marmelade need only one ROOT bone per armature, there is %d root bones " % len(RootBonesList))
            print(RootBonesList)
    
        PoseBones = Object.pose.bones