~siretart/ubuntu/utopic/blender/libav10

« back to all changes in this revision

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

  • Committer: Reinhard Tartler
  • Date: 2014-05-31 01:50:05 UTC
  • mfrom: (14.2.27 sid)
  • Revision ID: siretart@tauware.de-20140531015005-ml6druahuj82nsav
mergeĀ fromĀ debian

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