Addons:
[blender-addons-contrib.git] / io_export_marmalade.py
1 # ***** GPL LICENSE BLOCK *****
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 # All rights reserved.
16 # ***** GPL LICENSE BLOCK *****
17
18 # Marmalade SDK is not responsible in any case of the following code.
19 # This Blender add-on is freely shared for the Blender and Marmalade user communities.
20
21
22 bl_info = {
23     "name": "Marmalade Cross-platform Apps (.group)",
24     "author": "Benoit Muller",
25     "version": (0, 5, 1),
26     "blender": (2, 6, 0),
27     "location": "File > Export > Marmalade cross-platform Apps (.group)",
28     "description": "Export Marmalade Format files (.group)",
29     "warning": "",
30     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\
31         "Scripts/Import-Export/Marmalade_Exporter",
32     "tracker_url": "https://projects.blender.org/tracker/index.php?"\
33         "",
34     "category": "Import-Export"}
35
36 import os
37 import shutil
38 from math import radians
39
40 import bpy
41 from mathutils import Matrix
42
43 import mathutils
44 import math
45
46 import datetime
47
48 import subprocess
49
50
51 #Container for the exporter settings
52 class MarmaladeExporterSettings:
53
54     def __init__(self,
55                  context,
56                  FilePath,
57                  Optimized=True,
58                  CoordinateSystem=1,
59                  FlipNormals=False,
60                  ApplyModifiers=False,
61                  Scale=100,
62                  AnimFPS=30,
63                  ExportVertexColors=True,
64                  ExportMaterialColors=True,
65                  ExportTextures=True,
66                  CopyTextureFiles=True,
67                  ExportArmatures=False,
68                  ExportAnimation=0,
69                  ExportMode=1,
70                  MergeModes=0,
71                  Verbose=False):
72         self.context = context
73         self.FilePath = FilePath
74         self.Optimized = Optimized
75         self.CoordinateSystem = int(CoordinateSystem)
76         self.FlipNormals = FlipNormals
77         self.ApplyModifiers = ApplyModifiers
78         self.Scale = Scale
79         self.AnimFPS = AnimFPS
80         self.ExportVertexColors = ExportVertexColors
81         self.ExportMaterialColors = ExportMaterialColors
82         self.ExportTextures = ExportTextures
83         self.CopyTextureFiles = CopyTextureFiles
84         self.ExportArmatures = ExportArmatures
85         self.ExportAnimation = int(ExportAnimation)
86         self.ExportMode = int(ExportMode)
87         self.MergeModes = int(MergeModes)
88         self.Verbose = Verbose
89         self.WarningList = []
90
91
92 def ExportMadeWithMarmaladeGroup(Config):
93     print("----------\nExporting to {}".format(Config.FilePath))
94     if Config.Verbose:
95         print("Opening File...")
96     Config.File = open(Config.FilePath, "w")
97
98     if Config.Verbose:
99         print("Done")
100
101     if Config.MergeModes > 0:
102         # Merge mode only work with Optimised setting
103         Config.Optimized = True
104
105     if Config.Verbose:
106         print("writing group header")
107
108     Config.File.write('// Marmalade group file exported from : %s\n' % bpy.data.filepath)
109     Config.File.write('// Exported %s\n' % str(datetime.datetime.now()))
110     Config.File.write("CIwResGroup\n{\n\tname \"%s\"\n" % bpy.path.display_name_from_filepath(Config.FilePath))
111
112     if Config.Verbose:
113         print("Generating Object list for export... (Root parents only)")
114     if Config.ExportMode == 1:
115         Config.ExportList = [Object for Object in Config.context.scene.objects
116                              if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}
117                              and Object.parent is None]
118     else:
119         ExportList = [Object for Object in Config.context.selected_objects
120                       if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}]
121         Config.ExportList = [Object for Object in ExportList
122                              if Object.parent not in ExportList]
123     if Config.Verbose:
124         print("  List: {}\nDone".format(Config.ExportList))
125
126     if Config.Verbose:
127         print("Setting up...")
128
129     if Config.ExportAnimation:
130         if Config.Verbose:
131             print(bpy.context.scene)
132             print(bpy.context.scene.frame_current)
133         CurrentFrame = bpy.context.scene.frame_current
134         #comment because it crashes Blender on some old blend file: bpy.context.scene.frame_current = bpy.context.scene.frame_current
135     if Config.Verbose:
136         print("Done")
137     
138     Config.ObjectList = []
139     if Config.Verbose:
140         print("Writing Objects...")
141     WriteObjects(Config, Config.ExportList)
142     if Config.Verbose:
143         print("Done")
144
145     if Config.Verbose:
146         print("Objects Exported: {}".format(Config.ExportList))
147
148     if Config.ExportAnimation:
149         if Config.Verbose:
150             print("Writing Animation...")
151         WriteKeyedAnimationSet(Config, bpy.context.scene)
152         bpy.context.scene.frame_current = CurrentFrame
153         if Config.Verbose:
154             print("Done")
155     Config.File.write("}\n")
156     CloseFile(Config)
157     print("Finished")
158
159
160 def GetObjectChildren(Parent):
161     return [Object for Object in Parent.children
162             if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}]
163
164
165 #Returns the vertex count of Mesh in not optimized version, counting each vertex for every face.
166 def GetNonOptimizedMeshVertexCount(Mesh):
167     VertexCount = 0
168     for Face in Mesh.faces:
169         VertexCount += len(Face.vertices)
170     return VertexCount
171
172
173 #Returns the file path of first image texture from Material.
174 def GetMaterialTextureFullPath(Config, Material):
175     if Material:
176         #Create a list of Textures that have type "IMAGE"
177         ImageTextures = [Material.texture_slots[TextureSlot].texture for TextureSlot in Material.texture_slots.keys() if Material.texture_slots[TextureSlot].texture.type == "IMAGE"]
178         #Refine a new list with only image textures that have a file source
179         TexImages = [Texture.image for Texture in ImageTextures if getattr(Texture.image, "source", "") == "FILE"]
180         ImageFiles = [Texture.image.filepath for Texture in ImageTextures if getattr(Texture.image, "source", "") == "FILE"]
181         if TexImages:
182             filepath = TexImages[0].filepath
183             if TexImages[0].packed_file:
184                 TexImages[0].unpack()
185             if not os.path.exists(filepath):
186                 #try relative path to the blend file
187                 filepath = os.path.dirname(bpy.data.filepath) + filepath
188             #Marmalade doesn't like jpeg/tif so try to convert in png on the fly
189             if (TexImages[0].file_format == 'JPEG' or TexImages[0].file_format == 'TIFF') and os.path.exists(filepath):
190                 marmaladeConvert = os.path.expandvars("%S3E_DIR%\\..\\tools\\ImageMagick\\win32\\convert.exe")
191                 if (os.path.exists(marmaladeConvert)):
192                     srcImagefilepath = filepath
193                     filepath = os.path.splitext(filepath)[0] + '.png'
194                     if Config.Verbose:
195                         print("  /!\\ Converting Texture %s in PNG: %s{}..." % (TexImages[0].file_format, filepath))
196                         print('"%s" "%s" "%s"' % (marmaladeConvert, srcImagefilepath, filepath))
197                     subprocess.call([marmaladeConvert, srcImagefilepath, filepath])
198             return filepath
199     return None
200
201
202 def WriteObjects(Config, ObjectList, geoFile=None, mtlFile=None, GeoModel=None,  bChildObjects=False):
203     Config.ObjectList += ObjectList
204
205     if bChildObjects == False and Config.MergeModes > 0:
206         if geoFile == None:
207             #we merge objects, so use name of group file for the name of Geo
208             geoFile, mtlFile = CreateGeoMtlFiles(Config, bpy.path.display_name_from_filepath(Config.FilePath))
209             GeoModel = CGeoModel(bpy.path.display_name_from_filepath(Config.FilePath))
210
211     for Object in ObjectList:
212         if Config.Verbose:
213             print("  Writing Object: {}...".format(Object.name))
214         
215         if Config.ExportArmatures and Object.type == "ARMATURE":           
216             Armature = Object.data
217             ParentList = [Bone for Bone in Armature.bones if Bone.parent is None]
218             if Config.Verbose:
219                 print("    Writing Armature Bones...")
220             #Create the skel file
221             skelfullname = os.path.dirname(Config.FilePath) + "\models\%s.skel" % (StripName(Object.name))
222             ensure_dir(skelfullname)
223             if Config.Verbose:
224                 print("      Creating skel file %s" % (skelfullname))
225
226             skelFile = open(skelfullname, "w")
227             skelFile.write('// skel file exported from : %r\n' % os.path.basename(bpy.data.filepath))   
228             skelFile.write("CIwAnimSkel\n")
229             skelFile.write("{\n")
230             skelFile.write("\tnumBones %d\n" % (len(Armature.bones)))
231             Config.File.write("\t\".\models\%s.skel\"\n" % (StripName(Object.name)))
232
233             WriteArmatureParentRootBones(Config, Object, ParentList, skelFile)
234
235             skelFile.write("}\n")
236             skelFile.close()
237             if Config.Verbose:
238                 print("    Done")
239
240         ChildList = GetObjectChildren(Object)
241         if Config.ExportMode == 2:  # Selected Objects Only
242             ChildList = [Child for Child in ChildList
243                          if Child in Config.context.selected_objects]
244         if Config.Verbose:
245             print("    Writing Children...")
246         WriteObjects(Config, ChildList, geoFile, mtlFile, GeoModel, True)
247         if Config.Verbose:
248             print("    Done Writing Children")
249
250         if Object.type == "MESH":
251             if Config.Verbose:
252                 print("    Generating Mesh...")
253             if Config.ApplyModifiers:
254                 if Config.ExportArmatures:
255                     #Create a copy of the object and remove all armature modifiers so an unshaped
256                     #mesh can be created from it.
257                     Object2 = Object.copy()
258                     for Modifier in [Modifier for Modifier in Object2.modifiers if Modifier.type == "ARMATURE"]:
259                         Object2.modifiers.remove(Modifier)
260                     Mesh = Object2.to_mesh(bpy.context.scene, True, "PREVIEW")
261                 else:
262                     Mesh = Object.to_mesh(bpy.context.scene, True, "PREVIEW")
263             else:
264                 Mesh = Object.to_mesh(bpy.context.scene, False, "PREVIEW")
265             if Config.Verbose:
266                 print("    Done")
267                 print("    Writing Mesh...")
268
269             # Flip ZY axis (Blender Z up: Marmalade: Y up) ans Scale appropriately
270             X_ROT = mathutils.Matrix.Rotation(-math.pi / 2, 4, 'X')
271
272             if Config.MergeModes == 0:
273                 # No merge, so all objects are exported in MODEL SPACE and not in world space
274                 # Calculate Scale of the Export
275                 meshScale = Object.matrix_world.to_scale()  # Export is working, even if user doesn't have use apply scale in Edit mode.
276
277                 scalematrix = Matrix()
278                 scalematrix[0][0] = meshScale.x * Config.Scale
279                 scalematrix[1][1] = meshScale.y * Config.Scale
280                 scalematrix[2][2] = meshScale.z * Config.Scale
281
282                 Mesh.transform(scalematrix * X_ROT)
283             else:
284                 # In Merge mode, we need to keep relative postion of each objects, so we export in WORLD SPACE
285                 SCALE_MAT = mathutils.Matrix.Scale(Config.Scale, 4)
286                 Mesh.transform(Object.matrix_world * SCALE_MAT * X_ROT)
287
288              # manage merge options
289    
290             if Config.MergeModes == 0:
291                 #one geo per Object, so use name of Object for the Geo file
292                 geoFile, mtlFile = CreateGeoMtlFiles(Config, StripName(Object.name))
293                 if Config.Optimized == True:
294                     GeoModel = CGeoModel(StripName(Object.name))  
295                 
296             # Write the Mesh in the Geo file   
297             WriteMesh(Config, Object, Mesh, geoFile, mtlFile, GeoModel)
298
299             if Config.MergeModes == 0:
300                 # no merge so finalize the file, and discard the file and geo class
301                 FinalizeGeoMtlFiles(Config, geoFile, mtlFile)
302                 geoFile = None
303                 mtlFile = None
304                 GeoModel = None
305             elif Config.MergeModes == 1:
306                 # merge in one Mesh, so keep the Geo class and prepare to change object
307                 GeoModel.NewObject() 
308             elif Config.MergeModes == 2:
309                 # merge several Meshes in one file: so clear the mesh data that we just written in the file,
310                 # but keep Materials info that need to be merged across objects
311                 GeoModel.ClearAllExceptMaterials()
312
313             if Config.Verbose:
314                 print("    Done")
315
316             if Config.ApplyModifiers and Config.ExportArmatures:
317                 bpy.data.objects.remove(Object2)
318             bpy.data.meshes.remove(Mesh)
319
320         if Config.Verbose:
321             print("  Done Writing Object: {}".format(Object.name))
322
323     if bChildObjects == False:
324         # we have finish to do all objects
325         if GeoModel:
326             if Config.MergeModes == 1:
327                 # we have Merges all objects in one Mesh, so time to write this big mesh in the file
328                 GeoModel.PrintGeoMesh(geoFile)
329                 # time to write skinfile if any
330                 len(GeoModel.useBonesDict)
331                 if len(GeoModel.useBonesDict) > 0:
332                     PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, GeoModel.name)
333             if Config.MergeModes > 0:
334                 WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel)
335                 FinalizeGeoMtlFiles(Config, geoFile, mtlFile)
336         geoFile = None
337         mtlFile = None
338         GeoModel = None
339
340
341 def CreateGeoMtlFiles(Config, Name):
342     #Create the geo file
343     geofullname = os.path.dirname(Config.FilePath) + ("\models\%s.geo" % Name)
344     ensure_dir(geofullname)
345     if Config.Verbose:
346         print("      Creating geo file %s" % (geofullname))  
347     geoFile = open(geofullname, "w")
348     geoFile.write('// geo file exported from : %r\n' % os.path.basename(bpy.data.filepath))
349     geoFile.write("CIwModel\n")
350     geoFile.write("{\n")
351     geoFile.write("\tname \"%s\"\n" % Name)
352     # add it to the group
353     Config.File.write("\t\".\models\%s.geo\"\n" % Name)
354
355     # Create the mtl file
356     mtlfullname = os.path.dirname(Config.FilePath) + "\models\%s.mtl" % (Name)
357     ensure_dir(mtlfullname)
358     if Config.Verbose:
359         print("      Creating mtl file %s" % (mtlfullname))
360     mtlFile = open(mtlfullname, "w")
361     mtlFile.write('// mtl file exported from : %r\n' % os.path.basename(bpy.data.filepath))   
362     return geoFile, mtlFile
363
364
365 def FinalizeGeoMtlFiles(Config, geoFile, mtlFile):
366     if Config.Verbose:
367         print("      Closing geo file")  
368     geoFile.write("}\n")
369     geoFile.close()
370     if Config.Verbose:
371         print("      Closing mtl file")  
372     mtlFile.close()
373
374
375 def WriteMesh(Config, Object, Mesh,  geoFile=None, mtlFile=None, GeoModel=None):
376     if geoFile == None or mtlFile == None:
377         print (" ERROR not geo file arguments in WriteMesh method")
378         return
379
380     if Config.Optimized:
381         if GeoModel == None:
382             print (" ERROR not GeoModel arguments in WriteMesh method")
383             return
384
385         BuildOptimizedGeo(Config, Object, Mesh, GeoModel)
386         if Config.MergeModes == 0 or Config.MergeModes == 2:
387             #if we don't merge, or if we write several meshes into one file ... write the mesh everytime we do an object
388             GeoModel.PrintGeoMesh(geoFile)
389  
390     else:
391         #exports not optimized by face (duplicated vertex, normals might be better in rare cases)
392         if Config.Verbose:
393             print("      Writing Mesh Vertices and normals...")
394         WriteMeshVerticesAndNormals(Config, Object, Mesh, geoFile)
395         if Config.Verbose:
396             print("      Done\n      Writing Mesh Vertices and Normals...")
397         bVertexColors = False
398         if Config.ExportVertexColors and (len(Mesh.vertex_colors) > 0):
399             if Config.Verbose:
400                 print("      Writing Mesh Vertices Colors...")
401             bVertexColors = WriteMeshVerticesColors(Config, Mesh, geoFile)
402             if Config.Verbose:
403                 print("      Done")
404         bUVTextures = False
405         if Config.ExportTextures and (len(Mesh.uv_textures) > 0):
406             if Config.Verbose:
407                 print("      Writing Mesh UV Coordinates...")
408             bUVTextures = WriteMeshUVCoordinates(Config, Mesh, geoFile)
409             if Config.Verbose:
410                 print("      Done")
411         if Config.Verbose:
412             print("      Writing Poly QuadsTris...")
413         WriteMeshPoly(Config, Mesh, geoFile, bVertexColors, bUVTextures)
414         if Config.Verbose:
415             print("      Done")
416     if Config.Verbose:
417         print("      Done\n      Writing Mesh Materials...")
418
419     if Config.MergeModes == 0:
420         #No merge, so we can diretly write the Mtl file associated to this object
421         if Config.Optimized == False:
422             WriteMeshMaterials(Config, Mesh, mtlFile)
423             geoFile.write("\t}\n")
424         else:
425             WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel)
426
427     if Config.Verbose:
428         print("      Done")
429   
430     if Config.ExportArmatures:
431         if Config.Verbose:
432             print("      Writing Mes Weights...")
433         if Config.Optimized:
434             WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel)
435         else:
436             WriteMeshSkinWeights(Config, Object, Mesh)
437         if Config.Verbose:
438             print("      Done")
439
440
441 ###### optimized version fo Export, can be used also to merge several object in one single geo File ######
442
443 # CGeoModel
444 #  -> List Vertices
445 #  -> List Normales
446 #  -> List uv 0
447 #  -> List uv 1
448 #  -> List Vertex Colors
449 #  -> List Materials
450 #       -> Material name
451 #       -> Blender Material Object
452 #       -> List Tris -> Stream Indices v,vn,uv0,uv1,vc
453 #       -> List Quads -> Stream Indices v,vn,uv0,uv1,vc
454
455
456 #############
457 #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
458 #############                           
459 class CGeoIndexList:
460     __slots__ = "v", "vn", "uv0", "uv1", "vc"
461     
462     def __init__(self, v, vn, uv0, uv1, vc):
463         self.v = v
464         self.vn = vn
465         self.uv0 = uv0
466         self.uv1 = uv1
467         self.vc = vc
468
469         
470 #############
471 #Store a Quad or a Tri in marmalade geo format : 3 or 4 CIndexList depending it is a Tri or a Quad
472 #############                        
473 class CGeoPoly:
474     __slots__ = "pointsList",
475     
476     def __init__(self):
477         self.pointsList = []
478
479     def AddPoint(self, v, vn, uv0, uv1, vc):
480         self.pointsList.append( CGeoIndexList(v, vn, uv0, uv1, vc))
481
482     def PointsCount(self):
483         return len(self.pointsList)
484
485     def PrintPoly(self, geoFile):
486         if len(self.pointsList) == 3:
487             geoFile.write("\t\t\t\tt ")
488         if len(self.pointsList) == 4:
489             geoFile.write("\t\t\t\tq ")
490         for point in self.pointsList:
491             geoFile.write(" {%d, %d, %d, %d, %d}" % (point.v, point.vn, point.uv0, point.uv1, point.vc))
492         geoFile.write("\n")
493
494
495 #############
496 #Store all the poly (tri or quad) assigned to a Material in marmalade geo format
497 #############                        
498 class CGeoMaterialPolys:
499     __slots__ = "name", "material", "quadList", "triList", "currentPoly"
500     
501     def __init__(self, name, material=None):
502         self.name = name
503         self.material = material
504         self.quadList = []
505         self.triList = []
506         self.currentPoly = None
507
508     def BeginPoly(self):
509         self.currentPoly = CGeoPoly()
510
511     def AddPoint(self, v, vn, uv0, uv1, vc):
512         self.currentPoly.AddPoint(v, vn, uv0, uv1, vc)       
513              
514     def EndPoly(self):
515         if (self.currentPoly.PointsCount() == 3):
516             self.triList.append(self.currentPoly)
517         if (self.currentPoly.PointsCount() == 4):
518             self.quadList.append(self.currentPoly)
519         self.currentPoly = None
520
521     def ClearPolys(self):
522         self.quadList = []
523         self.triList = []
524         self.currentPoly = None
525
526     def PrintMaterialPolys(self, geoFile):
527         geoFile.write("\t\tCSurface\n")
528         geoFile.write("\t\t{\n")
529         geoFile.write("\t\t\tmaterial \"%s\"\n" % self.name)
530         if self.triList:
531             geoFile.write("\t\t\tCTris\n")
532             geoFile.write("\t\t\t{\n")
533             geoFile.write("\t\t\t\tnumTris %d\n" % (len(self.triList)))
534             for poly in self.triList:
535                 poly.PrintPoly(geoFile)
536             geoFile.write("\t\t\t}\n")
537
538         if self.quadList:
539             geoFile.write("\t\t\tCQuads\n")
540             geoFile.write("\t\t\t{\n")
541             geoFile.write("\t\t\t\tnumQuads %d\n" % (len(self.quadList)))
542             for poly in self.quadList:
543                 poly.PrintPoly(geoFile)
544             geoFile.write("\t\t\t}\n")
545         geoFile.write("\t\t}\n")
546
547
548 #############
549 #Store all the information on a Model/Mesh (vertices, normal, certcies color, uv0, uv1, TRI, QUAD) in marmalade geo format
550 #############  
551 class CGeoModel:
552     __slots__ = ("name", "MaterialsDict", "vList", "vnList", "vcList", "uv0List", "uv1List",
553                 "currentMaterialPolys", "vbaseIndex","vnbaseIndex", "uv0baseIndex", "uv1baseIndex",
554                 "armatureObjectName", "useBonesDict", "mapVertexGroupNames")
555                 
556     def __init__(self, name):
557         self.name = name
558         self.MaterialsDict = {}
559         self.vList = []
560         self.vnList = []
561         self.vcList = []
562         self.uv0List = []
563         self.uv1List = []
564         self.currentMaterialPolys = None
565         #used xx baseIndex are used when merging several blender objects into one Mesh in the geo file (internal offset)
566         self.vbaseIndex = 0
567         self.vnbaseIndex = 0
568         self.uv0baseIndex = 0
569         self.uv1baseIndex = 0
570
571         # Store some information for skin management , when we merge several object in one big mesh (MergeModes 1)
572         # can only work if in the object list only one is rigged with an armature... and if it is located in 0,0,0
573         self.armatureObjectName = ""
574         #useBonesKey : bit field, where each bit is a VertexGroup.Index): Sum(2^VertGroupIndex).
575         #useBonesDict[useBonesKey] = tuple(VertexGroups.group, list(Vertex))
576         self.useBonesDict = {}
577         self.mapVertexGroupNames = {}
578
579
580
581     def AddVertex(self, vertex):
582         self.vList.append(vertex.copy())
583
584     def AddVertexNormal(self, vertexN):
585         self.vnList.append(vertexN.copy())
586
587     # 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)
588     def AddVertexUV0(self, u, v):
589         self.uv0List.append((u, v))
590         return len(self.uv0List) - 1 - self.uv0baseIndex 
591
592     def AddVertexUV1(self, u, v):
593         self.uv1List.append((u, v))
594         return len(self.uv1List) - 1 - self.uv1baseIndex 
595
596     # 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)
597     def AddVertexColor(self, r, g, b, a):
598         for i in range(0, len(self.vcList)):
599             col = self.vcList[i]
600             if col[0] == r and col[1] == g and col[2] == b and col[3] == a:
601                 return i
602
603         self.vcList.append((r, g, b, a))
604         return len(self.vcList)-1
605
606     def BeginPoly(self, MaterialName, material=None):
607         if MaterialName not in self.MaterialsDict:
608             self.currentMaterialPolys = CGeoMaterialPolys(MaterialName, material)
609         else:
610             self.currentMaterialPolys = self.MaterialsDict[MaterialName]
611         self.currentMaterialPolys.BeginPoly()
612
613     def AddPoint(self, v, vn, uv0, uv1, vc):
614         if v != -1:
615             v += self.vbaseIndex
616         if vn != -1:
617             vn += self.vnbaseIndex
618         if uv0 != -1:
619             uv0 += self.uv0baseIndex
620         if uv1 != -1:
621             uv1 += self.uv1baseIndex
622                 
623         self.currentMaterialPolys.AddPoint(v, vn, uv0, uv1, vc)       
624                               
625     def EndPoly(self):
626         self.currentMaterialPolys.EndPoly()
627         self.MaterialsDict[self.currentMaterialPolys.name] = self.currentMaterialPolys
628         self.currentMaterialPolys = None
629
630     def NewObject(self):
631         #used in Merge mode 1: allows to merge several blender objects into one Mesh.
632         self.vbaseIndex = len(self.vList)
633         self.vnbaseIndex = len(self.vnList)
634         self.uv0baseIndex = len(self.uv0List)
635         self.uv1baseIndex = len(self.uv1List)
636
637     def ClearAllExceptMaterials(self):
638         #used in Merge mode 2: one geo with several mesh
639         self.vList = []
640         self.vnList = []
641         self.vcList = []
642         self.uv0List = []
643         self.uv1List = []
644         self.currentMaterialPolys = None
645         self.vbaseIndex = 0
646         self.vnbaseIndex = 0
647         self.uv0baseIndex = 0
648         self.uv1baseIndex = 0
649         for GeoMaterialPolys in self.MaterialsDict.values():
650             GeoMaterialPolys.ClearPolys()
651         self.useBonesDict = {}
652         self.mapVertexGroupNames = {}
653         self.armatureObjectName = ""
654
655     def PrintGeoMesh(self, geoFile):
656         geoFile.write("\tCMesh\n")
657         geoFile.write("\t{\n")
658         geoFile.write("\t\tname \"%s\"\n" % (StripName(self.name)))
659
660         if self.vList:
661             geoFile.write("\t\tCVerts\n")
662             geoFile.write("\t\t{\n")
663             geoFile.write("\t\t\tnumVerts %d\n" % len(self.vList))
664             for vertex in self.vList:
665                 geoFile.write("\t\t\tv { %.9f, %.9f, %.9f }\n" % (vertex[0], vertex[1], vertex[2]))                      
666             geoFile.write("\t\t}\n")
667
668         if self.vnList:
669             geoFile.write("\t\tCVertNorms\n")
670             geoFile.write("\t\t{\n")
671             geoFile.write("\t\t\tnumVertNorms  %d\n" % len(self.vnList))
672             for vertexn in self.vnList:
673                 geoFile.write("\t\t\tvn { %.9f, %.9f, %.9f }\n" % (vertexn[0], vertexn[1], vertexn[2]))                      
674             geoFile.write("\t\t}\n")
675
676         if self.vcList:
677             geoFile.write("\t\tCVertCols\n")
678             geoFile.write("\t\t{\n")
679             geoFile.write("\t\t\tnumVertCols %d\n" % len(self.vcList))
680             for color in self.vcList:
681                 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           
682             geoFile.write("\t\t}\n")
683
684         if self.uv0List:
685             geoFile.write("\t\tCUVs\n")
686             geoFile.write("\t\t{\n")
687             geoFile.write("\t\t\tsetID 0\n")
688             geoFile.write("\t\t\tnumUVs %d\n" % len(self.uv0List))
689             for uv in self.uv0List:
690                  geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (uv[0], uv[1]))                       
691             geoFile.write("\t\t}\n")
692
693         if self.uv1List:
694             geoFile.write("\t\tCUVs\n")
695             geoFile.write("\t\t{\n")
696             geoFile.write("\t\t\tsetID 1\n")
697             geoFile.write("\t\t\tnumUVs %d\n" % len(self.uv1List))
698             for uv in self.uv1List:
699                  geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (uv[0], uv[1]))                       
700             geoFile.write("\t\t}\n")
701
702         for GeoMaterialPolys in self.MaterialsDict.values():
703             GeoMaterialPolys.PrintMaterialPolys(geoFile)
704         geoFile.write("\t}\n")
705
706     def GetMaterialList(self):
707         return list(self.MaterialsDict.keys())
708
709     def GetMaterialByName(self, name):
710         if name in self.MaterialsDict:
711             return self.MaterialsDict[name].material
712         else:
713             return None       
714
715
716
717 #############
718 # iterates faces, vertices ... and store the information in the GeoModel container
719 def BuildOptimizedGeo(Config, Object, Mesh, GeoModel):
720     if GeoModel == None:
721         GeoModel = CGeoModel(filename, Object.name)
722
723     #Store Vertex stream, and Normal stream (use directly the order from blender collection
724     for Vertex in Mesh.vertices:
725         GeoModel.AddVertex(Vertex.co)
726         Normal = Vertex.normal
727         if Config.FlipNormals:
728             Normal = -Normal
729         GeoModel.AddVertexNormal(Normal)
730     #Check if some colors have been defined
731     vertexColours = None
732     if Config.ExportVertexColors and (len(Mesh.vertex_colors) > 0):
733         vertexColours = Mesh.vertex_colors[0].data
734
735     #Check if some uv coordinates have been defined
736     UVCoordinates = None
737     if Config.ExportTextures and (len(Mesh.uv_textures) > 0):
738         for UV in Mesh.uv_textures:
739             if UV.active_render:
740                 UVCoordinates = UV.data
741                 break
742
743     #Iterate on Faces and Store the poly (quad or tri) and the associate colors,UVs
744     for Face in Mesh.faces:
745         # stream for vertex (we use the same for normal)
746         Vertices = list(Face.vertices)
747         if Config.CoordinateSystem == 1:
748             Vertices = Vertices[::-1]
749         # stream for vertex colors
750         if vertexColours:
751             MeshColor = vertexColours[Face.index]
752             if len(Vertices) == 3:
753                 FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3))
754             else:
755                 FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3, MeshColor.color4))
756             if Config.CoordinateSystem == 1:
757                 FaceColors = FaceColors[::-1]
758             colorIndex = []
759             for color in FaceColors:
760                 index = GeoModel.AddVertexColor(color[0], color[1], color[2], 1)  #rgba => no alpha on vertex color in Blender so use 1
761                 colorIndex.append(index)
762         else:
763             colorIndex = list((-1,-1,-1,-1))
764
765         # stream for UV0 coordinates
766         if UVCoordinates:
767             uvFace = UVCoordinates[Face.index]
768             uvVertices = []
769             for uvVertex in uvFace.uv:
770                 uvVertices.append(tuple(uvVertex))
771             if Config.CoordinateSystem == 1:
772                 uvVertices = uvVertices[::-1]
773             uv0Index = []
774             for uvVertex in uvVertices:
775                 index = GeoModel.AddVertexUV0(uvVertex[0], 1 - uvVertex[1]) 
776                 uv0Index.append(index)
777         else:
778             uv0Index = list((-1, -1, -1, -1))
779
780         # stream for UV1 coordinates
781         uv1Index = list((-1, -1, -1, -1))
782
783         mat = None
784         # find the associated material
785         if Face.material_index < len(Mesh.materials):
786             mat = Mesh.materials[Face.material_index]
787         if mat:
788             matName =  mat.name
789         else:
790             matName = "NoMaterialAssigned"  # There is no material assigned in blender !!!, exporter have generated a default one          
791             
792         # now on the material, generates the tri/quad in v,vn,uv0,uv1,vc stream index
793         GeoModel.BeginPoly(matName, mat)
794
795         for i in range(0, len(Vertices)):
796             GeoModel.AddPoint(Vertices[i], Vertices[i], uv0Index[i], uv1Index[i], colorIndex[i])
797
798         GeoModel.EndPoly()
799
800                               
801 #############
802 # Get the list of Material in use by the CGeoModel
803 def WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel):
804     for matName in GeoModel.GetMaterialList():
805         Material = GeoModel.GetMaterialByName(matName)
806         WriteMaterial(Config, mtlFile, Material)
807
808
809 ##################### Not Optimized Export, we use a Face export
810
811
812 def WriteMeshVerticesAndNormals(Config,  Object, Mesh, geoFile):
813     # Not optimized, simply iterate Blender Face, and writes all face vertices
814     # Marmalade groups per material, and then groups per Tir and Quad.
815     # So generate vertices grouped together per Tri of the same material, and same quad of the same material
816     geoFile.write("\tname \"%s\"\n" % (StripName(Object.name)))
817     geoFile.write("\tCMesh\n")
818     geoFile.write("\t{\n")
819     geoFile.write("\t\tname \"%s\"\n" % (StripName(Object.name)))
820     geoFile.write("\t\tCVerts\n")
821     geoFile.write("\t\t{\n")
822     Index = 0
823     VertexCount = GetNonOptimizedMeshVertexCount(Mesh)
824     geoFile.write("\t\t\tnumVerts %d\n" % VertexCount)
825                 
826     if Config.Verbose:
827             print("      Writing Mesh vertices...%d => %d" % (len(Mesh.vertices), VertexCount))
828     matCount =  len(Mesh.materials)
829     if matCount == 0:
830         matCount = 1  #No material defined for the Mesh !!!! => generate a default Material
831     for matIndex in range(0, matCount):
832         if Config.Verbose:
833             print("      Material Index: %d >" % matIndex)
834         for polyCount in range(3, 5):
835             faceCount = 0
836             for Face in Mesh.faces:
837                 if Face.material_index == matIndex:
838                     if len(Face.vertices) == polyCount:
839                         Vertices = list(Face.vertices)
840                         faceCount = faceCount + 1                           
841                         if Config.CoordinateSystem == 1:
842                             Vertices = Vertices[::-1]
843                         for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
844                             Position = Vertex.co
845                             geoFile.write("\t\t\tv { %.9f, %.9f, %.9f }\n" % (Position[0], Position[1], Position[2]))            
846             if Config.Verbose and polyCount == 3:
847                 print("         Tri Poly count  Index: %d" % faceCount)
848             elif Config.Verbose and polyCount == 4:
849                 print("         Quad Poly count  Index: %d" % faceCount)
850     geoFile.write("\t\t}\n")
851
852     #WriteMeshNormals
853     geoFile.write("\t\tCVertNorms\n")
854     geoFile.write("\t\t{\n")
855     geoFile.write("\t\t\tnumVertNorms  %d\n" % VertexCount)
856       
857     if Config.Verbose:
858             print("      Writing Mesh normals...")
859     matCount =  len(Mesh.materials)
860     if matCount == 0:
861         matCount = 1  # No material defined for the Mesh !!!! => generate a default Material
862     for matIndex in range(0, matCount):
863         if Config.Verbose:
864             print("      Material Index: %d >" % matIndex)
865         for polyCount in range(3, 5):
866             faceCount = 0
867             for Face in Mesh.faces:
868                  if Face.material_index == matIndex:
869                     if len(Face.vertices) == polyCount:
870                         Vertices = list(Face.vertices)
871                         faceCount = faceCount + 1
872                         if Config.CoordinateSystem == 1:
873                             Vertices = Vertices[::-1]
874                         for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
875                             if Face.use_smooth:
876                                 Normal = Vertex.normal
877                             else:
878                                 Normal = Face.normal
879                             if Config.FlipNormals:
880                                 Normal = -Normal
881                             geoFile.write("\t\t\tvn { %.9f, %.9f, %.9f }\n" % (Normal[0], Normal[1], Normal[2]))            
882             if Config.Verbose and polyCount == 3:
883                 print("         Tri Poly count  Index: %d" % faceCount)
884             elif Config.Verbose and polyCount == 4:
885                 print("         Quad Poly count  Index: %d" % faceCount)
886     geoFile.write("\t\t}\n")
887
888
889 def WriteMeshVerticesColors (Config, Mesh, geoFile):
890     if len(Mesh.vertex_colors) > 0:
891         vertexColours = Mesh.vertex_colors[0].data
892         if len(vertexColours) > 0:
893             geoFile.write("\t\tCVertCols\n")
894             geoFile.write("\t\t{\n")
895             Index = 0
896             VertexCount = GetNonOptimizedMeshVertexCount(Mesh)
897             geoFile.write("\t\t\tnumVertCols %d\n" % VertexCount)
898                                 
899             if Config.Verbose:
900                     print("      Writing Mesh vertices Colors...%d" % (len(Mesh.vertices)))
901                     
902             matCount =  len(Mesh.materials)
903             if matCount == 0:
904                 matCount = 1  #No material defined for the Mesh !!!! => generate a default Material
905             for matIndex in range(0, matCount):
906                 if Config.Verbose:
907                     print("      Material Index: %d >" % matIndex)
908                 for polyCount in range(3, 5):
909                     faceCount = 0
910                     for Face in Mesh.faces:
911                         if Face.material_index == matIndex:
912                             if len(Face.vertices) == polyCount:
913                                 Vertices = list(Face.vertices)
914                                 print("       - Face Index: %d / %d" % (Face.index, len(vertexColours)))
915                                 print(vertexColours)
916                                 MeshColor = vertexColours[Face.index]
917                                 if polyCount == 3:
918                                     FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3))
919                                 else:
920                                     FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3, MeshColor.color4))
921                                 faceCount = faceCount + 1                           
922                                 if Config.CoordinateSystem == 1:
923                                     Vertices = Vertices[::-1]
924                                     FaceColors = FaceColors[::-1]
925                                 for color in FaceColors:
926                                     geoFile.write("\t\t\tcol { %.6f, %.6f, %.6f, 1 }\n" % (color[0], color[1], color[2]))            
927                     if Config.Verbose and polyCount == 3:
928                         print("         Tri Poly count  Index: %d" % faceCount)
929                     elif Config.Verbose and polyCount == 4:
930                         print("         Quad Poly count  Index: %d" % faceCount)
931             geoFile.write("\t\t}\n")
932             return True
933     return False
934
935
936 def WriteMeshUVCoordinates(Config, Mesh, geoFile):
937     geoFile.write("\t\tCUVs\n")
938     geoFile.write("\t\t{\n")
939     geoFile.write("\t\t\tsetID 0\n")
940     
941     UVCoordinates = None
942     for UV in Mesh.uv_textures:
943         if UV.active_render:
944             UVCoordinates = UV.data
945             break
946     if UVCoordinates:
947         uvCount = 0
948         VertexCount = GetNonOptimizedMeshVertexCount(Mesh)
949         geoFile.write("\t\t\tnumUVs %d\n" % VertexCount)
950
951         if Config.Verbose:
952             print("      Writing Mesh UVs...")
953         matCount =  len(Mesh.materials)
954         if matCount == 0:
955             matCount = 1  #No material defined for the Mesh !!!! => generate a default Material
956         for matIndex in range(0, matCount):
957             if Config.Verbose:
958                 print("      Material Index: %d >" % matIndex)
959             for polyCount in range(3, 5):
960                 faceCount = 0         
961                 #for Face in UVCoordinates:
962                 #for Face in Mesh.faces:
963                 for i in range(0, len(Mesh.faces)):
964                     Face = Mesh.faces[i]
965                     uvFace = UVCoordinates[i]
966                     Vertices = []
967                     if Face.material_index == matIndex:
968                         if len(Face.vertices) == polyCount:
969                             for Vertex in uvFace.uv:
970                                 Vertices.append(tuple(Vertex))
971                             if Config.CoordinateSystem == 1:
972                                 Vertices = Vertices[::-1]
973                             for Vertex in Vertices:
974                                 geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (Vertex[0], 1 - Vertex[1]))                        
975                             faceCount = faceCount + 1
976                             uvCount = uvCount + len(Vertices)
977                 if Config.Verbose and polyCount == 3:
978                     print("         Tri Poly count  Index: %d" % faceCount)
979                 elif Config.Verbose and polyCount == 4:
980                     print("         Quad Poly count  Index: %d" % faceCount)
981
982         geoFile.write("\t\t}\n")
983         if Config.Verbose:
984              print("         Total UVCount : %d" % uvCount)
985         return True
986     return False
987
988
989
990 def WriteMeshPoly(Config, Mesh, geoFile, bVertexColors, bUVTextures):
991     # groups per tri and per Quad belonging to the same material
992     Index = 0
993     VertexCount = GetNonOptimizedMeshVertexCount(Mesh)
994
995     matCount =  len(Mesh.materials)
996     if matCount == 0:
997         matCount = 1  #No material defined for the Mesh !!!! => generate a default Material
998     for matIndex in range(0, matCount):
999         if Config.Verbose:
1000             print("      Material Index: %d >" % matIndex)
1001             
1002         #first check if there is Tri, Quad , or both, or ... none :-)
1003         TriCount = 0
1004         QuadCount = 0
1005         for polyCount in range(3, 5):
1006             for Face in Mesh.faces:
1007                 if Face.material_index == matIndex:
1008                     if len(Face.vertices) == polyCount:
1009                         if polyCount == 3:
1010                             TriCount = TriCount + 1
1011                         elif polyCount == 4:
1012                             QuadCount = QuadCount + 1
1013                             
1014         if Config.Verbose:
1015             print("            Poly Count Tris %d - Quads %d " % (TriCount, QuadCount))
1016             
1017         if TriCount > 0 or QuadCount > 0:
1018             geoFile.write("\t\tCSurface\n")
1019             geoFile.write("\t\t{\n")
1020             if matIndex < len(Mesh.materials):
1021                 geoFile.write("\t\t\tmaterial \"%s\"\n" % Mesh.materials[matIndex].name)
1022             else:
1023                 geoFile.write("\t\t\tmaterial NoMaterialAssigned // There is no material assigned in blender !!!, exporter have generated a default one\n")
1024             streamIndex = 0
1025             #Write the Tri for this material, if any
1026             if TriCount > 0:
1027                 geoFile.write("\t\t\tCTris\n")
1028                 geoFile.write("\t\t\t{\n")
1029                 geoFile.write("\t\t\t\tnumTris %d\n" % (TriCount))
1030                 for Face in Mesh.faces:
1031                     if Face.material_index == matIndex:
1032                         if len(Face.vertices) == 3:
1033                             vc1 = vc2 = vc3 = -1
1034                             if bVertexColors:
1035                                 vc1 = streamIndex
1036                                 vc2 = streamIndex + 1
1037                                 vc3 = streamIndex + 2
1038                             uv1 = uv2 = uv3 = -1
1039                             if bUVTextures:
1040                                 uv1 = streamIndex
1041                                 uv2 = streamIndex+1
1042                                 uv3 = streamIndex+2
1043                             geoFile.write("\t\t\t\tt {%d, %d, %d, -1, %d} {%d, %d, %d, -1, %d} {%d, %d, %d, -1, %d}\n"
1044                                           % (streamIndex, streamIndex, uv1, vc1, streamIndex+1, streamIndex+1, uv2, vc2, streamIndex+2, streamIndex+2, uv3, vc3))
1045                             streamIndex = streamIndex + 3
1046                 geoFile.write("\t\t\t}\n")
1047             #Write the Quad for this material, if any
1048             if QuadCount > 0:
1049                 geoFile.write("\t\t\tCQuads\n")
1050                 geoFile.write("\t\t\t{\n")
1051                 geoFile.write("\t\t\t\tnumQuads %d\n" % (QuadCount))
1052                 for Face in Mesh.faces:
1053                     if Face.material_index == matIndex:
1054                         if len(Face.vertices) == 4:
1055                             vc1 = vc2 = vc3 = vc4 = -1
1056                             if bVertexColors:
1057                                 vc1 = streamIndex
1058                                 vc2 = streamIndex + 1
1059                                 vc3 = streamIndex + 2
1060                                 vc3 = streamIndex + 3
1061                             uv1 = uv2 = uv3 = uv4 = -1
1062                             if bUVTextures:
1063                                 uv1 = streamIndex
1064                                 uv2 = streamIndex + 1
1065                                 uv3 = streamIndex + 2
1066                                 uv4 = streamIndex + 3
1067                             geoFile.write("\t\t\t\tq {%d, %d, %d, -1, %d} {%d, %d, %d, -1, %d} {%d, %d, %d, -1, %d} {%d, %d, %d, -1, %d}\n"
1068                                           % (streamIndex, streamIndex, uv1, vc1, streamIndex+1, streamIndex+1, uv2, vc2,
1069                                              streamIndex+2, streamIndex+2, uv3, vc3, streamIndex+3, streamIndex+3, uv4, vc4))
1070                             streamIndex = streamIndex + 4
1071                 geoFile.write("\t\t\t}\n")
1072
1073             geoFile.write("\t\t}\n")
1074
1075
1076 def WriteMeshMaterials(Config, Mesh, mtlFile):    
1077     Materials = Mesh.materials
1078     if Materials.keys():
1079         for Material in Materials:
1080             WriteMaterial(Config, mtlFile, Material)
1081     else:
1082         if Config.Verbose :
1083             print("         NO MATERIAL ASSIGNED TO THE MESH in Blender !!! generating a default material")
1084         WriteMaterial(Config, mtlFile)         
1085
1086
1087 def WriteMaterial(Config, mtlFile, Material=None):
1088     mtlFile.write("CIwMaterial\n")
1089     mtlFile.write("{\n")
1090     if Material:
1091         mtlFile.write("\tname \"%s\"\n" % Material.name)
1092
1093         if Config.ExportMaterialColors:
1094             #if bpy.context.scene.world:
1095             #    MatAmbientColor = Material.ambient * bpy.context.scene.world.ambient_color
1096             MatAmbientColor = Material.ambient * Material.diffuse_color
1097             mtlFile.write("\tcolAmbient {%.2f,%.2f,%.2f,%.2f} \n" % (MatAmbientColor[0] * 255, MatAmbientColor[1] * 255, MatAmbientColor[2] * 255, Material.alpha * 255))
1098             MatDiffuseColor = Material.diffuse_intensity * Material.diffuse_color
1099             mtlFile.write("\tcolDiffuse  {%.2f,%.2f,%.2f} \n" % (MatDiffuseColor * 255)[:])
1100             MatSpecularColor = Material.specular_intensity * Material.specular_color
1101             mtlFile.write("\tcolSpecular  {%.2f,%.2f,%.2f} \n" % (MatSpecularColor * 255)[:])
1102             # EmitColor = Material.emit * Material.diffuse_color
1103             # mtlFile.write("\tcolEmissive {%.2f,%.2f,%.2f} \n" % (EmitColor* 255)[:])
1104
1105             
1106     else:
1107         mtlFile.write("\tname \"NoMaterialAssigned\" // There is no material assigned in blender !!!, exporter have generated a default one\n")
1108
1109     #Copy texture
1110     if Config.ExportTextures:
1111         Texture = GetMaterialTextureFullPath(Config, Material)
1112         if Texture:
1113             mtlFile.write("\ttexture0 .\\textures\\%s\n" % (bpy.path.basename(Texture)))
1114             
1115             if Config.CopyTextureFiles:
1116                 if not os.path.exists(Texture):
1117                     #try relative path to the blend file
1118                     Texture = os.path.dirname(bpy.data.filepath) + Texture
1119                 if os.path.exists(Texture):
1120                     textureDest = os.path.dirname(Config.FilePath) + "\\models\\textures\\%s" % (bpy.path.basename(Texture))
1121                     ensure_dir(textureDest)
1122                     if Config.Verbose:
1123                         print("      Copying the texture file %s ---> %s" % (Texture, textureDest))
1124                     shutil.copy(Texture, textureDest)
1125                 else:
1126                     if Config.Verbose:
1127                         print("      CANNOT Copy texture file (not found) %s" % (Texture))
1128     mtlFile.write("}\n")
1129
1130
1131 def WriteMeshSkinWeights(Config, Object, Mesh):
1132     ArmatureList = [Modifier for Modifier in Object.modifiers if Modifier.type == "ARMATURE"]
1133     if ArmatureList:
1134         ArmatureObject = ArmatureList[0].object
1135         if ArmatureObject is None:
1136             return
1137         ArmatureBones = ArmatureObject.data.bones
1138
1139         # Marmlade need to declare a vertex per list of affected bones
1140         # so first we have to get all the combinations of affected bones that exist int he mesh
1141         # 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
1142
1143         useBonesDict = {}
1144         #useBonesKey => pair_ListGroupIndices_ListAssignedVertices
1145         #useBonesDict[useBonesKey] = tuple(VertexGroups.group, list(Vertex))
1146
1147         mapVertexGroupNames = {} 
1148         matCount = len(Mesh.materials)
1149         if matCount == 0:
1150             matCount = 1 #No material defined for the Mesh !!!! => generate a default Material
1151         for matIndex in range(0, matCount):
1152             streamIndex = 0
1153             for polyCount in range(3, 5):
1154                 for Face in Mesh.faces:
1155                     if Face.material_index == matIndex:
1156                         if len(Face.vertices) == polyCount:
1157                             Vertices = list(Face.vertices)
1158                             if Config.CoordinateSystem == 1:
1159                                 Vertices = Vertices[::-1]
1160                             for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
1161                                 AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, useBonesDict, mapVertexGroupNames, streamIndex) 
1162                                 streamIndex = streamIndex + 1
1163
1164         PrintSkinWeights(Config, StripName(ArmatureObject.name), useBonesDict, mapVertexGroupNames, StripName(Object.name))
1165
1166          
1167 def WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel):
1168     ArmatureList = [Modifier for Modifier in Object.modifiers if Modifier.type == "ARMATURE"]
1169     if ArmatureList:
1170         ArmatureObject = ArmatureList[0].object
1171         if ArmatureObject is None:
1172             return
1173         ArmatureBones = ArmatureObject.data.bones
1174
1175         GeoModel.armatureObjectName = StripName(ArmatureObject.name)
1176
1177         # Marmlade need to declare a vertex per list of affected bones
1178         # so first we have to get all the combinations of affected bones that exist int he mesh
1179         # 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
1180         
1181         for Vertex in Mesh.vertices:
1182             VertexIndex = Vertex.index + GeoModel.vbaseIndex
1183             AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, VertexIndex)
1184
1185         if Config.MergeModes != 1:
1186             # write skin file directly
1187             PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, StripName(Object.name))
1188
1189
1190 def PrintSkinWeights(Config, ArmatureObjectName, useBonesDict, mapVertexGroupNames, GeoName):        
1191         #Create the skin file
1192         skinfullname = os.path.dirname(Config.FilePath) + "\models\%s.skin" % GeoName
1193         ensure_dir(skinfullname)
1194         if Config.Verbose:
1195             print("      Creating skin file %s" % (skinfullname))
1196         skinFile = open(skinfullname, "w")
1197         skinFile.write('// skin file exported from : %r\n' % os.path.basename(bpy.data.filepath))   
1198         skinFile.write("CIwAnimSkin\n")
1199         skinFile.write("{\n")
1200         skinFile.write("\tskeleton \"%s\"\n" % ArmatureObjectName)
1201         skinFile.write("\tmodel \"%s\"\n" % GeoName)
1202
1203         # now we have Bones grouped in the dictionary , along with the associated influenced vertex weighting
1204         # So simply iterate the dictionary
1205         Config.File.write("\t\".\models\%s.skin\"\n" % GeoName)
1206         for pair_ListGroupIndices_ListAssignedVertices in useBonesDict.values():
1207             skinFile.write("\tCIwAnimSkinSet\n")
1208             skinFile.write("\t{\n")
1209             skinFile.write("\t\tuseBones {")
1210             for vertexGroupIndex in pair_ListGroupIndices_ListAssignedVertices[0]:
1211                 skinFile.write(" %s" % mapVertexGroupNames[vertexGroupIndex])
1212             skinFile.write(" }\n")
1213             skinFile.write("\t\tnumVerts %d\n" % len(pair_ListGroupIndices_ListAssignedVertices[1]))
1214             for VertexWeightString in pair_ListGroupIndices_ListAssignedVertices[1]:
1215                 skinFile.write(VertexWeightString)
1216             skinFile.write("\t}\n")
1217
1218         skinFile.write("}\n")
1219         skinFile.close()
1220
1221
1222 def AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, useBonesDict, mapVertexGroupNames, VertexIndex):
1223     #build useBones
1224     useBonesKey = 0
1225     vertexGroupIndices = []
1226     weightTotal = 0.0
1227     if (len(Vertex.groups)) > 4:
1228         print ("ERROR Vertex %d is influenced by more than 4 bones\n" % (VertexIndex))
1229     for VertexGroup in Vertex.groups:
1230         mapVertexGroupNames[VertexGroup.group] = StripBoneName(Object.vertex_groups[VertexGroup.group].name)
1231         if (len(vertexGroupIndices))<4:  #ignore if more 4 bones are influencing the vertex
1232             useBonesKey = useBonesKey + pow(2, VertexGroup.group)
1233             vertexGroupIndices.append(VertexGroup.group)
1234             weightTotal = weightTotal + VertexGroup.weight
1235     if (weightTotal == 0):
1236         print(" ERROR Weight is ZERO for vertex %d " % (VertexIndex))
1237         print(vertexGroupIndices)
1238         bWeightTotZero = True  #avoid divide by zero
1239     else:
1240         bWeightTotZero = False
1241     
1242     if len(vertexGroupIndices) > 0:
1243         vertexGroupIndices.sort();
1244            
1245         #build the vertex weight string: vertex indices, followed by influence weight for each bone
1246         VertexWeightString = "\t\tvertWeights { %d" % (VertexIndex)
1247         for vertexGroupIndex in vertexGroupIndices:
1248             #get the weight of this specific VertexGroup (aka bone)
1249             boneWeight = 1
1250             for VertexGroup in Vertex.groups:
1251                 if VertexGroup.group == vertexGroupIndex:
1252                     boneWeight = VertexGroup.weight
1253             #calculate the influence of this bone compared to the total of weighting applied to this Vertex
1254             if not bWeightTotZero:
1255                 VertexWeightString += ", %.7f" % (boneWeight / weightTotal)
1256             else:
1257                 VertexWeightString += ", %.7f" % (1.0 / len(vertexGroupIndices))
1258         VertexWeightString += "}"
1259         if bWeightTotZero:
1260             VertexWeightString += " // total weight was zero in blender , export assign default weighting." 
1261         if (len(Vertex.groups)) > 4:
1262             VertexWeightString += " // vertex is associated to more than 4 bones in blender !! skip some bone association (was associated to %d bones)." % (len(Vertex.groups))
1263         VertexWeightString += "\n"
1264            
1265         #store in dictionnary information
1266         if useBonesKey not in useBonesDict:
1267             VertexList = []
1268             VertexList.append(VertexWeightString)
1269             useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
1270         else:
1271             pair_ListGroupIndices_ListAssignedVertices = useBonesDict[useBonesKey]
1272             pair_ListGroupIndices_ListAssignedVertices[1].append(VertexWeightString)
1273             useBonesDict[useBonesKey] = pair_ListGroupIndices_ListAssignedVertices
1274     else:
1275         print ("ERROR Vertex %d is not skinned (it doesn't belong to any vertex group\n" % (VertexIndex)) 
1276
1277
1278
1279 ############# ARMATURE: Bone export, and Bone animation export 
1280
1281          
1282 def WriteArmatureParentRootBones(Config, Object, RootBonesList, skelFile):
1283
1284     if len(RootBonesList) > 1:
1285         print(" /!\\  WARNING ,Marmelade need only one ROOT bone per armature, there is %d root bones " % len(RootBonesList))
1286         print(RootBonesList)
1287         
1288     PoseBones = Object.pose.bones
1289     for Bone in RootBonesList:
1290         if Config.Verbose:
1291             print("      Writing Root Bone: {}...".format(Bone.name))
1292
1293         PoseBone = PoseBones[Bone.name]
1294         WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True)
1295         #WriteOneBoneRestPosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True, Vector(),Quaternion())
1296         if Config.Verbose:
1297             print("      Done")
1298         WriteArmatureChildBones(Config, Object, Bone.children, skelFile)
1299
1300             
1301 def WriteArmatureChildBones(Config, Object, BonesList, skelFile):
1302     PoseBones = Object.pose.bones
1303     for Bone in BonesList:
1304         if Config.Verbose:
1305             print("      Writing Child Bone: {}...".format(Bone.name))
1306         PoseBone = PoseBones[Bone.name]
1307         WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True)
1308         #WriteOneBoneRestPosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True, Vector(),Quaternion())
1309         if Config.Verbose:
1310             print("      Done")
1311             
1312         WriteArmatureChildBones(Config, Object, Bone.children, skelFile)
1313
1314
1315 def WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, File, isSkelFileNotAnimFile):
1316     # Compute armature scale : 
1317     # Many others exporter require sthe user to do Apply Scale in Object Mode to have 1,1,1 scale and so that anim data are correctly scaled
1318     # Here we retreive the Scale of the Armture Object.matrix_world.to_scale() and we use it to scale the bones :-)
1319     # So new Blender user should not complain about bad animation export if they forgot to apply the Scale to 1,1,1
1320
1321     armScale = Object.matrix_world.to_scale()
1322     ## scalematrix = Matrix()
1323     ## scalematrix[0][0] = armScale.x * Config.Scale
1324     ## scalematrix[1][1] = armScale.y * Config.Scale
1325     ## scalematrix[2][2] = armScale.z * Config.Scale
1326
1327     if isSkelFileNotAnimFile:
1328         #skel file, bone header
1329         File.write("\tCIwAnimBone\n")
1330         File.write("\t{\n")
1331         File.write("\t\tname \"%s\"\n" % StripBoneName(Bone.name))
1332         if Bone.parent:
1333             File.write("\t\tparent \"%s\"\n" % StripBoneName(Bone.parent.name))
1334     else:
1335         #anim file, bone header
1336         File.write("\t\t\n")
1337         File.write("\t\tbone \"%s\" \n" % StripBoneName(Bone.name))
1338
1339     if Bone.parent:
1340         ParentPoseBone = PoseBones[Bone.parent.name]
1341         locmat = ParentPoseBone.matrix.inverted() * PoseBone.matrix
1342     else:
1343         locmat = PoseBone.matrix
1344         if Config.MergeModes > 0:
1345             # Merge mode is in world coordinates .. anyway merge mesh doesn't work really with armature that should be local to one mesh
1346             locmat = Object.matrix_world * PoseBone.matrix
1347             armScale.x =  armScale.y = armScale.z = 1  
1348         
1349     loc = locmat.to_translation()
1350     quat = locmat.to_quaternion()
1351   
1352     if not Bone.parent:  # and Config.MergeModes == 0:
1353         #flip Y Z axes (only on root bone, other bones are local to root bones, so no need to rotate)
1354         X_ROT = mathutils.Matrix.Rotation(-math.pi / 2, 4, 'X')
1355         quat.rotate(X_ROT)
1356         loc.rotate(X_ROT)
1357
1358         
1359     #Scale the bone
1360     loc.x *= (armScale.x * Config.Scale)
1361     loc.y *= (armScale.y * Config.Scale)
1362     loc.z *= (armScale.z * Config.Scale)
1363     
1364     File.write("\t\tpos { %.9f, %.9f, %.9f }\n" % (loc[0], loc[1], loc[2]))
1365     File.write("\t\trot { %.9f, %.9f, %.9f, %.9f }\n" % (quat.w, quat.x, quat.y, quat.z))
1366
1367     if isSkelFileNotAnimFile:
1368         File.write("\t}\n")
1369
1370       
1371 def WriteKeyedAnimationSet(Config, Scene):  
1372     for Object in [Object for Object in Config.ObjectList if Object.animation_data]:
1373         if Config.Verbose:
1374             print("  Writing Animation Data for Object: {}".format(Object.name))
1375         Action = Object.animation_data.action
1376         if Action:
1377             #Object animated (aka single bone object)
1378             #build key frame time list
1379             keyframeTimes = set()
1380             if Config.ExportAnimation == 1:
1381                 # Exports only key frames
1382                 for FCurve in Action.fcurves:
1383                     for Keyframe in FCurve.keyframe_points:
1384                         if Keyframe.co[0] < Scene.frame_start:
1385                             keyframeTimes.add(Scene.frame_start)
1386                         elif Keyframe.co[0] > Scene.frame_end:
1387                             keyframeTimes.add(Scene.frame_end)
1388                         else:
1389                             keyframeTimes.add(int(Keyframe.co[0]))
1390             else:
1391                 # Exports all frames
1392                 keyframeTimes.update(range(scene.frame_start, scene.frame_end + 1, 1))
1393             keyframeTimes = list(keyframeTimes)
1394             keyframeTimes.sort()
1395             if len(keyframeTimes):
1396                 #Create the anim file for offset animation (or single bone animation
1397                 animfullname = os.path.dirname(Config.FilePath) + "\\anims\\%s_offset.anim" % (StripName(Object.name))
1398                 #not yet supported
1399                 """
1400                 ##    ensure_dir(animfullname)
1401                 ##    if Config.Verbose:
1402                 ##        print("      Creating anim file (single bone animation) %s" % (animfullname))
1403                 ##    animFile = open(animfullname, "w")
1404                 ##    animFile.write('// anim file exported from : %r\n' % os.path.basename(bpy.data.filepath))   
1405                 ##    animFile.write("CIwAnim\n")
1406                 ##    animFile.write("{\n")
1407                 ##    animFile.write("\tent \"%s\"\n" % (StripName(Object.name)))
1408                 ##    animFile.write("\tskeleton \"SingleBone\"\n")
1409                 ##    animFile.write("\t\t\n")
1410                 ##
1411                 ##    Config.File.write("\t\".\\anims\\%s_offset.anim\"\n" % (StripName(Object.name)))
1412                 ##
1413                 ##    for KeyframeTime in keyframeTimes:
1414                 ##        #Scene.frame_set(KeyframeTime)    
1415                 ##        animFile.write("\tCIwAnimKeyFrame\n")
1416                 ##        animFile.write("\t{\n")
1417                 ##        animFile.write("\t\ttime %.2f // frame num %d \n" % (KeyframeTime/Config.AnimFPS, KeyframeTime))
1418                 ##        animFile.write("\t\t\n")
1419                 ##        animFile.write("\t\tbone \"SingleBone\" \n")
1420                 ##        #postion
1421                 ##        posx = 0
1422                 ##        for FCurve in Action.fcurves:
1423                 ##            if FCurve.data_path == "location" and FCurve.array_index == 0: posx = FCurve.evaluate(KeyframeTime)
1424                 ##        posy = 0
1425                 ##        for FCurve in Action.fcurves:
1426                 ##            if FCurve.data_path == "location" and FCurve.array_index == 1: posy = FCurve.evaluate(KeyframeTime)
1427                 ##        posz = 0
1428                 ##        for FCurve in Action.fcurves:
1429                 ##            if FCurve.data_path == "location" and FCurve.array_index == 2: posz = FCurve.evaluate(KeyframeTime)
1430                 ##        animFile.write("\t\tpos {%.9f,%.9f,%.9f}\n" % (posx, posy, posz))
1431                 ##        #rotation
1432                 ##        rot = Euler()
1433                 ##        rot[0] = 0
1434                 ##        for FCurve in Action.fcurves:
1435                 ##            if FCurve.data_path == "rotation_euler" and FCurve.array_index == 1: rot[0] = FCurve.evaluate(KeyframeTime)
1436                 ##        rot[1] = 0
1437                 ##        for FCurve in Action.fcurves:
1438                 ##            if FCurve.data_path == "rotation_euler" and FCurve.array_index == 2: rot[1] = FCurve.evaluate(KeyframeTime)
1439                 ##        rot[2] = 0
1440                 ##        for FCurve in Action.fcurves:
1441                 ##            if FCurve.data_path == "rotation_euler" and FCurve.array_index == 3: rot[2] = FCurve.evaluate(KeyframeTime)
1442                 ##        rot = rot.to_quaternion()
1443                 ##        animFile.write("\t\trot {%.9f,%.9f,%.9f,%.9f}\n" % (rot[0], rot[1], rot[2], rot[3]))
1444                 ##        #scale
1445                 ##        scalex = 0
1446                 ##        for FCurve in Action.fcurves:
1447                 ##            if FCurve.data_path == "scale" and FCurve.array_index == 0: scalex = FCurve.evaluate(KeyframeTime)
1448                 ##        scaley = 0
1449                 ##        for FCurve in Action.fcurves:
1450                 ##            if FCurve.data_path == "scale" and FCurve.array_index == 1: scaley = FCurve.evaluate(KeyframeTime)
1451                 ##        scalez = 0
1452                 ##        for FCurve in Action.fcurves:
1453                 ##            if FCurve.data_path == "scale" and FCurve.array_index == 2: scalez = FCurve.evaluate(KeyframeTime)
1454                 ##        animFile.write("\t\t//scale {%.9f,%.9f,%.9f}\n" % (scalex, scaley, scalez))
1455                 ##        #keyframe done
1456                 ##        animFile.write("\t}\n")
1457                 ##    animFile.write("}\n")
1458                 ##    animFile.close()
1459                 """
1460             else:
1461                 if Config.Verbose:
1462                     print("    Object %s has no useable animation data." % (StripName(Object.name)))
1463
1464             if Config.ExportArmatures and Object.type == "ARMATURE":
1465                 if Config.Verbose:
1466                     print("    Writing Armature Bone Animation Data...\n")
1467                 PoseBones = Object.pose.bones
1468                 Bones = Object.data.bones
1469                 #riged bones animated 
1470                 #build key frame time list
1471                 keyframeTimes = set()
1472                 if Config.ExportAnimation==1:
1473                     # Exports only key frames
1474                     for FCurve in Action.fcurves:
1475                         for Keyframe in FCurve.keyframe_points:
1476                             if Keyframe.co[0] < Scene.frame_start:
1477                                 keyframeTimes.add(Scene.frame_start)
1478                             elif Keyframe.co[0] > Scene.frame_end:
1479                                 keyframeTimes.add(Scene.frame_end)
1480                             else:
1481                                 keyframeTimes.add(int(Keyframe.co[0]))
1482                 else:
1483                     # Exports all frame
1484                     keyframeTimes.update(range(scene.frame_start, scene.frame_end + 1, 1))
1485                    
1486                 keyframeTimes = list(keyframeTimes)
1487                 keyframeTimes.sort()
1488                 if len(keyframeTimes):
1489                     #Create the anim file
1490                     animfullname = os.path.dirname(Config.FilePath) + "\\anims\\%s.anim" % (StripName(Object.name))
1491                     ensure_dir(animfullname)
1492                     if Config.Verbose:
1493                         print("      Creating anim file (bones animation) %s\n" % (animfullname))
1494                         print("      Frame count %d \n" % (len(keyframeTimes)))
1495                     animFile = open(animfullname, "w")
1496                     animFile.write('// anim file exported from : %r\n' % os.path.basename(bpy.data.filepath))   
1497                     animFile.write("CIwAnim\n")
1498                     animFile.write("{\n")
1499                     animFile.write("\tskeleton \"%s\"\n" % (StripName(Object.name)))
1500                     animFile.write("\t\t\n")
1501
1502                     Config.File.write("\t\".\\anims\\%s.anim\"\n" % (StripName(Object.name)))
1503
1504                     for KeyframeTime in keyframeTimes:
1505                         if Config.Verbose:
1506                             print("     Writing Frame %d:" % KeyframeTime)
1507                         animFile.write("\tCIwAnimKeyFrame\n")
1508                         animFile.write("\t{\n")
1509                         animFile.write("\t\ttime %.2f // frame num %d \n" % (KeyframeTime / Config.AnimFPS, KeyframeTime))
1510                         #for every frame write bones positions
1511                         Scene.frame_set(KeyframeTime)
1512                         for PoseBone in PoseBones:
1513                             if Config.Verbose:
1514                                 print("      Writing Bone: {}...".format(PoseBone.name))
1515                             animFile.write("\t\t\n")
1516
1517                             Bone = Bones[PoseBone.name]
1518                             WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, animFile, False)
1519                         #keyframe done
1520                         animFile.write("\t}\n")
1521                     animFile.write("}\n")
1522                     animFile.close()
1523             else:
1524                 if Config.Verbose:
1525                     print("    Object %s has no useable animation data." % (StripName(Object.name)))
1526         if Config.Verbose:
1527             print("  Done") #Done with Object
1528  
1529
1530                                 
1531  
1532 ################## Utilities
1533             
1534 def StripBoneName(name):
1535     return name.replace(" ", "")
1536
1537
1538 def StripName(Name):
1539     
1540     def ReplaceSet(String, OldSet, NewChar):
1541         for OldChar in OldSet:
1542             String = String.replace(OldChar, NewChar)
1543         return String
1544     
1545     import string
1546     
1547     NewName = ReplaceSet(Name, string.punctuation + " ", "_")
1548     return NewName
1549
1550
1551 def ensure_dir(f):
1552     d = os.path.dirname(f)
1553     if not os.path.exists(d):
1554         os.makedirs(d)
1555         
1556
1557 def CloseFile(Config):
1558     if Config.Verbose:
1559         print("Closing File...")
1560     Config.File.close()
1561     if Config.Verbose:
1562         print("Done")
1563
1564
1565 CoordinateSystems = (
1566     ("1", "Left-Handed", ""),
1567     ("2", "Right-Handed", ""),
1568     )
1569
1570
1571 AnimationModes = (
1572     ("0", "None", ""),
1573     ("1", "Keyframes Only", ""),
1574     ("2", "Full Animation", ""),
1575     )
1576
1577 ExportModes = (
1578     ("1", "All Objects", ""),
1579     ("2", "Selected Objects", ""),
1580     )
1581
1582 MergeModes = (
1583     ("0", "None", ""),
1584     ("1", "Merge in one big Mesh", ""),
1585     ("2", "Merge in unique Geo File containing several meshes", ""),
1586     )
1587
1588
1589 from bpy.props import StringProperty, EnumProperty, BoolProperty, IntProperty
1590
1591
1592 class MarmaladeExporter(bpy.types.Operator):
1593     """Export to the Marmalade model format (.group)"""
1594
1595     bl_idname = "export.marmalade"
1596     bl_label = "Export Marmalade"
1597
1598     filepath = StringProperty(subtype='FILE_PATH')
1599      #Export Mode
1600     ExportMode = EnumProperty(
1601         name="Export",
1602         description="Select which objects to export. Only Mesh, Empty, " \
1603                     "and Armature objects will be exported",
1604         items=ExportModes,
1605         default="1")
1606
1607     MergeModes = EnumProperty(
1608         name="Merge",
1609         description="Select if objects should be merged in one Geo File (it can be usefull if a scene is done by several cube/forms)." \
1610                     "Do not merge rigged character that have an armature.",
1611         items=MergeModes,
1612         default="0")
1613     
1614     #General Options
1615     Scale = IntProperty(
1616         name="Scale Percent",
1617         description="Scale percentage applied for export",
1618         default=100, min=1, max=1000)
1619     
1620     FlipNormals = BoolProperty(
1621         name="Flip Normals",
1622         description="",
1623         default=False)
1624     ApplyModifiers = BoolProperty(
1625         name="Apply Modifiers",
1626         description="Apply object modifiers before export",
1627         default=False)
1628     ExportVertexColors = BoolProperty(
1629         name="Export Vertices Colors",
1630         description="Export colors set on vertices, if any",
1631         default=True)
1632     ExportMaterialColors = BoolProperty(
1633         name="Export Material Colors",
1634         description="Ambient color is exported on the Material",
1635         default=True)
1636     ExportTextures = BoolProperty(
1637         name="Export Textures and UVs",
1638         description="Exports UVs and Reference external image files to be used by the model",
1639         default=True)
1640     CopyTextureFiles = BoolProperty(
1641         name="Copy Textures Files",
1642         description="Copy referenced Textures files in the models\\textures directory",
1643         default=True)
1644     ExportArmatures = BoolProperty(
1645         name="Export Armatures",
1646         description="Export the bones of any armatures to deform meshes",
1647         default=True)
1648     ExportAnimation = EnumProperty(
1649         name="Animations",
1650         description="Select the type of animations to export. Only object " \
1651                     "and armature bone animations can be exported. Full " \
1652                     "Animation exports every frame",
1653         items=AnimationModes,
1654         default="1")
1655     if bpy.context.scene:
1656         defFPS = bpy.context.scene.render.fps
1657     else:
1658         defFPS = 30                 
1659     AnimFPS = IntProperty(
1660         name="Animation FPS",
1661         description="Frame rate used to export animation in seconds (can be used to artficially slow down the exported animation, or to speed up it",
1662         default=defFPS, min=1, max=300)
1663
1664     #Advance Options
1665     Optimized = BoolProperty(
1666         name="Optimized the Vertices count",
1667         description="Optimize the vertices counts, uncheck if you fill that exported normals or vertex colors are not suitable",
1668         default=True)
1669      
1670     CoordinateSystem = EnumProperty(
1671         name="System",
1672         description="Select a coordinate system to export to",
1673         items=CoordinateSystems,
1674         default="1")
1675     
1676     Verbose = BoolProperty(
1677         name="Verbose",
1678         description="Run the exporter in debug mode. Check the console for output",
1679         default=True)
1680
1681     def execute(self, context):
1682         #Append .group
1683         FilePath = bpy.path.ensure_ext(self.filepath, ".group")
1684
1685         Config = MarmaladeExporterSettings(context,
1686                                          FilePath,
1687                                          CoordinateSystem=self.CoordinateSystem,
1688                                          Optimized=self.Optimized,
1689                                          FlipNormals=self.FlipNormals,
1690                                          ApplyModifiers=self.ApplyModifiers,
1691                                          Scale=self.Scale,
1692                                          AnimFPS=self.AnimFPS,
1693                                          ExportVertexColors=self.ExportVertexColors,
1694                                          ExportMaterialColors=self.ExportMaterialColors,
1695                                          ExportTextures=self.ExportTextures,
1696                                          CopyTextureFiles=self.CopyTextureFiles,
1697                                          ExportArmatures=self.ExportArmatures,
1698                                          ExportAnimation=self.ExportAnimation,
1699                                          ExportMode=self.ExportMode,
1700                                          MergeModes=self.MergeModes,
1701                                          Verbose=self.Verbose)
1702
1703         # Exit edit mode before exporting, so current object states are exported properly.
1704         if bpy.ops.object.mode_set.poll():
1705             bpy.ops.object.mode_set(mode='OBJECT')
1706
1707         ExportMadeWithMarmaladeGroup(Config)
1708         return {"FINISHED"}
1709
1710     def invoke(self, context, event):
1711         if not self.filepath:
1712             self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".group")
1713         WindowManager = context.window_manager
1714         WindowManager.fileselect_add(self)
1715         return {"RUNNING_MODAL"}
1716
1717
1718 def menu_func(self, context):
1719     self.layout.operator(MarmaladeExporter.bl_idname, text="Marmalade cross-platform Apps (.group)")
1720
1721
1722 def register():
1723     bpy.utils.register_module(__name__)
1724
1725     bpy.types.INFO_MT_file_export.append(menu_func)
1726
1727
1728 def unregister():
1729     bpy.utils.unregister_module(__name__)
1730
1731     bpy.types.INFO_MT_file_export.remove(menu_func)
1732
1733
1734 if __name__ == "__main__":
1735     register()