~ubuntu-branches/ubuntu/trusty/blender/trusty

« back to all changes in this revision

Viewing changes to release/scripts/addons_contrib/io_export_marmalade.py

  • Committer: Package Import Robot
  • Author(s): Matteo F. Vescovi
  • Date: 2012-05-12 20:02:22 UTC
  • mfrom: (14.2.16 sid)
  • Revision ID: package-import@ubuntu.com-20120512200222-lznjs2cxzaq96wua
Tags: 2.63a-1
* New upstream bugfix release
  + debian/patches/: re-worked since source code changed

Show diffs side-by-side

added added

removed removed

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