1
# ***** GPL LICENSE BLOCK *****
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.
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.
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 *****
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.
23
"name": "Marmalade Cross-platform Apps (.group)",
24
"author": "Benoit Muller",
26
"blender": (2, 63, 0),
27
"location": "File > Export > Marmalade cross-platform Apps (.group)",
28
"description": "Export Marmalade Format files (.group)",
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"}
37
from math import radians
40
from mathutils import Matrix
50
#Container for the exporter settings
51
class MarmaladeExporterSettings:
61
ExportVertexColors=True,
62
ExportMaterialColors=True,
64
CopyTextureFiles=True,
65
ExportArmatures=False,
66
ExportAnimationFrames=0,
67
ExportAnimationActions=0,
71
self.context = context
72
self.FilePath = FilePath
73
self.CoordinateSystem = int(CoordinateSystem)
74
self.FlipNormals = FlipNormals
75
self.ApplyModifiers = ApplyModifiers
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
91
def ExportMadeWithMarmaladeGroup(Config):
92
print("----------\nExporting to {}".format(Config.FilePath))
94
print("Opening File...")
95
Config.File = open(Config.FilePath, "w")
101
print("writing group header")
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))
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]
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]
119
print(" List: {}\nDone".format(Config.ExportList))
122
print("Setting up...")
124
if Config.ExportAnimationFrames:
126
print(bpy.context.scene)
127
print(bpy.context.scene.frame_current)
128
CurrentFrame = bpy.context.scene.frame_current
132
Config.ObjectList = []
134
print("Writing Objects...")
135
WriteObjects(Config, Config.ExportList)
140
print("Objects Exported: {}".format(Config.ExportList))
142
if Config.ExportAnimationFrames:
144
print("Writing Animation...")
145
WriteKeyedAnimationSet(Config, bpy.context.scene)
146
bpy.context.scene.frame_current = CurrentFrame
149
Config.File.write("}\n")
154
def GetObjectChildren(Parent):
155
return [Object for Object in Parent.children
156
if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}]
159
#Returns the file path of first image texture from Material.
160
def GetMaterialTextureFullPath(Config, 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"]
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'
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])
188
def WriteObjects(Config, ObjectList, geoFile=None, mtlFile=None, GeoModel=None, bChildObjects=False):
189
Config.ObjectList += ObjectList
191
if bChildObjects == False and Config.MergeModes > 0:
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))
197
for Object in ObjectList:
199
print(" Writing Object: {}...".format(Object.name))
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]
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)
210
print(" Creating skel file %s" % (skelfullname))
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)))
219
WriteArmatureParentRootBones(Config, Object, ParentList, skelFile)
221
skelFile.write("}\n")
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]
231
print(" Writing Children...")
232
WriteObjects(Config, ChildList, geoFile, mtlFile, GeoModel, True)
234
print(" Done Writing Children")
236
if Object.type == "MESH":
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")
248
Mesh = Object.to_mesh(bpy.context.scene, True, "PREVIEW")
250
Mesh = Object.to_mesh(bpy.context.scene, False, "PREVIEW")
253
print(" Writing Mesh...")
255
# Flip ZY axis (Blender Z up: Marmalade: Y up) ans Scale appropriately
256
X_ROT = mathutils.Matrix.Rotation(-math.pi / 2, 4, 'X')
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.
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
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)
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)
275
# manage merge options
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))
282
# Write the Mesh in the Geo file
283
WriteMesh(Config, Object, Mesh, geoFile, mtlFile, GeoModel)
285
if Config.MergeModes == 0:
286
# no merge so finalize the file, and discard the file and geo class
287
FinalizeGeoMtlFiles(Config, geoFile, mtlFile)
291
elif Config.MergeModes == 1:
292
# merge in one Mesh, so keep the Geo class and prepare to change object
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()
302
if Config.ApplyModifiers and Config.ExportArmatures:
303
bpy.data.objects.remove(Object2)
304
bpy.data.meshes.remove(Mesh)
307
print(" Done Writing Object: {}".format(Object.name))
309
if bChildObjects == False:
310
# we have finish to do all objects
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)
327
VertexList.append("\t\tvertWeights { %d, 1.0}" % i)
328
GeoModel.useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
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)
343
def CreateGeoMtlFiles(Config, Name):
345
geofullname = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "%s.geo" % Name
346
ensure_dir(geofullname)
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")
353
geoFile.write("\tname \"%s\"\n" % Name)
354
# add it to the group
355
Config.File.write("\t\".\models\%s.geo\"\n" % Name)
357
# Create the mtl file
358
mtlfullname = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "%s.mtl" % Name
359
ensure_dir(mtlfullname)
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
367
def FinalizeGeoMtlFiles(Config, geoFile, mtlFile):
369
print(" Closing geo file")
373
print(" Closing mtl file")
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")
383
print (" ERROR not GeoModel arguments in WriteMesh method")
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)
392
print(" Done\n Writing Mesh Materials...")
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)
401
if Config.ExportArmatures:
403
print(" Writing Mesh Weights...")
404
WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel)
409
###### optimized version fo Export, can be used also to merge several object in one single geo File ######
416
# -> List Vertex Colors
419
# -> Blender Material Object
420
# -> List Tris -> Stream Indices v,vn,uv0,uv1,vc
421
# -> List Quads -> Stream Indices v,vn,uv0,uv1,vc
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
428
__slots__ = "v", "vn", "uv0", "uv1", "vc"
430
def __init__(self, v, vn, uv0, uv1, vc):
439
#Store a Quad or a Tri in marmalade geo format : 3 or 4 CIndexList depending it is a Tri or a Quad
442
__slots__ = "pointsList",
447
def AddPoint(self, v, vn, uv0, uv1, vc):
448
self.pointsList.append( CGeoIndexList(v, vn, uv0, uv1, vc))
450
def PointsCount(self):
451
return len(self.pointsList)
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))
464
#Store all the poly (tri or quad) assigned to a Material in marmalade geo format
466
class CGeoMaterialPolys:
467
__slots__ = "name", "material", "quadList", "triList", "currentPoly"
469
def __init__(self, name, material=None):
471
self.material = material
474
self.currentPoly = None
477
self.currentPoly = CGeoPoly()
479
def AddPoint(self, v, vn, uv0, uv1, vc):
480
self.currentPoly.AddPoint(v, vn, uv0, uv1, vc)
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
489
def ClearPolys(self):
492
self.currentPoly = None
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)
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")
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")
517
#Store all the information on a Model/Mesh (vertices, normal, certcies color, uv0, uv1, TRI, QUAD) in marmalade geo format
520
__slots__ = ("name", "MaterialsDict", "vList", "vnList", "vcList", "uv0List", "uv1List",
521
"currentMaterialPolys", "vbaseIndex","vnbaseIndex", "uv0baseIndex", "uv1baseIndex",
522
"armatureObjectName", "useBonesDict", "mapVertexGroupNames", "armatureRootBone", "armatureRootBoneIndex", "skinnedVertices")
524
def __init__(self, name):
526
self.MaterialsDict = {}
532
self.currentMaterialPolys = None
533
#used xx baseIndex are used when merging several blender objects into one Mesh in the geo file (internal offset)
536
self.uv0baseIndex = 0
537
self.uv1baseIndex = 0
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 = []
552
def AddVertex(self, vertex):
553
self.vList.append(vertex.copy())
555
def AddVertexNormal(self, vertexN):
556
self.vnList.append(vertexN.copy())
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
563
def AddVertexUV1(self, u, v):
564
self.uv1List.append((u, v))
565
return len(self.uv1List) - 1 - self.uv1baseIndex
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)):
571
if col[0] == r and col[1] == g and col[2] == b and col[3] == a:
574
self.vcList.append((r, g, b, a))
575
return len(self.vcList)-1
577
def BeginPoly(self, MaterialName, material=None):
578
if MaterialName not in self.MaterialsDict:
579
self.currentMaterialPolys = CGeoMaterialPolys(MaterialName, material)
581
self.currentMaterialPolys = self.MaterialsDict[MaterialName]
582
self.currentMaterialPolys.BeginPoly()
584
def AddPoint(self, v, vn, uv0, uv1, vc):
588
vn += self.vnbaseIndex
590
uv0 += self.uv0baseIndex
592
uv1 += self.uv1baseIndex
594
self.currentMaterialPolys.AddPoint(v, vn, uv0, uv1, vc)
597
self.currentMaterialPolys.EndPoly()
598
self.MaterialsDict[self.currentMaterialPolys.name] = self.currentMaterialPolys
599
self.currentMaterialPolys = None
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)
608
def ClearAllExceptMaterials(self):
609
#used in Merge mode 2: one geo with several mesh
615
self.currentMaterialPolys = None
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 = []
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)))
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")
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")
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")
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")
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")
676
for GeoMaterialPolys in self.MaterialsDict.values():
677
GeoMaterialPolys.PrintMaterialPolys(geoFile)
678
geoFile.write("\t}\n")
680
def GetMaterialList(self):
681
return list(self.MaterialsDict.keys())
683
def GetMaterialByName(self, name):
684
if name in self.MaterialsDict:
685
return self.MaterialsDict[name].material
692
# iterates faces, vertices ... and store the information in the GeoModel container
693
def BuildOptimizedGeo(Config, Object, Mesh, GeoModel):
695
GeoModel = CGeoModel(filename, Object.name)
697
#Ensure tessfaces data are here
698
Mesh.update (calc_tessface=True)
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:
706
GeoModel.AddVertexNormal(Normal)
707
#Check if some colors have been defined
709
if Config.ExportVertexColors and (len(Mesh.vertex_colors) > 0):
710
vertexColors = Mesh.tessface_vertex_colors[0].data
712
#Check if some uv coordinates have been defined
714
if Config.ExportTextures and (len(Mesh.uv_textures) > 0):
715
for UV in Mesh.tessface_uv_textures:
717
UVCoordinates = UV.data
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
728
MeshColor = vertexColors[Face.index]
729
if len(Vertices) == 3:
730
FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3))
732
FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3, MeshColor.color4))
733
if Config.CoordinateSystem == 1:
734
FaceColors = FaceColors[::-1]
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)
740
colorIndex = list((-1,-1,-1,-1))
742
# stream for UV0 coordinates
744
uvFace = UVCoordinates[Face.index]
746
for uvVertex in uvFace.uv:
747
uvVertices.append(tuple(uvVertex))
748
if Config.CoordinateSystem == 1:
749
uvVertices = uvVertices[::-1]
751
for uvVertex in uvVertices:
752
index = GeoModel.AddVertexUV0(uvVertex[0], 1 - uvVertex[1])
753
uv0Index.append(index)
755
uv0Index = list((-1, -1, -1, -1))
757
# stream for UV1 coordinates
758
uv1Index = list((-1, -1, -1, -1))
761
# find the associated material
762
if Face.material_index < len(Mesh.materials):
763
mat = Mesh.materials[Face.material_index]
767
matName = "NoMaterialAssigned" # There is no material assigned in blender !!!, exporter have generated a default one
769
# now on the material, generates the tri/quad in v,vn,uv0,uv1,vc stream index
770
GeoModel.BeginPoly(matName, mat)
772
for i in range(0, len(Vertices)):
773
GeoModel.AddPoint(Vertices[i], Vertices[i], uv0Index[i], uv1Index[i], colorIndex[i])
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)
787
def WriteMaterial(Config, mtlFile, Material=None):
788
mtlFile.write("CIwMaterial\n")
791
mtlFile.write("\tname \"%s\"\n" % Material.name)
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)[:])
807
mtlFile.write("\tname \"NoMaterialAssigned\" // There is no material assigned in blender !!!, exporter have generated a default one\n")
810
if Config.ExportTextures:
811
Texture = GetMaterialTextureFullPath(Config, Material)
813
mtlFile.write("\ttexture0 .\\textures\\%s\n" % (bpy.path.basename(Texture)))
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)
823
print(" Copying the texture file %s ---> %s" % (Texture, textureDest))
824
shutil.copy(Texture, textureDest)
827
print(" CANNOT Copy texture file (not found) %s" % (Texture))
830
def GetFirstRootBone(ArmatureObject):
831
ArmatureBones = ArmatureObject.data.bones
832
ParentBoneList = [Bone for Bone in ArmatureBones if Bone.parent is None]
834
return ParentBoneList[0]
838
def GetVertexGroupFromBone(Object, Bone):
840
vertexGroupList = [VertexGroup for VertexGroup in Object.vertex_groups if VertexGroup.name == Bone.name]
842
return vertexGroupList[0]
846
def GetBoneListNames(Bones):
849
boneList.append(Bone.name)
850
boneList += GetBoneListNames(Bone.children)
854
def FindUniqueIndexForRootBone(Object, RootVertexGroup):
856
return RootVertexGroup.index
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)
863
def WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel):
864
ArmatureList = [Modifier for Modifier in Object.modifiers if Modifier.type == "ARMATURE"]
866
ArmatureObject = ArmatureList[0].object
867
if ArmatureObject is None:
869
RootBone = GetFirstRootBone(ArmatureObject)
870
RootVertexGroup = GetVertexGroupFromBone(Object, RootBone)
871
BoneNames = GetBoneListNames(ArmatureObject.data.bones)
873
GeoModel.armatureObjectName = StripName(ArmatureObject.name)
875
GeoModel.armatureRootBone = RootBone
876
GeoModel.armatureRootBoneIndex = FindUniqueIndexForRootBone(Object, RootVertexGroup)
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
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)
887
if Config.MergeModes != 1:
888
# write skin file directly
889
PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, StripName(Object.name))
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)
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)
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")
920
skinFile.write("}\n")
924
def AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, useBonesDict, mapVertexGroupNames, VertexIndex, RootBone, RootVertexGroup, BoneNames):
927
vertexGroupIndices = []
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
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,))
952
bWeightTotZero = False
954
if len(vertexGroupIndices) > 0:
955
vertexGroupIndices.sort();
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)
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)
969
VertexWeightString += ", %.7f" % (1.0 / len(vertexGroupIndices))
970
VertexWeightString += "}"
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"
977
#store in dictionnary information
978
if useBonesKey not in useBonesDict:
980
VertexList.append(VertexWeightString)
981
useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
983
pair_ListGroupIndices_ListAssignedVertices = useBonesDict[useBonesKey]
984
pair_ListGroupIndices_ListAssignedVertices[1].append(VertexWeightString)
985
useBonesDict[useBonesKey] = pair_ListGroupIndices_ListAssignedVertices
987
print ("ERROR Vertex %d is not skinned (it doesn't belong to any vertex group\n" % (VertexIndex))
991
############# ARMATURE: Bone export, and Bone animation export
994
def WriteArmatureParentRootBones(Config, Object, RootBonesList, skelFile):
996
if len(RootBonesList) > 1:
997
print(" /!\\ WARNING ,Marmelade need only one ROOT bone per armature, there is %d root bones " % len(RootBonesList))
1000
PoseBones = Object.pose.bones
1001
for Bone in RootBonesList:
1003
print(" Writing Root Bone: {}...".format(Bone.name))
1005
PoseBone = PoseBones[Bone.name]
1006
WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True)
1009
WriteArmatureChildBones(Config, Object, Bone.children, skelFile)
1012
def WriteArmatureChildBones(Config, Object, BonesList, skelFile):
1013
PoseBones = Object.pose.bones
1014
for Bone in BonesList:
1016
print(" Writing Child Bone: {}...".format(Bone.name))
1017
PoseBone = PoseBones[Bone.name]
1018
WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True)
1022
WriteArmatureChildBones(Config, Object, Bone.children, skelFile)
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
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")
1037
File.write("\t\tname \"%s\"\n" % StripBoneName(Bone.name))
1038
#get bone local matrix for rest pose
1040
File.write("\t\tparent \"%s\"\n" % StripBoneName(Bone.parent.name))
1041
localmat = Bone.parent.matrix_local.inverted() * Bone.matrix_local
1043
localmat = Bone.matrix_local
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
1051
ParentPoseBone = PoseBones[Bone.parent.name]
1052
localmat = ParentPoseBone.matrix.inverted() * PoseBone.matrix
1054
localmat = PoseBone.matrix
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
1064
localmat= X_ROT * armRot.to_matrix().to_4x4() * localmat #apply the armature rotation on the root bone
1067
loc = localmat.to_translation()
1068
quat = localmat.to_quaternion()
1071
loc.x *= (armScale.x * Config.Scale)
1072
loc.y *= (armScale.y * Config.Scale)
1073
loc.z *= (armScale.z * Config.Scale)
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))
1078
if isRestPoseNotAnimPose:
1082
def WriteKeyedAnimationSet(Config, Scene):
1083
for Object in [Object for Object in Config.ObjectList if Object.animation_data]:
1085
print(" Writing Animation Data for Object: {}".format(Object.name))
1087
if Config.ExportAnimationActions == 0 and Object.animation_data.action:
1088
actions.append(Object.animation_data.action)
1090
actions = bpy.data.actions[:]
1091
DefaultAction = Object.animation_data.action
1093
for Action in actions:
1094
if Config.ExportAnimationActions == 0:
1095
animFileName = StripName(Object.name)
1097
Object.animation_data.action = Action
1098
animFileName = "%s_%s" % (StripName(Object.name),StripName(Action.name))
1100
#Object animated (aka single bone object)
1101
#build key frame time list
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)
1113
keyframeTimes.add(int(Keyframe.co[0]))
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
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")
1135
## Config.File.write("\t\".\\anims\\%s_offset.anim\"\n" % animFileName))
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")
1146
## for FCurve in Action.fcurves:
1147
## if FCurve.data_path == "location" and FCurve.array_index == 0: posx = FCurve.evaluate(KeyframeTime)
1149
## for FCurve in Action.fcurves:
1150
## if FCurve.data_path == "location" and FCurve.array_index == 1: posy = FCurve.evaluate(KeyframeTime)
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))
1158
## for FCurve in Action.fcurves:
1159
## if FCurve.data_path == "rotation_euler" and FCurve.array_index == 1: rot[0] = FCurve.evaluate(KeyframeTime)
1161
## for FCurve in Action.fcurves:
1162
## if FCurve.data_path == "rotation_euler" and FCurve.array_index == 2: rot[1] = FCurve.evaluate(KeyframeTime)
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]))
1170
## for FCurve in Action.fcurves:
1171
## if FCurve.data_path == "scale" and FCurve.array_index == 0: scalex = FCurve.evaluate(KeyframeTime)
1173
## for FCurve in Action.fcurves:
1174
## if FCurve.data_path == "scale" and FCurve.array_index == 1: scaley = FCurve.evaluate(KeyframeTime)
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))
1180
## animFile.write("\t}\n")
1181
## animFile.write("}\n")
1186
print(" Object %s has no useable animation data." % (StripName(Object.name)))
1188
if Config.ExportArmatures and Object.type == "ARMATURE":
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)
1205
keyframeTimes.add(int(Keyframe.co[0]))
1208
keyframeTimes.update(range(Scene.frame_start, Scene.frame_end + 1, 1))
1210
keyframeTimes = list(keyframeTimes)
1211
keyframeTimes.sort()
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")
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)
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")
1234
Config.File.write("\t\".\\anims\\%s.anim\"\n" % animFileName)
1236
for KeyframeTime in keyframeTimes:
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:
1246
print(" Writing Bone: {}...".format(PoseBone.name))
1247
animFile.write("\t\t\n")
1249
Bone = Bones[PoseBone.name]
1250
WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, animFile, False)
1252
animFile.write("\t}\n")
1253
animFile.write("}\n")
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
1262
print(" Done") #Done with Object
1267
################## Utilities
1269
def StripBoneName(name):
1270
return name.replace(" ", "")
1273
def StripName(Name):
1275
def ReplaceSet(String, OldSet, NewChar):
1276
for OldChar in OldSet:
1277
String = String.replace(OldChar, NewChar)
1282
NewName = ReplaceSet(Name, string.punctuation + " ", "_")
1287
d = os.path.dirname(f)
1288
if not os.path.exists(d):
1292
def CloseFile(Config):
1294
print("Closing File...")
1300
CoordinateSystems = (
1301
("1", "Left-Handed", ""),
1302
("2", "Right-Handed", ""),
1306
AnimationFrameModes = (
1308
("1", "Keyframes Only", ""),
1309
("2", "Full Animation", ""),
1312
AnimationActions = (
1313
("0", "Default Animation", ""),
1314
("1", "All Animations", ""),
1318
("1", "All Objects", ""),
1319
("2", "Selected Objects", ""),
1324
("1", "Merge in one big Mesh", ""),
1325
("2", "Merge in unique Geo File containing several meshes", ""),
1329
from bpy.props import StringProperty, EnumProperty, BoolProperty, IntProperty
1332
class MarmaladeExporter(bpy.types.Operator):
1333
"""Export to the Marmalade model format (.group)"""
1335
bl_idname = "export.marmalade"
1336
bl_label = "Export Marmalade"
1338
filepath = StringProperty(subtype='FILE_PATH')
1340
ExportMode = EnumProperty(
1342
description="Select which objects to export. Only Mesh, Empty, " \
1343
"and Armature objects will be exported",
1347
MergeModes = EnumProperty(
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.",
1355
Scale = IntProperty(
1356
name="Scale Percent",
1357
description="Scale percentage applied for export",
1358
default=100, min=1, max=1000)
1360
FlipNormals = BoolProperty(
1361
name="Flip Normals",
1364
ApplyModifiers = BoolProperty(
1365
name="Apply Modifiers",
1366
description="Apply object modifiers before export",
1368
ExportVertexColors = BoolProperty(
1369
name="Export Vertices Colors",
1370
description="Export colors set on vertices, if any",
1372
ExportMaterialColors = BoolProperty(
1373
name="Export Material Colors",
1374
description="Ambient color is exported on the Material",
1376
ExportTextures = BoolProperty(
1377
name="Export Textures and UVs",
1378
description="Exports UVs and Reference external image files to be used by the model",
1380
CopyTextureFiles = BoolProperty(
1381
name="Copy Textures Files",
1382
description="Copy referenced Textures files in the models\\textures directory",
1384
ExportArmatures = BoolProperty(
1385
name="Export Armatures",
1386
description="Export the bones of any armatures to deform meshes",
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,
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,
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)
1408
CoordinateSystem = EnumProperty(
1410
description="Select a coordinate system to export to",
1411
items=CoordinateSystems,
1414
Verbose = BoolProperty(
1416
description="Run the exporter in debug mode. Check the console for output",
1419
def execute(self, context):
1421
FilePath = bpy.path.ensure_ext(self.filepath, ".group")
1423
Config = MarmaladeExporterSettings(context,
1425
CoordinateSystem=self.CoordinateSystem,
1426
FlipNormals=self.FlipNormals,
1427
ApplyModifiers=self.ApplyModifiers,
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)
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')
1445
ExportMadeWithMarmaladeGroup(Config)
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'}
1456
def menu_func(self, context):
1457
self.layout.operator(MarmaladeExporter.bl_idname, text="Marmalade cross-platform Apps (.group)")
1461
bpy.utils.register_module(__name__)
1463
bpy.types.INFO_MT_file_export.append(menu_func)
1467
bpy.utils.unregister_module(__name__)
1469
bpy.types.INFO_MT_file_export.remove(menu_func)
1472
if __name__ == "__main__":