Marmalade user feedback: more flexible on Skin management: all unassigned vertices...
[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, 3),
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(SCALE_MAT * X_ROT * Object.matrix_world)
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                 if len(GeoModel.useBonesDict) > 0:
331                     # some mesh was not modified by the armature. so we must skinned the merged mesh.
332                     # So unskinned vertices from unarmatured meshes, are assigned to the root bone of the armature
333                     for i in range(0, len(GeoModel.vList)):
334                         if not i in GeoModel.skinnedVertices:
335                             GeoModel.skinnedVertices.append(i)
336                             useBonesKey = pow(2, GeoModel.armatureRootBoneIndex)
337                             vertexGroupIndices = list((GeoModel.armatureRootBoneIndex,))
338                             if useBonesKey not in GeoModel.useBonesDict:                          
339                                 GeoModel.mapVertexGroupNames[GeoModel.armatureRootBoneIndex] = StripBoneName(GeoModel.armatureRootBone.name)
340                                 VertexList = []
341                                 VertexList.append("\t\tvertWeights { %d, 1.0}" % i)
342                                 GeoModel.useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
343                             else:
344                                 pair_ListGroupIndices_ListAssignedVertices = GeoModel.useBonesDict[useBonesKey]
345                                 pair_ListGroupIndices_ListAssignedVertices[1].append("\t\tvertWeights { %d, 1.0}" % i)
346                                 GeoModel.useBonesDict[useBonesKey] = pair_ListGroupIndices_ListAssignedVertices
347                     # now generates the skin file
348                     PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, GeoModel.name)
349             if Config.MergeModes > 0:
350                 WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel)
351                 FinalizeGeoMtlFiles(Config, geoFile, mtlFile)
352         geoFile = None
353         mtlFile = None
354         GeoModel = None
355
356
357 def CreateGeoMtlFiles(Config, Name):
358     #Create the geo file
359     geofullname = os.path.dirname(Config.FilePath) + ("\models\%s.geo" % Name)
360     ensure_dir(geofullname)
361     if Config.Verbose:
362         print("      Creating geo file %s" % (geofullname))  
363     geoFile = open(geofullname, "w")
364     geoFile.write('// geo file exported from : %r\n' % os.path.basename(bpy.data.filepath))
365     geoFile.write("CIwModel\n")
366     geoFile.write("{\n")
367     geoFile.write("\tname \"%s\"\n" % Name)
368     # add it to the group
369     Config.File.write("\t\".\models\%s.geo\"\n" % Name)
370
371     # Create the mtl file
372     mtlfullname = os.path.dirname(Config.FilePath) + "\models\%s.mtl" % (Name)
373     ensure_dir(mtlfullname)
374     if Config.Verbose:
375         print("      Creating mtl file %s" % (mtlfullname))
376     mtlFile = open(mtlfullname, "w")
377     mtlFile.write('// mtl file exported from : %r\n' % os.path.basename(bpy.data.filepath))   
378     return geoFile, mtlFile
379
380
381 def FinalizeGeoMtlFiles(Config, geoFile, mtlFile):
382     if Config.Verbose:
383         print("      Closing geo file")  
384     geoFile.write("}\n")
385     geoFile.close()
386     if Config.Verbose:
387         print("      Closing mtl file")  
388     mtlFile.close()
389
390
391 def WriteMesh(Config, Object, Mesh,  geoFile=None, mtlFile=None, GeoModel=None):
392     if geoFile == None or mtlFile == None:
393         print (" ERROR not geo file arguments in WriteMesh method")
394         return
395
396     if Config.Optimized:
397         if GeoModel == None:
398             print (" ERROR not GeoModel arguments in WriteMesh method")
399             return
400
401         BuildOptimizedGeo(Config, Object, Mesh, GeoModel)
402         if Config.MergeModes == 0 or Config.MergeModes == 2:
403             #if we don't merge, or if we write several meshes into one file ... write the mesh everytime we do an object
404             GeoModel.PrintGeoMesh(geoFile)
405  
406     else:
407         #exports not optimized by face (duplicated vertex, normals might be better in rare cases)
408         if Config.Verbose:
409             print("      Writing Mesh Vertices and normals...")
410         WriteMeshVerticesAndNormals(Config, Object, Mesh, geoFile)
411         if Config.Verbose:
412             print("      Done\n      Writing Mesh Vertices and Normals...")
413         bVertexColors = False
414         if Config.ExportVertexColors and (len(Mesh.vertex_colors) > 0):
415             if Config.Verbose:
416                 print("      Writing Mesh Vertices Colors...")
417             bVertexColors = WriteMeshVerticesColors(Config, Mesh, geoFile)
418             if Config.Verbose:
419                 print("      Done")
420         bUVTextures = False
421         if Config.ExportTextures and (len(Mesh.uv_textures) > 0):
422             if Config.Verbose:
423                 print("      Writing Mesh UV Coordinates...")
424             bUVTextures = WriteMeshUVCoordinates(Config, Mesh, geoFile)
425             if Config.Verbose:
426                 print("      Done")
427         if Config.Verbose:
428             print("      Writing Poly QuadsTris...")
429         WriteMeshPoly(Config, Mesh, geoFile, bVertexColors, bUVTextures)
430         if Config.Verbose:
431             print("      Done")
432     if Config.Verbose:
433         print("      Done\n      Writing Mesh Materials...")
434
435     if Config.MergeModes == 0:
436         #No merge, so we can diretly write the Mtl file associated to this object
437         if Config.Optimized == False:
438             WriteMeshMaterials(Config, Mesh, mtlFile)
439             geoFile.write("\t}\n")
440         else:
441             WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel)
442
443     if Config.Verbose:
444         print("      Done")
445   
446     if Config.ExportArmatures:
447         if Config.Verbose:
448             print("      Writing Mes Weights...")
449         if Config.Optimized:
450             WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel)
451         else:
452             WriteMeshSkinWeights(Config, Object, Mesh)
453         if Config.Verbose:
454             print("      Done")
455
456
457 ###### optimized version fo Export, can be used also to merge several object in one single geo File ######
458
459 # CGeoModel
460 #  -> List Vertices
461 #  -> List Normales
462 #  -> List uv 0
463 #  -> List uv 1
464 #  -> List Vertex Colors
465 #  -> List Materials
466 #       -> Material name
467 #       -> Blender Material Object
468 #       -> List Tris -> Stream Indices v,vn,uv0,uv1,vc
469 #       -> List Quads -> Stream Indices v,vn,uv0,uv1,vc
470
471
472 #############
473 #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
474 #############                           
475 class CGeoIndexList:
476     __slots__ = "v", "vn", "uv0", "uv1", "vc"
477     
478     def __init__(self, v, vn, uv0, uv1, vc):
479         self.v = v
480         self.vn = vn
481         self.uv0 = uv0
482         self.uv1 = uv1
483         self.vc = vc
484
485         
486 #############
487 #Store a Quad or a Tri in marmalade geo format : 3 or 4 CIndexList depending it is a Tri or a Quad
488 #############                        
489 class CGeoPoly:
490     __slots__ = "pointsList",
491     
492     def __init__(self):
493         self.pointsList = []
494
495     def AddPoint(self, v, vn, uv0, uv1, vc):
496         self.pointsList.append( CGeoIndexList(v, vn, uv0, uv1, vc))
497
498     def PointsCount(self):
499         return len(self.pointsList)
500
501     def PrintPoly(self, geoFile):
502         if len(self.pointsList) == 3:
503             geoFile.write("\t\t\t\tt ")
504         if len(self.pointsList) == 4:
505             geoFile.write("\t\t\t\tq ")
506         for point in self.pointsList:
507             geoFile.write(" {%d, %d, %d, %d, %d}" % (point.v, point.vn, point.uv0, point.uv1, point.vc))
508         geoFile.write("\n")
509
510
511 #############
512 #Store all the poly (tri or quad) assigned to a Material in marmalade geo format
513 #############                        
514 class CGeoMaterialPolys:
515     __slots__ = "name", "material", "quadList", "triList", "currentPoly"
516     
517     def __init__(self, name, material=None):
518         self.name = name
519         self.material = material
520         self.quadList = []
521         self.triList = []
522         self.currentPoly = None
523
524     def BeginPoly(self):
525         self.currentPoly = CGeoPoly()
526
527     def AddPoint(self, v, vn, uv0, uv1, vc):
528         self.currentPoly.AddPoint(v, vn, uv0, uv1, vc)       
529              
530     def EndPoly(self):
531         if (self.currentPoly.PointsCount() == 3):
532             self.triList.append(self.currentPoly)
533         if (self.currentPoly.PointsCount() == 4):
534             self.quadList.append(self.currentPoly)
535         self.currentPoly = None
536
537     def ClearPolys(self):
538         self.quadList = []
539         self.triList = []
540         self.currentPoly = None
541
542     def PrintMaterialPolys(self, geoFile):
543         geoFile.write("\t\tCSurface\n")
544         geoFile.write("\t\t{\n")
545         geoFile.write("\t\t\tmaterial \"%s\"\n" % self.name)
546         if self.triList:
547             geoFile.write("\t\t\tCTris\n")
548             geoFile.write("\t\t\t{\n")
549             geoFile.write("\t\t\t\tnumTris %d\n" % (len(self.triList)))
550             for poly in self.triList:
551                 poly.PrintPoly(geoFile)
552             geoFile.write("\t\t\t}\n")
553
554         if self.quadList:
555             geoFile.write("\t\t\tCQuads\n")
556             geoFile.write("\t\t\t{\n")
557             geoFile.write("\t\t\t\tnumQuads %d\n" % (len(self.quadList)))
558             for poly in self.quadList:
559                 poly.PrintPoly(geoFile)
560             geoFile.write("\t\t\t}\n")
561         geoFile.write("\t\t}\n")
562
563
564 #############
565 #Store all the information on a Model/Mesh (vertices, normal, certcies color, uv0, uv1, TRI, QUAD) in marmalade geo format
566 #############  
567 class CGeoModel:
568     __slots__ = ("name", "MaterialsDict", "vList", "vnList", "vcList", "uv0List", "uv1List",
569                 "currentMaterialPolys", "vbaseIndex","vnbaseIndex", "uv0baseIndex", "uv1baseIndex",
570                 "armatureObjectName", "useBonesDict", "mapVertexGroupNames", "armatureRootBone", "armatureRootBoneIndex", "skinnedVertices")
571                 
572     def __init__(self, name):
573         self.name = name
574         self.MaterialsDict = {}
575         self.vList = []
576         self.vnList = []
577         self.vcList = []
578         self.uv0List = []
579         self.uv1List = []
580         self.currentMaterialPolys = None
581         #used xx baseIndex are used when merging several blender objects into one Mesh in the geo file (internal offset)
582         self.vbaseIndex = 0
583         self.vnbaseIndex = 0
584         self.uv0baseIndex = 0
585         self.uv1baseIndex = 0
586
587         # Store some information for skin management , when we merge several object in one big mesh (MergeModes 1)
588         # can only work if in the object list only one is rigged with an armature... and if it is located in 0,0,0
589         self.armatureObjectName = ""
590         #useBonesKey : bit field, where each bit is a VertexGroup.Index): Sum(2^VertGroupIndex).
591         #useBonesDict[useBonesKey] = tuple(VertexGroups.group, list(Vertex))
592         self.useBonesDict = {}
593         self.mapVertexGroupNames = {}
594         self.armatureRootBone = None
595         self.armatureRootBoneIndex = 0
596         self.skinnedVertices = []
597
598
599
600     def AddVertex(self, vertex):
601         self.vList.append(vertex.copy())
602
603     def AddVertexNormal(self, vertexN):
604         self.vnList.append(vertexN.copy())
605
606     # 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)
607     def AddVertexUV0(self, u, v):
608         self.uv0List.append((u, v))
609         return len(self.uv0List) - 1 - self.uv0baseIndex 
610
611     def AddVertexUV1(self, u, v):
612         self.uv1List.append((u, v))
613         return len(self.uv1List) - 1 - self.uv1baseIndex 
614
615     # 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)
616     def AddVertexColor(self, r, g, b, a):
617         for i in range(0, len(self.vcList)):
618             col = self.vcList[i]
619             if col[0] == r and col[1] == g and col[2] == b and col[3] == a:
620                 return i
621
622         self.vcList.append((r, g, b, a))
623         return len(self.vcList)-1
624
625     def BeginPoly(self, MaterialName, material=None):
626         if MaterialName not in self.MaterialsDict:
627             self.currentMaterialPolys = CGeoMaterialPolys(MaterialName, material)
628         else:
629             self.currentMaterialPolys = self.MaterialsDict[MaterialName]
630         self.currentMaterialPolys.BeginPoly()
631
632     def AddPoint(self, v, vn, uv0, uv1, vc):
633         if v != -1:
634             v += self.vbaseIndex
635         if vn != -1:
636             vn += self.vnbaseIndex
637         if uv0 != -1:
638             uv0 += self.uv0baseIndex
639         if uv1 != -1:
640             uv1 += self.uv1baseIndex
641                 
642         self.currentMaterialPolys.AddPoint(v, vn, uv0, uv1, vc)       
643                               
644     def EndPoly(self):
645         self.currentMaterialPolys.EndPoly()
646         self.MaterialsDict[self.currentMaterialPolys.name] = self.currentMaterialPolys
647         self.currentMaterialPolys = None
648
649     def NewObject(self):
650         #used in Merge mode 1: allows to merge several blender objects into one Mesh.
651         self.vbaseIndex = len(self.vList)
652         self.vnbaseIndex = len(self.vnList)
653         self.uv0baseIndex = len(self.uv0List)
654         self.uv1baseIndex = len(self.uv1List)
655
656     def ClearAllExceptMaterials(self):
657         #used in Merge mode 2: one geo with several mesh
658         self.vList = []
659         self.vnList = []
660         self.vcList = []
661         self.uv0List = []
662         self.uv1List = []
663         self.currentMaterialPolys = None
664         self.vbaseIndex = 0
665         self.vnbaseIndex = 0
666         self.uv0baseIndex = 0
667         self.uv1baseIndex = 0
668         for GeoMaterialPolys in self.MaterialsDict.values():
669             GeoMaterialPolys.ClearPolys()
670         self.useBonesDict = {}
671         self.mapVertexGroupNames = {}
672         self.armatureObjectName = ""
673         self.armatureRootBone = None
674         self.armatureRootBoneIndex = 0
675         self.skinnedVertices = []
676
677     def PrintGeoMesh(self, geoFile):
678         geoFile.write("\tCMesh\n")
679         geoFile.write("\t{\n")
680         geoFile.write("\t\tname \"%s\"\n" % (StripName(self.name)))
681
682         if self.vList:
683             geoFile.write("\t\tCVerts\n")
684             geoFile.write("\t\t{\n")
685             geoFile.write("\t\t\tnumVerts %d\n" % len(self.vList))
686             for vertex in self.vList:
687                 geoFile.write("\t\t\tv { %.9f, %.9f, %.9f }\n" % (vertex[0], vertex[1], vertex[2]))                      
688             geoFile.write("\t\t}\n")
689
690         if self.vnList:
691             geoFile.write("\t\tCVertNorms\n")
692             geoFile.write("\t\t{\n")
693             geoFile.write("\t\t\tnumVertNorms  %d\n" % len(self.vnList))
694             for vertexn in self.vnList:
695                 geoFile.write("\t\t\tvn { %.9f, %.9f, %.9f }\n" % (vertexn[0], vertexn[1], vertexn[2]))                      
696             geoFile.write("\t\t}\n")
697
698         if self.vcList:
699             geoFile.write("\t\tCVertCols\n")
700             geoFile.write("\t\t{\n")
701             geoFile.write("\t\t\tnumVertCols %d\n" % len(self.vcList))
702             for color in self.vcList:
703                 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           
704             geoFile.write("\t\t}\n")
705
706         if self.uv0List:
707             geoFile.write("\t\tCUVs\n")
708             geoFile.write("\t\t{\n")
709             geoFile.write("\t\t\tsetID 0\n")
710             geoFile.write("\t\t\tnumUVs %d\n" % len(self.uv0List))
711             for uv in self.uv0List:
712                  geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (uv[0], uv[1]))                       
713             geoFile.write("\t\t}\n")
714
715         if self.uv1List:
716             geoFile.write("\t\tCUVs\n")
717             geoFile.write("\t\t{\n")
718             geoFile.write("\t\t\tsetID 1\n")
719             geoFile.write("\t\t\tnumUVs %d\n" % len(self.uv1List))
720             for uv in self.uv1List:
721                  geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (uv[0], uv[1]))                       
722             geoFile.write("\t\t}\n")
723
724         for GeoMaterialPolys in self.MaterialsDict.values():
725             GeoMaterialPolys.PrintMaterialPolys(geoFile)
726         geoFile.write("\t}\n")
727
728     def GetMaterialList(self):
729         return list(self.MaterialsDict.keys())
730
731     def GetMaterialByName(self, name):
732         if name in self.MaterialsDict:
733             return self.MaterialsDict[name].material
734         else:
735             return None       
736
737
738
739 #############
740 # iterates faces, vertices ... and store the information in the GeoModel container
741 def BuildOptimizedGeo(Config, Object, Mesh, GeoModel):
742     if GeoModel == None:
743         GeoModel = CGeoModel(filename, Object.name)
744
745     #Store Vertex stream, and Normal stream (use directly the order from blender collection
746     for Vertex in Mesh.vertices:
747         GeoModel.AddVertex(Vertex.co)
748         Normal = Vertex.normal
749         if Config.FlipNormals:
750             Normal = -Normal
751         GeoModel.AddVertexNormal(Normal)
752     #Check if some colors have been defined
753     vertexColours = None
754     if Config.ExportVertexColors and (len(Mesh.vertex_colors) > 0):
755         vertexColours = Mesh.vertex_colors[0].data
756
757     #Check if some uv coordinates have been defined
758     UVCoordinates = None
759     if Config.ExportTextures and (len(Mesh.uv_textures) > 0):
760         for UV in Mesh.uv_textures:
761             if UV.active_render:
762                 UVCoordinates = UV.data
763                 break
764
765     #Iterate on Faces and Store the poly (quad or tri) and the associate colors,UVs
766     for Face in Mesh.faces:
767         # stream for vertex (we use the same for normal)
768         Vertices = list(Face.vertices)
769         if Config.CoordinateSystem == 1:
770             Vertices = Vertices[::-1]
771         # stream for vertex colors
772         if vertexColours:
773             MeshColor = vertexColours[Face.index]
774             if len(Vertices) == 3:
775                 FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3))
776             else:
777                 FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3, MeshColor.color4))
778             if Config.CoordinateSystem == 1:
779                 FaceColors = FaceColors[::-1]
780             colorIndex = []
781             for color in FaceColors:
782                 index = GeoModel.AddVertexColor(color[0], color[1], color[2], 1)  #rgba => no alpha on vertex color in Blender so use 1
783                 colorIndex.append(index)
784         else:
785             colorIndex = list((-1,-1,-1,-1))
786
787         # stream for UV0 coordinates
788         if UVCoordinates:
789             uvFace = UVCoordinates[Face.index]
790             uvVertices = []
791             for uvVertex in uvFace.uv:
792                 uvVertices.append(tuple(uvVertex))
793             if Config.CoordinateSystem == 1:
794                 uvVertices = uvVertices[::-1]
795             uv0Index = []
796             for uvVertex in uvVertices:
797                 index = GeoModel.AddVertexUV0(uvVertex[0], 1 - uvVertex[1]) 
798                 uv0Index.append(index)
799         else:
800             uv0Index = list((-1, -1, -1, -1))
801
802         # stream for UV1 coordinates
803         uv1Index = list((-1, -1, -1, -1))
804
805         mat = None
806         # find the associated material
807         if Face.material_index < len(Mesh.materials):
808             mat = Mesh.materials[Face.material_index]
809         if mat:
810             matName =  mat.name
811         else:
812             matName = "NoMaterialAssigned"  # There is no material assigned in blender !!!, exporter have generated a default one          
813             
814         # now on the material, generates the tri/quad in v,vn,uv0,uv1,vc stream index
815         GeoModel.BeginPoly(matName, mat)
816
817         for i in range(0, len(Vertices)):
818             GeoModel.AddPoint(Vertices[i], Vertices[i], uv0Index[i], uv1Index[i], colorIndex[i])
819
820         GeoModel.EndPoly()
821
822                               
823 #############
824 # Get the list of Material in use by the CGeoModel
825 def WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel):
826     for matName in GeoModel.GetMaterialList():
827         Material = GeoModel.GetMaterialByName(matName)
828         WriteMaterial(Config, mtlFile, Material)
829
830
831 ##################### Not Optimized Export, we use a Face export
832
833
834 def WriteMeshVerticesAndNormals(Config,  Object, Mesh, geoFile):
835     # Not optimized, simply iterate Blender Face, and writes all face vertices
836     # Marmalade groups per material, and then groups per Tir and Quad.
837     # So generate vertices grouped together per Tri of the same material, and same quad of the same material
838     geoFile.write("\tCMesh\n")
839     geoFile.write("\t{\n")
840     geoFile.write("\t\tname \"%s\"\n" % (StripName(Object.name)))
841     geoFile.write("\t\tCVerts\n")
842     geoFile.write("\t\t{\n")
843     Index = 0
844     VertexCount = GetNonOptimizedMeshVertexCount(Mesh)
845     geoFile.write("\t\t\tnumVerts %d\n" % VertexCount)
846                 
847     if Config.Verbose:
848             print("      Writing Mesh vertices...%d => %d" % (len(Mesh.vertices), VertexCount))
849     matCount =  len(Mesh.materials)
850     if matCount == 0:
851         matCount = 1  #No material defined for the Mesh !!!! => generate a default Material
852     for matIndex in range(0, matCount):
853         if Config.Verbose:
854             print("      Material Index: %d >" % matIndex)
855         for polyCount in range(3, 5):
856             faceCount = 0
857             for Face in Mesh.faces:
858                 if Face.material_index == matIndex:
859                     if len(Face.vertices) == polyCount:
860                         Vertices = list(Face.vertices)
861                         faceCount = faceCount + 1                           
862                         if Config.CoordinateSystem == 1:
863                             Vertices = Vertices[::-1]
864                         for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
865                             Position = Vertex.co
866                             geoFile.write("\t\t\tv { %.9f, %.9f, %.9f }\n" % (Position[0], Position[1], Position[2]))            
867             if Config.Verbose and polyCount == 3:
868                 print("         Tri Poly count  Index: %d" % faceCount)
869             elif Config.Verbose and polyCount == 4:
870                 print("         Quad Poly count  Index: %d" % faceCount)
871     geoFile.write("\t\t}\n")
872
873     #WriteMeshNormals
874     geoFile.write("\t\tCVertNorms\n")
875     geoFile.write("\t\t{\n")
876     geoFile.write("\t\t\tnumVertNorms  %d\n" % VertexCount)
877       
878     if Config.Verbose:
879             print("      Writing Mesh normals...")
880     matCount =  len(Mesh.materials)
881     if matCount == 0:
882         matCount = 1  # No material defined for the Mesh !!!! => generate a default Material
883     for matIndex in range(0, matCount):
884         if Config.Verbose:
885             print("      Material Index: %d >" % matIndex)
886         for polyCount in range(3, 5):
887             faceCount = 0
888             for Face in Mesh.faces:
889                  if Face.material_index == matIndex:
890                     if len(Face.vertices) == polyCount:
891                         Vertices = list(Face.vertices)
892                         faceCount = faceCount + 1
893                         if Config.CoordinateSystem == 1:
894                             Vertices = Vertices[::-1]
895                         for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
896                             if Face.use_smooth:
897                                 Normal = Vertex.normal
898                             else:
899                                 Normal = Face.normal
900                             if Config.FlipNormals:
901                                 Normal = -Normal
902                             geoFile.write("\t\t\tvn { %.9f, %.9f, %.9f }\n" % (Normal[0], Normal[1], Normal[2]))            
903             if Config.Verbose and polyCount == 3:
904                 print("         Tri Poly count  Index: %d" % faceCount)
905             elif Config.Verbose and polyCount == 4:
906                 print("         Quad Poly count  Index: %d" % faceCount)
907     geoFile.write("\t\t}\n")
908
909
910 def WriteMeshVerticesColors (Config, Mesh, geoFile):
911     if len(Mesh.vertex_colors) > 0:
912         vertexColours = Mesh.vertex_colors[0].data
913         if len(vertexColours) > 0:
914             geoFile.write("\t\tCVertCols\n")
915             geoFile.write("\t\t{\n")
916             Index = 0
917             VertexCount = GetNonOptimizedMeshVertexCount(Mesh)
918             geoFile.write("\t\t\tnumVertCols %d\n" % VertexCount)
919                                 
920             if Config.Verbose:
921                     print("      Writing Mesh vertices Colors...%d" % (len(Mesh.vertices)))
922                     
923             matCount =  len(Mesh.materials)
924             if matCount == 0:
925                 matCount = 1  #No material defined for the Mesh !!!! => generate a default Material
926             for matIndex in range(0, matCount):
927                 if Config.Verbose:
928                     print("      Material Index: %d >" % matIndex)
929                 for polyCount in range(3, 5):
930                     faceCount = 0
931                     for Face in Mesh.faces:
932                         if Face.material_index == matIndex:
933                             if len(Face.vertices) == polyCount:
934                                 Vertices = list(Face.vertices)
935                                 print("       - Face Index: %d / %d" % (Face.index, len(vertexColours)))
936                                 print(vertexColours)
937                                 MeshColor = vertexColours[Face.index]
938                                 if polyCount == 3:
939                                     FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3))
940                                 else:
941                                     FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3, MeshColor.color4))
942                                 faceCount = faceCount + 1                           
943                                 if Config.CoordinateSystem == 1:
944                                     Vertices = Vertices[::-1]
945                                     FaceColors = FaceColors[::-1]
946                                 for color in FaceColors:
947                                     geoFile.write("\t\t\tcol { %.6f, %.6f, %.6f, 1 }\n" % (color[0], color[1], color[2]))            
948                     if Config.Verbose and polyCount == 3:
949                         print("         Tri Poly count  Index: %d" % faceCount)
950                     elif Config.Verbose and polyCount == 4:
951                         print("         Quad Poly count  Index: %d" % faceCount)
952             geoFile.write("\t\t}\n")
953             return True
954     return False
955
956
957 def WriteMeshUVCoordinates(Config, Mesh, geoFile):
958     geoFile.write("\t\tCUVs\n")
959     geoFile.write("\t\t{\n")
960     geoFile.write("\t\t\tsetID 0\n")
961     
962     UVCoordinates = None
963     for UV in Mesh.uv_textures:
964         if UV.active_render:
965             UVCoordinates = UV.data
966             break
967     if UVCoordinates:
968         uvCount = 0
969         VertexCount = GetNonOptimizedMeshVertexCount(Mesh)
970         geoFile.write("\t\t\tnumUVs %d\n" % VertexCount)
971
972         if Config.Verbose:
973             print("      Writing Mesh UVs...")
974         matCount =  len(Mesh.materials)
975         if matCount == 0:
976             matCount = 1  #No material defined for the Mesh !!!! => generate a default Material
977         for matIndex in range(0, matCount):
978             if Config.Verbose:
979                 print("      Material Index: %d >" % matIndex)
980             for polyCount in range(3, 5):
981                 faceCount = 0         
982                 #for Face in UVCoordinates:
983                 #for Face in Mesh.faces:
984                 for i in range(0, len(Mesh.faces)):
985                     Face = Mesh.faces[i]
986                     uvFace = UVCoordinates[i]
987                     Vertices = []
988                     if Face.material_index == matIndex:
989                         if len(Face.vertices) == polyCount:
990                             for Vertex in uvFace.uv:
991                                 Vertices.append(tuple(Vertex))
992                             if Config.CoordinateSystem == 1:
993                                 Vertices = Vertices[::-1]
994                             for Vertex in Vertices:
995                                 geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (Vertex[0], 1 - Vertex[1]))                        
996                             faceCount = faceCount + 1
997                             uvCount = uvCount + len(Vertices)
998                 if Config.Verbose and polyCount == 3:
999                     print("         Tri Poly count  Index: %d" % faceCount)
1000                 elif Config.Verbose and polyCount == 4:
1001                     print("         Quad Poly count  Index: %d" % faceCount)
1002
1003         geoFile.write("\t\t}\n")
1004         if Config.Verbose:
1005              print("         Total UVCount : %d" % uvCount)
1006         return True
1007     return False
1008
1009
1010
1011 def WriteMeshPoly(Config, Mesh, geoFile, bVertexColors, bUVTextures):
1012     # groups per tri and per Quad belonging to the same material
1013     Index = 0
1014     VertexCount = GetNonOptimizedMeshVertexCount(Mesh)
1015
1016     matCount =  len(Mesh.materials)
1017     if matCount == 0:
1018         matCount = 1  #No material defined for the Mesh !!!! => generate a default Material
1019     for matIndex in range(0, matCount):
1020         if Config.Verbose:
1021             print("      Material Index: %d >" % matIndex)
1022             
1023         #first check if there is Tri, Quad , or both, or ... none :-)
1024         TriCount = 0
1025         QuadCount = 0
1026         for polyCount in range(3, 5):
1027             for Face in Mesh.faces:
1028                 if Face.material_index == matIndex:
1029                     if len(Face.vertices) == polyCount:
1030                         if polyCount == 3:
1031                             TriCount = TriCount + 1
1032                         elif polyCount == 4:
1033                             QuadCount = QuadCount + 1
1034                             
1035         if Config.Verbose:
1036             print("            Poly Count Tris %d - Quads %d " % (TriCount, QuadCount))
1037             
1038         if TriCount > 0 or QuadCount > 0:
1039             geoFile.write("\t\tCSurface\n")
1040             geoFile.write("\t\t{\n")
1041             if matIndex < len(Mesh.materials):
1042                 geoFile.write("\t\t\tmaterial \"%s\"\n" % Mesh.materials[matIndex].name)
1043             else:
1044                 geoFile.write("\t\t\tmaterial NoMaterialAssigned // There is no material assigned in blender !!!, exporter have generated a default one\n")
1045             streamIndex = 0
1046             #Write the Tri for this material, if any
1047             if TriCount > 0:
1048                 geoFile.write("\t\t\tCTris\n")
1049                 geoFile.write("\t\t\t{\n")
1050                 geoFile.write("\t\t\t\tnumTris %d\n" % (TriCount))
1051                 for Face in Mesh.faces:
1052                     if Face.material_index == matIndex:
1053                         if len(Face.vertices) == 3:
1054                             vc1 = vc2 = vc3 = -1
1055                             if bVertexColors:
1056                                 vc1 = streamIndex
1057                                 vc2 = streamIndex + 1
1058                                 vc3 = streamIndex + 2
1059                             uv1 = uv2 = uv3 = -1
1060                             if bUVTextures:
1061                                 uv1 = streamIndex
1062                                 uv2 = streamIndex+1
1063                                 uv3 = streamIndex+2
1064                             geoFile.write("\t\t\t\tt {%d, %d, %d, -1, %d} {%d, %d, %d, -1, %d} {%d, %d, %d, -1, %d}\n"
1065                                           % (streamIndex, streamIndex, uv1, vc1, streamIndex+1, streamIndex+1, uv2, vc2, streamIndex+2, streamIndex+2, uv3, vc3))
1066                             streamIndex = streamIndex + 3
1067                 geoFile.write("\t\t\t}\n")
1068             #Write the Quad for this material, if any
1069             if QuadCount > 0:
1070                 geoFile.write("\t\t\tCQuads\n")
1071                 geoFile.write("\t\t\t{\n")
1072                 geoFile.write("\t\t\t\tnumQuads %d\n" % (QuadCount))
1073                 for Face in Mesh.faces:
1074                     if Face.material_index == matIndex:
1075                         if len(Face.vertices) == 4:
1076                             vc1 = vc2 = vc3 = vc4 = -1
1077                             if bVertexColors:
1078                                 vc1 = streamIndex
1079                                 vc2 = streamIndex + 1
1080                                 vc3 = streamIndex + 2
1081                                 vc3 = streamIndex + 3
1082                             uv1 = uv2 = uv3 = uv4 = -1
1083                             if bUVTextures:
1084                                 uv1 = streamIndex
1085                                 uv2 = streamIndex + 1
1086                                 uv3 = streamIndex + 2
1087                                 uv4 = streamIndex + 3
1088                             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"
1089                                           % (streamIndex, streamIndex, uv1, vc1, streamIndex+1, streamIndex+1, uv2, vc2,
1090                                              streamIndex+2, streamIndex+2, uv3, vc3, streamIndex+3, streamIndex+3, uv4, vc4))
1091                             streamIndex = streamIndex + 4
1092                 geoFile.write("\t\t\t}\n")
1093
1094             geoFile.write("\t\t}\n")
1095
1096
1097 def WriteMeshMaterials(Config, Mesh, mtlFile):    
1098     Materials = Mesh.materials
1099     if Materials.keys():
1100         for Material in Materials:
1101             WriteMaterial(Config, mtlFile, Material)
1102     else:
1103         if Config.Verbose :
1104             print("         NO MATERIAL ASSIGNED TO THE MESH in Blender !!! generating a default material")
1105         WriteMaterial(Config, mtlFile)         
1106
1107
1108 def WriteMaterial(Config, mtlFile, Material=None):
1109     mtlFile.write("CIwMaterial\n")
1110     mtlFile.write("{\n")
1111     if Material:
1112         mtlFile.write("\tname \"%s\"\n" % Material.name)
1113
1114         if Config.ExportMaterialColors:
1115             #if bpy.context.scene.world:
1116             #    MatAmbientColor = Material.ambient * bpy.context.scene.world.ambient_color
1117             MatAmbientColor = Material.ambient * Material.diffuse_color
1118             mtlFile.write("\tcolAmbient {%.2f,%.2f,%.2f,%.2f} \n" % (MatAmbientColor[0] * 255, MatAmbientColor[1] * 255, MatAmbientColor[2] * 255, Material.alpha * 255))
1119             MatDiffuseColor = Material.diffuse_intensity * Material.diffuse_color
1120             mtlFile.write("\tcolDiffuse  {%.2f,%.2f,%.2f} \n" % (MatDiffuseColor * 255)[:])
1121             MatSpecularColor = Material.specular_intensity * Material.specular_color
1122             mtlFile.write("\tcolSpecular  {%.2f,%.2f,%.2f} \n" % (MatSpecularColor * 255)[:])
1123             # EmitColor = Material.emit * Material.diffuse_color
1124             # mtlFile.write("\tcolEmissive {%.2f,%.2f,%.2f} \n" % (EmitColor* 255)[:])    
1125     else:
1126         mtlFile.write("\tname \"NoMaterialAssigned\" // There is no material assigned in blender !!!, exporter have generated a default one\n")
1127
1128     #Copy texture
1129     if Config.ExportTextures:
1130         Texture = GetMaterialTextureFullPath(Config, Material)
1131         if Texture:
1132             mtlFile.write("\ttexture0 .\\textures\\%s\n" % (bpy.path.basename(Texture)))
1133             
1134             if Config.CopyTextureFiles:
1135                 if not os.path.exists(Texture):
1136                     #try relative path to the blend file
1137                     Texture = os.path.dirname(bpy.data.filepath) + Texture
1138                 if os.path.exists(Texture):
1139                     textureDest = os.path.dirname(Config.FilePath) + "\\models\\textures\\%s" % (bpy.path.basename(Texture))
1140                     ensure_dir(textureDest)
1141                     if Config.Verbose:
1142                         print("      Copying the texture file %s ---> %s" % (Texture, textureDest))
1143                     shutil.copy(Texture, textureDest)
1144                 else:
1145                     if Config.Verbose:
1146                         print("      CANNOT Copy texture file (not found) %s" % (Texture))
1147     mtlFile.write("}\n")
1148
1149 def GetFirstRootBone(ArmatureObject):
1150     ArmatureBones = ArmatureObject.data.bones
1151     ParentBoneList = [Bone for Bone in ArmatureBones if Bone.parent is None]
1152     if ParentBoneList:
1153         return ParentBoneList[0]
1154     return None
1155
1156
1157 def GetVertexGroupFromBone(Object, Bone):
1158     if Bone:
1159         rootBoneList = [VertexGroup for VertexGroup in Object.vertex_groups  if VertexGroup.name == Bone.name]
1160         if rootBoneList:
1161             return rootBoneList[0]
1162     return None
1163
1164
1165 def FindUniqueIndexForRootBone(Object, RootVertexGroup):
1166     if RootVertexGroup:
1167         return RootVertexGroup.index
1168     else:
1169         #If there is not VertexGroup associated to the root bone name, we don't have a vertex index.
1170         #so use the next available free index
1171         return len(Object.vertex_groups)
1172
1173
1174 def WriteMeshSkinWeights(Config, Object, Mesh):
1175     ArmatureList = [Modifier for Modifier in Object.modifiers if Modifier.type == "ARMATURE"]
1176     if ArmatureList:
1177         ArmatureObject = ArmatureList[0].object
1178         if ArmatureObject is None:
1179             return
1180         RootBone = GetFirstRootBone(ArmatureObject)
1181         RootVertexGroup = GetVertexGroupFromBone(Object, RootBone)
1182
1183         # Marmalade need to declare a vertex per list of affected bones
1184         # so first we have to get all the combinations of affected bones that exist in the mesh
1185         # 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
1186
1187         useBonesDict = {}
1188         #useBonesKey => pair_ListGroupIndices_ListAssignedVertices
1189         #useBonesDict[useBonesKey] = tuple(VertexGroups.group, list(Vertex))
1190
1191         mapVertexGroupNames = {} 
1192         matCount = len(Mesh.materials)
1193         if matCount == 0:
1194             matCount = 1 #No material defined for the Mesh !!!! => generate a default Material
1195         for matIndex in range(0, matCount):
1196             streamIndex = 0
1197             for polyCount in range(3, 5):
1198                 for Face in Mesh.faces:
1199                     if Face.material_index == matIndex:
1200                         if len(Face.vertices) == polyCount:
1201                             Vertices = list(Face.vertices)
1202                             if Config.CoordinateSystem == 1:
1203                                 Vertices = Vertices[::-1]
1204                             for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
1205                                 AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, useBonesDict, mapVertexGroupNames, streamIndex, RootBone, RootVertexGroup) 
1206                                 streamIndex = streamIndex + 1
1207
1208         PrintSkinWeights(Config, StripName(ArmatureObject.name), useBonesDict, mapVertexGroupNames, StripName(Object.name))
1209
1210          
1211 def WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel):
1212     ArmatureList = [Modifier for Modifier in Object.modifiers if Modifier.type == "ARMATURE"]
1213     if ArmatureList:
1214         ArmatureObject = ArmatureList[0].object
1215         if ArmatureObject is None:
1216             return
1217         RootBone = GetFirstRootBone(ArmatureObject)
1218         RootVertexGroup = GetVertexGroupFromBone(Object, RootBone)
1219
1220         GeoModel.armatureObjectName = StripName(ArmatureObject.name)
1221         if RootBone:
1222             GeoModel.armatureRootBone = RootBone
1223             GeoModel.armatureRootBoneIndex = FindUniqueIndexForRootBone(Object, RootVertexGroup)
1224
1225         # Marmalade need to declare a vertex per list of affected bones
1226         # so first we have to get all the combinations of affected bones that exist in the mesh
1227         # 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
1228         
1229         for Vertex in Mesh.vertices:
1230             VertexIndex = Vertex.index + GeoModel.vbaseIndex
1231             AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, VertexIndex, RootBone, RootVertexGroup)
1232             GeoModel.skinnedVertices.append(VertexIndex)
1233
1234         if Config.MergeModes != 1:
1235             # write skin file directly
1236             PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, StripName(Object.name))
1237
1238
1239 def PrintSkinWeights(Config, ArmatureObjectName, useBonesDict, mapVertexGroupNames, GeoName):        
1240         #Create the skin file
1241         skinfullname = os.path.dirname(Config.FilePath) + "\models\%s.skin" % GeoName
1242         ensure_dir(skinfullname)
1243         if Config.Verbose:
1244             print("      Creating skin file %s" % (skinfullname))
1245         skinFile = open(skinfullname, "w")
1246         skinFile.write('// skin file exported from : %r\n' % os.path.basename(bpy.data.filepath))   
1247         skinFile.write("CIwAnimSkin\n")
1248         skinFile.write("{\n")
1249         skinFile.write("\tskeleton \"%s\"\n" % ArmatureObjectName)
1250         skinFile.write("\tmodel \"%s\"\n" % GeoName)
1251
1252         # now we have Bones grouped in the dictionary , along with the associated influenced vertex weighting
1253         # So simply iterate the dictionary
1254         Config.File.write("\t\".\models\%s.skin\"\n" % GeoName)
1255         for pair_ListGroupIndices_ListAssignedVertices in useBonesDict.values():
1256             skinFile.write("\tCIwAnimSkinSet\n")
1257             skinFile.write("\t{\n")
1258             skinFile.write("\t\tuseBones {")
1259             for vertexGroupIndex in pair_ListGroupIndices_ListAssignedVertices[0]:
1260                 skinFile.write(" %s" % mapVertexGroupNames[vertexGroupIndex])
1261             skinFile.write(" }\n")
1262             skinFile.write("\t\tnumVerts %d\n" % len(pair_ListGroupIndices_ListAssignedVertices[1]))
1263             for VertexWeightString in pair_ListGroupIndices_ListAssignedVertices[1]:
1264                 skinFile.write(VertexWeightString)
1265             skinFile.write("\t}\n")
1266
1267         skinFile.write("}\n")
1268         skinFile.close()
1269
1270
1271 def AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, useBonesDict, mapVertexGroupNames, VertexIndex, RootBone, RootVertexGroup):
1272     #build useBones
1273     useBonesKey = 0
1274     vertexGroupIndices = []
1275     weightTotal = 0.0
1276     if (len(Vertex.groups)) > 4:
1277         print ("ERROR Vertex %d is influenced by more than 4 bones\n" % (VertexIndex))
1278     for VertexGroup in Vertex.groups:
1279         if (VertexGroup.weight > 0):
1280             mapVertexGroupNames[VertexGroup.group] = StripBoneName(Object.vertex_groups[VertexGroup.group].name)
1281             if (len(vertexGroupIndices))<4:  #ignore if more 4 bones are influencing the vertex
1282                 useBonesKey = useBonesKey + pow(2, VertexGroup.group)
1283                 vertexGroupIndices.append(VertexGroup.group)
1284                 weightTotal = weightTotal + VertexGroup.weight
1285     if (weightTotal == 0):
1286         bWeightTotZero = True  #avoid divide by zero later on
1287         if (RootBone):
1288             if Config.Verbose:
1289                 print(" Warning Weight is ZERO for vertex %d => Add it to the root bone" % (VertexIndex))
1290             RootBoneGroupIndex = FindUniqueIndexForRootBone(Object, RootVertexGroup)
1291             mapVertexGroupNames[RootBoneGroupIndex] = StripBoneName(RootBone.name)
1292             useBonesKey = pow(2, RootBoneGroupIndex)
1293             vertexGroupIndices = list((RootBoneGroupIndex,))
1294
1295             weightTotal = 1
1296     else:
1297         bWeightTotZero = False
1298     
1299     if len(vertexGroupIndices) > 0:
1300         vertexGroupIndices.sort();
1301            
1302         #build the vertex weight string: vertex indices, followed by influence weight for each bone
1303         VertexWeightString = "\t\tvertWeights { %d" % (VertexIndex)
1304         for vertexGroupIndex in vertexGroupIndices:
1305             #get the weight of this specific VertexGroup (aka bone)
1306             boneWeight = 1
1307             for VertexGroup in Vertex.groups:
1308                 if VertexGroup.group == vertexGroupIndex:
1309                     boneWeight = VertexGroup.weight
1310             #calculate the influence of this bone compared to the total of weighting applied to this Vertex
1311             if not bWeightTotZero:
1312                 VertexWeightString += ", %.7f" % (boneWeight / weightTotal)
1313             else:
1314                 VertexWeightString += ", %.7f" % (1.0 / len(vertexGroupIndices))
1315         VertexWeightString += "}"
1316         if bWeightTotZero:
1317             VertexWeightString += " // total weight was zero in blender , export assign it to the RootBone with weight 1." 
1318         if (len(Vertex.groups)) > 4:
1319             VertexWeightString += " // vertex is associated to more than 4 bones in blender !! skip some bone association (was associated to %d bones)." % (len(Vertex.groups))
1320         VertexWeightString += "\n"
1321            
1322         #store in dictionnary information
1323         if useBonesKey not in useBonesDict:
1324             VertexList = []
1325             VertexList.append(VertexWeightString)
1326             useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
1327         else:
1328             pair_ListGroupIndices_ListAssignedVertices = useBonesDict[useBonesKey]
1329             pair_ListGroupIndices_ListAssignedVertices[1].append(VertexWeightString)
1330             useBonesDict[useBonesKey] = pair_ListGroupIndices_ListAssignedVertices
1331     else:
1332         print ("ERROR Vertex %d is not skinned (it doesn't belong to any vertex group\n" % (VertexIndex)) 
1333
1334
1335
1336 ############# ARMATURE: Bone export, and Bone animation export 
1337
1338          
1339 def WriteArmatureParentRootBones(Config, Object, RootBonesList, skelFile):
1340
1341     if len(RootBonesList) > 1:
1342         print(" /!\\  WARNING ,Marmelade need only one ROOT bone per armature, there is %d root bones " % len(RootBonesList))
1343         print(RootBonesList)
1344         
1345     PoseBones = Object.pose.bones
1346     for Bone in RootBonesList:
1347         if Config.Verbose:
1348             print("      Writing Root Bone: {}...".format(Bone.name))
1349
1350         PoseBone = PoseBones[Bone.name]
1351         WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True)
1352         #WriteOneBoneRestPosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True, Vector(),Quaternion())
1353         if Config.Verbose:
1354             print("      Done")
1355         WriteArmatureChildBones(Config, Object, Bone.children, skelFile)
1356
1357             
1358 def WriteArmatureChildBones(Config, Object, BonesList, skelFile):
1359     PoseBones = Object.pose.bones
1360     for Bone in BonesList:
1361         if Config.Verbose:
1362             print("      Writing Child Bone: {}...".format(Bone.name))
1363         PoseBone = PoseBones[Bone.name]
1364         WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True)
1365         #WriteOneBoneRestPosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True, Vector(),Quaternion())
1366         if Config.Verbose:
1367             print("      Done")
1368             
1369         WriteArmatureChildBones(Config, Object, Bone.children, skelFile)
1370
1371
1372 def WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, File, isSkelFileNotAnimFile):
1373     # Compute armature scale : 
1374     # 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
1375     # Here we retreive the Scale of the Armture Object.matrix_world.to_scale() and we use it to scale the bones :-)
1376     # So new Blender user should not complain about bad animation export if they forgot to apply the Scale to 1,1,1
1377
1378     armScale = Object.matrix_world.to_scale()
1379     ## scalematrix = Matrix()
1380     ## scalematrix[0][0] = armScale.x * Config.Scale
1381     ## scalematrix[1][1] = armScale.y * Config.Scale
1382     ## scalematrix[2][2] = armScale.z * Config.Scale
1383
1384     if isSkelFileNotAnimFile:
1385         #skel file, bone header
1386         File.write("\tCIwAnimBone\n")
1387         File.write("\t{\n")
1388         File.write("\t\tname \"%s\"\n" % StripBoneName(Bone.name))
1389         if Bone.parent:
1390             File.write("\t\tparent \"%s\"\n" % StripBoneName(Bone.parent.name))
1391     else:
1392         #anim file, bone header
1393         File.write("\t\t\n")
1394         File.write("\t\tbone \"%s\" \n" % StripBoneName(Bone.name))
1395
1396     if Bone.parent:
1397         ParentPoseBone = PoseBones[Bone.parent.name]
1398         locmat = ParentPoseBone.matrix.inverted() * PoseBone.matrix
1399     else:
1400         locmat = PoseBone.matrix
1401         if Config.MergeModes > 0:
1402             # Merge mode is in world coordinates .. anyway merge mesh doesn't work really with armature that should be local to one mesh
1403             locmat = Object.matrix_world * PoseBone.matrix
1404             armScale.x =  armScale.y = armScale.z = 1  
1405         
1406     loc = locmat.to_translation()
1407     quat = locmat.to_quaternion()
1408   
1409     if not Bone.parent:  # and Config.MergeModes == 0:
1410         #flip Y Z axes (only on root bone, other bones are local to root bones, so no need to rotate)
1411         X_ROT = mathutils.Matrix.Rotation(-math.pi / 2, 4, 'X')
1412         quat.rotate(X_ROT)
1413         loc.rotate(X_ROT)
1414
1415         
1416     #Scale the bone
1417     loc.x *= (armScale.x * Config.Scale)
1418     loc.y *= (armScale.y * Config.Scale)
1419     loc.z *= (armScale.z * Config.Scale)
1420     
1421     File.write("\t\tpos { %.9f, %.9f, %.9f }\n" % (loc[0], loc[1], loc[2]))
1422     File.write("\t\trot { %.9f, %.9f, %.9f, %.9f }\n" % (quat.w, quat.x, quat.y, quat.z))
1423
1424     if isSkelFileNotAnimFile:
1425         File.write("\t}\n")
1426
1427       
1428 def WriteKeyedAnimationSet(Config, Scene):  
1429     for Object in [Object for Object in Config.ObjectList if Object.animation_data]:
1430         if Config.Verbose:
1431             print("  Writing Animation Data for Object: {}".format(Object.name))
1432         Action = Object.animation_data.action
1433         if Action:
1434             #Object animated (aka single bone object)
1435             #build key frame time list
1436             keyframeTimes = set()
1437             if Config.ExportAnimation == 1:
1438                 # Exports only key frames
1439                 for FCurve in Action.fcurves:
1440                     for Keyframe in FCurve.keyframe_points:
1441                         if Keyframe.co[0] < Scene.frame_start:
1442                             keyframeTimes.add(Scene.frame_start)
1443                         elif Keyframe.co[0] > Scene.frame_end:
1444                             keyframeTimes.add(Scene.frame_end)
1445                         else:
1446                             keyframeTimes.add(int(Keyframe.co[0]))
1447             else:
1448                 # Exports all frames
1449                 keyframeTimes.update(range(Scene.frame_start, Scene.frame_end + 1, 1))
1450             keyframeTimes = list(keyframeTimes)
1451             keyframeTimes.sort()
1452             if len(keyframeTimes):
1453                 #Create the anim file for offset animation (or single bone animation
1454                 animfullname = os.path.dirname(Config.FilePath) + "\\anims\\%s_offset.anim" % (StripName(Object.name))
1455                 #not yet supported
1456                 """
1457                 ##    ensure_dir(animfullname)
1458                 ##    if Config.Verbose:
1459                 ##        print("      Creating anim file (single bone animation) %s" % (animfullname))
1460                 ##    animFile = open(animfullname, "w")
1461                 ##    animFile.write('// anim file exported from : %r\n' % os.path.basename(bpy.data.filepath))   
1462                 ##    animFile.write("CIwAnim\n")
1463                 ##    animFile.write("{\n")
1464                 ##    animFile.write("\tent \"%s\"\n" % (StripName(Object.name)))
1465                 ##    animFile.write("\tskeleton \"SingleBone\"\n")
1466                 ##    animFile.write("\t\t\n")
1467                 ##
1468                 ##    Config.File.write("\t\".\\anims\\%s_offset.anim\"\n" % (StripName(Object.name)))
1469                 ##
1470                 ##    for KeyframeTime in keyframeTimes:
1471                 ##        #Scene.frame_set(KeyframeTime)    
1472                 ##        animFile.write("\tCIwAnimKeyFrame\n")
1473                 ##        animFile.write("\t{\n")
1474                 ##        animFile.write("\t\ttime %.2f // frame num %d \n" % (KeyframeTime/Config.AnimFPS, KeyframeTime))
1475                 ##        animFile.write("\t\t\n")
1476                 ##        animFile.write("\t\tbone \"SingleBone\" \n")
1477                 ##        #postion
1478                 ##        posx = 0
1479                 ##        for FCurve in Action.fcurves:
1480                 ##            if FCurve.data_path == "location" and FCurve.array_index == 0: posx = FCurve.evaluate(KeyframeTime)
1481                 ##        posy = 0
1482                 ##        for FCurve in Action.fcurves:
1483                 ##            if FCurve.data_path == "location" and FCurve.array_index == 1: posy = FCurve.evaluate(KeyframeTime)
1484                 ##        posz = 0
1485                 ##        for FCurve in Action.fcurves:
1486                 ##            if FCurve.data_path == "location" and FCurve.array_index == 2: posz = FCurve.evaluate(KeyframeTime)
1487                 ##        animFile.write("\t\tpos {%.9f,%.9f,%.9f}\n" % (posx, posy, posz))
1488                 ##        #rotation
1489                 ##        rot = Euler()
1490                 ##        rot[0] = 0
1491                 ##        for FCurve in Action.fcurves:
1492                 ##            if FCurve.data_path == "rotation_euler" and FCurve.array_index == 1: rot[0] = FCurve.evaluate(KeyframeTime)
1493                 ##        rot[1] = 0
1494                 ##        for FCurve in Action.fcurves:
1495                 ##            if FCurve.data_path == "rotation_euler" and FCurve.array_index == 2: rot[1] = FCurve.evaluate(KeyframeTime)
1496                 ##        rot[2] = 0
1497                 ##        for FCurve in Action.fcurves:
1498                 ##            if FCurve.data_path == "rotation_euler" and FCurve.array_index == 3: rot[2] = FCurve.evaluate(KeyframeTime)
1499                 ##        rot = rot.to_quaternion()
1500                 ##        animFile.write("\t\trot {%.9f,%.9f,%.9f,%.9f}\n" % (rot[0], rot[1], rot[2], rot[3]))
1501                 ##        #scale
1502                 ##        scalex = 0
1503                 ##        for FCurve in Action.fcurves:
1504                 ##            if FCurve.data_path == "scale" and FCurve.array_index == 0: scalex = FCurve.evaluate(KeyframeTime)
1505                 ##        scaley = 0
1506                 ##        for FCurve in Action.fcurves:
1507                 ##            if FCurve.data_path == "scale" and FCurve.array_index == 1: scaley = FCurve.evaluate(KeyframeTime)
1508                 ##        scalez = 0
1509                 ##        for FCurve in Action.fcurves:
1510                 ##            if FCurve.data_path == "scale" and FCurve.array_index == 2: scalez = FCurve.evaluate(KeyframeTime)
1511                 ##        animFile.write("\t\t//scale {%.9f,%.9f,%.9f}\n" % (scalex, scaley, scalez))
1512                 ##        #keyframe done
1513                 ##        animFile.write("\t}\n")
1514                 ##    animFile.write("}\n")
1515                 ##    animFile.close()
1516                 """
1517             else:
1518                 if Config.Verbose:
1519                     print("    Object %s has no useable animation data." % (StripName(Object.name)))
1520
1521             if Config.ExportArmatures and Object.type == "ARMATURE":
1522                 if Config.Verbose:
1523                     print("    Writing Armature Bone Animation Data...\n")
1524                 PoseBones = Object.pose.bones
1525                 Bones = Object.data.bones
1526                 #riged bones animated 
1527                 #build key frame time list
1528                 keyframeTimes = set()
1529                 if Config.ExportAnimation==1:
1530                     # Exports only key frames
1531                     for FCurve in Action.fcurves:
1532                         for Keyframe in FCurve.keyframe_points:
1533                             if Keyframe.co[0] < Scene.frame_start:
1534                                 keyframeTimes.add(Scene.frame_start)
1535                             elif Keyframe.co[0] > Scene.frame_end:
1536                                 keyframeTimes.add(Scene.frame_end)
1537                             else:
1538                                 keyframeTimes.add(int(Keyframe.co[0]))
1539                 else:
1540                     # Exports all frame
1541                     keyframeTimes.update(range(Scene.frame_start, Scene.frame_end + 1, 1))
1542                    
1543                 keyframeTimes = list(keyframeTimes)
1544                 keyframeTimes.sort()
1545                 if Config.Verbose:
1546                     print("Exporting frames: ")
1547                     print(keyframeTimes)
1548                     if (Scene.frame_preview_end > Scene.frame_end):
1549                         print(" WARNING: END Frame of animation in UI preview is Higher than the Scene Frame end:\n Scene.frame_end %d versus Scene.frame_preview_end %d.\n"
1550                               % (Scene.frame_end, Scene.frame_preview_end))
1551                         print(" => You might need to change the Scene End Frame, to match the current UI preview frame end...\n=> if you don't want to miss end of animation.\n")
1552
1553                 if len(keyframeTimes):
1554                     #Create the anim file
1555                     animfullname = os.path.dirname(Config.FilePath) + "\\anims\\%s.anim" % (StripName(Object.name))
1556                     ensure_dir(animfullname)
1557                     if Config.Verbose:
1558                         print("      Creating anim file (bones animation) %s\n" % (animfullname))
1559                         print("      Frame count %d \n" % (len(keyframeTimes)))
1560                     animFile = open(animfullname, "w")
1561                     animFile.write('// anim file exported from : %r\n' % os.path.basename(bpy.data.filepath))   
1562                     animFile.write("CIwAnim\n")
1563                     animFile.write("{\n")
1564                     animFile.write("\tskeleton \"%s\"\n" % (StripName(Object.name)))
1565                     animFile.write("\t\t\n")
1566
1567                     Config.File.write("\t\".\\anims\\%s.anim\"\n" % (StripName(Object.name)))
1568
1569                     for KeyframeTime in keyframeTimes:
1570                         if Config.Verbose:
1571                             print("     Writing Frame %d:" % KeyframeTime)
1572                         animFile.write("\tCIwAnimKeyFrame\n")
1573                         animFile.write("\t{\n")
1574                         animFile.write("\t\ttime %.2f // frame num %d \n" % (KeyframeTime / Config.AnimFPS, KeyframeTime))
1575                         #for every frame write bones positions
1576                         Scene.frame_set(KeyframeTime)
1577                         for PoseBone in PoseBones:
1578                             if Config.Verbose:
1579                                 print("      Writing Bone: {}...".format(PoseBone.name))
1580                             animFile.write("\t\t\n")
1581
1582                             Bone = Bones[PoseBone.name]
1583                             WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, animFile, False)
1584                         #keyframe done
1585                         animFile.write("\t}\n")
1586                     animFile.write("}\n")
1587                     animFile.close()
1588             else:
1589                 if Config.Verbose:
1590                     print("    Object %s has no useable animation data." % (StripName(Object.name)))
1591         if Config.Verbose:
1592             print("  Done") #Done with Object
1593  
1594
1595                                 
1596  
1597 ################## Utilities
1598             
1599 def StripBoneName(name):
1600     return name.replace(" ", "")
1601
1602
1603 def StripName(Name):
1604     
1605     def ReplaceSet(String, OldSet, NewChar):
1606         for OldChar in OldSet:
1607             String = String.replace(OldChar, NewChar)
1608         return String
1609     
1610     import string
1611     
1612     NewName = ReplaceSet(Name, string.punctuation + " ", "_")
1613     return NewName
1614
1615
1616 def ensure_dir(f):
1617     d = os.path.dirname(f)
1618     if not os.path.exists(d):
1619         os.makedirs(d)
1620         
1621
1622 def CloseFile(Config):
1623     if Config.Verbose:
1624         print("Closing File...")
1625     Config.File.close()
1626     if Config.Verbose:
1627         print("Done")
1628
1629
1630 CoordinateSystems = (
1631     ("1", "Left-Handed", ""),
1632     ("2", "Right-Handed", ""),
1633     )
1634
1635
1636 AnimationModes = (
1637     ("0", "None", ""),
1638     ("1", "Keyframes Only", ""),
1639     ("2", "Full Animation", ""),
1640     )
1641
1642 ExportModes = (
1643     ("1", "All Objects", ""),
1644     ("2", "Selected Objects", ""),
1645     )
1646
1647 MergeModes = (
1648     ("0", "None", ""),
1649     ("1", "Merge in one big Mesh", ""),
1650     ("2", "Merge in unique Geo File containing several meshes", ""),
1651     )
1652
1653
1654 from bpy.props import StringProperty, EnumProperty, BoolProperty, IntProperty
1655
1656
1657 class MarmaladeExporter(bpy.types.Operator):
1658     """Export to the Marmalade model format (.group)"""
1659
1660     bl_idname = "export.marmalade"
1661     bl_label = "Export Marmalade"
1662
1663     filepath = StringProperty(subtype='FILE_PATH')
1664      #Export Mode
1665     ExportMode = EnumProperty(
1666         name="Export",
1667         description="Select which objects to export. Only Mesh, Empty, " \
1668                     "and Armature objects will be exported",
1669         items=ExportModes,
1670         default="1")
1671
1672     MergeModes = EnumProperty(
1673         name="Merge",
1674         description="Select if objects should be merged in one Geo File (it can be usefull if a scene is done by several cube/forms)." \
1675                     "Do not merge rigged character that have an armature.",
1676         items=MergeModes,
1677         default="0")
1678     
1679     #General Options
1680     Scale = IntProperty(
1681         name="Scale Percent",
1682         description="Scale percentage applied for export",
1683         default=100, min=1, max=1000)
1684     
1685     FlipNormals = BoolProperty(
1686         name="Flip Normals",
1687         description="",
1688         default=False)
1689     ApplyModifiers = BoolProperty(
1690         name="Apply Modifiers",
1691         description="Apply object modifiers before export",
1692         default=False)
1693     ExportVertexColors = BoolProperty(
1694         name="Export Vertices Colors",
1695         description="Export colors set on vertices, if any",
1696         default=True)
1697     ExportMaterialColors = BoolProperty(
1698         name="Export Material Colors",
1699         description="Ambient color is exported on the Material",
1700         default=True)
1701     ExportTextures = BoolProperty(
1702         name="Export Textures and UVs",
1703         description="Exports UVs and Reference external image files to be used by the model",
1704         default=True)
1705     CopyTextureFiles = BoolProperty(
1706         name="Copy Textures Files",
1707         description="Copy referenced Textures files in the models\\textures directory",
1708         default=True)
1709     ExportArmatures = BoolProperty(
1710         name="Export Armatures",
1711         description="Export the bones of any armatures to deform meshes",
1712         default=True)
1713     ExportAnimation = EnumProperty(
1714         name="Animations",
1715         description="Select the type of animations to export. Only object " \
1716                     "and armature bone animations can be exported. Full " \
1717                     "Animation exports every frame",
1718         items=AnimationModes,
1719         default="1")
1720     if bpy.context.scene:
1721         defFPS = bpy.context.scene.render.fps
1722     else:
1723         defFPS = 30                 
1724     AnimFPS = IntProperty(
1725         name="Animation FPS",
1726         description="Frame rate used to export animation in seconds (can be used to artficially slow down the exported animation, or to speed up it",
1727         default=defFPS, min=1, max=300)
1728
1729     #Advance Options
1730     Optimized = BoolProperty(
1731         name="Optimized the Vertices count",
1732         description="Optimize the vertices counts, uncheck if you fill that exported normals or vertex colors are not suitable",
1733         default=True)
1734      
1735     CoordinateSystem = EnumProperty(
1736         name="System",
1737         description="Select a coordinate system to export to",
1738         items=CoordinateSystems,
1739         default="1")
1740     
1741     Verbose = BoolProperty(
1742         name="Verbose",
1743         description="Run the exporter in debug mode. Check the console for output",
1744         default=True)
1745
1746     def execute(self, context):
1747         #Append .group
1748         FilePath = bpy.path.ensure_ext(self.filepath, ".group")
1749
1750         Config = MarmaladeExporterSettings(context,
1751                                          FilePath,
1752                                          CoordinateSystem=self.CoordinateSystem,
1753                                          Optimized=self.Optimized,
1754                                          FlipNormals=self.FlipNormals,
1755                                          ApplyModifiers=self.ApplyModifiers,
1756                                          Scale=self.Scale,
1757                                          AnimFPS=self.AnimFPS,
1758                                          ExportVertexColors=self.ExportVertexColors,
1759                                          ExportMaterialColors=self.ExportMaterialColors,
1760                                          ExportTextures=self.ExportTextures,
1761                                          CopyTextureFiles=self.CopyTextureFiles,
1762                                          ExportArmatures=self.ExportArmatures,
1763                                          ExportAnimation=self.ExportAnimation,
1764                                          ExportMode=self.ExportMode,
1765                                          MergeModes=self.MergeModes,
1766                                          Verbose=self.Verbose)
1767
1768         # Exit edit mode before exporting, so current object states are exported properly.
1769         if bpy.ops.object.mode_set.poll():
1770             bpy.ops.object.mode_set(mode='OBJECT')
1771
1772         ExportMadeWithMarmaladeGroup(Config)
1773         return {"FINISHED"}
1774
1775     def invoke(self, context, event):
1776         if not self.filepath:
1777             self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".group")
1778         WindowManager = context.window_manager
1779         WindowManager.fileselect_add(self)
1780         return {"RUNNING_MODAL"}
1781
1782
1783 def menu_func(self, context):
1784     self.layout.operator(MarmaladeExporter.bl_idname, text="Marmalade cross-platform Apps (.group)")
1785
1786
1787 def register():
1788     bpy.utils.register_module(__name__)
1789
1790     bpy.types.INFO_MT_file_export.append(menu_func)
1791
1792
1793 def unregister():
1794     bpy.utils.unregister_module(__name__)
1795
1796     bpy.types.INFO_MT_file_export.remove(menu_func)
1797
1798
1799 if __name__ == "__main__":
1800     register()